C++面经(5)(牛客)

1. 说一说C++11的新特性有哪些?
C++新特性主要包括语法改进和标准库的扩充两个方面,主要包括以下11点:
语法的改进:
(1)统一的初始化方法
(2)成员变量默认初始化
(3)auto关键字用于定义变量,编译器可以自动判断类型(前提:定义一个变量时对其进行初始化)
(4)decltype求表达式类型
(5)智能指针shared_ptr
(6)空指针nullptr
(7)基于范围的for循环
(8)右值引用和move语义,让程序员有意识减少进行深拷贝操作
标准库的扩充(STL中新加一些模板类):
(9)无序容器(哈希表),用法和功能和map一摸一样,区别在于不根据键值进行排序且效率更好
(10)正则表达式,可以认为正则表达式实质上是一个字符串,该字符串描述了一种特定模式的字符串
(11)Lambda表达式

  • 统一的初始化方法
    C++98/03可以使用初始化列表(initializer list)进行初始化:
int i_arr[3] = {1, 2, 3};
long l_arr[] = {1, 3, 2, 4};
struct A
{
    int x;
    int y;
} a = {1, 2};

但是这种初始化方式的适用性非常狭窄,只有上面提到的这两种数据类型可以使用初始化列表。在C++11中,初始化列表的适用性被大大增加了。他现在可以用于任何类型对象的初始化,实例如下:

#include <iostream>
#include <vector>
#include <list>
using namespace std;

class Foo
{
public:
    Foo(int)
    {
        cout << "Base?" << endl;
    }
    Foo(const Foo &)
    {
        cout << "构造?" << endl;
    }
};
int main(void)
{
    Foo a1(123);
    Foo a2 = 123;//这里需要注意一点是,对象的赋值(=)需要调用拷贝构造函数,但是123不是该类型对象,则会再调用原始构造函数来进行构造a2
    Foo a3 = {123};//新的构造方式,虽然使用了=,但是它仍然是列表初始化
    Foo a4{123};//C98/03不具备的,在C++11中,可以直接在变量名后面加上初始化列表
    int a5 = {3};
    int a6{3};
    return 0;
}
  • 成员变量默认初始化
    好处:构建一个类的对象不需要用构造函数初始化成员变量
//程序实例
#include <iostream>
using namespace std;
class B
{
public:
    int m = 1234;
    int n = 321;
};
int main()
{
    B b;
    cout << b.m << endl;
    return 0;
}
  • auto关键字
    用于定义变量,编译器可以自动判断类型(定义一个变量时对其进行初始化)
#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<int> vec(5);
    for (vector<int>::iterator iter = vec.begin(); iter != vec.end(); ++iter)
    {
        cout << "iterator:" <<*iter << endl;
    }
    for (auto data : vec)
    {
        cout << "auto:" << data << endl;
    }
    return 0;
}
/*
打印结果:
iterator:0
iterator:0
iterator:0
iterator:0
iterator:0
auto:0
auto:0
auto:0
auto:0
auto:0
*/

不说了,auto一个字,绝~

  • decltype求表达式的类型
    decltype是C++11新增的关键字,它和auto的功能一样,都用来在编译时期进行自动类型推导。
    因为auto并不适用于所有的自动类型推导场景,在某些特殊情况下auto用起来非常不方便,甚至压根无法使用,所以decltype关键字也被引入到C++中。
    auto和decltype都可以自动推导出变量类型,但它们的用法是有区别的:
auto varname = value;
decltype(exp) varname = value;

其中,varname表示变量名,value表示赋给变量的值,exp表示一个表达式。
auto根据"=“右边的初始值value推导出变量类型,而decltype根据exp表达式推导出变量的类型,跟”="右边的value没有关系。
另外,auto要求变量必须初始化,而decltype不要求。

// decltype 用法举例
int a = 0;
decltype(a) b = 1;          // b 被推导成了int
int decltype(10.8) x = 5.5; // x 被推导成了double
double decltype(x + 100) y; // y 被推导成了double
  • 智能指针
    智能指针就是对裸指针的包装,不用担心申请内存忘记释放。
    现阶段C++标准库有四种智能指针std::
    a. auto_ptr:(C++98中就有,现在完全被unique_ptr代替)
    b. shared_ptr(C++11):共享指针,多个指针指向同一个对象,最后一个指针被销毁时,这个对象才会被销毁。
    c. weak_ptr(C++):弱指针是辅助shared_ptr指针工作的,不占计数次数,可以监视share_ptr的生命周期。
    d. unique_ptr(C++):独占式指针,只有一个指针能指向该对象。当然该对象的所有权还是可以移交出去。
    shared_ptr 和unique_ptr、weak_ptr不同之处在于,多个shared_ptr智能指针可以共同使用同一块堆内存。并且,由于该类型智能指针在实现上采用的是引用计数机制,即便有一个shared_ptr指针放弃了堆内存(引用计数减1),也不会影响其它指向同一内存的shared_ptr指针(只有引用计数为0时,堆内存才会被自动释放)
#include <iostream>
#include <memory>//智能指针的引用需要导入这个头文件
using namespace std;
int main()
{   
    //构建 2 个智能指针
    shared_ptr<int> p1(new int(10));
    shared_ptr<int> p2(p1); 
    cout << *p2 << endl;
    p1.reset(); //引用计数减 1,p1为空指针,不影响p2
    if (p1)
    {
        cout << "p1 不为空" << endl;
    }
    else
    {
        cout << "p1 为空" << endl;
    }
    cout << *p2 << endl;
    cout << p2.use_count() << endl;//判断当前和p2同指向的智能指针有多少个
    return 0;
}
/*
程序运行结果:
10
p1为空
10
1
*/
  • 空指针nullptr(原来NULL)
    nullptr是nullptr_t类型的右值常量,专用于初始化空类型指针。nullptr_t是C++11新增加的数据类型,可称为"指针空值类型"。也就是说,nullptr仅是该类型的一个实例对象(已经定义好,可以直接使用)。所以不同类型的指针变量都可以使用nullptr来进行初始化。
int *a1 = nullptr;
char *a2 = nullptr;
double *a3 = nullptr;
  • 基于范围的for循环
// 普通的for循环
for (表达式 1; 表达式 2; 表达式 3)
{
    // 循环体
}

// 基于范围的for循环
for (declaration : expression)
{
    // 循环体
}
  • 右值引用和move语义
    (1)右值引用
    C++98/03标准中就有引用,使用"&"表示,但是只能操作C++中的左值,无法对右值添加引用。
int num = 10;
int &b = num; //正确
int &c = 10; //错误

如上所示,编译器允许我们为num左值建立一个引用,但不可以为10这个右值建立引用。因此,C++98/03标准中的引用又称为左值引用。
注意一点,虽然C++98/03标准不支持为右值建立非常量左值引用,但允许使用常量左值引用右值,也就是说,常量左值引用可以引用左值也可以引用右值,但是不能修改。

int num = 10;
const int &b = num;//正确
const int &c = 10;//正确

我们知道右值没有名称,因此要使用它只能借助引用的方式。这就产生一个问题,实际开发中我们可能需要对右值进行修改(移动语义,类似于内存转让和资源窃取,比拷贝负担要小很多,通过移动构造函数来实现),显然左值引用的方式是行不通的。
为此,C++标准新引入了另一种引用方式,称为右值引用,用"&&"表示。
需要注意的是,和声明左值引用一样,右值引用也必须立即进行初始化操作,且只能使用右值进行初始化,比如:

int num = 10;
int &&a = num;	//错误,右值引用不能初始化为左值
int &&a = 10;	//正确

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

int &&a = 10;
a = 100;
cout << a << endl;
/*
运行结果:
100
*/

另外注意一点就是,C++语法上是支持定义常量右值引用:

const int &&a = 10;	//编译器不报错,但没有意义

(2)move语义
move函数功能是将某个左值强制转化为右值。其常用于实现移动语义(调用移动构造函数时,为了释放原来的内存,把这块内存给新的对象使用)。

right_arg = move(left_arg) //其中,left_arg表示指定的左值对象。该函数会返回left_arg对象的右值形式right_arg
  • 无序容器(哈希表)
    用法和功能同map一摸一样,区别在于哈希表的效率更高。
    (1)无序容器内部存储的键值对是无序的,各键值对的存储位置取决于该键值对中的键
    (2)和关联容器相比,无序容器擅长通过键查找对应的值;但对于使用迭代器遍历容器中存储的元素,无序容器的执行效率是不如关联容器的
    和关联容器一样,无序容器只是一类容器的总称,其包含有4个具体的容器:

在这里插入图片描述

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
int main()
{
    //创建并初始化一个 unordered_map 容器,其存储的 <string,string> 类型的键值对
    std::unordered_map<std::string, std::string> my_uMap{{"教程1", "www.123.com"}, {"教程2", "www.234.com"}, {"教程3", "www.345.com"}};
    if (my_uMap.find("C语言教程") != my_uMap.end())//使用前先判断一下键是否存在
    {
        string str = my_uMap.at("C语言教程"); //查找指定键对应的值,效率比关联式容器高
        cout << "str = " << str << endl;
    }
    if (my_uMap.find("教程1") != my_uMap.end())
    {
        string str = my_uMap.at("教程1");
        cout << "str = " << str << endl;
    }
    //使用迭代器遍历哈希容器,效率不如关联式容器
    for (auto iter = my_uMap.begin(); iter != my_uMap.end(); ++iter)
    {
        cout << iter->first << " " << iter->second << endl;
    }
    return 0;
}
  • 正则表达式
    正则表达式实质上是一个字符串,该字符串描述了一种特定模式的字符串。常用符号意义如下:
    在这里插入图片描述

  • Lambda匿名函数
    匿名函数简单理解就是没有名称的函数,又称为lambda函数或者lambda表达式。
    lambda匿名函数很简单,可以套用如下的语法格式 :
    [外部变量访问方式说明符](参数)mutable noexcept/throw() ->返回值类型 {//函数体}
    其中各部分的含义分别为:
    a. [外部变量方位方式说明符]
    []方括号用于向编译器说明当前是一个lambda表达式,其不能被省略。在方括号内部,可以注明当前lambda函数的函数体可以使用哪些"外部变量"。所谓外部变量,指的是和当前lambda表达式位于同一作用域内的所有局部变量。
    b.(参数)
    和普通函数定义一样,lambda匿名函数也可以接收外部传递的多个参数。和普通函数不同的是,如果不需要传递参数,可以连同()小括号一起省略。
    c. mutable
    此关键字可以省略,如果使用则之前的小括号不能省略(参数个数可以为0)。默认情况下(省略mutable),对于以值传递方式引入的外部变量,不允许在lambda表达式内部修改它们的值(可以理解为这部分变量都是const变量)。如果想要修改它们,就必须使用mutable关键字。
    注意:对于以值传递引入的外部变量,lambda表达式修改的是拷贝的那一份,并不会修改真正的外部变量;
    d.noexcept/throw()
    可以省略,如果使用,在之前的小括号不能省略。默认情况下,lambda函数的函数体中可以抛出任何类型的异常。而标注noexcept关键字,则表示函数体内不会抛出任何异常;使用throw()可以指定lambda函数可以抛出的异常类型。
    e.->返回值类型
    指明lambda匿名函数的返回值类型。值得一提的是,如果lambda函数体内只有一个return语句,或者该函数返回void,则编译器可以自行推断出返回值类型,此情况下可以省略->返回值类型。
    f.函数体
    和普通函数一样,lambda匿名函数的内部代码都放置在函数体中。该函数体内除了可以使用指定传递进来的参数之外,还可以使用指定的外部变量以及全局范围内的所有变量。

#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
    int num[4] = {4, 2, 3, 1};
    //对 a 数组中的元素进行排序
    sort(num, num + 4, [=](int x, int y) -> bool { return x < y; });//等号表示采用值传递,后面是cmp函数的写法
    for (int n : num)
    {
        cout << n << " ";
    }
    return 0;
}
/*
1 2 3 4
*/

2. 说说C++中智能指针和指针的区别?

  • 智能指针
    如果在程序中使用new从堆(自由存储区)分配内存,等到不需要时,应使用delete将其自动释放。C++引用了智能指针,以帮助自动完成这个过程。随后为了更好的编程体验,C++11摒弃了auto_ptr,并新增了三种智能指针:unique_ptr,shared_ptr和weak_ptr。
  • 指针
    C语言提出的指针概念,C语言规定所有变量在使用前必须先定义,指定其类型,并按此分配内存单元。指针变量不同于整型变量和其它类型的变量,它是专门用来存放地址的,所以必须将它定义为“指针类型”。

智能指针和普通指针的区别在于智能指针实际上是对普通指针加了一层封装机制,区别是它负责自动释放所指对象,这样一层封装机制的目的为了使得智能指针可以方便的管理一个对象的生命周期。

3. 简述C++右值引用和转移语义?

  • 右值引用
    一般来说,能取地址的表达式就是右值引用,能取地址的是左值。
class A{};
A r = A();		//左值定义
A &r = A();		// error,A()是无名变量,是右值,无法通过这种方式取地址
A &&r = A();	//改成了右值引用,合法
  • 转移语义
    move()函数是将某个左值强制转化为右值,搭配右值引用使用实现移动语义。移动语义,类似于内存转让和资源窃取,比拷贝负担要小很多,通过移动构造函数来实现。

4. 简述一下C++11中四种类型转换?
C++中四种类型转换分别是const_cast、static_cast,dynamic_cast、reinterpret_cast,四种转换功能如下:
a. const_cast
将const变量转为非const变量
b. static_cast
最常用,可以用于各种隐式转换,比如非const转const,static_cast可以用于类向上转换,向下转换可以成功但是不安全。
c. dynamic_cast
只能用于含有虚函数的类转换,用于类向上和向下转换。
向上转换:指子类向基类转换
向下转换:指基类向子类转换
这两种转换,子类包含父类,父类转换为子类都可能出现非法内存访问问题。
d. reinterpret_cast
可以做任何类型的转换,不过不对转换结果保证,容易出现问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值