顺序非常随缘 教材清华郑莉
题型;选择,填空,判断,程序填空,综合程序
-2出分后记录:
我哪扣分了???我不理解
-1考后记录:
建议课本上粗体字全部看一遍记住
写的东西就。。没怎么考,运算符重载的代码还是有用的
2021-06-24
0.某些知识点
C++的内存布局:内存区分为5个区,分别是堆、栈、自由存储区、全局/静态存储区、常量存储区
标识符是一个以字母或下划线开头的,由字母 数字 下划线组成的字符串
比如说这样是合法的:_123
两个不同类型运算时,编译器默认选择精度高的类型
1. 关键字/运算符/函数/..
typedef 类型定义
1 |
|
enum 枚举
默认是012345
1 |
|
inline 内联
不在调用时发生传参,而是在编译时将函数体嵌入在每一个调用处
适用于规模小,功能简单,调用频繁的函数
1 |
|
friend 友元
- 友元函数 声明友元后该函数可以使用类的protected&private成员(会破坏隐藏、封装)
1 |
|
友元类 前向声明/函数实现拆到下面(这个保险)
若类B为类A的友元,则类B可以访问类A的所有成员
1 |
|
const 常量
const int maxn=1e9+7;
常成员函数P164
const是函数类型的组成部分,因此声明和定义都要写const;
若一个对象为const,则该对象只能使用常成员函数
不是const的对象也可以调用这个函数,但是若有重载,优先选择不带const的函数调用
1
2
3
4
5
6
7
8class A{
public:
void Print()const{
//
}
}
const A Obj;
A.Print();常数据成员
不能被赋值,初始化只能使用初始化列表
常引用
常引用所引用的对象不能被更新,如果用常引用作形参,就不会意外地发生对实参的更改。
习惯 对于在函数中无需改变值的参数,使用const传递,可以使普通对象和常对象都可以使用这个函数
当const在函数名前面的时候修饰的是函数返回值
const在函数名后面表示是类的常成员函数,该函数不能修改对象内的任何成员,只能发生读操作,不能发生写操作。
static 静态
class中静态成员/函数是所有类对象共用的,可以不通过类对象来使用
需要在类外定义值(因为不占用类对象的存储空间)
extern 全局
这 个 我 熟
1 |
|
& 引用
引用是一个变量的别名
注意:
- 引用不是一种独立的数据类型,堆引用只有声明没有定义
- 声明引用时(要为引用分配内存空间时),要进行初始化
- 声明引用后,不能再作为另一个变量的引用(指针可以修改)
- 不能建立引用数组/引用某一个数组里的量
引用作为形参传递给函数可以节省复制构造的时间
引用作为返回值,函数可以作为一个左值
1 |
|
new&delete
1 |
|
new:在堆上开一段空间,返回一个地址
若new一个类,如果写了构造函数,则new T和new T()都调用这个构造函数;没写构造函数使用默认构造函数时,除执行默认构造函数的操作外,还会为所有成员用0赋值
new数组:
1 |
|
delete:删除new出来的空间,配合使用,否则会造成内存泄漏
只能delete一次,否则报错报断点
使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算
与malloc&free的主要区别:
使用new操作符来分配对象内存时会经历三个步骤:
- 第一步:调用operator new 函数(对于数组是operator new[])分配一块足够大的,原始的,未命名的内存空间以便存储特定类型的对象。
- 第二步:编译器运行相应的构造函数以构造对象,并为其传入初值。
- 第三部:对象构造完成后,返回一个指向该对象的指针。
使用delete操作符来释放对象内存时会经历两个步骤:
- 第一步:调用对象的析构函数。
- 第二步:编译器调用operator delete(或operator delete[])函数释放内存空间。
总之来说,new/delete会调用对象的构造函数/析构函数以完成对象的构造/析构。而malloc则不会。
2.三种继承
首先protected是什么?
pbulic 全局,类内部外部子类都可访问;
private 私有,只有类内部可以访问;
protected 保护,只有本类、子类、父类可以访问。
对建立其所在类对象的模块来说,它与private成员的性质相同;对于其派生类来说,它与public成员的性质相同
既实现了数据隐藏,又方便继承,实现代码重用
public 公有继承
public -> public
protected -> protected
private -> 不可见
private 私有继承
public -> private
protected -> private
private -> 不可见
protected 保护继承
public -> protected
protected -> protected
private -> 不可见
3. 构造函数/复制构造函数-初始化列表
(https://www.runoob.com/w3cnote/cpp-construct-function-initial-list.html)
1 |
|
使用初始化列表是显式初始化,而下面的是直接为成员赋值
是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序
对非内置类型成员变量,为了避免两次构造,推荐使用类构造函数初始化列表
必须使用初始化列表的情况:
- 成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
- const 成员或引用类型的成员。因为 const 对象或引用类型只能初始化,不能对他们赋值。
组合类构造函数
构造顺序,按照内嵌对象在组合类中定义的顺序调用他们的构造函数;
最后调用本类的构造函数
继承类构造函数/复制构造函数
构造顺序,先父类后子类;
析构顺序,先子类后父类(总之就是都倒过来)
1 |
|
4. 类的组合-前向声明
在多重继承和类的组合中优先使用组合
1 |
|
前向声明只能说明B是一个类名,而未给出完整的定义:
尽管使用了前向引用声明,但是在提供一个完整的类定义之前,不能定义该类的对象(没有完整的定义)
1 |
|
error: field ‘b’ has incomplete type ‘B’
那为啥函数行啊??
只是告诉编译器这个函数的参数是这个类的,函数定义时不需要内存,调用时需要内存
而如果定义一个类对象b,而编译器此时并不知道B类内有什么(数据成员、函数等),不能定义
解决方案:使用对象引用或指针(编译器知道这是一个B类的引用/指针即可)
1 |
|
5. 函数指针-typedef
1 |
|
typedef?感觉这是我看到最好的例子了
1 |
|
在编程中使用typedef目的一般有两个,一个是给变量一个易记且意义明确的新名字,另一个是简化一些比较复杂的类型声明。
1 |
|
上面讨论的 typedef 行为有点像 #define 宏,用其实际类型替代同义字。不同点是 typedef 在编译时被解释,因此让编译器来应付超越预处理器能力的文本替换。例如:
1
typedef int (*PF) (const char *, const char *);
这个声明引入了 PF 类型作为函数指针的同义字,该函数有两个 const char * 类型的参数以及一个 int 类型的返回值。如果要使用下列形式的函数声明,那么上述这个 typedef 是不可或缺的:
1
PF Register(PF pf);
Register() 的参数是一个 PF 类型的回调函数,返回某个函数的地址,其署名与先前注册的名字相同。做一次深呼吸。下面我展示一下如果不用 typedef,我们是如何实现这个声明的:
1
int (*Register (int (*pf)(const char *, const char *))) (const char *, const char *);
(百度百科typedef)
6.虚函数
6.0. 函数的调用方式
直接调用
根据函数名称直接调用函数(编译时期已经确定)
- 普通的函数调用 a.fun()
- 对象的普通成员函数调用
- 对象的虚函数调用
间接调用(通过查虚表调用)
- 对象指针调用
- 对象引用调用
父类*/& p=&子类对象
p->fun();
若明确类域范围,不论指针或引用均为直接调用
p->子类::fun();
6.1. 虚函数原理
- 调用方法是间接调用(先查虚表地址,再查虚表中的虚函数指针)
- 增加了virtual关键字的对象头部4个字节是虚表地址(单继承情况下前四个,n继承前4n个是虚表地址)
6.2. 虚表大小的确定
- 编译时期确定
- 运行时内存中没有个数的表示(就是说内存中不存,它有n个虚函数)
- 虚表不以00结尾
6.3. 虚表中虚函数的顺序
- 子类继承了所有的父类虚函数(共有继承)
- 父类的虚函数顺序决定子类虚函数顺序
- 子类若重写了父类的虚函数,则会在子类的虚表中覆盖对应位置的函数(函数覆盖)
- 子类未重写某虚函数,则会直接继承父类的该虚函数
- 子类自己的新的虚函数会出现在自己虚表中父类所定义的虚函数后面
6.4. 虚析构函数
总的来说虚析构函数是为了避免内存泄露,而且是当子类中会有指针成员变量时才会使用得到的。也就说虚析构函数使得在删除指向子类对象的基类指针时可以调用子类的析构函数达到释放子类中堆内存的目的,而防止内存泄露的.
6.5. 虚函数多态性
成员函数中调用虚函数,具有多态性;
构造和析构函数中调用虚函数,没有多态性。
6.6. 纯虚函数与抽象类
父类为子类提供接口
纯虚函数:在虚函数后面加=0,不需要实现代码(要是想写也可以写),代表该类不能实例化(不能创建这个类的对象)
抽象类:包含纯虚函数的类,不能定义抽象类的对象,但是可以定义一个抽象类的指针和引用
特例
纯虚析构需要有实现
这个定义是必需的,因为虚析构函数工作的方式是:最底层的派生类的析构函数最先被调用,然后各个基类的析构函数被调用。这就是说,即使是抽象类,编译器也要产生对析构函数的调用,所以要保证为它提供函数体
1 |
|
7. 菱形继承(多继承二义性)
(这顺序真随缘)
1 |
|
若Furniture中有成员/函数,在使用SofaBed的对象调用的时候会导致不明确
因为菱形继承,SofaBed的对象中有两套Furniture
使用虚继承:
1 |
|
内存结构改变,Furniture的成员/函数保存在同一个地址中
8. 运算符重载&函数模板
不管了,上代码吧
1 |
|
9. 深复制&浅复制
上面的第21行开始
深复制新申请一块空间存,浅赋值则两者是一样的
10. 对象指针
存放对象地址的变量
一般对象指针 比如父类指针指向子类对象
this指针 非静态成员函数中的特殊指针
(也就是说static没有吗?)(qs)
1
2
3
4
5
6
7
8
9
10class A{
public:
static int fun();
private:
int a;
};
int A::fun(){
this->a=0;
}
error: 'this' is unavailable for static member functions指向类的非静态成员的指针
从来没用过…
1
2
3
4
5
6
7
8
9
10
11
12
13class A{
public:
int x;
void fun(int n){cout<<n<<endl;}
};
int main(){
A a;
int A::*ptr=&A::x;//类指针,指向类成员
void (A::*funptr)(int n)=&A::fun;//指向函数
a.*ptr=5;
(a.*funptr)(7);
return 0;
}指向类的静态成员的指针
使用普通的指针指向和访问
1
2
3
4
5
6
7
8
9
10
11class A{
public:
static int x;
};
int A::x=0;
int main(){
A a;
int *ptr=&A::x;
cout<<*ptr<<endl;
return 0;
}
11. 函数模板
函数模板定义用关键字template开始,之后是使用<>括起的模板参数表
1 |
|
若写了模板但是没有函数调用,则这个函数实际上没有生成(没有实例化)
因此函数指针要指向模板的实例化