[TOC]

1. 基础语法

1.1 引用运算 &const 关键字

引用运算 &

引用是已有变量的别名,创建引用时不会分配新内存,而是直接操作原变量。
引用的特性:

  1. 必须在定义时初始化。
  2. 一旦初始化,就和原变量绑定,不能重新绑定。

代码示例:

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

int main() {
int a = 10;
int &ref = a; // ref 是 a 的引用
cout << "a = " << a << ", ref = " << ref << endl;

ref = 20; // 修改 ref,相当于修改 a
cout << "After modifying ref: a = " << a << ", ref = " << ref << endl;

return 0;
}

输出:

1
2
a = 10, ref = 10
After modifying ref: a = 20, ref = 20
常见应用:
  1. 函数传参: 引用作为函数参数可以避免值传递时的拷贝,提高效率。
1
2
3
4
5
6
7
8
9
10
void increment(int &n) {
n++;
}

int main() {
int x = 5;
increment(x);
cout << "x = " << x << endl; // 输出 x = 6
return 0;
}
  1. 引用返回值: 引用可以作为函数返回值,允许函数返回原始变量的引用。
1
2
3
4
5
6
7
8
9
10
11
int& getReference(int &x) {
return x; // 返回引用
}

int main() {
int a = 10;
int &ref = getReference(a);
ref = 20; // 修改引用,影响原变量
cout << "a = " << a << endl; // 输出 a = 20
return 0;
}

const 关键字

const可以修饰变量、函数参数、成员函数等,表示不可修改的属性。

修饰变量:

1
2
const int x = 10; // x 是常量
x = 20; // 错误:x 不能被修改

修饰函数参数:const修饰函数参数,可以防止函数修改传入的值。

1
2
3
4
void display(const int &n) {
// n 是只读的,不能修改
cout << "n = " << n << endl;
}

对于类对象或结构体,通过引用传递避免拷贝开销,同时用 const 确保不会修改内容:

1
2
3
4
5
6
7
8
9
class MyClass {
public:
void display() const { cout << "MyClass display" << endl; }
};

void showObject(const MyClass &obj) {
obj.display(); // 可以调用 const 方法
// obj.modify(); // 错误:obj 是 const,不能调用能修改内容的方法
}

修饰指针:

  • 指针本身不可修改:int *const ptr;
  • 指针指向的值不可修改:const int *ptr;

代码示例:

1
2
3
4
5
6
7
8
cpp复制代码int main() {
const int a = 10; // a 是常量,不能修改
const int *ptr = &a; // 指向常量的指针
int b = 20;
int *const constPtr = &b; // 常量指针,地址不能修改

return 0;
}

1.2 内联函数

一言以蔽之,内联函数类似于宏定义

内联函数的定义:

内联函数是通过 inline 关键字声明的函数,目的是告诉编译器尽可能将该函数的代码插入到函数调用的位置,而不是通过正常的函数调用机制(如栈操作)来执行。通过这种方式,函数调用的开销可以被消除,从而提高程序的运行效率。

内联函数的优点:

  1. 避免函数调用开销
    每次函数调用都涉及一些开销,包括参数传递、栈帧的创建和销毁、函数返回等。对于非常简单的函数(如常见的数学计算、获取属性值等),每次调用这些小函数时,编译器可以直接将函数体嵌入到调用处,消除这些开销。
  2. 增强代码效率,适合频繁调用的小函数
    内联函数尤其适用于那些频繁调用的函数,因为它可以减少函数调用的时间开销。当一个小函数被大量调用时,内联可以显著提高程序的执行效率。例如,一些简单的 getter、setter 函数、数值计算函数等,内联处理后能显著优化性能。
  3. 减少函数调用栈开销
    传统函数调用需要维护调用栈,而内联函数的展开直接将代码嵌入调用处,避免了栈的创建和销毁过程,从而减少了内存和处理器的负担。

内联函数的语法:

内联函数的语法与普通函数唯一的区别是在函数前加上 inline 关键字。

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

inline int square(int x) {
return x * x;
}

int main() {
cout << "Square of 5: " << square(5) << endl;
return 0;
}
  • 在这个例子中,square 函数被声明为内联函数。inline 关键字告诉编译器尽可能将这个函数的代码直接插入到调用它的地方。
  • 当程序运行时,编译器会将 square(5) 替换为 5 * 5,避免了调用函数的传统开销。

内联函数的使用场景:

内联函数最适合用于以下几种情况:

  1. 简单函数:内联函数最适合那些计算量小、逻辑简单的函数。例如,单纯的数学运算、获取和设置变量值等。内联不会对大型、复杂的函数有显著的优化效果。
  2. 频繁调用的函数:对于需要频繁调用的短小函数,内联可以减少函数调用的时间和开销,尤其是在嵌套调用和循环中调用的函数。
  3. 不涉及递归:内联函数不适合递归函数,因为递归的每一次调用都涉及到不同的栈帧和参数,因此递归的内联展开会导致性能下降。
  4. 避免大量栈操作的函数:如果一个函数没有太多的局部变量,也没有复杂的逻辑,使用内联可以避免函数调用时产生的栈开销。

内联函数的限制:

  1. 不同编译器标准不同
    通过 inline 关键字声明函数并不意味着编译器一定会将其内联展开。编译器可能会基于函数的复杂性、调用频率、优化目标等因素决定是否进行内联展开。因此,内联函数只是对编译器的一个建议,而非强制要求。
  2. 复杂函数不适合内联
    内联函数适用于简单的、计算量小的函数。对于逻辑复杂、体积较大的函数,内联可能导致代码膨胀,使得代码变得冗长、占用更多内存,甚至可能引发性能下降。
  3. 递归函数不应内联
    递归函数是不能内联的,因为每次递归都会创建新的栈帧,而内联函数的目的是减少栈开销。递归展开会导致栈帧无法共享,进而消除内联的优势。

编译器如何决定内联展开:

  • 对于小函数,编译器通常会自动进行内联优化,即使没有显式声明 inline
  • 对于较大的函数,编译器一般不会内联,因为内联会导致代码膨胀,增加 CPU 缓存的压力,反而可能带来性能下降。
  • 编译器还可能根据一些编译选项、优化级别等决定是否内联。

内联与宏的比较:

内联函数有时可以替代宏(#define)来进行常见的计算,但它们之间有一些重要区别:

  • 类型安全:内联函数是强类型的,宏没有类型检查,容易引发隐式类型转换错误。
  • 调试友好:内联函数可以被调试器追踪,而宏无法单步调试。
  • 作用域:内联函数遵循正常的作用域规则,而宏在预处理阶段进行替换,可能引发作用域问题。
1
2
#define SQUARE(x) ((x) * (x)) // 宏
inline int square(int x) { return x * x; } // 内联函数

内联函数相较于宏有更强的类型检查和更好的可维护性。


1.3 函数参数缺省值

缺省参数的规则:

  1. 缺省值从右向左定义。(不能两边有缺省值,中间夹着一个正常的)
  2. 调用时可以省略有缺省值的参数。

代码示例:

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

void greet(const char *name = "Guest", int age = 18) {
cout << "Hello, " << name << "! Age: " << age << endl;
}

int main() {
greet(); // 使用默认值
greet("Alice"); // 覆盖第一个参数
greet("Bob", 25); // 覆盖所有参数
return 0;
}

输出:

1
2
3
Hello, Guest! Age: 18
Hello, Alice! Age: 18
Hello, Bob! Age: 25

1.4 输入输出

标准输入输出:

1
2
3
4
5
6
7
8
9
10
cpp复制代码#include <iostream>
using namespace std;

int main() {
int num;
cout << "Enter a number: ";
cin >> num;
cout << "You entered: " << num << endl;
return 0;
}
  • cin 从键盘输入。
  • cout 输出到屏幕。

格式化输出:

1
2
#include <iomanip>
cout << setprecision(2) << fixed << 3.14159; // 输出 3.14

二进制文件读写

写入二进制文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <fstream>
using namespace std;

struct Data {
int id;
char name[50];
};

int main() {
Data d = {1, "Alice"};
ofstream outFile("data.bin", ios::binary);
if (outFile) {
outFile.write((char *)&d, sizeof(d));
}
outFile.close();
return 0;
}
读取二进制文件:
1
2
3
4
5
6
7
ifstream inFile("data.bin", ios::binary);
Data d;
if (inFile) {
inFile.read((char *)&d, sizeof(d));
cout << "ID: " << d.id << ", Name: " << d.name << endl;
}
inFile.close();

1.5 异常处理

image-20250105145816419

image-20250105145901566

异常的基本结构:

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

int main() {
try {
int x = 0;
if (x == 0)
throw runtime_error("Division by zero!");
} catch (const runtime_error &e) {
cout << "Caught exception: " << e.what() << endl;
}
return 0;
}

输出:

1
Caught exception: Division by zero!

常见异常类型:

  • std::exception:所有异常的基类。
  • std::runtime_error:运行时错误。
  • std::invalid_argument:无效参数。

2. 类与对象

2.1 构造函数、复制构造函数、析构函数

构造函数

构造函数是类的特殊函数,在对象创建时自动调用,用于初始化对象。
特点:

  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
24
#include <iostream>
using namespace std;

class MyClass {
private:
int value;

public:
// 构造函数
MyClass(int v) {
value = v;
cout << "Constructor called, value = " << value << endl;
}

void display() {
cout << "Value: " << value << endl;
}
};

int main() {
MyClass obj1(10); // 调用构造函数
obj1.display();
return 0;
}

输出:

1
2
Constructor called, value = 10
Value: 10

复制构造函数

复制构造函数用于通过已有对象创建新对象。
语法:

1
ClassName(const ClassName &obj);

代码示例:

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
#include <iostream>
using namespace std;

class MyClass {
private:
int value;

public:
// 构造函数
MyClass(int v) : value(v) {}

// 复制构造函数
MyClass(const MyClass &obj) {
value = obj.value;
cout << "Copy constructor called!" << endl;
}

void display() {
cout << "Value: " << value << endl;
}
};

int main() {
MyClass obj1(10);
MyClass obj2 = obj1; // 调用复制构造函数
obj2.display();
return 0;
}

输出:

1
2
Copy constructor called!
Value: 10

析构函数

析构函数用于释放对象销毁时占用的资源。
特点:

  1. 名字为类名前加 ~
  2. 无参数、无返回值。
  3. 一个类只能有一个析构函数,不能重载

代码示例:

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

class MyClass {
public:
MyClass() {
cout << "Constructor called!" << endl;
}

~MyClass() {
cout << "Destructor called!" << endl;
}
};

int main() {
MyClass obj;
cout << "Inside main function." << endl;
return 0; // 程序结束,调用析构函数
}

输出:

1
2
3
Constructor called!
Inside main function.
Destructor called!

2.2 this 指针

this 是一个指针,指向调用成员函数的当前对象。常用于以下场景:

  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
24
25
26
27
#include <iostream>
using namespace std;

class MyClass {
private:
int value;

public:
MyClass(int value) {
this->value = value; // 使用 this 指针区分成员变量和局部变量
}

MyClass &setValue(int value) {
this->value = value;
return *this; // 返回当前对象引用
}

void display() {
cout << "Value: " << value << endl;
}
};

int main() {
MyClass obj(10);
obj.setValue(20).display(); // 链式调用
return 0;
}

输出:

1
Value: 20

2.3 友元函数、静态成员变量、const 成员函数

友元函数

友元函数可以访问类的私有成员,通常用来实现特殊操作。
语法:

1
friend ReturnType FunctionName(ClassName &obj);

代码示例:

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 <iostream>
using namespace std;

class MyClass {
private:
int value;

public:
MyClass(int v) : value(v) {}

// 声明友元函数
friend void display(const MyClass &obj);
};

// 定义友元函数
void display(const MyClass &obj) {
cout << "Value: " << obj.value << endl;
}

int main() {
MyClass obj(10);
display(obj); // 调用友元函数
return 0;
}

输出:

1
Value: 10

静态成员变量

静态成员变量属于类而不是对象,所有对象共享一份。
特点:

  1. 必须在类外初始化。
  2. 使用 ClassName::StaticMember 访问。

代码示例:

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
#include <iostream>
using namespace std;

class MyClass {
private:
static int count; // 声明静态成员变量

public:
MyClass() {
count++;
}

static int getCount() { // 静态成员函数
return count;
}
};

// 初始化静态成员变量
int MyClass::count = 0;

int main() {
MyClass obj1, obj2, obj3;
cout << "Number of objects: " << MyClass::getCount() << endl;
return 0;
}

输出:

1
Number of objects: 3

const 成员函数

const 成员函数保证函数不会修改类的成员变量。
语法:

1
ReturnType FunctionName() const;

代码示例:

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
#include <iostream>
using namespace std;

class MyClass {
private:
int value;

public:
MyClass(int v) : value(v) {}

int getValue() const { // const 成员函数
return value;
}

void setValue(int v) {
value = v;
}
};

int main() {
const MyClass obj(10);
cout << "Value: " << obj.getValue() << endl;
// obj.setValue(20); // 错误:const 对象只能调用 const 成员函数
return 0;
}

输出:

1
Value: 10

2.4 数据封装与访问限定符

访问限定符

  • public: 公有成员,类外部可访问。
  • private: 私有成员,仅类内部可访问。
  • protected: 受保护成员,仅类及其子类可访问。

代码示例:

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>
using namespace std;

class MyClass {
private:
int privateValue;

public:
int publicValue;

protected:
int protectedValue;

public:
MyClass() : privateValue(10), publicValue(20), protectedValue(30) {} //构造函数初始化

void display() {
cout << "Private: " << privateValue << ", Public: " << publicValue
<< ", Protected: " << protectedValue << endl;
}
};

int main() {
MyClass obj;
obj.publicValue = 50; // 公有成员可直接访问
// obj.privateValue = 60; // 错误:私有成员不能直接访问
obj.display();
return 0;
}

输出:

1
Private: 10, Public: 50, Protected: 30

3. 运算符重载

运算符重载允许为用户自定义的类定义运算符的行为。
注意:

  • 不能重载 ::(作用域解析)、.(成员访问)等。
  • 至少有一个操作数是用户定义类型。

3.1 类内运算符重载

运算符可以作为类的成员函数重载。
语法:

1
ReturnType operatorSymbol(Arguments);

示例:重载 + 运算符

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
#include <iostream>
using namespace std;

class Complex {
private:
double real, imag;

public:
Complex(double r, double i) : real(r), imag(i) {}

// 重载 + 运算符
Complex operator+(const Complex &c) const {
return Complex(real + c.real, imag + c.imag);
}

void display() const {
cout << real << " + " << imag << "i" << endl;
}
};

int main() {
Complex c1(1.5, 2.5), c2(2.0, 3.0);
Complex c3 = c1 + c2; // 调用运算符重载
c3.display();
return 0;
}

输出:

1
3.5 + 5.5i

3.2 类外运算符重载

类外定义运算符时需要将其声明为友元函数。
语法:

1
friend ReturnType operatorSymbol(const ClassName &lhs, const ClassName &rhs);

示例:重载 << 运算符

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
#include <iostream>
using namespace std;

class Complex {
private:
double real, imag;

public:
Complex(double r, double i) : real(r), imag(i) {}

// 声明友元函数
friend ostream &operator<<(ostream &out, const Complex &c);
};

// 定义友元函数
ostream &operator<<(ostream &out, const Complex &c) {
out << c.real << " + " << c.imag << "i";
return out;
}

int main() {
Complex c1(1.5, 2.5);
cout << "Complex number: " << c1 << endl; // 使用重载运算符
return 0;
}

输出:

1
Complex number: 1.5 + 2.5i

3.3 类内运算符重载和类外运算符重载的区别

1. 定义方式

类内运算符重载
  • 定义位置: 在类的内部定义为类的成员函数。

  • 调用方式: 隐式使用类的对象调用。

  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class MyClass {
    private:
    int value;

    public:
    MyClass(int v) : value(v) {}

    // 类内重载运算符+
    MyClass operator+(const MyClass &other) const {
    return MyClass(value + other.value);
    }

    void display() const {
    cout << "Value: " << value << endl;
    }
    };

    int main() {
    MyClass a(10), b(20);
    MyClass c = a + b; // 调用类内的operator+
    c.display();
    return 0;
    }

类外运算符重载
  • 定义位置: 在类的外部定义为普通函数,通常声明为类的 友元函数(如果需要访问私有或保护成员)。

  • 调用方式: 使用运算符直接作用于类的对象。

  • 示例:

    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
    class MyClass {
    private:
    int value;

    public:
    MyClass(int v) : value(v) {}

    // 声明类外友元函数用于重载运算符+
    friend MyClass operator+(const MyClass &a, const MyClass &b);

    void display() const {
    cout << "Value: " << value << endl;
    }
    };

    // 类外定义operator+
    MyClass operator+(const MyClass &a, const MyClass &b) {
    return MyClass(a.value + b.value);
    }

    int main() {
    MyClass a(10), b(20);
    MyClass c = a + b; // 调用类外的operator+
    c.display();
    return 0;
    }

2. 参数与调用区别

类内运算符重载
  • 隐式传递左操作数: 类的成员函数默认会把调用者作为 左操作数,右操作数需要通过参数传递。

  • 参数数量: 一元运算符不需要参数,二元运算符只需要一个参数(右操作数)。

  • 调用方式:

    1
    2
    MyClass a, b;
    a + b; // 等价于 a.operator+(b);
类外运算符重载
  • 显式传递操作数: 所有操作数都通过参数传递,包括左操作数。

  • 参数数量: 一元运算符需要一个参数,二元运算符需要两个参数。

  • 调用方式:

    1
    2
    MyClass a, b;
    a + b; // 等价于 operator+(a, b);

3. 访问权限

类内运算符重载
  • 类内的成员函数可以直接访问类的 私有成员保护成员,无需额外处理。
类外运算符重载
  • 如果类外的重载函数需要访问类的私有成员或保护成员,必须将其声明为

    友元函数

    1
    friend MyClass operator+(const MyClass &a, const MyClass &b);

4. 功能范围

类内运算符重载
  • 类内重载通常用于运算符的

    左操作数

    必须是类的对象,例如:

    • a + b 中,a 必须是类的对象。
    • 如果左操作数不是类的对象(如 3 + a),类内运算符无法直接处理。
类外运算符重载
  • 类外重载可以处理更灵活的场景,比如:
    • 左操作数是类对象时:a + b
    • 左操作数不是类对象时:3 + a

示例:支持 3 + a 的类外重载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MyClass {
private:
int value;

public:
MyClass(int v) : value(v) {}

friend MyClass operator+(int num, const MyClass &a); // 支持3 + a
};

// 类外定义
MyClass operator+(int num, const MyClass &a) {
return MyClass(num + a.value);
}

int main() {
MyClass a(5);
MyClass b = 3 + a; // 支持左操作数为普通数据
return 0;
}

5. 代码设计与适用场景

比较维度 类内运算符重载 类外运算符重载
适用性 左操作数必须是类对象。 左操作数可以是类对象或其他类型。
访问权限 能直接访问类的私有或保护成员。 需要声明为友元函数才能访问私有成员。
代码风格 更紧密地绑定类本身,逻辑集中在类内。 功能更灵活,适合跨类或跨类型的操作符重载。
实现复杂度 较简单,适合常见的运算符重载。 适合需要更高灵活性的重载场景。

总结

  • 类内运算符重载:
    • 左操作数必须是类对象。
    • 使用更简单,适合内部逻辑紧密绑定类的情况。
  • 类外运算符重载:
    • 左操作数可以是类对象,也可以是其他类型。
    • 更灵活,适合需要处理跨类或多种数据类型操作的场景。

4. 多线程

多线程使程序可以同时运行多个任务,提高效率。C++11 提供了 std::thread 支持多线程。


4.1 创建线程

语法:

1
2
3
4
5
6
7
8
9
10
#include <thread>
void function() {
// 线程要执行的代码
}

int main() {
std::thread t(function); // 创建线程
t.join(); // 等待线程执行完毕
return 0;
}

示例:简单线程创建

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

void printMessage(const string &msg) {
cout << "Message: " << msg << endl;
}

int main() {
thread t1(printMessage, "Hello from thread!"); // 创建线程
t1.join(); // 等待线程结束
return 0;
}

输出:

1
Message: Hello from thread!

4.2 多线程同步

为了避免多个线程同时修改共享资源,C++ 提供了互斥锁 (std::mutex)。

示例:使用互斥锁

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 <thread>
#include <mutex>
using namespace std;

mutex mtx; // 定义互斥锁

void printNumbers(int id) {
for (int i = 0; i < 5; ++i) {
mtx.lock(); // 加锁
cout << "Thread " << id << ": " << i << endl;
mtx.unlock(); // 解锁
}
}

int main() {
thread t1(printNumbers, 1);
thread t2(printNumbers, 2);

t1.join();
t2.join();
return 0;
}

输出(线程顺序可能不同):

1
2
3
4
5
Thread 1: 0
Thread 2: 0
Thread 1: 1
Thread 2: 1
...

高级的lock方式:

1. std::mutex

首先要了解的是 **std::mutex**,它是一个基本的互斥量,用于实现线程间的同步。在 C++11 中,std::mutex 提供了基本的加锁和解锁功能。

  • **std::mutex::lock()**:加锁。
  • **std::mutex::unlock()**:解锁。
1
2
3
4
5
6
7
8
9
#include <mutex>

std::mutex mtx; // 定义一个全局的互斥量

void safeFunction() {
mtx.lock(); // 手动加锁
// 访问或修改共享资源
mtx.unlock(); // 手动解锁
}

尽管手动加锁和解锁是有效的,但它们需要小心处理,尤其是在异常发生时。如果发生异常而未能解锁互斥量,程序可能会进入死锁状态。因此,C++ 提供了 std::lock_guardstd::unique_lock 来简化互斥量的管理,确保锁在作用域结束时自动释放。

2. std::lock_guard

std::lock_guard 是一个简单的封装,它用于在作用域内自动加锁,并且在作用域结束时自动解锁。它是一种 作用域锁,非常适合于只需要加锁并自动解锁的场景。

  • 作用:当 std::lock_guard 对象被创建时,它会自动加锁互斥量。当 std::lock_guard 对象超出作用域时,它会自动调用析构函数,进而解锁互斥量。
  • 优点:避免手动解锁,降低死锁风险。

语法示例:

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

std::mutex mtx;

void safeFunctionWithLockGuard() {
std::lock_guard<std::mutex> lk(mtx); // 自动加锁
// 访问或修改共享资源
std::cout << "Shared resource accessed with lock_guard\n";
// 锁会在lk超出作用域时自动释放
}

int main() {
safeFunctionWithLockGuard(); // 调用函数,自动加锁和解锁
return 0;
}

在这个例子中:

  • lock_guard 对象 lk 被创建时,它会自动加锁互斥量 mtx
  • lk 超出作用域(即 safeFunctionWithLockGuard 函数结束时),lk 会自动解锁 mtx

std::lock_guard 的特点是简单且有效,但它不支持解锁或重新锁定操作,只适合于简单的加锁和解锁场景。

3. std::unique_lock

std::unique_lock 是一个比 std::lock_guard 更加灵活的锁管理工具,它提供了比 std::lock_guard 更多的功能,包括:

  • 可以显式地 手动解锁
  • 支持 锁的延迟(即在创建 unique_lock 对象时不立即加锁)。
  • 可以 重新锁定
  • 可以 转移锁的所有权,即将锁的所有权从一个 unique_lock 对象转移到另一个。

std::unique_lock可移动的,而 std::lock_guard 不是。

语法示例:

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

std::mutex mtx;

void safeFunctionWithUniqueLock() {
std::unique_lock<std::mutex> ul(mtx); // 自动加锁
// 访问或修改共享资源
std::cout << "Shared resource accessed with unique_lock\n";
ul.unlock(); // 手动解锁
// 你可以在这里做其他不需要锁的操作
ul.lock(); // 如果需要,可以重新加锁
// 访问共享资源
std::cout << "Shared resource accessed after relocking\n";
// 锁会在ul超出作用域时自动释放
}

int main() {
safeFunctionWithUniqueLock();
return 0;
}

在这个例子中:

  • std::unique_lock 在创建时自动加锁互斥量 mtx
  • 使用 ul.unlock() 手动解锁,之后可以执行一些不需要锁的操作。
  • ul.lock() 可以在之后重新加锁。
  • unique_lock 对象 ul 超出作用域时,mtx 会自动解锁。

std::unique_lock 提供了比 std::lock_guard 更高的灵活性,但也相对复杂一些。它适用于需要更细粒度控制的场景,如需要手动解锁和重新加锁的情况。

4. std::lockstd::lock_guardstd::unique_lock 的区别

  • **std::lock_guard**:
    • 简单,适用于无需手动解锁的场景。
    • 自动加锁并在作用域结束时自动解锁。
    • 不能显式解锁或重新加锁。
    • 不能转移所有权。
  • **std::unique_lock**:
    • 更灵活,适用于需要手动解锁、重新加锁、或转移锁所有权的场景。
    • 支持显式解锁(unlock())和重新加锁(lock())。
    • 支持锁的延迟和转移。
  • **std::mutexstd::lock**:
    • std::mutex 提供了基本的加锁和解锁机制。
    • std::lock 是一个可以同时加锁多个互斥量的工具,可以防止死锁的发生。

5. 总结

  • std::lock_guardstd::unique_lock 都是用来管理互斥量锁的工具,分别适用于不同的场景。
  • std::lock_guard 是简洁的自动锁管理工具,适合简单的加锁和解锁。
  • std::unique_lock 提供了更灵活的锁管理方式,支持手动解锁、重新加锁等高级操作。
  • 使用这些锁可以有效地避免死锁,减少编写线程安全代码时的复杂性。

5. STL(标准模板库)

STL 提供了常用的数据结构和算法。以下是重点内容。


5.1 常用容器

5.1.1 vector

动态数组,支持随机访问和自动扩容。

示例:基本操作

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

int main() {
vector<int> vec = {1, 2, 3};
vec.push_back(4); // 添加元素
vec.pop_back(); // 删除最后一个元素

for (int x : vec) {
cout << x << " ";
}
return 0;
}

输出:

1
1 2 3

5.1.2 queue

先进先出(FIFO)队列。

示例:基本操作

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

int main() {
queue<int> q;
q.push(1); q.push(2); q.push(3);

while (!q.empty()) {
cout << q.front() << " "; // 获取队头元素
q.pop(); // 移除队头元素
}
return 0;
}

输出:

1
1 2 3

5.1.3 deque

双端队列,可以从两端插入或删除。

示例:基本操作

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

int main() {
deque<int> dq = {1, 2, 3};
dq.push_front(0); // 前端插入
dq.push_back(4); // 后端插入

for (int x : dq) {
cout << x << " ";
}
return 0;
}

输出:

1
0 1 2 3 4
记录代码
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#include <iostream> //input output 流,类似于stdio.h
#include <vector>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <unordered_map>
#include <unordered_set>
using namespace std;


int main() {

//ventor 数组 的三种定义
vector <int> v1; //定义一个空数组
vector <int> v2(10); //定义一个10个元素的空数组
vector <int> v3(10,2); //定义一个10个元素的空数组,并且每个变量都初始化为2


v3.resize(12); //resize 可以更改数组长度,增加数组长度的时候,不会修改原有值
for(int i = 0 ;i <12;i++)
{
cout<<v3[i]<<" ";
}
cout<<endl;
//可替换原生for循环的工具:迭代器 (适用于c++11)
for(auto p =v3.begin();p!=v3.end();p++)
{
cout<<*p<<" ";
}
cout<<endl;
//使用迭代器的好处:无需考虑for循环的开头和结束



//set: 集合,元素互异,自动从小到大排序
//set在创建的时候,没有额外的参数
set <int> my_set;
//插入元素
my_set.insert(8);
my_set.insert(2);
my_set.insert(5);
my_set.insert(12);

//迭代器遍历
for(auto p = my_set.begin();p!=my_set.end();p++)
{
cout<<*p<<" ";
}
cout<<endl;
auto p = my_set.find(8); // .find(data)函数返回的是一个指针
cout<<"*p is "<<*p<<endl;

//set 清除某一个元素
my_set.erase(5);
for(auto p = my_set.begin();p!=my_set.end();p++)
{
cout<<*p<<" ";
}


//map
map<int,string> m;
m[2] ="hello";
m[1] = "wrold";

for(auto p = m.begin();p!=m.end();p++)
{
cout << (*p).first <<":"<<p->second<<" ";
}

map<string,int> m2;
m2["hello"] = 9;
m2["wrold"] = 3;

for(auto p = m.begin();p!=m.end();p++)
{
cout << (*p).first <<":"<<p->second<<" ";
}

//stack
stack <int> mystack;
mystack.push(4);
mystack.push(3);
mystack.push(1);
mystack.pop();
cout<<mystack.top()<<endl;

cout<<mystack.size()<<endl;


//queue
queue <int> q;
q.push(2);
q.push(4);
q.push(6);
q.push(-1);
q.push(3);

q.pop();

cout<<q.front()<<" "<<q.back()<<endl;


//使用哈希表实现的 unordered_map 顺序由哈希函数规定
//unordered_map 默认使用 std::hash 作为哈希函数
unordered_map<string,int> hashtable;
hashtable["apple"] = 10;
hashtable["banana"] = 4;
hashtable["pear"] = 6;

for(auto p = hashtable.begin();p!=hashtable.end();p++)
{
cout<<p->first<<" "<<p->second<<endl;
}

//使用哈希表实现的 unordered_set
unordered_set<int> mySet;
// 插入元素
mySet.insert(10);
mySet.insert(20);
mySet.insert(30);
mySet.insert(10); // 重复元素不会插入

// 检查元素
if (mySet.find(20) != mySet.end()) {
cout << "20 is in the set." << endl;
}

// 遍历元素 元素顺序由内部哈希函数决定
for (const int& num : mySet) {
cout << num << " ";
}
system("pause");
return 0;
}


5.2 算法库

5.2.1 std::sort

排序容器中的元素。

示例:

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

int main() {
vector<int> vec = {3, 1, 4, 1, 5};
sort(vec.begin(), vec.end()); // 升序排序

for (int x : vec) {
cout << x << " ";
}
return 0;
}

输出:

1
1 1 3 4 5

5.3 C++11 新特性

5.3.1 auto 关键字

自动推断变量类型。

1
2
auto x = 10;  // 推断为 int
auto y = 3.14; // 推断为 double

5.3.2 Lambda 表达式c

定义匿名函数的方式,适合轻量级操作。

语法:

1
[捕获列表](参数列表) -> 返回类型 { 函数体 }

示例:

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

int main() {
vector<int> vec = {1, 2, 3, 4, 5};
int sum = 0;

for_each(vec.begin(), vec.end(), [&sum](int x) { sum += x; });
cout << "Sum: " << sum << endl;
return 0;
}

输出:

1
Sum: 15