C++的部分面试题

本文详细探讨了C++面试中的关键知识点,涵盖基本语言特性、容器与算法、类与数据抽象、面向对象编程、编译与底层原理、C++11新特性等多个方面。深入讨论了变量类型、指针、函数、类、智能指针、内存管理、多态等核心概念,并解释了如volatile关键字、野指针、虚函数表等高级主题,还涉及到C++中的内存布局和内存区域划分。
摘要由CSDN通过智能技术生成

C++面试题

一.基本语言:
1.变量和基本类型
2.数组和指针
3.表达式
4.语句
5.函数

二.容器和算法
1.关联容器
2.顺序容器
3.泛型算法

三.类和数据抽象
1.类
2.复制控制
3.重载操作符和转换

四.面向对象和泛型编程
1.面向对象编程
2.模板与泛型编程

五.编译与底层
1.编译机制
2.底层原理和内存原理
3.高性能IO

六.C++11 新特性


1.谈谈C++中类的访问权限

在c++中通过关键字public,private,protect来定义成员变量和成员函数,在类的内部,各个函数之间可以相互访问,无论什么是共有的,私有的,还是受保护的,但是再类外,只能通过对象,调用公有的成员函数

2.Redis的定时机制怎么实现的
redis是一个单线程的事件模型。分为文件事件和时间事件,其定时机制通过时间事件实现。

3.什么情况下采用inline定义内联函数:
函数代码少,频繁调用时。

4.函数的定义不可以嵌套,但函数的调用可以嵌套

5.二维数组初始化默认为0

6.简单变量作为实参时,将把该变量所占内存单元的值传递给形参,实参和形参各占不同的内存单元,传递完后,实参和形参不再有任何联系,所以这种传递方式也叫做单向值传递方式。

7.子类型必须是子类继承了父类的所有可继承特性,也即公有继承,才能说是子类型,否则就只是单纯的子类。

8.(1)volatile int *p=(int *)0xaae0275c;*p=1
通过一个指针向其指向的内存地址写入数据。
*(2)volatile int p=(int )0xaae0275c;p[0]=1
通过一个指针向其指向的内存地址写入数据。

(3)(volatile int )0xaae0275c=1
这行代码其实和上面的两行代码没有本质的区别。先将地址0xaae0275c强制转换,告诉编译器这个地址上将存储一个int类型的数据;然后通过钥匙“
”向这块内存写入一个数据。

(4)(volatile int *)0xaae0275c[0]=1
[]优先级不同,在这里是先0xaae0275c[0]取出这个地址的值,然后将值强制转换成int 指针,所以左边也是一个数值,将一个右值赋给一个左值,显然行不通。类似指针,int p; p=1不合理

volatile关键字,顺便介绍一下 :
(1)用来同步,因为同一个东西可能在不同的存储介质中有多个副本,有些情况下会使得这些副本中的值不同,这是不允许的,所以干脆用volatile,让它只 有一个,没有其他的副本,这样就不会发生不同步的问题。
(2)防止编译器优化去掉某些语句,像我在arm中见到个寄存器非常奇怪,当中断来的时候,相对应的位置1,而清0又不能向这位写0,向这位写1才是1才 是清中断(清0),
// 假设0x560012300 为寄存器地址
#define INTPAND (volatile unsigned int )0x560012300
INTPAND = INTPAND; // 清中断
像编译器如果看到有INTPAND = INTPAND;这种看似无用的操作,如果没有volatile说明,编译器就很有可能会去掉INTPAND = INTPAND;实际上有用的东 西,却被编译器当没用的东西优化掉了。
(3)当地址是io端口的时候,读写这个地址是不能对它进行缓存的,这是相对于某些嵌入式中有
*才有这个。比如写这个io端口的时候,如果没有这个 volatile,很可能由于编译器的优化,会先把值先写到一个缓冲区,到一定时候 再写到io端口,这样就不能使数据及时的写到io端口,有了volatile说明以后, 就不会再经过***,write buffer这种,而是直接写到io端口,从而避免了读写 io端口的延时。

指针和引用的区别:
1.指针有自己的一块空间,而引用只是一个别名;
2.使用sizeof看一个指针的大小是4,而引用则是被引用对象的大小;
3.指针可以被初始化为NULL,而引用必须被初始化且必须是一个已有对象 的引用;
4.作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引 用的修改都会改变引用所指向的对象;
5.可以有const指针,但是没有const引用;
6.指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能 被改变;
7.指针可以有多级指针(**p),而引用至于一级;
8.指针和引用使用++运算符的意义不一样;
9.如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。

请你说一下你理解的c++中的smart pointer四个智能指针:
shared_ptr,unique_ptr,weak_ptr,auto_ptr:
C++里面的四个智能指针: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三个是c++11支持,并且第一个已经被11弃用。

为什么要使用智能指针:
智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。
请回答一下数组和指针的区别
在这里插入图片描述
请问野指针是什么?
野指针就是指向一个已删除的对象或者未申请访问受限内存区域的指针。

C++中的智能指针:
智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。C++ 11中最常用的智能指针类型为shared_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。该引用计数的内存在堆上分配。当新增一个时引用计数加1,当过期时引用计数减一。只有引用计数为0时,智能指针才会自动释放引用的内存资源。对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类。可以通过make_shared函数或者通过构造函数传入普通指针。并可以通过get函数获得普通指针。

智能指针有没有内存泄露的情况:
当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。

如何解决内存泄露的问题:
为了解决循环引用导致的内存泄漏,引入了weak_ptr弱指针,weak_ptr的构造函数不会修改引用计数的值,从而不会对对象的内存进行管理,其类似一个普通指针,但不指向引用计数的共享内存,但是其可以检测到所管理的对象是否已经被释放,从而避免非法访问。

请你说一下函数指针:
1、定义
函数指针是指向函数的指针变量。 int *p(int)
函数指针本身首先是一个指针变量,该指针变量指向一个具体的函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。
C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是大体一致的。
2、用途:
调用函数和做函数的参数,比如回调函数。
3、示例:

char * fun(char * p)  {}       // 函数fun
char * (*pf)(char * p);             // 函数指针pf
pf = fun;                        // 函数指针pf指向函数fun
pf(p);                        // 通过函数指针pf调用函数fun

请你来说一下fork函数:
Fork:创建一个和当前进程映像一样的进程可以通过fork( )系统调用:

说一下C++中析构函数的作用:
析构函数与构造函数对应,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统会自动执行析构函数。
析构函数名也应与类名相同,只是在函数名前面加一个位取反符,例如stud( ),以区别于构造函数。它不能带任何参数,也没有返回值(包括void类型)。只能有一个析构函数,不能重载。
如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数(即使自定义了析构函数,编译器也总是会为我们合成一个析构函数,并且如果自定义了析构函数,编译器在执行时会先调用自定义的析构函数再调用合成的析构函数),它也不进行任何操作。所以许多简单的类中没有用显式的析构函数。
如果一个类中有指针,且在使用的过程中动态的申请了内存,那么最好显示构造析构函数在销毁类之前,释放掉申请的内存空间,避免内存泄漏。
类析构顺序:1)派生类本身的析构函数;2)对象成员析构函数;3)基类析构函数。

说一下静态函数和虚函数的区别:
静态函数在编译的时候就已经确定运行时机。
虚函数在运行的时候动态绑定。虚函数因为用了虚函数表机制,调用的时候会增加一次内存开销。

说一说重载和覆盖:
重载:两个函数名相同,但是参数列表不同(个数,类型),返回值类型没有要求,在同一作用域中。
重写:子类继承了父类,父类中的函数是虚函数,在子类中重新定义了这个虚函数,这种情况是重写。
说一说Static关键字
1.加了static关键字的全局变量只能在本文件中使用。例如在a.c中定义了static int a=10;那么在b.c中用extern int a是拿不到a的值,a的作用域只在a.c中。
2.static定义的静态局部变量分配在数据段上,普通的局部变量分配在栈上,会因为函数栈帧的释放而被释放掉。
3. 对一个类中成员变量和成员函数来说,加了static关键字,则此变量/函数就没有了this指针了,必须通过类名才能访问。

说一说strcpy和strlen
strcpy是字符串拷贝函数,原型:
char strcpy(char dest, const char *src);
从src逐字节拷贝到dest,直到遇到’\0’结束,因为没有指定长度,可能会导致拷贝越界,造成缓冲区溢出漏洞,安全版本是strncpy函数。
strlen函数是计算字符串长度的函数,返回从开始到’\0’之间的字符个数。

说一下你理解的虚函数和多态:
多态的实现主要分为静态多态和动态多态,静态多态主要是重载,在编译的时候就已经确定;动态多态是用虚函数机制实现的,在运行期间动态绑定。
举个例子:一个父类类型的指针指向一个子类对象时候,使用父类的指针去调用子类中重写了的父类中的虚函数的时候,会调用子类重写过后的函数,在父类中声明为加了virtual关键字的函数,在子类中重写时候不需要加virtual也是虚函数。
虚函数的实现:在有虚函数的类中,类的最开始部分是一个虚函数表的指针,这个指针指向一个虚函数表,表中放了虚函数的地址,实际的虚函数在代码段(.text)中。当子类继承了父类的时候也会继承其虚函数表,当子类重写父类中虚函数时候,会将其继承到的虚函数表中的地址替换为重新写的函数地址。使用了虚函数,会增加访问内存开销,降低效率。

回答一下++i和i++的区别:
++i先自增1,再返回,i++先返回i,再自增1。

++i和i++的实现:

  1. ++i 实现:
int&  int::operator++()
{
*this +=1return *this}
  1. i++ 实现:
const int  int::operatorint{
int oldValue = *this++*this);
return oldValue;
}

以下四行代码的区别是什么? const char * arr = “123”; char * brr = “123”; const char crr[] = “123”; char drr[] = “123”;
const char * arr = “123”;
//字符串123保存在常量区,const本来是修饰arr指向的值不能通过arr去修改,但是字符串“123”在常量区,本来就不能改变,所以加不加const效果都一样
char * brr = “123”;
//字符串123保存在常量区,这个arr指针指向的是同一个位置,同样不能通过brr去修改"123"的值
const char crr[] = “123”;
//这里123本来是在栈上的,但是编译器可能会做某些优化,将其放到常量区
char drr[] = “123”;
//字符串123保存在栈区,可以通过drr去修改。

请你说一下c++是怎么定义常量的?常量存放在内存的哪个位置?
对于局部常量,存放在栈区;对于全局常量,编译期一般不分配内存,放在符号表中以提高访问效率;字面值常量,比如字符串,放在常量区。

Const修饰成员函数的目的是什么?
const修饰的成员函数表明函数调用不会对对象做出任何更改,事实上,如果确认不会对对象做更改,就应该为函数加上const限定,这样无论const对象还是普通对象都可以调用该函数。

如果同时定义了两个函数,一个带const,一个不带,会有问题吗?
不会,这相当于函数的重载。

说一说隐式类型的转换:
首先,对于内置类型,低精度的变量给高精度变量赋值会发生隐式类型转换。
其次,对于只存在单个参数的构造函数的对象构造来说,函数调用可以直接使用该参数传入,编译器会自动调用其构造函数生成临时对象。

说说你了解的类型转换:
reinterpret_cast:可以用于任意类型的指针之间的转换,对转换的结果不做任何保证
dynamic_cast:这种其实也是不被推荐使用的,更多使用static_cast,dynamic本身只能用于存在虚函数的父子关系的强制类型转换,对于指针,转换失败则返回nullptr,对于引用,转换失败会抛出异常
const_cast:对于未定义const版本的成员函数,我们通常需要使用const_cast来去除const引用对象的const,完成函数调用。另外一种使用方式,结合static_cast,可以在非const版本的成员函数内添加const,调用完const版本的成员函数后,再使用const_cast去除const限定。
static_cast:完成基础数据类型;同一个继承体系中类型的转换;任意类型与空指针类型void* 之间的转换。

C++函数栈空间的最大值:
默认是1M,不过可以调整。

说一说extern “C”:
C++调用C函数需要extern C,因为C语言没有函数重载。

回答一下new/delete与malloc/free的区别是什么:
首先,new/delete是C++的关键字,而malloc/free是C语言的库函数,后者使用必须指明申请内存空间的大小,对于类类型的对象,后者不会调用构造函数和析构函数。

虚函数表具体是怎样实现运行时多态的?
子类若重写父类虚函数,虚函数表中,该函数的地址会被替换,对于存在虚函数的类的对象,在VS中,对象的对象模型的头部存放指向虚函数表的指针,通过该机制实现多态。

C语言参数压栈顺序?
从右到左

C++如何处理返回值?
生成一个临时变量,把它的引用作为函数参数传入函数内。
回答一下C++中拷贝赋值函数的形参能否进行值传递?
不能。如果是这种情况下,调用拷贝构造函数的时候,首先要将实参传递给形参,这个传递的时候又要调用拷贝构造函数。。如此循环,无法完成拷贝,栈也会满。

请你回答一下malloc与new区别
malloc需要给定申请内存的大小,返回的指针需要强转。
new会调用构造函数,不用指定内存大小,返回的指针不用强转。

请你说一说重载和覆盖:
重载:两个函数名相同,但是参数列表不同(个数,类型),返回值类型没有要求,在同一作用域中
重写:子类继承了父类,父类中的函数是虚函数,在子类中重新定义了这个虚函数,这种情况是重写。

请你来说一下C++中类成员的访问权限
C++通过 public、protected、private 三个关键字来控制成员变量和成员函数的访问权限,它们分别表示公有的、受保护的、私有的,被称为成员访问限定符。在类的内部(定义类的代码内部),无论成员被声明为 public、protected 还是 private,都是可以互相访问的,没有访问权限的限制。在类的外部(定义类的代码之外),只能通过对象访问成员,并且通过对象只能访问 public 属性的成员,不能访问 private、protected 属性的成员。

请你来说一下C++中struct和class的区别
在C++中,可以用struct和class定义类,都可以继承。区别在于:structural的默认继承权限和默认访问权限是public,而class的默认继承权限和默认访问权限是private。
另外,class还可以定义模板类形参,比如template <class T, int i>。
你回答一下C++类内可以定义引用数据成员吗?
可以,必须通过成员函数初始化列表初始化。

回答一下什么是右值引用,跟左值又有什么区别?
右值引用是C++11中引入的新特性 , 它实现了转移语义和精确传递。它的主要目的有两个方面:

  1. 消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。
  2. 能够更简洁明确地定义泛型函数。

左值和右值的概念:
左值:能对表达式取地址、或具名对象/变量。一般指表达式结束后依然存在的持久对象。
右值:不能对表达式取地址,或匿名对象。一般指表达式结束就不再存在的临时对象。
右值引用和左值引用的区别:

  1. 左值可以寻址,而右值不可以。
  2. 左值可以被赋值,右值不可以被赋值,可以用来给左值赋值。
  3. 左值可变,右值不可变(仅对基础类型适用,用户自定义类型右值引用可以通过成员函数改变)。

说一下一个C++源文件从文本到可执行文件经历的过程?
对于C++源文件,从文本到可执行文件一般需要四个过程:
预处理阶段:对源代码文件中文件包含关系(头文件)、预编译语句(宏定义)进行分析和替换,生成预编译文件。
编译阶段:将经过预处理后的预编译文件转换成特定汇编代码,生成汇编文件
汇编阶段:将编译阶段生成的汇编文件转化成机器码,生成可重定位目标文件
链接阶段:将多个目标文件及所需要的库连接成最终的可执行目标文件。

include头文件的顺序以及双引号””和尖括号<>的区别?
Include头文件的顺序:对于include的头文件来说,如果在文件a.h中声明一个在文件b.h中定义的变量,而不引用b.h。那么要在a.c文件中引用b.h文件,并且要先引用b.h,后引用a.h,否则汇报变量类型未声明错误。
双引号和尖括号的区别:编译器预处理阶段查找头文件的路径不一样。
对于使用双引号包含的头文件,查找头文件路径的顺序为:
当前头文件目录
编译器设置的头文件路径(编译器可使用-I显式指定搜索路径)
系统变量CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定的头文件路径
对于使用尖括号包含的头文件,查找头文件的路径顺序为:
编译器设置的头文件路径(编译器可使用-I显式指定搜索路径)
系统变量CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定的头文件路径
说一说C++的内存管理是怎样的?
在C++中,虚拟内存分为代码段、数据段、BSS段、堆区、文件映射区以及栈区六部分。
代码段:包括只读存储区和文本区,其中只读存储区存储字符串常量,文本区存储程序的机器代码。
数据段:存储程序中已初始化的全局变量和静态变量
bss 段:存储未初始化的全局变量和静态变量(局部+全局),以及所有被初始化为0的全局变量和静态变量。
堆区:调用new/malloc函数时在堆区动态分配内存,同时需要调用delete/free来手动释放申请的内存。
映射区:存储动态链接库以及调用mmap函数进行的文件映射
栈:使用栈空间存储函数的返回地址、参数、局部变量、返回值。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值