C++ 笔记
关于编译器
gcc
主要用作 C 语言编译器,但它实际上可以编译多种语言的代码,包括 C、C++等g++
专门用于编译 C++ 代码
在使用gcc
编译时,需要添加-lstdc++ -o
,-lstdc++
选项用于告诉编译器链接 C++ 标准库,-o
选项指定输出文件的名称。
而在使用g++
时,则无需,但要在JSON文件里修改编译器(许多软件、工具、框架都使用 JSON 作为配置文件格式)
基础知识
1 | using namespace std; |
需要避免与用户自定义的类或函数名字与__C++ 标准库__大量常用的工具和数据结构(例如 vector
、string
、map
等)冲突。为了避免每次都写 std::
前缀在前面声明这样一句话。
for
常见的几种使用方式:
- 传统的循环方式,
for(int i = 0;;)
,如果是循环中初始化的变量,作用域只能在for循环里。 - 遍历容器或数组,
for (declaration : container)
- 使用引用的范围,
for (int& num : numbers)
- 这里的
&
表示引用,当函数参数是大对象时,使用值传递会导致对象被复制,消耗大量的内存和性能。通过使用按引用传递,可以避免复制,只传递对象的引用。与指针有很大的区别,比如指针可以是空的,但引用必须在初始化的时候就指向某个对象,等。当被引用的对象不能进行更改时,可以用const
进行修饰。,此外该符号还有取地址的意思。
- 这里的
字符串
- 判断两个字符串是否相同的方式
- 运算符
==
- 方法
std::string::compare()
,返回0表示字符串相等 - 函数
std::strcmp()
,返回0表示字符串相等 - 函数
boost::iequals()
,比较时忽略大小写
- 运算符
insert
可用于给哈希表添加元素,push_back
用于动态的(数组vector、不能用于哈希表)插入元素
复杂度
- __时间复杂度__可以理解为在最坏情况下,程序中某个操作(例如循环或递归)执行的次数。大 O 表示法的规则,我们忽略常数因子。
- __空间复杂度__,算法执行过程中需要额外的存储空间,比如循环中迭代的
i
复杂度就是O(n),不管输入或常量规模多大,复杂度都是O(1)。
哈希表
unordered_set
只储存键,__官方库不支持<vector.insert()
函数进行插入,unordered_map
即储存键也储存值,只插入键时,默认的值是0,不能只插入键,必须插入键值。比如map1[p[i]] = 0;
。当元素不在容器中时,.find()
会返回.end()
。.erase()
用于删除哈希表的某个键。.clear()
用于清空所有键。_对于一般的数据类型都是有官方的哈希函数的,但是如果是数组则需要自己定义,比如vector<int>
或者vector<char>
_。通过.size()获取哈希表的大小。
哈希表可以直接存放基本数据类型,如 int
、float
、double
、char
、bool
等。哈希表也可以存放标准库中的数据类型,如 std::string
、std::vector
、std::pair
等。哈希表可以存放自定义数据类型,但需要满足以下条件:
- 作为键(Key)时:必须定义哈希函数和相等比较函数。
- 作为值(Value)时:无需额外操作。
常见用途:
- 快速查找
- 去重
- 计数
- 快速判断元素是否存在
将哈希表中的键存入数组:
1 | for (const auto& key : keys) { //&表示引用,相当于直接操作要被遍历的变量,减少了地址的开销 |
双指针
- 快慢指针
- 左右指针
作用主要在于降低时间复杂度,比如在处理三数之和的数组时…
排序
C++标准库有可以直接使用的排序函数,sort(数组的开始位置,数组结束的位置)
,默认是升序排序,降序如下:
1 | //升序 |
宏定义
宏定义(关键字:define)C/C++等编程语言中的一种预处理指令,用于在编译之前对代码进行文本替换。
常见用法
(1) 定义常量
用于定义程序中使用的常量,避免魔法数字(Magic Number)。
1 |
(2) 定义函数式宏
可以定义带参数的宏,类似于函数,但本质是文本替换。
1 |
(3) 简化代码
用于简化重复性代码,提高可读性。
1 |
(4) 条件编译
与#ifdef
、#ifndef
等指令结合,实现条件编译。
1 |
|
链表
使用临时链表复制当前链表:
1 | ListNode *tempA = headA; |
常量指针(Pointer to Constant)
常量指针是指 指针指向的值不能被修改,但指针本身可以指向其他地址。
语法:
1 | const int* ptr; |
指针常量(Constant Pointer)
指针常量是指 指针本身的值(即指向的地址)不能被修改,但可以通过指针修改指向的值。
语法:
1 | int* const ptr; |
常量指针常量(Constant Pointer to Constant)
常量指针常量是指 指针本身的值和指针指向的值都不能被修改。
语法:
1 | const int* const ptr; |
STL容器
vector
常用操作方法
创建
1
2
3
4
5
6
7
8
9
10
11// 创建一个空的 vector
std::vector<int> vec1;
// 创建包含 5 个元素的 vector,初始值为 0
std::vector<int> vec2(5);
// 创建包含 5 个元素的 vector,初始值为 10
std::vector<int> vec3(5, 10);
// 使用初始化列表创建 vector
std::vector<int> vec4 = {1, 2, 3, 4, 5};访问元素
1
2
3
4
5
6
7
8
9
10
11// 使用下标访问元素
std::cout << "Element at index 2: " << vec[2] << std::endl;
// 使用 at() 访问元素(会检查边界)
std::cout << "Element at index 3: " << vec.at(3) << std::endl;
// 访问第一个元素
std::cout << "First element: " << vec.front() << std::endl;
// 访问最后一个元素
std::cout << "Last element: " << vec.back() << std::endl;插入元素
1
2
3
4
5// 在末尾插入元素
vec.push_back(4);
// 在指定位置插入元素
vec.insert(vec.begin() + 1, 10); // 在索引 1 处插入 10删除元素
1
2
3
4
5
6
7
8
9
10
11// 删除末尾元素
vec.pop_back();
// 删除指定位置的元素
vec.erase(vec.begin() + 1); // 删除索引 1 处的元素
// 删除一段范围内的元素
vec.erase(vec.begin() + 1, vec.begin() + 3); // 删除索引 1 到 2 的元素
// 清空 vector
vec.clear();
链表
链表的基本结构是一个节点类,包含数据域和指针域。以下是单链表的节点定义:
1 | CPPstruct ListNode { |
链表根据指针域的不同可以分为以下几种:
- 单链表(Singly Linked List)
每个节点只有一个指针域,指向下一个节点。最后一个节点的指针域为 nullptr
。
1 | head -> [1] -> [2] -> [3] -> nullptr |
- 双链表(Doubly Linked List)
每个节点有两个指针域,分别指向前一个节点和后一个节点。
1 | head <-> [1] <-> [2] <-> [3] <-> nullptr |
节点定义:
1 | CPPstruct ListNode { |
- 循环链表(Circular Linked List)
单链表或双链表的最后一个节点的指针域指向头节点,形成一个环。
1 | head -> [1] -> [2] -> [3] -> head |
以下是链表的一些常见操作:
- 创建链表
1 | CPPListNode* head = new ListNode(1); |
- 遍历链表
1 | CPPListNode* curr = head; |
- 插入节点
在头部插入
1 | CPPListNode* newNode = new ListNode(0); |
在尾部插入
1 | CPPListNode* curr = head; |
在中间插入
1 | CPPListNode* newNode = new ListNode(2.5); |
- 删除节点
删除头部节点
1 | CPPListNode* temp = head; |
删除尾部节点
1 | CPPListNode* curr = head; |
删除中间节点
1 | CPPListNode* temp = head->next; |
- 查找节点
1 | CPPListNode* curr = head; |
- 反转链表
1 | CPPListNode* prev = nullptr; |
- 检测环
使用快慢指针(Floyd’s Cycle Detection Algorithm):
1 | CPPListNode* slow = head; |
二叉树
- 中序遍历(Inorder Traversal)
顺序:左子树 → 根节点 → 右子树
中序遍历会按照“左-根-右”的顺序访问二叉树的节点。
示例代码:
1 | class Solution { |
- 前序遍历(Preorder Traversal)
顺序:根节点 → 左子树 → 右子树
前序遍历会按照“根-左-右”的顺序访问二叉树的节点。
1 | class Solution { |
- 后序遍历(Postorder Traversal)
顺序:左子树 → 右子树 → 根节点
后序遍历会按照“左-右-根”的顺序访问二叉树的节点。
1 | class Solution { |