1. 类
1.1 类与对象
类是抽象的,不占用存储空间;对象是具体的,占用存储空间;对象是类的一个实例,类是对象的抽象出来的一种具有相似属性的模板。
1.2 类与结构体
区别:
- 关键字不同,结构体是struct,类是class
- 成员默认访问权限不同,结构体默认public,类默认private
- 在c语言中无类的概念,且struct中不能定义成员函数;在c++中,增加了class类型,同时扩展了struct的功能,struct中也能定义成员函数
**建议小贴士:**使每一种成员访问限定符在类体中只出现一次,并且先写public部分,把private部分放在类体的后部,这样可以使得用户将注意力集中在能被外界调用的成员上,使得阅读者的思路更清晰。
1.3 静态成员
- 静态数据成员是在所有对象之外单独开辟一段空间来存放。只要在类中定义了静态数据成员,即使不定义对象,也为静态数据成员分配了空间,它可以被引用;
- 如果在一个函数中定义了静态变量,在函数结束时该静态变量并不被释放,仍然存在并保留其值。静态数据成员也类似,它不随对象的建立而分配空间,也不随对象的撤销而释放。静态数据成员是程序在编译时被分配空间,到程序结束时释放空间;
- 静态数据成员可以通过对象名引用( . ),也可以通过类名来引用(:?;
- 静态成员函数与非静态成员函数的根本区别:非静态成员函数有this指针,而静态成员函数没有this指针。由此决定了静态成员函数不能访问本类中的非静态成员。
1.4 对象的存储空间
-
最权威的结论:非静态成员变量总和加上编译器为了CPU计算做出的数据对齐处理和支持虚函数所产生的负担的总和
-
空类型对象中不包含任何信息,应该大小为0.但是当声明该类型的对象时,它必须在内存中占用一定的空间,否则无法使用这些对象。至于占用多少内存,由编译器决定。C++中每个空类型的实例占1byte空间;
-
静态数据成员不占对象的内存空间;
-
构造函数、析构函数以及成员函数不占对象的存储空间;
-
编译器为了支持虚函数,会产生额外的负担,这正是指向虚函数表的指针的大小(指针变量在64位机器上占8byte)。如果一个类中有一个或者多个虚函数,没有成员变量,那么类相当于含有一个指向虚函数表的指针,占8byte;
-
单一继承的空类空间也是1,多重继承的空类空间还是1,但是虚继承设计虚表(虚指针),所以占用8byte
-
优先级:成员运算符“.”的优先级别高于指针运算符“*”
1.5 this指针
特点:
- 只能在成员函数中使用,在全局函数、静态成员函数中都不能使用this
- this指针是在成员函数开始前构造,并在成员函数结束后清除
- this指针会因编译器不同而有不同的存储位置,可能是栈、寄存器或全局变量
- this指针是类的指针
- 因为this指针只有在成员函数中才有定义,所以获得一个对象后,不能通过对象使用this指针,所以也就无法知道一个对象的this指针的位置。不过,可以在成员函数中指定this指针的位置
- 普通的类函数(不论是非静态成员函数,还是静态成员函数)都不会创建一个函数表来保存函数指针,只有虚函数才会被放到函数表中
1.6 类模板
-
格式
template <class T>
class 类名称{
};
eg:
template <class T>
class Operation{
public:
Operation(T a, T b) : x(a), y(b){}
private:
T x,y;
}; -
实例化类
eg: Operation<int> opobj(1, 2); -
在类外定义
eg:
template<class T>
Operation::Operation(T a, T b):x(a), y(b){} -
声明和使用类模板的步骤:
(1)先写一个实际的类
(2)将此类中准备改变的类型名改用一个自己制定的虚拟类型名
(3)在类声明前面加入一行,格式为:template<class 虚拟类型参数>。
(4)在类模板定义对象时使用如下格式:
类模板名 <实际类型名> 对象名;
类模板名 <实际类型名> 对象名(实参表列);
(5)如果在类模板外定义成员函数,应携程类模板形式:
template <class 虚拟类型参数>
函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参列表){…}
2 继承与派生
- 派生类的缺点:(1)数据冗余,浪费大量空间;(2)对象的建立、赋值、复制和参数的传递中花费许多无谓的时间,降低效率。
- 原则:根据派生类的需要慎重选择基类,使冗余量最小
- 派生的各个成员的访问属性
public_A | protected_A | private_A | public_B | protected_B | private_B | public_C | protected_C | private_C | public_D | protected_D | private_D | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
基类A | 公用 | 保护 | 私有 | |||||||||
公用派生类B(继承A) | 公用 | 保护 | 不可访问 | 公用 | 保护 | 私有 | ||||||
保护派生类C(继承B) | 保护 | 保护 | 不可访问 | 保护 | 保护 | 不可访问 | 公有 | 保护 | 私有 | |||
私有派生类D(继承C) | 私有 | 私有 | 不可访问 | 私有 | 私有 | 不可访问 | 私有 | 私有 | 不可访问 | 公有 | 保护 | 私有 |
- 构造函数和析构函数调用顺序:
构造函数:(1)基类构造函数;(2)子对象构造函数;(3)派生类构造函数
析构函数:(2)派生类析构函数;(2)成员类对象析构函数;(2)基类析构函数
3 类的多态
-
定义:多态性是指具有不同功能的函数可以用同一个函数名;
-
多态的表现形式:
(1)静态多态(重载,模板)–>是在编译的时候,就确定调用函数的类型。
(2)动态多态(覆盖,虚函数实现)–>在运行的时候,才确定调用的是哪个函数,动态绑定。运行基类指针指向派生类的对象,并调用派生类的函数。
虚函数实现原理:虚函数表和虚函数指针。 -
覆盖(override)、重载(overload)、隐藏
(1)覆盖是指子类重新定义父类的虚函数的做法;
(2)而重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)
(3)覆盖是指派生类中存在重新定义的函数,其函数名、参数列、返回值类型必须同父类中的相对应被覆盖的函数严格一致,覆盖函数和被覆盖函数只有函数体(花括号中的部分)不同.
当派生类对象调用子类中该同名函数时会自动调用子类中的覆盖版本,而不是父类中的被覆盖函数版本,这种机制就叫做覆盖
(4)成员函数被重载的特征有:
–>1.相同的范围(在同一个类中);
–>2. 函数名字相同;
–>3. 参数不同;
–>4.virtual关键字可有可无。
(5)覆盖的特征有:
–>1.不同的范围(分别位于派生类与基类);
–>2. 函数名字相同;
–>3.参数相同;
–>4.基类函数必须有virtual关键字。
(6)隐藏是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
–>1.如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
–>2. 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆) -
C++明确指出,当derived class 对象经由一个base class指针被删除、而该base class带着一个non-virtual析构函数,会导致对象的derived成分没有被销毁掉