面试总结-C++

C++面试题总结

编程基础

C++ 内存管理方式

堆、栈、自由存储区、全局/静态存储区、常量存储区 自由存储区存储malloc申请的内存 (1)从静态存储区域分配 。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如 全局变量, static 变量 。 (2)在栈上创建 。在执行函数时, 函数内局部变量的存储单元都可以在栈上创建 ,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。 (3)从堆上分配 , 亦称动态内存分配 。程序在运行的时候用 malloc 或 new 申请任意多少的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。

extern “C”和extern的作用

extern “C”的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern “C”后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。 (http://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777431.html)

为什么需要内存对齐

c++内存问题

  1. 缓冲区溢出(buffer overrun)。 用 std::vector/std::string 或自己编写 Buffer class 来管理缓冲区,自动记住用缓冲区的长度,并通过成员函数而不是裸指针来修改缓冲区。
  2. 空悬指针/野指针。 用 shared_ptr/weak_ptr
  3. 重复释放(double delete)。
  4. 内存泄漏(memory leak)。
  5. 不配对的 new[]/delete。 把 new[] 统统替换为 std::vector/scoped_array。
  6. 内存碎片(memory fragmentation)。

static关键字

作用:

  • 函数体内 static 变量的作用范围为该函数体,不同于 auto 变量, 该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值
  • 在模块内的 static 全局变量可以被模块内所有函数访问,但不能被模块外其他函数访问
  • 在模块内的 static 函数只可被这一模块内的其他函数调用,这个函数的使用范围被限制在声明它的模块内。起到了隐藏的作用
  • 在类的 static 成员变量属于整个类所拥有,对类的所以对象只有一份拷贝
  • 在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的 static 成员变量静态全局变量不要放在头文件里 (https://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777431.html) 第6条

C++中的static关键字的总结

几个复制的声明

void * ( * (*fp1)(int))[10];  //fp1是一个指针,指向一个函数,函数参数为int,函数返回参数是一个指针,指针指向一个数组,数组中有10个元素,每个元素是一个void* 指针。
float (*(* fp2)(int,int,int))(int);  //fp2是一个指针,指向一个函数,函数参数为3个int,函数的返回值是一个指针,指针指向一个函数,函数的参数是1个int,返回float。 
int (* ( * fp3)())[10]();  //fp3是一个指针,指向一个函数,函数没有参数,函数返回值为一个指针,指针指向一个数组,数组中有10个元素,每个元素是一个函数指针,函数没有参数,返回int。

strlen()和sizeof()

区别与联系:

  1. 种类: sizeof是运算符,并不是函数,结果在编译时得到,因此sizeof不能用来返回动态分配的内存空间的大小。用sizeof来返回类型以及静态分配的对象、结构或数组所占的空间,返回值跟对象、结构、数组所存储的内容没有关系;strlen是字符处理的库函数,当数组名作为参数传入时,实际上数组就退化成指针了。。
  2. 输入参数: sizeof参数可以是任何数据的类型或者数据(sizeof参数不退化),还可以用函数做参数;strlen的参数只能是字符指针且结尾是’\0’的字符串。
  3. 功能: sizeof():获得保证能容纳实现所建立的最大对象的字节大小 strlen():返回字符串的长度。该字符串可能是自己定义的,也可能是内存中随机的,该函数实际完成的功能是从代表该字符串的第一个地址开始遍历,直到遇到结束符NULL。返回的长度大小不包括NULL。
  4. 返回: 当适用于一个结构类型时或变量, sizeof 返回实际的大小, 当适用于一静态地空间数组, sizeof 归还全部数组的尺寸。 sizeof 操作符不能返回动态地被分派了的数组或外部的数组的尺寸

https://blog.csdn.net/21aspnet/article/details/1539951

变量声明和定义

  • 声明仅仅是把变量的声明的位置及类型提供给编译器,并不分配内存空间;定义要在定义的地方为其分配存储空间。
  • 相同变量可以再多处声明(外部变量extern),但只能在一处定义。

结构体和union

1.在存储多个成员信息时,编译器会自动给struct第个成员分配存储空间,struct 可以存储多个成员信息,而Union每个成员会用同一个存储空间,只能存储最后一个成员的信息。

2.都是由多个不同的数据类型成员组成,但在任何同一时刻,Union只存放了一个被先选中的成员,而结构体的所有成员都存在。

3.对于Union的不同成员赋值,将会对其他成员重写,原来成员的值就不存在了,而对于struct 的不同成员赋值 是互不影响的。

未特殊说明时,按结构体中size最大的成员对齐(若有double成员),按8字节对齐。 eg:

struct sTest
{
int a;  //sizeof(int) = 4
char b;  //sizeof(char) = 1
short c; //sizeof(short) = 2       
}x;  #最终实际占用不止4+1+2,因为要考虑内存对齐的问题
union uTest
{
int a;   //sizeof(int) = 4
double b;  //sizeof(double) = 8
char c;  //sizeof(char) = 1
}x;   #分配的内存 size 就是8 byte

pragma pack () 取消指定对齐,恢复缺省对齐

static

malloc/new和free/delete

  • malloc和free是标准库函数,支持覆盖;new和delete是运算符,并且支持重载。
  • malloc仅仅分配内存空间,free仅仅回收空间,不具备调用构造函数和析构函数功能,用malloc分配空间存储类的对象存在风险;new和delete除了分配回收功能外,还会调用构造函数和析构函数。
  • malloc和free返回的是void类型指针(必须进行类型转换),new和delete返回的是具体类型指针。
  • free和delete对应。当delete一个对象数组时,delete只删除了一个,需要使用delete[]

const

宏定义和const函数的区别

  • 宏在编译时完成替换,直接进行替换,执行起来更快,但是可能会存在一些风险;函数调用在运行时需要跳转到具体调用函数。如: 123456#define area(x) x*xint main(){ int y=area(2+2); std::cout<<y<<std::endl; # 输出为8=2+2*2+2,而不是以为的16}
  • 宏函数属于在结构中插入代码,没有返回值;函数调用具有返回值。
  • 宏函数参数没有类型,不进行类型检查;函数参数具有类型,需要检查类型。
  • 一般可以对const常量进行调试,但不能对宏常量进行调试。
  • 宏函数不要在最后加分号。

总之,一般尽量用const比较好。

const的分辨(顶层const和底层const)

一般来说,const的分辨可以直接通过看const的最左侧,如果是指针,则指针是const,若为类型,则变量为const。当const在最左侧时,看const右侧。

  • 常量指针和指针常量: 常量指针是一个指针,读成常量的指针,指向一个只读变量。如int const *pconst int *p。 指针常量是一个常量,指针的值可以改变。如int *const p

指针和引用

区别

  1. 指针是具体的变量,需要占存储空间。引用只是别名,不占用具体存储空间。这是最基本的一点,其他的特点也就可想而知了。
  2. 指针可以先声明,但是引用声明的时候就必须初始化,不存在空的引用很容易理解。
  3. 指针变量可以改变所指的对象。但是引用一旦声明了就不能再改变引用的对象了。

引用相关问题

  1. 引用是某个变量的别名,因此定义的时候必须初始化,也不能把该引用再改成其他变量的别名。
  2. 声明一个引用并没有定义新变量,引用本身不是一种数据类型。也不占用存储空间。
  3. 不能建立数组的引用。其实这句话的意思是:不能建立引用的数组,例如:int & ref[3] = { 2, 3, 5}; 但是可以建立数组的引用:例如:int arr[3]; int (&tef)[3] = arr; 原因是:引用时不占空间的,声明引用数组没法分配空间。见为什么不能建立引用数组
  4. 将引用作为函数的参数时,可以避免对变量或者对象的复制,因此不会调用对象的拷贝构造函数。当不希望传入的引用参数不被改变时,使用const引用。
  5. 函数中不能返回局部变量的引用,不能返回函数内部ne分配的内存的引用。(虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成内存泄露。)。 可以返回类成员的引用,但最好是const。
  6. 当类中存在const或者引用时成员变量时,必须使用初始化表。

指针相关问题

指针的相关判断
    int *p[10]
    int (*p)[10]
    int *p(int)
    int (*p)(int)
```   
  
- int *p[10]表示指针数组,强调数组概念,是一个数组变量,数组大小为10,数组内每个元素都是指向int类型的指针变量。  
    
- int (*p)[10]表示数组指针,强调是指针,只有一个变量,是指针类型,不过指向的是一个int类型的数组,这个数组大小是10。
    
- int *p(int)是函数声明,函数名是p,参数是int类型的,返回值是int *类型的。
    
- int (*p)(int)是函数指针,强调是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。  
##### 指针与数组名
- 二者均可通过增减偏移量来访问数组中的元素。
- 数组名不是真正意义上的指针,可以理解为常指针,所以数组名没有自增、自减等操作。
- 当数组名当做形参传递给调用函数后,就失去了原有特性,退化成一般指针,多了自增、自减操作,但sizeof运算符不能再得到原数组的大小了。
##### 野指针
空悬指针,不是指向null的指针,是指向垃圾内存的指针。  
- 产生原因及解决办法:
    - 指针变量未及时初始化 => 定义指针变量及时初始化,要么置空。
    - 指针free或delete之后没有及时置空 => 释放操作后立即置空。
##### 指针和数组的区别
数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。  
1. 修改内容上的差别:指针可能指向一块内存,但是指向的常量却无法通过下标计算。

char a[] = “hello”; a[0] = ‘X’; char *p = “world”; // 注意p 指向常量字符串,指向的是常量区 p[0] = ‘X’; // 编译器不能发现该错误,运行时错误

1

2. 用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。

char a[] = “hello world”; char *p = a; cout<< sizeof(a) << endl; // 12 字节 cout<< sizeof(p) << endl; // 4 字节 //计算数组和指针的内存容量 void Func(char a[100]) { cout<< sizeof(a) << endl; // 4 字节而不是100 字节 }

### volatile
- volatile定义变量的值是易变的,每次用到这个变量的值的时候都要去重新读取这个变量的值,而不是读寄存器内的备份。
    
- 多线程中被几个任务共享的变量需要定义为volatile类型。  
### 堆栈
#### 区别
1. 申请方式不同:栈由系统自动分配,堆由程序员手动分配  
2. 申请大小不同:栈顶和栈底都是设定好的,大小固定,可以通过`ulimit -a`查看,通过`ulimit -s`修改。堆向高地址扩展,是不连续的内存区域,大小可以调整。    
3. 申请效率不同:栈由系统分配,速度快,没有碎片。堆速度慢,且有碎片。
#### 内存分配
https://blog.csdn.net/nkguohao/article/details/8771867 
## 面向对象
### 面向对象三大特性
- 封装性:数据和代码捆绑在一起,避免外界干扰和不确定性访问。
    
- 继承性:让某种类型对象获得另一个类型对象的属性和方法。
- 多态性:同一事物表现出不同事物的能力,即向不同对象发送同一消息,不同的对象在接收时会产生不同的行为(重载实现编译时多态,虚函数实现运行时多态)。
### 构造函数和析构函数
1.构造函数、析构函数中都不要调用虚函数  
   
我们知道,构造函数一般不能是虚函数,而析构函数一般必须是虚函数。原理也很清晰,构造函数,由于构造顺序是从基类到派生类,所以调用虚函数,可能派生类还没有构造出来,没有意义。而对于析构函数来说,又必须是虚函数,因为只有先从子类对象进行销毁,才能保证资源不泄露。    
   
在构造函数和析构函数中都不要调用虚函数也是这个道理。  
### 成员变量和成员函数
1.静态成员变量是需要初始化  
其实这样说的是有点问题的,应该是静态成员是需要定义的。
因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这是矛盾的。    
《c++primer》里面说在类外定义和初始化是保证static成员变量只被定义一次的好方法。 但static const int就可以在类里面初始化

class Base{ public: static int class_p; //只有声明,而没有定义,不能直接调用 }; int Base::class_p=3; //进行定义 https://blog.csdn.net/qq_16209077/article/details/52602601 ```

拷贝构造函数

调用情况:

  1. 用一个类的对象去初始化该类的另一个对象时。
  2. 函数形参是类的对象时,调用函数将函数的形参和实参结合的时候。
  3. 函数返回值是类的对象,函数调用完成返回时。

重写拷贝构造函数

一般会默认生成类的拷贝构造函数,但是当涉及动态分配存储空间时,默认的拷贝构造函数就会有问题,因此需要重写拷贝构造函数,并且采用深拷贝。 浅拷贝和深拷贝:

多态

多态:对于不同对象接收相同消息时产生不同的动作。C++的多态性具体体现在运行和编译两个方面: 编译时多态:函数和运算符的重载。 运行时多态:继承和虚函数。

友元

特性:单向的,传递性,不能继承

标准模板库

编译和调试

编译过程

预处理->编译->汇编->链接

  • 预处理:展开宏定义;处理条件编译;处理#include指令;去掉注释;添加行号和文件名标识;保留所有#pragma编译器指令。
  • 编译:词法分析;语法分析;语义分析;中间语言生成;目标代码生成与优化。
  • 链接:各个源代码模块独立的被编译,然后将他们组装起来成为一个整体,组装的过程就是链接。被链接的各个部分本本身就是二进制文件,所以在被链接时需要将所有目标文件的代码段拼接在一起,然后将所有对符号地址的引用加以修正。静态库和动态库 二者的不同点在于代码被载入的时刻不同。静态库和动态库的最大区别,静态情况下,把库直接加载到程序中,而动态库链接的时候,它只是保留接口,将动态库与程序代码独立,这样就可以提高代码的可复用度,和降低程序的耦合度。
  • 静态库的代码在编译过程中已经被载入可执行程序,程序运行时将不再需要该静态库,因此可执行程序体积比较大。在Linux中以.a结尾
  • 动态库(共享库)的代码在可执行程序运行时才载入内存,在编译过程中仅简单的引用,因此代码体积比较小,在程序运行时还需要动态库存在。不同的应用程序如果调用相同的库,那么在内存中只需要有一份该动态库(共享库)的实例。在Linux中以.so结尾 当静态库和动态库同名时, gcc命令将优先使用动态库.为了确保使用的是静态库, 编译时可以加上 -static 选项,因此多第三方程序为了确保在没有相应动态库时运行正常,喜欢在编译最后应用程序时加入-static 优缺点: 1.动态库运行时会先检查内存中是否已经有该库的拷贝,若有则共享拷贝,否则重新加载动态库(C语言的标准库就是动态库)。静态库则是每次在编译阶段都将静态库文件打包进去,当某个库被多次引用到时,内存中会有多份副本,浪费资源。 2.动态库更新很容易,当库发生变化时,接口没变只需要用新的动态库替换掉就可以。静态库需要重新编译。 3.静态库静态库一次性完成了所有内容的绑定,运行时就不必再去考虑链接的问题了,执行效率会高一些。

安全相关

类型安全

类型安全很大程度上可以理解为内存安全。类型安全的代码不会试图去访问自己没有被授权的内存区域。 对于C语言来说,很多操作都不是类型安全的。例如打印的时候:printf("%f\n",10) //编译通过,没有报错,结果为0.000000. 对于C++来说,有些操作也不是类型安全的,比如不同类型指针之间可以强制转换(reinterpret cast) 注:C#、Java是类型安全的 C++使用得当,可以远比C更有类型安全性。 (1)操作符new返回的指针类型严格与对象匹配,而不是void (2)C中很多以void为参数的函数可以改写为C++模板函数,而模板是支持类型检查的; (3)引入const关键字代替#define constants,它是有类型、有作用域的,而#define constants只是简单的文本替换; (4)一些#define宏可被改写为inline函数,结合函数的重载,可在类型安全的前提下支持多种类型,当然改写为模板也能保证类型安全; (5)C++提供了dynamic_cast关键字,使得转换过程更加安全,因为dynamic_cast比static_cast涉及更多具体的类型检查。

线程安全

如果代码在多线程运行和单线程运行具有相同的结果,那就是线程安全的。 线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

异常安全

当异常抛出时,带有异常安全的函数会: (1)不泄露任何资源 一般采用RAII技术,即以对象管(智能指针)理资源来防止资源泄漏。 (2)不允许数据被破坏(例如正常指针变野指针) (3)少些try catch,因为大量的try catch会影响代码逻辑。导致代码丑陋混乱不优雅 解决异常安全的问题: 1.多使用RAII,使用智能指针来管理内存。由于unwind机制的保证,当异常发生时,函数栈内已构造的局部对象的析构函数会被一一调用,在析构函数内释放资源,也就杜绝了内存泄漏的问题。 2.做好程序设计。特别是异常发生时的回滚机制的正确使用,copy-and-swap是有效的方法。 3.注意需要异常保证的函数内部的调用函数,异常安全等级是以有最低等级异常保证的函数确定的。一个系统即使只有一个函数不是异常安全的,那么系统作为一个整体就不是异常安全的。 4.流对象,资源对象,new对象,不应该直接作为参数,一旦抛出异常,就可能会导致严重的问题,函数也许会被错误的执行,资源也许会泄漏。 5.减少全局变量的使用。 6.如果不知道如何处理异常,就不要捕获异常,直接终止比吞掉异常不处理要好。 7.保证构造、析构、swap不会失败

类型安全 与 线程安全 、异常安全、事务安全

其他

为什么说栈比堆要快

  1. 分配和释放:堆在分配和释放时都要调用函数(MALLOC,FREE),比如分配时会到堆空间去寻找足够大小的空间(因为多次分配释放后会造成空洞),这些都会花费一定的时间,具体可以看看MALLOC和FREE的源代码,他们做了很多额外的工作,而栈却不需要这些。
  2. 访问时间,访问堆的一个具体单元,需要两次访问内存,第一次得取得指针,第二次才是真正得数据,而栈只需访问一次。
  3. 堆的内容被操作系统交换到外存的概率比栈大,栈一般是不会被交换出去的。

c++协程的实现

ucontext-人人都可以实现的简单协程库

设计模式

单例模式

工厂方法

观察者模式

怎么判断两个结构体变量是否相等?

1,元素的话,一个个比咯:if(p1->age==p2->age)…有一个元素不等,即是两个实例不相等!没什么效率高的方法吧!

2,指针直接比较,如果保存的是同一个实例地址,则(p1==p2)为真!

3,重载==运算符;

###

Refeence:

常见C++面试题

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Python爬虫与数据挖掘

Python正则表达式初识(八)

继续分享Python正则表达式的基础知识,今天给大家分享的特殊字符是“\w”和“\W”,具体的教程如下。

1255
来自专栏微信公众号:Java团长

Java动态代理原理及解析

代理模式是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个真实对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类...

984
来自专栏北京马哥教育

Python 面试问答 Top 25

Python 是一种解释型,交互式,面向对象的高级编程语言。和别的一些使用标点符号的语言不同,Python使用了大量的英语单词作为关键字,因而具有很好的可读性。...

1353
来自专栏xx_Cc的学习总结专栏

C - 基础总结

36311
来自专栏小灰灰

Shell学习笔记

1. 变量 声明变量 以 a-zA-Z 开头,不包含特殊字符 等号两边没有空格 不与保留字符重名 PATH="/user/yihui" 使用 变量前加 $ 符号...

20010
来自专栏阮一峰的网络日志

在PHP语言中使用JSON

我写过一篇《数据类型和JSON格式》,探讨它的设计思想。今天,我想总结一下PHP语言对它的支持,这是开发互联网应用程序(特别是编写API)必须了解的知识。

1343
来自专栏软件开发 -- 分享 互助 成长

C++ STL stack和queue

C++ STL中独立的序列式容器只有vector,list,deque三种,stack和queue其实就是使用容器适配器对deque进行了封装,使用了新接口。 ...

2039
来自专栏Python爬虫与数据挖掘

Python正则表达式初识(八)

继续分享Python正则表达式的基础知识,今天给大家分享的特殊字符是“\w”和“\W”,具体的教程如下。

1002
来自专栏magicsoar

C++获取private的变量-偷走private

private提供了对数据的封装,使得private成员只能被类自身的成员函数以及类的友元访问,其他的函数或者类想要访问private成员只能通过该类所提供的s...

18510
来自专栏liulun

Nim教程【十五】【完结】

模版 模版是Nim语言中的抽象语法树,它是一种简单的替换机制,在编译期被处理 这个特性使Nim语言可以和C语言很好的运行在一起 像调用一个方法一样调用一个模版 ...

2348

扫码关注云+社区

领取腾讯云代金券