C++程序员面试必学:代码实例详解

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:《C++程序员面试宝典》详尽地覆盖了C++基础知识、面向对象编程、内存管理、STL、异常处理、模板以及C++11新特性等多个面试必考主题,并提供了大量代码示例。书中不仅强调了基本概念,还深入讲解了函数、类、继承、多态、智能指针、设计模式等关键知识点,帮助读者深入理解C++编程,并在面试中表现出色。对于初学者来说,这本书是掌握C++并提升编程技能的宝贵资源。 c++程序员面试宝典(含代码)

1. 基础概念与语法

1.1 语言起源与发展

C++是一门静态类型、编译式、通用的程序设计语言。由Bjarne Stroustrup在1979年发明,最初称为C with Classes。C++继承了C语言的高效性,并增加了面向对象编程特性,成为一种多范式编程语言。

1.2 关键字与标识符

C++语言中,关键字(如 int , class , return 等)具有特殊意义,用于控制程序的结构和数据类型。用户定义的标识符(如变量名、函数名)必须遵守特定的命名规则。

1.3 基本语法元素

C++程序由变量、常量、运算符、表达式等基本元素构成。每个元素都遵循特定的语法规则来实现数据的声明、初始化和操作。

#include <iostream>
using namespace std;

int main() {
    int num = 10; // 声明并初始化变量
    cout << "The number is: " << num << endl; // 输出变量值
    return 0;
}

在上述代码示例中, int 是数据类型关键字, num 是变量名, 10 是常量, cout endl 是标准输出流对象与操纵符。程序执行流程通过 main() 函数体现。

1.4 数据类型和变量

C++支持多种数据类型,包括基本类型、枚举类型、引用类型、指针类型等。变量的声明是对程序中使用的内存空间进行标识和类型说明。

int age = 25; // 基本数据类型的变量声明和初始化

上述代码展示了如何声明一个 int 类型的变量 age 并初始化为25。基本数据类型包括整型、浮点型、字符型等。

1.5 运算符和表达式

运算符用于执行数值或逻辑运算。表达式是由运算符和操作数组成,可以产生值或对变量赋值。

int sum = 10 + 20; // 算术运算符用于加法运算

在该例中, + 是算术运算符,用于计算两个数的和,并将结果赋值给变量 sum

通过这些基础概念与语法的学习,读者将建立起对C++语言核心特性的初步认识,为后续深入学习面向对象编程、内存管理等高级主题打下坚实的基础。

2. 面向对象编程基础

2.1 类与对象的基本概念

面向对象编程(OOP)是现代编程范式之一,它提供了一种将数据和处理数据的方法捆绑到一起的模型。OOP 的核心是类和对象的概念。类可以被看作是对象的蓝图或模板,而对象则是根据这个蓝图创建的实例。

2.1.1 类的定义和对象的创建

在 C++ 中,类通过关键字 class struct 来定义。类可以包含成员变量(属性)和成员函数(方法)。以下是一个简单的类定义的例子:

class Person {
private:
    std::string name;
    int age;

public:
    // 构造函数
    Person(std::string n, int a) : name(n), age(a) {}

    // 方法:获取姓名
    std::string getName() const { return name; }

    // 方法:获取年龄
    int getAge() const { return age; }
};

在上面的代码中, Person 是一个类,它有两个私有成员变量 name age ,以及一个构造函数和两个成员函数 getName getAge 。我们可以通过 Person 类创建一个对象:

int main() {
    Person person("Alice", 30); // 创建 Person 类的对象
    std::cout << person.getName() << " is " << person.getAge() << " years old." << std::endl;
    return 0;
}

2.1.2 构造函数和析构函数

构造函数是一种特殊类型的成员函数,用于在创建对象时初始化对象。析构函数用于在对象销毁前执行清理工作。构造函数和析构函数具有特殊的名字,构造函数的名字与类名相同,而析构函数的名字为类名前加 ~ 符号。

class Person {
private:
    std::string name;
    int age;

public:
    Person(std::string n, int a) : name(n), age(a) { // 构造函数
        std::cout << "Person created: " << name << std::endl;
    }

    ~Person() { // 析构函数
        std::cout << "Person destroyed: " << name << std::endl;
    }
};

2.1.3 访问控制和封装

访问控制是通过类的三个关键字: public protected private 来实现的。 public 成员可以在任何地方被访问, protected 成员可以在派生类中被访问,而 private 成员只能在类的内部被访问。

封装是将数据(或数据结构)与操作数据的代码捆绑在一起的过程。封装可以防止数据被外部访问,提高了代码的可维护性和安全性。在上面的 Person 类例子中, name age 成员变量被设置为私有,确保了对这些成员的访问必须通过公有方法 getName getAge ,从而保护了数据不被非法访问。

2.2 继承与多态

2.2.1 继承的原理与实现

继承是面向对象编程中的一个基本特性,它允许创建一个类(子类)继承另一个类(基类)的成员变量和方法。这有助于代码复用,减少代码冗余,同时允许创建更为复杂的数据结构。

class Employee : public Person {
private:
    std::string department;

public:
    Employee(std::string n, int a, std::string d) 
        : Person(n, a), department(d) {}

    std::string getDepartment() const { return department; }
};

在这个例子中, Employee 类继承自 Person 类。 Employee 类拥有 Person 类的所有属性和方法,并增加了额外的成员变量 department 和一个获取部门的方法 getDepartment

2.2.2 多态的表现和应用

多态是指允许不同类的对象对同一消息做出响应的能力。在 C++ 中,多态通常通过虚函数实现。一个基类的指针或引用可以指向派生类的对象,且调用的函数是实际对象的版本,而非基类中的版本。

void printEmployeeDetails(Person& p) {
    std::cout << p.getName() << " is " << p.getAge() << " years old." << std::endl;
}

int main() {
    Employee emp("Bob", 25, "IT");
    printEmployeeDetails(emp); // 输出: Bob is 25 years old.
    return 0;
}

在上面的例子中, printEmployeeDetails 函数接受一个 Person 类型的引用,但实际上可以接收任何继承自 Person 的对象。这就允许了多态的行为,因为被调用的方法是基于对象的实际类型。

2.2.3 虚函数与动态绑定

虚函数允许派生类重新定义基类中的方法行为。当我们使用基类指针或引用调用一个虚函数时,运行时会根据指针或引用所指对象的实际类型来决定调用哪个版本的函数,这个过程被称为动态绑定。

class Person {
public:
    virtual void print() {
        std::cout << "Person details." << std::endl;
    }
};

class Employee : public Person {
public:
    void print() override {
        std::cout << "Employee details." << std::endl;
    }
};

int main() {
    Person p;
    Employee e;
    Person &rp = p;
    Person &re = e;

    rp.print(); // 输出: Person details.
    re.print(); // 输出: Employee details.
    return 0;
}

在这个例子中, print 函数在 Person 类中被定义为虚函数,并在 Employee 类中被重写。通过基类引用 Person &rp Person &re ,我们分别调用了 p e print 函数,但实际调用的版本取决于对象的实际类型,展示了动态绑定的特性。

通过继承与多态,OOP 提供了一种强大的方式来构建灵活且可扩展的软件系统。继承允许类的扩展,多态则允许类之间的差异性被抽象化,这使得程序设计更加灵活和高效。

3. 内存管理:栈、堆、引用、智能指针

3.1 内存分配基础

3.1.1 栈内存的特点和限制

在C++中,栈内存是用于存储局部变量的内存区域。它是一种后进先出(LIFO)的数据结构,操作系统通过维护一个栈指针来管理栈内存的分配和回收。当一个函数被调用时,其参数和局部变量被压入栈中,当函数执行完毕后,这些变量随之出栈。

栈内存的优点在于其分配和回收速度非常快,通常只需要简单的指针操作。然而,栈内存也有着显著的限制:

  • 大小有限 :栈的大小通常由操作系统预设,例如,在64位Linux系统中,栈的默认大小可能是8MB,但在一些嵌入式系统中可能只有几千字节。
  • 生命周期固定 :栈上的变量生命周期仅限于其所在的函数作用域。如果试图访问已经出栈的变量,将会导致未定义行为。

3.1.2 堆内存的管理与异常

堆内存是用于动态分配的对象的内存区域。与栈内存不同,堆内存的生命周期不依赖于任何特定的作用域,这使得堆内存更加灵活。然而,堆内存的管理却要复杂得多,并且更容易出错。

在C++中,可以通过 new delete 运算符来分配和释放堆内存。例如:

int* p = new int(42); // 分配堆内存
delete p;             // 释放堆内存

堆内存管理的常见问题是内存泄漏,即分配的内存没有被适当释放。此外,频繁的内存分配和释放可能导致内存碎片化。

为了优化堆内存管理,C++11引入了智能指针(如 std::unique_ptr std::shared_ptr ),它们可以帮助自动管理内存,减少内存泄漏的风险。这些智能指针将在3.2节详细讨论。

3.2 引用与智能指针

3.2.1 引用的定义与使用

在C++中,引用可以看作是变量的别名。一旦一个引用被初始化为指向一个对象,它将始终引用同一个对象。引用的定义语法如下:

int value = 42;
int& ref_value = value; // ref_value是value的引用

引用的主要用途包括:

  • 传递大型对象给函数时使用引用,以避免不必要的复制。
  • 返回大型对象时使用引用,以避免返回对象的副本。

然而,引用的初始化必须在定义时完成,并且一旦定义,引用的指向不可更改,这限制了它的应用。

3.2.2 智能指针的种类和优势

智能指针是C++11中引入的模板类,它们的目的是自动管理内存,减少内存泄漏和悬空指针的风险。最常用的智能指针有:

  • std::unique_ptr :拥有其所指向的对象,当 unique_ptr 被销毁时,它指向的对象也会被自动删除。
  • std::shared_ptr :允许多个指针指向同一个对象,对象会在最后一个 shared_ptr 被销毁时被删除。

智能指针通过引用计数的方式跟踪有多少个 shared_ptr 对象共享同一资源,当最后一个 shared_ptr 被销毁或重新指向时,资源被释放。

3.2.3 智能指针在资源管理中的应用

使用智能指针可以极大地简化资源管理的复杂性,尤其是在异常处理和多线程编程中。例如,考虑以下代码:

void processResource(std::shared_ptr<Resource> resource) {
    // 使用resource进行处理
}

int main() {
    auto resource = std::make_shared<Resource>(); // 使用make_shared高效创建shared_ptr
    processResource(resource);
    // resource在离开main函数作用域时自动被销毁
    return 0;
}

在上面的例子中,我们创建了一个 Resource 对象并将其包装在 shared_ptr 中。这个智能指针在 processResource 函数中被传递,而无需关心资源的释放问题。即使 processResource 抛出异常, resource 也会在 main 函数结束时自动被销毁。

智能指针的使用大大减少了程序员需要编写的内存管理代码量,同时提高了代码的安全性和可靠性。

通过本章节的介绍,我们了解了栈内存和堆内存的基本概念,它们的优缺点以及如何在实际编程中选择合适的内存管理方式。我们也探讨了引用的定义和作用,以及智能指针如何帮助我们更安全地管理堆内存。在下一节中,我们将深入标准模板库(STL)的容器和算法,探索如何利用这些强大的工具来处理数据。

4. 标准模板库(STL)使用

4.1 STL容器

4.1.1 容器的分类和选择

STL容器是C++标准库中的一个强大功能,它提供了一系列的数据结构和算法,使得程序员可以更加方便地存储和操作数据。容器大致可以分为序列容器、关联容器和无序关联容器。在选择合适的容器时,需要根据数据的特性以及算法的需求来考虑。

  • 序列容器 :包括 vector , deque , list , forward_list 。序列容器中数据元素是线性存储,元素间有明确的顺序关系。
  • vector :动态数组,支持快速随机访问,但在非尾部插入和删除操作效率较低。
  • deque :双端队列,可以在两端快速插入和删除,内部结构类似多个小的数组。
  • list :双向链表,所有操作,包括插入和删除都在常数时间内完成,但不支持快速随机访问。
  • forward_list :单向链表,比 list 有更小的内存开销。

  • 关联容器 :包括 set , multiset , map , multimap 。关联容器基于平衡二叉树实现,可以快速检索数据。

  • set/multiset :集合,不允许重复元素,元素自动排序。
  • map/multimap :映射,键值对结构,键不允许重复,值可以重复。

  • 无序关联容器 :包括 unordered_set , unordered_multiset , unordered_map , unordered_multimap 。与关联容器相比,无序关联容器基于哈希表,查询操作平均时间复杂度为常数级。

选择合适的容器时,需要考虑以下因素: - 数据的增删查改频率和场景。 - 是否需要快速随机访问。 - 是否需要元素有序。 - 对内存使用的限制。

代码示例与逻辑分析

下面是一个使用 vector map 的基本示例:

#include <iostream>
#include <vector>
#include <map>

int main() {
    // 使用vector存储整数
    std::vector<int> vec = {1, 2, 3, 4, 5};
    // 使用map存储字符串
    std::map<std::string, int> map;
    map["one"] = 1;
    map["two"] = 2;
    map["three"] = 3;
    // 遍历并打印vector中的元素
    for (int elem : vec) {
        std::cout << elem << ' ';
    }
    // 遍历并打印map中的元素
    for (const auto& pair : map) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
    return 0;
}

在上述代码中: - 我们创建了一个 vector<int> 来存储整数。 - 创建了一个 map<std::string, int> 来存储字符串到整数的映射。 - 使用范围for循环来遍历 vector map 中的元素,并打印出来。

选择容器时,如果需要频繁在尾部插入和随机访问元素, vector 是很好的选择。如果需要键值对存储,并且键是唯一的, map 是一个不错的选择。

4.1.2 常用容器的使用方法与案例

每种容器都有其特定的成员函数和操作方式。下面列举了几个常用容器的一些典型用法:

#include <iostream>
#include <vector>
#include <list>
#include <map>

int main() {
    // 使用vector插入元素
    std::vector<int> vec;
    vec.push_back(10); // 在vector末尾插入元素
    // 使用list的双向迭代器
    std::list<int> lst = {1, 2, 3, 4, 5};
    for (auto it = lst.begin(); it != lst.end(); ++it) {
        *it = *it * 10; // 将每个元素乘以10
    }
    // 使用map插入键值对
    std::map<std::string, int> map;
    map["one"] = 1;
    map["two"] = 2;
    // 使用map的下标操作
    if (map.find("three") == map.end()) {
        map["three"] = 3; // 如果键不存在,进行插入
    }
    return 0;
}

在使用STL容器时,必须熟悉其成员函数和特有操作。比如: - push_back() 方法用于在 vector list 的尾部插入元素。 - 双向迭代器允许容器内元素的双向访问,适用于 list 。 - find() 方法用于在 map 中查找键是否存在。

表格展示容器特性

| 容器类型 | 时间复杂度 | 是否有序 | 是否允许重复 | 特殊操作 | |----------|------------|----------|--------------|----------| | vector | O(1)随机访问 | 是 | 是 | push_back(), pop_back() | | deque | O(1)随机访问 | 是 | 是 | push_front(), pop_front() | | list | O(1)双向迭代 | 否 | 是 | insert(), erase() | | set | O(log n)插入 | 是 | 否 | lower_bound(), upper_bound() | | map | O(log n)插入 | 是 | 否 | find(), operator[] |

在实际开发中,根据不同的应用场景,我们选择不同的容器和操作来保证代码的效率和可读性。

4.2 STL算法与迭代器

4.2.1 核心算法的介绍与实例

STL算法是一组通用算法,用于处理容器中的数据,与容器类型独立。算法通过迭代器来操作数据,因此它们通常可以适用于任何支持所需操作的容器。

常用的算法包括: - std::sort :用于对容器中的元素进行排序。 - std::find :在容器中查找指定的元素。 - std::copy :从一个容器复制元素到另一个容器。 - std::transform :对容器中的每个元素应用某种操作。

#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {5, 2, 8, 6, 3};
    // 使用std::sort对vector进行排序
    std::sort(vec.begin(), vec.end());
    // 使用std::find查找元素
    auto it = std::find(vec.begin(), vec.end(), 3);
    if (it != vec.end()) {
        std::cout << "Element 3 found at position: " << std::distance(vec.begin(), it) << std::endl;
    }
    // 使用std::copy将元素从一个vector复制到另一个vector
    std::vector<int> vec2;
    std::copy(vec.begin(), vec.end(), std::back_inserter(vec2));
    // 使用std::transform将每个元素乘以2
    std::transform(vec.begin(), vec.end(), vec.begin(), [](int x) { return x * 2; });
    return 0;
}

在上述示例中,我们展示了如何排序、查找、复制以及应用自定义操作到容器中的元素。这些算法都是模板函数,因此可以用于任何支持相应迭代器操作的容器。

4.2.2 迭代器的使用技巧和限制

迭代器是一种泛型指针,它允许我们访问容器中的元素,但不需知道容器具体是如何实现的。迭代器是算法和容器之间的桥梁。

迭代器的主要操作包括: - *iter :解引用迭代器,获取迭代器指向的元素。 - iter-> :访问迭代器指向的元素的成员。 - ++iter iter++ :移动迭代器到下一个元素。 - --iter iter-- :移动迭代器到上一个元素。 - iter + n iter - n :前进或后退n个元素。 - iter1 - iter2 :返回两个迭代器之间的距离。 - iter1 == iter2 iter1 != iter2 :比较迭代器是否相等。

迭代器有一个重要限制,那就是不支持算术运算符 + - 来前进或后退多个位置,因为这会导致迭代器失效。为了前进或后退多个位置,可以使用 std::advance() 函数。

代码块示例:使用迭代器遍历容器

#include <iostream>
#include <vector>
#include <iterator>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    // 使用迭代器遍历vector
    for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
        std::cout << *it << ' ';
    }
    std::cout << std::endl;
    // 使用auto自动类型推导简化迭代器声明
    for (auto it = begin(vec); it != end(vec); ++it) {
        std::cout << *it << ' ';
    }
    std::cout << std::endl;
    // 使用C++11范围for循环简化代码
    for (int elem : vec) {
        std::cout << elem << ' ';
    }
    std::cout << std::endl;
    return 0;
}

在上述代码中: - 我们展示了如何使用迭代器遍历 vector 中的元素。 - 使用 begin() end() 函数获取容器的开始和结束迭代器。 - 使用 auto 关键字可以自动推导迭代器类型,避免冗长的类型声明。 - C++11引入的范围for循环提供了更简洁的遍历容器的方式。

迭代器是STL算法的核心,理解和掌握迭代器的使用对于深入学习STL是必不可少的。通过迭代器,我们可以灵活地处理容器中的数据,利用STL提供的强大算法库简化编程工作。

5. 异常处理机制

5.1 异常的捕获与处理

异常的基本概念

在C++中,异常是一种处理程序错误的方式。程序在运行时遇到的问题,如除以零、内存分配失败等,可以被抛出为异常。异常提供了一种将控制权从当前执行上下文中移出,并且根据问题的性质将其传递到能够处理这些问题的代码部分的方法。

异常的定义与传播

异常对象通常在发生错误的位置被抛出,然后在函数调用堆栈中向上传播,直到它被一个适当的 try-catch 块捕获并处理。异常可以是任何类型的对象,但通常使用继承自 std::exception 的类型,因为它们提供了额外的错误信息。

#include <iostream>
#include <stdexcept> // 包含标准异常类的头文件

void functionThatMayThrow() {
    // 假设这里是代码中可能会抛出异常的地方
    throw std::runtime_error("A runtime error has occurred");
}

int main() {
    try {
        functionThatMayThrow();
    } catch(const std::exception& e) {
        // 捕获异常并输出错误信息
        std::cerr << "Exception caught: " << e.what() << std::endl;
    }
    return 0;
}
try-catch-finally结构的运用

try 块用来包裹可能出现异常的代码。如果在 try 块中抛出了异常,控制流将立即跳转到匹配的 catch 块。 catch 块需要明确指出它可以处理的异常类型。 finally 块并不是C++语言的一部分,但是通常用来执行清理代码,无论是否发生异常都会执行。

try {
    // 可能抛出异常的代码
} catch (const SomeExceptionType& e) {
    // 处理特定类型的异常
} catch (...) {
    // 处理任何其他类型的异常
} finally {
    // 最终执行清理代码
}

5.2 异常安全编程

异常安全性的概念和实践

异常安全性是指当程序抛出异常时,程序的状态仍然能够保持有效。异常安全的代码应该保证不会泄露资源,不会违反数据结构的不变式,以及在异常发生后,程序还可以继续安全地运行。

异常安全性的保证通常分为三个层次: - 基本保证 :如果异常被抛出,程序不会泄露资源,对象处于有效状态,但可能无法保持原始状态。 - 强保证 :如果异常被抛出,程序将回滚到抛出异常前的状态。 - 不抛出保证 :承诺不会抛出异常,所有操作都是安全的。

void functionThatIsExceptionSafe() {
    // 实现异常安全的代码
    std::lock_guard<std::mutex> lock(mx); // 自动资源管理,保证基本异常安全性
    // ... 操作共享资源 ...
}
异常安全的代码编写原则

要编写异常安全的代码,应该遵循一些原则: - 使用RAII(Resource Acquisition Is Initialization)来管理资源。 - 避免在析构函数中抛出异常。 - 使用智能指针自动管理内存。 - 避免异常的“隐藏”(比如,在析构函数、操作符重载中抛出异常前先进行异常处理)。 - 减少异常的传播距离,通常在最接近产生异常的地方进行处理。

class MyResource {
public:
    MyResource() { /* 初始化资源 */ }
    ~MyResource() { /* 清理资源 */ }
    void doSomething() { /* 操作资源 */ }
};

void functionThatIsExceptionSafe() {
    std::unique_ptr<MyResource> resource = std::make_unique<MyResource>();
    try {
        resource->doSomething();
    } catch (...) {
        // 如果doSomething抛出异常,unique_ptr会自动释放资源
        throw;
    }
}

异常处理是确保程序健壮性和可维护性的重要组成部分。良好的异常处理机制能够提高程序的可靠性和安全性,使程序在面对错误时能够采取适当的恢复措施,而不是直接崩溃或者留下难以追踪的资源泄露。

6. C++11新特性概览

C++11是在C++编程语言发展史上具有里程碑意义的一个版本,它引入了大量的新特性,以满足现代编程的需求。本章将深入探讨C++11的一些关键特性,帮助你掌握其精髓并应用到实际编程中。

6.1 自动类型推导与Lambda表达式

C++11引入的新特性之一是自动类型推导,这极大地简化了模板编程和复杂类型的声明。另一个重要特性是Lambda表达式,它为编写简洁的函数对象提供了一种便利的方式。

6.1.1 auto关键字的使用

在C++11中, auto 关键字可以用来进行变量的自动类型推导。这在使用迭代器或者复杂类型时尤为有用,可以减少冗余的代码并且避免类型错误。

#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    for(auto it = vec.begin(); it != vec.end(); ++it) {
        std::cout << *it << " ";
    }
    return 0;
}

在这个例子中,迭代器 it 被声明为 auto 类型,编译器会自动推导出其类型为 std::vector<int>::iterator

6.1.2 Lambda表达式的定义和优势

Lambda表达式提供了一种创建匿名函数对象的方式,可以捕获外部变量,并在需要函数对象的地方直接定义和使用它们。

#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    int a = 10;
    std::for_each(vec.begin(), vec.end(), [a](int& x) {
        x += a;
    });
    for(auto x : vec) {
        std::cout << x << " ";
    }
    return 0;
}

在这个例子中, [a](int& x) 创建了一个捕获变量 a 的Lambda表达式,其作用是将容器中的每个元素增加 a 的值。

6.2 并发编程新特性

C++11在并发编程方面做了大量改进,引入了线程库、原子操作和内存模型等新特性,这些为编写高效且线程安全的代码提供了工具。

6.2.1 线程库的使用和线程管理

C++11引入的线程库基于 <thread> 头文件,使得创建和管理线程变得更加简单和直观。

#include <thread>
#include <iostream>

void print_id(int id) {
    std::cout << "ID: " << id << '\n';
}

void f() {
    for(int i = 0; i < 10; ++i) {
        print_id(i);
    }
}

int main() {
    std::thread t1(f);
    std::thread t2(f);
    t1.join();
    t2.join();
    return 0;
}

在这个例子中,主线程创建了两个线程 t1 t2 ,它们并行执行函数 f 。使用 join() 方法等待线程完成。

6.2.2 原子操作与内存模型

原子操作保证了操作的不可分割性,是并发编程中保证线程安全的基石。C++11提供了一个新的 <atomic> 头文件,包含了一系列原子类型和操作。

#include <atomic>
#include <thread>
#include <iostream>

std::atomic<int> atomic_var(0);

void increment() {
    ++atomic_var;
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join();
    t2.join();
    std::cout << "Value of atomic_var: " << atomic_var << '\n';
    return 0;
}

在这个例子中,两个线程同时增加一个原子变量 atomic_var ,由于它是原子类型,所以其值会正确增加。

通过以上例子和解释,相信你已经对C++11的一些新特性有了较为深入的了解。这些新特性使得C++编程更加高效和现代化,也为解决现代编程问题提供了更多的工具。在实际应用中,你可以结合这些特性编写出更加优雅和高效的代码。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:《C++程序员面试宝典》详尽地覆盖了C++基础知识、面向对象编程、内存管理、STL、异常处理、模板以及C++11新特性等多个面试必考主题,并提供了大量代码示例。书中不仅强调了基本概念,还深入讲解了函数、类、继承、多态、智能指针、设计模式等关键知识点,帮助读者深入理解C++编程,并在面试中表现出色。对于初学者来说,这本书是掌握C++并提升编程技能的宝贵资源。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值