面试题(7)|C/C++基础(5):C++11新特性

目录

1.关键字及新语法

1.1 auto关键字及用法

1.2 nullptr关键字及用法

1.3 for循环语法

1.4 初始化列表

1.5 请你说说 auto 和 decltype 如何使用

2.STL容器

2.1 std::array

2.2 std::forward_list

2.3 std::unordered_map和std::unordered_multimap

2.4 std::unordered_set和std::unordered_multiset

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 委托构造

5.2 继承构造

6. 泛型编程

6.1 可变参模板

7.其他

7.1 std::function,std::bind封装可执行对象

7.2 lambda表达式

Lambda 表达式语法:

编译器实现 Lambda 表达式大致分为一下几个步骤:

7.3 正则表达式

7.4 右值引用

参考文献


 更多参见:C++面试题系列

github总结:

GitHub - AnthonyCalandra/modern-cpp-features: A cheatsheet of modern C++ language and library features.

 GitHub - changkun/modern-cpp-tutorial: 📚 Modern C++ Tutorial: C++11/14/17/20 On the Fly | https://changkun.de/modern-cpp/

C++11中的可变参数模板、右值引用和lambda这几个新特性。

1.关键字及新语法

1.1 auto关键字及用法

编译器根据初始值自动推导出变量类型。但是不能用于函数传参和数组类型推导?

for(auto itr = vec.cnegin(); itr != vec.cend(); ++itr);

1.2 nullptr关键字及用法

nullptr是一种特殊类型的字面值,可以转换成任意的指针类型;null是宏定义为0,在函数重载的时候会发生错误。

C++中NULL和nullptr的区别

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

参见:C++面试题系列:智能指针

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. 右值引用的使用场景

右值引用可以实现移动语义、完美转发。

参考文献

C++11常用新特性快速一览

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

haimianjie2012

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值