1、成员类型
• 公有成员(public member):
○ 在程序的任何地方都可以被访问
○ 实行信息隐藏的类将其 public 成员限制在成员函数上
○ 这种函数定义了可以被一般程序用来操纵该类类型对象的操作
• 私有成员(private member)
○ 只能被成员函数和类的友元访问
○ 实行信息隐藏的类把其数据成员声明为 private
• 被保护成员(protected member)
○ 对派生类(derived class)就像 public 成员一样
○ 对其他程序则表现得像 private
2、成员函数
• 成员函数声明/定义
○ 成员函数必须先在其类体内被声明,而且类体必须在成员函数被定义之前先出现
○ 通常,在类体外定义的成员函数不是 inline 的
○ 可以通过显式地在类体中出现的函数声明上使用关键字 inline,或者通过在类体外出现的函数定义上显式使用关键字 inline,来定义内联函数
○ 由于内联函数必须在调用它的每个文本文件中被定义,所以没有在类体中定义的内联成员函数必须被放在类定义出现的头文件中
• 构造函数(具体见下)
• const 成员函数
○ 类的设计者通过把成员函数声明为 const 以表明它们不修改类对象
○ 只有被声明为 const 的成员函数才能被一个 const 类对象调用
○ 把一个成员函数声明为 const 可以保证这个成员函数不修改类的数据成员
○ 但是,如果该类含有指针,那么在 const 成员函数中就能修改指针所指的对象
○ const成员函数可以被相同参数表的非 const 成员函数重载
○ 构造函数和析构函数是两个例外,即使构造函数和析构函数不是 const 成员函数,const 类对象也可以调用它们
• volatile 成员函数
○ 如果一个类对象的值可能被修改的方式是编译器无法控制或检测的,则把它声明为 volatile
○ 与 const 类对象类似,对于一个 volatile 类对象,只有 volatile 成员函数、构造函数和析构函数可以被调用
• mutable 数据成员
○ 为了允许修改一个类的数据成员,即使它是一个 const 对象的数据成员,我们也可以把该数据成员声明为 mutable(易变的)
○ mutable 数据成员永远不会是 const 成员,即使它是一个 const 对象的数据成员
○ mutable 成员总可以被更新,即使是在一个 const 成员函数中
3、构造函数
• 构造函数(详见笔记三)
○ 构造函数的名字必须与类名相同,用于初始化类的对象
• 拷贝构造函数
○ 浅拷贝
§ 默认的复制构造函数只是完成了对象之间的位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B
§ 这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存
§ 这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。
○ 深拷贝
§ 自定义复制构造函数需要注意,对象之间发生复制,资源重新分配,即A有5个空间,B也应该有5个空间,而不是指向A的5个空间
4、友元
• 概述
○ 在某些情况下,允许某个函数而不是整个程序可以访问类的私有成员,这样做会比较方便
○ 友元(friend)机制允许一个类授权其他的函数访问它的非公有成员
• 使用方法
○ 友元声明以关键字 friend 开头,它只能出现在类的声明中
○ 由于友元不是授权友谊的类的成员,所以它们不受其在类体中被声明的 public private 和 protected 区的影响
○ 友元函数可以访问类中的私有成员和其他数据,但需要通过对对象进行引用来实现
• 友元类型
○ 命名空间函数
○ 另一个类的成员函数
○ 一个完整的类
○ 全局函数
5、类声明和定义
• 定义
○ 一旦到了类体的结尾 即结束右括号 我们就说一个类被定义了
○ 一旦定义了一个 类,则该类的所有成员就都是已知的,类的大小也是已知的了
• 声明
○ 当一个类的类头被看到时,它就被视为已经被声明了
○ 我们只能以有限的方式使用已经被声明但还没有被定义的类类型
• 使用
○ 只有已经看到了一个类的定义,我们才能把一个数据成员声明成该类的对象
○ 如果没有定义类,那么我们就不能定义这类类型的对象,因为类类型的大小不知道,编译器不知道为这种类类型的对象预留多少存储空间
○ 我们可以声明指向该类类型的指针或引用,允许指针和引用是因为它们都有固定的大小,这与它们指向的对象的大小无关
○ 因为该类的大小和类成员都是未知的,所以要等到完全定义了该类,我们才能将解引用操作符 * 应用在这样的指针上,或者使用指针或引用来指向某一个类成员
6、类对象
• 空间分配
○ 类的定义不会引起存储区分配,只有当定义一个类的对象时,系统才会分配存储区
○ 每个类对象都有自己的类数据成员拷贝
• 初始化
○ 一个对象可以被同一类类型的另一个对象初始化或赋值(拷贝构造函数)
○ 类类型的指针可以用同一类类型的类对象的地址做初始化或赋值
○ 面 向对象的程序设计对此作了扩展,允许基类的引用或指针引用到派生类的对象
• 成员函数调用
○ 同一个类的多个对象共用一个成员函数的拷贝
○ 成员函数通过 this 指针来识别不同的对象
7、this 指针
• 概述
○ 每个类对象都将维护自己的类数据成员的拷贝
○ 成员函数可以引用自己的类成员而无需使用成员访问操作符
• 使用特点
○ 每个类成员函数都含有一个指向被调用对象的指针,这个指针被称为 this
○ 在非 const 成员函数中,它的类型是指向该类类型的指针
○ 在 const 成员函数中,它是指向 const 类类型的指针
○ 在 volatile 成员函数中,是指向 volatile 类类型的指针
• 实现原理
○ 改变类成员函数的定义,用额外的参数 this 指针来定义每个成员函数
○ 改变每个类成员函数的调用,加上一个额外的实参——被调用对象的地址
8、静态数据
• 原理
○ 静态数据成员被当作该类类型的全局对象
○ 对于非静态数据成员,每个类对象都有自己的拷贝
○ 静态数据成员对每个类类型只有一个拷贝
○ 静态数据成员只有一份,由该类类型的所有对象共享访问
• 使用
○ 在类体中的数据成员声明前面加上关键字 static 就使该数据成员成为静态的
○ static 数据成员,遵从 public/private/protected访问规则
○ 一般地,静态数据成员在该类定义之外被初始化
○ 如同一个成员函数被定义在类定义之外一样,在这种定义中的静态成员的名字必须被其类名限定修饰
○ 静态数据成员可以被声明为任意类型,它们可以是 const 对象、数组或类对象等等
• 与全局对象对比
○ 静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其他全局名字冲突的可能性
○ 可以实现信息隐藏,静态成员可以是 private 成员,而全局对象不能
○ 与全局对象一样,对于静态数据成员。在程序中也只能提供一个定义
§ 这意味着,静态数据成员的初始化不应该被放在头文件中,而应该放在含有类的非 inline 函数定义的文件中
9、静态成员函数
• 声明
○ 静态成员函数的声明除了在类体中的函数声明前加上关键字 static,以及不能声明为 const 或 volatile 之外,与非静态成员函数相同
○ 出现在类体外的函数定义不能指定关键字 static
• 特点
○ 静态成员函数没有 this 指针,因此在静态成员函数中隐式或显式地引用这个指针都将导致编译时刻错误
○ 试图访问隐式引用 this 指针的非静态数据成员也会导致编译时刻错误
• 使用
○ 可以用成员访问操作符点 . 和箭头 -> 为一个类对象或指向类对象的指针调用静态成员函数
○ 也可以用限定修饰名直接访问或调用静态成员函数,而无需声明类对象
10、普通指针 & 类成员地址之间的差异
• 函数指针
○ 函数指针不能被赋值为成员函数的地址,否则会产生类型违例,编译报错,即使返回类型和参数表完全匹配
○ 因为成员函数有一个非成员函数不具有的属性——它的类(its class)
○ 指向成员函数的指针必须与向其赋值的函数类型匹配,不是两个而是三个方面都要匹配
§ 参数的类型和个数
§ 返回类型
§ 它所属的类类型
○ 在成员函数指针和普通函数指针之间的不匹配,是由于这两种指针在表示上的区别
§ 函数指针存储函数的地址,可以被用来直接调用那个函数
§ 成员函数指针首先必须被绑定在一个对象或者一个指针上,才能得到被调用对象的 this 指针,然后才调用指针所指的成员函数
§ 虽然普通函数指针和成员函数指针都被称作指 针,但是它们是不同的事物
• 数据指针
○ 在数据成员指针和普通指针之间的不匹配也是由于这两种指针的表示上的区别
○ 普通指针含有引用一个对象所需的全部信息
○ 数据成员指针在被用来访问数据成员之前,必须先被绑定到一个对象或指针上
○ 类成员的指针必须总是通过特定的对象或指向该类类型的对象的指针来访问
• 静态成员指针
○ 在非静态类成员的指针和静态类成员的指引之间有一个区别
○ 指向类成员的指针语法不能被用来引用类的静态成员
○ 静态类成员是属于该类的全局对象和函数,它们的指针是普通指针
11、联合(Union)
• 概述
○ 是一种特殊的类
○ 一个联合中的数据成员在内存中是互相重叠的,每个数据成员都在相同的内存地址开始
○ 分配给联合的存储区数量是“要包含它最大的数据成员“所需的内存数
○ 同一时刻只有一个成员可以被赋给一个值
• 使用限制
○ union 不能有静态数据成员或是引用成员
○ 如果一个类类型定义了构造函数、析构函数、或拷贝赋值操作符,则它不能成为 union 的成员类型
○ 我们可以为 union 定义成员函数,包括构造函数和析构函数