C++ 常见面试题

本文探讨了C++中的sizeof和strlen的区别,volatile的作用,const和volatile的组合,悬挂指针与野指针,typedef与define的区别,面向对象的三大特性,多态实现,构造函数和析构函数的异常处理,RTTI原理,以及内存分配问题如迭代器种类和处理malloc/new返回空指针的方法。
摘要由CSDN通过智能技术生成
1、sizeof和strlen的区别

        - sizeof是一个操作符,strlen是库函数

        - sizeof的参数可以是数据的类型,也可以是变量,而strlen只能是以'\0'结尾的字符串

        - 编译器在编译时就计算出了sizeof的结果,而strlen函数必须在运行时才能计算出结果,sizeof计算的是数据类型占用的内存大小,而strlen计算的是字符串实际的长度

        - 数组做sizeof的参数不退化,传递给strlen就退化为指针了

2、volatile有什么作用

        - volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改。volatile提醒编译器它后面所定义的变量随时都可能改变,因此编译后的程序每次需要存储或者读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新的话,将出现不一致的现象。所以遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

3、一个参数可以即是const又是volatile吗

        - 可以,用const和volatile同时修饰的变量,表示这个变量在程序内部是只读的,不能改变,只在程序外部条件变化下改变,并且编译器不会优化这个变量。每次使用这个变量时,都要小心地去内存读取这个变量的值,而不是去寄存器读取他的备份。

注意:在此一定要注意const的意思,const只是不允许程序中的代码改变某一变量,其在编译期发挥作用,它并没有实际的禁止某段内存的读写特性。

用gcc编译器是,取const变量的地址可以改变 变量的值,g++取const变量的地址改变 变量的值会编译出错。

4、悬挂指针和野指针有什么区别

        - 悬挂指针:当指针所指向的对象被释放,但是该指针没有任何改变,以至于其仍然指向已经被回收的内存地址,这种情况下该指针被称为悬挂指针。

        - 野指针:未初始化的指针

5、typedef和define有什么区别

        - 用法不同:typedef用来定义一种数据类型的别名,增强程序的可读性。define主要用来定义常量,以及书写复杂使用频繁的宏。

        - 执行实时机不同:typedef是编译过程的一部分,有类型检查的功能。define是宏定义,是预编译的部分,发生在编译之前,只是简单的进行字符串的替换,不进行类型的检查。

        - 作用域不同:typedef有作用域限定。define不受作用域约束,只要是在define声明后的引用都是正确的。

6、面向对象的三大特征

        - 封装:将客观事物封装成抽线的类,而类可以把自己的数据和方法暴露给可信的类或者对象,对不可信的类或者对象则进行信息隐藏。

        - 继承:可以使用现有类的所以功能,并且无需重新编写原来的类即可对功能进行拓展。

        - 多态:一个类实例的相同方法在不同情形下有不同的表现形式,使不同内部结构的对象可以共享相同的外部接口。

7、多态的实现有哪几种

        - 多态分为静态多态和动态多态。

        - 静态多态是通过重载和模板技术实现,在编译期间确定;

        - 动态多态是通过虚函数和继承关系实现的,执行动态绑定,在运行期间确定。

8、构造函数和析构函数可以抛出异常吗

        - 从语法上来说,构造函数和析构函数都可以抛出异常。但从逻辑上和风险控制上,构造函数和析构函数中尽量不要抛出异常,万不得已,一定要注意防止资源泄露,在析构函数中抛出异常还要注意栈展开带来的程序奔溃。

        - 在构造函数中抛出异常,在概念上将被视为该对象没有被成功构造,因此当前对象的析构函数就不会被调用。同时,由于构造函数本身也是一个函数,在函数体内抛出异常将导致当前函数运行的结束,并释放已经构造的成员对象,当然包含其基类的成员,要执行直接基类的和成员对象的析构函数。

        - 如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成资源泄露的问题。

        - 通常异常发生时,c++的异常处理机制在异常的传播过程中会进行栈展开,因发生异常而逐步退出符合语句和函数定义的过程,被称为栈展开。在栈展开过程中就会调用已经在栈上构造好的对象的析构函数来释放资源,此时若其他析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序奔溃。

9、RTTI时什么?其原理时什么?

        - RTTI即运行时类型识别,其功能由两个运算符实现:

        - typeid运算符,用于返回表达式的类型,可以通过基类的指针获取派生类的数据类型。

        - dynamic_cast运算符,具有类型检查的功能,用于将基类的指针或引用安全地转换成派生类的指针或引用。

10、迭代器的种类

        - 输入迭代器:是只读迭代器,在每个被遍历的位置上只能读取一次。例如find函数的参数就是输入迭代器。

        - 输出迭代器:是只写迭代器,在每个被遍历的位置上只能被写一次。

        - 向前迭代器:兼具输入和输出迭代器的能力,但是它可以对同一个位置重复进行读和写。但它不支持operator-,所以只能向前移动。

        - 双向迭代器:很像向前迭代器,只是它向后移动和向前移动一样。

        - 随机访问迭代器:有双休迭代器的所有功能。而且,它提供了“迭代器算术”,即在一步内可以向前或向后跳跃任意位置,包含指针的所以操作,可以进行随机访问,随意移动指定的步数。支持前面四种迭代器的所以操作。

11、内存块太小导致malloc和new返回空指针,该怎么处理

        - 对于malloc来说,需要判断其是否返回空指针,如果是则马上用return语句终止该函数或者exit终止该函数。

        - 对于new来说,默认抛出异常,所以可以用try catch代码块的方式;

try {

int *p = new int[1000000000];

}

catch(bad_alloc & memExp)

{

std::cout << memExp.what() << std::endl;

}

还可以使用set_new_handler函数的方式:

void no_more_memory() {
  cerr << "Unable to satisfy request for memory" << endl;
  abort();
}
int main() {
  set_new_handler(no_more_memory);
  int *ptr = new int[10000000];
}

这种方式,如果new不能满足内存分配请求,no_more_memroy会被反复调用,所以new_handler函数必须完成一下事情:

        - 让更多的内存可被使用:可以在程序一开始执行就分配一大块内存,之后当new_handler第一次被调用的时候,就将这些内存释放还给程序使用。

        - 使用另一个new_handler

        - 卸除new_handler,返回空指针,这样new就会抛出异常

        - 直接排除bad_allloc异常

        - 调用abort或者exit 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值