lunix555 / Cpp-interview-tips

主要是一些C++常用知识总结
1 stars 0 forks source link

C++11新特性篇 #7

Open lunix555 opened 2 years ago

lunix555 commented 2 years ago

image

lunix555 commented 2 years ago

auto & decltype

关于C++11新特性,最先提到的肯定是类型推导,C++11引入了auto和decltype关键字,使用他们可以在编译期就推导出变量或者表达式的类型,方便开发者编码也简化了代码。

auto:让编译器在编译器就推导出变量的类型,可以通过=右边的类型推导出变量的类型。

auto a = 10; // 10是int型,可以自动推导出a是int decltype:相对于auto用于推导变量类型,而decltype则用于推导表达式类型,这里只用于编译器分析表达式的类型,表达式实际不会进行运算。

cont int &i = 1; int a = 2; decltype(i) b = 2; // b是const int&

lunix555 commented 2 years ago

左值右值

众所周知C++11新增了右值引用,这里涉及到很多概念:

左值:可以取地址并且有名字的东西就是左值。

右值:不能取地址的没有名字的东西就是右值。

纯右值:运算表达式产生的临时变量、不和对象关联的原始字面量、非引用返回的临时变量、lambda表达式等都是纯右值。

将亡值:可以理解为即将要销毁的值。

左值引用:对左值进行引用的类型。

右值引用:对右值进行引用的类型。

移动语义(std::move):转移资源所有权,类似于转让或者资源窃取的意思,对于那块资源,转为自己所拥有,别人不再拥有也不会再使用。

完美转发(std:forward):可以写一个接受任意实参的函数模板,并转发到其它函数,目标函数会收到与转发函数完全相同的实参。

返回值优化:当函数需要返回一个对象实例时候,就会创建一个临时对象并通过复制构造函数将目标对象复制到临时对象,这里有复制构造函数和析构函数会被多余的调用到,有代价,而通过返回值优化,C++标准允许省略调用这些复制构造函数。

lunix555 commented 2 years ago

列表初始化

C++98中{}的初始化问题 在C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定。

int array1[] = {1,2,3,4,5}; int array2[5] = {0}; 对于一些自定义的类型,却无法使用这样的初始化。

vector v{1,2,3,4,5};//C++98无法编译 就无法通过编译,导致每次定义vector时,都需要先把vector定义出来,然后使用循环对其赋初始值,非常不方便。 C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。

在C++11中可以直接在变量名后面加上初始化列表来进行对象的初始化。

struct A { public: A(int) {} private: A(const A&) {} }; int main() { A a(123); A b = 123; // error A c = { 123 }; A d{123}; // c++11

int e = {123}; int f{123}; // c++11

return 0; }

列表初始化的好处

方便,且基本上可以替代括号初始化

可以使用初始化列表接受任意长度

可以防止类型窄化,避免精度丢失的隐式类型转换

什么是类型窄化,列表初始化通过禁止下列转换,对隐式转化加以限制:

从浮点类型到整数类型的转换

从 long double 到 double 或 float 的转换,以及从 double 到 float 的转换,除非源是常量表达式且不发生溢出

从整数类型到浮点类型的转换,除非源是其值能完全存储于目标类型的常量表达式

从整数或无作用域枚举类型到不能表示原类型所有值的整数类型的转换,除非源是其值能完全存储于目标类型的常量表达式

lunix555 commented 2 years ago

模板的改进 C++11关于模板有一些细节的改进:

模板的右尖括号

C++11之前是不允许两个右尖括号出现的,会被认为是右移操作符,所以需要中间加个空格进行分割,避免发生编译错误。

int main() { std::vector<std::vector> a; // error std::vector<std::vector > b; // ok }

模板的别名

C++11引入了using,可以轻松的定义别名,而不是使用繁琐的typedef。

typedef std::vector<std::vector> vvi; // before c++11 using vvi = std::vector<std::vector>; // c++11

函数模板的默认模板参数

C++11之前只有类模板支持默认模板参数,函数模板是不支持默认模板参数的,C++11后都支持。

template <typename T, typename U=int> class A { T value;
};

template // error class A { T value;
}; 类模板的默认模板参数必须从右往左定义,而函数模板则没有这个限制。

template <typename R, typename U=int> R func1(U val) { return val; }

template R func2(U val) { return val; }

int main() { cout << func1<int, double>(99.9) << endl; // 99 cout << func1<double, double>(99.9) << endl; // 99.9 cout << func1(99.9) << endl; // 99.9 cout << func1(99.9) << endl; // 99 cout << func2<int, double>(99.9) << endl; // 99 cout << func1<double, double>(99.9) << endl; // 99.9 cout << func2(99.9) << endl; // 99.9 cout << func2(99.9) << endl; // 99 return 0; } 对于函数模板,参数的填充顺序是从左到右的。

lunix555 commented 2 years ago

并发

c++11关于并发引入了好多好东西,有:

std::thread相关

std::mutex相关

std::lock相关

std::atomic相关

std::call_once相关

volatile相关

std::condition_variable相关

std::future相关

async相关

lunix555 commented 2 years ago

std:move介绍下,什么是左值右值,传递个对象会什么样?连续执行两次move会怎么样

取地址的、有名字的就是左值。左值的生存期长,可以作为赋值的对象。 右值指临时对象,只在当前语句有效,右值又可以细分为纯右值、将亡值。纯右值指的是临时变量和不跟对象关联的字面量值;将亡值则是 C++11 新增的跟右值引用相关的表达式,这样表达式通常是将要被移动的对象(移为他用)。如std::move 的返回值。将亡值可以理解为通过“盗取”其他变量内存空间的方式获取到的值

std::move()将左值强制转换为右值。

单独单写设计阻塞队列,不用锁怎么实现?

使用一个全局变量进行标志

lunix555 commented 2 years ago

std::function和std::bind

std::function 是一个可调用对象包装器,是一个类模板,可以容纳除了类成员函数指针之外的所有可调用对象,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟它们的执行。 定义格式:std::function<函数类型>。 std::function可以取代函数指针的作用,因为它可以延迟函数的执行,特别适合作为回调函数使用。它比普通函数指针更加的灵活和便利。

可将std::bind函数看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。 std::bind将可调用对象与其参数一起进行绑定,绑定后的结果可以使用std::function保存。 概括:设置可调用对象(函数)的某些参数为可变变量,将某些参数设置为固定值,然后将整个调用对象返回成std::funciton类型

lunix555 commented 2 years ago

std::array

array为在栈空间内申请的数组,逻辑上和物理上都是连续的,且因为栈空间的特性访问、复制效率较高,可以直接将一个array数组复制到另一个array数组。 对比之下,vector是在堆空间申请的内存,在逻辑上连续,物理上不连续,所以在复制的时候只能分各个元素赋值(用new和malloc申请的空间为非连续空间,具体的原因和windows页式分配空间有联系)。

lunix555 commented 2 years ago

lambda表达式

为一个匿名函数,支持在函数体内声明,其形式一般为: [ capture list ] ( parameter list) -> return type { function body } capture list,捕获列表,是一个lambda所在函数中定义的局部变量的列表。lambda函数体中可以使用这些局部变量。捕获可以分为按值捕获和按引用捕获。非局部变量,如静态变量、全局变量等可以不经捕获,直接使用;

parameter list,参数列表。从C++14开始,支持默认参数,并且参数列表中如果使用auto的话,该lambda称为泛化lambda(generic lambda);

return type,返回类型,这里使用了返回值类型尾序语法(trailing return type synax)。可以省略,这种情况下根据lambda函数体中的return语句推断出返回类型,就像普通函数使用decltype(auto)推导返回值类型一样;如果函数体中没有return,则返回类型为void。

function body,与任何普通函数一样,表示函数体。

Lambda表达式可以忽略参数列表和返回类型,但必须包含捕获列表和函数体。


auto f = [] { return 42; }
cout << f() << endl;

默认情况下,对于一个按值捕获的变量,lambda不能改变其值。如果希望能改变这个被捕获的变量的值,就必须在参数列表之后加上关键字mutable,因此,可变lambda不能省略参数列表:

int v1 = 42;
auto f=[v1] () mutable {return ++v1;};
v1=0;
auto j = f(); //j is 43

一个引用捕获的变量是否可以修改依赖于此引用指向的是一个const类型还是一个非const类型:

int v1 = 42;
auto f=[&v1] () {return ++v1;};
v1=0;
auto j = f(); //j is 1