C++ 11新特性 (三)_c++ 什么是让初始化行为具有确定的效果(1),物联网嵌入式开发技术类校招面试题汇总

42 阅读6分钟

和常量左值引用不同的是,右值引用还可以对右值进行修改。例如:

int && a = 10;
a = 100;
cout << a << endl;

另外值得一提的是,C++ 语法上是支持定义常量右值引用的,例如

const int&& a = 10;//编译器不会报错

学到这里,一些读者可能无法记清楚左值引用和右值引用各自可以引用左值还是右值,这里给大家一张表格,方便大家记忆:
C++11 中右值可以分为两种:一个是将亡值( xvalue, expiring value),另一个则是纯右值( prvalue, PureRvalue):

  • 纯右值:非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和 lambda 表达式等
  • 将亡值:与右值引用相关的表达式,比如,T&& 类型函数的返回值、 std::move 的返回值等。
    右值的使用:
    在 C++ 中在进行对象赋值操作的时候,很多情况下会发生对象之间的深拷贝,如果堆内存很大,这个拷贝的代价也就非常大,在某些情况下,如果想要避免对象的深拷贝,就可以使用右值引用进行性能的优化。
#include <iostream>
using namespace std;

class Test
{
public:
    Test() : m\_num(new int(100))
    {
        cout << "construct: my name is jerry" << endl;
    }

    Test(const Test& a) : m\_num(new int(\*a.m_num))
    {
        cout << "copy construct: my name is tom" << endl;
    }

    ~Test()
    {
        delete m_num;
    }

    int\* m_num;
};

Test getObj()
{
    Test t;
    return t;
}

int main()
{
    Test t = getObj();
    cout << "t.m\_num: " << \*t.m_num << endl;
    return 0;
};
construct: my name is jerry
copy construct: my name is tom
t.m_num: 100

过输出的结果可以看到调用 Test t = getObj(); 的时候调用拷贝构造函数对返回的临时对象进行了深拷贝得到了对象 t,在 getObj() 函数中创建的对象虽然进行了内存的申请操作,但是没有使用就释放掉了。如果能够使用临时对象已经申请的资源,既能节省资源,还能节省资源申请和释放的时间,如果要执行这样的操作就需要使用右值引用了,右值引用具有移动语义,移动语义可以将资源(堆、系统对象等)通过浅拷贝从一个对象转移到另一个对象这样就能减少不必要的临时对象的创建、拷贝以及销毁,可以大幅提高 C++ 应用程序的性能。

#include <iostream>
using namespace std;

class Test
{
public:
    Test() : m\_num(new int(100))
    {
        cout << "construct: my name is jerry" << endl;
    }

    Test(const Test& a) : m\_num(new int(\*a.m_num))
    {
        cout << "copy construct: my name is tom" << endl;
    }

    // 添加移动构造函数
    Test(Test&& a) : m\_num(a.m_num)
    {
        a.m_num = nullptr;
        cout << "move construct: my name is sunny" << endl;
    }

    ~Test()
    {
        delete m_num;
        cout << "destruct Test class ..." << endl;
    }

    int\* m_num;
};

Test getObj()
{
    Test t;
    return t;
}

int main()
{
    Test t = getObj();
    cout << "t.m\_num: " << \*t.m_num << endl;
    return 0;
};

construct: my name is jerry
move construct: my name is sunny
destruct Test class ...
t.m_num: 100
destruct Test class ...


通过修改,在上面的代码给 Test 类添加了移动构造函数(参数为右值引用类型),这样在进行 Test t = getObj(); 操作的时候并没有调用拷贝构造函数进行深拷贝,而是调用了移动构造函数,在这个函数中只是进行了浅拷贝,没有对临时对象进行深拷贝,提高了性能。

如果不使用移动构造,在执行 Test t = getObj() 的时候也是进行了浅拷贝,但是当临时对象被析构的时候,类成员指针 int* m_num; 指向的内存也就被析构了,对象 t 也就无法访问这块内存地址了。

在测试程序中 getObj() 的返回值就是一个将亡值,也就是说是一个右值,在进行赋值操作的时候如果 = 右边是一个右值,那么移动构造函数就会被调用。移动构造中使用了右值引用,会将临时对象中的堆内存地址的所有权转移给对象t,这块内存被成功续命,因此在t对象中还可以继续使用这块内存。

move 转移

在 C++11 添加了右值引用,并且不能使用左值初始化右值引用,如果想要使用左值初始化一个右值引用需要借助 std::move () 函数,使用std::move方法可以将左值转换为右值。使用这个函数并不能移动任何东西,而是和移动构造函数一样都具有移动语义,将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存拷贝。
从实现上讲,std::move 基本等同于一个类型转换:static_cast<T&&>(lvalue);,函数原型如下:

template<class \_Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) _NOEXCEPT
{	// forward \_Arg as movable
    return (static\_cast<remove_reference_t<_Ty>&&>(_Arg));
}

使用方法如下:

class Test
{
publicTest(){}
    ......
}
int main()
{
    Test t;
    Test && v1 = t;          // error
    Test && v2 = move(t);    // ok
    return 0;
}

在第 4 行中,使用左值初始化右值引用,因此语法是错误的。
在第 5 行中,使用 move() 函数将左值转换为了右值,这样就可以初始化右值引用了。

list<string> ls;
ls.push\_back("hello");
ls.push\_back("world");
......
list<string> ls1 = ls;        // 需要拷贝, 效率低
list<string> ls2 = move(ls);


如果不使用 std::move,拷贝的代价很大,性能较低。使用 move 几乎没有任何代价,只是转换了资源的所有权。如果一个对象内部有较大的堆内存或者动态数组时,使用 move () 就可以非常方便的进行数据所有权的转移。另外,我们也可以给类编写相应的移动构造函数(T::T(T&& another))和和具有移动语义的赋值函数(T&& T::operator=(T&& rhs)),在构造对象和赋值的时候尽可能的进行资源的重复利用,因为它们都是接收一个右值引用参数。

forward 转发

右值引用类型是独立于值的,一个右值引用作为函数参数的形参时,在函数内部转发该参数给内部其他函数时,它就变成一个左值,并不是原来的类型了。如果需要按照参数原来的类型转发到另一个函数,可以使用 C++11 提供的 std::forward () 函数,该函数实现的功能称之为完美转发。

// 函数原型
template <class T> T&& forward (typename remove\_reference<T>::type& t) noexcept;
template <class T> T&& forward (typename remove\_reference<T>::type&& t) noexcept;

// 精简之后的样子
std::forward<T>(t);

下面通过一个例子演示一下关于 forward 的使用:

#include <iostream>
using namespace std;

template<typename T>
void printValue(T& t)
{
    cout << "l-value: " << t << endl;
}

template<typename T>
void printValue(T&& t)
{
    cout << "r-value: " << t << endl;
}

template<typename T>
void testForward(T && v)
{
    printValue(v);
    printValue(move(v));
    printValue(forward<T>(v));
    cout << endl;
}

int main()
{
    testForward(520);
    int num = 1314;
    testForward(num);
    testForward(forward<int>(num));
    testForward(forward<int&>(num));
    testForward(forward<int&&>(num));

    return 0;
}

l-value: 520
r-value: 520
r-value: 520

l-value: 1314
r-value: 1314
l-value: 1314

l-value: 1314
r-value: 1314
r-value: 1314

l-value: 1314
r-value: 1314
l-value: 1314

l-value: 1314
r-value: 1314
r-value: 1314

列表初始化

关于 C++ 中的变量,数组,对象等都有不同的初始化方法,在这些繁琐的初始化方法中没有任何一种方式适用于所有的情况。为了统一初始化方式,并且让初始化行为具有确定的效果,在 C++11 中提出了列表初始化的概念。
统一的初始化:
在 C++11 中,列表初始化变得更加灵活了,来看一下下面这段初始化类对象的代码

#include <iostream>
using namespace std;

class Test
{
public:
    Test(int) {}
private:
    Test(const Test &);
};

int main(void)
{
    Test t1(520);
    Test t2 = 520; 
    Test t3 = { 520 };
    Test t4{ 520 };
    int a1 = { 1314 };
    int a2{ 1314 };
    int arr1[] = { 1, 2, 3 };
    int arr2[]{ 1, 2, 3 };
    return 0;
}

聚合体
在 C++11 中,列表初始化的使用范围被大大增强了,但是一些模糊的概念也随之而来,在前面的例子可以得知,列表初始化可以用于自定义类型的初始化,但是对于一个自定义类型,列表初始化可能有两种执行结果:

#include <iostream>
#include <string>
using namespace std;

struct T1
{
    int x;
    int y;
}a = { 123, 321 };

struct T2
{
    int x;
    int y;
    T2(int, int) : x(10), y(20) {}
}b = { 123, 321 };

int main(void)
{
    cout << "a.x: " << a.x << ", a.y: " << a.y << endl;
    cout << "b.x: " << b.x << ", b.y: " << b.y << endl;
    return 0;
}

a.x: 123, a.y: 321
b.x: 10, b.y: 20

非聚合体
对于聚合类型的类可以直接使用列表初始化进行对象的初始化,如果不满足聚合条件还想使用列表初始化其实也是可以的,需要在类的内部自定义一个构造函数, 在构造函数中使用初始化列表对类成员变量进行初始化:

#include <iostream>
#include <string>
using namespace std;

struct T1
{
    int x;
    double y;
    // 在构造函数中使用初始化列表初始化类成员
    T1(int a, double b, int c) : x(a), y(b), z(c){}
    virtual void print()
    {
        cout << "x: " << x << ", y: " << y << ", z: " << z << endl;
    }
private:
    int z;
};

int main(void)
{
    T1 t{ 520, 13.14, 1314 };	// ok, 基于构造函数使用初始化列表初始化类成员
    t.print();
    return 0;
}


另外,需要额外注意的是聚合类型的定义并非递归的,也就是说当一个类的非静态成员是非聚合类型时,这个类也可能是聚合类型,比如下面的这个例子:

#include <iostream>
#include <string>
using namespace std;

struct T1
{
    int x;
    double y;
private:
    int z;
};

struct T2
{
    T1 t1;
    long x1;
    double y1;
};

int main(void)
{
    T2 t2{ {}, 520, 13.14 };
    return 0;
}

可以看到,T1 并非一个聚合类型,因为它有一个 Private 的非静态成员。但是尽管 T2 有一个非聚合类型的非静态成员 t1,T2 依然是一个聚合类型,可以直接使用列表初始化的方式进行初始化。

最后强调一下 t2 对象的初始化过程,对于非聚合类型的成员 t1 做初始化的时候,可以直接写一对空的大括号 {},这相当于调用是 T1 的无参构造函数。

std::initializer_list
在 C++ 的 STL 容器中,可以进行任意长度的数据的初始化,使用初始化列表也只能进行固定参数的初始化,如果想要做到和 STL 一样有任意长度初始化的能力,可以使用 std::initializer_list 这个轻量级的类模板来实现。

先来介绍一下这个类模板的一些特点:

  • 它是一个轻量级的容器类型,内部定义了迭代器 iterator 等容器必须的概念,遍历时得到的迭代器是只读的。
  • 对于 std::initializer_list 而言,它可以接收任意长度的初始化列表,但是要求元素必须是同种类型 T
  • 在 std::initializer_list 内部有三个成员接口:size(), begin(), end()。
  • std::initializer_list 对象只能被整体初始化或者赋值。

std::initializer_list,使用初始化列表 { } 作为实参进行数据传递即可。

#include <iostream>
#include <string>
using namespace std;

void traversal(std::initializer_list<int> a)
{
    for (auto it = a.begin(); it != a.end(); ++it)
    {
        cout << \*it << " ";
    }
    cout << endl;
}

int main(void)
{
    initializer_list<int> list;
    cout << "current list size: " << list.size() << endl;
    traversal(list);

    list = { 1,2,3,4,5,6,7,8,9,0 };
    cout << "current list size: " << list.size() << endl;
    traversal(list);
    cout << endl;
    
    list = { 1,3,5,7,9 };


**收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。**
![img](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/a3cfcee17c294a63863071a79eced005~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5py65Zmo5a2m5Lmg5LmL5b-DQUk=:q75.awebp?rk3s=f64ab15b&x-expires=1772467343&x-signature=4StNz4gDS96tf5KsDScMQhq6v4Q%3D)
![img](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/5b48bb5e273a4de3afd638553fa58443~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5py65Zmo5a2m5Lmg5LmL5b-DQUk=:q75.awebp?rk3s=f64ab15b&x-expires=1772467343&x-signature=dVc7NmYjNl1tkB83txF%2FA47oCVE%3D)

**[如果你需要这些资料,可以戳这里获取](https://gitee.com/vip204888)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人**

**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**