为什么对于类的const成员,只能使用初始化列表,而不能在构造函数内部进行赋值操作

结论:对于类的const成员,只能使用初始化列表,而不能在构造函数内部进行赋值操作。

 

原因如下:

1、构造函数不能被声明为const函数,因此当我们创建一个类的const对象时,直到构造函数完成初始化的过程,对象才真正取得其“常量”的属性,因此,构造函数在const对象的构造过程中可以向其写值;见C++ primer P235;

当我们创建一个类的const对象时,直到构造函数完成初始化的过程,对象才真正取得其“常量”的属性

>> 所以从这个角度讲,构造函数没必要声明为const函数,因为构造函数在进行进行const对象的成员初始化工作的时候,在构造函数看来该对象还仍然是非const的属性,所以构造函数也就没必要声明为const属性。

=======20210122更新begin======

经评论区提醒,上面这段并不能解释题目中的问题,甚至和题目中的问题没有任何关系。当初估计是因为被“const对象”“初始化”这些字眼搞晕了。。。但是这对于更深刻理解题目中要解释的东西是有用的。

mark一下思考过程:

①const对象的初始化和class的const成员这两者压根不是一个层次的东西。你可以这么认为:const对象里面包含有const成员函数和非const成员函数。const对象只能调用const成员函数的原因就在于只有const成员函数能保证不改变其成员变量的值;

②要解释题目中的问题,主要弄清楚初始化和赋值的区别就可以了。如下面2所说:初始化列表对成员变量做的事情叫做初始化,而构造函数体内对成员变量做的事情就叫做赋值了。

③再举例解释一下初始化和赋值的区别就是:

int i = 0; //这是初始化

i = 2;  //这是赋值

上面的例子对应到instance class生成对象的过程就是(假设存在class Foo):

Foo foo;  //这是初始化;

Foo foo2;//这是初始化;

foo = foo2;  //这是赋值;

上面这三行解释了构造函数不能被声明为const函数的原因,因为Foo foo;这句会调用构造函数来完成foo这个对象初始化的过程(注意,整个构造函数的执行过程--包括初始化列表以及构造函数体内--都属于该对象初始化的过程)。这个过程结束之后,对象才取得其常量的属性。

④更进一步,考虑下面的例子:

const Foo foo; 

Foo foo2;

foo = foo2;  //;这句通过编译的前提条件是存在一个 const类型的copy assignment operator overload,即类似:Foo operator= (Foo foo) const { ...; }  的东西;从另外一个角度讲,const成员函数又是不能改变成员变量的值的,这直接导致了operator=失去了其本来的意义。

⑤、第④点有点儿乱,可以看一下例子更直观一些:https://codechina.csdn.net/baidu_35679960/cpp-example, const_object.cpp能通过编译可以运行,const_object_2.cpp build failed;

=======20210122更新end======

2、初始化类的成员有两种方式,一是使用初始化列表,二是在构造函数体内进行赋值操作。因此,由于常量只能初始化不能赋值,所以常量成员必须使用初始化列表;[1]

(当然你可以在类定义的时候,就对const成员变量进行赋值: class A{const int a = 1;};但是这样操作的话,这个变量就失去了意义,即基于这个类生成的所有对象a的值都为1!
>>> 20200813, 更正,理论上定义class的时候不能够对其中的成员变量赋值,因为定义一个class并不会为这个class分配空间,是只有在instance class时候才会分配空间,既然没有空间,当然不能赋值。所以对于const变量的初始化只能使用初始化列表。

再更进一步的讲,其实非const变量也可以在类定义的时候就进行赋值的操作,但是static变量不可以!)
>>> 20200826, 对于static 变量,是所有的对象共享同一个内存空间,从表面看起来好像是"static 对象属于class,而不是属于某个对象"。static 变量的初始化需要在类定义的外部显示的使用  变量类型 类名::变量名 = 0; 来进行初始化 。对于static const 变量可以使用类似的方法来初始化。

3、主要是性能问题,对于内置类型,如int, float等,使用初始化类表和在构造函数体内初始化差别不是很大,但是对于类类型来说,最好使用初始化列表,为什么呢?使用初始化列表少了一次调用默认构造函数的过程,这对于数据密集型的类来说,是非常高效的。[1]

 

#include <iostream>

using namespace std;

class Test1
{
public:
    Test1() {cout << "Construct Test1" << endl ;} // 无参构造函数
    Test1(const Test1& t1) // 拷贝构造函数
    {
        cout << "Copy constructor for Test1" << endl ;this->a = t1.a ;
    }
    Test1& operator = (const Test1& t1) // 重载赋值运算符(也称为重载赋值函数)
    {
        cout << "assignment for Test1" << endl ;
        this->a = t1.a ;
        return *this;
    }
private:
    int a ;
};

class Test2
{
public:
    Test1 test1; //此处第二次调用Test 1的construct;
    //std::cout<<"进入Test2 的构造函数"<<std::endl; //类中只能包含成员变量和成员函数!
    Test2(Test1 &t1){test1 = t1 ;} //此处的“=”调用重载的“=”操作符;
};

int main(){
    Test1 t1; //此处第一次调用Test1的construct;
    cout<<"end of fist construct t1"<<endl;
    Test2 t2(t1);
}

 

输出为:

Construct Test1
end of fist construct t1
Construct Test1
assignment for Test1

解释一下:
第一行输出对应调用代码中第一行,构造一个Test1对象
第三行输出对应Test2构造函数中的代码,用默认的构造函数初始化对象test1,这就是所谓的初始化阶段。

此时,如果test1没有默认的构造函数怎么办?

>> 会报错:
main.cpp: In constructor 'Test2::Test2(Test1&)':
main.cpp:27: error: no matching function for call to 'Test1::Test1()'
main.cpp:9: note: candidates are: Test1::Test1(const Test1&)
main.cpp: In function 'int main()':
main.cpp:31: error: no matching function for call to 'Test1::Test1()'
main.cpp:9: note: candidates are: Test1::Test1(const Test1&)

第四行输出对应Test2的赋值运算符,对test1执行赋值操作,这就是所谓的计算阶段

 

如果使用初始化列表来实现Test2的构造函数;

class Test2
{
public:
    Test1 test1 ;
    Test2(Test1 &t1):test1(t1){}
};

输出为:

Construct Test1
end of fist construct t1
Copy constructor for Test1

 

第一行输出对应 调用代码的第一行
第三行输出对应Test2的初始化列表,直接调用拷贝构造函数初始化test1,省去了调用默认构造函数的过程。
所以一个好的原则是,能使用初始化列表的时候尽量使用初始化列表

 

参考:

[1] 初始化列表

  • 13
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值