类的构造函数
2023年12月20日 2023年12月30日
constructor
妥善初始化类对象的每一个数据成员
与类同名, 无返回类型
不能声明为const
支持重载
1class T 2{ 3public: 4 T(); 5};
内置类型和复合类型视情况分合
定义构造函数
1struct Sales_data 2{ 3 Sales_data(istream &); 4 Sales_data() {} // 类内定义 5}; 6 7Sales_data::Sales_data(istream &is) // 类外定义 8{ 9 read(is, *this); 10}
const对象的创建
当我们构建一个具有顶层const的对象, 构造函数结束后,对象才具有顶层const
构造函数在const对象的构造过程中可以向其写值
构造函数和数据成员初始化
建议
- 如果数据成员满足以下条件, 建议为它们提供类内初始值, 或者在初始值列表中对其初始化
- 内置类型
- 具有顶层const
- 为左值引用
- 内置类型
- 对构造函数传参时, 实参一般不同于数据成员的类内初始值
在函数体执行之前, 已完成数据成员初始化. 按照数据成员在类中的出现顺序
函数体内对数据成员使用赋值运算符, 均为为赋值操作
默认构造函数
形参列表为空; 或者每个形参都有默认值的构造函数
默认初始化类对象时调用
如果类没有定义任何构造函数, 编译器会提供默认构造函数的合成版本
如果默认构造函数的合成版本非删除 delete
:
此时无初始值列表
数据成员初始化方式:
- 有类内初始值
- 无类内初始值, 默认初始化数据成员
如果在类中定义了其他的构造函数, 可以显式要求编译器提供默认构造函数的合成版本
-
默认构造函数的合成版本为内联
1class T 2{ 3public: 4 T() = default; 5};
-
默认构造函数的合成版本非内联
1class T 2{ 3public: 4 T(); 5}; 6 7T::T() = default;
显式要求编译器提供默认构造函数的合成版本为 =default
, 仍可能得到一个删除 delete
的默认构造函数的合成版本
1#include <iostream> 2 3using namespace std; 4 5class test 6{ 7public: 8 test(int aa) : a(aa) {} 9 int a; 10}; 11 12class Foo 13{ 14public: 15 Foo() = default; 16 void PrintFoo() { cout << a; } 17 18private: 19 int a; 20 test t; 21}; 22 23int main() 24{ 25 return 0; 26}
给出告警
warning: explicitly defaulted default constructor is implicitly deleted [-Wdefaulted-function-deleted] Foo() = default; ^ note: default constructor of 'Foo' is implicitly deleted because field 't' has no default constructor test t; ^ 1 warning generated.
编译器提供非删除 delete
的默认构造函数的合成版本的前提
所有类类型数据成员均可默认初始化, 即所属类拥有默认构造函数
或者, 对于那些没有默认构造函数的类类型数据成员,给出类内初始值
默认构造函数的合成版本为删除
delete
两种情形:
- 显式删除: 要求编译器如此
- 隐式删除: 存在数据成员无类内初始值, 又无法默认初始化的情形; 编译器提供的默认构造函数的合成版本为隐式删除
implicitly-delete
构造函数初始值列表
在定义时给出
使用给定值初始化数据成员
建议与数据成员在类中的出现顺序一致
委托构造函数
delegating constructor
初始值列表有且只有一个同名构造函数作为入口, 代为初始化对象:
- 初始值列表中不初始化数据成员
- 在初始值列表中调用其他构造函数, 且只调用一个
先执行被委托的构造函数的初始值列表和函数体. 之后把控制权交还给委托者的函数体
被委托的构造函数通常在初始值列表中初始化了所有数据成员,委托者只传递自己有的参数,其他数据成员使用默认值
示例
-
多个委托构造函数
1class Sales_data 2{ 3public: 4 Sales_data(string s, unsigned cnt, double price) : bookNo(s), units_sold(cnt), revenue(cnt*price) {} 5 6 // 以下均是委托构造函数 7 8 // 默认构造函数也可以是委托构造函数 9 Sales_data() : sales_data("", 0, 0) {} 10 11 Sales_data(string s) : sales_data(s, 0, 0) {} 12 13 Sales_data(istream &is) : Sales_data() { read(is, *this); } 14};
-
构造函数和被构造函数的调用顺序
1#include <iostream> 2#include <string> 3 4using std::cout; 5using std::endl; 6using std::ostream; 7using std::string; 8 9class GamePlayer 10{ 11 friend ostream &operator<<(ostream &os, const GamePlayer &gp); 12 13public: 14 GamePlayer(string _name, int _age) : name(_name), age(_age) { cout << "This is delegated constructor.\n"; } // 被委托的构造函数 15 GamePlayer() : GamePlayer("", 0) { cout << "This is default constructor, which is delegating.\n"; } 16 GamePlayer(string _name) : GamePlayer(_name, 0) { cout << "This is another delegating constructor.\n"; } 17 18private: 19 int age; 20 string name; 21}; 22 23ostream &operator<<(ostream &os, const GamePlayer &gp) 24{ 25 if (gp.name == "") 26 os << "none"; 27 else 28 os << gp.name; 29 os << '\t' << gp.age; 30 return os; 31} 32 33int main() 34{ 35 GamePlayer gp; 36 cout << gp << endl; 37 GamePlayer gp2("Kate", 25); 38 cout << gp2 << endl; 39 GamePlayer gp3("Mary"); 40 cout << gp3 << endl; 41 return 0; 42}
输出
This is delegated constructor. This is default constructor, which is delegating. none 0 This is delegated constructor. Kate 25 This is delegated constructor. This is another delegating constructor. Mary 0
转换构造函数
converting constructor
只接受一个实参, 或者除第一个参数外均有默认实参的构造函数
实质是定义了一条从构造函数的参数类型向类类型转换的规则
如果未使用关键字explicit,该规则可用于一次隐式转换
只允许一次隐式转换
- 函数F接受A类型变量
1void F(T a);
- A类型支持B到A的隐式转换
1void A(const B &b);
- B类型支持C到B的隐式转换
1void B(const C &c);
- 函数F可以接受B类型对象,不接受C类型对象
1A a; 2B b; 3C c; 4 5F(a); // 正确 6F(b); // 正确: 发生一次隐式转换, b转换为A类型对象 7F(c); // 错误: 隐式转换最多一次
explicit关键字
explicit只用于转换构造函数, 只在声明时给出
将转换构造函数声明为显式, 定义显式转换规则
要么显式调用转换构造函数,要么配合static_cast使用
不可用于隐式转换
- 函数F接受A类型变量
1void F(T a);
- A类型支持B到A的显式转换
1explicit void A(const B &b);
- 函数F不接受B类型对象
1A a; 2B b; 3 4F(a); // 正确 5F(b); // 错误: 不支持B到A的隐式转换 6F(static_cast<A>(b)); // 正确 7F(A(b)); // 正确
举例
接受const char *的string的构造函数不是explicit的
接受容器大小的构造函数是explicit的
示例
1class Sales_data 2{ 3public: 4 Sales_data() = default; 5 6 explicit Sales_data(const string &s) : bookNo(s) {} 7 explicit Sales_data(istream &); 8}; 9 10// 以下操作不被允许 11item.combine(null_book); 12item.combine(cin); 13 14// 可以使用显式转换 15item.combine(Sales_data(null_book)); 16item.combine(static_cast<Sales_data>(cin)); 17 18Sales_data item1(null_book); // 正确,是直接初始化 19Sales_data item2 = null_book; // 错误:不允许将explicit构造函数用于拷贝形式的初始化过程