7-5 构造函数再探
目录
- 7.5.1 构造函数初始化值列表
- 初始化与赋值
- 初始化顺序
- 默认实参和构造函数
- 7.5.2 委托构造函数
- 7.5.3 隐式的类类型转换
- 机制
- 只允许一步类型转换
- 抑制转换 : explicit
- 7.5.4 类的静态成员
- 使用
- 初始化
- 静态成员可以适用于某些特殊场景
7.5.1 构造函数初始化值列表
初始化与赋值
观察下面两段构造函数的代码
//初始化bookNo,units_sold,revenue
Sale_data(const String &s, unsigned cnt, double price) :
bookNo(s), units_sold(cnt), revenue(cnt*price) {};
//对bookNo,units_sold,revenue赋值
Sale_data(const String &s, unsigned cnt, double price){
bookNo = s;
units_sold = cnt;
revenue = cnt * price;
}
-
第一种写法是对各成员进行初始化
-
第二种写法本质上是先对各成员进行默认初始化,再对其赋值
有些时候必须用初始化,而不能用赋值
#include
using namespace std;
struct X{
const int i;
int &ir;
//正确:对const int 和 int& 初始化
X(int ii) : i(ii), ir(ii) {};
X(int ii){
i = ii; //错误:不能对const int 赋值
ir = ii; //错误:int &没被初始化
}
};
如果类成员是const, 引用或其他必须初始化(没有默认构造函数)的类型,那么必须用构造函数初始值列表进行初始化
初始化顺序
初始化的顺序由声明顺序决定,与构造函数初始值列表无关
#include
using namespace std;
class X{
public :
int i;
int j;
X(int val = 1) : j(val), i(j) {};
};
int main(){
X x(1);
//输出:0 , 1
cout<
X(int val = 1) : j(val), i(j)
看似是用val先初始化j,在用j初始化i。
但实际上是先用未定义的j值初始化i,然后用val初始化j
启发:
- 构造函数初始值列表的初始化顺序要与声明顺序一致
- 避免用成员初始化成员
默认实参和构造函数
下面两段代码等价
//直接手动添加默认构造函数
class X{
public :
int i;
X() = default;
X(int ii) : i(ii) {};
};
//采用默认实参的构造函数可以充当默认构造函数使用
class X{
public :
int i;
X(int ii = 0) : i(ii) {};
};
7.5.2 委托构造函数
class Sale_data{
public :
//非委托的构造函数
Sale_data(string s, unsigned cnt, double price) :
bookNo(s), units_sold(cnt), revenue(cnt*price) {}
//委托的构造函数
Sale_data() : Sale_data(" ", 0, 0) {}
Sale_data(string &s) : Sale_data(s, 0, 0) {}
Sale_data(istream &io) : Sale_data(){
read(io, *this);
}
private :
string bookNo;
unsigned units_sold;
double revenue;
};
Sale_data()
和Sale_data(string s)
委托给Sale_data(string s, unsigned cnt, double price)
Sale_data(istream &io)
委托给Sale_data()
,Sale_data()
再委托给Sale_data(string s, unsigned cnt, double price)
7.5.3 隐式的类类型转换
机制
如果一个构造函数只接受一个参数,那么它实际上定义了一个该参数转换为类类型的隐式转换机制
#include
using namespace std;
class X{
public :
X() = default;
X(string ss) : s(ss) {}
//此处的参数必须为const,否则无法接受 常量
X &combine(const X &y){
s += y.s;
return *this;
}
string s;
};
int main(){
X x("hello ");
string str = "world!";
//str被隐式转换为X
x.combine(str);
//输出 hello world!
cout<
只允许一步类型转换
X.combine("world!")
是错误的,因为“world!”先转换为了string,string又转化为X,包含了两步类型转换。
可以显示强制转换
x.combine((string)"world!"); //显式转换为string,隐式转换为X
x.combine((X)"world !"); //显式转换为X
抑制转换 : explicit
抑制构造函数定义的隐式类型转换:将构造函数声明为explicit
explicit X(string ss) : s(ss) {}
explicit只能出现在函数的声明处,不能出现在定义处
不能将explicit构造函数用于拷贝形式的初始化过程P265
7.5.4 类的静态成员
用static
关键字声明类的静态成员,它与类直接关联,对所有类的对象可见,被所有对象共享,但不属于任何一个对象。
- 因为不属于任何一个对象,所以不包含this指针
- 不能声明为const
- static关键字只能出现在类内部的声明中,而static函数的定义可内可外
- static成员的声明周期是整个程序的生命周期
使用
class Account{
public :
static double rate() {return 3.14;}
//...
};
-
用类名访问
double r = Account::rate();
-
用类的对象或对象的引用或指针访问
Account ac1,&ac2; Account *ac3 = &ac1; r = ac1.rate(); r = ac2.rate(); r = ac3->rate();
初始化
-
外部初始化
class X{ public : //... static int m; }; int X::m = 1;
-
内部初始化:constexpr
class X{ public : //... static constexpr int m = 1; }; constint X::m; //即使在内部初始化一般也得在外部声明一下
静态成员可以适用于某些特殊场景
-
一般成员的类型不能是其所属的类的类型,最多只能是其所属类的类型的指针或引用,而静态成员可以
-
静态成员可以作为默认实参