type
status
date
slug
summary
tags
category
icon
password

一.前言

一些神话
在学习C++之前,我们应该先学好C语言。
C++和C语言的区别是C++是一种面向对象的语言,C是面向过程的语言
越是低级的代码效率越高,汇编> C > C++
当一个C++程序员最重要的是数学要好
——Bjarne Stroustrup
为什么是C++?
notion image
如果同学们比较关注时事,会发现2025乃至未来的很长一点时间内,AI都将成为社会的主流发展方向。这里面py荣登编程语言榜首并且遥遥领先的原因也是因为AI。
AI对编程学习的改变:程序员可以将大模型作为外置大脑,编程无需去关心那些过程上的简单逻辑,只需要去关注目的。比如:
曾经很多程序员需要大量记忆,练习,思考才能习得的技能变得不再重要,比如操作SQL的能力、模式匹配、语法和逻辑分析、复杂for循环分析等。
还有些任务是AI难以完成的,比如?
逻辑电路设计,你的卧室有一盏灯,门口和床头各有一个开关,这两个开关都可以独立控制这盏灯,如何用代码实现这一逻辑?尝试用AI去实现这一问题。

二. C++基础知识

首先我们默认来上课的同学都有C语言基础。在C的基础之上,我们来学习C++

构造/析构函数

虽然表面上来说,C++和C最大的区别就是C++有类,而C只有结构体。但实际上C++的类和结构体二者并没有很大的差别,你可以把C++的struct看作一个都是public属性的class。
C++和C最大的区别是C++有构造函数和析构函数,构造函数就是对象创建自动调用的函数,析构函数就是对象释放自动调用的函数。
这个改动看起来只是一个语法糖级别的改动,只是让程序员少写一个初始化函数和释放函数,但实际上这个改动产生了一个C++当中极其重要的概念——RAII(资源获取即初始化)
一些准则
首先需要明确的是一个优秀的C++程序员需要具备的能力,我们前两天的课程以一些C++程序员都应该遵守的准则来切入——

区分静态和运行时

代码的直接表达

看一个实际的例子
再来一个:
在日常生活中,我们会经常用到数据表合并。比如新生入学的表和报名选课的表,合并后就是每个学生的选课情况。
student_id
name
age
1
王宁
2
张超
22
3
李敏
20 21
course_id
student_id
course_name
101
2
数学
102
3
体育
103
1
历史
name
course_name
王宁
历史
张超
数学
李敏
体育

不要泄露资源

资源就是任何需要你释放的东西,其中C++泄露最多的就是内存,也就是人们常说的内存泄漏

不要浪费时间或者空间

低耦合

什么是高耦合?
有一个案例,端游《英雄联盟》代码量最大的英雄——水晶先锋和琴瑟仙女。比如英雄在泉水自动回血的机制,代码中不是检测到英雄的位置在泉水的回血范围,然后就增加英雄的HP,而是放一个隐身的琴瑟仙女施放回血技能,很显然这样做的好处就是只要你完成琴瑟仙女的回血技能,你可以在任何需要回血的场景放一个隐身的琴瑟仙女。缺点就是当琴瑟仙女这个英雄的逻辑出现问题的时候,所有与之相关的回血场景都会出现问题。
什么是低耦合?
比如windows的文件夹系统,有不同的排布方式:平铺、大图标、小图标。文件夹的数据什么都是固定的,只是绘制方式被抽离,也就是经典的MVC设计模式。这样的设计之下当需要一种全新的UI,只需要添加新UI的绘制逻辑就行了。

三. C++经典特性

面向对象

封装:抽象模型,区分内外
继承+多态:
1. 继承基类以获取其内容,来解决代码重用的问题。
2. 声明某个子类完全兼容于某基类。
3.基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同。

虚函数

C语言是面向过程的语言,面向过程意味着很多概念要靠用户来定义,然而编程语言的概念和现实世界的概念往往存在一些偏差,如果编程语言不能更好地对应于现实世界(面向对象),那么有些程序写起来就会非常困难。
我们拿一个现实世界的例子来看,我们去超市购物,挑选不同的商品放进购物车,等到都选好后集中扫码,超市的结算系统会将所有商品的价格相加。
这里的问题就来了:我们日常生活中所说的将每一个商品的价格相加,实际上说的是将具体商品的价格相加,也就是可乐的价格加胡萝卜的价格加牛奶的价格,但是在代码中是直接将商品的价格提取出来。
在C和C++当中,类的数据成员和函数成员不是同样的概念,或者说,类的数据是每一个类实例都私有的,但是类的成员函数是所有类实例共用的。所以C和C++的类实例调用函数的时候只看类的纸面类型,不看对象的创建类型,当一个可乐变成一个商品的时候,它默认就访问不到可乐的函数了。
💡
1.C++中class和struct基本是等价的,一般来说当class中需要区分private成员的时候就定义为class。
2.优先将成员定义为private,减少外部曝光。

面向过程

比如我们实现一本字典,主要用于用户查询。使用面向对象的角度思考问题,我们需要定义一个类
程序之外,我们可以将字典存在一个文件中,结构大概是
这就是面向对象的角度看问题,然而如果我们换一个角度,常用汉字有几万个,拼音只有几百种类,这样的数据组织方式为对人类友好但是对机器不友好的,因为会存储大量重复数据。因此我们可以根据这种特性将二者分离存储。

泛型

模版:用来生成代码的代码。
类模板
函数模板
模板元编程
模板元编程利用了模板在编译器生成代码,现在已经被constexpr代替,比如上述代码只需要写成

函数式

函数式编程对我们的启发是,写代码的时候尽量使用纯函数,纯函数是一种不会受到外部因子影响,输出和输入严格相关的函数。纯函数的好处是只要单元测试通过,它永远都不会出现问题。

四. STL

1尽可能使用标准库
原因:节省时间。不要重新发明轮子。不要复制他人的工作。当其他人进行改进时,从他们的工作中受益。当你进行改进时,可以帮助其他人。越来越多的人知道标准库。与您自己的代码或大多数其他库相比,它更稳定、维护良好且广泛可用。

容器

array

array是静态数组,相比原生数组提供了更好的封装和安全性,同时保留了原生数组的高性能特性。现代C++中我们推荐使用array作为静态大小数组的实现,除非有特殊原因才需要使用原生数组。

vector

动态数组,元素内存连续,需要注意的就是它大小改变的过程。
notion image
vector扩容示意图

deque

多个数组关联起来的双端队列,主要用于在两端高效插入删除,可以看作元素是数组的链表
notion image
deque内存布局示意图

list

双向链表,任务列表就是一个很经典的链表的例子

forward_list

单向链表,比如一个存储大量短信消息的消息队列
💡
链表的内存结构较为简单,但是需要注意的是双向链表的每个节点要比单向链表多一个指针,所以能用单向链表的场景不要使用双向链表。另外由于链表的元素内存不连续,访问链表会出现内存不命中的情况,所以链表的实际性能要比数组低很多,哪怕是在简单的插入场景下。

set、multiset、map、multimap

notion image
map、set内存布局示意图
这里我们把出现频次较低的set和出现频次更高的map放在一起讲,因为map就是一个特化的set,是一个允许携带更多信息的set,或者反过来说set是一个没有携带value的map。它们的底层数据结构都是红黑树。因为红黑树自带排序的效果,所以以上容器都是有序的,至于是否允许出现相同元素,完全是插入元素时的人为操作,当插入比较元素key的时候发现相同是元素继续执行的是multi前缀的容器,直接丢弃的是无前缀的。
💡
红黑树插入的时候可能会导致树的不平衡,不平衡的时候红黑树会进行一些平衡操作,这个时候会导致原有的内存布局发生改变,如果依靠地址指针去访问元素会导致刻舟求剑的效果。

unordered_set、unordered_multiset、unordered_map、unordered_multimap

notion image
hash表内存示意图
unordered前缀代表无序,以上几个容器的底层数据结构都是哈希表,哈希表本质是一个数组和一个哈希函数组成,这个数组的每一个节点都是一个链表或者红黑树,被称之为,元素插入的时候会通过哈希函数计算其应该放在数组的哪一位置。当出现相同位置时会有冲突处理机制,值得注意的是哈希表也会有扩容机制,当哈希表的负载因子(已有元素数除以总大小)高过某一限度的时候,哈希表就会自动扩容。
💡
hash表扩容的时候相当于重新申请一个最外层数组,然后将元素一个一个重新计算hash函数添加进去,这个过程也会有一定的开销,在使用的时候最好能预设大小来避免出现太多次的扩容。
顺序不敏感的场景优先使用unordered类型容器。
问题
为什么上面的例子中没有提到stack这一数据结构?

迭代器

迭代器是一种用于遍历和访问容器中元素的对象,它提供了一组统一的接口,使得可以以相似的方式处理不同类型的容器。STL中的容器(如vector、list、set等)都提供了迭代器,用于访问容器中的元素。
💡
在熟悉了stl的容器后,和我们熟悉的数组对比,会发现数组是很容易通过数组名得到其内部成员的指针的,迭代器的思想就是由此而来,迭代器可以看作是一个指向容器元素的指针,只不过迭代器重载运算符,所以对于迭代器的+并不是简单对地址进行运算。
迭代器失效
迭代器失效是指在对容器进行一些修改操作后,原本有效的迭代器可能不再指向容器中的有效元素,或者指向的元素值发生了变化。这可能导致使用失效的迭代器进行访问或操作时出现未定义的行为
随堂作业
请写出vector list set unordered_set这四种容器的迭代器失效场景
此外迭代器还有一些零散的知识点,比如c开头的迭代器,r开头的迭代器。

算法

std::sort:对指定范围内的元素进行排序。它使用快速排序或者堆排序来实现。
std::find:在指定范围内查找指定的值,返回第一个匹配的元素的迭代器。
td::transform:对一个范围内的元素进行变换,并将结果存储到另一个范围中。
std::copy:将一个范围内的元素复制到另一个范围中。
std::accumulate:对一个范围内的元素进行累加操作,返回累加结果。
std::count:统计指定范围内等于给定值的元素个数。
std::unique:移除指定范围内的连续重复元素,返回指向新范围的尾后迭代器。
std::reverse:反转指定范围内的元素。
👋
学习stl算法的诀窍:请不要在代码中出现显式的for循环,在写循环之前先思考自己所写的需求是不是一种已经实现的算法,stl有100多种算法,如果一个人的代码里有大量for循环,这意味着他不懂stl算法。

五.类型转化

static_cast

static_cast属于静态转化,也就是编译期就已经完成的转化,编译器会负责转化过程,因此如果转化失败编译器会发出警告。
使用场景

reinterpret_cast

reinterpret_cast也属于静态转化,但是reinterpret_cast是属于比较低级的转化,或者说更符合“强制转化”的定义,因此reinterpret_cast的转化是不会报错的,如果进行错误的转化会导致未定义行为。reinterpret_cast常用的场景一般是指针转整数或者是整数转指针等场景。

dynamic_cast

RTTI 提供了一组用于查询对象类型的工具,主要包括 typeid 运算符和 dynamic_cast 运算符。
C++中对象指针的向上转化是不需要任何额外的操作的,但是向下转化是无法直接完成的,需要在运行时检查对象的实际类型,并决定是否转化。dynamic_cast专门用于这种情况的转化。
  • 对于指针类型,dynamic_cast失败时返回空指针,成功时返回转换后的指针。
  • 对于引用类型,dynamic_cast 失败时抛出std::bad_cast 异常,成功时返回转换后的引用。
上面的例子实际上是dynamic_cast的不好用法,因为在我们明确知道ptr是Child类型的时候,我们可以直接使用static_cast来转化。
综上所述,dynamic_cast在一般情况下都是不建议使用的,如果你的代码中需要用到dynamic_cast的话大概率是你的程序设计存在缺陷,请优先考虑虚函数来实现所用功能。一般我们会在单元测试这样的调试场景下使用dynamic_cast。

const_cast

const_cast用来去掉const类型的const限制,常用的场景是如果一个函数的参数需要一个非const类型,然而你手头只有一个const类型。当然,也可以去掉类型的volatile 和 __unaligned属性。
🔔
不要用const_cast去修改const类型的对象值,这属于未定义行为

六.工具篇

使用Visual Studio开发C++ 而不是vs code或者其他
notion image

编译器调试入门

断点

条件断点、变化断点

内存

指针的大小是多少?
Cheat Engine的工作原理是什么?

堆栈

线程&进程

模块

程序运行的所有部分

反汇编

代码经过编译会变得面目全非,反汇编是能让我们确认编译后内容的一种最为直观的方式

git使用

版本控制
远程仓库
github就是一个远程仓库,对应的就是存储文件的远程物理机器,表现在本地就是一个远程路径(url)
版本库
一个被隐藏的.git文件夹
工作区
就是当前的目录下的文件
暂存区
index文件,执行git add之后工作区相关文件会加入暂存区
执行git commit之后暂存区的文件会被加入版本库
清楚了以上基本概念之后,我们来说一下git提交作业的基本流程:
  1. 创建本地git仓库,然后开始编写代码,代码的改动最好是每一个完整改动都用一次commit提交,最好不要一个commit提交大量改动
  1. 将本地仓库和远程仓库关联
  1. 将本地仓库中的代码push到远程仓库,并且测试从远程仓库pull代码到本地
wps编码规范
现代C++核心准则(下)软件工程:面向对象的概念和记号
Loading...
Koreyoshi
Koreyoshi
一个无可救药的乐观主义者
Latest posts
软件测试:集成测试
2025-3-25
软件测试:控制流测试
2025-3-25
软件测试:系统测试
2025-3-25
软件测试:数据流测试
2025-3-25
软件测试:测试驱动开发
2025-3-25
软件工程:面向对象的概念和记号
2025-3-24
Announcement
🎉写给自己的2025心愿🎉
保研
国奖
完善博客
学一门乐器
发表一篇论文
拍摄人生照片
去3个城市旅游
专业课知识视频
拍摄毕业季视频
----- 2025 ------
👏希望我们一起变好👏