再谈构造函数
构造函数体赋值
class Date { public: Date(int year = 2023, int month = 10, int day = 30) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; };
我们通过构造函数可以给对象赋值,但是不能称为初始化,因为初始化只能初始化一次,而构造函数函数体中可以进行多次赋值。
初始化列表
class Date { public: Date(int year = 2023, int month = 10, int day = 30) :_year(year) ,_month(month) ,_day(day) {} private: int _year; int _month; int _day; };
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟
一个放在括号中的初始值或表达式。
下面的大括号里还可以进行一系列赋值或者其他操作,相当于是构造。
注意:
- 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)。
- 类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量
- const成员变量
- 自定义类型成员(且该类没有默认构造函数时)
class A { public: A(int a = 0) :_a(a) {} private: int _a; int _b; }; class B { public: B(int& a,int b,A& c) :_a(a) ,_b(b) ,_c(c) {} private: int& _a; const int _b; A _c; }; int main() { int a = 6; int b = 3; A c; B b(a,b,c); return 0; }
- f具有常属性,不可修改。
- 引用在声明时就需要初始化,而且之后不可以修改。
- 自定义类型在没有默认构造函数的时候,我们有没有给参数,或者说他在类里声明,我们都需要把他加进初始化列表中进行初始化。
3.尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,
一定会先使用初始化列表初始化。
其实也就是不管是内置类型还是自定义类型,都会先去调用他的默认构造或者构造函数,在进入构造函数前,先走初始化列表,不管我们是否写了初始化。
class A { public: A(int num = 0) :_a(num) {} A(const A& _A) { _a = _A._a; _b = _A._b; } int Getnum_a() { return _a; } private: int _a; int _b; }; int main() { //会先去调用默认构造函数,然后在构造之前走初始化列表 //将a._a初始化为0 A a; cout << a.Getnum_a() << endl; return 0; }
4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后
次序无关
class A { public: //这里初始化顺序不是按先写就先初始化 //而是按照声明顺序进行初始化 A(int a) :_a2(a) ,_a1(_a2) {} void print() const { cout << _a1 << " " << _a2 << endl; } private: int _a1; int _a2; }; int main() { A a(1); a.print(); return 0; }
explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数或者多个参数的构造函数,还可以通过赋值类型转换的方式构造对象。
解释在代码注释里
class Date { public: Date(int year) :_year(year) {} Date(int year, int month, int day) :_year(year) ,_month(month) ,_day(day) {} void print() const { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int main() { //C++支持单个参数的构造函数这样使用 //先用2023去构造临时对象,该临时对象再进行和对象b的拷贝构造 Date date1 = 2023; date1.print(); //C++11支持多参数这样使用,和上面是相同的道理 Date date2 = { 2023,10,30 }; date2.print(); return 0; }
class Date { public: Date(int year) :_year(year) {} Date(int year, int month, int day) :_year(year) ,_month(month) ,_day(day) {} void print() { cout << _year << "-" << _month << "-" << _day << endl; } void print() const { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date date1 = 2023; date1.print(); Date date2 = { 2023,10,30 }; date2.print(); const Date date3 = { 2023,10,31 }; date3.print(); return 0; }
但是这样写可读性不好,有没有看着别扭的感觉,所以如果我们不想让这样的方式能通过编译,就使用explicit关键字修饰该构造函数。
class Date { public: explicit Date(int year) :_year(year) {} explicit Date(int year, int month, int day) :_year(year) , _month(month) , _day(day) {} void print() { cout << _year << "-" << _month << "-" << _day << endl; } void print() const { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date date1 = 2023; date1.print(); Date date2 = { 2023,10,30 }; date2.print(); //用这三个数去构造一个临时对象,然后拷贝构造 const Date date3 = { 2023,10,31 }; date3.print(); return 0; }
加上explicit后就不允许这样构造对象了。
我们再举一个其他栗子:
class Date { public: Date(int year,int month,int day) :_year(year) ,_month(month) ,_day(day) { cout << "Date()" << endl; } private: int _year; int _month; int _day; }; int main() { const Date& a = { 2023,11,2 }; return 0; }
解释:我们用三个参数去构造一个临时对象,a就是这个临时变量的别名,如果我们不加const就会报错,因为临时变量具有常性,而我们先前说过权限放大和缩小的问题,一个const类型的对象被非const类型引用,会出现权限放大,所以我们要加上const。
Static成员
我们先来抛出一个问题,假如我们想要计算一共有几个对象被构造,那么如何计算?我们有如下几个方案,判断他们的可行性。
方案一:搞一个全局变量,在每一次的调用构造函数时++。
方案二:在类里定义一个计数的变量。
接下来我们来看他们是否可行:
先看方案一实现的代码:
#include <iostream> using namespace std; int count = 0; class A { public : A() :_a(1) ,_b(2) { count++; } void print() const { cout << _a << " " << _b << endl; } private: int _a; int _b; }; void func(A a) { //... } int main() { A a; func(a); return 0; }
结果不明确是因为我们使用了using namespce std;展开了该命名空间,而该命名空间里有函数的名字也叫做count,所以就出现了结果不明确,当然,我们可以换个名字,不是非要用count,而且,我不展开std命名空间不可以吗,我只展开部分,比如cout或者endl展开就好,难道不可以吗?是的,都可以。
接着看方案二:
class A { public: A() :_a(1) , _b(2) { count++; } void print() const { cout << _a << " " << _b << endl; } private: int _a; int _b; int count; }; void func(A a) { //... } int main() { A a; func(a); return 0; }
这样可以吗?显然不行,每个对象都有count,这样的话,不管哪个count,都只是1,都只构造一次。
那么有没有更好的解决方案呢?有的,就是我们接下来要说的static成员。
lesson3-C++类和对象(下)(二)+https://developer.aliyun.com/article/1393915