lunix555 / Cpp-interview-tips

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

C++基础知识(98/03篇) #1

Open lunix555 opened 2 years ago

lunix555 commented 2 years ago

1、c/c++内存模型

C++ 内存分为栈、堆、数据段、BSS段、代码段、映射区 栈是存放函数的局部变量和函数参数等,由编译器自动分配和释放。堆是动态分配的内存空间,必须由程序员手动申请和释放。数据段是存储程序中已经初始化的全局变量和静态变量。BSS段是存储未初始化的全局变量和静态变量,以及所有被初始化为0的全局变量和静态变量。代码段是存放代码,函数,以及字符串常量的地方。映射区存储了动态链接库以及调用mmap函数进行的文件映射

2、内联函数是什么,是不是只要声明为内联函数就一定会在原地展开

在函数声明或定义前加inline关键字,则该函数被称为内联函数。c++编译器会直接将调用内联函数的地方直接展开成相对应的指令,没有了函数栈帧的开销,从而提高了程序的运行效率。 不是,当代码很长,或者循环递归不会展开。

3、编译的四个过程

预处理阶段:引入头文件,宏替换,删除注释…,生成以.i为结尾的预编译文件 编译阶段:检查语义语法规错误,如果没有错误则将代码解释为汇编汇编,生成以.s为结尾的汇编文件 汇编阶段: 将汇编代码解释为二进制的cpu指令,生成.o为结尾的可重定向目标文件 链接阶段: 将多个目标文件及所需要的库连接成最终的可执行目标文件

4、c++和c的区别

C是面向过程的语言,而C++是面向对象的语言 c++具有封装、继承和多态三种特性 C和C++动态管理内存的方法不一样,C是使用malloc/free函数,而C++除此之外还有new/delete关键字 C++支持函数重载,而C不支持函数重载

5、指针和引用有什么区别

引用必须在定义时初始化,指针没有要求。 引用在初始化引用一个实体后,不能更改引用实体而指针可以。 没有NULL引用,但是有NULL指针 有多级指针,但是没有多级引用 访问实体时,指针需要解引用,而引用是编译器自行处理 引用比指针用起来相对安全 引用自加自减都是实体值的改变,而指针自加自减是地址的偏移 sizeof求的是指针的大小,4个字节或者8个字节;而sizeof求引用变量时求实体变量的大小

6、什么是内存泄漏,如何防止内存泄漏,如何判断内存泄漏

内存泄漏是指由于疏忽或错误造成了程序未能释放掉不再使用的内存。

养成良好的编码规范,申请的内存空间记着匹配的去释放内存 使用内存泄漏工具检测 采用RAII思想或者智能指针来管理资源 在linux环境下可以使用内存泄漏检测工具Valgrind,我们写代码是可以添加内存申请和释放的统计功能,统计当前申请和释放的内存是否一致。

7、malloc/free和new/delete的区别

malloc和free是函数,new和delete是操作符 malloc的返回值为void*, 在使用时必须强转;而new后跟的是空间的类型,就不需要强转 malloc申请空间失败时,返回的是NULL,而new申请空间失败时,会抛异常 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理

new和new[]的区别在于new[]在申请自定义类时会多次调用构造函数,delete[]同理

8、什么是左值和右值

一般情况下,左值指的是既能出现在赋值等号左边,也能出现在赋值等号的右边,并且可以进行取地址的变量。右值指的是只能出现在赋值等号右边的值,且不能进行取地址的变量。在C++中右值有常量、匿名变量和将亡值。其他的都是左值

9、什么是野指针,野指针处理方式

野指针就是指向一个已删除的对象或者指向一个未申请并且访问受限的内存区域的指针

定义指针时未初始化 释放指针后未置指针为NULL 指针的操作超越了变量作用域 处理方式:

初始化指针时置指针为NULL 释放指针后置指针为NULL

10、函数重载

函数重载是用来描述同名函数具有相同或者相似功能,在同一作用域中,当两个函数的函数名相同但是参数列表不同(个数,类型),这两个函数就形成了函数重载

11、const关键字的作用

1、const修饰的局部变量或者全局变量声明时必须初始化,且该变量只能读不能写 2、const修饰指针时,如果const在 号的左边,则修饰的是指针指向的内容不能变,const在 号右边时,则修饰的是指针,表示指针的指向不能发送改变。如果 *号左边和右边都有const,则表示指针指向的内容和指针本身都是不可变的 3、const修饰的成员函数不能修改对象的成员变量 4、const修饰的函数参数在函数体内不能被修改

12、static关键字的作用

1、未初始化的静态变量的值默认为0 2、修饰全局变量或者函数时,则该变量和函数只能在当前文件可见 3、修饰局部变量时,变量的生命周期随程序,只有程序结束,变量的生命周期才结束 4、static修饰的成员函数没有this指针,不能调用非静态成员函数和非静态变量,只能通过类名来访问 5、static修饰的成员变量在整个类中只有一份,被所有对象共享,必须在类外初始化,也是只能通过类名来访问

13、内存对齐的原因

第一个原因是不是所有的硬件平台都可以访问任意地址上的任意数据,某些平台只能在特定的地址上获得特定的数据,否则就会抛异常;第二个原因是使用内存对齐,可以高CPU访问内存的效率 修改默认对齐数:#pragma pack(n)

14、什么是指针

指针就是一种变量的类型,定义一个指针变量,这个变量中可以存放一块内存空间的地址,通过这个地址可以访问这块内存空间。

15、联合体和结构体的区别

结构体和联合体都是由多个不同的类型变量组成的,但是联合体所有成员共用一块内存,而结构体每个成员都有各自的内存。给联合体的不同变量赋值, 会覆盖掉其他变量的值,而给结构体的不同变量赋值是互不影响的。

16、数组和指针的区别

数组是保存多个同类型数据的集合;而指针是一个变量,用来保存其他变量的内存地址的 sizeof数组是求数组中全部元素占内存空间的大小;而sizeof指针求的是指针的大小,不是4个字节就是8个字节 当使用数组作为函数参数时会退化为指针

17、C++中拷贝赋值函数的形式能否进行值传递

不能。如果是进行值传递,调用拷贝构造函数时,首先要将实参传给形参,这个传递的时候也要调用拷贝构造函数,如此循环会造成无限递归,无法完成拷贝

18、纯虚函数的作用

为了方便使用多态特性,我们会在基类中定义纯虚函数。但在很多情况下,基类本身生成对象是不合情理的。(例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。)如果将函数定义为纯虚函数,则编译器要求在派生类中必须进行重写来实现多态性。同时含有纯虚函数的类称为抽象类,它不能生成实例化生成对象

19、为什么析构函数必须是虚函数,而C++默认不是虚函数

将父类的析构函数设置为虚函数,可以保证我们使用父类指针指向一个子类对象时,释放父类指针的同时也可以释放子类的空间,防止内存泄漏。而C++默认析构函数不是虚函数,因为虚函数需要额外的虚函数表的虚表指针,占用额外的内存。对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。

20、C++函数栈空间的最大值

默认为1M,可以调整

21、vector和list的区别

区别: 1、vector底层是通过数组实现的;list底层是通过链表来实现的 2、vector支持随机访问;list不支持随机访问 3、vector迭代器是原生指针;list迭代器是封装链表结点的一个类 4、vector在插入和删除时可能会导致迭代器失效;list只在删除的时候会导致迭代器的失效 5、vector不容易造成内存碎片,空间利用率高;list容易造成内存碎片,空间利用率低 6、vector在非尾插尾删的时间复杂度都为O(n),list在任何地方插入和删除的时间复杂度都为O(1) 使用场景: 如果频繁地随机访问,且不关心插入删除效率,就使用vector;具有大量插入和删除操作,而不关心随机访问,就是用list

22、迭代器和指针的区别

迭代器不是指针,而是类模板。它模拟了指针的一些功能,通过重载了指针的一些操作符,封装了指针,提供了比指针更高级的行为

23、说一说STL迭代器是怎么删除元素的

对于vector,deque来说,删除元素后,后边的每个元素的迭代器都会失效,但是后边的每个元素都会向前移动一个位置。返回的是下一个有效的迭代器 对于list来说,它使用了不连续的内存,删除元素后会返回下一个有效的迭代器 对于关联容器map,set来说,删除元素后,当前元素迭代器失效,但是其结构是红黑树,删除当前元素不会影响到下一个元素的迭代器,所以调用erase之前,记录下一个元素的迭代器即可

24、C++中的struct和class区别

都可以定义类,都可以被继承,区别在于struct的默认访问权和默认继承权都是公有的,而class的默认访问权和默认继承权都是私有的

25、哪些成员变量必须在初始化列表初始化

引用、const、没有默认构造函数的自定义成员变量

26、include头文件的“”和<>的区别

对于双引号包含的头文件,查找头文件默认为当前头文件目录下查找。而尖括号查找头文件默认是编译器设置的头文件路径下查找

27、什么时候会发生段错误

非法访问内存地址。例如使用野指针、试图修改字符串常量的内容

28、栈和堆的区别

栈是由高地址向低地址扩展,而堆是由低地址向高地址扩展 堆中的内存需要手动申请和释放,而栈是操作系统自动申请和释放 堆中频繁调用malloc和new,容易产生内存碎片,而栈不会产生内存碎片 堆的分配效率低,而栈的分配效率高

29、变量的声明和定义有什么区别

变量的声明是不会分配地址的,而变量的定义时才会为变量分配地址内存 一个变量可以在多个地方声明,但是只能在一个地方定义 关键字extern是声明一个变量,表示该变量在其他地方已经定义好了

30、sizeof和strlen的区别

sizeof是一个操作符,strlen是库函数 sizeof的参数可以是数据的类型,也可以是变量,而strlen的参数只能是以\0结尾的字符串 sizeof计算的是数据对象占内存的大小,而strlen计算的是字符串的实际长度

31、说说面向对象的三大特性

面向对象的三大特性为封装、继承和多态 封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来与对象进行交互 继承:继承是面向对象程序设计使代码复用的重要手段,它允许程序员在保持原有类的特性的基础上进行扩展,增加功能,产生新的一个类,被继承的类称为基类,继承基类的类称为派生类 多态:多态分为静态多态和动态多态,静态多态主要是重载,在编译的时候就已经确定了;动态多态主要是通过虚函数实现的,在运行期间动态绑定。例如当父类指针指向一个子类对象时,此时的父类指针调用子类重写父类的虚函数时,会去调用子类重写过的虚函数

lunix555 commented 2 years ago

32、实现一个string类

class String { public: String(const char* str = "") { assert(str != nullptr); _str = new char[strlen(str) + 1]; strcpy(_str, str); }

String(const String& s)
    :_str(nullptr)
{
    String tmp(s._str);
    swap(_str, tmp._str);

}

String(String&& s)
    :_str(s._str)
{
    s._str = nullptr;
}

String& operator=(String&& s)
{
    if (this != &s)
    {
        delete[] _str;
        _str = s._str;
        s._str = nullptr;
    }
    return *this;
}

String& operator=(String s)
{
    if (this != &s)
    {
        swap(_str, s._str);
    }
    return *this;
}

~String()
{
    if (_str != nullptr)
    {
        delete[] _str;
        _str = nullptr;
    }
}

private: char* _str; };

lunix555 commented 2 years ago

33、重写、重载和隐藏的区别

函数重载:同一作用域下同名函数具有不同的参数列表,且不关心返回值 函数隐藏:在父类和子类中,只要函数名相同,不管参数列表和返回值是否相同,就会产生函数隐藏 重写覆盖:在父类和子类中,当父类中存在虚函数,子类中存在函数与父类的虚函数同名,且返回值和参数列表都相同,则就产生重写

34、说说你对虚函数的理解

虚函数最主要的目的就是为了实现类的多态。在有虚函数的类中,类的前4个字节用来保存虚表指针,这个虚表指针指向一个虚表,这个虚表是在数据段中的,表中存放的是虚函数的地址,而实际的虚函数是在代码段中的。当父类指针指向子类对象时,会调用子类重写过的虚函数来实现多态

35、构造函数可以是虚函数吗

构造函数是不能是虚函数的,虚函数地址存放在虚表中的,要找到虚表就必须通过虚表指针找到,但是虚表指针是存在对象中的。如果构造函数是虚的,就需要通过虚表指针来调用,可是对象还没有实例化,就没有虚表指针,就找不到构造函数,也就不能完整对象的实例化。所以构造函数不能是虚函数

lunix555 commented 2 years ago

36、深浅拷贝的区别及实现一个深拷贝

浅拷贝是按字节拷贝,我们默认不写编译器生成的拷贝构造就是浅拷贝,当我们类中存在资源时,如果调用拷贝构造,此时两个对象都共用了一份资源,各自调用析构时会产生二次释放的问题;深拷贝是重新申请一个新的空间分配给新拷贝的对象,两个对象各自使用不同内存空间的资源,调用析构时就不存在二次释放的问题

class String { public: //构造函数 String(const char str = "") { //str不能为空 assert(str != nullptr); //开辟一个与传入的字符串的长度 + 1的空间 +1是保存 \0 _str = new char[strlen(str) + 1]; //将传入的字符串内容拷贝到当前类的内容中 strcpy(_str, str); } String(const String& s) //先给新的对象开辟空间 :_str(new char[strlen(s._str) + 1]) { //再将拷贝的内容拷贝到新的对象中 strcpy(_str, s._str); } private: char _str; };

lunix555 commented 2 years ago

37、实现一个vector类

底层实现是数组,采用动态空间,数据结构上就是线性连续的地址空间,用三个迭代器,其中_Myfirst和_Mylast指向连续空间中已使用的范围,用_Myend指向整个连续地址空间的末尾 vector容量永远大于或等于其大小,一旦容量等于大小那么就是满载,如果在加入元素,那么需要整个vector搬家

template class Vector { public: typedef T iterator; typedef const T const_iterator; Vector() :_start(nullptr) ,_finish(nullptr) ,_endOfStorage(nullptr) {} //n个val的构造函数 Vector(int n, const T& val = T()) :_start(new T[n]) ,_finish(_start +n) ,_endOfStorage(_finish) { for (int i = 0; i < n; ++i) { _start[i] = val; } } //通过迭代器产生的构造函数 template Vector(InputIterator first, InputIterator last) :_start(nullptr) , _finish(nullptr) , _endOfStorage(nullptr) { while (first != last) { pushBack(first); ++first; } } Vector(const Vector& v) { //深拷贝--->开空间--->拷贝 _start = new T[v.Capacity()]; for (size_t i = 0; i < v.Size(); i++) { _start[i] = v[i]; } _finish = _start + v.Size(); _endOfStorage= _start + v.Capacity(); } Vector operator=(Vector v) { Swap(v); return this; }

void Swap(Vector<T>& v)
{
    swap(_start, v._start);
    swap(_finish, v._finish);
    swap(_endOfStorge, v._endOfStorge);
}
~Vector()
{
    if (_start)
    {
        delete[] _start;
        _start = _finish = _endOfStorage = nullptr;
    }
}

private: iterator _start; iterator _finish; iterator _endOfStorage; };

lunix555 commented 2 years ago

38、请你说说C++中智能指针如何防止内存泄漏的

智能指针主要管理对上分配的内存空间,将一个普通的指针封装为一个栈对象。当栈对象的生命周期结束时,会调用它的析构函数释放普通指针的内存,从而防止内存泄漏

39、请你介绍一下C++中四种智能指针的实现原理

auto_ptr、unique_ptr和shared_ptr都是通过RAII的思想来实现的,其中auto_ptr在进行赋值拷贝时会将资源进行转移,是一种有缺陷的智能指针;而unique是在auto_ptr的基础上,直接将拷贝构造和赋值运算符重载函数设置为删除函数,不允许外部进行赋值拷贝操作;而shared_ptr是C++11中最常用的智能指针,它采用引用计数的方法,记录当前资源被多少个智能指针引用。当新增一个引用时计数器会+1,当减少一个引用时计数器会-1。只有计数器为0时,智能指针才会释放内存资源。但是shared_ptr会造成循环引用的问题。第四种指针也就是weak_ptr就是来解决这种问题的,weak_ptr不能单独使用,只能搭配shared_ptr一起使用

40、请你回答智能指针存在内存泄漏的情况

当两个对象互相使用一个shared_ptr成员变量指向对方时,会造成循环引用,使引用计数器失效,从而造成内存泄漏。为了解决这个问题,引入了weak_ptr弱指针,weak_ptr不会修改引用计数器的值,也不会对对象的内存进行管理

lunix555 commented 2 years ago

41、简单实现一个shared_ptr智能指针

template class Share_ptr { public: Share_ptr(T ptr) :_ptr = ptr ,_cntPtr(new size_t(1)) ,_mtx(new mutex) {} T& operator() { return _ptr; } T operator->() { return _ptr; } ~Share_ptr() { if (subCnt() == 0) { if (_ptr != nullptr) { delete _ptr; delete _cntPtr; delete _mtx; _ptr = nullptr; _cntPtr = nullptr; _mtx = nullptr; } } }

Share_ptr(Share_ptr<T>& sp)
    :_ptr(sp._ptr)
    ,_cntPtr(sp._cntPtr)
    ,_mtx(sp._mtx)
{
    addCnt();
}

Share_ptr<T>& operator=(Share_ptr<T>& sp)
{
    if (_ptr != sp._ptr)
    {
        if (subCnt() == 0)
        {
            delete _ptr;
            delete _cntPtr;
            delete _mtx;
        }
        _ptr = sp._cntPtr;
        _cnt = sp._cntPtr;
        _mtx = sp._mtx;

        addCnt();
    }

    return *this;
}

void addCnt()
{
    _mtx.lock();
    ++(*_cntPtr);
    _mtx->unlock();
}

size_t subCnt()
{
    _mtx->lock();
    --(*_cntPtr);
    _mtx->unlock();
    return *_cntPtr;
}

private: T _ptr; size_t _cntPtr; mutex* _mtx; };

lunix555 commented 2 years ago

42、右值引用的作用

1、可以实现移动语义,减少拷贝次数来提高代码运行效率 2、给中间临时变量取名字 3、实现完美转发

43、说一说C++中四种cast类型转换

C++中四种cast类型转化分别有:static_cast、reinterpret_cast、const_cast、dynamic_cast。相较于C语言的转换可以进行错误检查,且转换明确。 static_cast主要用于隐式类型转换,可以用于多态中子类对象转换成父类对象,也可以将父类对象转换成子类对象,但是不安全;(与C语言的强制类型装类似,可能会损失精度) const_cast主要是将const变量转换为非const变量; dynamic_cast只能用于存储在虚函数的类中,主要用于多态中父类对象的指针或引用转换为子类对象中的指针或引用,如果转换不成功会返回空或者抛异常; reinterpret_cast主要是将一种类型转换为另一种不同的类型,但有可能会出现问题,是底层二进制的强制拷贝,不会损失精度

44、使用const定义常量比#define好的原因

1、定义的const常量有类型检查 2、define宏是在预处理阶段展开,,而const定义的变量时在编译运行阶段使用,便于调试

45、extern和volatile的作用

当extern用在一个变量前面,表示声明一个变量,该变量在其他地方已经定义过了,直接使用就行。 还有一个作用是extern ‘C’ ,表示指定代码用C的编译规则来进行编译 volatile是用于修饰一个变量,保持变量的内存可见性,防止编译器的过度优化和(或)多线程相关的特殊属性。如果没有volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。

推荐一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:

  1). 并行设备的硬件寄存器(如:状态寄存器)

  2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)

  3). 多线程应用中被几个任务共享的变量

46、malloc、calloc、realloc的区别

mallo和calloc都是申请一块新的空间,但是malloc申请的空间都没有进行初始化,而calloc申请的空间都初始化为0。realloc是在原有空间的基础上进行扩充,虽然其参数中有要扩展的空间,最好在用指针来接收这块扩展了的空间。

lunix555 commented 2 years ago

47、map和unorder_map的区别

1.需要引入的头文件不同

map: #include < map > unordered_map: #include < unordered_map >

2.内部实现机理不同 map:内部实现了一个红黑树(红黑树是非严格平衡二叉搜索树,而AVL是严格平衡二叉搜索树),红黑树具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素。因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行的操作。map中的元素是按照二叉搜索树(又名二叉查找树、二叉排序树,特点就是左子树上所有节点的键值都小于根节点的键值,右子树所有节点的键值都大于根节点的键值)存储的,使用中序遍历可将键值按照从小到大遍历出来。 unordered_map: 内部实现了一个哈希表(也叫散列表,通过把关键码值映射到Hash表中一个位置来访问记录,查找的时间复杂度可达到O(1),其在海量数据处理中有着广泛应用)。因此,其元素的排列顺序是无序的。

3.优缺点以及适用处 map:

优点:

有序性,这是map结构最大的优点,其元素的有序性在很多应用中都会简化很多的操作; 红黑树,内部实现一个红黑树使得map的很多操作在lgn的时间复杂度下就可以实现,因此效率非常的高; 缺点: 空间占用率高,因为map内部实现了红黑树,虽然提高了运行效率,但是因为每一个节点都需要额外保存父节点、孩子节点和红/黑性质,使得每一个节点都占用大量的空间;平均查找复杂度(O(log(n)))

适用处:对于那些有顺序要求的问题,用map会更高效一些

unordered_map:

优点: 因为内部实现了哈希表,因此其查找速度非常的快;(最快O(1),最慢O(n)) 缺点: 哈希表的建立比较耗费时间; 适用处:对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用unordered_map。 总结:

内存占有率的问题就转化成红黑树 VS hash表 , 还是unorder_map占用的内存要高; 但是unordered_map执行效率要比map高很多; 对于unordered_map或unordered_set容器,其遍历顺序与创建该容器时输入的顺序不一定相同,因为遍历是按照哈希表从前往后依次遍历的。

lunix555 commented 2 years ago

48、STL

主要由以下部分组成:

容器: 迭代器: 算法: 仿函数: 适配器: 空间配置器:

lunix555 commented 2 years ago

49、resize和reserve的区别

resize是重置容器内元素的个数 reserve是重置容器的容量大小

50、c++类成员的访问权限

public:这一类成员可以被成员函数、外部对象和派生类访问 protected:外部不可访问,只有派生类成员函数和本类成员函数可以访问 private:只有类内部成员函数可以访问 继承方式

公有继承

所有protected和public可以被派生类继承而来,访问权限不变

保护继承

所有protected和public可以被派生类继承而来,访问权限变为protected

私有继承

所有protected和public可以被派生类继承而来,访问权限变为private

50、各数据类型占的内存

image

lunix555 commented 2 years ago

51、一个参数既可以是const还可以是volatile吗?解释为什么。

是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

52、一个指针可以是volatile 吗?解释为什么。

是的。尽管这并不很常见。一个例子是当一个中断服务子程序修改一个指向一个buffer的指针时。

53、下面的函数被用来计算某个整数的平方,它能实现预期设计目标吗?如果不能,试回答存在什么问题:

  int square(volatile int *ptr)

  {

  return ptr *ptr;

  }

  下面是答案: 这段代码是个恶作剧。这段代码的目的是用来返指针ptr指向值的平方,但是,由于ptr指向一个volatile型参数,编译器将产生类似下面的代码:

  int square(volatile int *ptr)

  {

  int a,b;

  a = *ptr;

  b = *ptr;

  return a * b;

  }

  由于*ptr的值可能在两次取值语句之间发生改变,因此a和b可能是不同的。结果,这段代码可能返回的不是你所期望的平方值!正确的代码如下:

  long square(volatile int *ptr)

  {

  int a;

  a = *ptr;

  return a * a;

  }

lunix555 commented 2 years ago

Pairs为一个结构体,这样它的所有成员都是public的。可以更加方便的进行访问。

namespace std { template <typename T1, typename T2> struct pair { //member T1 first; T2 second; } }

操作:

image

make_pair函数定义

make_pair是使用最多的一个函数,通过该函数我们可以直接返回一个pairs。

如下为c++11之前的make_pair函数定义。 namespace std { template <template T1, template T2> pair<T1, T2> make_pair(const T1& x, const T2& y) { return pair<T1, T2>(x, y); } }

并且由于该函数使用了函数模板,所在不需要指定参数类型。 因此对于 std::pair<int, char>(4, 'a') 可以写成 std::make_pair(4, 'a')。

c++11的make_pair函数定义则引入了move的概念,其定义如下 namespace std { template <template T1, template T2> pair<T1, T2> make_pair(const T1&& x, const T2&& y) { return pair<T1, T2>(x, y); } }

这里我们可以通过使用std::move()来强制使用move语义 std::string s, t; ... auto p = std::make_pair(std::move(s), std::move(t)); ...

std::ref()来强制使用ref语义 std::int i, j; ... auto p = std::make_pair(std::ref(i), std::ref(j)); ++p.first; ++i; std::cout << "i = " << i << std::endl; // i = 2 ...

lunix555 commented 2 years ago

Tuple

相对于 pairs 来说,tuples 可以有多个元素。

  1. 定义 namespace std { template //c++11 表示多个参数模板 class tuple { public: explicit tuple(const Types&...); //显示构造函数 template explicit tuple(UTypes&&...); //显示构造函数, move语义, 隐式的类型转换 ... } }

从定义我们可以看出,tuple可以接受任意多个参数,支持move语义和隐式类型转换。并且需要被显示的构造。 image