1. C++ 发展概述
1.1发展历史
1979 年:Bjarne Stroustrup 在贝尔实验室工作时,发现 C 语言在表达能力、可维护性和可扩展性上的不足,开始研发 C++ 雏形。
1983 年:在 C 语言基础上添加面向对象特性(类、封装、继承),正式命名为 C++。
1989 年:ANSI 和 ISO 联合成立标准化委员会,启动 C++ 标准化工作。
1994 年:提出首个标准化草案,新增部分特性,随后投票通过将 STL 纳入 C++ 标准。
1998 年:ANSI/ISO 标准正式投入使用,为 C++ 首个官方版本。
1.2 版本更新
时间
版本
核心内容
1998 年
C++98
首个官方版本,获 ISO 认可,以模板重写标准库,引入 STL(标准模板库)
2003 年
C++03
聚焦稳定性和兼容性,修复 C++98 漏洞,引入 tr1 库
2011 年
C++11
革命性更新,新增 lambda、范围 for、右值引用、移动语义、变长模板参数、智能指针、标准线程库等
2014 年
C++14
扩展 C++11,修复漏洞,新增泛型 lambda 返回值类型推导、二进制字面常量等
2017 年
C++17
增强功能与表达能力,引入 if constexpr、折叠表达式等,改进 string、filesystem 等标准库组件
2020 年
C++20
重要里程碑,引入协程(Coroutines)、概念(Concepts)、模块化(Modules)等
2023 年
C++23
小版本更新,完善现有特性,新增 if consteval、函数、flat_map、import std 导入标准库等
2026 年
C++26
制定中
1.3 C++23 相关插曲
网络库(networking)原计划纳入 C++23,因 senders/receivers 相关争议推迟至 C++26。
std::generator 因原作者延迟提交,更换作者后走加急通道,能否纳入存在不确定性。
modular standard library 通过单独提交 module std 的短小 paper 推进,大概率通过。
2. C++ 参考文档
3. C++ 的重要性
3.1 编程语言排行榜(2025 年 6 月 TIOBE)
排名
2024 年排名
编程语言
评级
变化
1
-
Python
15.39%
+2.93%
2
3
C++
10.03%
-1.33%
3
2
C
9.23%
-3.14%
4
4
Java
8.40%
-2.88%
5
5
C#
6.65%
-0.06%
6
7
JavaScript
3.32%
+0.51%
7
14
Go
1.93%
+0.93%
8
9
SQL
1.75%
+0.28%
9
6
Visual Basic
1.66%
-1.67%
10
15
Fortran
1.53%
+0.53%
11
11
Delphi/Object Pascal
1.52%
+0.27%
12
19
Swift
1.27%
+0.33%
13
10
Assembly language
1.26%
-0.03%
14
12
MATLAB
1.26%
+0.14%
15
8
PHP
1.22%
-0.52%
16
13
Scratch
1.17%
+0.15%
17
20
Rust
1.17%
+0.26%
18
18
Ruby
1.11%
+0.17%
19
29
Kotlin
1.01%
+0.50%
20
22
COBOL
0.96%
+0.22%
3.2 应用领域
大型系统软件开发:编译器、数据库、操作系统、浏览器等。
音视频处理:基于 FFmpeg、WebRTC、Mediasoup、ijkplayer 等开源库,核心技术栈为 C++。
PC 客户端开发:Windows 桌面软件(如 WPS),常用 C+++QT(跨平台 GUI 框架)。
服务端开发:高并发后台服务(游戏服务、流媒体服务、量化高频交易服务),对性能要求高。
游戏引擎开发:UE4、Cocos2d-x 等开源引擎均基于 C++,需掌握 C++ 基础、数据结构、图形学知识。
嵌入式开发:智能手环、摄像头、扫地机器人等设备,分为嵌入式应用开发和驱动开发。
机器学习引擎:底层算法多由 C++ 实现,上层用 Python 封装,适合机器学习系统开发。
测试开发 / 测试:测试开发需使用测试工具(selenium、Jmeter),设计测试用例、编写自动化脚本;功能测试以手动测试为主。
4. C++ 学习建议与书籍推荐
4.1 学习难度
C++ 相对难学难精,学习曲线陡峭,受历史包袱、语言设计和发展历史影响。
4.2 书籍推荐
《C++ Primer》 :经典语法书籍,前后中期均可使用,前期可预习,中后期作为语法字典。
《STL 源码剖析》(侯捷) :从底层实现角度剖析 STL 源码,帮助理解数据结构、算法实现及泛型封装,学习中期可以读。
《Effective C++》(侯捷翻译) :包含 55 个高效使用 C++ 的条款,中后期阅读,工作 1-2 年后可重读。
5. C++ 的第一个程序
5.1 兼容 C 语言版本
cpp
复制代码
// test.cpp
#include<stdio.h>
int main()
{
printf("hello world\n");
return 0;
}
**注意:**文件后缀改为.cpp,VS 编译器调用 C++ 编译器,Linux 下用 g++ 编译(非 gcc)。
5.2 C++ 原生版本
cpp
复制代码
// test.cpp
#include<iostream>
using namespace std;
int main()
{
cout << "hello world\n" << endl;
return 0;
}
6. 命名空间(namespace)
6.1 核心价值
解决标识符命名冲突 或名字污染 问题:变量 、函数 、类大量 存在于全局作用域 时易冲突,namespace 将标识符本地化,隔离不同作用域 。
6.2 定义规则
关键字:namespace + 命名空间名 + {},{} 内为命名空间成员(变量、函数、类型等)。
本质:定义独立的域(命名空间域),与全局域、函数局部域、类域并列 ,仅影响编译时查找逻辑,++不影响变量生命周期++。
嵌套定义:支持命名空间嵌套。
多文件合并:不同文件中同名 namespace 会默认合并为一个,不冲突。
标准库命名空间:C++ 标准库均放在 std(standard)命名空间中。
6.3 定义示例
6.3.1 正常定义
cpp
复制代码
#include <stdio.h>
#include <stdlib.h>
// 命名空间名常用项目名或个人缩写
namespace hy
{
int rand = 10;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
int val;
struct Node* next;
};
}
int main()
{
printf("%p\n", rand); // 访问全局rand函数指针
printf("%d\n", hy::rand); // 访问hy命名空间中的rand
return 0;
}
6.3.2 嵌套定义
cpp
复制代码
namespace hy{
// 嵌套命名空间hyy
namespace hyy
{
int rand = 1;
int Add(int left, int right)
{
return left + right;
}
}
// 嵌套命名空间hhy
namespace hhy
{
int rand = 2;
int Add(int left, int right)
{
return (left + right) * 10;
}
}
}
int main()
{
printf("%d\n", hy::hyy::rand);
printf("%d\n", hy::hhy::rand);
printf("%d\n", hy::hyy::Add(1, 2));
printf("%d\n", hy::hhy::Add(1, 2));
return 0;
}
6.3.3 多文件定义
cpp
复制代码
// Stack.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
namespace bit
{
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void STInit(ST* ps, int n);
void STDestroy(ST* ps);
void STPush(ST* ps, STDataType x);
void STPop(ST* ps);
STDataType STTop(ST* ps);
int STSize(ST* ps);
bool STEmpty(ST* ps);
}
// Stack.cpp
#include"Stack.h"
namespace bit
{
void STInit(ST* ps, int n)
{
assert(ps);
ps->a = (STDataType*)malloc(n * sizeof(STDataType));
ps->top = 0;
ps->capacity = n;
}
void STPush(ST* ps, STDataType x)
{
assert(ps);
// 满了扩容
if (ps->top == ps->capacity)
{
printf("扩容\n");
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
// 其他函数实现省略...
}
// Queue.h
#pragma once
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
namespace bit
{
typedef int QDataType;
typedef struct QueueNode
{
int val;
struct QueueNode* next;
}QNode;
typedef struct Queue
{
QNode* phead;
QNode* ptail;
int size;
}Queue;
void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq, QDataType x);
void QueuePop(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
bool QueueEmpty(Queue* pq);
int QueueSize(Queue* pq);
}
// Queue.cpp
#include"Queue.h"
namespace bit
{
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = NULL;
pq->ptail = NULL;
pq->size = 0;
}
// 其他函数实现省略...
}
// test.cpp
#include"Queue.h"
#include"Stack.h"
// 全局定义独立的Stack
typedef struct Stack
{
int a[10];
int top;
}ST;
void STInit(ST* ps){}
void STPush(ST* ps, int x){}
int main()
{
// 调用全局的Stack
ST st1;
STInit(&st1);
STPush(&st1, 1);
STPush(&st1, 2);
printf("%d\n", sizeof(st1));
// 调用bit命名空间的Stack
bit::ST st2;
printf("%d\n", sizeof(st2));
bit::STInit(&st2);
bit::STPush(&st2, 1);
bit::STPush(&st2, 2);
return 0;
}
6.4 使用方式
6.4.1 指定命名空间访问(推荐)
cpp
复制代码
#include<stdio.h>
namespace N
{
int a = 0;
int b = 1;
}
int main()
{
printf("%d\n", N::a);
return 0;
}
6.4.2 using 展开单个成员
cpp
复制代码
#include<stdio.h>
namespace N
{
int a = 0;
int b = 1;
}
using N::b;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
return 0;
}
6.4.3 using 展开全部成员(不推荐项目使用)
cpp
复制代码
#include<stdio.h>
namespace N
{
int a = 0;
int b = 1;
}
using namespace N;
int main()
{
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
7. C++ 输入与输出
7.1 核心组件
头文件:<iostream>(Input Output Stream 缩写,标准输入输出流库)。
核心对象:
std::cin :istream 类对象,面向窄字符的标准输入流。
std::cout :ostream 类对象,面向窄字符的标准输出流。
std::endl :函数,插入换行符并刷新缓冲区。
运算符:<<(流插入运算符)、>>(流提取运算符) 。
7.2 特性
自动识别变量类型 (通过函数重载实现)。
支持自定义类型对象的输入输出 。
包含<iostream>后,VS 编译器可间接使用 printf/scanf,其他编译器可能报错 。
7.3 使用示例
cpp
复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
int main()
{
int a = 0;
double b = 0.1;
char c = 'x';
cout << a << " " << b << " " << c << endl;
std::cout << a << " " << b << " " << c << std::endl;
scanf("%d%lf", &a, &b);
printf("%d %lf\n", a, b);
// 自动识别类型
cin >> a;
cin >> b >> c;
cout << a << endl;
cout << b << " " << c << endl;
return 0;
}
7.4 提高 IO 效率(竞赛常用)
cpp
复制代码
#include<iostream>
using namespace std;
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
return 0;
}
8. 缺省参数
8.1 定义
声明或定义函数时为参数指定缺省值,调用时未传实参则使用缺省值,否则使用指定实参 。
分类:全缺省(所有参数有缺省值)、半缺省(部分参数有缺省值)。
8.2 规则
半缺省参数必须从右往左 依次连续缺省,不能间隔跳跃。
函数调用时,实参必须从左到右依次传递 ,不能跳跃。
函数声明和定义分离时,缺省参数只能在声明中指定,不能同时出现。
8.3 示例
8.3.1 全缺省与半缺省
cpp
复制代码
#include <iostream>
using namespace std;
// 全缺省
void Func1(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
// 半缺省(从右往左连续缺省)
void Func2(int a, int b = 10, int c = 20)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
Func1();
Func1(1);
Func1(1, 2);
Func1(1, 2, 3);
Func2(100);
Func2(100, 200);
Func2(100, 200, 300);
return 0;
}
8.3.2 声明与定义分离
cpp
复制代码
// Stack.h
#include <iostream>
#include <assert.h>
using namespace std;
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
// 声明时指定缺省值
void STInit(ST* ps, int n = 4);
// Stack.cpp
#include"Stack.h"
// 定义时不指定缺省值
void STInit(ST* ps, int n)
{
assert(ps && n > 0);
ps->a = (STDataType*)malloc(n * sizeof(STDataType));
ps->top = 0;
ps->capacity = n;
}
// test.cpp
#include"Stack.h"
int main()
{
ST s1;
STInit(&s1); // 使用缺省值4
// 已知需插入1000个数据,直接指定容量
ST s2;
STInit(&s2, 1000);
return 0;
}
**注意:**函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值。
9. 函数重载
9.1 定义
同一作用域中允许存在同名函数,要求形参不同(参数个数、类型或类型顺序不同),支持多态行为。
C 语言不支持同一作用域同名函数。
9.2 规则
返回值不同不能作为重载条件(调用时无法区分)。
缺省参数可能导致调用歧义(如 void f () 与 void f (int a=10),调用 f () 时编译器无法区分) 。
9.3 示例
cpp
复制代码
#include<iostream>
using namespace std;
// 1. 参数类型不同
int Add(int left, int right)
{
cout << "int Add(int left, int right)" << endl;
return left + right;
}
double Add(double left, double right)
{
cout << "double Add(double left, double right)" << endl;
return left + right;
}
// 2. 参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
// 3. 参数类型顺序不同
void f(int a, char b)
{
cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
cout << "f(char b, int a)" << endl;
}
int main()
{
Add(10, 20);
Add(10.1, 20.2);
f();
f(10);
f(10, 'a');
f('a', 10);
return 0;
}
10. 引用
10.1 概念与定义
引用是已存在变量的别名,编译器不为引用开辟内存空间 ,与被引用变量共用一块内存。
语法:类型 & 引用别名 = 引用对象 ;
10.2 特性
定义时必须初始化。
一个变量可以有多个引用。
引用一旦绑定一个实体,不能再引用其他实体。
10.3 示例
cpp
复制代码
#include<iostream>
using namespace std;
int main()
{
int a = 0;
// b、c是a的别名
int& b = a;
int& c = a;
// d是b的别名,本质还是a的别名
int& d = b;
++d;
// 地址相同
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << &d << endl;
return 0;
}
10.4 核心用途
10.4.1 引用传参
功能与指针传参类似,更简洁,可改变被引用对象的值,减少拷贝提高效率 。
cpp
复制代码
// 交换函数
void Swap(int& rx, int& ry)
{
int tmp = rx;
rx = ry;
ry = tmp;
}
int main()
{
int x = 0, y = 1;
cout << x << " " << y << endl;
Swap(x, y);
cout << x << " " << y << endl;
return 0;
}
10.4.2 引用返回值
cpp
复制代码
#include<iostream>
#include<assert.h>
using namespace std;
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void STInit(ST& rs, int n = 4)
{
rs.a = (STDataType*)malloc(n * sizeof(STDataType));
rs.top = 0;
rs.capacity = n;
}
void STPush(ST& rs, STDataType x)
{
assert(rs.a);
// 满了扩容
if (rs.top == rs.capacity)
{
printf("扩容\n");
int newcapacity = rs.capacity == 0 ? 4 : rs.capacity * 2;
STDataType* tmp = (STDataType*)realloc(rs.a, newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
rs.a = tmp;
rs.capacity = newcapacity;
}
rs.a[rs.top] = x;
rs.top++;
}
// 引用返回栈顶元素
int& STTop(ST& rs)
{
assert(rs.top > 0);
return rs.a[rs.top - 1];
}
int main()
{
ST st1;
STInit(st1);
STPush(st1, 1);
STPush(st1, 2);
cout << STTop(st1) << endl;
STTop(st1) += 10; // 直接修改栈顶元素
cout << STTop(st1) << endl;
return 0;
}
10.4.3 指针引用(简化链表操作)
cpp
复制代码
#include<iostream>
using namespace std;
typedef struct ListNode
{
int val;
struct ListNode* next;
}LTNode, *PNode;
// 指针引用,无需二级指针
void ListPushBack(PNode& phead, int x)
{
PNode newnode = (PNode)malloc(sizeof(LTNode));
newnode->val = x;
newnode->next = NULL;
if (phead == NULL)
{
phead = newnode;
}
else
{
// 找到尾节点(省略实现)
PNode tail = phead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
int main()
{
PNode plist = NULL;
ListPushBack(plist, 1);
return 0;
}
注意: ⼀些主要用C代码实现版本数据结构教材中,使用C++引用替代指针传参,目的是简化程序,避开复杂的指针,但是很多同学没学过引用,导致⼀头雾水。
10.5 const 引用
10.5.1 核心规则
可引用 const 对象,但必须用 const 引用(避免权限放大)。
可引用普通对象(权限缩小)。
引用临时对象(如表达式结果、类型转换中间值)时,必须用 const 引用(临时对象具有常性)。
10.5.2 示例
cpp
复制代码
#include<iostream>
using namespace std;
int main()
{
const int a = 10;
// 编译报错:权限放大
// int& ra = a;
const int& ra = a;
// 编译报错:不能修改常量
// ra++;
int b = 20;
const int& rb = b;
// 编译报错:不能修改const引用
// rb++;
// 引用字面量(临时对象)
const int& rc = 30;
// 引用表达式结果(临时对象)
// 编译报错:权限放大
// int& rd = a * 3;
const int& rd = a * 3;
double d = 12.34;
// 编译报错:类型转换产生临时对象,权限放大
// int& re = d;
const int& re = d;
return 0;
}
注意:类似 int& rb = a*3; double d = 12.34; int& rd = d; 这样一些场景下a*3的和结果保存在一个临时对象中,int& rd = d 也是类似,在类型转换中 会产生临时对象存储中间值 ,也就是时,rb和rd引用的都是临时对象 ,而C++规定临时对象具有常性 ,所以这里 就触发了权限放大,必须要用常引用才可以。
所谓临时对象就是编译器需要一个空间暂存表达式的求值结果时临时创建的一个未命名的对象, C++中把这个未命名对象叫做临时对象。
10.6 指针与引用的区别
对比维度
引用
指针
语法概念
变量别名,不开辟空间
存储变量地址,开辟空间(32 位 4 字节,64 位 8 字节)
初始化
必须初始化
建议初始化,语法非必须
指向修改
一旦绑定,不能更换指向
可随时改变指向
访问方式
直接访问
需解引用访问
sizeof 含义
引用类型的大小
地址空间所占字节数
安全性
几乎无空引用、野引用问题,更安全
易出现空指针、野指针,安全性较低
11. 内联函数(inline)
11.1 定义
用 inline 修饰的函数,编译时编译器会在调用处展开,无需建立栈帧,提高效率。
11.2 规则
inline 是编译器建议,编译器可忽略(如递归函数、代码量大的函数)。
适用于频繁调用的短小函数。
不建议声明和定义分离(分离会导致链接错误,展开后无函数地址)。
VS debug 版本默认不展开 inline,需手动设置才能调试。
11.3 与 C 语言宏函数的对比
宏函数:预处理时替换,易出错、不方便调试。
内联函数:编译时展开,类型安全、支持调试,替代宏函数。
11.4 示例
11.4.1 内联函数使用
cpp
复制代码
#include<iostream>
using namespace std;
inline int Add(int x, int y)
{
int ret = x + y;
ret += 1;
ret += 1;
ret += 1;
return ret;
}
int main()
{
int ret = Add(1, 2);
cout << Add(1, 2) * 5 << endl;
return 0;
}
11.4.2 宏函数的问题与正确实现
cpp
复制代码
#include<iostream>
using namespace std;
// 错误示例1:参数带类型,语法错误
//#define ADD(int a, int b) return a + b;
// 错误示例2:无外层括号,运算优先级问题
//#define ADD(a, b) a + b;
// 错误示例3:无内层括号,复杂表达式出错
//#define ADD(a, b) (a + b)
// 正确实现:内外均加括号,不带分号
#define ADD(a, b) ((a) + (b))
int main()
{
int ret = ADD(1, 2);
cout << ADD(1, 2) << endl;
cout << ADD(1, 2) * 5 << endl; // 需外层括号避免优先级问题
int x = 1, y = 2;
ADD(x & y, x | y); // 需内层括号保证表达式独立运算
return 0;
}
11.4.3 声明与定义分离的问题
cpp
复制代码
// F.h
#include <iostream>
using namespace std;
inline void f(int i);
// F.cpp
#include "F.h"
void f(int i)
{
cout << i << endl;
}
// main.cpp
#include "F.h"
int main()
{
// 链接错误:无法解析外部符号(inline展开后无函数地址)
f(10);
return 0;
}
12. nullptr(C++11)
12.1 NULL 的问题
NULL 是宏 ,C++ 中定义为 0,C 中定义为 (void*) 0。
调用重载函数时易出错(如 f (int) 和 f (int*),f (NULL) 会调用 f (int),而非预期的 f (int*))。
12.2 nullptr 的特性
关键字 ,特殊类型字面量,仅可隐式转换为任意指针类型,不能转换为整数类型。
避免 NULL 的类型转换问题,明确表示空指针。
12.3 示例
cpp
复制代码
#include<iostream>
using namespace std;
void f(int x)
{
cout << "f(int x)" << endl;
}
void f(int* ptr)
{
cout << "f(int* ptr)" << endl;
}
int main()
{
f(0); // 调用f(int x)
f(NULL); // 调用f(int x),非预期
f((int*)NULL); // 调用f(int* ptr),需显式转换
// 编译报错:无法转换参数类型
// f((void*)NULL);
f(nullptr); // 调用f(int* ptr),符合预期
return 0;
}
13.总结
13.1 核心语法特性
知识点
核心价值
关键规则
命名空间
解决命名冲突
namespace定义,支持嵌套 / 多文件合并;使用方式:指定命名空间(推荐)、展开单个 / 全部成员
输入输出
标准化 IO 操作
依赖<iostream>,cin/cout自动识别类型;竞赛可优化ios提升效率
缺省参数
简化函数调用
半缺省需从右往左连续;声明 / 定义分离时仅声明指定缺省值
函数重载
实现多态
同一作用域同名函数,形参(个数 / 类型 / 顺序)不同;返回值不同不构成重载
引用
变量别名,减少拷贝
定义必初始化,绑定后不可更改;const引用可引用常量 / 临时对象,用于传参 / 返回值
内联函数
提升调用效率
inline是编译器建议,短小高频函数适用;声明 / 定义不分离(避免链接错误)
nullptr
规范空指针表示
C++11 新增,解决 NULL(宏定义为 0)的重载调用歧义,仅隐式转换为指针类型