C++新特性

C++11的特性:

auto关键字

自动类型推导,可以让编译器自动推断变量的类型。用于变量的声明和定义。

auto variable_name = expression;
  1. Lambda表达式:匿名函数,可以方便地在代码中定义函数对象,可以在代码中定义一个函数对象,而不必显示地编写函数的名称和类型。
[capture_list](parameter_list) -> return_type { function_body }

auto sum = [](int a, int b) -> int { return a + b; };
int result = sum(3, 4); // result的值为7

apture_list表示Lambda表达式的捕获列表,用于捕获Lambda表达式中的外部变量;parameter_list表示Lambda表达式的参数列表;return_type表示Lambda表达式的返回类型;function_body表示Lambda表达式的函数体

Lambda表达式的一个重要特点是可以捕获外部变量,也就是在Lambda表达式中使用定义在Lambda表达式外部的变量。捕获变量的方式有两种:值捕获引用捕获。值捕获会将变量的值复制到Lambda表达式中,而**引用捕获则会引用变量的地址。**例如:

int x = 10;
auto f1 = [x]() mutable { x = 20; }; // 值捕获
auto f2 = [&x]() { x = 30; }; // 引用捕获

f1(); // x的值仍为10,因为f1中的x是复制品
f2(); // x的值变为30,因为f2中的x是引用

范围for循环

可以遍历容器中的元素。

它提供了一种简单、直观的方法来遍历数组、容器和其他支持迭代器的数据结构。范围for循环的语法格式如下:

for (type variable : range) {
    // 循环体
}

type表示迭代器指向的对象的类型variable循环变量,用于存储每次迭代中的元素值range表示要遍历的数据结构,可以是数组、容器或支持迭代器的数据结构。

#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = { 1, 2, 3, 4, 5 };
    for (int x : v) {
        std::cout << x << " ";
    }
    return 0;
}

for循环可以避免许多错误,例如数组越界、容器迭代器的错误使用

智能指针:

unique_ptr、shared_ptr、weak_ptr等,可以帮助程序员更好地管理内存。
1. shared_ptr
1、shared_ptr的实现机制是在拷贝构造时使⽤同⼀份引⽤计数
(1)⼀个模板指针T* ptr指向实际的对象
(2)⼀个引⽤次数必须new出来的,不然会多个shared_ptr⾥⾯会有不同的引⽤次数⽽导致多次delete。
(3)重载operator*和operator->使得能像指针⼀样使⽤shared_ptr
(4)重载copy constructor使其引⽤次数加⼀(拷贝构造函数)
(5)重载operator=(赋值运算符如果原来的shared_ptr已经有对象,则让其引⽤次数减⼀并判断引⽤是否为零(是否调⽤delete),然后将新的对象引⽤次数加⼀
(6)重载析构函数使引⽤次数减⼀并判断引⽤是否为零; (是否调⽤delete)
2. 线程安全问题
(1)同⼀个shared_ptr被多个线程“读”是安全的;
(2)同⼀个shared_ptr被多个线程“写”是不安全的;
证明:在多个线程中同时对⼀个shared_ptr循环执⾏两遍swap。 shared_ptr的swap函数的作⽤就是和另外⼀个shared_ptr交换引⽤对象和引⽤计数,是写操作。执⾏两遍swap之后,hared_ptr引⽤的对象的值应该不变)
(3)共享引⽤计数的不同的shared_ptr被多个线程”写“ 是安全的。

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> ptr1(new int(42));
    std::shared_ptr<int> ptr2 = ptr1;
    std::cout << *ptr1 << " " << *ptr2 << std::endl;
    return 0;
}
  1. unique_ptr
    1、unique_ptr**”唯⼀”拥有其所指对象同⼀时刻只能有⼀个unique_ptr指向给定对象,离开作⽤域时,若其指向对象,则将其所指对象销毁(默认delete)
    2、定义unique_ptr时需要
    将其绑定到⼀个new返回的指针上**。
    3、unique_ptr不⽀持普通的拷贝和赋值(因为拥有指向的对象)但是可以拷贝和赋值⼀个将要被销毁的unique_ptr;可以通过release或者reset将指针所有
    权从⼀个(⾮const)unique_ptr转移到另⼀个unique。
#include <iostream>
#include <memory>

int main() {
    std::unique_ptr<int> ptr1(new int(42));
    std::unique_ptr<int> ptr2 = std::move(ptr1); //所有权转移
    std::cout << *ptr2 << std::endl;
    //std::cout << *ptr1 << std::endl; //错误,ptr1 已经失效
    return 0;
}
  1. weak_ptr
    1、weak_ptr是为了配合shared_ptr⽽引⼊的⼀种智能指针它的最⼤作⽤在于协助shared_ptr⼯作,像旁观者那样观测资源的使⽤情况,但weak_ptr没有共享资源,它的构造不会引起指针引⽤计数的增加
    2、和shared_ptr指向相同内存shared_ptr析构之后内存释放,在使⽤之前使⽤函数lock()检查weak_ptr是否为空指针
#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> ptr1(new int(42));
    std::weak_ptr<int> ptr2 = ptr1;
    std::cout << *ptr1 << " " << *ptr2.lock() << std::endl;
    ptr1.reset();
    std::cout << ptr2.expired() << std::endl; //true
    return 0;
}

线程支持库

std::thread、std::mutex、std::condition_variable等,可以方便地创建和管理线程。

#include <iostream>
#include <thread>

// 线程函数
void thread_func(int count) {
    for (int i = 0; i < count; i++) {
        std::cout << "thread_func: " << i << std::endl;
    }
}

int main() {
    // 创建一个新线程,并执行 thread_func 函数
    std::thread t(thread_func, 10);
    // 等待线程执行完毕
    t.join();
    return 0;
}

程序使用std::thread库创建了一个新的线程,并执行了函数thread_func。函数thread_func用于输出一些信息,它的参数count指定了输出的次数。程序还调用了t.join()函数来等待线程执行完毕,然后结束程序的执行

除了std::thread库之外,C++11标准库还提供了一些其他的线程支持库,例如std::mutex、std::condition_variable、std::atomic等。这些库可以帮助我们实现线程同步、互斥等操作,从而更加方便地编写多线程程序。

std::function:

函数包装器,可以用于存储和调用任意可调用对象。

C++11引入了std::function,它是一种通用的、可调用的对象类型,可以用于存储和传递任意可调用对象,例如函数、函数指针、成员函数指针、函数对象等

std::function是一个模板类,它的参数是可调用对象的类型,例如

#include <functional>
#include <iostream>

// 普通函数
int add(int a, int b) {
    return a + b;
}

// 函数对象
class Add {
public:
    int operator()(int a, int b) const {
        return a + b;
    }
};

// 成员函数
class Calculator {
public:
    int add(int a, int b) const {
        return a + b;
    }
};

int main() {
    std::function<int(int, int)> f1 = add;
    std::cout << f1(1, 2) << std::endl;

    Add adder;
    std::function<int(int, int)> f2 = adder;
    std::cout << f2(3, 4) << std::endl;

    Calculator c;
    std::function<int(const Calculator&, int, int)> f3 = &Calculator::add;
    std::cout << f3(c, 5, 6) << std::endl;

    return 0;
}

定义了三种可调用对象:普通函数add,函数对象Add,和成员函数Calculator::add。程序使用std::function定义了三个可调用对象f1、f2、f3,分别表示add、Add::operator()和Calculator::add这三个函数或函数对象。可以看到,使用std::function可以非常方便地存储和传递各种可调用对象。

nullptr关键字:

表示空指针,取代了以前的NULL宏定义。

在C++总中,旧的NULL实际上是一个宏定义,通常被定义为0或者(void*)0,但这种定义存在一些问题。例如,如果把NULL传递给一个期望指针类型的函数可能会导致函数重载的匹配失败,从而引发编译错误。此外,C++11之前的标准并没有规定NULL的实现方式,不同编译器和平台可能会存在不一致的问题

因此,在C++11标准中引入了一个新的空指针常量nullptr。它的类型是std::nullptr_t,可以隐式转换为任意指针类型包括void指针。nullptr的优点包括:

  • 安全性:nullptr是一种明确的、类型安全的表示空指针的方式,避免了一些潜在的问题。
  • 可读性:nullptr更加清晰、直观,易于阅读和理解
#include <iostream>

void foo(int* ptr) {
    std::cout << "foo(int*): " << ptr << std::endl;
}

void foo(char* ptr) {
    std::cout << "foo(char*): " << ptr << std::endl;
}

int main() {
    int* p1 = nullptr;
    char* p2 = nullptr;

    foo(p1);
    foo(p2);
    // foo(nullptr); // 编译错误,无法匹配到正确的函数

    return 0;
}

程序定义了两个函数foo,分别接受int和char类型的指针参数。在main函数中,程序使用nullptr初始化了两个指针变量p1和p2,分别表示int指针和char指针。在调用foo函数时,程序**传递了这两个指针作为参数,可以看到,编译器正确地选择了对应的函数。**如果尝试使用nullptr作为参数,编译器将无法匹配到正确的函数,从而引发编译错误。

移动语义

它的主要作用是优化对象的复制和赋值操作,避免不必要的内存分配和拷贝,从而提高程序的运行效率。

在C++11之前,对象的复制和赋值操作都是通过拷贝构造函数和赋值操作符实现的,这些操作都会涉及到内存的分配和拷贝,对于大型对象和动态内存分配来说,这些操作的开销是很大的。而移动语义则可以通过将资源从一个对象转移到另一个对象来避免这些开销。

移动语义的实现需要使用右值引用和移动构造函数(Move Constructor)以及移动赋值操作符(Move Assignment Operator)。在使用右值引用定义一个变量时,可以使用std::move()函数将左值转换为右值引用移动构造函数和移动赋值操作符的参数都是右值引用,它们通过移动而不是拷贝资源,从而实现了移动语义。

使用移动语义可以避免大量的内存分配和拷贝,从而提高程序的性能和效率。例如,对于一个动态分配的对象,通过移动语义可以避免拷贝内存的操作,只需要修改指针的指向即可,这将大大减少内存操作的开销。同时,移动语义还可以提高程序的安全性和健壮性,避免了因为对象被误删除而引发的问题

#include <iostream>
#include <utility>

class Object {
public:
    Object() { std::cout << "Construct Object" << std::endl; }
    Object(const Object& other) { std::cout << "Copy Construct Object" << std::endl; }
    Object(Object&& other) { std::cout << "Move Construct Object" << std::endl; }
};

int main() {
    Object obj1; // 构造对象
    Object obj2(std::move(obj1)); // 移动构造对象
    Object obj3 = std::move(obj2); // 移动赋值对象
    return 0;
}

在这个例子中,程序定义了一个Object类,它有默认构造函数、拷贝构造函数和移动构造函数。在main函数中,程序先构造了一个obj1对象,然后通过std::move函数将obj1转换为右值引用,并使用移动构造函数构造了一个obj2对象,这个操作不会调用拷贝构造函数,因为已经使用了移动语义。接着,程序通过移动赋值操作符将obj2对象的资源移动到obj3对象中。在这个过程中,移动构造函数和移动赋值操作符都可以避免不必要的内存分配和拷

完美转发

完美转发是C++11引入的一个新特性,它能够将函数参数以相同的方式转发给另一个函数,不需要进行类型转换或者创建新的对象,从而避免了额外的内存分配和性能开销。

在C++中,函数参数的传递方式有两种:传值和传引用。对于小型对象,可以通过传值的方式传递参数,但对于大型对象或需要修改的对象,传引用会更加高效。在C++11之前,函数模板中的参数只能通过值或引用的方式传递,无法支持完美转发。而通过使用引入的新特性,可以在不丢失参数类型信息的情况下将参数传递给另一个函数。

完美转发的实现通常涉及到以下几个要点:

  1. 使用模板类型推导:由于完美转发需要保留参数的类型信息,所以使用函数模板来定义参数类型。
  2. 使用右值引用参数:右值引用是支持移动语义的关键,可以通过std::move()将左值转换为右值引用。
  3. 使用std::forward()进行转发:std::forward()是一个非常重要的函数,可以根据参数的类型和值类别进行正确的转发。如果参数是一个左值引用,那么std::forward()会将它转换为一个左值引用;如果参数是一个右值引用,那么std::forward()会将它转换为一个右值引用。

下面是一个简单的完美转发的例子:


#include <iostream>#include <utility>void foo(int& i) {
    std::cout << "foo(int&): " << i << std::endl;
}

void foo(const int& i) {
    std::cout << "foo(const int&): " << i << std::endl;
}

void foo(int&& i) {
    std::cout << "foo(int&&): " << i << std::endl;
}

template<typename T>
void bar(T&& t) {
    foo(std::forward<T>(t));
}

int main() {
    int i = 42;
    const int ci = 100;

    bar(i); // 调用 foo(int&)
    bar(ci); // 调用 foo(const int&)
    bar(42); // 调用 foo(int&&)

    return 0;
}

在这个例子中,程序定义了三个函数foo,分别用于处理左值引用、常量左值引用和右值引用参数。程序还定义了一个函数模板bar,它的参数是一个万能引用(Universal Reference),使用std::forward()对参数进行转发。在main函数中,程序分别调用bar函数传递一个左值引用、常量左值引用和右值引用参数。由于使用了std::forward()函数进行转发,程序可以正确地将参数类型和值类别转发给foo函数,从而避免了额外的类型转换和内存分配,提高了程序的性能和效率。

左值引用和右值引用

左值引用和右值引用是C++中非常重要的两个概念,它们是支持移动语义的关键。左值引用用于表示一个左值对象的引用,右值引用用于表示一个右值对象的引用。

左值引用使用&符号进行声明,可以引用一个左值对象。左值是一个可以被取地址的表达式,通常是一个具有名称的变量或对象。对于左值引用,它引用的对象必须是一个左值对象,因为左值对象可以被修改,需要通过引用来传递和操作。

右值引用使用&&符号进行声明,可以引用一个右值对象。右值是一个临时对象,通常是一个表达式的结果或者一个无名临时对象。右值引用被用来表示一个临时对象的引用,通常用于实现移动语义和完美转发。

在C++11中,右值引用被引入到语言中,通过右值引用可以实现移动语义。移动语义允许在不拷贝或分配额外内存的情况下,将一个对象从一个位置移动到另一个位置。通过使用右值引用,程序可以避免不必要的内存分配和性能开销,提高程序的效率和性能。

例如,下面的代码定义了一个使用右值引用参数的函数,用于交换两个对象的值:

template<typename T>
void swap(T&& a, T&& b) {
    T temp = std::move(a);
    a = std::move(b);
    b = std::move(temp);
}

在这个函数中,程序使用右值引用T&&声明了两个参数a和b,通过std::move()函数将a和b转换为右值引用。在函数体中,程序使用std::move()函数将a和b的值移动到临时变量temp中,将temp的值移动回a和b中,完成两个对象的值的交换。这里使用右值引用,可以避免不必要的拷贝和分配操作,提高了程序的效率和性能。对于一个对象,它的类型和值类别是两个不同的概念。一个对象的类型指的是它的数据类型,而它的值类别指的是它在表达式中的使用方式。对于一个表达式,它的值类别可以是左值、右值或纯右值。左值引用只能引用左值对象,右值引用只能引用右值对象,但它们都可以引用一个具有左值引用或右值引用类型的对象。在使用右值引用时,需要注意避免出现悬垂引用的情况。

垂悬引用

悬垂引用(dangling reference)是指一个指向已经被释放或者无效的对象的引用。通常情况下,当一个对象被销毁或者超出作用域时,与之相关的指针或引用应该被置为nullptr或者被销毁。如果没有这样做,那么这些指针或引用就会变成悬垂引用。

悬垂引用是一种常危险的编程错误,它可能导致程序崩溃、未定义的行为或者数据损坏等问题。下面是一个悬垂引用的例子:


#include <iostream>int& func() {
    int x = 1;
    return x; // 返回一个对局部变量的引用
}

int main() {
    int& y = func(); // y成为一个悬垂引用
    std::cout << y << std::endl; // 输出1或者崩溃
    return 0;
}

在这个例子中,定义了一个函数func,它返回一个对局部变量x的引用。在main函数中,使用y引用了func返回的引用,但是当func函数返回后,局部变量x就被销毁了,此时y成为了一个悬垂引用。在使用悬垂引用时,程序的行为是未定义的,有可能会输出1,也有可能会崩溃。为了避免悬垂引用的问题,需要遵循以下几个规则:

  1. 引用或指针被使用之前,应该确保它们指向的对象是有效的
  2. 在引用或指针指向的对象被销毁或者离开作用域时,应该将它们设置为nullptr或者销毁它们。
  3. 尽量避免返回对局部变量或者临时变量的引用或指针。如果需要返回引用或指针,应该确保返回的对象的生命周期足够长,例如返回成员变量、静态变量或者堆上分配的对象等。

浅拷贝、深拷贝

浅拷贝和深拷贝都是针对对象拷贝时的复制方式的概念。

浅拷贝是指对于一个对象,只是拷贝它的指针或者引用等浅层次的成员,而没有复制它们所指向的数据。也就是说,浅拷贝只是复制了原对象的地址,并没有新分配内存。因此,当进行浅拷贝时,拷贝对象和原对象共享同一块内存空间,它们的指针指向相同的数据,当其中一个对象修改数据时,另一个对象的数据也会被修改,容易出现不可预期的结果。

深拷贝是指对于一个对象,需要拷贝它所有的成员包括成员所指向的数据。也就是说,深拷贝是在堆中为新对象分配一块与原对象不同的内存空间,并将原对象的数据全部复制到新的内存空间中。因此,当进行深拷贝时拷贝对象和原对象拥有各自独立的内存空间,它们的数据互不干扰

在C++中,默认情况下对象的拷贝是浅拷贝。如果需要进行深拷贝,需要手动实现拷贝构造函数和赋值运算符函数。下面是一个深拷贝的例子:


#include <iostream>class Person {
public:
    Person(const char* name, int age) {
        m_name = new char[strlen(name) + 1];
        strcpy(m_name, name);
        m_age = age;
    }

    // 深拷贝
    Person(const Person& other) {
        m_name = new char[strlen(other.m_name) + 1];
        strcpy(m_name, other.m_name);
        m_age = other.m_age;
    }

    // 析构函数
    ~Person() {
        delete[] m_name;
    }

    // 赋值运算符函数
    Person& operator=(const Person& other) {
        if (this != &other) {
            delete[] m_name;
            m_name = new char[strlen(other.m_name) + 1];
            strcpy(m_name, other.m_name);
            m_age = other.m_age;
        }
        return *this;
    }

private:
    char* m_name;
    int m_age;
};

int main() {
    Person p1("Tom", 18);
    Person p2 = p1; // 使用拷贝构造函数进行深拷贝
    Person p3("Jerry", 20);
    p3 = p1; // 使用赋值运算符函数进行深拷贝
    return 0;
}

在这个例子中,定义了一个Person类,它包含了一个char类型的指针m_name和一个int类型的成员m_age。在Person类的构造函数中,使用new运算符在堆上分配了一块内存空间,并将字符串name复制到m_name。

C++14:

  1. 泛型lambda表达式:可以使用auto关键字在lambda表达式中定义泛型函数。
  2. 常量表达式函数:可以在编译期间求值的函数。
  3. 二进制字面量:可以使用0b或0B前缀表示二进制数。
  4. std::make_unique:可以方便地创建unique_ptr指针。
  5. std::exchange:原子交换操作,可以帮助实现线程安全的代码。
  6. 通用的数字分隔符:可以在数字中添加下划线,方便阅读和书写。

泛型lambda表达式

它允许lambda表达式的参数使用auto关键字来推导类型,从而让 lambda 表达式更加灵活。泛型 lambda 表达式的语法形式如下:


auto lambda = [](auto parameter) { /* lambda body */ };

其中,auto 参数可以接受任何类型的参数,而不需要显式指定类型。这样,我们就可以写出一个通用的 lambda 表达式,可以适用于不同类型的参数。

泛型 lambda 表达式的优点在于它可以帮助我们更加简洁和灵活地编写代码,特别是在处理容器等泛型算法时更加方便。下面是一个使用泛型 lambda 表达式的例子,它对一个整型数组中的元素进行累加:


#include <iostream>
#include <numeric>
#include <vector>
int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    auto sum = std::accumulate(v.begin(), v.end(), 0, [](auto x, auto y) {
        return x + y;
    });
    std::cout << "sum = " << sum << std::endl;
    return 0;
}

在这个例子中,我们使用泛型 lambda 表达式来定义一个累加器函数。这个函数接受两个参数 x 和 y,它们的类型可以自动推导。在 std::accumulate 算法中,我们将这个 lambda 函数作为第三个参数传递,用于累加整型数组中的元素。

需要注意的是,虽然泛型 lambda 表达式可以让代码更加简洁,但也会带来一些类型推导方面的问题。因此,在使用泛型 lambda 表达式时,需要仔细考虑参数的类型推导,以及可能出现的类型转换等问题,以确保代码的正确性和可读性

常量表达式函数

常量表达式函数是指在编译期间就能被完全计算出来的函数,它可以在一些需要常量表达式的地方使用,例如数组大小的声明模板实参、constexpr 变量等。

C++11 引入了 constexpr 关键字,用于**声明常量表达式函数。**constexpr 函数的语法形式如下:


constexpr returnType functionName(parameterList) {
    // function body
}

其中,returnType 表示函数的返回类型,functionName 表示函数的名称,parameterList 表示函数的参数列表,{} 中的部分表示函数的实现。

需要注意的是,constexpr 函数必须满足以下条件:

  1. 函数的参数和返回值都必须是字面值类型(包括内置类型和某些类类型)。
  2. 函数体中只能包含一条 return 语句或者简单的语句表达式(表达式中只包含字面值常量、全局或静态变量、constexpr 变量等)。
  3. 函数体中不能包含任何语句(如 if、while、for 等),除了简单的控制流语句(如 switch、if constexpr 等)。

下面是一个简单的 constexpr 函数的例子,用于计算斐波那契数列的第 n 个数:

c++Copy code
constexpr int fibonacci(int n) {
    return (n <= 1) ? n : (fibonacci(n-1) + fibonacci(n-2));
}

int main() {
    constexpr int result = fibonacci(10);
    static_assert(result == 55, "Fibonacci calculation error");
    return 0;
}

在这个例子中,我们使用 constexpr 关键字声明了一个斐波那契数列的函数,用于计算斐波那契数列的第 n 个数。在 main 函数中,我们使用 constexpr 变量来存储计算结果,并使用 static_assert 断言来验证计算结果的正确性。需要注意的是,在编译期间,编译器会对 constexpr 函数进行求值,然后将结果用于初始化 constexpr 变量。

二进制字面量

C++14 引入了二进制字面量(binary literal)的特性,允许程序员直接使用二进制数字表示整型常量,不需要手动转换成十进制或者使用其他转换工具。二进制字面量的语法形式为


0b binary_digits

其中,binary_digits 表示二进制数字序列,只能包含 0 和 1 两个数字。需要注意的是,C++14 规定二进制字面量的长度必须是 8 的倍数,否则编译器会报错。另外,二进制字面量只能用于表示无符号整型,例如:


unsigned char c = 0b11010101;    // 二进制字面量表示无符号字符
unsigned int n = 0b10101101;     // 二进制字面量表示无符号整数

在使用二进制字面量时,也可以使用单引号(')来分隔数字,以提高可读性,例如:


unsigned int n = 0b1010'1101;    // 二进制字面量使用单引号分隔数字

除了二进制字面量之外,C++14 还支持八进制字面量和十六进制字面量,其语法分别为:


0o octal_digits     // 八进制字面量
0x hexadecimal_digits   // 十六进制字面量

其中,octal_digits 和 hexadecimal_digits 分别表示八进制数字序列和十六进制数字序列,具体用法与二进制字面量类似。需要注意的是,为了避免字面量的可读性问题,C++17 中建议使用单引号或者下划线来分隔数字,例如:


unsigned int n = 0x1234'5678;   // 十六进制字面量使用单引号分隔数字
unsigned int m = 0xAB_CD_EF;   // 十六进制字面量使用下划线分隔数字

std::make_unique

std::make_unique 是 C++14 标准库中新增加的一个函数模板,用于创建一个 std::unique_ptr 智能指针的对象并返回其指针

std::unique_ptr 是一种智能指针,具有独占式拥有权,用于管理动态分配的对象,它会在其生命周期结束时自动释放所管理的资源

std::make_unique 的函数声明如下:


template< class T, class... Args >
std::unique_ptr<T> make_unique( Args&&... args );

其中,模板参数 T 表示要创建的对象的类型,参数包 Args... 表示构造函数的参数列表,使用了完美转发(forwarding)技术,可以将参数原封不动地转发给构造函数。

使用 std::make_unique 创建智能指针的示例如下:


#include <memory>#include <iostream>class MyClass {
public:
    MyClass(int a, int b) : m_a(a), m_b(b) {
        std::cout << "MyClass constructor called." << std::endl;
    }

    ~MyClass() {
        std::cout << "MyClass destructor called." << std::endl;
    }

private:
    int m_a, m_b;
};

int main() {
    auto ptr = std::make_unique<MyClass>(1, 2);
    return 0;
}

在上述示例中,使用 std::make_unique 创建了一个 MyClass 类型的对象,并将其封装在了一个 std::unique_ptr<MyClass> 智能指针中。当 ptr 指针超出作用域时,它所管理的对象会被自动释放,因此不需要手动调用 delete 函数来释放内存,从而避免了内存泄漏的问题。

std::exchange

std::exchangeC++14 标准库中新增加的一个函数模板,用于将一个对象的值交换为给定的新值,返回原来的值

std::exchange 的函数声明如下:


template<class T, class U=T>
T exchange(T& obj, U&& new_value);

其中,参数 obj 是要被交换值的对象,参数 new_value 是新值,使用了完美转发技术,可以接受任意类型的参数。函数会将 obj 的旧值返回,并将其设置为 new_value

使用 std::exchange 的示例如下:


#include <iostream>#include <utility>int main() {
    int x = 1, y = 2;
    int z = std::exchange(x, y);
    std::cout << "x = " << x << ", y = " << y << ", z = " << z << std::endl;
    return 0;
}

在上述示例中,std::exchange(x, y) 会将 x 的值设为 y,并返回 x 原来的值 1,因此输出结果为 x = 2, y = 2, z = 1

使用 std::exchange 可以方便地实现某些算法,如将一个对象插入到容器中,并返回其原来的位置,示例如下:


#include <iostream>
#include <vector>
#include <utility>
int main() {
    std::vector<int> v{1, 2, 3, 4, 5};
    int x = 6;
    auto it = v.insert(std::next(v.begin(), 2), std::exchange(x, 7));
    std::cout << "v = ";
    for (auto i : v) {
        std::cout << i << " ";
    }
    std::cout << ", x = " << x << ", *it = " << *it << std::endl;
    return 0;
}

在上述示例中,std::exchange(x, 7) 会将 x 的值设为 7,并返回 x 原来的值 6,插入到容器 v 中,并返回插入元素的迭代器,输出结果为 v = 1 2 7 3 4 5, x = 7, *it = 6

通用的数字分隔符

通用的数字分隔符是 C++17 标准新增的一项特性,可以用来提高数字的可读性。在 C++17 之前,如果要表示较大的整数或浮点数,可能需要使用下划线或空格手动分隔,这样代码不够简洁。

C++17 中,可以在数字字面量中使用单引号 ' 来分隔数字,这样可以提高数字的可读性,代码也更简洁。

以下是使用通用的数字分隔符的示例:


#include <iostream>int main() {
    int a = 123'456'789;
    double b = 3.141'592'653'589'793;
    std::cout << "a =" << a << std::endl;
    std::cout << "b = " << b << std::endl;
    return 0;
}

在上述示例中,使用单引号将数字分隔开,可以使数字更加易读。输出结果为:


a = 123456789
b = 3.14159

注意,通用的数字分隔符只能用于数字字面量中,不能用于变量或表达式中。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Lambda表达式是一种匿名函数,它可以作为对象传递,并且可以在需要函数的任何地方替换掉具名函数。Lambda表达式通过闭包捕获变量,这意味着它们可以访问它们定义时可见的所有非局部变量。 在Lambda表达式中,捕获是指Lambda表达式获取其定义之外的变量的值,这些变量可以在Lambda表达式内部使用。这个捕获的过程是非常灵活的,它提供了多种捕获方法,包括值捕获、引用捕获、隐式捕获和显式捕获等。 值捕获通过拷贝的方式捕获外部变量的值,这样Lambda表达式就可以在其余部分的执行过程中更改这些值,而不会影响原始变量。 引用捕获通过引用方式捕获外部变量的值,这样Lambda表达式会与原始变量共享同一个存储位置,Lambda表达式对变量的修改将反映到原始变量中。 隐式捕获使用编译器自动推导的方式捕获局部变量。隐式捕获可以根据Lambda表达式中访问的变量来判断它们是按值捕获还是按引用捕获。 显式捕获需要使用捕获列表显示地指定捕获方式,以及对应的外部变量。在显式捕获中,可以同时使用多种捕获方式。 Lambda表达式的捕获方式可以影响程序的性能和正确性。当Lambda表达式使用的自由变量是可变的时,引用捕获比值捕获更安全。如果捕获的外部变量在Lambda表达式执行的时候作为参数传递给其他函数,那么引用捕获可能会导致难以发现的错误,此时应该使用值捕获。 总之,Lambda表达式捕获是编程中重要的一部分。良好的捕获方式将有助于提高代码质量和性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值