C++ 基础入门

C++ 是一种广泛使用的、面向对象的、通用的编程语言,它在系统/应用软件开发、游戏开发、嵌入式系统等领域有着广泛的应用。对于初学者来说,掌握 C++ 的基础知识和核心概念是学习编程的重要一步

1. C++ 的历史与特点

C++ 由 Bjarne Stroustrup 在 1985 年开发,最初是作为 C 语言的扩展,后来发展成为一种支持面向对象编程(OOP)的语言。C++ 保留了 C 语言的大部分特性,同时增加了类、继承、多态等 OOP 的核心概念,使得它在处理大型程序时更加高效和灵活

主要特点包括:

灵活性: C++ 支持多种编程范式,包括过程化编程、面向对象编程和泛型编程。开发者可以根据需要选择合适的编程风格,使程序设计更加灵活和多样化

面向对象编程(OOP): C++ 支持类和对象的概念,通过封装、继承和多态等机制实现面向对象编程。这种编程方式提高了代码的复用性和可维护性,使程序结构更加清晰和模块化

泛型编程: C++ 引入了模板机制,允许开发者编写通用的代码,从而提高代码的复用性和灵活性。标准模板库(STL)提供了丰富的容器、算法和迭代器,简化了常见的数据结构和操作

高性能: C++ 是一种编译型语言,可以直接生成高效的机器码,适合开发对性能要求极高的应用。它继承了 C 语言的高效特性,程序运行速度快,执行效率高

内存管理: C++ 提供了对内存的直接控制,开发者可以手动管理内存,但也需要谨慎处理以避免内存泄漏等问题。C++ 还支持智能指针和 RAII 技术,提高了内存管理的安全性

可移植性: C++ 源代码可以在不同平台上编译、链接和运行,无需为每个平台单独编写代码,节省时间和精力。C++ 的可移植性使其成为跨平台开发的理想选择

强类型检查: C++ 是一种静态类型语言,在编译时进行类型检查,提高代码的安全性和效率。这种类型检查机制有助于在编译阶段发现潜在的错误,提高程序的健壮性

标准库支持: C++ 标准库( Standard Template LibrarySTL )提供了大量的容器、算法和迭代器,方便开发者进行高效的编程。这些库函数涵盖了从数据结构到算法实现的各个方面,极大地提高了开发效率

多线程支持: 自 C++11 起,C++ 引入了线程库,便于多线程编程,实现并发执行和任务分配。多线程支持使得 C++ 能够更好地利用现代多核处理器的性能

兼容性: C++ 保持了与 C 语言的兼容性,绝大多数 C 语言程序可以不经修改直接在 C++ 环境中运行。这种兼容性使得 C++ 成为一种灵活的编程语言,能够无缝集成现有的 C 语言代码

指针: C++ 支持指针功能,可以直接与内存交互,用于内存、结构和数组等。指针提供了对内存的直接访问能力,增强了程序的灵活性和效率

模块化: C++ 允许使用函数将程序分解为部分,便于理解和修改。模块化设计使得程序结构更加清晰,便于维护和扩展

2. C++ 的基本结构

C++ 的基本结构是构成程序的基础,它决定了程序的组织方式和执行流程

C++ 程序通常包含以下几个部分

预处理指令:#include <iostream> ,用于引入头文件

命名空间: 不同库或模块中相同标识符(如变量、函数名)可通过命名空间隔离,命名空间类似于操作系统中的目录和文件的关系,用于避免命名冲突,命名空间通过 namespace 关键字定义,在命名空间外部,可以通过使用每个标识符的完整名称或 using 声明来访问其成员

1. using 声明(引入特定成员)

2. using namespace 指令(引入整个空间,慎用)

3. 头文件中禁止使用 using namespace,防止包含时扩散冲突

4. 避免全局污染:优先使用显式限定(Namespace::member)或 using 声明(非 using namespace)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;

namespace First {
void sayHello() {
cout << "Hello First Namespace" << endl;
}
}

namespace Second {
void sayHello() {
cout << "Hello Second Namespace" << endl;
}
}

int main() {
First::sayHello();
Second::sayHello();
return 0;
}

嵌套命名空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

namespace Outer {
int x = 10;
namespace Inner {
int y = 20;
}
}

int main() {
cout << Outer::x << endl;
cout << Outer::Inner::y << endl;
return 0;
}

主函数: int main() 是程序的入口点

输出语句:std::cout << "Hello, World!"; ,用于输出信息

返回值: return 0; 表示程序正常结束

1
2
3
4
5
6
#include <iostream>

int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}

输出:Hello, World!

3. C++ 的基本语法

C++ 的语法与 C 语言非常相似,但有一些扩展和改进

C++ 的一些基本语法元素

1. 变量声明

想要使用变量,必须先做 声明,同时还要指明保存数据所需要的空间大小,使用 int , float , bool , char 等关键字声明变量

1
2
3
4
int a; // 声明
a = 1; // 赋值操作
int a = 1; // 声明且赋值操作
a = 2; // 修改变量值

2. 数据类型

C++ 提供了多种基本数据类型,如 int , float , double , bool , char 等,使用 sizeof() 运算符可获取类型或变量占用的字节数

一、基本数据类型

整数类型: 用于表示整数值,包括 intshortlonglong long 以及它们的无符号版本 unsigned intunsigned shortunsigned longunsigned long long 。这些类型用于存储整数数据,其范围和精度取决于具体类型。例如,int 通常占用 4 个字节,而 short 通常占用 2 个字节

浮点类型: 用于表示实数,包括 floatdoublelong doublefloat 通常占用 4 个字节,可以存储 7 位小数;double 通常占用 8 个字节,可以存储 15 位小数;long double 通常占用 16 个字节,精度更高

字符类型: 用于表示单个字符,包括 charwchar_tchar 通常占用 1 个字节,而 wchar_t 用于表示宽字符,但其大小由实现定义,不可靠

布尔类型: 用于表示布尔值,即 truefalse,通常占用 1 个字节

空类型: 表示没有值或没有返回值,常用于函数返回值或指针,如 void*

二、用户自定义数据类型

结构体(struct)

结构体用于封装多个不同类型的成员变量,从而形成一个逻辑上相关的数据集合,C++中 struct 可包含构造函数、析构函数、继承和多态,功能与 class 几乎相同,仅默认权限不同,结构体成员默认为公有(public),结构体的成员可以通过点运算符(.)

1
2
3
4
5
struct 结构体名称 {
数据类型 成员1;
数据类型 成员2;
// ...
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct Student {
// 数据成员
int id;
std::string name;
double score;

Student(int a, std::string b, double c) : id(a), name(b), score(c), {} // 自定义构造函数

// 成员函数(C++支持)
void printInfo() {
std::cout << "ID: " << id << ", Name: " << name << std::endl;
}
};

Student p1(10, "Alice", 1.5);

std::cout << "ID: " << p1.id << ", Name: " << p1.name << ", Score: " << p1.score << std::endl;

// 2. 创建一个学生对象
// 注意:这里使用了一个简单的构造函数来初始化学生对象
Student s1 = {101, "Alice", 95.5}; // C++11起支持

std::cout << "ID: " << s1.id << ", Name: " << s1.name << ", Score: " << s1.score << std::endl;
1
2
3
输出:
ID: 10, Name: Alice, Score: 1.5
ID: 101, Name: Alice, Score: 95.5

联合(union)

联合用于存储多个数据元素,但这些元素共享同一内存位置,因此只能同时存储一个元素

  1. 联合的大小等于其成员中最大的成员的大小

  2. 联合不能作为基类使用,因为它不能继承自其他类

  3. 联合不能包含引用类型的成员,因为引用需要额外的存储空间

  4. 匿名联合不使用点运算符,其成员必须是数据类型,不允许有成员函数或私有/受保护的成员,为了防止访问错误

  5. 建议在联合中定义一个额外的对象(判别式)来跟踪当前存储的值的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>
using namespace std;

class Token {
public:
enum TokenKind { INT, CHAR, DBL };
TokenKind tok;
union {
char cval;
int ival;
double dval;
} val;

void print() {
switch (tok) {
case INT:
cout << "Integer: " << val.ival << endl;
break;
case CHAR:
cout << "Character: " << val.cval << endl;
break;
case DBL:
cout << "Double: " << val.dval << endl;
break;
}
}
};

int main() {
Token token;

token.tok = Token::INT;
token.val.ival = 42;
token.print();

token.tok = Token::CHAR;
token.val.cval = 'a';
token.print();

token.tok = Token::DBL;
token.val.dval = 3.1416;
token.print();

return 0;
}

枚举(enum)

枚举用于定义一组命名的常量,枚举成员默认从 0 开始递增,也可以显式指定值

1
2
3
4
5
6
7
8
9
10
11
enum class WeekDay {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
};

WeekDay today = WeekDay::Wednesday; // 成员需通过枚举名::访问,避免命名冲突
1
2
3
4
5
6
7
enum RGB {
R = 2,
G,
B = 5
};

G的值为R+1,结果为3

类(class)

类用于创建类实例,包含成员函数和数据成员,是 C++ 面向对象编程的核心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ClassName {
public: // 公共成员(外部可访问)
// 构造函数
ClassName(参数列表) { ... }

// 成员函数
void memberFunc() { ... }

protected: // 受保护成员(仅子类及自身可访问)
int protectedVar;

private: // 私有成员(仅自身可访问)
int privateVar;

}; // 注意分号不可省略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 类定义
class Person {
public:
void setName(const std::string& name);
std::string getName() const;

private:
std::string name;
};

// 成员函数的实现
void Person::setName(const std::string& name) {
this->name = name;
}

std::string Person::getName() const {
return name;
}

三、派生数据类型

派生数据类型是从基本数据类型或用户定义数据类型衍生出来的数据类型

数组

数组用于存储固定大小、相同类型元素的连续内存集合,编译时确定大小,不可动态扩展,数组索引从 0 开始,支持多维数组,内存连续性:元素物理地址相邻,支持高效随机访问

声明方式

1
2
3
4
5
6
7
8
// 一维数组 
int arr1[5](); // 未初始化,值为随机数
int arr2[5]() = {1, 2}; // 部分初始化,剩余元素自动补0 → {1,2,0,0,0}
int arr3[] = {1,2,3}; // 自动推断大小为3

// 二维数组(数组的数组)
int matrix[2][3] = {{1,2,3}, {4,5,6}}; // 标准初始化
int matrix2[][2] = {1,2,3,4}; // 省略行数,自动推断为2行

遍历方式

1
2
3
4
5
6
7
8
9
10
int arr[5]()  = {10,20,30,40,50};
// 1. for循环(推荐控制索引范围)
for(int i=0; i<5; i++) cout << arr[i] << " ";

// 2. 范围for循环(C++11)
for(int num : arr) cout << num << " ";

// 3. 指针遍历
int *p = arr;
while(p < arr + 5) cout << *p++ << " ";

多维数组操作

1
2
3
4
5
6
7
8
int matrix[2]()[3]() = {{1,2,3}, {4,5,6}};
// 嵌套循环遍历
for(int i=0; i<2; i++) {
for(int j=0; j<3; j++) {
cout << matrix[i][j] << " ";
}
}
// 内存按行连续存储:matrix[0][3] 等价于 matrix[1]()[0]()

指针

指针用于存储变量的地址,它允许程序员直接访问和操作内存地址,通过指针,可以实现对内存的灵活管理、动态分配、数组操作、函数调用等高级功能

获取地址与解引用

1
2
3
int num = 10;
int *ptr = &num; // ptr 指向 num 的地址
cout << *ptr; // 输出 10

空指针与野指针

空指针(null pointer) 通常用 nullptr(C++11 引入)或 NULL 表示,表示没有指向任何有效内存地址。野指针(dangling pointer) 是指指向已释放内存的指针,访问野指针会导致未定义行为,预防:指针释放后置为NULL(delete p; p = NULL;),避免再次使用

动态内存分配

1
2
3
4
5
int *pn = new int; // 分配一个整数的内存
int *psome = new int[20]; // 分配 20 个整数的内存

delete pn; // 使用 delete 释放内存
delete[] psome;

指针运算

指针可以进行算术运算,如加减操作,但需注意基于指针指向的数据类型大小

1
2
3
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
p++; // 指向下一个元素

指针与数组

数组名在大多数情况下被视为指向数组首元素的指针,但数组和指针本质不同。数组名没有自己的存储空间,而指针有自己的存储空间

指针作为函数参数

将指针作为函数参数传递时,函数内对指针所指向变量的修改将影响原始变量

1
2
3
4
5
void increment(int *p) {
(*p)++;
}
int num = 10;
increment(&num); // num 变为 11

智能指针

智能指针是C++标准库( <memory> 头文件)提供的自动内存管理工具,通过 RAII(资源获取即初始化)机制,将动态分配的内存(堆内存)封装为对象,在智能指针生命周期结束时自动释放所管理的内存,C++智能指针是现代C++内存管理的核心工具,正确使用它们可以大幅提升代码的安全性和可维护性,解决了手动管理内存的两大痛点:

内存泄漏(忘记delete)

悬空指针(对象已释放但指针仍被使用)

unique_ptr: 用于独占所有权的场景(如管理局部动态对象、避免拷贝)

shared_ptr: 用于共享所有权的场景(如多个对象共享同一资源)

weak_ptr: 用于解决 shared_ptr 循环引用的问题(如观察者模式、缓存)

创建

unique_ptr: 独占所有权的智能指针,同一时间只能有一个 unique_ptr 指向同一个对象,禁止拷贝(copy constructoroperator 被删除),但允许移动(move constructormove operator 有效,优先使用 std::make_unique (C++14引入),它更简洁、异常安全(避免 new 后构造函数抛出异常导致内存泄漏),当 unique_ptr 超出作用域或被 reset() 时,自动调用 delete 释放所管理的内存(若管理数组,则调用 delete[]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <memory>
#include <iostream>

class Test {
public:
Test(int id) : id_(id) { std::cout << "Test " << id_ << " constructed.\n"; }
~Test() { std::cout << "Test " << id_ << " destructed.\n"; }
void show() { std::cout << "Test " << id_ << " is alive.\n"; }
private:
int id_;
};

int main() {
// 方式1:用make_unique创建(推荐)
std::unique_ptr<Test> up1 = std::make_unique<Test>(1);
up1->show(); // 访问对象成员

// 方式2:用new直接构造(不推荐,除非需要自定义删除器)
std::unique_ptr<Test> up2(new Test(2));
up2->show();

return; // up1、up2超出作用域,自动析构Test对象
}
1
2
3
4
5
6
7
8
9
10
11
12
13
输出:

Test 1 constructed

Test 1 is alive

Test 2 constructed

Test 2 is alive

Test 2 destructed

Test 1 destructed

shared_ptr: 共享所有权的智能指针,多个 shared_ptr 可以指向同一个对象,通过引用计数( reference count )跟踪对象的被引用次数,当最后一个 shared_ptr 析构或 reset() 时,引用计数变为0,自动释放对象内存,引用计数的修改是原子操作(线程安全),但对象本身的访问需要手动同步(如用 mutex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <memory>
#include <iostream>

class Test {
public:
Test(int id) : id_(id) { std::cout << "Test " << id_ << " constructed.\n"; }
~Test() { std::cout << "Test " << id_ << " destructed.\n"; }
void show() { std::cout << "Test " << id_ << " is alive.\n"; }
private:
int id_;
};

int main() {
// 方式1:用make_shared创建(推荐)
std::shared_ptr<Test> sp1 = std::make_shared<Test>(1);
std::cout << "sp1 use count: " << sp1.use_count() << "\n"; // 引用计数:1

// 方式2:用new直接构造(不推荐)
std::shared_ptr<Test> sp2(new Test(2));
std::cout << "sp2 use count: " << sp2.use_count() << "\n"; // 引用计数:1

// 拷贝shared_ptr,引用计数增加
std::shared_ptr<Test> sp3 = sp1;
std::cout << "sp1 use count after copy: " << sp1.use_count() << "\n"; // 引用计数:2

return; // sp1、sp2、sp3析构,引用计数变为0,自动释放对象
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
输出:
Test 1 constructed

sp1 use count: 1

Test 2 constructed

sp2 use count: 1

sp1 use count after copy: 2

Test 2 destructed

Test 1 destructed

移动所有权

unique_ptr: 不能拷贝,但可以通过 std::move 转移所有权(原 unique_ptr 变为 nullptr

1
2
3
4
5
6
7
std::unique_ptr<Test> up1 = std::make_unique<Test>(1); 
std::unique_ptr<Test> up2 = std::move(up1); // 转移所有权,up1变为nullptr

if (up1 == nullptr) {
std::cout << "up1 is null.\n";
}
up2->show(); // up2拥有对象所有权
1
2
3
4
5
6
7
8
9
输出:

Test 1 constructed

up1 is null

Test 1 is alive

Test 1 destructed

管理动态数组

unique_ptr: 支持管理动态数组,需指定数组类型(T[]),此时会自动调用 delete[] 释放内存

1
2
3
4
5
6
7
std::unique_ptr<int[]> up_arr = std::make_unique<int[]>(5); // 创建5个int的数组 
for (int i = 0; i < 5; ++i) {
up_arr[i] = i + 1; // 像普通数组一样访问
std::cout << up_arr[i] << " ";
}
std::cout << "\n";
// 超出作用域时,自动释放数组(delete[])

引用计数操作

shared_ptr:
use_count():返回当前引用计数(仅用于调试,不要依赖其值做逻辑判断)
reset():重置 shared_ptr,引用计数减少(若指定新对象,则指向新对象)
get():返回裸指针(需谨慎使用,避免悬空指针)

1
2
3
4
5
6
7
8
9
std::shared_ptr<Test> sp1 = std::make_shared<Test>(1); 
std::shared_ptr<Test> sp2 = sp1;
std::cout << "sp1 use count: " << sp1.use_count() << "\n"; // 2

sp2.reset(); // sp2重置,引用计数减少到1
std::cout << "sp1 use count after reset sp2: " << sp1.use_count() << "\n"; // 1

sp1.reset(new Test(2)); // sp1指向新对象,原对象引用计数变为0,自动释放
std::cout << "sp1 use count after reset to new object: " << sp1.use_count() << "\n"; // 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
输出:

Test 1 constructed

sp1 use count: 2

sp1 use count after reset sp2: 1

Test 1 destructed

Test 2 constructed

sp1 use count after reset to new object: 1

Test 2 destructed

循环引用问题

shared_ptr: 当两个 shared_ptr 互相引用时,会导致引用计数永远不为0,内存无法释放(内存泄漏)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <memory>
#include <iostream>

struct A {
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A destructed.\n"; }
};

struct B {
std::shared_ptr<A> a_ptr;
~B() { std::cout << "B destructed.\n"; }
};

int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b; // A引用B
b->a_ptr = a; // B引用A(循环引用)

// a和b析构时,引用计数各为1(互相引用),无法释放
return;
}
1
2
输出:
(无析构输出,内存泄漏)

避免循环引用:weak_ptr 代替 shared_ptr 来打破循环

不要用裸指针初始化多个shared_ptr:

1
2
3
4
5
6
7
int* p = new int; 

std::shared_ptr<int> sp1(p);

std::shared_ptr<int> sp2(p);

会导致p被释放两次(程序崩溃)

优先使用make_shared: make_sharednew 更高效,且避免内存泄漏(例如 new 后构造函数抛出异常, make_shared 会自动回收内存

弱引用的智能指针

弱引用: 指向 shared_ptr 管理的对象,但不增加引用计数(不影响对象的生命周期)

解决循环引用: 当两个对象互相引用时,用 weak_ptr 代替其中一个 shared_ptr ,打破循环

需锁定访问: 不能直接访问对象,必须通过 lock() 方法获取 shared_ptr (若对象已释放, lock() 返回 nullptr

修改上面的循环引用例子,将j结构体 B 中的 shared_ptr<A> 改为 weak_ptr<A>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <memory>
#include <iostream>

struct A {
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A destructed.\n"; }
};

struct B {
std::weak_ptr<A> a_ptr; // 用weak_ptr代替shared_ptr
~B() { std::cout << "B destructed.\n"; }
};

int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b; // A引用B(shared_ptr)
b->a_ptr = a; // B引用A(weak_ptr,不增加引用计数)

// a析构时,引用计数变为0(b的a_ptr是weak_ptr,不影响),释放A;
// A释放后,b的引用计数变为0(a的b_ptr是shared_ptr),释放B。
return;
}
1
2
3
4
5
输出:

A destructed

B destructed

访问 weak_ptr 指向的对象: 需通过lock()方法获取 shared_ptr ,若对象已释放, lock() 返回 nullptr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
std::shared_ptr<Test> sp = std::make_shared<Test>(1); 
std::weak_ptr<Test> wp = sp; // 从shared_ptr构造weak_ptr

// 方式1:用lock()获取shared_ptr
if (auto locked_sp = wp.lock()) {
locked_sp->show(); // 访问对象
} else {
std::cout << "Object has been destructed.\n";
}

// 方式2:用expired()判断对象是否已释放
if (!wp.expired()) {
auto locked_sp = wp.lock();
locked_sp->show();
} else {
std::cout << "Object has been destructed.\n";
}

sp.reset(); // 释放对象
if (wp.expired()) {
std::cout << "Object has been destructed.\n";
}
1
2
3
4
5
6
7
8
9
10
输出:
Test 1 constructed

Test 1 is alive

Test 1 is alive

Test 1 destructed

Object has been destructed

函数

函数包含逻辑代码,可被程序多次调用,一个完整的函数定义主要包括:返回类型、函数名、参数列表和函数体

1
2
3
返回类型 函数名(参数列表) {
// 函数体
}

返回类型: 指定返回值的数据类型(如 void 表示无返回值)

函数名称: 这是函数的实际名称。函数名和参数列表一起构成了函数签名(告知编译器)

参数列表: 参数就像是占位符,可为空或包含多个参数,参数按值传递(拷贝副本)或引用传递(修改原始数据)

函数体: 函数要执行的语句块,函数的主体

高级特性

函数重载: 是 C++ 中编译时多态的核心机制之一,重载函数的声明和定义与普通函数类似,但需保证参数列表不同,通过不同参数列表实现同名函数,需满足参数类型、个数或顺序差异

1. 参数个数不同

1
2
void fun() 
void fun(int a)

2. 参数类型不同

1
2
void fun(int a) 
void fun(double a)

3. 参数顺序不同: 不同顺序中需要有不同的类型

1
2
void fun(int a, double b) 
void fun(double a, int b)
1
2
3
4
void fun(int a, int b) 
void fun(int b int a)

// 这种是不行的, 形参名的调换不构成重载

注意:返回值类型不同不能作为函数重载的依据

1
2
3
4
// 无法重载,编译器会报错“重定义”

int fun()
void fun()

引用参数的特殊处理

当重载函数包含引用参数( int&const int&int&& )时,编译器会根据实参的左值/右值属性匹配

1. int&: 匹配可修改的左值(如变量)

2. const int&: 匹配不可修改的左值(如 const 变量)或右值(如表达式结果)

3. int&&: 匹配右值(如临时对象、表达式结果)

1
2
3
4
5
6
7
8
9
10
11
12
void fun(int& a) { cout << "左值引用: " << a << endl; } 
void fun(const int& a) { cout << "const左值引用: " << a << endl; }
void fun(int&& a) { cout << "右值引用: " << a << endl; }

int main() {
int a = 10; // 左值
const int b = 20; // const左值
fun(a); // 调用 fun(int&)
fun(b); // 调用 fun(const int&)
fun(a + b); // 调用 fun(int&&)(a+b是右值)
return 0;
}
1
2
3
4
5
6
7
输出:

左值引用: 10

const左值引用: 20

右值引用: 30

函数重载仅在同一作用域内有效:

1
2
3
4
5
6
7
8
9
10
11
12
13
void fun() { cout << "全局函数" << endl; }  // 全局作用域

class MyClass {
public:
void fun() { cout << "类成员函数" << endl; } // 类作用域
};

int main() {
fun(); // 调用全局函数
MyClass obj;
obj.fun(); // 调用类成员函数
return 0;
}

上述代码不会报错,因为两个fun函数属于不同作用域

默认参数

在声明或定义中为参数指定默认值,调用时可省略

1
2
3
void display(int a = 10, int b = 20) { /* ... */ }

display();

内联函数

使用 inline 关键字建议编译器展开函数体,也就是代码直接嵌入调用处,减少调用开销

适用于: (通常 ≤ 10行代码)且频繁调用(如循环内部)、类成员访问器( getter/setter

不适用于: 函数体含循环/递归、代码较长(导致代码膨胀)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 正确方式:定义时加 inline 关键字(声明中加无效)
inline int add(int a, int b) {
return a + b;
}

// 类内定义自动内联(无需显式写 inline)
class Circle {
private:
double radius;
public:
double getArea() { // 自动视为内联
return 3.14 * radius * radius;
}
};

// 类外定义需显式 inline
class Circle {
public:
double getArea();
};
inline double Circle::getArea() {
return 3.14 * radius * radius;
}

递归函数

函数直接或间接调用自身,需设置基准条件避免无限递归

1
2
3
int factorial(int n) { 
return n == 1 ? 1 : n * factorial(n-1);
}

Lambda 函数与表达式

C++11 提供了对匿名函数的支持,称为 Lambda 函数(也叫 Lambda 表达式),它们通常用于需要一个小型、临时的函数,以便在某个上下文中使用

1
[capture_list](parameter_list) mutable -> return_type { function_body; }

捕获列表 (capture_list): 指定哪些外部变量可以被 Lambda 函数访问,以及是以值还是引用的方式访问

mutable:允许修改值捕获的副本(默认不可修改)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>

int main() {
int x = 10;

// 使用 mutable 关键字允许修改按值捕获的变量
auto lambda = [x]() mutable {
x += 5;
std::cout << "x inside lambda: " << x << std::endl; // 输出: x inside lambda: 15
};

lambda();
std::cout << "x outside lambda: " << x << std::endl; // 输出: x outside lambda: 10
return 0;
}

常见的捕获方式包括

[] 不捕获任何变量,使用未定义变量会引发错误

[&] 捕获所有外部变量的引用,任何被使用到的外部变量都隐式地以引用方式加以引用

[=] 捕获所有外部变量的值(副本),任何被使用到的外部变量都隐式地以传值方式加以引用

[a, &b] 变量 a 按值捕获,变量 b 按引用捕获

[=, &a, &b] 除 a 和 b 按引用捕获外,其他变量按值捕获

[&, a, b] 除 a 和 b 按值捕获外,其他变量按引用捕获

[this] 捕获指向当前对象的 this 指针(访问成员变量)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>

int main() {
int x = 10;
int y = 5;

// 按值捕获 x,按引用捕获 y
auto lambda = [=, &y]() -> int {
x += 2;
y += 3;
return x + y;
};

int result = lambda();
std::cout << "Result: " << result << std::endl; // 输出: Result: 20
std::cout << "x: " << x << ", y: " << y << std::endl; // 输出: x: 10, y: 8
return 0;
}

参数列表 (parameter_list): Lambda 函数的参数列表,类似于普通函数

返回类型 (return_type): Lambda 函数的返回类型,可以省略,编译器会自动推断

函数体 (function_body): Lambda 函数的具体实现代码

虚函数和纯虚函数

虚函数允许子类重写父类的方法。纯虚函数是一种没有实现的方法,要求子类必须实现

1
2
3
4
5
6
7
8
9
10
11
class Base {
public:
virtual void display() = 0; // 纯虚函数
};

class Derived : public Base {
public:
void display() override {
std::cout << "Derived class" << std::endl;
}
};

友元函数

友元函数是一种非成员函数,但可以访问其私有和受保护的成员。友元函数通过在类内部使用 friend 关键字声明,从而获得访问类私有成员的权限。友元函数不是类的成员函数,因此不能使用 this 指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Box {
private:
int width;
public:
Box(int w) : width(w) {}
friend int friendGetArea(Box box);
};

int friendGetArea(Box box) {
return box.width * box.width;
}

int main() {
Box box(5);
std::cout << friendGetArea(box) << std::endl; // 输出: Area: 25
}

构造函数和拷贝构造函数

构造函数在创建对象时自动调用,用于初始化资源和状态。拷贝构造函数用于从现有对象复制数据到新对象

1
2
3
4
5
class A {
public:
A() {} // 构造函数
A(const A& other) {} // 拷贝构造函数
};

引用

引用是C++中的一种特殊的数据类型描述,用于在程序的不同部分使用两个以上的变量名指向同一块地址,使得对其中任何一个变量的操作实际上都是对同一地址单元进行的操作。引用的声明方式为:类型名称 & 引用名 = 原名

1
2
3
4
int a = 10;
int& b = a; // b是a的引用
b = 20; // 修改b会同步修改a
cout << a; // 输出20 [3]()

引用的核心特点

1. 必须初始化: 声明时必须绑定目标变量,如 int& b = a

2. 无独立内存空间: 引用与目标变量共享内存地址,&b == &a

3. 不可重新绑定: 一旦绑定目标变量,无法再指向其他变量

4. 安全性优势: 不能为 NULL,避免空指针风险

5. 引用作为函数参数: 使用引用传递参数能避免数据副本,提高效率,且更清晰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
using namespace std;

void exchange(int& x, int& y) {
int temp = x;
x = y;
y = temp;
}

int main() {
int a, b;
cout << "请输入两个数字: " << endl;
cin >> a >> b;
exchange(a, b);
cout << "交换后: " << a << " " << b << endl;
return 0;
}

常量

常量是固定且不可变的值,一旦初始化,其值在程序中不可改变。常量通常使用 const 关键字修饰,常量的作用域与其他变量相同,可以是块作用域、函数作用域、文件作用域或命名空间作用域

1
2
3
4
5
6
7
void example() {
const int BLOCK_CONSTANT = 5; // 块作用域
// BLOCK_CONSTANT 只能在 example 函数内部使用
}

const int GLOBAL_CONSTANT = 10; // 文件作用域
// GLOBAL_CONSTANT 可以在整个文件中使用

常量可以是任何基本数据类型或复合数据类型,包括指针(但指针指向的内容可以被修改,除非指针本身也是指向常量的指针)

1
2
3
const int* ptr; // 指针指向的值是常量
int* const ptr2; // 指针本身是常量
const int* const ptr3; // 指针本身和指向的值都是常量

常量引用可以初始化为左值或右值,但初始化为右值时,只能使用 const T& 类型

1
2
3
4
int a = 10;
const int& ref = a; // 正确
const int& ref2 = 20; // 正确
int& ref3 = 30; // 错误

常量函数: 通过在函数名后添加 const 关键字声明为常量函数,这些函数不能改变对象的值,且只能由非常量对象调用

1
2
3
4
5
6
7
8
9
class Example {
public:
int getValue() const {
return value;
}

private:
int value;
};

四、 运算符

包括算术运算符(+, -, *, /, %)、关系运算符(==, !=, >, <, >=, <=)、逻辑运算符(&&, ||, !)等

算术运算符

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
using namespace std;

int main() {
int a = 10, b = 20;
cout << "加法: " << a + b << endl; // 输出: 30
cout << "减法: " << a - b << endl; // 输出: -10
cout << "乘法: " << a * b << endl; // 输出: 200
cout << "除法: " << b / a << endl; // 输出: 2
cout << "取模: " << b % a << endl; // 输出: 0
return 0;
}

关系运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
using namespace std;

int main() {
int a = 10, b = 20;
cout << "等于: " << (a == b) << endl; // 输出: 0 (false)
cout << "不等于: " << (a != b) << endl; // 输出: 1 (true)
cout << "大于: " << (a > b) << endl; // 输出: 0 (false)
cout << "小于: " << (a < b) << endl; // 输出: 1 (true)
cout << "大于等于: " << (a >= b) << endl; // 输出: 0 (false)
cout << "小于等于: " << (a <= b) << endl; // 输出: 1 (true)
return 0;
}

逻辑运算符

1
2
3
4
5
6
7
8
9
10
#include <iostream>
using namespace std;

int main() {
bool x = true, y = false;
cout << "逻辑与: " << (x && y) << endl; // 输出: 0 (false)
cout << "逻辑或: " << (x || y) << endl; // 输出: 1 (true)
cout << "逻辑非: " << (!x) << endl; // 输出: 0 (false)
return 0;
}

赋值运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;

int main() {
int a = 10;
int b = a; // 基本赋值
int c = 20;
c += a; // 加法赋值,等价于 c = c + a
c -= a; // 减法赋值,等价于 c = c - a
c *= a; // 乘法赋值,等价于 c = c * a
c /= a; // 除法赋值,等价于 c = c / a
c %= a; // 取模赋值,等价于 c = c % a
cout << "基本赋值: " << b << endl; // 输出: 10
cout << "加法赋值: " << c << endl; // 输出: 30
cout << "减法赋值: " << c << endl; // 输出: 20
cout << "乘法赋值: " << c << endl; // 输出: 200
cout << "除法赋值: " << c << endl; // 输出: 20
cout << "取模赋值: " << c << endl; // 输出: 0
return 0;
}

指针运算符

1
2
3
4
5
6
7
8
9
10
#include <iostream>
using namespace std;

int main() {
int var = 20;
int *ptr = &var; // 取地址
cout << "变量的地址: " << ptr << endl;
cout << "通过指针访问变量的值: " << *ptr << endl; // 解引用
return 0;
}

位运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
using namespace std;

int main() {
int a = 60; // 二进制表示为 0011 1100
int b = 13; // 二进制表示为 0000 1101
cout << "按位与: " << (a & b) << endl; // 输出: 12 (二进制 0000 1100)
cout << "按位或: " << (a | b) << endl; // 输出: 61 (二进制 0011 1101)
cout << "按位异或: " << (a ^ b) << endl; // 输出: 49 (二进制 0011 0001)
cout << "左移: " << (a << 2) << endl; // 输出: 240 (二进制 1111 0000)
cout << "右移: " << (a >> 2) << endl; // 输出: 15 (二进制 0000 1111)
return 0;
}

五、控制结构

包括 if-elseswitchforwhile 等循环和条件语句

1
2
3
4
5
6
7
if (x > 0) {
cout << "Positive number";
} else if (x == 0) {
cout << "Zero";
} else {
cout << "Negative number";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
using namespace std;

int main() {
char op;
float num1, num2;

cout << "Enter operator either + or - or * or /: ";
cin >> op;

cout << "Enter two operands: ";
cin >> num1 >> num2;

switch(op) {
case '+':
cout << num1 + num2;
break;

case '-':
cout << num1 - num2;
break;

case '*':
cout << num1 * num2;
break;

case '/':
if(num2 != 0.0)
cout << num1 / num2;
else
cout << "Divide by zero situation";
break;

default:
cout << "Error! operator is not correct";
break;
}

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for (int i = 0; i < 10; i++) {
if (i == 5) break;
cout << i << " ";
}

int i = 0;
while (i < 10) {
cout << i << " ";
i++;
}

int i = 0;
do {
cout << i << " ";
i++;
} while (i < 10);

4. 面向对象编程(OOP)

C++ 是一种面向对象的编程语言,支持 OOP 的核心概念

1. 类(Class)

定义对象的蓝图,包含数据成员和成员函数

2. 对象(Object)

类的实例,具有类定义的属性和行为

3. 封装(Encapsulation)

通过访问修饰符( public , private , protected )控制类的内部成员的访问

4. 继承(Inheritance)

子类可以继承父类的属性和方法,实现代码复用

5. 多态(Polymorphism)

通过虚函数实现不同类对象的统一接口,提高代码的灵活性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>

class Rectangle {
public:
int width, height;
Rectangle(int w, int h) : width(w), height(h) {}
int area() const {
return width * height;
}
};

int main() {
Rectangle rect(5, 10);
std::cout << "Area: " << rect.area() << std::endl;
return 0;
}

输出:Area: 50

5. 标准模板库(STL)

C++ 的 STL 提供了丰富的容器、算法和迭代器,简化了开发过程

容器

管理数据的集合,封装存储结构

常用的容器包括

向量(vector): 动态数组,支持随机访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
push_back(value):在向量末尾添加一个元素

pop_back():移除向量的最后一个元素

at(index):返回指定索引位置的元素,如果索引越界会抛出异常

operator[]:返回指定索引位置的元素,不进行越界检查

front():返回向量的第一个元素

back():返回向量的最后一个元素

begin():返回指向向量第一个元素的迭代器

end():返回指向向量末尾元素后一个位置的迭代器

size():返回向量中元素的数量

capacity():返回当前向量分配的内存大小

resize(new_size, value):调整向量大小,如果新大小大于当前大小,则用指定值填充新元素;如果新大小小于当前大小,则删除多余元素

reserve(new_capacity):预先分配足够的内存空间,避免频繁的内存重新分配

clear():清空向量中的所有元素

empty():判断向量是否为空

swap(other_vector):交换两个向量的内容

insert(position, value):在指定位置插入一个元素

erase(position):删除指定位置的元素

sort():对向量中的元素进行排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include <iostream>
#include <vector>
#include <algorithm> // for sort function

using namespace std;

int main() {
// 声明一个整数向量
vector<int> vec;

// 使用 push_back 添加元素
vec.push_back(10);
vec.push_back(20);
vec.push_back(30);

// 使用 at 访问元素
cout << "Element at index 1: " << vec.at(1) << endl;

// 使用 operator[] 访问元素
cout << "Element at index 2: " << vec[2] << endl;

// 使用 front 和 back 访问首尾元素
cout << "First element: " << vec.front() << endl;
cout << "Last element: " << vec.back() << endl;

// 使用 begin 和 end 遍历向量
cout << "Vector elements: ";
for (auto it = vec.begin(); it != vec.end(); ++it) {
cout << *it << " ";
}
cout << endl;

// 使用 size 获取向量大小
cout << "Size of vector: " << vec.size() << endl;

// 使用 capacity 获取向量容量
cout << "Capacity of vector: " << vec.capacity() << endl;

// 使用 resize 调整向量大小
vec.resize(5, 0); // 调整为5个元素,新元素初始化为0
cout << "After resizing, size: " << vec.size() << endl;
cout << "After resizing, elements: ";
for (int num : vec) {
cout << num << " ";
}
cout << endl;

// 使用 reserve 预先分配内存
vec.reserve(10);
cout << "After reserving, capacity: " << vec.capacity() << endl;

// 使用 clear 清空向量
vec.clear();
cout << "After clearing, size: " << vec.size() << endl;

// 使用 empty 判断向量是否为空
if (vec.empty()) {
cout << "Vector is empty." << endl;
}

// 使用 insert 插入元素
vec.insert(vec.begin(), 5);
vec.insert(vec.begin() + 1, 15);
cout << "After inserting elements, vector: ";
for (int num : vec) {
cout << num << " ";
}
cout << endl;

// 使用 erase 删除元素
vec.erase(vec.begin() + 1);
cout << "After erasing element at index 1, vector: ";
for (int num : vec) {
cout << num << " ";
}
cout << endl;

// 使用 sort 对向量进行排序
vector<int> numbers = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
sort(numbers.begin(), numbers.end());
cout << "Sorted vector: ";
for (int num : numbers) {
cout << num << " ";
}
cout << endl;

return 0;
}

列表(list): 双向链表,支持插入和删除操作

构造函数

1
2
3
4
list<int> a{1, 2, 3};:初始化一个包含元素1, 2, 3的列表
list<int> a(n);:声明一个包含n个元素的列表,每个元素都是0
list<int> a(n, m);:声明一个包含n个元素的列表,每个元素都是m
list<int> a(first, last);:声明一个列表,其元素的初始值来源于由区间所指定的序列中的元素,first和last是迭代器

插入元素

1
2
3
4
5
push_front(value):将元素从前面推入列表
push_back(value):在列表末尾插入一个新元素
insert(pos_iter, ele_num, ele):在指定位置插入指定数量的元素
emplace_front(args...):在列表开头构造元素
emplace_back(args...):在列表末尾构造元素

删除元素

1
2
3
4
5
pop_front():从正面弹出或删除列表中的元素
pop_back():从末尾弹出或删除列表中的元素
remove(value):删除所有与给定值相等的元素
remove_if(predicate):删除所有对谓词返回true的元素
clear():删除列表容器的所有元素,使其大小为0

检查状态

1
2
empty():检查列表容器是否为空。
size():返回列表中元素的数量。

其他操作

1
2
3
4
5
assign(first, last):将新内容分配给列表容器,并用新内容替换旧内容
sort():对列表进行排序
reverse():反转列表中元素的顺序
merge(other_list):合并两个已排序的列表
unique():删除连续重复的元素
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <list>
using namespace std;

int main() {
list<int> mylist{1, 2, 3, 4, 5};
mylist.push_front(6); // 在前面插入6
mylist.push_back(7); // 在末尾插入7

for (auto it = mylist.begin(); it != mylist.end(); ++it)
cout << ' ' << *it;
return 0;
}

映射(map): 键值对集合,支持快速查找,map 中的键必须是唯一的。如果插入一个已经存在的键,新的值不会替换原有值,而是忽略插入操作,map 内部会自动根据键的大小进行排序,因此遍历时元素是按顺序排列的,由于 map 基于红黑树实现,查找、插入和删除操作的时间复杂度为 O(log n),适合需要频繁查找和修改的场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
插入元素:
insert(pair<const Key, T> val):插入一个键值对
operator[]:通过键访问或插入值。如果键不存在,则插入默认值

访问元素:
operator[]:通过键访问值
at(const Key& key):通过键访问值,如果键不存在则抛出异常

查找元素:
find(const Key& key):返回指向键为 key 的元素的迭代器,如果不存在则返回 end()
count(const Key& key):返回键为 key 的元素数量(对于 map,返回 0 或 1)

删除元素:
erase(iterator position):删除指定位置的元素
erase(const Key& key):删除键为 key 的元素
clear():清空所有元素

遍历元素:
使用迭代器遍历,例如 for (auto it = m.begin(); it != m.end(); ++it)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <map>
using namespace std;

int main() {
// 创建一个 map 容器
map<string, int> ageMap;

// 添加元素
ageMap["Alice"] = 30;
ageMap["Bob"] = 25;

// 访问元素
cout << "Alice's age: " << ageMap["Alice"] << endl;

// 遍历 map
for (const auto& pair : ageMap) {
cout << pair.first << ": " << pair.second << endl;
}

// 查找元素
if (ageMap.count("Bob")) {
cout << "Bob is in the map." << endl;
}

// 删除元素
ageMap.erase("Alice");

// 清空 map
ageMap.clear();

return 0;
}

集合(set ): 有序集合,支持唯一元素,集合中的元素不能重复,可以添加或删除元素,但不能修改现有元素的值,插入元素时自动排序,元素通过键引用,而非容器中的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
insert():用于向set中插入元素

erase():用于从set中删除元素

clear():用于清除set中的所有元素

empty():用于检查set是否为空

size():用于返回set的大小

begin():返回指向第一个元素的迭代器

end():返回指向最后一位元素的下一位的迭代器

find():查找指定元素,返回其迭代器或结束迭代器

count():统计集合中特定元素的数量

lower_bound():返回第一个大于等于给定值的值的地址

upper_bound():返回第一个大于给定值的值的地址

equal_range():返回一个pair,first为第一个大于等于目标的迭代器,second为第一个大于目标的迭代器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <set>
using namespace std;

int main() {
set<int> s;
s.insert(10);
s.insert(20);
s.insert(30);

// 遍历set
for (int a : s) {
cout << a << " ";
}
cout << endl;

// 检查元素是否存在
if (s.find(20) != s.end()) {
cout << "20 exists in set" << endl;
}

// 删除元素
s.erase(20);

// 检查set是否为空
if (!s.empty()) {
cout << "Set is not empty" << endl;
}

// 获取set的大小
cout << "Size of set: " << s.size() << endl;

return 0;
}

迭代器

提供遍历容器的统一接口,连接容器与算法,begin() , end() , 迭代器是STL中泛化的指针,提供统一接口遍历不同容器(如 vectorlistmap )的元素

1. 基础遍历(所有容器通用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <vector>
#include <iostream>
using namespace std;
int main() {
vector<int> vec = {1, 2, 3, 4};
// 正向遍历
for (auto it = vec.begin(); it != vec.end(); ++it) {
cout << *it << " "; // 输出:1 2 3 4
}
// C++11范围for循环(底层基于迭代器)
for (int val : vec) {
cout << val << " "; // 同上
}
return 0;
}
// 范围 for 循环内部实现基于迭代器,因此在循环中不能随意增加或删除元素,否则会导致未定义行为

2. 反向遍历(双向/随机访问迭代器支持)

1
2
3
4
list<string> words = {"hello", "world"};
for (auto rit = words.rbegin(); rit != words.rend(); ++rit) {
cout << *rit << " "; // 输出:world hello
}

3. 插入与删除操作

1
2
3
4
vector<int> v = {10, 20, 30};
auto it = v.begin() + 1;
it = v.insert(it, 15); // 插入后v={10,15,20,30},it指向15
it = v.erase(it); // 删除15,it指向20(需更新迭代器!)

4. 关联容器特例(set/map)

1
2
3
4
5
6
7
8
9
10
set<int> s = {5, 2, 8, 1};
// 遍历自动排序结果:1 2 5 8
for (auto it = s.begin(); it != s.end(); ++it) {
cout << *it << " ";
}
// 查找操作(返回迭代器)
auto pos = s.find(5);
if (pos != s.end()) {
s.erase(pos); // 安全删除
}

vector的push_back()可能引发扩容,导致所有迭代器失效;erase()使被删元素及之后的迭代器失效

对vector/deque插入/删除元素后,必须重新获取迭代器。关联容器(如set)删除元素时,优先使用it = container.erase(it) 语法

算法

独立于容器的通用操作(排序、查找等),sort() , find() , copy()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// sort(): 对容器进行排序
sort(a.begin(), a.end()); // 默认升序排序
sort(a.begin(), a.end(), greater<>()); // 使用greater对象进行降序排序

// distance(): 计算两个迭代器之间的距离
int dist = distance(a.begin(), a.end()); // 计算a的大小

// copy(): 将一个范围内的元素复制到另一个范围
vector<int> b(a.size());
copy(a.begin(), a.end(), b.begin()); // 将a的内容复制到b

// next_permutation(): 生成下一个排列
sort(a.begin(), a.end()); // 先排序
do {
// 处理当前排列
} while (next_permutation(a.begin(), a.end())); // 生成所有排列

// set_intersection(): 计算两个集合的交集
vector<int> a{1, 2, 3, 4, 5};
vector<int> b{3, 4, 5, 6, 7};
vector<int> result;
set_intersection(a.begin(), a.end(), b.begin(), b.end(), back_inserter(result)); // 计算交集

函数对象

函数对象是重载了 operator() 的类对象,可像函数一样被调用。本质是类实例,而非函数指针,实现定制化操作,谓词(如 greater<int> ),函数对象可包含成员变量,记录调用间的状态(普通函数无法做到)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <vector>
#include <algorithm>

struct GreaterThan {
bool operator()(int a, int b) const {
return a > b;
}
};

int main() {
std::vector<int> vec = {5, 3, 8, 1, 2};
std::sort(vec.begin(), vec.end(), GreaterThan());
for (int num : vec) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}

适配器

修改组件接口(如容器→栈/队列),栈 stack , 队列 queue, 优先队列 priority_queue

栈(stack)

栈是一种后进先出(LIFO)的数据结构。栈适配器可以基于 deque、vector 或 list。默认情况下,栈适配器使用 deque 作为底层容器,只能从栈顶进行插入和删除操作,提供 push()、pop()、top()、empty() 和 size() 等操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <stack>
#include <list>

int main() {
// 创建一个使用 list 作为基础容器的 stack 适配器
std::list<int> values{1, 2, 3};
std::stack<int, std::list<int>> my_stack(values);

// 查看 my_stack 存储元素的个数
std::cout << "size of my_stack: " << my_stack.size() << std::endl;

// 将 my_stack 中存储的元素依次弹栈,直到其为空
while (!my_stack.empty()) {
std::cout << my_stack.top() << std::endl; // 输出栈顶元素
my_stack.pop(); // 弹出栈顶元素
}

return 0;
}

队列(queue)

队列是一种先进先出(FIFO)的数据结构。队列适配器默认使用 deque 作为底层容器,也可以使用 list,元素在队尾插入并在前端删除,提供 push()、pop()、front()、back()、empty() 和 size() 等操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <queue>

int main() {
std::queue<int> q;

// 在队尾压入新元素
q.push(1);
q.push(2);
q.push(3);

// 查看队列长度
std::cout << "size of queue: " << q.size() << std::endl;

// 将队列中的元素依次弹出,直到其为空
while (!q.empty()) {
std::cout << q.front() << std::endl; // 输出队首元素
q.pop(); // 弹出队首元素
}

return 0;
}

优先队列(priority_queue)

优先队列是一种不遵循 FIFO 或 LIFO 原则的数据结构,元素的排序基于优先级(使用最大堆),因此具有最高优先级的元素始终位于前端。优先队列适配器默认使用 vector 作为底层容器,也可以使用 deque,元素的排序基于优先级,提供 push()、pop()、top()、empty() 和 size() 等操作,优先队列适配器不能基于 list,因为 list 不支持随机访问操作,可以自定义比较函数来实现不同的优先级规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <queue>
#include <vector>

struct Node {
int x, y;
Node(int a = 0, int b = 0) : x(a), y(b) {}
};

struct cmp {
bool operator()(Node a, Node b) {
if (a.x == b.x) return a.y > b.y;
return a.x > b.x;
}
};

int main() {
std::priority_queue<Node, std::vector<Node>, cmp> q;

for (int i = 0; i < 10; ++i)
q.push(Node(rand(), rand()));

while (!q.empty()) {
std::cout << q.top().x << ' ' << q.top().y << std::endl;
q.pop();
}

return 0;
}

分配器

管理堆内存的对象,管理内存分配策略(默认基于 new/delete),allocator<T>

分配器提供了多种成员函数

1
2
3
4
5
6
7
allocate(size_t n):分配n个对象所需的内存

deallocate(T* p, size_t n):释放由allocate分配的内存

construct(T* p, const T& val):在已分配的内存上构造对象

destroy(T* p):销毁对象但不解分配内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <memory>

int main() {
std::allocator<int> alloc;
int* p = alloc.allocate(5); // 分配5个int的内存

for (int i = 0; i < 5; ++i) {
alloc.construct(p + i, i * 10); // 构造对象
}

for (int i = 0; i < 5; ++i) {
std::cout << p[i] << " "; // 输出: 0 10 20 30 40
}
std::cout << std::endl;

for (int i = 0; i < 5; ++i) {
alloc.destroy(p + i); // 销毁对象
}
alloc.deallocate(p, 5); // 释放内存

return 0;
}

6. 文件操作

在C++中,文件 I/O(输入/输出)是程序与外部世界交换信息的重要手段。它允许程序从文件中读取数据或将数据写入文件。C++标准库提供了多种文件流类来处理文件操作,文件 I/O 操作主要通过 <fstream> 库实现

常用的文件操作包括

ifstream: 用于从文件读取数据,输入文件流(读操作),继承自 istream

ofstream: 用于将数据写入文件,输出文件流(写操作),继承自 ostream

fstream: 用于文件的输入输出,兼具读写功能

打开文件: 通过构造函数或 open() 成员函数打开文件,并指定文件名和打开模式

读写操作: 使用插入(<<)或提取(>>)操作符读取或写入文件中的数据

关闭文件: 使用 close() 函数显式关闭文件,或者让文件 I/O 变量超出作用域(文件I/O类的析构函数会自动关闭文件

要在 C++ 中进行文件处理,必须在 C++ 源代码文件中包含头文件 <iostream> 和 <fstream>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <fstream>
#include <iostream>

int main() {
std::ifstream src("source.png", std::ios::binary); // 二进制读
std::ofstream dest("copy.png", std::ios::binary); // 二进制写

if (!src || !dest) {
std::cerr << "文件打开失败!" << std::endl;
return 1;
}

// 高效逐块复制
const int BUFFER_SIZE = 4096;
char buffer[BUFFER_SIZE];
while (src.read(buffer, BUFFER_SIZE)) {
dest.write(buffer, src.gcount()); // 写入实际读取的字节
}
dest.write(buffer, src.gcount()); // 写入剩余字节

src.close();
dest.close();
return 0;
}

结果:copy.png和 source.png一样了

文件打开模式

要从文件读取信息或者向文件写入信息之前,首先要打开文件,在C++中,文件打开模式可以通过 std::ios_base 类中的标志来指定

ios::in: 以读取模式打开文件

ios::out: 以写入模式打开文件

ios::app: 以追加模式打开文件,所有新数据写入到文件的末尾

ios::ate: 将文件指针定位到文件的末尾

ios::binary: 以二进制模式打开文件,而不是文本模式

ios::trunc: 如果文件已存在,则删除文件内容

ios::nocreate: 如果文件不存在,则打开失败

ios::noreplace: 如果文件已存在,则打开失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <fstream>
using namespace std;

void main()
{
ofstream outFile;
outFile.open("number.txt", ios::app);
if (!outFile.is_open())
{
cout << " problem with opening the file ";
}
else
{
outFile << 200 << endl;
cout << "done writing" << endl;
}
outFile.close();
}

输出:done writing

读取文件

读取文件时,可以使用 std::ifstream 类,使用 >> 或 getline 读取文件内容

1. 包含头文件:

#include <fstream> // 核心文件流库

#include <string> // 用于getline等操作

2. 创建流对象: std::ifstream fin; // ifstream 专用于输入(读取)

3. 打开文件并校验:

1
2
3
4
5
fin.open("data.txt",  std::ios::in);  // 文本模式打开 
if (!fin.is_open()) { // 必须检查是否成功打开
std::cerr << "文件打开失败!";
return -1;
}

模式选项(可组合使用): std::ios::in(读)、std::ios::binary(二进制)、std::ios::ate(初始定位到文件末尾)

4. 读取数据: 四种核心读取方法

方法 1: 运算符 >>(简单但有限制),适用场景: 读取无空格的数据(如数字、单词),缺陷: 忽略空格和换行,不保留原始格式

1
2
3
4
char buffer[100];
while (fin >> buffer) { // 遇空格/换行终止
std::cout << buffer << std::endl;
}

方法 2: std::getline(推荐按行处理),优势: 保留行内空格,内存安全(自动处理字符串内存)

1
2
3
4
std::string line;
while (std::getline(fin, line)) { // 读取整行(含空格)
std::cout << line << std::endl;
}

方法 3: 流对象的 getline 方法,注意: 需预设缓冲区大小,避免溢出

1
2
3
4
char buffer[100];
while (fin.getline(buffer, 100)) { // 读取到字符数组
std::cout << buffer << std::endl;
}

方法 4: 逐字符读取( get() ),用途: 需精细控制字符时(如解析特殊结构)缺点: 效率低,大文件不推荐

1
2
3
4
char ch;
while (fin.get(ch)) { // 逐个字符读取
std::cout << ch;
}

特殊场景处理:二进制文件读取 必须指定 std::ios::binary 模式

1
2
3
4
std::ifstream binFile("data.bin",  std::ios::binary);
char* data = new char[1024];
binFile.read(data, 1024); // 直接读取字节块
binFile.close();

5. 关闭文件: fin.close(); // 显式关闭释放资源

二进制模式比文本模式更快(无换行符转换)

写入文件

std::ofstream(输出文件流): 专用于写入文件,需包含头文件 <fstream>

1
2
3
4
5
6
7
8
9
#include <fstream>
int main() {
std::ofstream fout("data.txt"); // 创建或覆盖文件
if (fout.is_open()) {
fout << "Hello, World!\n"; // 写入文本
fout.close(); // 必须关闭
}
return 0;
}

std::fstream(多功能文件流): 支持读写操作,通过模式参数控制

1
2
3
std::fstream file("data.txt",  std::ios::out | std::ios::app); // 追加模式 
file << "New line\n";
file.close();

写入数据方法

1. 文本写入: 使用 << 操作符(类似 cout

1
fout << "Integer: " << 42 << "\nFloat: " << 3.14;

2. 二进制写入: 用 write() 直接写入内存数据

1
2
int data = 100;
fout.write(reinterpret_cast<char*>(&data), sizeof(data)); // 二进制写入

3. 格式化控制: 设置精度与宽度

1
2
3
fout.precision(2);     // 保留2位小数 
fout.width(10); // 输出宽度10字符
fout << 3.14159; // 输出 " 3.14"

性能优化与安全

1. 缓冲区管理: 默认有缓冲区,手动刷新可避免数据丢失

1
2
3
4
5
fout << "Important data";
fout.flush(); // 立即写入磁盘
调整缓冲区大小(减少I/O次数):
char buffer[8192]; // 8KB缓冲区
fout.rdbuf()->pubsetbuf(buffer, sizeof(buffer));

2. 错误处理: 检查文件是否成功打开

1
2
3
4
if (!fout) {
std::cerr << "File open failed!";
return -1;
}

3. 线程安全: 多线程写入时需加锁(如 std::mutex

高级场景

1. 追加数据

1
2
std::ofstream log("log.txt",  std::ios::app);
log << "Event: User login\n";

2. 大文件处理: 分块写入避免内存溢出

1
2
std::vector<char> largeData(1000000, 'A'); // 1MB数据 
fout.write(largeData.data(), largeData.size());

3. 跨平台路径处理: 使用 std::filesystem(C++17起)

1
2
#include <filesystem>
std::ofstream fout(std::filesystem::path("data/demo.txt"));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <fstream>
#include <iostream>

int main() {
// 1. 打开文件(追加模式)
std::ofstream fout("output.txt", std::ios::app);
if (!fout) {
std::cerr << "Error opening file!";
return 1;
}

// 2. 写入数据
fout << "---- New Entry ----\n";
fout << "Timestamp: " << time(nullptr) << "\n";

// 3. 检查写入状态
if (fout.fail())
std::cerr << "Write failed!";

// 4. 关闭文件
fout.close();
return 0;
}

文件常见问题及解决方案

1. 检查文件是否成功打开: 使用 is_open() 成员函数检查文件是否成功打开,如果未成功,则采取相应措施

2. 显式关闭文件: 尽管C++流对象在析构时会自动关闭文件,显式调用 close() 函数可以提高代码的清晰度和控制力

3. 使用追加或读写模式: 如果需要在文件末尾追加内容,可以使用 ios::app 模式;如果需要读写同一文件,可以使用 ios::in | ios::out 模式

4. 注意字符编码: 明确文件的编码格式,必要时使用第三方库处理不同编码的读写

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2015-2025 kindyourself@163.com
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信