C++11学习笔记(2)——标准库的基本概念

1.命名空间

命名空间提供了一种在C++中组织和管理标识符名称的机制,防止命名冲突,增强代码的组织和可读性。
最常用的是std,以它为例有以下三种用法:

  • 直接指定
std::cout << "?" <<std::endl;
  • 用using声明
using std::cout;
using std::endl;
cout << "?" <<endl;
  • 声明整个命名空间(一般写法)
using namespace std;
cout << "?" <<endl;

2.头文件的名称与格式

C++头文件(header files)是包含在C++源代码文件中的文件,用于声明类、函数、变量和其他代码元素的接口和定义。头文件通常包含函数原型、类的声明、宏定义以及其他需要在多个源代码文件中共享的声明。
头文件的主要作用是:

  • 提供接口声明:头文件中包含了函数、类和变量的声明。其他源代码文件可以包含该头文件,以便使用其中声明的代码元素。通过头文件的使用,可以在不暴露实现细节的情况下,让源代码文件了解和使用接口。

  • 减少重复代码:通过将常用的代码放入头文件中,可以在多个源代码文件中重复使用,避免了代码重复编写的问题。这样有助于提高代码的重用性和维护性。

  • 模块化开发:头文件可以将代码逻辑和功能划分为独立的模块。通过包含特定的头文件,可以将相关的功能集中在一起,使代码结构更加清晰和可维护。

在使用头文件时,通常需要注意以下几点:

  • 使用预处理指令 #include 包含头文件。例如,#include “myheader.h” 将包含名为 “myheader.h” 的头文件。
  • 头文件中应该包含保护性的预处理指令,以避免多重包含。例如,使用 #ifndef、#define 和 #endif 来创建头文件的保护性宏。
  • 头文件应该只包含接口声明和必要的定义,避免在头文件中添加大量的实现代码,以防止重复定义错误。
//c++库头
#include<iostream>
#include<string>
//c库头(以c开头代替.h)
#include<cstdlib>
#include<cstring>
//旧的c库头仍然有效
#include<stdlib.h>
//另外的.h文件,文件头可加入define防止冲突
#ifndef MYHEADER_H
#define MYHEADER_H
#include "myheader.h"

3.差错和异常

在C++中,错误(errors)和异常(exceptions)处理是用于处理程序中可能发生的错误或异常情况的机制。这些机制允许识别和处理潜在的错误,并采取相应的措施,以确保程序的正常执行或进行适当的错误处理。

  • 错误处理:
    错误处理是一种机制,用于处理可能发生的错误情况,如无效输入、文件读取失败、内存分配错误等。在C++中,可以使用条件语句(如if语句和switch语句)来检查错误条件并采取相应的操作。常见的错误处理方式包括打印错误消息、返回错误码或异常值等。

  • 异常处理:
    异常处理是一种更高级的错误处理机制,它允许在错误发生时将控制权从当前代码块转移到异常处理代码块中。异常处理使用try-catch语句块来捕获和处理异常。当发生异常时,程序将跳转到最近的匹配catch块,并执行相应的异常处理代码。

  • 抛出异常(Throwing Exceptions):通过使用throw语句,可以在代码中显式地抛出异常。可以抛出各种类型的异常,包括内置类型(如整数、字符等)和自定义类型(如类对象)。抛出异常时,控制流将立即跳转到最接近的匹配catch块。

  • 捕获异常(Catching Exceptions):使用try-catch语句块来捕获和处理异常。在try块中,可以编写可能引发异常的代码。如果在try块中抛出了异常,程序将跳转到与异常类型匹配的catch块,并执行相应的处理代码。

  • 处理异常(Handling Exceptions):catch块中的代码用于处理捕获的异常。可以根据异常类型执行相应的操作,如输出错误消息、进行恢复操作、重新抛出异常等。如果没有找到匹配的catch块,异常将传播到上层调用栈中,直到找到匹配的catch块或程序终止。

  • 清理资源(Cleanup Resources):异常处理还可以用于确保在异常发生时释放已分配的资源。通过在适当的catch块或finally块中执行资源清理操作,可以防止资源泄漏和内存泄漏。

一些异常类

为了处理标准库可能抛出的所有异常,应当包含:

#include<exception>
#include<stdexcept>
#include<system_error>
#include<new>
#include<ios>
#include<future>
#include<typeinfo>

逻辑错误(Logic Error):

逻辑错误是指程序中的错误逻辑或错误用法导致的异常情况。这些错误通常在程序运行时发生,并且不符合预期的逻辑规则。在C++中,std::logic_error是表示逻辑错误的异常类的基类,它提供了派生类来表示不同类型的逻辑错误。

std::invalid_argument:
表示无效参数的异常类。它用于指示传递给函数的参数无效或不合法。

std::domain_error:
表示域错误的异常类。它用于指示某些操作的结果超出了定义的有效域范围。

std::length_error:
表示长度错误的异常类。它用于指示某些操作的结果超出了定义的最大长度。

std::out_of_range:
表示超出范围的异常类。它用于指示索引超出容器或数组的有效范围。

运行时错误(Runtime Error):

运行时错误是指程序在运行过程中发生的错误,导致程序无法正常执行或产生异常情况。在C++中,std::runtime_error是表示运行时错误的异常类的基类,它提供了派生类来表示不同类型的运行时错误。

std::runtime_error:
表示运行时错误的基类异常类。它通常用于表示由于程序执行错误而引发的异常情况。

std::overflow_error:
表示溢出错误的异常类。它用于指示某些算术操作的结果超出了可表示的范围。

std::underflow_error:
表示下溢错误的异常类。它用于指示某些算术操作的结果低于可表示的最小值。

std::range_error:
表示范围错误的异常类。它用于指示某些操作的结果超出了定义的有效范围。

标准库抛出的异常类:

除了上述逻辑错误和运行时错误的异常类外,C++标准库还提供了其他异常类来表示不同的异常情况。这些异常类大多数都从std::exception派生,其中std::exception是所有标准异常类的基类。

std::bad_alloc:
表示内存分配失败的异常类。它用于指示无法满足内存分配请求。

std::bad_cast:
表示类型转换失败的异常类。它用于指示无法在运行时进行类型转换。

std::bad_function_call:
表示无效函数调用的异常类。

语言支持异常类

涉及到C++的语言支持(language support)时,C++标准库还提供了一些与语言特性相关的异常类。

std::bad_typeid:
表示typeid操作失败的异常类。它用于指示无法获取类型信息的异常情况。

std::bad_exception:
表示未被捕获的异常的异常类。它用于指示发生了无法处理的异常情况。

std::bad_weak_ptr:
表示std::weak_ptr操作失败的异常类。它用于指示std::weak_ptr无效或过期的情况。

std::bad_variant_access:
表示访问std::variant的异常类。它用于指示对std::variant的非活跃成员进行访问的异常情况。

成员函数

异常类通常具有一些成员函数,用于提供关于异常的信息和处理方式。以下是常见的异常类成员函数:

  • what():
    what()是一个虚函数,定义在std::exception基类中。派生的异常类可以重写该函数以提供特定的异常描述信息。what()函数返回一个C风格的字符串(const char*),描述异常的详细信息。

  • virtual ~exception():
    析构函数用于异常对象的清理和资源释放。在异常处理过程中,当异常对象超出作用域或被重新抛出时,析构函数会自动调用。

  • 其他成员函数:
    某些异常类可能提供其他成员函数,用于获取更多有关异常的信息。这些函数可以根据异常类的具体目的和设计来定义。例如,std::invalid_argument异常类提供了invalid_argument::what()函数以及一个返回无效参数的成员函数。

自定义异常类可以根据需要添加适当的成员函数。这些函数可以提供异常的自定义信息、错误码、附加数据等,以帮助更好地处理异常情况。

#include <exception>
#include <iostream>

class MyException : public std::exception {
    
    
private:
    std::string errorMessage;

public:
    MyException(const std::string& message) : errorMessage(message) {
    
    }

    const char* what() const noexcept {
    
    
        return errorMessage.c_str();
    }

    std::string getErrorMessage() const {
    
    
        return errorMessage;
    }
};

int main() {
    
    
    try {
    
    
        throw MyException("Custom exception occurred");
    } catch (const std::exception& e) {
    
    
        std::cout << "Exception caught: " << e.what() << std::endl;

        const MyException* myException = dynamic_cast<const MyException*>(&e);
        if (myException) {
    
    
            std::cout << "Custom error message: " << myException->getErrorMessage() << std::endl;
        }
    }

    return 0;
}

在这个例子中,自定义异常类MyException重写了基类std::exception中的what()函数,以提供异常的描述信息。它还定义了一个额外的成员函数getErrorMessage(),用于获取自定义错误消息。在main()函数中,我们抛出自定义异常,并在catch块中捕获并处理它。我们使用dynamic_cast将异常对象转换为MyException类型,并调用getErrorMessage()函数获取自定义错误消息。

exception_ptr

std::exception_ptr是C++标准库中的一种特殊类型,它用于捕获和存储异常的指针,并允许在稍后的时间点重新抛出该异常。

std::exception_ptr是通过调用std::current_exception()函数来获取异常指针。这个函数通常在catch块中调用,它会返回一个指向当前异常的指针。

以下是std::exception_ptr的主要用法:

  • 捕获异常并存储为std::exception_ptr:
try {
    
    
    // 可能会抛出异常的代码
} catch (...) {
    
    
    std::exception_ptr exPtr = std::current_exception();
    // 存储异常指针以便稍后重新抛出
}

在catch块中,我们可以使用std::current_exception()函数获取当前异常的指针,并将其存储在std::exception_ptr类型的变量中。

重新抛出异常:

try {
    
    
    // 可能会抛出异常的代码
} catch (...) {
    
    
    std::exception_ptr exPtr = std::current_exception();
    // 存储异常指针以便稍后重新抛出
    // ...
    std::rethrow_exception(exPtr); // 重新抛出异常
}

在catch块中,我们可以使用std::rethrow_exception()函数将之前存储的异常指针重新抛出。这会导致异常在调用堆栈中的下一个适当位置再次被抛出。

在其他地方处理异常:

    try {
    
    
        // ...
    } catch (const std::exception& e) {
    
    
        std::exception_ptr exPtr = std::current_exception();
        // ...
    }

    // 在稍后的时间点处理异常
    try {
    
    
        std::rethrow_exception(exPtr);
    } catch (const std::exception& e) {
    
    
        // 处理重新抛出的异常
    }

我们可以在catch块之外存储异常指针,并在稍后的时间点再次捕获和处理异常。

使用std::exception_ptr,我们可以在异常处理过程中将异常保存下来,并在稍后的时间点进行处理。这对于需要将异常从一个线程传递到另一个线程或在异步操作中处理异常非常有用。

需要注意的是,std::exception_ptr只能捕获和重新抛出标准异常类型或自定义异常类型,而不能捕获非异常的错误或整型值等。此外,对于异常处理的正确使用,应谨慎处理异常指针以避免悬挂指针或资源泄漏等问题。

4.可被调用的对象

在C++中,可调用对象(Callable Object)是指可以像函数一样被调用的对象。可调用对象可以是函数指针、函数对象(也称为函数符或函数子)、Lambda 表达式或绑定到成员函数的指针等。

下面是几种常见的可调用对象类型:

函数指针

函数指针是指向函数的指针变量。可以通过函数指针来调用相应的函数。例如:

void myFunction(int x) {
    
    
 // 函数体
}

// 函数指针声明和赋值
void (*functionPtr)(int) = myFunction;

// 使用函数指针调用函数
functionPtr(10);

函数对象(函数符或函数子)

函数对象是类对象,它可以像函数一样被调用。函数对象通过重载operator()函数来实现。例如:

struct MyFunctor {
    
    
    void operator()(int x) {
    
    
        // 函数体
    }
};

// 创建函数对象并调用
MyFunctor functor;
functor(10);

Lambda 表达式

Lambda 表达式是一种匿名函数,可以在需要时直接定义和使用。Lambda 表达式可以捕获外部变量,并使用[]中的捕获列表来指定捕获方式。例如:

int x = 5;

// Lambda 表达式
auto lambda = [x](int y) {
    
    
    return x + y;
};

// 使用 Lambda 表达式调用函数
int result = lambda(10);

绑定到成员函数的指针

可以通过绑定到成员函数的指针来调用类的成员函数。例如:

struct MyClass {
    
    
    void myMemberFunction(int x) {
    
    
        // 成员函数体
    }
};

// 成员函数指针声明和赋值
void (MyClass::*memberFunctionPtr)(int) = &MyClass::myMemberFunction;

// 创建对象并调用绑定的成员函数
MyClass obj;
(obj.*memberFunctionPtr)(10);

可调用对象在许多情况下非常有用,例如作为函数参数、回调函数、函数对象算法等。它们提供了更灵活的函数调用方式,并允许以更直观的方式定义和使用函数功能。

5.并发和多线程

此部分在书的第18章,所以这里只是简略介绍

C++11引入了对并发和多线程编程的支持,提供了一组多线程库和原语,使得在C++中编写并发程序更加方便和安全。以下是C++11中并发和多线程编程的一些关键特性:

线程库(Thread Library)

C++11引入了头文件,其中包含了用于创建、管理和同步线程的类和函数。你可以使用std::thread类来创建新的线程,通过构造函数传递一个可调用对象(函数指针、函数对象、Lambda表达式等)作为线程的执行体。

互斥量(Mutex)和锁(Lock)

为了保护共享数据免受并发访问的冲突,C++11提供了std::mutex和std::lock_guard等类。互斥量用于实现互斥访问,而锁则用于自动化互斥量的加锁和解锁操作。

条件变量(Condition Variable):

C++11引入了std::condition_variable类,用于线程间的条件同步。条件变量通常与互斥量一起使用,以实现线程等待某个条件的发生。

原子操作(Atomic Operations):

C++11引入了头文件,其中包含了原子类型和原子操作函数。原子操作是一种能够保证操作的原子性(不可分割)的操作。原子类型可以在多线程环境下进行安全的读取和写入操作,而无需额外的锁定机制。

并发容器(Concurrent Containers):

C++11提供了一些并发安全的容器类,如std::vector、std::map、std::queue等的并发版本。这些容器类能够在多线程环境下进行并发访问而不需要额外的同步机制。通过这些容器,我们可以安全地在多线程中共享数据。

线程局部存储(Thread-Local Storage):

C++11引入了线程局部存储(TLS)的支持,通过关键字thread_local可以声明线程局部变量。线程局部变量是每个线程独立拥有的变量,每个线程都有自己的变量副本。这在多线程环境中非常有用,因为每个线程可以独立地访问和修改自己的变量副本,而不会干扰其他线程的数据。

以上是C++11中并发和多线程编程的一些重要特性。这些特性使得在C++中处理并发任务和编写多线程程序更加容易和安全。然而,正确地使用并发和多线程编程仍然需要注意资源共享、同步机制和避免竞态条件等方面的问题,以确保程序的正确性和性能。

6.分配器

此部分在书的第19章,所以这里只是简略介绍

在C++11中,引入了分配器(Allocator)的概念,它提供了一种在内存分配和释放方面更加灵活的方式。分配器可以用于自定义内存管理策略,例如使用特定的内存池或实现自定义的分配算法。

C++11中的分配器主要与容器相关,每个容器都可以指定自己的分配器类型。以下是C++11中的几个与分配器相关的重要概念和类:

std::allocator:

std::allocator是C++标准库中的默认分配器。它定义了内存的分配和释放操作,可以用于大多数容器类。

std::allocator_traits:

std::allocator_traits是一个模板类,用于访问和操作分配器的属性和行为。它提供了一组用于管理分配器的类型和函数。

分配器要求:

分配器类需要定义以下成员函数:allocate(分配内存块)、deallocate(释放内存块)、construct(在已分配的内存中构造对象)和destroy(销毁对象并释放内存)。

容器的分配器:

大多数C++标准库容器类都有一个模板参数用于指定分配器类型。例如,std::vector的分配器类型是std::allocator,可以通过第二个模板参数来指定自定义的分配器类型。

  • 第四章完工

Guess you like

Origin blog.csdn.net/qq_44616044/article/details/131171922