目录
9、如果类数据成员拥有一个默认初始化的值,在C++11新标准中,最好的方式就是把这个默认值声明成一个类内初始值。
16.1、彩蛋:为什么只提供元素数量不提供初值时vector只能直接初始化而不能进行拷贝初始化?
1、数据抽象和封装
- 数据抽象即接口和实现分离。接口即用户能执行的操作;实现包括类的数据成员、实现接口的函数体以及定义类所需的各种私有函数。
- 封装用来实现接口和实现的分离。
实现数据抽象和封装,首先要定义一个抽象数据类型,只有数据成员的类不能算是抽象数据类型,只有在数据的基础之上定义操作才能封装(隐藏)它的数据成员。
2、this
和Python不同,c++中任何对类成员的直接访问都被看做对this的隐式引用,而Python对类成员的访问必须通过self,否则就是对一个局部变量的访问。
this是一个常量指针而不是指向常量的指针,即为type *const。所以不能把this绑定到一个常量对象上,这也使得无法在常量对象上调用普通的成员函数。
因为this是隐式的,所以c++中只有通过在成员函数的参数列表后放置const来表示this是一个指向常量的指针,即const type *const,这样的函数就是常量成员函数。(257页)
如果这个const成员函数返回*this,则这个返回的值将是常量对象或常量(对象的)引用(273页)
也即const成员返回值应该是const对象。
3、成员函数
成员函数定义的返回类型、名字、参数列表都需要个对应声明一致。
4、构造函数
构造函数不能声明为const的,这是因为调用构造函数期间,对象还没有创造出来,自然谈不到const属性。
- 默认构造函数。因为有了类内初始值,所以合成的默认构造函数会优先使用这些值。只有在类内没有定义任何构造函数时编译器才会合成一个默认构造函数。默认构造函数(不应该)有任何形参。如果定义了其它构造函数,又需要默认构造函数,则需要显示定义它。
- 一个构造函数的所有参数都有默认实参,那么它实际上定义了默认构造函数。
- 类的定义就是一个块,所以如果没有提供类内初始值,默认构造函数执行默认初始化类成员时,它们的值将是未定义的。如果编译器不支持类内初始值,默认构造函数就应该使用构造函数初始值列表来初始化类成员变量。
- = default.可以在参数列表后加上= default来要求编译器生成构造函数。它可以作为声明的一部分(此时构造函数时内联的),也可以作为定义的一部分出现下类声明外部。
5、友元
- 一般来说,最好在类定义的开始或结束位置集中声明友元。
- 友元声明只是制定了访问的权限,如果希望类的用户可以调用某个友元函数,则必须在友元声明之外再对函数进行一次声明。(有很多编译器并没有强制这一点,但最好还是提供独立的声明。此外,还有一个例外,参见18章笔记第2.8条)
- 友元函数可以定义在类的内部,这样的函数是隐式内联的。虽然被定义了,但仍然需要在(类的外部)声明这个函数从而使得函数可以使用(278页):
即这里最重要的是理解友元声明的作用是影响访问权限,它本身并非普通意义上的声明
- 友元可以是普通函数,是一个类或者一个类的成员函数。
- 类的友元类的成员函数可以访问此类的包括非公有成员在内的所有成员。注意:访问某个类的所有成员的意思即访问这种类的所有实例的所有成员(例子见277页)。
- 成员函数作为友元。这时对程序结构有如下要求(278页)。
只声明而不定义clear的原因是因为还没有声明Screen类型。
- 声明友元时只声明名字是不够的,必须包括它们的形参列表。也即虽然重载函数的名字相同,如果想把他们声明成友元,必须单独声明(278页),即不能通过声明一个名字为友元使得所有重载版本都成为友元。
6、类型成员(269页)
可以在类内定义某种类型在类内的别名,这种类型名字和其它成员一样存在访问限制。
还可以使用类型别名
用来定义类型的成员必须先定义后使用,这一点和其它类型的成员不同。因此,类型成员通常出现在类开始的地方。
7、内联函数
可以再类内把inline作为声明的一部分显示声明成员函数,也能在类的外部修饰成员函数的定义,两种做法效果是一样的。
也可以同时在声明和定义处使用inline,但最好在类外部定义的地方使用inline。
8、可变数据成员(271)
用mutable修饰的成员永远不会是const,即使它是const对象的成员,所以,一个const成员函数可以改变一个可变成员的值。
9、如果类数据成员拥有一个默认初始化的值,在C++11新标准中,最好的方式就是把这个默认值声明成一个类内初始值。
10、const重载
函数重载时(参加第二章笔记6.1节),形参的底层const不同的同名函数可以构成重载。对成员函数来说类似,const方法和同名非const方法也可以构成重载。因为非常量版本的方法对常量对象是不可用的,只能在常量对象上调用const方法;而虽然在非常量对象上可以调用常量或非常量方法,但是调用非常量方法更匹配。(274页)
11、类类型
只有当类完全完成后才算被定义,所以一个类的成员类型不能是它自己,但是一个名字出现后就算被生命过了,所以类成员类型可以是指向其自身的指针或引用。
12、类的作用域
在类的外部定义类的成员时,必须提供类名和成员名,一旦遇到了类名,定义的其他部分就位于类的作用域之内了,包括函数名和参数列表,结果就是我们可以直接使用类的其它成员而无需授权了
上图参数列表中直接使用了类内定义的类型ScreenIndex。
但是函数返回类型在函数名字的前面,所以返回类型用到的名字位于类作用域之外,故返回类型必须指明它们是哪个类的成员。(279页)
13、名字查找通用规则(280页)
- 名字首先在块中寻找名字的声明语句,且只考虑被使用的名字之前的声明。Python没有块作用域。
- 然后再寻找外层作用域直到成功或报错。
14、类定义与名字
类编译时对名字的处理如下(与上条通用规则有所不同):
- 编译成员声明
- 类全部可见后编译函数体
上述过程的含义是:处理成员函数定义而非声明时,因为所有成员声明已经被编译了,所以定义可以使用所有成员的名字。(280页)
那么处理成员函数声明时遇到的名字会怎么做呢?声明中的名字包括参数列表中的名字和返回类型的名字都必须在使用前确保可见。所以,如果遇到了尚未出现的名字(即还没有编译的类成员的声明),编译器将在这个名字之前的类内作用域寻找,如果没有找到才回去类外寻找(280页)
上图中成员函数balance声明中的名字Money在名字balance之前的类内没有出现,所以会继续在类外找到typedef名字;但是balance定义(即函数体)内的名字bal就可以使用整个类内作用域的名字,所以它被解析为成员变量bal。
如果成员使用了类外定义的类型名,则这个类不能在之后重新定义该名字(281页)
所以把类型名定义放在类的开始处可以解决这个问题。
当成员定义在类的外部时,如果此时名字查找到了全局作用域,因为定义在类的外部,所以类定义之前的作用域和成员函数定义之前的作用域可能不是重合的,所以这两个地方的名字都需要考虑(283)
成员函数setHeight定义之前可以看到名字verify,所以可以使用它,尽管类定义之前看不到名字verify。
15、委托构造函数(C++11,287页)
一个委托构造函数使用它所属类的其它构造函数执行它自己的初始化过程。语法形式就像在委托构造函数初始值列表中调用被委托的构造函数。
委托可以进行多次,比如上图中第四个委托第二个,第二个又委托第一个构造函数。
16、隐式的类类型转换和转换构造函数
如果一个构造函数只接受一个实参,那么它实际上定义了参数的类型转换为本类类型的隐式转换规则,它也被称为转换构造函数。(289页)
类类型转换只能只能允许一步而不能连续转换两种类型。(290页)
可以通过将构造函数声明为explicit来阻止构造函数用于隐式转换。因为单参数构造函数才可以用作隐式类型转换,所以无需将其它类型的构造函数声明为explicit。且explicit只能用于构造函数的声明而非定义。explicit构造函数只能用于直接初始化而不能用于拷贝初始化(这一点不是针对类型转化而只是说类对象的初始化)
隐式转化被阻止了,那显式转换呢?可以!
16.1、彩蛋:为什么只提供元素数量不提供初值时vector只能直接初始化而不能进行拷贝初始化?
实际上vector提供了一个接受容量参数的构造函数,那么可能原因有两种:
- 容量参数在底层不是int,int转换为容量参数后无法再调用这个特定的构造函数。
- 如果上面的原因不成立,那就是因为这个构造函数是explicit的(292页),所以他不能用于隐式类型转换。
17、聚合类(aggregate class,292页
目录
9、如果类数据成员拥有一个默认初始化的值,在C++11新标准中,最好的方式就是把这个默认值声明成一个类内初始值。
16.1、彩蛋:为什么只提供元素数量不提供初值时vector只能直接初始化而不能进行拷贝初始化?
)
相当于c中的struct
对它可以使用列表初始化语法
18、字面值类型的类(293页)
除了算数类型、指针和引用外,某些类也可以是字面值类型(参见第二章笔记第11条),这样的类可能含有constexpr函数(这样的函数是隐式const的)。
数据成员都是字面值的聚合类都是字面值类类型。得聚合类成为字面值常量类要满足以下条件
19、constexpr构造函数(294页)
构造函数不能是const的,但字面值类型类的构造函数可以是constexpr函数,因为如第18条笔记所述,它必须有一个constexpr构造函数。
因为是constexpr函数,所以它只能有一条返回语句,倒是构造函数又没有返回值,所以它的函数体一般都是空的。
最后,它还可以用=default来声明。
20、 静态成员(296页)
包括成员函数和数据成员。static只能出现在类内声明语句而不是类外定义处。
通常情况下,静态成员不应该在类内初始化。例外是可以为类内静态成员提供const整数类型的类内初始值,且成员类型必须是字面值类型的constexpr。
静态数据成员可以是不完全类型,此外,它还可以是它所属的类类型,而非静态成员只能声明成它所属类类型的指针或引用(297页)。此外,静态成员可以作为默认实参
21、不完全类型(276页)
当只声明而没有定义类时(前向声明),对应的名字引入的类型被称为不完全类型。