知识点整理收集自网络c++面经,作为自己面试复习c++的笔记。知识点均为手动整理,并非复制粘贴,可能有表述不合适地方,欢迎评论纠正。

文章目录

  • ​​1,c和c++的区别​​
  • ​​2,c++和java的区别​​
  • ​​3,什么是面向对象,面向对象的几大特征是什么​​
  • ​​3.1多态的实现过程​​
  • ​​4,指针和引用的区别​​
  • ​​5,new/delete和malloc/free的区别​​
  • ​​6,volatile关键字​​
  • ​​7,static关键字​​
  • ​​7.1什么是static变量​​
  • ​​7.2 static关键字的作用​​
  • ​​8,const关键字​​
  • ​​8.1 const关键字的作用​​
  • ​​9,inline内联函数​​
  • ​​9.1 友元函数​​
  • ​​9,define/const/inline区别​​
  • ​​10,extern关键字作用​​
  • ​​11,C++智能指针​​
  • ​​12,构造函数为什么不能定义为虚函数,析构函数为什么可以?​​
  • ​​13 C++11新特性​​
  • ​​14,如何让一个类不能实例化​​
  • ​​15,如何让main函数之前执行函数?​​
  • ​​16,C++如何创建一个类,使得他只能在堆或者栈上创建?​​
  • ​​17,什么是c++的命名空间​​
  • ​​18,一个C++源文件从文本到可执行文件经历的过程​​
  • ​​19,include 的顺序以及尖叫括号和双引号的区别​​
  • ​​20,线程池是怎么实现的​​
  • ​​21,c++的内存管理方式​​
  • ​​22,栈和堆的区别,为什么栈要快​​
  • ​​23,自己的优缺点​​
  • ​​24,结构体和联合体的区别​​
  • ​​25,在什么情况下会用到常引用​​
  • ​​26,将引用作为函数返回值类型,有什么好处和需要遵守的规则?​​
  • ​​27,函数的重载和重写(覆盖)的区别​​
  • ​​28,虚函数和纯虚函数的区别​​
  • ​​29,c++是不是类型安全的​​
  • ​​30,数组和指针的区别​​
  • ​​31,栈内存与文字常量区​​
  • ​​32,定义数组的最大长度限制因素​​
  • ​​33,全局变量和局部变量是怎么实现的,有什么区别?操作系统是怎么知道的?​​
  • ​​34,设计模式懂吗?简单举个例子​​
  • ​​35,STL库?常见的STL容器有哪些?​​
  • ​​36,c++内存分配器​​
  • ​​37,unordered_map和map的区别​​
  • ​​38,未初始化的全局变量放在哪​​
  • ​​39,什么是野指针​​
  • ​​40,在C++STL中常用的容器和类型,下面哪些支持下标"[]"运算?​​
  • ​​41,拷贝构造函数​​
  • ​​42, 赋值函数​​
  • ​​42,深拷贝和浅拷贝​​
  • ​​42.1浅拷贝​​
  • ​​42.2,深拷贝​​
  • ​​43,静态链接库和动态链接库的区别​​
  • ​​44 select、poll、epoll之间的区别总结​​
  • ​​45,GDB调试​​
  • ​​46,对称加密与非对称加密​​
  • ​​47,Qt信号与槽实现​​
  • ​​48,auto和decltype的区别​​
  • ​​49,左值引用与右值引用​​
  • ​​50,函数模板与类模板(全特化与偏特化)​​
  • ​​51,CAS自旋锁(无锁队列)​​

1,c和c++的区别

  • c是面向过程的语言,c++是面向对象的语言
  • c中动态分配内存是malloc/free,c++是new/delete
  • c++有引用概念,c没有
  • c++有类的概念,c没有
  • c++有函数的重载,c没有
  • c的变量只能在函数的开头处声明和定义,而c++随时定义随时使用

2,c++和java的区别

  • c++应用在中层和底层,java应用在高层
  • java没有指针的概念,完全使用面向对象的思想,提高了代码质量
  • java在web方向的应用具有c++无可比拟的优势
  • java自动回收垃圾,而c++需要在析构函数中程序员手动回收
  • java采用接口来取代c++的多继承

3,什么是面向对象,面向对象的几大特征是什么

  • 面向对象是一种基于对象的,基于类的软件开发思想。面向对象具有封装,继承,多态的特性。
    封装和继承好理解,重点是多态:
  • 封装:也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
  • 继承:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
  • 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
    C++中,实现多态有以下方法:继承,虚函数,抽象类,覆盖,隐藏,模板(重载和多态无关)。

3.1多态的实现过程

多态主要是通过虚函数实现的,虚函数的实现依赖虚函数表,一个类中存在虚函数时,在构造函数构造时进行初始化虚函数表,并用一个指针指向该表。子类继承时也继承了该指针,当子类对修改了虚函数,相当于修改了虚函数指针,那么父类的虚函数表指针也会被修改,这样就实现了,某基类型的指针指向其子类型的对象,调用的方法是该子类型的方法

4,指针和引用的区别

  • 引用是变量的别名,指针是变量在内存中实际的地址
  • 引用在使用时必须初始化,而指针可以为空(NULL)
  • 引用指向的变量不可以变,而指针指向的变量可以变
  • 引用的创建不会调用类的构造函数

5,new/delete和malloc/free的区别

  • new是运算符,malloc是c语言的库函数
  • new可以重载,malloc不可重载
  • new是按照变量类型大小分配内存,malloc是按照字节分配
  • new可以调用构造函数,delete可以调用析构函数,malloc/free不能
  • new返回的是指定对象的指针,malloc返回的是void*(malloc的返回值一般都需要类型转换)
  • malloc内存不够时可以使用relloc扩充,而new没有这样的操作
  • new内存分配失败时返回bad_malloc,malloc分配失败返回NULL

6,volatile关键字

volatile是一个特征修饰符(type specifier).volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。
volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了,每次从内存读取这个数据的值,而不是从寄存器读取这个值。

7,static关键字

7.1什么是static变量

  • static变量:在编译器编译时产生,在程序结束时销毁,并不会因为出了变量的作用域而重置,并且默认初始化为0
  • static类成员变量:所有对象访问同一个,更改同一个并且static类成员变量需要在类外进行初始化
  • static类成员函数:所有类的对象共享一个static函数,static函数不能访问非static变量,并且可以通过类名::函数名直接调用
  • static全局函数:限制他的作用域只能在本文件之内。

7.2 static关键字的作用

  • 修饰局部变量
  • 修饰全局变量
  • 修饰局部函数
  • 修饰全局函数
  • 修饰类的成员变量(static类成员变量需要在类外进行初始化),成员函数

8,const关键字

const为常量的意思,即值只能被访问,不能被修改

8.1 const关键字的作用

  • 修饰全局/局部变量
  • 修饰指针 const int *a(即a指针指向的值不可变,地址可变)
const int *a=new int(3);
int b=4;
cout<<a<<endl;
//0x1c1420
a=&b;
cout<<a<<endl;
//0x6ffe14
cout<<*a<<endl;
//4
  • 修饰指针指向的对象,int * const a (a指针的地址不可变,但是值可变)
int *const a=new int(3);
int b=4;
*a=b;
cout<<*a<<endl;
//输出a=3
  • 修饰引用做参数
  • const修饰成员变量,必须在构造函数列表初始化
  • const修饰成员函数,成员函数不能修改非静态成员变量,调用非const成员函数

9,inline内联函数

在c/c++中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了inline修饰符,表示为内联函数。

栈空间就是指放置程序的局部数据(也就是函数内数据)的内存空间。

在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足而导致程序出错的问题,如,函数的死循环递归调用的最终结果就是导致栈内存空间枯竭。

下面我们来看一个例子:

#include <stdio.h>
//函数定义为inline即:内联函数
inline char* dbtest(int a) {
return (i % 2 > 0) ? "奇" : "偶";
}

int main()
{
int i = 0;
for (i=1; i < 100; i++) {
printf("i:%d 奇偶性:%s /n", i, dbtest(i));
}
}

上面的例子就是标准的内联函数的用法,使用inline修饰带来的好处我们表面看不出来,其实,在内部的工作就是在每个for循环的内部任何调用dbtest(i)的地方都换成了(i%2>0)?”奇”:”偶”,这样就避免了频繁调用函数对栈内存重复开辟所带来的消耗。
内联函数不能递归,不能包含复杂的结构控制语句例如while、switch,并且内联函数只是对编译器的一个建议,具体编译器是否使用内联函数还是要看编译器

9.1 友元函数

​类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员​​​。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。

#include <iostream>

using namespace std;

class Box
{
double width;
public:
friend void printWidth( Box box );
void setWidth( double wid );
};

// 成员函数定义
void Box::setWidth( double wid )
{
width = wid;
}

// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box )
{
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "Width of box : " << box.width <<endl;
}

// 程序的主函数
int main( )
{
Box box;

// 使用成员函数设置宽度
box.setWidth(10.0);

// 使用友元函数输出宽度
printWidth( box );

return 0;
}

9,define/const/inline区别

本质:define只是在编译器预处理时进行的简单字符替换,const和inline参加编译器的编译

  • define不会做类型检查,const拥有类型,会执行相应的类型检查
  • define仅仅是宏替换,不占用内存,而const会占用内存
  • const内存效率更高,编译器通常将const变量保存在符号表中,而不会分配存储空间,这使得它成为一个编译期间的常量,没有存储和读取的操作
  • 内联函数在编译时展开,而宏是由预处理器对宏进行展开
  • 内联函数会检查参数类型,宏定义不检查函数参数 ,所以内联函数更安全。
  • 宏不是函数,而inline函数是函数

10,extern关键字作用

声明一个外部变量==(函数内部声明的extern变量,需要在函数外部进行定义)==。

#include <iostream>
using namespace std;
void change();
int main()
{
//引用性定义extern变量
extern int num;
//调用一次change函数
cout<<num<<endl;
change();
cout<<num<<endl;
}
//声明num
int num=0;
void change()
{
num+=3;
}

11,C++智能指针

​​添加链接描述​​

weak_ptr的作用:解决/避免 shared_ptr的循环引用

12,构造函数为什么不能定义为虚函数,析构函数为什么可以?

虚函数的执行依赖于虚函数表。而虚函数表需要在构造函数中进行初始化工作,即初始化vptr,让他指向正确的虚函数表。而在构造对象期间,虚函数表还没有被初始化,将无法进行。
在类的继承中,如果有基类指针指向派生类,那么用基类指针delete时,如果不定义成虚函数,派生类中派生的那部分无法析构。

13 C++11新特性

  • Lambda 表达式
    c++11支持匿名函数
  • auto 类型指示符

auto让编译器通过初始值来推算变量的类型,所以,其定义的变量必须要有初始值;

  • 序列for循环
map<string, int> m{{"a", 1}, {"b", 2}, {"c", 3}};  
for (auto p : m){
cout << p.first << " : " << p.second << endl;
}
  • nullptr 常量

有几种生成空指针的方法:

int *p1 = nullptr; // 等价于int *p1 = 0;
int *p2 = 0;
int *p3 = NULL; // 等价于int *p3 = 0;
  • 类型别名声明
    使用类型别名可以使复杂的类型名字变得更简单明了,易于理解和使用;
    现在有两种方法可以用来定义类型别名,一种是 ​​​typedef​​​ ,另一种则是新标准中的 ​​using​​;
//int 类型别名为INT
typedef int INT;
using INT=int;

14,如何让一个类不能实例化

将类定义为抽象基类或者将构造函数声明为private。

15,如何让main函数之前执行函数?

在main函数之前声明一个类的全局的对象,那么该类的构造函数就会在main函数之前执行:

class simpleClass  
{
public:
simpleClass( )
{
cout << "simpleClass constructor.." << endl; //step2
}
};


simpleClass g_objectSimple; //step1全局对象

int _tmain(int argc, _TCHAR* argv[]) //step3
{
return 0;
}

可单步调试查看执行顺序为step1、step2、step3

16,C++如何创建一个类,使得他只能在堆或者栈上创建?

首先明白一点,由Class c=new Classa()生成的变量存放在堆上,使用Class c生成的变量存放在栈上
只能在堆上生成对象:将析构函数设置为私有。
原因:存储在栈上的变量,编译器自动管理栈上对象的生命周期(自动执行类的析构函数),编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性。若析构函数不可访问,则不能在栈上创建对象。
只能在栈上生成对象:将new 和 delete 重载为私有。
原因:存储在堆上变量,是用new关键字生成的对象指针。如果将new关键字重载为私有,就不能使用new运算符,那么对象指针就不能被实例化,就只能在栈上生成对象。
其过程分为两阶段:第一阶段,使用new在堆上寻找可用内存,分配给对象;第二阶段,调用构造函数生成对象。将new操作设置为私有,那么第一阶段就无法完成,就不能够再堆上生成对象。

17,什么是c++的命名空间

可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。

18,一个C++源文件从文本到可执行文件经历的过程

一、预处理(产生.i文件,-E)
二、编译(产生.s文件,-s)
三、汇编(产生.o或.obj文件,-c)
四、链接(产生.out或.exe文件,-o)
1、静态链接/库
2、动态链接/库

19,include 的顺序以及尖叫括号和双引号的区别

尖括号<xxx.h>,表示编译器只在系统默认目录或尖括号内的工作目录下搜索头文件,并不去用户的工作目录下寻找,所以一般尖括号用于包含标准库文件,例如:stdio.h,stdlib.h。
双引号"xxx.h",表示编译器先在用户的工作目录下搜索头文件,如果搜索不到则到系统默认目录下去寻找,所以双引号一般用于包含用户自己编写的头文件。

20,线程池是怎么实现的

线程池的实现思想:“管理一个任务队列,一个线程队列,然后每次取一个任务分配给一个线程去做,循环往复。

21,c++的内存管理方式

  • 自动存储(栈)
    函数的形参,函数内部声明的变量及结构体变量自动存储在栈里面
    在所属函数被调用时自动产生,在函数结束时自动消亡
  • 静态存储
    static静态变量,全局变量 存储在这里
    在编译器编译时分配的一段静态内存,程序开始时创建,结束时销毁

在以前的C语言中,包括DATA段(全局初始化区)与BSS段(全局未初始化段)。其中,初始化的全局变量和静态变量存放在DATA段,未初始化的全局变量和静态变量存放在BSS段。BSS段特点:在程序执行前BSS段自动清零,所以未初始化的全局变量和静态变量在程序执行前已经成为0

  • 动态存储(堆)
    new/malloc创建的指针变量
    存放在堆里,由程序员手动释放或操作系统自动回收
  • 文字常量区:存放常量,而且不允许修改
  • 程序代码区:存放程序的二进制代码

22,栈和堆的区别,为什么栈要快

因为堆变量是存储是随意的(链表存储结构),存放的变量由程序员自己定义的,需要由程序员手动释放,而栈变量在变量出来作用域后就被自动释放了

23,自己的优缺点

优:学习能力强,喜欢接触新鲜的事物,勇于面对挑战
缺:考虑事物不全面,之前的项目中,对于用户体验考虑不全面,只考虑到完成用户的需求,但没考虑到需要提高用户体验

24,结构体和联合体的区别

结构体和联合体都是由多个不同数据类型成员组成,但是在同一时刻,联合体只存放了一个被选中的成员(所有成员公用一块内存地址),而结构体的成员都存在(不同的成员存放的地址不同)

25,在什么情况下会用到常引用

const int &a=b;
用于使用引用作为参数传入函数,但是想要保护传递的参数在函数内不能被更改的情况。

26,将引用作为函数返回值类型,有什么好处和需要遵守的规则?

好处:在内存中不产生被返回的变量的副本
注意:不能返回函数内局部变量的引用和new分配内存的引用(局部变量一出了作用域,就会被销毁,相应的引用也会失效)

27,函数的重载和重写(覆盖)的区别

  • 重载:在同一类中,允许存在相同的函数名,但是这些函数的参数不同
  • 重写:派生类重新定义基类的相同名称的虚函数

28,虚函数和纯虚函数的区别

  • 虚函数 在基类中使用virtual修饰的成员函数,允许派生类针对基类的虚函数进行重写,实现通过基类指针调用派生类的方法
  • 纯虚函数:在基类中仅有函数名字的虚函数。要求派生类必须进行重写,否者派生类也为虚拟的,不能被实例化(纯虚函数不能被调用)
  • 如果一个类含有纯虚函数,那么这个类称为抽象类(抽象类不能被实例化)

29,c++是不是类型安全的

不是,c++不同两个类型的指针可以进行强制类型转换(malloc函数返回值为void* 需进行强制类型转换)

30,数组和指针的区别

  • 修改地址所指的值的区别 数组通过下标进行访问,指针通过*p
  • 使用sizeof可以直接计算出数组的总字节数,而指针只能获取一个指针变量的字节数

31,栈内存与文字常量区

char str1[] = "abc";
  char str2[] = "abc";

  const char str3[] = "abc";
  const char str4[] = "abc";
//
  const char *str5 = "abc";
  const char *str6 = "abc";

  char *str7 = "abc";
  char *str8 = "abc";

  cout << ( str1 == str2 ) << endl;//0 分别指向各自的栈内存
  cout << ( str3 == str4 ) << endl;//0 分别指向各自的栈内存
  cout << ( str5 == str6 ) << endl;//1指向文字常量区地址相同

  cout << ( str7 == str8 ) << endl;//1指向文字常量区地址相同

  结果是:0 0 1 1

解答:str1,str2,str3,str4是数组变量,它们有各自的内存空间(栈中存储);而str5,str6,str7,str8是指针,它们指向相同的常量区域。

32,定义数组的最大长度限制因素

int a[10]
大小(size)的数据类型,也就是数组下标的数据类型,其实也是一个限制因素。在C/C++中,数组下标的类型是std::size_t,因此数组的大小首先不能超过size_t所能表示的大小。这个数据类型是在库文件stdio.h中通过typedef声明的,对于32位程序它被定义为unsighed int,对于64位程序定义为unsigned long。前者能表示的最大大小为2^32-1, 后者为 2^64-1。

33,全局变量和局部变量是怎么实现的,有什么区别?操作系统是怎么知道的?

  • 生命周期不同,全局变量随着程序的创建而创建,程序的销毁而销毁。局部变量只能在函数内部使用,退出函数体就被销毁
  • 使用方法不同,全局变量可以在任何作用域内都能使用,局部变量只能在定义的作用域内使用
  • 通过内存分配的位置来知道,全局变量分配在静态存储区,局部变量分配在栈中

34,设计模式懂吗?简单举个例子

  • 单例模式:保证一个类只有一个实例,并提供一个访问他的全局结点
    适用于:当类只能有一个实例且用户可以从一个众所周知的位置访问他时
  • 工厂模式:提供一个用于创建对象的接口,让子类决定实例化哪个类(用一个方法来代替new关键字)。
    适用:当类不知道具体要实例化哪个对象时,当一个类需要他的子类来创建对象时

35,STL库?常见的STL容器有哪些?

答:STL库是一种泛型编程,他包括容器和算法,迭代器。
vector,list,deque,stack,map
vector:是一种动态分配内存的的容器,相对于传统的array,array的内存分配是静态的,vector可以动态分配,自动扩充空间
算法:排序,复制,插入,删除
迭代器是STL的精髓,我们这样描述它:迭代器提供了一种方法,使它能够按照顺序访问某个容器所含的各个元素,但无需暴露该容器的内部结构。它将容器和算法分开,好让这二者独立设计。

36,c++内存分配器

c++的容器大小在程序运行时改变,分配器处理容器的内存分配与释放请求。

37,unordered_map和map的区别

  • 构造函数:hash_map需要hash函数,等于函数;map只需要小于函数
  • 存储结构:hash_map采用hash表存储,map采用红黑树。
  • unordered_map比map查找更快,map占内存多,使用于对顺序有要求的
    map:
    优点:
    有序性,这是map结构最大的优点,其元素的有序性在很多应用中都会简化很多的操作
    红黑树,内部实现一个红黑书使得map的很多操作在lgn的时间复杂度下就可以实现,因此效率非常的高
    缺点: 空间占用率高,因为map内部实现了红黑树,虽然提高了运行效率,但是因为每一个节点都需要额外保存父节点、孩子节点和红/黑性质,使得每一个节点都占用大量的空间
    适用处:对于那些有顺序要求的问题,用map会更高效一些
    unordered_map:
    优点: 因为内部实现了哈希表,因此其查找速度非常的快
    缺点: 哈希表的建立比较耗费时间
    适用处:对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用unordered_map

38,未初始化的全局变量放在哪

  • 未初始化:BSS
  • 初始化:数据区

39,什么是野指针

野指针,也就是指向不可用内存区域的指针。如果对野指针进行操作,将会使程序发生不可预知的错误,甚至可能直接引起崩溃。

  • 指针变量未被初始化
  • 指针指向内存被释放了,但是指针没有置NULL
  • 指针超过了变量的作用范围

40,在C++STL中常用的容器和类型,下面哪些支持下标"[]"运算?

vector map deque string unordered_map

41,拷贝构造函数

  • 什么时候会调用拷贝构造函数:

(1)一个对象以传递的方式传入函数体
(2)一个对象以传递的方式从函数返回
(3)一个对象需要通过另外一个对象进行初始化。

A a;
A b=a; //调用拷贝构造函数(b不存在)
A c(a) ; //调用拷贝构造函数
  • 拷贝构造函数的声明:
    和构造函数类似,需要传入被拷贝的对象的常引用:
//构造函数
CExample(int b)
{
a = b;
}

//拷贝构造
CExample(const CExample& C)
{
a = C.a;
cout<<"copy"<<endl;
}
  • 默认的拷贝构造函数
    如果没有定义拷贝构造函数,编译器会默认生成拷贝构造函数。仅仅使用“老对象”的数据成员的值对“新对象”的数据成员一一进行赋值。
    1,为什么拷贝构造函数不能以值传递方式传入
    当 一个对象需要以值方式传递时,编译器会生成代码调用它的拷贝构造函数以生成一个复本。如果类A的拷贝构造函数是以值方式传递一个类A对象作为参数的话,此时会调用类A的拷贝构造函数,结果就是调用类A的拷贝构造函数导 致又一次调用类A的拷贝构造函数,这就是一个无限递归。

42, 赋值函数

也是对值操作时调用,对指针进行操作不调用!!!!
赋值运算的重载声明如下:

A& operator = (const A& other)

他和拷贝构造函数的区别是,赋值函数是已经初始化的对象,使用=进行值传递赋值时会调用,而拷贝构造函数是没有初始化的函数通过这个对象就行初始化时调用

A a;
A b=a; //调用拷贝构造函数(b不存在)
A c(a) ; //调用拷贝构造函数

/****/

class A;
A a;
A b;
b = a ; //调用赋值函数(b存在)

42,深拷贝和浅拷贝

浅拷贝:经过拷贝后两个变量指向同一地址,值相同
深拷贝:经过拷贝后,两个变量指向不同地址,且值相同

42.1浅拷贝

在进行对象的拷贝时,如果调用的是默认拷贝构造函数,则仅仅是将老对象成员一一赋值到新对象,如果涉及到动态分配的变量,则两个对象会指向同一个内存区域:

class Rect
{
public:
Rect() // 构造函数,p指向堆中分配的一空间
{
p = new int(100);
}
~Rect() // 析构函数,释放动态分配的空间
{
if(p != NULL)
{
delete p;
}
}
private:
int width;
int height;
int *p; // 一指针成员
};

int main()
{
Rect rect1;
Rect rect2(rect1); // 复制对象(浅拷贝)
return 0;
}

c++常见面试题_面试


这样就造成,如果在销毁两个对象时,会对一个内存空间释放两次出现错误。解决方法就是深拷贝

42.2,深拷贝

在拷贝时,实现拷贝构造函数,在拷贝构造函数中重新动态分配空间,将原变量的值复制给新地址:

class Rect
{
public:
Rect() // 构造函数,p指向堆中分配的一空间
{
p = new int(100);
}
Rect(const Rect& r)
{
width = r.width;
height = r.height;
p = new int; // 为新对象重新动态分配空间
//获取原值 并将该值复制给新对象的地址上
*p = *(r.p);
}
~Rect() // 析构函数,释放动态分配的空间
{
if(p != NULL)
{
delete p;
}
}
private:
int width;
int height;
int *p; // 一指针成员
};

c++常见面试题_初始化_02

43,静态链接库和动态链接库的区别

c++常见面试题_初始化_03


程序编译在链接阶段,会对静态链接库和动态链接库进行连接

  • 静态连接库 将源文件用到的所有库函数与汇编阶段产生的目标文件进行链接生成可执行文件
    优点:可执行文件在任何电脑上直接可以运行,方便移植,不用外部依赖
    缺点:可执行文件往往会非常大,且每次升级库文件都需要重新编译
  • 动态链接库 在程序运行时,从系统环境中找到库文件,再进行链接
    优点:相对于静态链接库,在内存中多个程序使用一个动态库,节约内存
    缺点:移植性太差,如果两个电脑的动态库文件目录不同,很有可能导致程序缺少库文件而运行失败

44 select、poll、epoll之间的区别总结

45,GDB调试

1,gdb 调试之调试前提

并非所有的程序都可以直接调试,gdb 程序的前提是即将调试的程序中必须包含有调试符号信息。因此在程序编译生成时必须指定生成debug版本的程序,因为只有debug版本的程序在编译生成的时候才会加入程序的调试符号信息
2,加载程序
直接终端运行gdb,将可执行程序名以空格分隔,紧跟其后即可

gdb ./test

若要调试正在运行中的程序,则使用 gdb -p 选项指定进程id来连接到这个程序
3,开始调试
run start
注意: 这里run 命令敲击后,则直接开始运行程序,直到断点位置停下或者程序结束

[san@San doc]$ gdb ./test
Reading symbols from /home/san/doc/test...done.
(gdb) run
Starting program: /home/san/doc/./test
gval:100
gval:100
gval:101

注意: 这里start 命令敲击后,则程序从main函数的起始位置停下,开始逐步调试

(gdb) start 
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Temporary breakpoint 1 at 0x4005a7: file test.c, line 13.
Starting program: /home/san/doc/./test
Temporary breakpoint 1, main () at test.c:13
13 int i = 0;

4,查看调试行附近的代码
list

(gdb) list
8 strcpy(buf, "我爱我的祖国");
9 return gval;
10 }
11 int main()
12 {
13 int i = 0;
14
15 printf("gval:%d\n", gval);
16 for (i = 0; i < 10; i++) {
17 gval += i;

5,逐步调试的 setp与next
step 和 next 命令敲击后都是运行当前行代码,进入下一行。不同的是当调试行为函数时,step会进入函数内部继续逐步调试,而next则是直接将函数运行完毕
6,断点之break

(gdb) break test.c:21
Breakpoint 6 at 0x400608: file test.c, line 21.
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/san/doc/./test
gval:100
gval:100
gval:101
gval:103
...
Breakpoint 6, main () at test.c:21
21 printf("%s\n", buf);

info break 用于查看断点信息, 能够看到示例中有一个断点ID为6的断点信息; 使用delete删除断点时,默认为删除所有断点信息,可以使用y 或 n 决定是否删除。同时也可以直接使用delete删除断点的时候直接通过断点ID删除指定的断点。

(gdb) info break
Num Type Disp Enb Address What
6 breakpoint keep y 0x0000000000400608 in main at test.c:21
breakpoint already hit 1 time
(gdb) delete
Delete all breakpoints? (y or n) n
(gdb) delete 6
(gdb) info break
No breakpoints or watchpoints.
(gdb)

7,查看变量或赋值变量
print 用于查看变量的数据内容以及可以设置变量的数据,这在我们调试程序的时候非常的实用。

(gdb) list
3 #include <stdlib.h>
4 #include <string.h>
5 int gval = 100;
6 int mycopy(char *buf)
7 {
8 strcpy(buf, "我爱我的祖国");
9 return gval;
10 }
11 int main()
12 {
(gdb) print gval
$1 = 145
(gdb) print gval=300
$2 = 300
(gdb) print gval
$3 = 300
(gdb)

8,查看程序调用栈信息
backtrace 用于查看调用栈信息,从示例中可以看出在程序因为异常错误退出时,调用栈顶函数为mycopy 函数,则可以认为程序退出是在mycopy 函数中出现了某个错误(因为程序运行在这个函数中的时候还没有来得及运行完函数然后出栈函数,就退出了)。

(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/san/doc/./test
gval:100
gval:100
....
Program received signal SIGSEGV, Segmentation fault.
0x000000000040057c in mycopy (buf=0x0) at test.c:8
8 strcpy(buf, "我爱我的祖国");
(gdb) backtrace
#0 0x000000000040057c in mycopy (buf=0x0) at test.c:8
#1 0x0000000000400628 in main () at test.c:23

46,对称加密与非对称加密

对称加密: 加密和解密的秘钥使用的是同一个.
非对称加密: 与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。

47,Qt信号与槽实现

信号和槽执行流程:
moc查找头文件中的signals,slots,标记出信号和槽
将信号槽信息存储到类静态变量staticMetaObject中,并且按声明顺序进行存放,建立索引。
当发现有connect连接时,将信号槽的索引信息放到一个map中,彼此配对。
当调用emit时,调用信号函数,并且传递发送信号的对象指针,元对象指针,信号索引,参数列表到active函数
通过active函数找到在map中找到所有与信号对应的槽索引
根据槽索引找到槽函数,执行槽函数

48,auto和decltype的区别

auto 和 decltype 都会自动推导出变量 varname 的类型:
auto 根据=右边的初始值 value 推导出变量的类型;
decltype 根据 exp 表达式推导出变量的类型,跟=右边的 value 没有关系。

另外,auto 要求变量必须初始化,也就是在定义变量的同时必须给它赋值;而 decltype 不要求,初始化与否都不影响变量的类型。这很容易理解,因为 auto 是根据变量的初始值来推导出变量类型的,如果不初始化,变量的类型也就无法推导了。

auto 将变量的类型和初始值绑定在一起,而 decltype 将变量的类型和初始值分开;虽然 auto 的书写更加简洁,但 decltype 的使用更加灵活。

49,左值引用与右值引用

左值和右值的概念:

int a; // a 为左值
a = 3; // 3 为右值
  • 左值是可寻址的变量,有持久性;
  • 右值一般是不可寻址的常量,或在表达式求值过程中创建的无名临时对象,短暂性的。
  • 左值和右值主要的区别之一是左值可以被修改,而右值不能。

左值引用和右值引用:

左值引用:引用一个对象;
右值引用:就是必须绑定到右值的引用,C++11中右值引用可以实现“移动语义”,通过 && 获得右值引用。

int x = 6; // x是左值,6是右值
int &y = x; // 左值引用,y引用x

int &z1 = x * 6; // 错误,x*6是一个右值
const int &z2 = x * 6; // 正确,可以将一个const引用绑定到一个右值

int &&z3 = x * 6; // 正确,右值引用
int &&z4 = x; // 错误,x是一个左值

右值引用和相关的移动语义是C++11标准中引入的最强大的特性之一,通过std::move()可以避免无谓的复制,提高程序性能。
std::move并不能移动任何东西,它唯一的功能是将一个左值强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义。从实现上讲,std::move基本等同于一个类型转换:static_cast<T&&>(lvalue);

50,函数模板与类模板(全特化与偏特化)

模板特化的分类

针对特化的对象不同,分为两类:函数模板的特化和类模板的特化

  • 函数模板的特化

当函数模板需要对某些类型进行特化处理,称为函数模板的特化。

  • 类模板的特化

当类模板内需要对某些类型进行特别处理时,使用类模板的特化。

特化整体上分为全特化和偏特化

  • 全特化

就是模板中模板参数全被指定为确定的类型。

全特化也就是定义了一个全新的类型,全特化的类中的函数可以与模板类不一样。

  • 偏特化

就是模板中的模板参数没有被全部确定,需要编译器在编译时进行确定。

全特化的标志就是产生出完全确定的东西,而不是还需要在编译期间去搜寻适合的特化实现,貌似在我的这种理解下,全特化的 东西不论是类还是函数都有这样的特点,
模板函数只能全特化,没有偏特化(以后可能有)。

详见:

​​模板的全特化与偏特化​​

51,CAS自旋锁(无锁队列)

  • CAS自旋锁

CAS操作——Compare And Set,或是 Compare & Swap,现在几乎所有的CPU指令都支持CAS的原子操作,X86下对应的是 CMPXCHG 汇编指令。有了这个原子操作,我们就可以用其来实现各种无锁(lock free)的数据结构。

这个操作用C语言来描述就是下面这个样子:(代码来自Wikipedia的Compare And Swap词条)意思就是说,看一看内存*reg里的值是不是oldval,如果是的话,则对其赋值newval。

int compare_and_swap (int* reg, int oldval, int newval)
{
int old_reg_val = *reg;
if (old_reg_val == oldval)
*reg = newval;
return old_reg_val;
}
  • 无锁队列的链表实现

下面的东西主要来自John D. Valois 1994年10月在拉斯维加斯的并行和分布系统系统国际大会上的一篇论文——《Implementing Lock-Free Queues》。

我们先来看一下进队列用CAS实现的方式:

EnQueue(x) //进队列
{
//准备新加入的结点数据
q = new record();
q->value = x;
q->next = NULL;

do {
p = tail; //取链表尾指针的快照
} while( CAS(p->next, NULL, q) != TRUE); //如果没有把结点链在尾指针上,再试

CAS(tail, p, q); //置尾结点
}

我们可以看到,程序中的那个 do- while 的 Re-Try-Loop。就是说,很有可能我在准备在队列尾加入结点时,别的线程已经加成功了,于是tail指针就变了,于是我的CAS返回了false,于是程序再试,直到试成功为止。这个很像我们的抢电话热线的不停重播的情况。

你会看到,为什么我们的“置尾结点”的操作(第12行)不判断是否成功,因为:

如果有一个线程T1,它的while中的CAS如果成功的话,那么其它所有的 随后线程的CAS都会失败,然后就会再循环,
此时,如果T1 线程还没有更新tail指针,其它的线程继续失败,因为tail->next不是NULL了。
直到T1线程更新完tail指针,于是其它的线程中的某个线程就可以得到新的tail指针,继续往下走了。

详见​​无锁队列实现原理​​