当前位置:   article > 正文

C++知识点12——构造函数初步_为什么类中const不能初始化

为什么类中const不能初始化

构造函数就是定义了类的对象的初始化方式的函数,在初始化类的对象时,会被自动调用

构造函数无返回值,可以被重载(有多个构造函数,可以有多种初始化的方式,参考C++知识点4——vector与string简述

  1. class A
  2. {
  3. public:
  4. A(int a) {cout<<"a constructor"<<endl;}
  5. A(){cout<<__func__<<endl;}
  6. ~A() {}
  7. };
  8. int main(int argc, char const *argv[])
  9. {
  10. A a;
  11. A b(10);
  12. return 0;
  13. }

 

注意:

注意区分下面两行代码

  1. A a;
  2. A a();

第一个是执行默认初始化,而第二个是声明了一个函数,如果将main函数中默认初始化改为第二种形式,程序不会打印构造函数中的log

  1. int main(int argc, char const *argv[])
  2. {
  3. A a();//仅仅是声明了一个函数,内存中并没有a对象
  4. A b(10);
  5. return 0;
  6. }

上述代码没有生成a对象,所以,更不能用A a()来调用其他成员

 

构造函数不能是const的成员函数,因为在构造函数中有可能会改变对象的成员,如果又将构造函数设为const,那么对象将无法被初始化

  1. class A
  2. {
  3. public:
  4. A() const {}
  5. ~A() {}
  6. };

 

如果类中没有自己定义构造函数,那么编译器会在必要的时候自动生成一个构造函数(A() {}),并且类的对象在在初始化时,执行默认初始化(用默认构造函数初始化对象)

但是,最好不要使用编译器提供的默认构造函数

  1. class A
  2. {
  3. public:
  4. int &b;
  5. };
  6. int main(int argc, char const *argv[])
  7. {
  8. A a;
  9. return 0;
  10. }

上述代码类中定义了一个引用,但是使用默认的构造函数并没有初始化引用,错过了引用初始化的唯一机会另外,导致程序报错,如果是类中定义了一个指针,也会造成未初始化的指针

 

如果想让错误消失,一种方法就是不定义引用,二是自己定义构造函数,下面代码展示了第二种办法

  1. class A
  2. {
  3. public:
  4. A():a(10), b(a){cout<<__func__<<endl;}
  5. int &b;
  6. int a;
  7. };
  8. int main(int argc, char const *argv[])
  9. {
  10. A a;
  11. cout<<a.b<<endl;
  12. return 0;
  13. }

冒号后面的就是初始化列表,用逗号隔开

 

构造函数的初始化列表与初始化顺序

尽可能使用初始化列表初始化变量,因为这是初始化成员变量的唯一机会,不要在构造函数中对成员变量进行赋值,因为有些变量无法赋值

  1. class A
  2. {
  3. public:
  4. A();
  5. const int a;
  6. };
  7. A::A()
  8. {
  9. a=10;
  10. cout<<__func__<<endl;
  11. }
  12. int main(int argc, char const *argv[])
  13. {
  14. A a;
  15. return 0;
  16. }

上述代码在编译时提示没有初始化const int,因为该成员唯一的初始化机会就是在构造函数的初始化列表中,当在构造函数中对a进行“初始化”时,其实是赋值操作,但是const变量无法重新赋值

正确做法就是将a在初始化列表中初始化

 

构造函数初始化成员变量的顺序是按照成员定义的顺序来初始化的

  1. class A
  2. {
  3. public:
  4. A();
  5. ~A() {}
  6. const int a;
  7. int b;
  8. int c;
  9. };
  10. A::A():a(10),b(5),c(1)
  11. {
  12. cout<<__func__<<endl;
  13. }
  14. int main(int argc, char const *argv[])
  15. {
  16. A a;
  17. cout<<a.a<<a.b<<a.c<<endl;
  18. return 0;
  19. }

上述代码中成员变量的初始化顺序是a,b,c(因为a最前,b次之,c最后),所以,在b初始化时,c中的值不确定,所以不能用c初始化b,将构造函数改成如下形式

  1. A::A():a(10),b(c),c(1)
  2. {
  3. cout<<__func__<<endl;
  4. }

可见,b的值是一个无效值,因为用c的无效值初始化

所以,在构造函数进行成员函数初始化时,最好不要让成员变量彼此初始化,如果必须这样,那么请注意成员函数的初始化顺序

 

explicit关键与构造函数

explicit关键字用来修饰构造函数,加上该关键字的构造函数不能执行隐式转换,具体例子见如下代码

  1. class A
  2. {
  3. public:
  4. /*explicit*/A(int a):mem(a) {cout<<"a constructor"<<endl;}
  5. A():mem(0){cout<<__func__<<endl;}
  6. ~A() {}
  7. A add(const A &one, const A & two);
  8. int mem;
  9. };
  10. A A::add(const A &one, const A & two)
  11. {
  12. A tmp;
  13. tmp.mem=one.mem+two.mem;
  14. return tmp;
  15. }
  16. int main(int argc, char const *argv[])
  17. {
  18. A one,two;
  19. one.mem=10;
  20. two.mem=20;
  21. A res1=one.add(one, two);
  22. cout<<res1.mem<<endl;
  23. A res2=one.add(10, 20);
  24. cout<<res2.mem<<endl;
  25. return 0;
  26. }

构造函数A(int a) 没有用explicit修饰,所以当执行第25行代码时,将10和20从int型数据隐式转化为两个类A的临时对象,并输出两次log,然后将这两个临时对象传入add函数

但是如果将构造函数A(int a)前添加explicit,那么上述代码就会报错,因为加上explicit关键字后,会防止上述过程

调用add时,提示函数匹配错误

 

此外,加上explicit修饰构造函数后,初始化对象只能用直接初始化,不能拷贝初始化

  1. class A
  2. {
  3. public:
  4. explicit A(int a):mem(a) {cout<<"a constructor"<<endl;}
  5. A():mem(0){cout<<__func__<<endl;}
  6. ~A() {}
  7. int mem;
  8. };
  9. int main(int argc, char const *argv[])
  10. {
  11. A one(10);
  12. A two=20;
  13. return 0;
  14. }

编译上述代码时,会提示转化错误

解决办法:

1.将explicit关键字去掉。2.将13行注释掉。3.使用static_cast转换,4。显式构造一个对象

解决办法3的代码

  1. int main(int argc, char const *argv[])
  2. {
  3. A one(10);
  4. A two=static_cast<A>(20);
  5. return 0;
  6. }

 

解决办法4的代码

  1. class A
  2. {
  3. public:
  4. explicit A(int a):mem(a) {cout<<"a constructor"<<mem<<endl;}
  5. A():mem(0){cout<<__func__<<endl;}
  6. ~A() {}
  7. A add(const A &one, const A & two);
  8. int mem;
  9. };
  10. A A::add(const A &one, const A & two)
  11. {
  12. cout<<__func__<<endl;
  13. A tmp;
  14. tmp.mem=one.mem+two.mem;
  15. return tmp;
  16. }
  17. int main(int argc, char const *argv[])
  18. {
  19. A res2=res2.add(A(10), A(20));//创建两个A的对象,调用顺序从右向左
  20. cout<<res2.mem<<endl;
  21. return 0;
  22. }

上述的输出结果为编译器优化的结果,关于编译器优化的具体内容见博客https://blog.csdn.net/davidhopper/article/details/90696200,写的很好

 

将上述代码关闭编译器优化的命令如下

 g++ -g -fno-elide-constructors -Wall constructor.cpp

输出结果如下

其中,调用了两次拷贝构造函数,第一次是函数add返回时调用的,第二次是res2初始化的时候调用的,因为在拷贝构造函数中没有进行赋值操作,所以打印结果是mem的初始值0

 

如果在拷贝构造函数中添加赋值操作

mem=t.mem;

打印结果如下

上过过程的讨论见https://bbs.csdn.net/topics/396829997

 

参考:

《C++ Primer》

https://blog.csdn.net/davidhopper/article/details/90696200

 

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/209041
推荐阅读
  

闽ICP备14008679号