常见知识点归纳
<试着归纳一下哈哈,错误的地方欢迎指出来。>
1 C++11新特性 ? nullptr,智能指针,auto跟decltype,for循环按区间迭代,继承构造(using A::A;),unordered_map跟unordered_set,array数组等。
2 auto跟decltype的使用(auto跟decltype的区别) ? auto关键字,就是让编译器根据初始化来推断它的数据类型。 ? decltype关键字,也是让编译器来推断,但它不需要初始化,但需要提供一个东西或者对象给它。
auto num1; //错误,auto需要初始化
auto num2 = 1.0;//正确
int a = 0;
decltype(a) b; //根据a为int,定义int型变量b
3 继承构造(using A::A; ) ? 继承构造,如果没有调用到其中的某个具体的构造函数的话,编译器是不会对它产生真正的代码的。 ? 调用继承构造,对子类自身的成员可以用初始化表达式(data{1}; )
4 lambda表达式([](int in){return in;}; ) ? 关于捕获问题/类中无法捕获私有成员,需要通过捕获this指针。
5 array(与内置数组、vector的区别) ? array需要给定大小,它分配在栈上,是内置数组的升级版,它比内置数组在使用上更安全也更方便。 ? array跟内置数组的区别:array数组,用at()查询,会有越界判断,array可以拷贝构造。 ? array跟vector的区别:array数组,大小一开始就需要给定,不能再修改,没办法添加删除。array没有push_back这样的函数使用。
6 unordered_map,unordered_set ? unordered_map无序键值对,底层算法是哈希表,搜索时间复杂度为O(1) ? unordered_map[x] = y;如果x不存在,则新建x-y,如果x存在,则覆盖x的值。 ? unordered_map.insert(pair…)的话,则如果x存在会插入失败,返回第二个参数为false.返回类型是它的迭代器。 ? unordered_set 就是只有键,没有值,或者说它的键等于它的值,就是一个序列,元素不会重复。 ? map,是有序的,它的底层算法是红黑树,搜索时间复杂度为O(logn)。
7 move移动构造(A(A&& a) )
? 左值:内存中有它实际的地址的变量。 ? 右值:即时数,或者临时存在的变量,就是这一句执行完它就生命周期结束释放掉了。 ? 右值引用,从move移动构造说起,move移动构造会转移控制权,把右边的数据、指针这些复制过去后,也就是简单的浅拷贝,然后把 右边的数据、指针这些置0或者置空nullptr。通过move移动构造,右边就成了一个空对象,然后右边就被释放了,但是原来的对象以 及被转移了,所以完成了移动构造。
class T{
private:
int a;
int *b;
public:
T(T&& t){ //调用时: T Ta = move(Tb);
a = t.a;t.a=0;
b = t.b;t.b = nullptr;
}
};
8 智能指针(shared_ptr,unique_ptr,weak_ptr) ? 智能指针有三个,shared_ptr,unique_ptr,weak_ptr三个。 ? 比较常用的是shared_ptr,它内部采用一个计数的方式,当有引用它时,则计数加一,当释放的时候,计数减一,当计数值为0时它自 动调用析构释放。 ? unique_ptr,它首先是用explicit修饰参数构造函数,禁止它的隐式转换,然后删除默认拷贝构造函数、赋值操作符,删除操作就是在 后面加上=delete,这样它实例化后别人就不能从它这里得到,所以它的引用就只有它自己这一个。但是它保留有移动构造move,它可 以转移控制权。 ? weak_ptr,它不能单独使用,它必须配合shared_ptr使用。它可以由shared_ptr赋值得到,但是它的引用不会引起shared_ptr的计数 加一,删除也不会引起计数减一。类似在shared_ptr的成环引用时就可以用weak_ptr来解决。 关于成环引用的问题:https://blog.csdn.net/weixin_45963692/article/details/119305458
9 const修饰,static修饰 ? const修饰变量,表示变量为常量,初始化后,就是不能修改的。 ? const修饰函数,表示该函数不会做出任何改变。 const修饰函数参数,表示在该函数中不会对该参数值修改。 ? const修饰对象,这个对象为常对象,可以调用它的常函数,不能改的。 ? static修饰变量,表示该变量为静态变量。 ? static修饰全局变量、或者函数,则该变量、函数只在该文件中可见,对文件外部是不可见的。 ? static修饰局部变量,它只会定义一次,放在全局数据区,在下一次调用时会直接使用,不会再定义它。 ? static修饰类成员变量时,则该变量是一个共享数据,它的初始化必须要全局区进行初始化。
? const跟#define的区别: ? #define宏定义是在预处理期展开,然后在编译时把所有用到的地方用宏定义常量替换,不能对它进行调试,生命周期结束于编译期。 ? const常量是一个”运行时“的概念,在程序运行时使用,属性为只读。 ? 存储方式上,前者不会分配内存,存储在程序的代码段中,后者需要进行内存分配。 ? const跟constexpr的区别:
10 inline(与宏的区别) ? inline关键字,建议编译器内联,而实际上有没有内联需要编译器决定,对一些简单函数,会在调用处直接插入代码片段,这样就省去 了函数调用的开销。 ? inline与宏的区别:宏是在预处理时做的,内联是编译的时候做的。宏是简单的字符串替代,而内联的话我们可以给它传入参数、返回 参数,然后交给编译器去生成替换的代码片段。
11 new跟malloc区别(delete/free) ? new不需要指定大小,malloc需要指定大小。 ? new不需要强制转换类型,malloc需要转换类型。 ? new会调用构造函数,可以直接初始化列表赋值,malloc不会调用构造函数且无法直接初始化。 ? new属于操作符,特定情况下可以重载操作符,malloc属于函数,一般包含在<stdlib.h>中,不可以重载。 ? new分配失败时会抛出bad_alloc异常,而malloc会返回一个nullptr,所以对malloc需要检查是否分配失败(失败返回nullptr)
12 四种转换static_cast,dynamic_cast,const_cast,reinterpret_cast ? static_cast的使用跟C的强制数据类型转换一样,但是当它用在面向对象时,将父类指针转换为子类指针(下行转换),是不安全的。 ? dynamic_cast,用于父类指针转换为子类指针的,它会动态检测(与static_cast的区别),如果不成功则返回一个空指针,安全。 ? const_cast的话,一般是用在常变量跟普通变量的转换。 ? reinterpret_cast的话,重新解释,比如用一个整形的数,来存下指针本身的值,就可以用这个来转换(int* 转换为int)。
13 关键字 extern(extern “C”) ? 关键字extern,声明时表示该变量、函数在别的文件里已经有被定义过了,这里只是声明。 ? extern“C”,是C++要使用C函数的时候需要写的声明。因为C++函数重载的功能,C++对函数名的接口是函数名加上参数列表,但是C不 支持,C就是单纯的函数名,所以声明extern“C”是改变它的接口,让C++可以调用C。
14 指针与引用的区别 ? 指针,是指向一片内存的起始地址。引用,是这片内存的一个别名。 ? sizeof指针,是指针大小,sizeof引用,是这个东西的大小。 ? 指针可以不用初始化,可以为空。引用必须初始化,引用不能为空。 ? 指针可以改变指向,但是引用初始化之后就不能再改变它引用别人了。 ? 指针在使用的时候需要加解引用操作符,引用可以变量名直接使用。
15 理解“面向对象”、“封装”、“继承”、“多态” ? 面向对象,是一种编程的思维,把各个的东西作为各个对象,然后我们依靠这些对象间的关系来进行编程,这更符合我们的实际生活。 ? 封装,将这个对象内部作为一个黑盒子,外部是无法知道的,它内部会严格划分访问权限,它会提供接口给外部调用。这样提高了它的 安全性,而且封装后,也就成了模块化编程,减低了程序的耦合性。 ? 继承,继承的目的时实现代码复用,在一个现有类上扩展一些属性、功能时,可以直接复用。 ? 多态,多态的实现,是由虚函数机制实现的。就是用基类指针指向派生类的时候,可以通过基类指针调用派生类中的具体函数。
16 虚函数、纯虚函数、虚函数表 ? 虚函数,在类中成员函数声明时,用virtual修饰,则将它声明为虚函数。当派生类中重写这个虚函数的时候,即可将派生类中重写的这 个具体函数动态绑定到基类的指针,也就是说实现通过基类的指针,调用派生类的具体函数。 ? 纯虚函数,就是虚函数之后加上 =0,即把它声明为纯虚函数。有纯虚函数的类称为抽象类,抽象类是不能实例化的,派生类需要对每 一个纯虚函数负责,全部重写后才能实例化。 ?虚函数表,当一个类中出现虚函数的时候,编译器就会给它加上一个虚函数表指针vptr,它指向虚函数表。当子类从父类那里继承而来的 时候,会拷贝一个父类的虚函数表,这样在子类重写虚函数的时候,就能覆盖父类的虚函数,当父类指针调用时则通过vptr找到虚函数 表再找到对应的具体虚函数,也就是使派生类的具体函数动态绑定到父类指针。 ? 而纯虚函数的话,则是在虚函数表中留空位,也就是留一个值为0,然后当它还没有被重写时,也就是留有空函数指针,此时编译器会 阻止它的实例化。
? 构造函数为什么不能声明为虚函数?首先创建一个对象必须指出它的类型,否则无法创建,然后如果我们把构造函数声明为虚函数,那 也就是说会在运行时才确定调用具体函数,但是此时对象还未创建,这就形成了一个类似死锁的东西。
? 析构函数为什么常常声明为虚函数?如果析构函数不是虚函数的话,当使用父类指针来指向子类对象时,其生命周期结束后会调用父类的析构函数,而不会对子类进行析构,子类申请的内存就丢失了,就造成了析构函数。为了避免这一点,通常将析构函数声明为虚函数。
17 类、结构体与联合体之间的区别 ? class与struct的区别:class默认访问权限private,struct默认权限public ? 联合体union,它是一个数据集合,它的数据成员是共用一片内存的,内存长度为联合体中最大的那个数据成员。某一时刻,联合体只 有一个数据成员是有意义的。
//用union判断储存方式是大端还是小端
bool bigending(){
union{
unsigned int a_int;
unsigned char a_char;
}a;
a.a_int = 1; //大端:数据的高字节保存在低地址。
return a_char == 0; //如果是bigending,那就是 01 00 00 00,如果是smallending,那就是00 00 00 01
}
18 深拷贝与浅拷贝 ? 浅拷贝,就是对着源对象一一复制,对指针也同样直接复制。 ? 深拷贝,针对那些有分配内存的对象,因为如果一一复制的话,会导致两个对象的指针是指向同一个东西的。所以深拷贝就需要重新分 配空间,然后再对空间上的数据一一复制。
19 ifndef/define/endif ? 防卫式声明,作用是避免重复定义,如果没有写的话,比如a引用这个头文件,b引用这个头文件,然后当c同时引用a和b时,那一开始 的这个头文件就被定义了两遍,就是一个重复定义的错误了。
20 继承、组合的区别 ? 继承,就是从父类继承相同接口,然后自己再加上自己本身的接口。 ? 组合,就是A类中有一个成员是B类对象,A类可以通过这个成员对象实现B类的接口,然后自己再加上自己本身的接口。 ? 继承是 is a,组合是 has a。 ? 典型例子就是,从链表生成栈:https://blog.csdn.net/weixin_45963692/article/details/119305715
21 函数重载跟函数重写 ? 重载是指函数名相同但参数列表不相同,重载函数时编译器会根据调用时的参数列表匹配对应的函数,实际上函数名是函数名加上参数 列表,所以虽然重载函数的函数名是一样的,但是编译器对它们的标识是不一样的,实现了函数重载。 ? 重写是指在虚函数机制中,子类重写父类的虚函数,函数名相同,参数列表也相同。
22 什么时候一定需要初始化列表 ? 常量成员变量,调用基类的构造函数,成员对象的构造 ? 构造顺序是:成员对象构造函数 > 基类构造函数 > 常量
23 内存分为哪些部分以及区别 ? C++一般分为5个区,堆、栈、全局存储区、常量存储区、自由存储区 ? 堆是由程序员手动申请,然后手动释放的。程序员需要对它的生命周期负责,否则只能在程序结束时由操作系统回收。 ? 栈是编译器根据需要自动申请分配的。比如临时变量,函数传参pass by value。 ? 全局存储区,就存全局变量,静态变量的一个空间。 ? 自由存储区 ? 常量存储区,就是存常量的一个空间,比如字符串常量的分配。
const char *p = "123abc";// "123abc"为字符串常量,存放在常量存储区
char p[] = "123abc"; // 分配在栈上
24 迭代器失效问题以及解决方案 ? 顺序容器vector中,当erase()的时候,删除元素后返回迭代器,如果单纯it++则会错误(错误的原因是:后部分数据已经往前移了,此时it就已经是下一位了,不该再it++)。 关联容器也有这个问题,在这里有讲讲:https://blog.csdn.net/weixin_45963692/article/details/119305901
25 #include<xxx.h>与#include”xxx.h”的区别 ? 前者是在标准库中寻找 ? 后者是在当前工作路径中找,比如自己写的头文件
26 C++运行时发生了什么 ? 预处理,编译,汇编,链接,可执行程序 ? 预处理:先处理宏定义、条件编译指令、特殊符号、注释这些处理 ? 编译:编译成汇编代码(.s文件) ? 汇编:将汇编代码转化为机器识别的二进制代码,生成(.obj文件) ? 链接:将程序关联的外部文件关联起来,形成(.exe文件) ? 什么是静态链接,什么是动态链接?
27 volatile ? volatile修饰变量的时候,意思是这个值可能会由其他途径发生修改,如果没加volatile的话,编译器会优化这个数值,当这个数值在某 些阶段内没有发生修改的话,下一次使用的时候编译器就直接使用上一次的值。所以加上volatile,就让编译器不要对它优化,当每次 使用这个变量的时候,都是去内存中读取最新的数值。
28 如何编写一个智能指针 29 C++类型安全是什么意思? 30 函数指针表达跟使用
31 内存对齐相关(计算类的大小) ? 类的内存对齐有两部分,首先是数据类型自身对齐,然后是整个类的数据类型对齐。 ? 数据类型自身对齐,数据存放的起始地址为数据类型长度的整数倍。比如int只能0x0000、0x0004。 ? 整个类的数据类型对齐,就是按照长度最大的那个数据类型的长度为基准,空余位置则字节填充。
class T0{ //假设从0x0000开始,a:0x0000-0x0003,b:0x0004,c:0x0008-0x0015,而0x0005-0x0007则字节填充。
private: //a长度为4,只能从0x0000或者0x0004开始,这里可以从0x0000开始。b长度1位,可以任意位开始,所以在0x0004。
int a; //c长度为8,只能从0x0000后者0x0008开始,因为这时候内存排到0x0005了,所以只能从0x0008开始,差的填充。
char b; //所以sizeof(T0)为16.
double c;
};
class T1{ //b:0x0000,c:0x0008-0x0015,d:0x0016-0x0019,而0x0001-0x0007、0x0020-0x0023则字节填充
private: //所以sizeof(T1)为24.
char b;
double c;
int a;
};
? 内存对齐的目的是:提高CPU访问的速度。为什么?
32 C++什么类不能被继承
33 STL六大件 ? STL六大件分别是:容器,分配器,迭代器,算法,适配器,仿函数 ? 容器,就是用来存放数据的。 ? 分配器,为容器的数据提供分配合适的内存。 ? 迭代器,就是因为数据的存放是由容器决定的,数据的存储方式对外部是不可见的。容器向外提供迭代器,我们可以用迭代器来进行访 问它而不需要知道它的具体具体和它的寻找方式。 ? 算法,基于迭代器去访问容器,于是实现了容器跟算法的分离。 ? 适配器,主要就是对已有的容器,隐藏或增加特定的接口以实现新的样貌,符合逻辑需求。比如stack的底层实现是deque。 ? 仿函数
34 关于空类指针调用函数的问题 类的成员函数是放在代码区的,函数地址是根据类型去查找的,编译期就确定了它的调用地址,对象调用其成员函数实际上是传入一个this的指针来实现的,但是不需要传入参数时则空对象可以调用,如下。
class T{
private:
int a;
public:
void fun1(){cout << "fun1() of void" << endl;};
void fun2(){cout << "fun2() of *this:" << a << endl;}
virtual void fun3(){cout << "fun3() of virtual" << endl;}
};
int main(){
T* p;
p->fun1();//正确。
p->fun2();//错误,需要传入this指针给函数,空指针错误。
p->fun3();//错误,调用虚函数需要访问vptr,空指针错误。
return 0;
}
|