目录
2.3 std::unordered_map和std::unordered_multimap
2.4 std::unordered_set和std::unordered_multiset
7.1 std::function,std::bind封装可执行对象
更多参见:C++面试题系列
github总结:
C++11中的可变参数模板、右值引用和lambda这几个新特性。
1.关键字及新语法
1.1 auto关键字及用法
编译器根据初始值自动推导出变量类型。但是不能用于函数传参和数组类型推导?
for(auto itr = vec.cnegin(); itr != vec.cend(); ++itr);
1.2 nullptr关键字及用法
nullptr是一种特殊类型的字面值,可以转换成任意的指针类型;null是宏定义为0,在函数重载的时候会发生错误。
1.3 for循环语法
std::vector<int> arr(5,100);
//&启用了引用
for(auto &i:arr){
std::cout<<i<<std::endl;
}
1.4 初始化列表
用初始化列表对类型进行初始化。initializer_list、初始化列表、列表初始化
1.5 请你说说 auto 和 decltype 如何使用
标准回答
C++11 提供了多种简化声明的功能,尤其在使用模板时。
1. auto
实现自动类型推断,要求进行显示初始化,让编译器能够将变量的类型设置为初始值的类型:
auto a = 12;
auto pt = &a;
double fm(double a, int b)
{
return a + b;
}
auto pf = fm;
简化模板声明
for(std::initializer_list<double>::iterator p = il.begin(); p != il.end(); p++)
for(auto p = il.begin(); p != il.end(); p++)
2. decltype
decltype 将变量的类型声明为表达式指定的类型。
decltype(expression) var;
decltype(x) y; // 让y的类型与x相同,x是一个表达式
double x;
int n;
decltype(x*n) q;
decltype(&x) pd;
template<typename t="">
void ef(T t, U u)
{
decltype(T*U) tu;
}
2.STL容器
2.1 std::array
std::array保存在栈内存中,std::vector保存在堆内存中,比vector具有更高的性能。
std::array会在编译的时候创建一个固定大小的数组,不能隐式转换为指针,定义时需要指定大小和类型。
void foo(int *p,int len){
return ;
}
std::array<int 4> arr={1,2,3,4};
//C风格接口传参
//foo(arr,arr.size()); //非法,无法隐式转换
foo(&arr[0],arr.size());
foo(arr.data(),arr.size());
//使用'std::sort'
std::sort(arr.begin(),arr.end());
2.2 std::forward_list
std::forward_list是单向链表;std::list是双向链表。
插入删除时,时间复杂度为o(1),不支持随机访问。与std::list占用更少空间,具有更高空间利用率。
2.3 std::unordered_map和std::unordered_multimap
无序容器,元素不进行排序,内部通过哈希表实现,插入和搜索元素的平均复杂度为O(constant)。
std::map: 存储唯一的键, 默认从小到大排序
std::mulitmap: 可存储重复的键 , 默认从小到大排序
std::unordered_map: 用散列函数排序,键唯一
std::unordered_multimap:用散列函数排序,键可重复
2.4 std::unordered_set和std::unordered_multiset
无序容器,元素不进行排序,内部通过哈希表实现,插入和搜索元素的平均复杂度为O(constant)。
2.5 std::tuple
3.多线程
3.1 std::thread
3.2 std::atomic
3.3 std::condition_variable
4.智能指针内存管理
4.1 std::shared_ptr
4.2 std::weak_ptr
5.面向对象
5.1 委托构造
C++11引入了委托构造的概念,使得构造函数在同一个类中可以调用另一个构造函数,从而简化代码。
class Base{
public:
int value1;
int value2;
Base(){
value1=1;
}
Base(int value):Base(){//委托Base()构造函数
value2=2;
}
}
5.2 继承构造
在继承体系中,如果派生类想要使用基类的构造函数,需要在构造函数中显示声明。
假若基类拥有为数众多的不同版本的构造函数,在派生类中得写很多对应的“透传”构造函数。
struct A{
A(int i){}
A(double d,int i){}
A(float f,int i,const char* c){}
//....等等系列的构造函数版本
};
struct B:A{
B(int i):A(i){}
B(double d,int i):A(d,i){}
B(float f,int i,const char* c):A(f,i,e){}
//....等等好多个和基类构造函数对应的构造函数
};
C++11的继承构造
struct A{
A(int i){}
A(double d,int i){}
A(float f,int i,const char* c){}
//....等等系列的构造函数版本
};
struct B:A{
using A::A;
//关于基类各构造函数的继承一句话搞定
//.....
};
如果一个继承构造函数不被相关的代码使用,编译器不会为之产生真正的函数代码,这样比“透传”基类各个构造函数更加节省目标代码空间。
6. 泛型编程
6.1 可变参模板
简述一下 C++11 中的可变参数模板新特性
得分点 概念、语法、模板参数包、展开参数包
标准回答
在 C++11 之前,类模板和函数模板只能含有固定数量的模板参数。
C++11 增强了模板功能,它对参数进行了高度泛化,允许模板定义中包含 0 到任意个、任意类型的模板参数,这就是可变参数模板。
可变参数模板的加入使得 C++11 的功能变得更加强大,能够很有效的提升灵活性。
1. 可变参数函数模板语法:
template<typename... t="">
void fun(T...args)
{
// 函数体
}
模板参数中, typename(或者 class)后跟 ... 就表明 T 是一个可变模板参数,它可以接收多种数据类型,又称模板参数包。
fun() 函数中,args 参数的类型用 T... 表示,表示 args 参数可以接收任意个参数,又称函数参数包。
2. 可变参数类模板语法:
template <typename... types=""> class test;
3. 展开参数包的方式
- 可变参数函数模板可以采用递归方式、逗号表达式 + 初始化列表的方式展开参数包;
- 可变参数类模板可以采用递归+继承的方式展开参数包。
加分回答
C++ 11 标准提供的 tuple 元组类就是一个典型的可变参数模板类,它的定义如下:
template <typename... types="">
class tuple;</typename...></typename...></typename...>
7.其他
7.1 std::function,std::bind封装可执行对象
7.2 lambda表达式
请你说说 C++ Lambda 表达式用法及实现原理
标准回答
Lambda 表达式语法:
[外部变量访问方式说明符] (参数) mutable noexcept/throw() -> 返回值类型 { 函数体; };
其中各部分的含义分别为:
1. [外部变量方位方式说明符]
[ ] 方括号用于向编译器表明当前是一个Lambda 表达式,其不能被省略。
在方括号内部,可以注明当前 Lambda 函数的函数体中可以使用哪些“外部变量”。所谓外部变量,指的是和当前 lambda 表达式位于同一作用域内的所有局部变量。
[外部变量]的定义方式: 外部变量格式:功能
[] :空方括号表示当前 lambda 匿名函数中不导入任何外部变量。
[=]:只有一个 = 等号,表示以值传递的方式导入所有外部变量;
[&]:只有一个 & 符号,表示以引用传递的方式导入所有外部变量;
[val1,val2,...] :表示以值传递的方式导入 val1、val2 等指定的外部变量,同时多个变量之间没有先后次序;
[&val1,&val2,...]:表示以引用传递的方式导入 val1、val2等指定的外部变量,多个变量之间没有前后次序;
[val,&val2,...] :以上 2 种方式还可以混合使用,变量之间没有前后次序。
[=,&val1,...]:表示除 val1 以引用传递的方式导入外,其它外部变量都以值传递的方式导入。
[this] : 表示以值传递的方式导入当前的 this 指针。
2. (参数)
和普通函数的定义一样,Lambda 匿名函数也可以接收外部传递的多个参数。和普通函数不同的是,如果不需要传递参数,可以连同 () 小括号一起省略;
3. mutable
此关键字可以省略,如果使用则之前的 () 小括号将不能省略(参数个数可以为 0)。默认情况下,对于以值传递方式引入的外部变量,不允许在 Lambda 表达式内部修改它们的值(可以理解为这部分变量都是 const 常量)。而如果想修改它们,就必须使用 mutable 关键字。对于以值传递方式引入的外部变量,Lambda 表达式修改的是拷贝的那一份,并不会修改真正的外部变量。
4. noexcept/throw()
可以省略,如果使用,在之前的 () 小括号将不能省略(参数个数可以为 0)。默认情况下,Lambda 函数的函数体中可以抛出任何类型的异常。
而标注 noexcept 关键字,则表示函数体内不会抛出任何异常;
使用 throw() 可以指定 Lambda 函数内部可以抛出的异常类型。
5. -> 返回值类型
指明 Lambda 匿名函数的返回值类型。
如果 Lambda 函数体内只有一个 return 语句,或者该函数返回 void,则编译器可以自行推断出返回值类型,此情况下可以直接省略 -> 返回值类型。
6. 函数体
和普通函数一样,Lambda 匿名函数包含的内部代码都放置在函数体中。该函数体内除了可以使用指定传递进来的参数之外,还可以使用指定的外部变量以及全局范围内的所有全局变量。
编译器实现 Lambda 表达式大致分为一下几个步骤:
1. 创建一个未命名的类,实现构造函数,使用 Lambda 表达式的函数体重载 operator()(所以 Lambda 表达式 也叫匿名函数对象)
2. 创建未命名的类的对象
3. 通过对象调用 operator()
[lambda表达式](https://blog.csdn.net/qq_37085158/article/details/124626913)
7.3 正则表达式
7.4 右值引用
请你说说左值、右值、左值引用、右值引用、右值引用的使用场景
标准回答
1. 左值
在 C++ 中可以取地址的、有名字的就是左值
int a = 10; // 其中 a 就是左值
2. 右值 不能取地址的、没有名字的就是右值
int a = 10; // 其中 10 就是右值右值
3. 左值引用
左值引用就是对一个左值进行引用。
传统的 C++ 引用(现在称为左值引用)使得标识符关联到左值。左值是一个表示数据的表达式(如变量名或解除引用的指针),程序可获取其地址。
最初,左值可出现在赋值语句的左边,但修饰符 const 的出现使得可以声明这样的标识符,即不能给它赋值,但可获取其地址:
int n;
int * pt = new int;
const int b = 101;
int & rn = n;
int & rt = *pt;
const int & rb = b;
const int & rb = 10;
4. 右值引用
右值引用就是对一个右值进行引用。
C++ 11 新增了右值引用(rvalue reference),这种引用可指向右值(即可出现在赋值表达式右边的值),但不能对其应用地址运算符。
右值包括字面常量(C-风格字符串除外,它表示地址)、诸如 x + y 等表达式以及返回值的函数(条件是该函数返回的不是引用),右值引用使用 && 声明: int x = 10; int y = 23; int && r1 = 13; int && r2 = x + y; double && r3 = std::sqrt(2.0);
5. 右值引用的使用场景
右值引用可以实现移动语义、完美转发。