【C++ Primer 第七章】 构造函数再探、委托构造函数

构造函数初始值列表

1.  构造函数的初始值有时必不可少

• 有时我们可以忽略数据成员初始化和赋值之间的差异,但并非总能这样。如果成员时const或者是引用的话,必须将其初始化。类似的,当成员属于某种类类型且该类没有定义默认构造函数时,也必须将这个成员初始化。

例如:

1 class ConstRef
2 {
3 public:
4     ConstRef(int ii);
5 private:
6     int i;
7     const int ci;
8     int &ri;
9 };

和其他常量对象或者引用一样,成员ci和ri都必须被初始化。因此,如果我们没有为它们提供构造函数初始值的话将引发错误:

1 //错误:ci和ri必须初始化
2
3 ConstRef::ConstRef(int ii)
4 {
5 //赋值
6     i=ii;  //正确
7     ci=ii;   //错误:不能给const赋值
8     ri=i;    //错误:ri没有初始化
9 }

随着构造函数体一开始执行,初始化就完成了。我们初始化const或者引用类型的数据成员的唯一机会就是通过构造函数初始值,因此该构造函数的正确形式应该是:

ConstRef::ConstRef(int ii): i(ii),ci(ii),ri(i) {}  //正确:显式地初始化引用和const成员

如果成员时const、引用,或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初值(因为如果进行赋值操作,首先要进行一次默认初始化,这样要使用都类类型的默认构造函数)。

2.  成员初始化顺序

• 成员的初始化顺序与它们在类定义中的出现顺序一致:第一个成员先把初始化,然后第二个,以此类推。构造函数初始值列表中初始值的前后位置关系不会影响实际的初始化顺序。

• 一般来说,初始化的顺序没什么特别要求。不过如果一个成员是用另一个成员来初始化的,那么这两个成员的初始化顺序就很关键了。

举个例子,考虑下面这个类:

class X
{

  int i;

  int j;

public:

  X(int val):j(val),i(j) {}    //未定义的,i在j之前被初始化

};

在此例中,从构造函数的形式上来仿佛是先用val初始化了j,然后再用j初始化i。实际上,i先把初始化,因此这个初始值的效果是试图使用未定义的j初始化i!

如果可能的话,最好用构造函数的参数作为成员的初始值,而尽量避免使用同一个对象的其他成员。这样的好处是我们可以不必考虑成员的初始化顺序。例如,X的构造函数如果写成如下的形式效果会更好:

X(int val):j(val),i(val) {}

3.  默认实参和构造函数

//定义默认构造函数,令其与只接受一个string实参的构造函功能相同

Sales_data() {}

等价于

Sales_data(string s=" "):bookNo(s) {}

当没有给定实参,或者给定了一个string实参时,两个版本的类创建了相同的对象。因为我们不提供实参也能调用上面含有一个参数的构造函数,所以该构造函数实际上为我们的类提供了默认构造函数。

• 如果一个构造函数为所有参数都提供了默认实参,则它实际上也定义了默认的构造函数。

委托构造函数

• C++11新标准扩展了构造函数初始值的功能,使得我们可以定义所谓的委托构造函数。一个委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程,或者说它把它自己的一些(或者全部)职责给了其他构造函数。

和其他构造函数一样,一个委托构造函数也有一个成员初始值的列表和一个函数体。在委托构造函数内,成员初始值列表只有一个唯一的入口,就是类名本身。和其他成员初始值一样,类名后面紧跟圆括号括起来的参数列表,参数列表必须与类中另一个构造函数匹配。

举个例子,我们使用委托构造函数重写Sales_data类,重写后的形式如下所示:

 1 class Sales_data
 2 {
 3 public:
 4     //非委托构造函数使用对应的实参初始化成员
 5     Sales_data(string s,unsigned cnt,double price):bookNo(s),units_sold(cnt),revenue(cnt*price) {}
 6
 7     //其余构造函数全都是委托给另一个构造函数
 8     //如果要在类的外面定义构造函数,则初始值列表在定义时写出,而不是在类内声明时写出,委托构造函数也是这样,在定义时写
 9     10     Sales_data():Sales_data(" ",0,0) {}
11     Sales_data(string s):Sales_data(s,0,0)  {}
12     Sales_data(istream &is):Sales_data()  {read(is,*this);}
13
14     //其他成员与之前的版本一致
15 };

原文地址:https://www.cnblogs.com/sunbines/p/8905833.html

时间: 2024-08-02 13:50:18

【C++ Primer 第七章】 构造函数再探、委托构造函数的相关文章

【C++ Primer 第10章】再探迭代器

反向迭代器 • 反向迭代器就是在容器中从尾元素向首元素反向移动的迭代器.对于反向迭代器,递增(以及递减)操作的含义会颠倒过来. • 递增一个反向迭代器(++it)会移动到前一个元素:递减一迭代器(--it)会移动到下一个元素. • 除了forward_list之外,其他容器都支持反向迭代器.我们可以通过调用rbegin.rcend.crbegin和crend成员函数来获得反向迭代器.这些成员函数返回指向容器尾元素和首元素之前一个位置的迭代器.与普通迭代器一样,反向迭代器也有const和非cons

C++primer 第七章 类(有关类的相关定义)

 类的基本思想:数据抽象和封装.数据抽象是一种依赖于接口和实现分离的编程技术.类的接口包括用户所能执行的操作:类的实现则包括类的数据成员.负责接口实现的函数体以及所需的各种私有函数.封装实现了类的接口和实现的分离(用自己的话讲,就是封装把类的实现隐藏了,别人已将功能的实现帮你完成了,你只需要直接调用).       1.定义抽象数据类型:        类作用域:类本身就是一个作用域.       编译器分两步处理类:首先编译成员的声明:然后才是成员函数体.       A.定义read和pri

【足迹C++primer】19、构造函数再探

首先说一下为什么要特别写一个文档来说明这个层次结构的事情,各位都在eclipse中使用java.io.File类读取过workspace中的文件,我相信在不了解eclipse目录层次结构的情况很少有人可以一次性把路径给写对了,为了避免以后再出现这种令人难堪的情况,还是编写这样一篇扫盲性的说明文档比较合适.当然重点绝不是上面的资源路径怎么编写,而是影响围绕整个工程的生命周期的构建活动. 首先看一下eclipse在默认情况下创建一个普通的Java Project的目录结构: 从上面可以看出来ecli

【C++ Primer 第七章】 默认构造函数的作用

默认构造函数的作用 1.  默认构造函数 C++ 默认构造函数是对类中的参数提供默认值的构造函数,一般情况下,是一个没有参数值的空函数,也可以提供一些的默认值的构造函数,如果用户没有定义构造函数,那么编译器会给类提供一个默认的构造函数,但是只要用户自定义了任意一个构造函数,那么编译器就不会提供默认的构造函数,这种情况下,容易编译报错,所以正确的写法就是用户在定义构造函数的时候,也需要添加一个默认的构造函数,这样就不会造成编译报错. 如:用户自定义的默认构造函数 class Test { publ

C++Primer 第七章

//1.定义在类内部的函数是隐式内联的. //2.默认情况下,this指针的类型是指向类类型非常量版本的常量指针.对于类的常量成员函数的声明方法是:将const放置于成员函数的参数列表后,用于修饰this指针,此时这个const是底层const. //3.编译器处理类的时候,先编译成员的声明,然后才轮到成员函数体.所以成员函数可以随意使用类的其他成员而无须在意这些成员出现的顺序. // 类的类型成员必须先定义后使用.类的类型成员也受访问说明符限制(public等).使用sizeof时候,类的类型

【C++ Primer 第七章】 隐式的类类型转换

转换构造函数 转换构造函数:如果构造函数只接受一个实参,则实际上定义了转换此类类型的隐式转换机制,有时我们把这种构造函数称作转换构造函数. 能通过一个实参调用的构造函数定义了一条从构造函数的参数类型向类类型隐式转换的规则. 例如,在Sales_data类中,接受string的构造函数和接受istream的构造函数分别定义了从这两种类型向Sales_data隐式转换的规则.也就是说,在需要使用Sales_data的地方,我们可以使用string或者istream作为替代: 构造函数: Sales_

C++primer第七章 函数

本章将介绍函数的定义和声明.然后具体分析三类特殊的函数:内联(inline)函数.类成员函数和重载函数. 7.1. 函数的定义 函数由函数名以及一组操作数类型唯一地表示.函数的操作数,也即形参,在一对圆括号中声明,形参与形参之间以逗号分隔.函数执行的运算在一个称为函数体的块语句中定义.每一个函数都有一个相关联的返回类型. 函数的调用 C++ 语言使用调用操作符(即一对圆括号)实现函数的调用.正如其他操作符一样,调用操作符需要操作数并产生一个结果.调用操作符的操作数是函数名和一组(有可能是空的)由

【C++ Primer 第七章】 【C++ Primer 第七章】 定义数据抽象类

Screen类 • 我们可以在声明和定义的地方同时说明inline,但这么做其实是合法的.不过,最好只在类外部定义的地方说明inline. 可变数据成员 可变数据成员:有时(但并不频繁)会发生这样一种情况,我们希望能修改类的某个数据成员,即是在一个const成员函数内,可以在变量的申明中加入mutable关键字做到这一点. • 一个可变数据成员永远不会是const,即使它是const对象的成员. 1 class Screen 2 { 3 public: 4 void some_member()

c++ primer 5th 笔记:第七章

第七章 笔记 1. 定义在类内部的函数是隐式的inline函数. 2. 使用const的成员函数被称作常量成员函数(const member function). 3. 首先编译成员的声明,然后才轮到成员函数体.因此,成员函数体可以随意使用类中的其他成员而无须在意这些成员出现的次序. 4. IO类属于不能被拷贝的类型,因此只能通过引用来传递它们. 5. 每个类都分别定义了它的对象被初始化的方式,类通过一个或几个特殊的成员函数来控制其对象的初始化过程.这些函数叫做构造函数. 6. 默认构造函数无须