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
};
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐