小言_互联网的博客

c++ primer读书笔记——第7章 类

172人阅读  评论(0)

使用类定义自己的数据类型,通过定义新的类型来反映待解决问题中的各种概念。
类的基本思想是数据抽象封装。数据抽象是一种依赖于接口和实现分离的编程技术。类的接口包括用户所能执行的操作,类的实现包括类的数据成员、负责接口实现的函数体及定义类所需的各种私有函数。封装意味着类的用户只能使用接口而无法访问实现部分。
作为一个设计良好的类,既要有直观且易于使用的接口,也必须具备高效的实现过程。
在考虑如何实现我们的类之前,首先来看看应该如何使用上面这些接口函数。
成员函数体可以定义在类内也可以定义在类外
为什么需要this指针?
需要知道具体是类的哪个对象调用成员函数,成员函数通过一个名为this的额外的隐式参数来访问调用它的那个对象。
如果调用total.isbn(),编译器负责把total的地址传递给isbn的隐式形参this,调用Sales_data的isbn成员时传入了total的地址。
在成员函数的内部,我们可以直接使用调用该函数的对象的成员,无须通过成员访问运算符来实现,因为this所指的正是这个对象,任何对类成员的直接访问都被看成是this的隐式引用。
this形参是隐式定义的,this总是指向某个对象,this是一个常量指针(顶层const),不允许改变this中保存的地址。
引入const成员函数
在成员函数的参数列表后紧跟const关键字。
默认情况下,this类型是顶层const。const关键字修改this指针既为顶层const,又是底层const,从而可以在常量对象上调用普通的成员函数。
在参数列表后使用const的成员函数被称作常量成员函数。
常量对象、以及常量对象的引用或指针都只能调用常量成员函数。
一个const成员函数如果以引用的形式返回*this,那么它的返回类型将是常量引用。
定义类相关的非成员函数
类似add,read,print的函数,从概念上来说属于类的接口的组成部分,但实际上并不属于类本身。
如果函数在概念上属于类但不定义在类中,则它一般应与类声明(而非定义)在同一个头文件中,方便引用。
IO类属于不能被拷贝的类型,只能通过引用来传递它们。
构造函数
控制类对象的初始化过程,初始化类对象的数据成员
构造函数可以重载,必须在参数数量和参数类型上有所区别
无论何时,只要类的对象被创建,就会执行构造函数
构造函数的名字与类名相同,构造函数没有返回类型
构造函数不能被声明为const
类通过一个特殊的构造函数来控制默认初始化过程,这个函数叫默认构造函数,默认构造函数无需任何实参。
如果我们的类没有显式地定义构造函数,那么编译器就会为我们隐式地定义一个默认构造函数,编译器创造的构造函数又叫合成的默认构造函数。
合成的默认构造函数初始化规则:
1 如果存在类内初始值,用它来初始化成员
2 如果不存在类内初始值,默认初始化该成员
编译器只有在发现类不包含任何构造函数的情况下才会替我们生成一个默认的构造函数。
一旦我们定义了一些其他的构造函数,除非我们再定义一个默认的构造函数,否则类将没有默认构造函数。
如果一个类在某种情况下需要控制对象的初始化,那么该类很可能在所有情况下都需要控制。
在c++11新标准中,如果我们需要默认的行为,那么可以通过在参数列表后面写上=default来要求编译器生成构造函数。
除了定义类的对象如何初始化之外,类还需要控制拷贝、赋值和销毁对象时发生的行为。
如果我们不主动定义这些操作,编译器会替我们合成它们。一般来说,编译器生成的版本将对对象的每个成员执行拷贝、赋值和销毁操作。对于某些类来说,合成的版本无法正常工作。
访问控制和封装
在c++中使用访问说明符(public & private)加强类的封装性
struct和class定义类的唯一区别:默认访问权限不一样
友元
类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元。
友元函数:只需要加一条以friend关键字开始的函数声明语句。
友元声明只能出现类定义的内部
友元不是类的成员也不受所在区域访问控制级别的约束
必须在友元声明之外再专门对函数进行一次声明。
如果一个类指定了友元类,则友元类的成员函数可以访问此类包括非公有成员在内的所有成员。
友元关系不存在传递性。
某个类的成员函数也可以做另一个类的友元函数

封装的优点
1 确保用户代码不会无意间破坏封装对象的状态
2 被封装的类的具体实现细节可以随时改变,无需调整用户级别的代码

类型成员:
类可以自定义某种类型在类中的别名。
类型成员一样存在访问限制。
用来定义类型的成员必须先定义后使用,类型成员通常出现在类开始的地方。
令成员函数作为内联函数:
有一些规模较小的函数适合被声明成内联函数。
定义在类内部的函数是自动内联的。
可以在类的内部把inline作为声明的一部分显式地声明成员函数,也能在类的外部用inline关键字修饰函数的定义
无需在声明和定义的地方同时说明inline,最好只在类外部定义的地方说明inline
inline成员函数也应该与相应的类定义在同一个头文件中
重载成员函数:
成员函数也可以被重载,只要在参数个数或类型上有所区别就可以。
成员函数的函数匹配过程和非成员函数非常类似。编译器根据实参的个数和类型来决定运行哪个版本的函数。
可变数据成员:
可变数据成员的特点:任何成员函数,包括const函数在内都能改变它的值
可变数据成员的声明:在数据成员声明前加mutable关键字
类数据成员的初始值:
最好把默认值声明成一个类内初始值
类内初始值必须用=或者{}的形式表示

非常量版本的函数对于常量对象是不可用的。
只能在一个常量对象上调用const成员函数。
可以在非常量对象上调用常量版本或者非常量版本,但非常量版本是个更好的选择。

当一个成员函数调用另一个成员函数时,this指针在其中隐式地传递。

对于两个类,即使它们的成员完全一样,这两个类也是不同的类型。
可以直接把类名作为类型的名字使用,也可以把类名跟在关键字class或struct后面。
类似函数的声明和定义分离开一样,我们也可以仅声明类而暂时不定义它。这种声明被称为前向声明。在一个类声明之后定义之前是一个不完全类型。
不完全类型只能在非常有限的情况下使用:
1 定义指向这种类型的指针或引用
2 声明以不完全类型作为参数或者返回类型的函数
在我们创建类的对象前该类必须被定义过。
例外:直到类被定义后数据成员才能被声明成这种类类型,必须首先完成类的定义,编译器才能知道存储该数据成员需要多少空间。

class Link_screen
{
  Screen window;
  Link_screen *next;
  Link_screen *prev;
}

函数重载和友元
如果想把一组重载函数声明为它的友元,则需要对这组函数中的每一个分别声明

7.4 类的作用域

一个类就是一个作用域,因此在类的外部定义成员函数必须同时提供类名和函数名,在类的外部,成员的名字被隐藏起来了

7.5 构造函数再探

对于任何c++的类来说,构造函数都是其中的重要组成部分。
随着构造函数一开始执行,初始化就完成了
初始化const或者引用类型的数据成员的唯一机会就是通过构造函数初始值
成员初始化的顺序:成员初始化顺序与他们在类定义中的出现顺序一致,构造函数初始值列表中初始值的前后位置关系不会影响实际的初始化顺序
最好令构造函数初始值的顺序与成员声明的顺序保持一致,如果可能的话,最好避免使用某些成员初始化其他成员
**委托构造函数:**委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程。

隐式类类型转换
第14章会讲解如何把一种类类型转换成另一种类类型
如果构造函数只接受一个实参,则它实际定义了转换为此类类型的隐式转换规则。能通过一个实参调用的构造函数定义了一条从构造函数的参数类型向类类型的隐式转换的规则。
只允许一步类类型转换


通过在构造函数前加explicit阻止构造函数定义的隐式转换
explicit构造函数只能用于直接初始化
explicit构造函数显式地强制进行转换

聚合类
聚合类的满足条件:
1 所有成员都是public
2 没有定义任何构造函数
3 没有类内初始值
4 没有基类,也没有虚函数
聚合类的初始化:提供一个花括号括起来的成员初始值列表,并用它初始化聚合类的数据成员。初始值的顺序必须与声明的顺序一致。初始值列表的元素个数小于等于类的成员个数,如果初始值列表中的元素个数少于类的成员数量,则靠后的成员被值初始化
显式地初始化类的对象的成员存在三个明显的缺点:
1 要求类的所有成员都是public
2 正确初始化每个对象的每个成员的重任交给了类的用户而非类的作者
3 添加或者删除一个成员之后,所有的初始化语句都需要更新

类的静态成员

有时候类需要它的一些成员与类本身直接相关,而不是与类的各个对象保持关联。
静态成员声明:在成员声明前加上static,使其与类关联在一起。
静态成员可以是public也可以是private
静态数据成员可以是常量、引用、指针、类类型
静态成员不与任何对象绑定在一起,它们不包含this指针(this指针就是将成员函数和某一个对象绑定在一起)
静态成员函数不能声明为const
使用类的静态成员:使用作用域运算符直接访问静态成员
成员函数不用通过作用域运算符就能直接使用静态成员

静态成员可以作为默认实参,非静态数据成员不能作为默认实参


转载:https://blog.csdn.net/weixin_43854189/article/details/101271213
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场