再谈构造函数
构造函数体赋值
在创建对象时,编译器会通过调用构造函数,给对象中的各个成员变量一个合适的初始值:
class Date
{
public:
// 构造函数
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
需要注意的是:虽然通过调用上述的构造函数后,对象中的每个成员变量都有了一个初始值,但是构造函数中的语句只能将其称作为赋初值,而不能称作为初始化。因为初始化只能初始化一次,而构造函数体内可以进行多次赋值。
class Date
{
public:
// 构造函数
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;// 第一次赋值
_year = 2024;// 第二次赋值
//...
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式。
class Date
{
public:
// 构造函数
Date(int year = 0, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
注意事项:
一、每个成员变量在初始化列表中只能出现一次
因为初始化只能进行一次,所以同一个成员变量在初始化列表中不能多次出现。
二、类中包含以下成员,必须放在初始化列表进行初始化:
1.引用成员变量
引用类型的变量在定义时就必须给其一个初始值,所以引用成员变量必须使用初始化列表对其进行初始化。
#include <iostream>
using namespace std;
class MyClass {
private:
int& ref; // 引用成员变量
public:
MyClass(int& r) : ref(r) { // 使用初始化列表初始化引用成员变量
}
void printRef() {
cout << "Ref: " << ref << endl;
}
};
int main() {
int value = 5;
MyClass obj(value);
obj.printRef(); // 输出: Ref: 5
return 0;
}
2.const成员变量
被const修饰的变量也必须在定义时就给其一个初始值,也必须使用初始化列表进行初始化。
#include <iostream>
using namespace std;
class MyClass {
private:
const int x; // const成员变量
public:
MyClass(int val) : x(val) { // 使用初始化列表初始化const成员变量
}
void printX() {
cout << "x: " << x << endl;
}
};
int main() {
MyClass obj(10);
obj.printX(); // 输出: x: 10
return 0;
}
3.自定义类型成员(该类没有默认构造函数)
若一个类没有默认构造函数,那么我们在实例化该类对象时就需要传参对其进行初始化,所以实例化没有默认构造函数的类对象时必须使用初始化列表对其进行初始化。
在这里再声明一下,默认构造函数是指不用传参就可以调用的构造函数:
1.我们不写,编译器自动生成的构造函数。
2.无参的构造函数。
3.全缺省的构造函数。
#include <iostream>
using namespace std;
class MyOtherClass {
private:
int value;
public:
MyOtherClass(int val) : value(val) { // 自定义类型的构造函数
}
void printValue() {
cout << "Value: " << value << endl;
}
};
class MyClass {
private:
MyOtherClass obj; // 自定义类型成员,该类没有默认构造函数
public:
MyClass(int val) : obj(val) { // 使用初始化列表初始化自定义类型成员
}
void printObjValue() {
obj.printValue();
}
};
int main() {
MyClass obj(5);
obj.printObjValue(); // 输出: Value: 5
return 0;
}
三、尽量使用初始化列表初始化
因为初始化列表实际上就是当你实例化一个对象时,该对象的成员变量定义的地方,所以无论你是否使用初始化列表,都会走这么一个过程(成员变量需要定义出来)。
类似于如下代码:
// 使用初始化列表
int a = 10
// 在构造函数体内初始化(不使用初始化列表)
int a;
a = 10;
2.对于自定义类型,使用初始化列表可以提高代码的效率
class Time
{
public:
Time(int hour = 0)
{
_hour = hour;
}
private:
int _hour;
};
class Test
{
public:
// 使用初始化列表
Test(int hour)
:_t(12)// 调用一次Time类的构造函数
{}
private:
Time _t;
};
对于以上代码,当我们要实例化一个Test类的对象时,我们使用了初始化列表,在实例化过程中只调用了一次Time类的构造函数。我们若是想在不使用初始化列表的情况下,达到我们想要的效果,就不得不这样写了:
class Time
{
public:
Time(int hour = 0)
{
_hour = hour;
}
private:
int _hour;
};
class Test
{
public:
// 在构造函数体内初始化(不使用初始化列表)
Test(int hour)
{ //初始化列表调用一次Time类的构造函数(不使用初始化列表但也会走这个过程)
Time t(hour);// 调用一次Time类的构造函数
_t = t;// 调用一次Time类的赋值运算符重载函数
}
private:
Time _t;
};
这时,当我们要实例化一个Test类的对象时,在实例化过程中会先在初始化列表时调用一次Time类的构造函数,然后在实例化t对象时调用一次Time类的构造函数,最后还需要调用了一次Time类的赋值运算符重载函数,效率就降下来了。
四、成员变量在类中声明的次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后顺序无关
举个例子:
#include <iostream>
using namespace std;
int i = 0;
class Test
{
public:
Test()
:_b(i++)
,_a(i++)
{}
void Print()
{
cout << "_a:" << _a << endl;
cout << "_b:" << _b << endl;
}
private:
int _a;
int _b;
};
int main()
{
Test test;
test.Print(); //打印结果test._a为0,test._b为1
return 0;
}
代码中,Test类构造函数的初始化列表中成员变量_b先初始化,成员变量_a后初始化,按道理打印结果test._a为1,test._b为0,但是初始化列表的初始化顺序是成员变量在类中声明次序,所以最终test._a为0,test._b为1。
explicit关键字
概念:
explicit关键字用于声明构造函数是显式的,禁止隐式转换。
特性:
- 防止编译器进行隐式类型转换。
- 适用于单参数的构造函数。
示例代码:
#include <iostream>
using namespace std;
class MyClass {
public:
int x;
// 显式构造函数
explicit MyClass(int val) : x(val) {
}
};
int main() {
MyClass obj = 5; // 编译错误,禁止隐式转换
MyClass obj2(5); // 正确,显式调用构造函数
return 0;
}
static成员
概念: static
成员属于类本身,而不是类的实例。这意味着无论创建了多少个类的实例,static
成员只有一份拷贝。它可以是静态数据成员(static data member)或静态成员函数(static member function)。
特性:
static
成员可以在不创建类的实例的情况下访问。- 它们被所有该类的实例共享。
static
数据成员在类内声明,但必须在类外进行定义和初始化。static
成员函数没有this
指针,因此不能直接访问非静态成员。
#include <iostream>
using namespace std;
class MyClass {
public:
static int staticData; // 静态数据成员的声明
static void staticFunction() { // 静态成员函数的定义
cout << "This is a static member function" << endl;
}
};
// 静态数据成员的定义和初始化
int MyClass::staticData = 0;
int main() {
MyClass::staticFunction(); // 访问静态成员函数
cout << "Static data: " << MyClass::staticData << endl; // 访问静态数据成员
return 0;
}
C++11中成员初始化的新玩法
在 C++11 中,引入了成员初始化列表(member initializer list)和委托构造函数(delegating constructors)。成员初始化列表允许在构造函数中对成员进行初始化,而不是在构造函数体内进行赋值。
示例代码:
#include <iostream>
using namespace std;
class MyClass {
private:
int x;
int y;
public:
MyClass(int a, int b) : x(a), y(b) {} // 成员初始化列表
MyClass(int a) : MyClass(a, 0) {} // 委托构造函数
};
int main() {
MyClass obj(5, 10);
MyClass obj2(7);
return 0;
}
友元
概念: 友元(friend)是一种C++机制,允许一个类或函数访问另一个类的私有成员。友元可以是函数、类或整个类,使得其它类或函数能够访问类的私有或受保护成员。
友元函数: 可以访问类的私有成员,但不是类的成员函数。
友元类: 整个类被声明为另一个类的友元,可以访问该类的私有成员。
示例代码:
#include <iostream>
using namespace std;
class B; // 前置声明
class A {
private:
int dataA;
public:
A(int x) : dataA(x) {}
friend class B; // B类是A类的友元
};
class B {
public:
void showData(A obj) {
cout << "Data in A: " << obj.dataA << endl; // 可以访问A的私有成员
}
};
int main() {
A objA(5);
B objB;
objB.showData(objA); // 通过B类访问A类的私有成员
return 0;
}
内部类
概念: 在一个类的内部定义的类被称为内部类。内部类拥有与外部类相同的访问权限,但外部类不能直接访问内部类的成员,除非使用对象实例。
特性:
- 内部类可以访问外部类的所有成员,包括私有成员。
- 内部类的对象必须通过外部类的对象来创建。
示例代码:
#include <iostream>
using namespace std;
class Outer {
private:
int outerData;
public:
Outer(int x) : outerData(x) {}
class Inner { // 内部类的定义
private:
int innerData;
public:
Inner(int y) : innerData(y) {}
void display(Outer obj) { // 内部类访问外部类成员
cout << "Outer data: " << obj.outerData << endl;
}
};
};
int main() {
Outer objOuter(10);
Outer::Inner objInner(20); // 内部类对象的创建
objInner.display(objOuter); // 通过外部类对象访问内部类的方法
return 0;
}