[0.x] 前言 🎓
对C++了解的越多,你就会越讨厌这门语言……
[0.1.x] 课程相关
- 课程信息 - 教师:翁恺 - 课时:16节 - 智云课堂链接 - 阅读材料 - Bruce Eckel’s Programming Blog - Thinking in C++ - Thinking in C++, 2nd Edition, Volume 1 - Thinking in C++, 2nd Edition, Volume 2 - Bjarne Stroustrup’s Homepage - The Design and Evolution of C++ - cppreference - C++ Primer - 菜鸟教程(可以用于中文术语对照) - XXJJ的笔记1 C++面向对象 |
---|
[0.2.x] 学习日志
12345 123 123 12345 |
【2022】 [1.13] L1 [1.14] L2 [1.15] L3 L4 |
[1.16] L5 [1.17] L6 [1.18] 整理 [1.19] L7 |
[1.21] L8 T1 [1.22] L9 [1.23] L10 [1.24] L11 T2 整理 |
[1.25] L12 整理 [1.26] L13 T5 [1.27] L14 [1.28] L15 L16 |
[1.29] T6 |
---|---|---|---|---|---|
[0.3.x] 笔记结构
| > 内容上,笔记中大多数情况下只记录我认为有必要记录下来的事情,加上智云自己的问题,所以可能和课程内容相比有些缺失。
- 主要按照课程进度进行追踪记录
- 对应的知识笔记会在对应知识点附近以🔗&超链接的形式出现
| | —- |
[0.4.x] 练习&作业
[1.x] 课程笔记 📖
[1.1.x] L1
- cin``cout 本质上都是变量,而对应的<<``>> 是经过重载的属于这些变量的位移运算符,表示的含义是将之后的 tocken 塞入输出流 / 从输入流读取,并且返回一个 cin / cout 变量。- 🔗 **string** 区别于int 等,是一个类,形如name.length() 的语句体现了面向对象语言的思想。- 🔗动态内存分配 new``delete [01:23:00] |
---|
[1.2.x] L2
|
- 🔗引用reference [00:05:00]-[00:32:00]
- “如果你要传一个(大)结构体进函数,那么最好的办法是传一个指针而不是结构体。”——《C圣经》
- 然而在所有传入指针的函数中,都存在一个安全问题——即我不希望这个函数会对我传入的这个指针指向的变量有影响,所以有这样一种操作:
- void function(**const** typename *p);
可以让函数虽然能得到地址,但无法修改变量
- 声明和定义的区别:
- 对于编译器来说,声明(declaration)不产生实际代码,仅仅表示“存在这么一个东西”;而定义(defination)产生实际代码,它直接决定了“这个东西是什么”这件事
- eg1.在定义全局变量x
之前(代码文本上的“之前”)的位置想使用该全局变量x
,可以使用extern int x;
来声明它(也就是说不能在这里给它赋初值)
- eg2.struct node{ int x,y; };
本身和其成员变量在此处都算声明
- 🔗引入类的概念
-this
指针[00:55:00]
- “所有普通的成员函数都有一个隐藏的参数,是它参数表里的第一项,是Cpp编译器做的语法糖,而这个参数就叫this
,是这个结构体的指针。”
- “成员函数在使用的时候会默认在引用成员变量的地方前加上this
指针,以便我们更方便地写代码。”即我们可以直接在成员函数中使用成员变量。
- “C++98所有的代码都是可以被翻译成C的。”
- “在C++中,class
和struct
基本上一样,区别在于访问权限。”
- 在class
中,你可以用public:
和private:
来标识哪些是公开的,哪些是私有的
- 一般来说,数据成员应该是私有的,方法是对外公开的
- 🔗**::**
范围解析运算符resolver
- container一般指存放对象的容器,常见操作为get()
和put()
- stash一般指可以扩大的container,且stash可以放任何同类型的东西,常见操作为add()
和fetch()
- 类和对象的关系
- 类是对象的抽象,对象是类的实例
| | —- |
[1.3.x] L3
|
- OOP Characteristics [00:17:00]
- Everything is an object.
- A program is a bunch of objects telling each other what to do by sending messages.
- “这里的关键词是”what”,为什么,因为我只需要一个结果,这是一个请求,我不需要告诉它如何做,不是”how”。”
- “对象和对象之间的交互方式是要求对方做什么,而不是指点对方做什么。”
- Each object has its own memory made up of other objects.
- Every object has a type.
- All objects of a particular type can receive the same messages.
- “这句话也需要反过来理解,所有能接收相同消息的我们当做同一种类型的对象。”
- “其实oop的核心就是封装、继承、多态。”
- 🔗构造函数 C’tor **(constructor) [00:22:00]
- 🔗析构函数 D’tor (destructor) [00:55:00]
- “通常我们在析构函数里做的事情是处理(消除)内存以外的其他资源。”
- definition of a class**
- 我们希望一个类的被分离放在两个文件里
- 在xxx.h
文件中,我们希望存放类的声明及其函数的原型
- 在xxx.cpp
文件中,我们希望存放这些函数的body
- 而这两个文件的前缀应该相同
- 也希望一个头文件只声明一个类
- 并且希望它符合 “standard header file structure” (下方)
- Compile Unit [01:15:00]
- header = interface [01:20:00]
- standard header file structure
```cpp
ifndef HEADER_FLAG
define HEADER_FLAG
// …
endif // HEADER_FLAG
<br />- 抽象思维 [01:24:00]<br />- TDD测试驱动开发<br /> |
| --- |
<a name="CY4Zb"></a>
## [1.4.x] L4
| <br />- 构造函数的初始化列表 [00:23:00] (没仔细讲,提了一嘴)<br />```cpp
Clock::Clock():
hour(24), minute(60) // hour和minute作为Clock类的两个成员对象做初始化
{
// ...
}
- 容器Container(Collection) [00:33:00]
- 🔗STL 标准模板库
- 泛型
|
| —- |
[1.5.x] L5
- 🔗函数 [00:04:00] - 🔗初始化 - 初始化列表 [00:26:00] - 构造函数支持🔗重载(Overload), 类似Go的interface,使用满足参数条件的那个 - 默认构造器的必要性 [00:50:00] - 🔗函数默认参数Default Argument - 🔗C++ access control [01:14:00] - 🔗内联函数 inline |
---|
[1.6.x] L6
- 🔗 **const** [00:04:00]- 指针& const 相关 [00:16:00]- 指针与对象相关 [00:30:00] - 对象& const 相关 [00:34:00]- 🔗 static [00:45:00]- namespace [01:15:00]- using [01:18:00]- 继承(Inheritance) - 嵌入对象(Embedded Objects),需要被初始化或者存在默认构造器,我们希望他们是private的 |
---|
[1.7.x] L7
因为智云课堂的关系,我实在听不清内容,所以这节课我以文本学习的形式为主 主要参考资料为Thinking in C++(参见[0.1.阅读材料]) 🔗继承Inheritance 🔗继承中的权限控制 |
---|
[1.8.x] L8
- 🔗多态Polymorphism [00:09:00] - 两个技术基础: - 🔗向上造型Upcast[00:20:00] - 🔗动态绑定Dynamic Binding - 多态变量Polymophic Variable [00:52:00] - 🔗切片Slice [00:15:00] - 🔗向上造型Upcast - 🔗虚函数 virtual [00:38:00]- 虚函数表 [01:00:00] - 🔗动态绑定与静态绑定 - 🔗Override关系 - 子类override了父类的函数可以返回父类的函数的返回值的子类(这个情况只适用于指针和引用) - 父类的函数返回一个水果,子类override以后可以返回一个苹果(但是你不能直接返回一个苹果对象) - 本质和上面的向上造型类似 - 我们不希望出现重新定义父类中的非virtual函数的行为(出于效率考虑,我们更希望静态绑定) - 重定义default value是没有效果的,因为这件事是在编译时刻进行的,但多态特性在运行时刻才会体现,所以不会起作用(参考视频中的说法,就是default value是由指针的“类模板”决定的,而非对象的“实际情况”决定的) - 在 Shape``Ellipse``Circle 这个例子中,Shape 的概念非常抽象(我可以让你绘制一个圆,但我无法让你绘制一个形状),所以Shape::render(); 并没有实际内容,我们可以通过定义Shape::render() = 0; 来指定它是一个纯虚函数,此时Shape 是一个抽象类,它不能用来制造对象实例- 在习惯中,如果一个类没有成员变量,且所有函数都是虚函数,则它成为一个 interface 类 |
---|
[1.9.x] L9
| cpp
void f(){
Stash Students(); // 这是一个函数声明而非对象声明,是为了向古老版本兼容
}
xxjj详细讨论该问题的文章:无参构造对象时为什么不加括号
🔗拷贝构造函数Copy C’tor [00:30:00]
🔗重载运算符Overloaded Operators [01.19.00] |
| —- |
[1.10.x] L10
| 一个操作在结果和副作用的区分 [00:22:00]
🔗成员/全局 运算符重载选择策略
🔗运算符重载规范 [00:08:00]
🔗输入输出流与重载
- 🔗创造一个控制符 [00:42:00]
🔗赋值与拷贝构造 [00:43:00]
🔗重载类型转换
🔗初始化 [01:20:00]
函数传入传出 [01:23:00]
- 一般传入时我们更希望使用指针/引用而非直接传对象,有时更希望有const
修饰\
- 具体来说:
- 如果你希望存储一个对象,那么就传入一个对象
- 如果你只希望得到它的值,那么传入一个常指针或常引用
- 如果你想对该对象做一些事,那么传入它的指针或者引用
- 如果你在函数内新建了一个对象,那么将整个对象传出
- 如果你希望传出的是一个指针,那最好只传出传入的指针
- 永远不要在函数内新建某些东西并传出它的指针
🔗左值与右值 [01:33:00]
- 右值引用的作用 [01:38:00]
|
| —- |
[1.11.x] L11
| 🔗模板Template [00:05:00]
- 利用模板,你可以进行泛型编程
- 这意味着你可以将函数的类型当做参数
- 函数模板
- 类模板
weak
关键字 [01:35:00] |
| —- |
[1.12.x] L12
| 异常Exceptions [00:05:00]assert
[00:39:00]
- 常用于集成测试检查
- catch
后的括号中可以使用...
来捕捉任何异常;或者<typeName> &<refName>
来捕捉异常抛出的对象,然后在后面使用<refName>
。其中,前者(即catch(...)
)必须放在最后
- 在catch
语句下再次throw;
(之后不带参数),会将已经捕捉到的异常再次向上抛出
- 程序会根据catch
书写的顺序检查异常,并且只处理catch到的第一个异常
继承与异常
- 在顺序方面,将某个异常类的子类放在该异常类之后会被认为是不合理的,因为无论如何这个异常类的子类都不会被捕捉到
- 可以用下图方式来声明一个函数可能抛出什么异常
- 如果函数中抛出了没有在一开始声明的异常,会被认为是unexpected exception
- 如果catch()
直接捕捉对象而非引用,容易造成内存泄漏
|
| —- |
[1.13.x] L13
| 引用计数reference counting:记录指向该对象的指针数量
智能指针Smart Pointer [00:06:00]
- 模板
- 遗传
- 引用计数
主要是智能指针实现的解析,但是感觉看课比如看代码,所以就先这样吧x
|
| —- |
[1.14.x] L14
| 类的设计方法
- 目标:
- 易于理解
- 易于维护
- 具有可重用性
- 责任驱动设计Responsibility-driven design
- 耦合Coupling
- 内聚Cohesion
- 重构Refactoring
耦合关系
- 耦合指两个分开的单元之间的联系
- 如果两个单元联系很紧密,我们称之为紧耦合
<解耦>
- 我们更希望是松耦合,这样理解代码会简单很多
- 实现松耦合的技术:
- 回调call-back [00:36:00]
- 中央消息机制message mech [00:37:00]
内聚关系
- 单个的单元负责的事情需要有所限制(这个单元可以是函数、对象甚至是变量)
- 如果一个单元只有一个单一的任务,则认为它具有高内聚
- 我们更希望是高内聚,即尽可能让一个单元仅负责一个任务
一种Bad Design的标志——代码复制Code duplicationhandler
enum
|
| —- |
[1.15.x] L15
| #include <functional>
lambda
函数
流Stream
- 特征:[一维] [单向]
继承构造函数**using**
派生类无法获得默认参数值!
委派构造函数
但是我们要避免出现循环链接 |
| —- |
[1.16.x] L16
| 浅拷贝 & 深拷贝
浅拷贝
- 编译器自动执行的拷贝
- 在有指针的情况下,这种拷贝是有害的
移动构造
- “引用”的初始化一般被称为“绑定”
完美转发
|
| —- |