📒 C ++ 语法笔记
一、C --> C++
1.1 命名空间
命名空间(
Namespace):命名空间是
C++中用来组织代码的一种机制,它可以将标识符(如变量名、函数名、类名等)分组,避免命名冲突。
C++标准库中的所有标识符都定义在std命名空间中。例如,
std::cout、std::cin、std::vector等。
using namespace std;的作用:这句代码的作用是将
std命名空间中的所有标识符引入到当前作用域中,这样就可以直接使用
std命名空间中的标识符,而不需要在每个标识符前面加上std::前缀。例如,使用
using namespace std;后,可以直接写cout和cin,而不需要写
std::cout和std::cin。
- 示例代码:
#include <iostream>
using namespace std;
int main()
{
cout << "Hello, World!" << endl; //endl 是换行
return 0;
}1.2 输入输出
C:主要使用printf和scanf函数进行输入输出。例如:
#include <stdio.h>
int main()
{
int num;
printf("请输入一个数字:");
scanf("%d", &num);
printf("你输入的数字是:%d\n", num);
return 0;
}C++: 使用输入输出流cin和cout,更加灵活和方便。例如:
#include <iostream>
using namespace std;
int main()
{
int num;
cout << "请输入一个数字:";
cin >> num;
cout << "你输入的数字是:" << num << endl; // endl 是换行
return 0;
}1.3 内存管理
C:主要使用malloc、free等函数进行动态内存分配和释放。例如:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* ptr = (int*)malloc(sizeof(int));
if (ptr != NULL)
{
*ptr = 10;
printf("分配的内存中的值:%d\n", *ptr);
free(ptr);
}
return 0;
}C++:使用new和delete操作符进行动态内存分配和释放。例如:
#include <iostream>
using namespace std;
int main()
{
int* ptr = new int;
*ptr = 10;
cout << "分配的内存中的值:" << *ptr << endl;
delete ptr;
return 0;
}1.4 函数重载
C: 函数名必须唯一,不能有重载的概念。C++: 支持函数重载,即可以有多个同名函数,只要它们的参数类型或参数个数不同即可。例如:
#include <iostream>
using namespace std;
void print(int num)
{
cout << "打印的数字是:" << num << endl;
}
void print(double num)
{
cout << "打印的浮点数是:" << num << endl;
}
int main()
{
print(10);
print(3.14);
return 0;
}1.5 引用
C: 没有引用的概念,只有指针。C++: 引入了引用的概念,引用是一个变量的别名。例如:优点:引用可以避免指针的复杂操作,并且可以提高代码的可读性。
在函数参数传递中,引用可以避免拷贝,提高效率。
#include <iostream>
using namespace std;
void swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int x = 10, y = 20;
swap(x, y);
cout << "x = " << x << ", y = " << y << endl;
return 0;
}二、C++ の STL
2.1 概述
🔍 什么是
STL?STL是C++标准库的一部分,提供了一套通用的、模板化的数据结构和算法。它让你可以高效地处理数据,而不用自己写底层逻辑。
- 📦 简述
STL的三大核心组件
| 组件 | 作用 | 示例 |
|---|---|---|
| 容器 | 存储数据 | vector<int> , map<string,int> |
| 算法 | 操作数据(排序、查找等) | sort() , find() |
| 迭代器 | 连接容器和算法的 "桥梁" | vector<int>::iterator |
2.2 快速上手
- 第一个
STL程序:用vector存数据并排序
// 用 vector 存数据并排序
#include <iostream>
#include <vector> // 使用 stl 的 vector 来存储
#include <algorithm> // 用于 sort
using namespace std;
int main()
{
// 定义并初始化一个vector
vector<int> nums = {5, 2, 8, 1, 3};
// 使用 sort 进行排序
// 补充:begin() 指向第一个位置; end() 指向最后一个元素的下一个位置
sort(nums.begin(),nums.end());
// 使用 for 的范围遍历进行输出
for(int n: nums){
cout << n << endl;
}
return 0;
}📌 练习:
- 创建一个
vector,存入{3.1, 1.4, 2.5, 4.7},排序后输出。 - 尝试用
std::reverse(nums.begin(), nums.end())反转顺序, 然后逆序输出。
🏷️示例代码:
#include <iostream>
#include <vector>
#include <algorithm> // 使用 sort 和 reverse
using namespace std;
int main()
{
// 创建一个存储浮点数的 vector 并初始化
vector<double> nums = {3.1, 1.4, 2.5, 4.7};
// 排序
sort(nums.begin(),nums.end());
// 升序输出
for(double n: nums){
cout << n << ' ';
}
cout << endl << "---------" << endl;
// 反转
reverse(nums.begin(), nums.end());
// 降序输出
for(double n : nums){
cout << n << ' ';
}
return 0;
}2.3 深入理解 vector
2.3.1 概述
在前面的
快速上手里已经使用过vector了,它也是 是
C++里最常用的序列容器, 所以我们可以先从它下手,深入了解一下vector: 动态数组, “变长数组” ,“倍增的思想” ,内存连续,支持随机访问,尾部插入快
- 常用 10 条 命令:
| 目的 | 代码示例 | 复杂度 |
|---|---|---|
| ① 创建 | vector<int> v | O(1) |
| ② 尾增 | v.push_back(7); | 均摊 O(1) |
| ③ 尾删 | v.pop_back(); | O(1) |
| ④ 长度 | v.size(); | O(1) |
| ⑤ 判空 | v.empty(); | O(1) |
| ⑥ 下标访问 | v[i] or v.at(i) | O(1) |
| ⑦ 遍历 | 1. 范围遍历: for(int x : v)2. 遍历下标访问: for(int i; i < v.size(); i++)3. 迭代器遍历: for(vector<int>::iterator i = v.begin(); i!=v.end(); i++) | O(n) |
| ⑧ 清空 | v.clear(); | O(n) |
| ⑨ 前后元素 | v.front() & v.back() | O(1) |
| ⑩ 前后指针 | v.begin() & v.end() | O(n) |
2.3.2 构造 & 初始化
vector<int> a; // 创建一个空 vector
vector<int> a(10); // 10 个 0
vector<int> b(10, 3); // 10 个 3
vector<int> c{1,2,3,4}; // 列表初始化 C++11
vector<string> d = {"abc", "def"}; // 存储字符串的 vector
int arr[] = {9,8,7};
vector<int> e(arr, arr+3); // 从 C 数组拷贝
vector<int> f(e.begin(), e.end()); // 从另一容器拷贝2.3.3 基本操作
#include <iostream>
#include <vector>
using namespace std;
// 自定义一个打印 vector 的函数
void print(vector<int> a){
for(int x : a){
cout << x << ' ';
}
cout << endl;
}
int main()
{
vector<int> nums; // 创建一个空 vector
nums.push_back(2); // 尾部新增元素
nums.push_back(3);
nums.push_back(1);
nums.push_back(4);
print(nums); // 2 3 1 4
nums.pop_back(); // 尾部删除元素
print(nums); // 2 3 1
// 第一个元素
cout << nums.front() << endl; // 2
// 最后一个元素
cout << nums.back() << endl; // 1
// vector 大小
cout << nums.size() << endl; // 3
// 清空
nums.clear();
cout << nums.size() << endl; // 0
return 0;
}2.3.4 插入 & 删除
insert / erase: “ 动中间 ” 的接口insert:- 返回指向插入元素的迭代器
- 所有后面的元素整体后移一位
O(n) - 如果
size()==capacity()会先扩容(全部搬迁)再后移;
erase:- 返回指向下一个迭代器
- 所有后面整体元素前移
O(n) - 可以依靠返回的迭代器配合
2.3.4的remove()和unique()使用
iterator insert(pos, val); // 在 pos 插入
iterator erase(pos); // 删单个
iterator erase(first, last); // 删区间
v.insert(v.begin()+2, 99); // 下标 2 处插入 99
v.erase(v.end()-2); // 删倒数第二个
// 惯用套路: 删全部等于 key 的元素(erase-remove)
v.erase(remove(v.begin(), v.end(), key), v.end());
// 去重套路:
v.erase(unique(v.begin(),v.end()), v.end());2.3.5 remove & unique
- 这是很多初始者一开始产生疑惑的地方,
unique和remove是<algorithm>里的一对 “ 假象兄弟 ”:- 都不真正删除对象(不缩小容器
size) - 都只负责把 “好元素” 紧凑到前面,然后返回一个
new_end迭代器 - 都必须再手动
erase才能完成 “物理删除” - 复杂度都是
O(n),额外空间O(1)
- 都不真正删除对象(不缩小容器
- 两者区别在于 “ 判定好坏的标准 ”
| 算法 | 保留规则 | 典型用途 | 需不需要相邻 |
|---|---|---|---|
remove | 值 != key | 按值删除 | 不需要相邻 |
unique | 相邻且相等 | 去重 | 必须相邻 |
📌 注意:使用
unique的关键点:算法本质是(快慢)双指针,所以必须要相邻并且相等才能正确比较,
所以在使用之前
必须要进行排序,这样才能把 “全局重复” 变成 “相邻重复”
remove示例代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<int> nums = {5,7,3,7,8,7};
// remove {key = 7}
// 初始 {5,7,3,7,8,7} → 整理后 {5,3,8,?,?,?} → erase 后 {5,3,8}
// remove(nums.begin(),nums.end(),7); // 按值(逻辑)remove
// for(int x : nums) cout << x << ' '; // 5 3 8 7 8 7
// 可以发现只是把 不删除的元素移到了前面, 没有改变 size, 那么后面一段将会是脏数据
// 利用返回值 erase : 删除 new_end 到 end 的尾部垃圾
// 那么就可以实现对原 vector 删除一个或多个指定 key 的操作
nums.erase(remove(nums.begin(),nums.end(),7), nums.end()); // 5 3 8
for(int x : nums) cout << x << ' ';
return 0;
}unique示例代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<int> nums = {1,1,2,2,2,3,4,4};
// 初始 {1,1,2,2,2,3,4,4} → 整理后 {1,2,3,4,?, ?, ?, ?} → erase 后 {1,2,3,4}
// 如果未排序 {1,2,1,2} → unique 后 {1,2,1,2}(一个也没去掉)
// 必须先排序 !!
sort(nums.begin(), nums.end());
// 查看排序好的序列
for(int x: nums) cout << x << ' '; // 1 1 2 2 2 3 4 4
cout << endl;
// 去重
nums.erase(unique(nums.begin(),nums.end()), nums.end());
// 查看去重后的序列
for(int x: nums) cout << x << ' '; // 1 2 3 4
return 0;
}2.3.6 常用算法搭档
- 需要头文件
#include <algorithm>sort(v.begin(), v.end()); // 升序
sort(v.rbegin(), v.rend()); // 降序
binary_search(v.begin(), v.end(), value); // 二分查找: (升序可用), 返回是否存在
lower_bound(v.begin(), v.end(), value); // 二分查找: 返回第一个大于等于value的迭代器
upper_bound(v.begin(), v.end(), value); // 二分查找: 返回第一个大于value的迭代器
find(v.begin(), v.end(), value); // 返回查找元素的迭代器
reverse(v.begin(), v.end()); // 翻转2.3.7 支持比较运算
// 支持比较运算(字典序)
vector<int> d(3,4), e(4,3);
// d: 4 4 4; e: 3 3 3 3
cout << (d < e) << endl; // 0
cout << (d > e) << endl; // 12.4 键值对 pair
2.4.1 概述
接下来了解一个非常常用的轻量级的模板类
pair,它用于将两个值组合成一个对象,常用于函数返回多个值、容器(如
std::map)中存储键值对等场景。联想:它像不像当我们想用一个具有两个属性的结构体的简易替换
思路打开:不止可以存储两个,比如存储三个
pair<int,pair<int,int>>
#include <utility> // 其它头文件也会包含 pair
std::pair<T1, T2> p;T1和T2可以是任意类型(基本类型、类、容器等)。pair有两个公有成员:first和second。
- 基本操作:
| 操作 | 示例 |
|---|---|
| 创建 | make_pair(11,"irai") |
| 访问 | p.first & p.second |
| 修改 | p.first = 10; |
| 比较 | p1 < p2 |
2.4.2 创建 &初始化
#include <iostream>
using namespace std;
int main()
{
// 方法1:默认构造
pair<int, string> p1;
p1 = {18, "irai"};
// 方法2:直接初始化
pair<int, string> p2(42, "irai");
// 方法3:使用 make_pair(自动推导类型)
auto p3 = make_pair(3.14, 'A');
// 方法4:列表初始化(C++11)
pair<int, double> p4{1, 2.5};
return 0;
}2.4.3 访问元素
pair<int, string> p(11, "irai");
cout << p.first << ' ' << p.second << endl; // 11 irai2.4.4 修改元素
p.first = 20;
p.second = "world";2.4.5 比较操作
- 支持比较运算符(
==,!=,<,<=,>,>=),按字典序比较(先比first,再比second):
pair<int, int> a(1, 2);
pair<int, int> b(1, 3);
bool result = a < b; // true,因为 first 相等,second 2 < 32.5 string
2.5.0 概览
- 常用操作
| 操作 | 行为 |
|---|---|
size() / length() | 返回字符串长度 |
empty() | 判空 |
clear() | 清空字符串 |
substr(起始下标, (子串长度)) | 返回子串 |
c_str | 返回字符串所在字符数组的起始地址 |
2.5.1 构造 / 赋值
// 1. 空串
string s1;
// 2. 用 C-string 构造
string s2("hello");
// 3. 重复字符
string s3(8, '*'); // ********
// 4. 用另一个 string 构造(拷贝)
string s4(s2);
// 4. 用迭代器区间构造
string s5(s2.begin(), s2.begin()+3); // "hel"
// 6. 赋值运算符
s1 = "world";
s1 = s2;2.5.2 大小 & 容量
string s = "abcdef";
s.size(); // 6 O(1)
s.length(); // 6 完全等价 size()
s.empty(); // false
s.capacity(); // 当前已申请的内存,≥size()
s.reserve(100);// 提前扩容, 避免后续频繁 realloc
s.shrink_to_fit(); // C++11,请求缩容(非强制)2.5.3 元素访问
string s = "hello";
s[0] = 'H'; // (不安全但好用) 不检查越界
s.at(0) = 'H'; // (安全) 越界抛 std::out_of_range
s.front() = 'H'; // C++11
s.back() = 'O'; // C++112.5.4 修改
// 4.1 追加
string s = "a";
s += "b"; // "ab"
s.push_back('c'); // "abc"
s.append(3, 'd'); // "abcddd"
s.append("xyz", 2); // "abcdddxy" (只取前 2 字符)
// 4.2 插入
string s = "ace";
s.insert(1, "B"); // "aBce" (pos, string)
s.insert(s.begin() + 2, 'C'); // "aBCce"
// 4.3 删除
s.erase(2, 2); // 从 pos=2 开始删 2 字符
s.pop_back(); // C++11,删最后一个
//4.4 替换
string s = "I have a book";
s.replace(7, 6, "a pen"); // "I have a pen"
//4.5 清空
s.clear(); // 秒变空串,容量不变2.5.5 查找
string s = "hello world hello";
s.find("lo"); // 3 (第一次出现)
s.rfind("lo"); // 15 (最后一次出现)
s.find("abc"); // string::npos (不存在)
s.find_first_of("aeiou"); // 1 ('e') 找 s 第一个出现的字母
s.find_last_of("aeiou"); // 7 ('o') 找 s 最后一个出现的字母
s.find_first_not_of("he"); // 2 ('l') 找 s 第一个没有的字母2.5.6 比较
string a = "apple", b = "banana";
a == b; // false
a != b; // true
a < b; // true (字典序)
a.compare(b); // <0 (等价于 strcmp)
a.compare(0, 3, b, 0, 3); // 比较子串 "app" vs "ban"2.5.7 截取
string s = "abcdef";
// substr(哪个下标开始, 几个字符)
string sub = s.substr(2, 3); // "cde" (pos, count)2.5.8 数字转换 (c++11)
string s1 = to_string(123); // "123"
string s2 = to_string(3.1415); // "3.141500"2.5.9 遍历
string s = "hello";
for (auto it = s.begin(); it != s.end(); ++it) *it = toupper(*it);
// 现在 s == "HELLO"
// C++11 范围 for
for (char &c : s) c = tolower(c); // 又变回 "hello"2.5.10 转字符数组
string s = "hello";
const char* p = s.c_str(); // 返回 '\0' 结尾的 const char*
printf("%s", p); // hello2.5.11 常用算法
string s = "31415926";
reverse(s.begin(), s.end()); // "92695141"
sort(s.begin(), s.end()); // "11245699"
auto it = find(s.begin(), s.end(), '5'); // 返回查找到的迭代器
// 原地修改s为下一个排列,返回有无这样的排序的bool值
bool ok = next_permutation(s.begin(), s.end());补充:
std::next_permutation是C++标准库<algorithm>中的一个函数,用于
原地将序列变换为字典序的下一个排列。如果存在这样的排列,返回true;否则将序列变换为字典序最小的排列(即升序),并返回
false。
2.5.12 拼接大量小片段
// 糟糕:频繁 realloc
string bad;
cout << bad.capacity(); // 0
for (int i = 0; i < 1e5; ++i) bad += "x";
// 推荐:先 reserve
string good;
good.reserve(1e5);
cout << good.size() << endl; // 0
cout << good.capacity() << endl; //102343
for (int i = 0; i < 1e5; ++i) good += "x";2.6 queue
- 普通队列:
queue:先进先出
// 头文件
#include <queue>- 常用操作:
// 构造
queue<T> q; // 一般
queue<int> q; // int 为元素
struct rec{…}; queue<rec> q; // 结构体为元素
q.size() // 大小
q.empty() // 判空
q.push(x); // 向队尾插入一个元素
q.pop() // 弹出队头元素
q.front() // 返回队头元素
q.back() // 返回队尾元素2.7 priority_queue
- 优先队列(堆):
priority_queue
// 头文件
#include <queue>- 构造:
// 构造
priority_queue<int> q; // 大根堆
priority_queue<int, vector<int>, greater<int>> q; // 小根堆
struct rec{…}; priority_queue<rec> q; // 结构体 rec 中必须定义小于号
priority_queue<pair<int, int>>q; // pair 默认先比较 first 再比较 second补充:
std::priority_queue默认使用std::less<T>作为比较规则,也就说它会按
“<”语义 建 大根堆(看上去 “大的排在前面” )。❓ 疑惑:大根堆明明是大的在堆顶,为什么要用小于号作比较规则
解释:一个比较函数 若
comp(a,b)==true则a 的优先级低于 b默认比较器是
std::less,即comp(a,b) = (a < b),那么 当
a < b为真时,比较器说 “ a 优先级低于 b ”,于是 b 会往堆顶走。📌 更好的理解方式:这一点我发现有点像
冒泡排序:当我们写冒泡排序的时候,如果要写一个
降序的,那么比较函数就是c++bool comp(int a, int b){ if(a < b) return true; else return false; } //冒泡排序中: // 如果 a 小于 b 则 swap , 也就是小的数a去后面 if(comp(a,b)) swap(a,b);最后出来的效果是
降序, 那看起来不就像大根堆那样,大的数在前面吗然后我们看一下我们写的比较函数,不就是利用
<实现的比较规则吗!!重载比较符号:
c++struct Node { int val; // 大根堆:值大的优先级高(小于号比较: 小的数去后面) bool operator<(const Node& rhs) const { return val < rhs.val; // 注意这里是“小于” } };- 小根堆的写法:
c++// 1. 构造函数中换比较器 priority_queue<int, vector<int>, greater<int>> q; // 2. 重载比较运算符 bool operator>(const Node& a, const Node& b) { // (大于号比较: 大的数去后面 -- 小根堆) return a.val > b.val; // 值越小优先级越高 } // 3. 自定义比较器 auto cmp = [](const Node& a, const Node& b) { // (大于号比较: 大的数去后面 -- 小根堆) return a.val > b.val; // 返回 true 表示 a 优先级低于 b → 小值上浮 }; std::priority_queue<Node, std::vector<Node>, decltype(cmp)> pq(cmp);
- 三个核心操作:
push(x); // 把元素插入堆
pop(); // 删除堆顶元素
top(); // 查询堆顶元素(最大值)2.8 stack
默认基于
std::deque封装而成,先进后出的特点头文件:
#include <stack>- 定义:
stack<int> s; // 默认使用 deque 作为底层容器也可以指定底层容器(如 vector 或 list):
stack<int, vector<int>> s;- 常用操作:
| 操作 | 说明 |
|---|---|
push(x) | 将元素 x 压入栈顶 |
pop() | 移除栈顶元素(不返回值) |
top() | 返回栈顶元素的引用 |
empty() | 判断栈是否为空 |
size() | 返回栈中元素个数 |
2.9 哈希表
- C++ STL 里的 “哈希表” 就是无序关联容器(C++11 起引入), 共四个模板:
| 容器名 | 键唯一 | 能否重复键 | 头文件 |
|---|---|---|---|
unordered_map | 唯一 | 否 | <unordered_map> |
unordered_multimap | 可重复 | 是 | <unordered_map> |
unordered_set | 唯一 | 否 | <unordered_set> |
unordered_multiset | 可重复 | 是 | <unordered_set> |
用法 95% 与
map/set相同,只是 内部红黑树 → 哈希表 ,接口多了“桶”相关函数。下面以
unordered_map为例,列出常用操作。
- 1). 增 / 改 / 查
unordered_map<string,int> mp;
mp["Bob"] = 18; // 插入或覆盖
mp.insert({"Alice",20}); // 仅当 key 不存在才插入
mp.emplace("Tom",19); // 原位构造,少一次拷贝
auto it = mp.find("Bob"); // 找不到返回 mp.end()
if (it != mp.end()) cout << it->second;
int age = mp.at("Alice"); // key 不存在抛 std::out_of_range
int age2 = mp["Carol"]; // 不存在会默认构造 value(int 为 0)
// 也就是 age 2 为 0 的同时,mp 插入了一份 {"Carol" : 0}- 2). 删
mp.erase("Bob"); // 按 key 删, 返回删除个数 0/1
mp.erase(it); // 按迭代器删,O(1) 均摊
mp.clear(); // 清空- 3). 大小 & 判空
mp.size(); // 元素个数
mp.empty(); // 是否空- 4). 遍历
// for 范围遍历
for(auto x : mp){
cout << x.first << ' ' << x.second << endl;
}
// 迭代器遍历
for(auto it = mp.begin(); it != mp.end(); it ++ ){
cout << it->first << ' ' << it->second << endl;
}❓ 为什么一个用
.一个用->范围
for拿到的x是容器中 "元素" 的一份拷贝, 即pair<string,int>所以使用的是 对象的成员选择运算符
.迭代器遍历中的
it是指向元素的指针,所以使用 指针的成员选择运算符->如果说
auto& x : mp拿到的x就是加了一份引用,内部不会拷贝
- 5). 当
key不是内置类型时,需要自己写哈希函数和相等比较:- 哈希函数 —— 告诉哈希表 “怎么把 ’元素‘ 散列到桶里” ;
- 相等比较 —— 告诉哈希表 “怎么判断两个 ‘元素‘ 相同” 。
#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
struct Point{
int x, y;
// 1. 重载相等
bool operator==(const Point& t) const{
return x == t.x && y == t.y;
}
};
// 2. 独立的哈希器
struct PointHash {
size_t operator()(const Point& p) const noexcept {
// 下面这一行就是“哈希函数”的全部工作:把两个整数映射成一个 size_t
return hash<int>{}(p.x) ^ (hash<int>{}(p.y) << 1);
}
};
int main()
{
unordered_map<Point, int, PointHash> mp; // 第三个模板实参传哈希器
mp[{1, 2}] = 42;
cout << mp[{1, 2}] << endl; // 42
return 0;
}- 6). 返回某个元素个数
mp.count({1,2}); // 返回 {1,2} 的个数
// 更多是在 unordered_set : 个数就是 1/0 , 那么我们可以判断某个数在不在 set
unordered_set<int> s;
s.insert(1);
s.insert(1);
cout << s.count(1) << endl; // 1
cout << s.count(10) << endl; // 0