1.static的使用
1)修饰局部变量:在函数内部使用static修饰局部变量,会使它成为静态局部变量。静态局部变量只会被初始化一次,且只有在第一次调用该函数时才会被初始化,之后每次调用该函数时都会保留上一次的值.从原来的栈区存放改为全局区。
2)修饰全局变量:在函数外部使用static修饰全局变量,会使它成为静态全局变量。静态全局变量的作用域仅限于当前文件,即只能在当前文件内被访问,不能被其他文件访问。
3)修饰成员变量:在类中使用static修饰成员变量,会使它成为静态成员变量。静态成员变量的生命周期与程序的生命周期相同,即它只会被初始化一次,且存在于所有类对象之外。静态成员变量在类内部声明,在类外部需要进行定义和初始化。
4)修饰成员函数:在类中使用static修饰成员函数,会使它成为静态成员函数。静态成员函数不依赖于任何类对象,只能访问类的静态成员变量和静态成员函数,不能访问类的非静态成员变量和非静态成员函数。静态成员函数通过类名来访问。
2.const的使用
1)修饰变量:变量的值不能改变
2)修饰指针:左定值(常量指针)右定向(指针常量)
3)修饰成员变量:const修饰成员变量,只在某个对象的生命周期中是常量,因为类可以创建多个对象,不同的对象其const数据成员值可以不同。所以不能在类的声明中初始化const数据成员,因为类的对象还没有创建时,编译器还不知道const数据成员值是多少。所以只能在类的构造函数的初始化列表中初始化。
4)修饰成员函数:可以使用类中的所有成员变量,但是不能改变他们的值。
3.指针常量,常量指针,常指针常量
1)指针常量:可以改变指针指向的值,但是不能改变指针的指向
2)常量指针:可以改变指针的指向,但是不能改变指针指向的值
3)常指针常量:指针的指向和指向的值都不能改变
4.指针和引用的异同
相同点:都可以对变量进行改变
不同点:
1)指针本质是一个变量,需要分配内存,引用是给一个对象起别名,不需要分配内存
2)引用在定义时必须初始化,而指针可以不用初始化
3)指针初始化可以为空,但是引用必须是一个已有对象的引用
4)指针和引用的自增运算结果不同。指针是指向下一个地址,而引用是引用的变量值+1
5)Sizeof不同,指针得到的是指针的大小,引用得到的是引用变量的大小。
5.如何理解多态
定义:同一操作作用于不同的对象,产生不同的执行结果。C++多态意味着当调用虚成员函数时,会根据调用类型对象的实际类型执行不同的操作。
实现:通过虚函数实现,用virtual声明的成员函数就是虚函数,允许子类重写。声明基类的指针或者引用指向不同的子类对象,调用相应的虚函数,可以根据指针或引用指向的子类的不同从而执行不同的操作。
Overload(重载):函数名相同,参数类型或顺序不同的函数构成重载。
Override(重写):派生类覆盖基类用virtual声明的成员函数。
Overwrite(隐藏):派生类的函数屏蔽了与其同名的基类函数。派生类的函数与基类函数同名,但是参数不同,隐藏基类函数。如果参数相同,但是基类没有virtual关键字,基类函数将被隐藏。
6.虚函数表
多态是由虚函数实现的,而虚函数主要是通过虚函数表实现的。如果一个类中包含虚函数,那么这个类就会包含一张虚函数表,虚函数表存储的每一项是一个虚函数的地址。该类的每个对象都会包含一个虚指针(虚指针存在于对象实例地址的最前面,保证虚函数表有最高的性能),虚指针指向虚函数表。注意:对象不包含虚函数表,只有虚指针,类才包含虚函数表,派生类会生成一个兼容基类的虚函数表。
7.常用数据结构
vector:向量,连续存储,可随机访问。
deque:双向队列,连续存储,随机访问。
list:链表,内存不连续,不支持随机访问。
stack:栈,不可随机访问,只允许再开头增加/删除元素。
queue:单向队列,尾部增加,开头删除。
set:集合,采用红黑树实现,可随机访问。查找、插入、删除时间复杂度为O(logn)。
map:图,采用红黑树实现,可随机访问。查找、插入、删除时间复杂度为O(logn)。
hash_set:哈希表,随机访问。查找、插入、删除时间复杂读为O(1)。
8.TCP
9.C和C++的区别
C语言是面向过程的,C++是C的超集,继承并扩展了C语言,是面向对象的
10. struct 和class 有什么区别?
1)struct的成员默认是public属性的,class的成员默认是private属性的
2)struct继承默认是public属性的,class继承默认是private属性的
3)“class”这个关键字还用于定义模板参数,就像“typename”。但关键字“struct”不用于定义模板参数
11.extern "C"的作用
extern "C" 的主要作用就是为了能够正确实现C++代码调用其它C语言代码。加上extern "C"后,会提示编译器这部分代码按C语言(而不是C++)的方式进行编译。
12.C++重载、重写、多态
重载:位于同一个类中,函数名字相同,形参列表不同
重写:被重写的函数必须是virtual,一般用于子类在继承父类是,重写父类中的方法,函数名称和参数列表都相同
多态:要有继承,要有虚函数重写,父类指针或引用指向子类对象。
13.多态的理解
多态:就是多种形态,C++的多态分为静态多态和动态多态。静态多态就是重载,因为在编译器决议确定,所以成为静态多态。动态多态即运行时多态是通过继承重写基类的虚函数实现的多态,因为在运行时决议确定,所以称为动态多态,也叫运行时多态。
14.对虚函数机制的理解,单继承、多继承、虚继承条件下虚函数表的结构
单继承:一个子类只有一个直接父类时称这个继承关系为单继承。将基类虚表中的内容拷贝一份到子类虚表中,如果派生类重写了基类某个虚函数,就用派生类自己的虚函数替换掉原先基类虚函数的入口地址。
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承。多重继承会有多个虚函数表,几重继承就会有几个虚函数表。这些表按照派生的顺序一次排列,如果子类改写了父类的虚函数,那么就会用子类自己的虚函数覆盖虚函数表相应的位置,如果子类有新的虚函数,那么就添加到第一个虚函数表的末尾。
虚继承:C++使用虚拟继承,解决从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题,将共同基类设置为虚基类。这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。
15.如果虚函数是有效的,那为什么不把所有函数设为虚函数?
虚函数是有代价的,由于每个虚函数的对象都要维护一个虚函数表,因此在使用虚函数的时候会产生一定的系统开销,这是没有必要的。另外,虚函数的调用相对于普通函数要更慢一些,因为每次都要查找虚函数表,有一定的时间开销。
16.为什么要虚继承?
非虚继承时,显然D会继承两次A ,内部就会存储两份A的数据,浪费空间,而且还会有二义性,D调用A的方法时,由于有两个A,究竟调用哪个A的方法呢,编译器也不知道,就会报错,所以有了虚继承,解决了空间浪费以及二义性问题。在虚继承下,只有一个共享的基类对象被继承,而无论该基类在派生层次中出现多少次。共享的基类子对象被称为虚基类,在虚继承下,基类对象的复制以及由此引起的二义性被消除了。
17.构造函数为什么一般不定义为虚函数、析构函数一般定义为虚函数
1.构造函数不可以定义为虚函数:虚函数的调用需要通过虚函数指针指向虚函数表,虽然虚函数表在编译时就有,但虚函数指针在创建对象之后才有,因此无法调用虚构造函数
2.析构函数一般定义为虚函数:在动态多态时,子类继承父类,子类重写父类的虚析构函数,父类指针或引用指向子类,这样就可以通过父类指针调用子类的析构函数释放子类中开辟的内存。
18.为什么虚函数表中有两个析构函数
这是因为对象有两种构造方式,栈构造和堆构造,所以在对应的实现上,对象也有两种析构方法,其中堆上的对象和栈上的对象的析构不同之处在于,栈内存的析构不需要执行delete函数,会自动被回收。
19.为什么基类的析构函数要是虚函数
因为如果不设置成虚函数,析构的过程中只会调用到基类的析构函数而不会调用子类的析构函数,可能会产生内存泄漏。
20. volatile 关键字的作用?什么时候需要使用volatile 关键字
volatile关键字告诉编译器其修饰的变量是易变的,它会确保修饰的变量每次读操作都从内存里读取,每次写操作都将值写到内存里。volatile关键字就是给编译器做个提示,告诉编译器不要对修饰的变量做过度的优化,提示编译器该变量的值可能会以其它形式被改变。
volatile用于读写操作不可以被优化掉的内存,用于特种内存中。
(volatile 意思是易变的,是一种类型修饰符,在C/C++中用来阻止编译器因误认某段代码无法被代码本身所改变,而造成的过度优化。编译器每次读取 volatile 定义的变量时,都从内存地址处重新取值。
这里就有点疑问了,难道编译器取变量的值不是从内存处取吗?
并不全是,编译器有时候会从寄存器处取变量的值,而不是每次都从内存中取。因为编译器认为变量并没有变化,所以认为寄存器里的值是最新的,另外,通常来说,访问寄存器比访问内存要快很多,编译器通常为了效率,可能会读取寄存器中的变量。但是,变量在内存中的值可能会被其它元素修改,比如:硬件或其它线程等。)
21.各种强制类型转换的原理及使用
C++中有四个强制类型转换的关键字
1.static_cast:用于数据类型的强制转换,强制将一种数据类型转换为另一种数据类型。
用法:static_cast <类型说明符> (变量或表达式)
int a = 10;
int b = 3;
double result = static_cast<double>(a) / static_cast<double>(b);
它主要有如下几种用法:
(1)用于类层次结构中基类和派生类之间指针或引用的转换
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的
进行下行转换(把基类的指针或引用转换为派生类表示),由于没有动态类型检查,所以是不安全的
(2)用于基本数据类型之间的转换,如把int转换成char。这种转换的安全也要开发人员来保证
(3)把空指针转换成目标类型的空指针
(4)把任何类型的表达式转换为void类型
注意:static_cast不能转换掉expression的const、volitale或者__unaligned属性。
static_cast:可以实现C++中内置基本数据类型之间的相互转换。
2.const_cast:用于去除指向常量对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用
用法:const_cast<type_id> (expression)
该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。
常量指针被转化成非常量指针,并且仍然指向原来的对象;
常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。
const int a = 10;
const int * p = &a;
int *q;
q = const_cast<int *>(p);
*q = 20; //fine
3.reinterpret_cast:reinterpret_cast主要有三种强制转换用途:改变指针或引用的类型、将指针或引用转换为一个足够长度的整形、将整型转换为指针或引用类型。
用法:reinterpret_cast<type_id> (expression)
type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。
它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。
4.dynamic_cast:
用法:dynamic_cast<type_id> (expression)
(1)其他三种都是编译时完成的,dynamic_cast是运行时处理的,运行时要进行类型检查。
(2)不能用于内置的基本数据类型的强制转换。
(3)dynamic_cast转换如果成功的话返回的是指向类的指针或引用,转换失败的话则会返回NULL。
(4)使用dynamic_cast进行转换的,基类中一定要有虚函数,否则编译不通过。
dynamic_cast用于类继承层次间的指针或引用转换。主要还是用于执行“安全的向下转型(safe downcasting)”,
也即是基类对象的指针或引用转换为同一继承层次的其他指针或引用。
至于“先上转型”(即派生类指针或引用类型转换为其基类类型),本身就是安全的,尽管可以使用dynamic_cast进行转换,但这是没必要的, 普通的转换已经可以达到目的,毕竟使用dynamic_cast是需要开销的。
22.什么时候用指针,什么时候用引用
对于那些函数,它们只使用传递过来的值,而不对值进行修改
1)如果数据对象很小,如内置数据类型或小型结构,使用按值传递
2)如果数据对象是数组,则使用指向const的指针
3)如果数据对象是较大的结构,则使用const指针或者const引用,以提高程序的效率
4)如果数据对象是类对象,则使用const引用
对于那些函数,它们需要修改传递过来的值
1)如果数据对象很小,如内置数据类型或小型结构,则使用指针
2)如果数据对象是数组,则只能使用指针
3)如果数据对象是较大的结构,则使用指针或者引用,以提高程序的效率
4)如果数据对象是类对象,则使用引用
23.一般什么情况下会出现内存泄漏?怎么用C++在编码层面尽量避免内存泄漏。
内存泄漏是指程序向系统申请分配内存使用(new),用完却没有归还(delete),结果申请的那块内存程序不能使用,而系统也无法再将它分配给需要的程序。
如何避免:
1.将基类的析构函数设为虚函数
2.new和delete配对使用
3.使用智能指针
24.对面向对象的理解
C++面向对象的三大特性是封装、继承、多态。
C++面向对象编程就是把一切事物都变成一个个对象,用属性和方法来描述对象的信息,比如定义一个猫对象,猫的眼睛、毛发、嘴巴就可以定义为猫对象的属性,猫的叫声和走路就可以定义为猫对象的方法。
25.内存分区
代码区:存放函数体的二进制代码,由操作系统进行管理
全局区(静态区):存放全局变量,静态变量和常量
栈区:存放函数参数、局部变量等
堆区:由程序员进行手动分配和释放,若程序员不释放,在程序结束时有操作系统进行回收。
26.纯虚函数
纯虚函数是只有声明没有实现的虚函数,包含纯虚函数的类是抽象类,不能被实例化,只有实现了这个纯虚函数的子类才能生成对象。
27.深拷贝和浅拷贝
浅拷贝是对对象指针的复制,原对象和副本指向的是相同资源
深拷贝是开辟一片新的内存空间,将原对象的资源复制到新的空间,返回新空间的地址
深拷贝可以避免重复释放和写冲突。
28.悬挂指针和野指针、空指针、void指针
悬挂指针:当指针所指向的对象被释放后,指针的指向没有改变,还是指向被回收的内存地址
野指针:未初始化的指针
空指针:使用null或者nullptr初始化,不指向任何对象
Void指针:一种特殊类型的指针,可以存放任意对象的地址,任意类型的指针可以指向void指针,反之不行,void指针需要强制类型转换才能赋值给其他指针
29.new和malloc有什么区别
1.new和delete是c++的关键字/运算符,malloc和free是c++/c语言的标准函数
2.malloc需要显示地指定分配的内存大小,new不需要
3.new操作符从自由存储区上为对象动态分配内存空间,而malloc函数从堆上分配内存。注:凡是通过new操作符进行内存申请,该内存即为自由存储区
4.new操作符内存分配成功时,返回对象类型,无需进行类型转换,故new是符合类型安全的操作符,malloc返回void*,需要通过强制类型转换将void*指针转换成我们需要的类型
5.new操作符内存分配失败时,抛出bad_alloc异常,malloc内存分配失败时返回NULL
6.new操作符有构造和析构函数,在开辟空间的同时,会调用自定义对象的构造函数来完成初始化,malloc只会开辟空间
7.new相对malloc效率要低,因为new的底层封装了malloc
30.malloc的内存可以用delete释放吗
可以,但是一般不这么用。malloc和free是C语言中的函数,C++为了兼容C语言保留下来这一对函数。简单来说,new可以理解为,先执行malloc来申请内存,后调用构造函数来初始化对象,delete是先执行析构函数,后使用free来释放内存。若先new再使用free来释放空间的话,可能会出现一些错误。而先使用malloc,再使用delete的话没有问题。
31.malloc出来20字节内存,为什么free不需要传入20呢,不会产生内存泄漏吗?
因为不能保证程序员使用free时传入的参数是和malloc一致的,从而导致内存泄漏等问题。现在free的解决方式是让free函数自己确定要释放多少内存,可以使用的方式是在申请内存时多申请一些空间来存储内存大小,在free时再获取这个大小进行释放。
32.new[]和delete[]一定要配对使用吗?new[]和delete[]为何要配对使用?
1.不一定,当类型为int、float等内置类型时,可以不配对使用,但是建议还是配对使用。
2.new[]为一个数组申请内存时,编译器还会悄悄地在内存中保存一个整数,用来表示数组中元素的个数。因为在delete一块内存时,我们不仅要知道指针指向多大的内存,更重要的是要知道指针指向的数组中对象的个数。因为只有知道了对象数量才能一一调用它们的析构函数,完成对数组中所有对象的清理。如果使用的是delete,则编译器只会将指针所指的对象当作单个对象来处理。所以对于数组,需要使用delete[]来处理,符号[]会告诉编译器在delete这块内存时,先去获取保存的那个元素数量值,然后再进行一一清理。
33.基类的析构函数一般写成虚函数的原因
在析构指向子类的父类指针时,根据虚函数表找到子类的析构函数,从而正确释放子类对象的资源
34.什么情况下会调用拷贝构造函数
1)一个对象以值传递的方式传入函数体
2)一个对象以值传递的方式从函数返回
3)一个对象需要另一个对象进行初始化
35.为什么拷贝构造函数必须是引用传递而不是值传递
当一个对象需要以值方式传递时,编译器会生成代码调用它的拷贝构造函数以生成一个复本。如果类A的拷贝构造函数是以值方式传递一个类A对象作为参数的话,当需要调用类A的拷贝构造函数时,需要以值方式传进一个A的对象作为实参; 而以值方式传递需要调用类A的拷贝构造函数;结果就是调用类A的拷贝构造函数导致又一次调用类A的拷贝构造函数,这就是一个无限递归。
36.指针传递和引用传递的区别
指针传递的是变量“地址”,引用传的是变量“别名”。区别就是指针传递的时候是新声明了一个指针(指向相同地址)然后传进来,而引用则是将变量本身传进来(只不过换了个名字)。都可以对原值进行访问和修改,无需创建副本。
37.成员初始化列表的概念,为什么使用成员初始化列表会快一些
成员初始化列表就是在类或者构造函数中,在参数列表后以冒号为开头,逗号为分隔的一系列初始化字段
快:因为使用成员初始化列表进行初始化的话,会直接调用传入参数的拷贝构造函数进行初始化,省去了一次执行传入参数的默认构造函数的过程。
38.静态链接和动态链接
静态链接:在链接阶段,将源文件中用到的库函数与汇编生成的目标文件合并生成可执行文件;
优点:方便移植,执行速度快
缺点:占内存,每次更新都要重新链接
动态链接:把调用的函数所在的文件模块和调用函数所在文件的位置等信息链接进目标程序,程序在运行时再从文件模块中寻找相应的代码。
优点:应用灵活,不占内存
缺点:速度不如静态链接快
39.虚函数的实现原理
1)带有虚函数的类,编译器会为其分配一个虚函数表(用数组实现),里面记录了虚函数的地址,当此类被继承时,若子类重写了虚函数,则在子类的虚函数表中覆盖父类的虚函数地址,否则继承父类的虚函数地址。
2)实例化后,对象有一个虚函数指针,虚函数指针指向虚函数表,在程序运行时,通过虚函数指针找到虚函数表中对应的函数地址,调用虚函数
40.指针函数和函数指针
指针函数:就是一个返回指针的函数,其本质是一个函数,而该函数的返回值是一个指针。
int *fun(int x,int y);
函数指针:其本质是一个指针变量,该指针指向这个函数。总结来说,函数指针就是指向函数的指针。
int (*fun)(int x,int y);
41.内存对齐
为什么要进行内存对齐?比如结构体的成员可以有不同的数据类型,所占的大小也不一样。同时,由于CPU读取数据是按块读取的,内存对齐可以使得CPU一次就可以将所需的数据读进来
对齐规则:第一个数据成员放在偏移量为0的地方,以后每个数据成员的起始位置都要从自身大小的整数倍开始存储
内存对齐的好处:1.加快内存读取效率2.便于在不同平台之间移植
内存对齐的缺点:空间浪费
c++11
1.了解auto 和decltype 吗?
auto:可以让编译器在编译时就推导出变量的类型
decltype:用于推导表达式类型
int func() { return 0; };
decltype(func()) i; // i是int
int x = 0;
decltype(x) y; // y是int
decltype(x + y) z; // z是int
2.谈一谈你对左值和右值的了解,了解左值引用和右值引用吗?
左值:在内存中有确定存储地址、有变量名、表达式结束依然存在的值。
左值引用:绑定到左值的引用,通过&来获得左值引用。
右值:在内存中没有确定存储位置、没有变量名,表达式结束就会销毁的值。
右值引用:绑定到右值的引用,通过&&来获得右值引用。
int a1 = 10; // 非常量左值
const int a2 = 10; // 常量左值
int& b1 = a1; // 非常量左值引用
const int& b2 = a2; // 常量左值引用
int&& c1 = 10; // 非常量右值引用
const int&& c2 = 10; // 常量右值引用,10是非常量右值
3.了解移动语义和完美转发吗?
移动语义:可以理解为转移所有权,拷贝是对于别人的资源,自己重新分配一块内存存储复制过来的资源,而对于移动语义,类似于转让或者资源窃取的意思,对于那块资源,转为自己所拥有,别人不再拥有也不会再使用,通过C++11新增的移动语义可以省去很多拷贝负担,如何利用移动语义,主要通过移动构造函数。
完美转发:指可以写一个接受任意实参的函数模板,并转发到其它函数,目标函数会收到与转发函数完全相同的实参。转发函数实参是左值那目标函数实参也是左值,转发函数实参是右值那目标函数也是右值。
4.平时会用到function、bind、lambda 吗,都什么场景下会用到?
std::function是C++11标准库中提供的一种可调用对象的通用类型,它可以存储任意可调用对象,如函数指针,函数对象,成员函数指针和lambda表达式。std::function类模板是一个类似于函数指针的类型,但它是可以处理任意可调用对象的,并且可以检查调用对象是否为空。
基本语法:
std::function<return_type(parameter_types)> var_name;
int func(int x, int y) { return x + y; }
std::function<int(int, int)> f = func;
class A {
public:
int mem_func(int x) { return x * x; }
};
std::function<int(A*, int)> f2 = &A::mem_func;
std::function对象可以像普通函数一样调用,并且可以使用bool类型的运算符来检查调用对象是否为空。
std::function<int(int, int)> f;
if (f)
std::cout << f(1, 2) << std::endl;
else
std::cout << "f is empty" << std::endl;
std::bind的头文件是 <functional>,它是一个函数适配器,接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。
std::bind主要有以下两个作用:
- 将可调用对象和其参数绑定成一个防函数;
- 只绑定部分参数,减少可调用对象传入的参数。
1.std::bind绑定普通函数
double callableFunc (double x, double y) {return x/y;}
auto NewCallable = std::bind (callableFunc, std::placeholders::_1,2);
std::cout << NewCallable (10) << '\n';
1.bind的第一个参数是函数名,普通函数做实参时,会隐式转换成函数指针。因此std::bind(callableFunc,_1,2)等价于std::bind (&callableFunc,_1,2);
2._1表示占位符,位于<functional>中,std::placeholders::_1;
3.第一个参数被占位符占用,表示这个参数以调用时传入的参数为准,在这里调用NewCallable时,给它传入了10,其实就想到于调用callableFunc(10,2);
2.std::bind绑定一个成员函数
class Base
{
public:
void display_sum(int a1, int a2)
{
std::cout << a1 + a2 << '\n';
}
int m_data = 30;
};
int main()
{
Base base;
auto newiFunc = std::bind(&Base::display_sum, &base, 100, std::placeholders::_1);
f(20); // should out put 120.
}
1.bind绑定类成员函数时,第一个参数表示对象的成员函数的指针,第二个参数表示对象的地址。
2.必须显式地指定&Base::diplay_sum,因为编译器不会将对象的成员函数隐式转换成函数指针,所以必须在Base::display_sum前添加&;
3.使用对象成员函数的指针时,必须要知道该指针属于哪个对象,因此第二个参数为对象的地址 &base;
3.绑定一个引用参数
5.enum 和enum class 有什么区别?
枚举作用域是指枚举类型成员名字的作用域,起自其声明之处,终止枚举定义结束之处。enum与class enum区别在于是否限定其作用域。C语言规定,枚举类型(enum)的成员的可见范围被提升至该枚举类型所在的作用域内。这被认为有可能污染了外部的作用域,为此,C++11引入了枚举类(enum class)解决此问题。
#enum
enum Sex
{
Girl,
Boy
};
// 错误,编译器提示 Girl,Boy重定义
enum Student
{
Girl,
Boy
};
#enum class
enum class Sex
{
Girl,
Boy
};
enum class Student
{
Girl,
Boy
};
int main(int argc, char *argv[])
{
Sex a = Sex::Gril;
Student b = Student::Gril;
//两者处于不同作用域下,不会重定义
}
枚举定义将被限制在枚举作用域内,并且不能隐式转换为整数类型,但是可以显式转化为整数类型,
enum class Sex
{
Girl,
Boy
};
int main(int argc, char *argv[])
{
Sex a=Sex::Gril;
int d1 =a; // 错误,无法从“Girl”隐式转换为“int”。
int d2 = int(a); // 正确,显示将enum class转换为整数
return 0;
}
STL
1.C++直接使用数组好还是使用std::array 好?std::array 是怎么实现的?
array是C++11中新提出来的容器类型,与内置数组相比,array是一种更容易使用,更加安全的数组类型,可以用来替代内置数组。作为数组的升级版,继承了数组最基本的特性,也融入了很多容器操作
数组初始化不能直接使用拷贝和赋值!!!数组的传递只能是遍历的形式来拷贝
和数组不同的是,array可以使用拷贝和赋值的形式进行初始化
array<int,10> ial1={0,1,2,3};
array<int,10> copy=ial1;//只要保证两者的类型一致就可以(包括元素类型和大小)
2.STL ⾥ resize 和 reserve 的区别?clear 是怎么实现的?
reserve()函数:会有可能将容量变大
(1)如果n大于容器现有的容量(capacity),比如你容器原来是100的容量,我现在指定n=200,那么就需要在自由内存区为整个容器重新分配一块新的更大的连续空间【因为vector是顺序容器,所以存储空间是连续的,如果之前的存储空间不够了,必须这样做】,然后将容器内所有的有效元素从旧位置全部复制到新位置,这个过程是调用拷贝构造函数,然后释放旧位置的所有存储空间,并调整容器的元素位置指示器。所以reserve的时候如果n比原来的大,结果只是让容器的冗余容量(即没有分配元素的存储区)变大,容器的实际大小,即元素个数并没有改变。
(2)如果n小于容器原来的容量,那么这个函数什么作用也没有了
resize()函数:会改变size的大小
(1)如果n大于容器当前的大小(即容器的size,这里和capacity无关),则在容器的末尾插入n-size()个初始值为c的元素;如果没有指定初始值,那就元素类型的默认构造函数来初始化。
(2)如果n小于容器当前的大小,则删除末尾的size()-n个元素,这样就导致容器的大小变了,size 变小了。但是这种类型的容器在删除一个元素的时候并不会释放元素本身的内存空间【这也是为了保留这块空间以避免将来要插入新元素的时候又要进行存储空间重分配】,所以容器的容量即capacity其实是没有改变的。
(3)n等于容器当前的大小,则什么也不做。
reserve和resize的共同点就是:都不缩减容器本身的容量。即对内存空间并没有影响
clear实现:clear只是将vector的size置0,并不保证capacity为0,因此clear并不能释放vector已经申请的内存。
3.deque 的底层数据结构是什么?它的内部是怎么实现的?
4.map 和unordered_map 有什么区别?分别在什么场景下使用?
5.list 的使用场景?std::find 可以传入list 对应的迭代器吗?
6.string 的常用函数
=,assign() // 赋值
swap() // 交换
+=,append(),push_back() // 尾部添加字符
insert() // 插入字符
erase() // 删除字符
clear() // 清空字符
replace() // 替换字符
substr() // 截取子字符串
操作系统
C++ Qt常用面试题整理(不定时更新)_qt面试题-CSDN博客