C++ 突袭
参考视频1:https://www.bilibili.com/video/BV1ZT4y1C7WR/?p=24&spm_id_from=333.788.top_right_bar_window_history.content.click
参考视频2:https://www.bilibili.com/video/BV15e411V7W9?p=30&vd_source=ecc5d7a46ba5b1bb791ce5ea5a39fe39
基本知识
- 必须在函数声明中声明默认参数!
- 默认参数必须在形参列表的结尾!避免歧义
- 函数声明就是函数的身份证,外部调用方不看定义只看声明
- 和 取地址 类似
结构体
- 第一种是最基本的结构体定义,其定义了一个结构体A。
- 第二种则是在定义了一个结构体B的同时定义了一个结构体B的变量m。
- 第三种结构体定义没有给出该结构体的名称,但是定义了一个该结构体的变量n。
也就是说,若是想要在别处定义该结构体的变量是不行的,只有变量n这种在定义结构体的同时定义变量才行。
//第一种
struct A {
int a;
};
//第二种
struct B{
int b;
} m;
//第三种
struct{
int c;
} n;
-
第四种结构体定义在第一种结构定义的基础上加了关键字typedef,此时我们将struct D{int d}看成是一个数据类型,但是因为并没有给出别名,直接用D定义变量是不行的。如D test;,不能直接这样定义变量test。但struct D test;可行。
-
第五种结构体定义在第四种结构体定义的基础上加上了别名x,此时像在第四种结构体定义中说得那样,此时的结构体E有别名x,故可以用x定义E的结构体变量。用E不能直接定义,需要在前面加struct,如struct E test;。
-
第六种结构体定义在第五种的基础上减去了结构体名,但是若是直接使用y来定义该结构体类型的变量也是可以的。如y test;。(常用)
//第四种 struct = D{ int d; }
typedef struct D{
int d;
};
//第五种 struct E{ int e; } = x
typedef struct E{
int e;
} x;
//第六种 struct { int f; } = x
typedef struct {
int f;
} y;
B选项应该换行 1) #define AA struct aa 2) AA { int n; float m; }td1;
函数
overload 函数重载
要求:
- 函数的名字相同
- 参数列表(数量,类型)不同
注意:与返回值无关
问题 --避免overload歧义
inline 内联函数
内联函数:指建议编译器编译时将某个函数在调用处直接展开,避免运行时调用开销。
内联函数的要求:若一函数功能简单,则函数调用的额外开销占比较高。
注意:inline只是建议
- 并不是写了 inline 关键字就一定会被内联,只是提出建议,由编译器决定是否采纳
- 内联这个动作发生在编译时,提升运行时的效率
面向对象
面向对象:既然随着系统参与实体的增多,过程变得复杂,那就不费力描述每一个可能的过程了,转而描述每一个实体。
实体=属性+行为
从面向过程到面向对象 对于问题:求解不同图形的周长和面积
抽象:class 类和对象
面向对象的四个特征之1:抽象=分析问题,识别出各个实体及其属性和行为
每个实体 = 一个类class = 定义它的属性(成员变量) + 行为(成员函数)
例如:
class Circle {
private:
double radius;
public:
Circle(double radius) {
this->radius = radius;
}
double getRadius() {
return radius;
}
};
简单理解:结构体 + 行为(成员函数) = 类
- 事实上C++中也支持结构体定义成员方法,两者并无本质区别了
- 根据使用场景选择结构体或类:
•结构体:主要记录数据,极少行为(如资源配置信息、网络连接信息等)
•类:既有属性也有行为(如学生类、用户类、玩家类等)
特殊的成员函数:构造函数与析构函数
constructor 构造函数:对象实例化时,分配空间后,完成对象的构造工作(如初始化成员变量、分配资源等)
destructor 析构函数:对象生命周期结束时,回收空间前,完成对象的清理工作(如释放资源等
this 指针
• this的中文含义:这、这个、当前这个
• this指针在类定义内部使用,指向当前对象
- 只有非静态类成员才有this指针。
this指针保证每个对象拥有自己的数据成员,但共享处理这些数据成员的代码。(√)
封装 Encapsulate
封装:将类的一些成员变量或方法藏起来,不允许外界直接操作
- 不允许直接操作 ≠ 不允许操作,而是通过自定义的特定方法操作
getter / setter 方法
为某些私有成员变量提供外部读写方法:get_xxx(读) / set_xxx(写)
- getter 和 setter 一般是 public 的,不然没意义
- getter和setter函数的名字没有要求,只是一般用get_xxx,不是特殊函数。
getter函数通常会被设置为const函数,setter函数则通常接收const参数
补 const常成员函数:不能修改类成员变量,如果修改了,回过不了编译
//getter函数的通常格式(设xxx的类型为T)
T get_xxx() const {
return xxx;
}
//setter函数的通常格式(设xxx的类型为T)
void set_xxx(const T& xxx) {
this->xxx = xxx;
}
继承和多态
继承
人和学生之间是层次递进关系,而非并列关系:若一个实体是学生,则一定是人
• 那么在定义学生类时,无需把属于人的那一部分属性和方法再定义一遍
• 直接让学生继承自人:获得人的属性和方法
• 目的?代码复用
父类和子类:不同身份之间的层次递进关系
- 张华首先是一个人,具体一点是一个学生,再具体一点是大学生...
- 人 ← 学生 ← 大学生 ← ...
类的派生与继承 Inherit
继承方式:决定父类成员在子类中的访问控制属性
- 派生类继承了基类的private成员,但是不能直接访问,只能通过派生类的友元函数访问
- 公有继承不改变控制属性,保护继承和私有继承指示父类成员在子类中的相应控制属性
父子同名成员并存
- 子类中同时有两个n和两个func()
- 直接使用默认指子类成员
- 如果需要使用父类的成员,需要使用父类名字空间显式指明
class Father {
public:
int n = 1;
void func() {
cout << "This is Father";
}
};
class Son : public Father {
public:
int n = 2;
void func() {
cout << "This is Son";
}
void set() {
Father::n = -1;
n = -2;
}
};
int main(){
Son son;
son.func();
son.Father::func();
son.set();
cout << son.Father::n;
cout << son.n;
}
分析:
- 此处问的是“通过派生类对象访问”,意即在外部以a.xxx的方式访问。
- 如果题目问“派生类能访问的”,则意指在类定义内部访问,则除了基类私有成员以外的都可访问
virtual 虚函数 和虚类
回顾:继承来源于同一对象可能有多重身份,且这些身份有层级递进关系
• 实践中会有这类情况:
• 父类中的某些行为需要在子类中被更加具体地细化
• 父类中的某些行为不可确定,必须在子类中实现
于是虚函数的概念产生:父类的虚函数可以在子类中被重写(override),即重新实现,但参数和返回值必须保持一致!
此外含有虚函数的类叫做虚类
分析:
- 子类重写基类的虚函数时,可以继续通过virtual关键字申明为虚函数,表明允许被它的子类(孙子)继续重写,
- 如果没有这个需要或者不允许继续重写则不加
纯虚函数、抽象类和接口
纯虚函数:不实现,仅声明为纯虚函数,留待子类里重写定义
因为 某些类是抽象的,不是具体的,不可独立存在:
•我可以是男人或女人,但不可能仅是“人类对象”
•可以有矩形对象、三角形对象... 但不能有“图形对象”
含有纯虚函数的类叫抽象类,仅有纯虚函数的类叫接口
注意: 抽象类和接口不可实例化
分析:
“建立对象”指实例化,因为抽象类中的纯虚函数没有定义(留给子类来实现),所以抽象类不能实例化
虚继承
https://blog.csdn.net/crystal_avast/article/details/7678704
友元函数
- 友元只是破坏了类的隐藏性和封装性,不能被继承,没有this指针 ,没有破环继承性机制。
- 可以直接调用,不需要通过对象或者指针。
多态 Polymorphism
面向对象的四个特征之4:多态
• 已多次强调:同一对象可以有多重层级递进身份
• 有这种情况:同一对象在不同的场合中,被外界所关注的是不同的身份
• 但他的本质和应有的行为并不会因外界眼光而改变
理解多态:
- 一个对象就是内存中的一个实体,它只能属于一个确定的类:最精确的子类
- 它可能在不同处被视为不同身份,但它本质行为方式应与外界如何看待它无关!
问题:如何保证一个对象执行其最本质身份的行为?
- 利用虚函数重写 + 指针!!
- 指向子类对象的父类指针!!!
多态的意义:代码复用
- 通过“虚函数 + 指向子类对象的父类指针”,可以把不同的子类统一视为其共同父类
- 于是无需针对不同的子类写相同逻辑,统一视作其共同父类,利用指针操作即可
本质是虚函数将能做什么和怎么做分离
,父类指定要做什么,子类来实现具体做法
问题:写一个函数,求一个图形的面积和周长之比
• 没有多态:需要为每种图形都实现一个函数
• 有多态:只需实现一个函数
• 它不关心这个图形具体是什么,反正能求面积和周长即可
静态联编与动态联编
• 上述利用虚函数重写+指针实现的多态特指运行时多态,与之相对的是编译时多态
联编(bind):确定具体要调用多个同名函数中的哪一个
- 静态联编:在
编译时
就确定了要调用的是哪个函数(根据多个重载函数的参数列表确定) - 动态联编:直到
运行时
才知道实际调用的是哪个函数(根据指针指向对象的实际身份)
也就说:
- 静态联编 = 编译时多态 = 函数重载 = overload
- 动态联编 = 运行时多态 = 虚函数重写 = override
运算符重载
https://www.cnblogs.com/kingwz/p/16554387.html
struct node {
int aa;
int bb;
int cc;
// bool operator<(const node &x) const {
// return true;
// }
bool operator<(const node& x) const {
return true;
}
};