深入探索c++构造函数之二:参数化列表初始化与赋值的区别
1、初始化与赋值的区别我们在定义变量时习惯于对其立即进行初始化,有时候也会先定义再赋值。由于赋值与初始化都是通过“=”来完成的,以至于很多时候我们忽略了二者的去呗,实际上这二者时候很大区别的。string foo="Hello World";//定义并进行初始化string bar;//默认初始化为空的string对象bar="Hello World";//为bar赋值//const修饰的变量、引
1、初始化与赋值的区别
我们在定义变量时习惯于对其立即进行初始化,有时候也会先定义再赋值。由于赋值与初始化都是通过“=”来完成的,以至于很多时候我们忽略了二者的去呗,实际上这二者时候很大区别的。
string foo="Hello World";//定义并进行初始化
string bar; //默认初始化为空的string对象
bar="Hello World"; //为bar赋值
//const修饰的变量、引用定义时必须进行初始化,不能赋值
const int a=3; //定义并进行初始化
const int b;//错误,定义时必须初始化
b=3;//错误,const型变量不能进行赋值操作
int c=3;
int cc=4;
int &rc=c;//rc为c的引用,定义时必须进行初始化
rc=cc;//错误,引用变量不能赋值
int &rc1;//错误,定义时没有初始化
就对象的数据成员而言,初始化与赋值也有类似的区别。如果没有在构造函数的初始值列表中显示的进行初始化成员,则该成员将在构造函数体执行执行进行默认初始化,然后在构造函数体中进行赋值操作。
Sales_data::Sales_data(const std::string &s,unsigned n,double p): //版本1
bookNo(s),units_sold(n),revenue(p*n){}//参数列表初始化
//构造函数的另外一种写法,没有使用构造函数的初始值
Sales_data::Sales_data(const std::string &s,unsigned n,double p)//版本2
//相当于省略了初始化列表,进行默认初始化。然后在函数体中进行赋值操作
{
bookNo=s;
units_sold=n;
revenue=p*n;
}
上面两段代码最后的结果都是相同的:当构造函数完成之后,数据成员的值都是相同的。区别是版本1初始化了他的数据成员,版本2对数据成员执行了赋值操作。虽然最终数据成员的值都是相同的,但是执行效率存在差异:
版本1:只进行了一次初始化操作
版本2:先进行一次初始化操作(默认初始化),然后进行赋值操作
从上面可以看出,版本2多了一次赋值操作,因此效率有所降低。
2、构造函数的初始值有时必不可少
有时候我们可以忽略成员函数初始化与赋值之间的差别,但是并非总能这样。前面已经提到,如果变量类型是const或者引用的话,必须在定义的时候进行初始化且不能进行赋值操作。同样,如果数据成员是const或者引用,必须将其初始化。类似的当成员属于某种类类型,且该类没有定义默认构造函数时,也必须将这个成员初始化。
(1)成员是const或引用,必须进行初始化而不能赋值
class ConstRef{
public:
constRef(int ii);
private:
int i;
const int ci;
int &ri;
};
ConstRef::ConstRef(int ii) //错误版本
//省略参数化列表
{
i=ii; //正确
ci=ii; //错误,不能给const赋值
ri=i; //错误,不能给ri赋值
}
ConstRef::ConstRef(int ii):i(ii),ci(ii),ri(i){}//正确版本
//显示初始化引用和const成员
在上面的例子中,由于数据成员是const和引用,因此,必须在定义的同时进行初始化,而不能在构造函数的函数体中进行赋值操作。
(2)成员为类类型且没有默认构造函数时必须被初始化
#include <iostream>
using namespace std;
class testA
{
public:
testA(int ii):i(ii){cout<<"testA inited :i="<<i<<endl;}
private:
int i;
};
class testB
{
public:
testB(double dd,testA AA)//这个构造函数是有问题的
//省略参数化列表,进行默认初始化
{
d=dd;
A=AA;
}
private:
double d;
testA A;
};
int main()
{
testA A1(12);
testB B(1,A1);//错误
return 0;
}
上面这段代码会产生编译错误,testB中有testA类型成员A,由于testB的构造函数省略了参数化列表,会进行默认初始化,因此testA类型成员会调用自身默认构造函数进行初始化,但是testA没有定义默认构造函数,故产生编译错误。
解决方法有两个:
(1)testB构造函数用参数话列表进行初始化
(2)为testA定义增加默认构造函数
//解决方法1:testB构造函数用参数化列表进行初始化
class testB
{
public:
testB(double dd,testA AA):d(dd),A(AA){}//参数化列表进行初始化
private:
double d;
testA A;
};
//解决方法2:testA增加默认构造函数
class testA
{
public:
testA(int ii):i(ii) {cout<<"testA inited :i="<<i<<endl;}
testA():i(0) {cout<<"testA inited :i=0"<<endl;}//增加默认构造函数
private:
int i;
};
最后,再次强调赋值和列表初始化的区别:
如果构造函数的函数体内通过赋值的方式让成员变量获取初值,在函数体执行之前会进行一次默认初始化(默认初始化规则见上一篇文章)。特别强调的时候,如果成员变量有类类型,默认初始化会调用类的默认构造函数,如果没有默认构造函数机会出错。
#include <iostream>
using namespace std;
class testA
{
public:
testA(int ii):i(ii) {cout<<"testA initialization :i="<<i<<endl;}
testA():i(0) {cout<<"testA default initialization :i=0"<<endl;}
private:
int i;
};
class testB
{
public:
testB(double dd,testA AA)
//相当于省略了初始化列表,会进行默认初始化
{//进行该函数体之前会进行默认初始化
d=dd;
A=AA;
}
private:
double d;
testA A;
};
int main()
{
testA A1(5);
testB B(3.14,A1);
return 0;
}
输出结果为:
testA initialization :i=5 //定义变量A1会输出该语句
testA default initialization :i=0 //testB构造函数函数体之前对成员A进行默认初始化
最后补充一点:
列表初始化个成员变量的初始化顺序
成员变量从初始化顺序是跟类中变量定义的先后顺序一致,与初始化列表无关,并且初始化列表中变量的顺序可以与定义的顺序不一致。
class Number
{
private:
//成员变量定义顺序为:i,d,f 初始化顺讯也为i -> d -> f
int i;
double d;
float f;
public:
Number(int ii,double dd,float ff):f(ff),i(ii),d(dd){}
//列表顺序为:f,i,d ,初始化顺序无关,初始化顺序仍然为i ,d,f
};
更多推荐
所有评论(0)