菜狗的C++ primer读书笔记:第七章 类

1.类的基本思想是数据抽象和封装。数据抽象是一种依赖于接口和实现分离的编程技术。类的接口包括用户所能执行的操作;类的实现则包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数。封装则实现了类的接口和实现的分离。类想要实现实现数据抽象和封装需要首先定义一个抽象数据类型。作为一个设计良好的类,既要有直观且易于使用的接口,也必须具备高效的实现过程。
2.成员函数的声明必须在类的内部,它的定义则既可以在类的内部也可以在类的外部。定义在类内部的函数是隐式的inline函数。
3.成员函数通过一个名为this的额外的隐式参数来访问调用它的那个对象。任何对类成员的直接访问都被看作this的隐式引用。对我们来说,this形参是隐式定义的,实际上任何自定义名为this的参数或变量的行为都是非法的。this是一个常量指针,我们不允许改变this中保存的地址。
4.默认情况下,this的类型是指向类类型非常量版本的常量指针,这意味着在默认情况下我们不能把this绑定到一个常量对象上,这使得我们不能在一个常量对象上调用普通的成员函数。C++允许把const关键字放在成员函数的参数列表后面,紧跟在参数列表之后的const表示this是一个指向常量的指针。像这样使用const的成员函数被称作常量成员函数。常量对象以及常量对象的引用或指针都只能调用常量成员函数。
5.编译器分两步处理类:首先编译成员的声明,然后才轮到成员函数体。因此成员函数体可以随意使用类中的其他成员而无须在意这些成员出现的次序。
6.在类的外部定义成员函数时,成员函数的名字必须包含它所属的类名。
7.类的作者常常需要定义一些辅助函数,尽管这些函数定义的操作从概念上来说属于类的接口的组成部分,但它们实际上并不属于类本身。一般来说,如果非成员函数是类接口的组成部分,则这些函数的声明应该与类在同一个头文件中。
8.在定义类自己的输入输出函数时,需要注意两点:一是IO类属于不能被拷贝的类型,因此我们只能通过引用来传递它们,而且因为读取和写入操作都会改变流的内容,所以两个函数接受的都是普通引用,而非对常量的引用。二是执行输出任务的函数应该尽量减少对格式的控制,这样可以确保由用户代码来决定是否换行。
9.类的构造函数定义了它的对象被初始化的方式,无论何时只要类的对象被创建就会执行构造函数。
10.构造函数的名字和类名相同。和其他函数不一样的是,构造函数没有返回类型。而且构造函数也不能被声明成const,构造函数在const对象的构造过程中可以向其写值。
11.类通过一个特殊的构造函数来控制默认初始化的过程,这个函数叫做默认构造函数,默认构造函数无需任何实参。
12.编译器创建的构造函数又称为合成的默认构造函数。合成的默认构造函数只适合非常简单的类,对一个普通的类来说,必须定义它自己的默认构造函数。原因有三:
①一旦我们定义了一些其他的构造函数,那么除非我们再定义一个默认构造函数,否则类将没有默认构造函数。(只有当类没有声明任何构造函数时,编译器才会自动地生成默认构造函数)
②对于某些类来说,合成的默认构造函数可能执行错误的操作。
③有时候编译器不能为某些类合成默认的构造函数。
13.在C++11中,如果我们需要默认的行为,那么可以通过在参数列表后面写上= default 来要求编译器生成构造函数。其中 = default 既可以和声明一起出现在类的内部(隐式内联),也可以作为定义出现在类的外部。
14.构造函数初始化列表负责为新创建的对象的一个或几个数据成员赋初值。构造函数初始值是成员名字的一个列表,每个名字后面紧跟括号里(或者在花括号内的)成员初始值。如果编译器不支持类内初始值,则所有构造函数都应该显式地初始化每个内置类型的成员。
15.除了定义类的对象如何初始化之外,类还需要控制拷贝、赋值和销毁对象时发生的行为。如果我们不主动定义这些操作,则编译器将会替我们合成他们。但对于某些类来说,合成的版本无法正常工作。
16.C++中使用访问说明符加强类的封装性:
定义在public说明符之后的成员在整个程序内可被访问,public成员定义类的接口。定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问,private部分封装了类的实现细节。
17.struct和class的唯一区别是默认访问权限不太一样。如果我们使用前者,则定义在第一个访问说明符之前的成员是public的;相反,如果我们使用后者,则这些成员是private的。
18.类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元。如果类想把一个函数作为它的友元,只需要增加一条以friend关键字开始的函数声明语句即可。友元声明只能出现在类定义的内部,一般来说,最好在类定义开始或结束前的位置集中声明友元。
19.封装有两个重要的优点:
①确保用户代码不会无意间破坏封装对象的状态
②被封装的类的具体实现细节可以随时改变,而无须调整用户级别的代码
20.友元的声明仅仅指定了访问的权限,而非一个通常意义上的函数声明。如果我们希望类的用户能够调用某个友元函数,那么我们必须在友元声明之外再专门对函数进行一次声明。甚至就算在类内定义该函数,我们也必须在类的外部提供相应的声明从而使得函数可见。我们通常把友元的声明与类本身放在同一个头文件中。
21.除了定义数据和函数成员之外,类还可以自定义某种类型在类中的别名。由类定义的类型名字和其他成员一样存在访问限制,可以是public或者private中的一种。用来定义类型的成员必须先定义后使用,类型成员通常出现在类开始的地方。
22.有时我们希望能修改类的某个数据成员,即使是在一个const成员函数内,我们可以通过在变量的声明中加入mutable关键字。一个可变数据对象永远不会是const,即使它是const对象的成员。
23.类内初始值必须使用=的初始化形式或者花括号括起来的直接初始化形式。
24.使用定义返回类型为引用的成员函数有时可以简化操作。例如:A.fun1().fun2()
25.const成员函数的this是一个指向const的指针而*this是const对象,如果以引用的形式返回 *this,那么它的返回类型将是常量引用。
26.当我们在某个对象上调用重载的某函数时,该对象是否是const决定了应该调用该函数的哪个版本,这是一种基于const的函数重载。
27.对于公共代码最好使用私有功能函数,实践中设计良好的C++代码常常包含大量类似于do_display(P248)的小函数,通过调用这些函数,可以完成一组其它函数的“实际工作”。
28.可以仅声明类而暂时不定义它,不完全类型只能在非常有限的情景下使用:可以定义这种类型的指针或引用,也可以声明以不完全类型作为参数或者返回类型的函数。
29.直到类被定义之后数据成员才能被声明成这种类型。因为只有当类全部完成后类才算被定义,所以一个类的成员不能是该类自己,然而一旦一个类的名字出现后就被认为是声明的,因此类允许包含指向它自身类型的引用或指针。
30.如果一个类指定了友元类,则友元类的成员函数可以访问此类包括非公有成员在内的所有成员。但要注意的是,友元关系并不存在传递性,每个类只负责控制自己的友元类或友元函数。
31.除令整个类作为友元之外,还可以只把类的某个成员函数声明成友元,此时我们必须明确指出该成员属于哪个类。但要想令某个成员函数作为友元,我们必须仔细组织程序的结构以满足声明和定义的彼此依赖关系。
32.每个类都会定义自己的定义域。在类的作用域之外,普通数据和函数成员只能由对象、引用或者指针使用成员访问运算符来访问;对于类类型成员则使用作用域运算符访问。
33.在类外定义成员函数时,函数的返回类型通常出现在函数名之外。因此当成员函数定义在类的外部时,返回类型中使用的名字都位于类的作用域之外,这时返回类型必须指明它是哪个类型的成员。
34.名字查找:首先在名字所在的块中寻找其声明语句,只考虑在名字使用之前出现的声明;如果没有找到,继续查找外层作用域;如果最终没找到匹配的声明,则程序报错。
35.编译器处理完类中的全部声明后才会处理成员函数的定义。按照这种两阶段的方式处理类可以简化类代码的组织方式。因为成员函数体直到整个类可见后才会被处理,所以它能使用类中定义的名字的任何名字。
36.一般地,内层作用域的名字可以覆盖外层作用域的名字。但是在类中,如果成员使用了外层作用域中的某个名字,而该名字代表一种类型,则类不能在之后重新定义该名字。类型名的定义通常出现在类的开始处,这样就能确保所有使用该类型的成员都出现在类型名的定义之后。
37.成员函数中的名字查找:首先在成员函数内查找该名字的声明;如果在成员函数内没找到,则在类内查找,这时类的所有成员都可以被考虑;如果类内也没找到,在成员函数定义之前的作用域内继续查找。
38.如果类的成员被隐藏了,我们可以通过类的名字或显式使用this指针强制访问成员。如果外层的对象被隐藏了,我们可以用作用域运算符访问它。
39.在构造函数中,有时我们可以忽略数据成员初始化和赋值之间的差异,但并非总是这样。如果成员是const或者引用的话,必须将其初始化。类似地当成员属于某种类类型,且该类没有定义默认构造函数时也必须将这个成员初始化。在很多类中,初始化和赋值的区别事关底层效率问题,前者直接初始化数据成员,后者则先初始化再赋值;除了效率问题外更重要的是一些数据成员必须被初始化。
40.构造函数中成员的初始化顺序与它们在类定义中出现的顺序一致。构造函数初始值列表中初始值的前后位置关系不会影响实际的初始化顺序。最好令构造函数初始值的顺序与成员声明的顺序保持一致。而且如果可能的话,尽量避免使用某些成员初始化其他成员。
41.如果一个构造函数为所有参数提供了默认实参,则它实际上也定义了默认构造函数。
42.C++11使得我们可以定义委托构造函数,一个委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程,或者说它把它自己的一些(或者全部)职责委托给了其他构造函数。当一个构造函数委托给另一个构造函数时,受委托的构造函数的初始值列表和函数体被依次执行。
43.当对象被默认初始化或值初始化事自动执行默认构造函数。如果想定义一个使用默认构造函数进行初始化的对象,要去掉对象名后的括号对,否则就声明成了一个函数。
默认初始化 : ①


值初始化: ①


44.如果构造函数只接受一个实参,则它实际上定义了转换为此类型的隐式转换机制,有时把这种构造函数称作转换构造函数。但并不是所有的单参数构造函数都是转换构造函数。
45.我们可以通过将构造函数声明为explicit抑制构造函数定义的隐式转换,此关键字只对一个实参的构造函数有效。只能在类内声明构造函数时使用explicit关键字,在类外定义是不应重复。当我们用explicit关键字声明构造函数时,它将只能以直接初始化的形式使用。尽管编译器不会将explicit的构造函数用于隐式转换,但我们可以使用这样的构造函数显示地强制进行转换。
46.聚合类:
①所有成员都是public的
②没有定义任何构造函数
③没有类内初始值
④没有基类也没有virtual函数
我们可以提供一个花括号括起来的成员初始值列表,并用它初始化聚合类的数据成员,且初始值的顺序必须与声明的顺序一致。
47.字面值常量类:
①数据成员必须都是字面值类型
②类必须至少含有一个constexpr函数成员
③如果一个数据成员含有类内初始值,则内置成员类型的初始值必须是一条常量表达式;如果成员属于某种类类型,则初始值必须用成员自己的constexpr构造函数
④类必须使用析构函数的默认定义,该成员负责销毁类的对象
除此之外数据成员都是字面值类型的聚合类也是字面值常量类
48.尽管构造函数不能是const的,但字面值常量类的构造函数可以是constexpr构造函数。constexpr构造函数可以声明成=default。constexpr构造函数体一般来说应该是空的。constexpr构造函数必须初始化所有数据成员,初始值或者使用constexpr函数或者是一条常量表达式。
49.我们通过在成员的声明之前加上关键字static使得其与类关联在一起,使它与类本身直接相关,而不是与类的各个对象保持关联,这种成员称作静态成员。类的静态成员存在于任何对象之外,对象中不包括任何与静态数据成员有关的数据,且静态成员函数也不与任何对象绑定在一起,它们不包含this指针。
50.我们既可以使用作用域运算符直接访问静态成员,也可以使用类的对象、引用或者指针访问静态成员。
51.static关键字只出现在类内部的声明语句中。一般来说,我们不能在类内部初始化静态成员,必须在类的外部定义和初始化每个静态成员。
52.然而我们也可以为静态成员提供const整数类型的类内初始值,不过要求静态成员必须是字面值常量类型的constexpr,因为这些成员本身就是常量表达式,所以它们能用在所有适合于常量表达式的地方,比如指定数组成员的维度。即使一个常量静态数据成员在类内部被初始化了,通常情况下也应在类的外部定义一下该成员,如果在内部已经提供了初始值定义时就不能指定初始值了。
53.在某些非静态数据成员可能非法的场合,静态成员却可以正常地使用。静态数据成员的类型就可以是它所属的类类型,而非静态数据成员则受限制。另一个区别是我们可以使用静态成员作为默认实参,而非静态数据成员不行因为它的值本身属于对象的一部分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值