C++面试问题

1.面向对象的特性

  封装:把抽象的客观事物封装类,包括属性和方法,访问权限等,进行信息隐藏。(优点:可以隐藏实现细节,使得代码模块化)

  继承:利用类的已有的功能,对这些功能进行扩展。(优点:可以扩展已存在的代码模块(类))构造函数、复制构造函数、析构函数、赋值运算符不能被继承。

  多态:不同对象,接受同样消息,表现不同行为。

2.堆和栈

  栈区(stack)由编译器自动分配释放,存放函数的参数值,局部变变量的值等,其操作方式类似于数据结构中的栈可静态亦可动态分配

堆区(heap)一般由程序员分配释放,若程序员不释放,可能造成内存泄漏,程序结束时可能由OS回收。只可动态分配分配方式类似于链表。

全局静态存储区:存放全局变量和静态变量。包括全局初始化DATA段和全局未初始化BSS段。系统自动释放。

文字常量区:字符串

程序代码区:存放函数体的二进制代码。 

3.malloc和new

1.malloc与free是C++/C语言的标准库函数stdlib.h,new/delete是C++的运算符。但它们都可用于申请动态内存和释放内存。

2.对于非内部数据类型的对象而言,用maloc/free无法满足动态对象的要求。因为对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。 

3.new可以认为是malloc加构造函数的执行。new出来的指针对象指针。而malloc返回的都是void*指针。new delete在实现上其实调用了malloc,free函数。

4.new 建立的是一个对象;malloc分配的是一块内存。

4.虚函数实现机制,虚继承在sizeof中有没有影响,构造函数能否为虚函数,与纯虚函数

  编译器会为每个有虚函数的类创建一个虚函数表,该虚函数表将被该类的所有对象共享。类的每个虚函数占据虚函数表中的一块。如果类中有N个虚函数,那么其虚函数表将有N*4字节的大小。

  在有虚函数的类的实例中分配了指向这个表的指针的内存,所以,当用父类的指针来操作一个子类的时候,这张虚函数表指明了实际所应该调用的函数。

特别的

  (1)当存在类继承并且析构函数中有必须要进行的操作时(如需要释放某些资源,或执行特定的函数)析构函数需要是虚函数,否则若使用父类指针指向子类对象,在delete时只会调用父类的析构函数,而不能调用子类的析构函数,从而造成内存泄露或达不到预期结果;

  (2)内联函数不能为虚函数:内联函数需要在编译阶段展开,而虚函数是运行时动态绑定的,编译时无法展开;

(3)构造函数不能为虚函数:构造函数在进行调用时还不存在父类和子类的概念,父类只会调用父类的构造函数,子类调用子类的,因此不存在动态绑定的概念;但是构造函数中可以调用虚函数,不过并没有动态效果,只会调用本类中的对应函数;

简单讲就是没有意义。虚函数的作用在于通过子类的指针或引用来调用父类的那个成员函数。而构造函数是在创建对象时自己主动调用的,不可能通过子类的指针或者引用去调用。

  (4)静态成员函数不能为虚函数:静态成员函数是以类为单位的函数,与具体对象无关,虚函数是与对象动态绑定的。  

5.重载和重写(覆盖)

  方法的重写Overriding和重载Overloading是多态性的不同表现。

  重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。

重写:在子类中定义某方法与其父类有相同的名称和参数和返回值。

重载:在一个类中定义了多个同名的方法参数个数、类型,有无const。

同名隐藏:子类中,出现了非重写的同名函数,父类中所以同名函数都被隐藏。  

6.extern “C”的作用

extern声明的函数和变量可以在本模块或其它模块中使用

extern "C"实现C++与C及其它语言的混合编程,是用在C和C++之间的桥梁。之所以需要这个桥梁是因为C编译器编译函数时不带函数的类型信息,只包含函数符号名字;而C++编译器为了实现函数重载,编译时会带上函数的类型信息,如他把上面的a函数可能编译成_a_float这样的符号为了实现重载。

extern "C"的惯用法:

  在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:

extern "C"{

#include "cExample.h"

}

  而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。

extern本身作为关键字修饰变量(函数)时声明该变量(函数)是外部变量(函数),通常全局变量在头文件中用这种方式声明,在对应源文件中定义,来防止重定义的错误。

7.C++为什么用模板类,为什么用泛型

  通过泛型可以定义类型安全的数据结构(类型安全),而无须使用实际的数据类型(可扩展)。这能够显著提高性能并得到更高质量的代码(高性能),因为您可以重用数据处理算法,而无须复制类型特定的代码(可重用)。

8.结构体内存对齐,与什么有关(CPU)

  在系统默认的对齐方式下:每个成员相对于这个结构体变量地址的偏移量正好是该成员类型所占字节的整数倍,且最终占用字节数为成员类型中最大占用字节数的整数倍。

  为什么要对齐?当CPU访问正确对齐的数据时,它的运行效率最高,当数据大小的数据模数的内存地址是0时,数据是对齐的。 

9.指针和引用

1.指针是一个变量,这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。

2.指针可以有多级,但是引用只能是一级;

3.指针的值可以为空,也可能指向一个不确定的内存空间,但是引用的值不能为空,并且引用在定义的时候必须初始化为特定对象;(因此引用更安全)

4.指针的值在初始化后可以改变,而引用在进行初始化后就不会再改变引用对象了;

5.sizeof引用得到的是所指向的变量(对象)的大小,而sizeof指针得到的是指针本身的大小;

6.指针和引用的自增(++)运算意义不一样;

10.static关键字作用

  在C语言中,关键字static有三个明显的作用:

1)在函数体内,一个被声明为静态的变量在这一函数被调用过程中维持上一次的值不变,即只初始化一次(该变量存放在静态变量区,而不是栈区)。

2)在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外访问。(注:模块可以理解为文件)

3)在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。

      【补充】《C和指针》中说static有两层含义:指明存储属性;改变链接属性。

      具体解释:(1)全局变量(包括函数)加上static关键字后,链接属性变为internal,也就是将他们限定在了本作用域内;(2)局部变量加上static关键字后,存储属性变为静态存储,不存储在栈区,下一次将保持上一次的尾值。

  除此之外,C++中还有新用法:

4)在类中的static成员变量意味着它为该类的所有实例所共享,也就是说当某个类的实例修改了该静态成员变量,其修改值为该类的其它所有实例所见;

5)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量(当然,可以通过传递一个对象来访问其成员)。 

11.volatile

volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。

使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据 

12.#define与const的区别

• define不会做类型检查(容易出错),const拥有类型,会执行相应的类型检查 
• define仅仅是宏替换,不占用内存,而const会占用内存 
• const内存效率更高,编译器可能将const变量保存在符号表中,而不会分配存储空间,这使得它成 为一个编译期间的常量,没有存储和读取的操作

  当使用#define定义一个简单的函数时,强烈建议使用内联函数替换!

13.C++中的强制类型转换

• reinterpret_cast: 转换一个指针为其它类型的指针。它也允许从一个指针转换为整数类型,反之亦 然. 这个操作符能够在非相关的类型之间转换. 操作结果只是简单的从一个指针到别的指针的值的 二进制拷贝. 在类型之间指向的内容不做任何类型的检查和转换?

class A{}; class B{};

A* a = new A;

B* b = reinterpret_cast(a);

• static_cast: 允许执行任意的隐式转换和相反转换动作(即使它是不允许隐式的),例如:应用到类 的指针上, 意思是说它允许子类类型的指针转换为父类类型的指针(这是一个有效的隐式转换), 同 时, 也能够执行相反动作: 转换父类为它的子类

class Base {}; class Derive:public Base{};

Base* a = new Base;

Derive *b = static_cast(a);

• dynamic_cast: 只用于对象的指针和引用. 当用于多态类型时,它允许任意的隐式类型转换以及相 反过程. 不过,与static_cast不同,在后一种情况里(注:即隐式转换的相反过程),dynamic_cast 会检查操作是否有效. 也就是说, 它会检查转换是否会返回一个被请求的有效的完整对象。检测在 运行时进行. 如果被转换的指针不是一个被请求的有效完整的对象指针,返回值为NULL. 对于引用 类型,会抛出bad_cast异常。
• const_cast: 这个转换类型操纵传递对象的const属性,或者是设置或者是移除,例如:

class C{}; const C* a = new C;

C *b = const_cast(a);

 

14.析构函数中抛出异常时概括性总结
  (1) C++中析构函数的执行不应该抛出异常;
  (2) 假如析构函数中抛出了异常,那么系统将变得非常危险,也许很长时间什么错误也不会发生;但也许系统有时就会莫名奇妙地崩溃而退出了,而且什么迹象也没有;
  (3) 当在某一个析构函数中会有一些可能(哪怕是一点点可能)发生异常时,那么就必须要把这种可能发生的异常完全封装在析构函数内部,决不能让它抛出函数之外,即在析构函数内部写出完整的throw...catch()块。

15.C++11新特性

Lambda、变参模板、auto、decltype、constexpr、智能指针、列表初始化、正则表达式、线程库、静态断言、委托构造。

智能指针:

1)智能指针能够帮助我们处理资源泄露问题;

2)它也能够帮我们处理空悬指针的问题;

3)它还能够帮我们处理比较隐晦的由异常造成的资源泄露。

 

*auto_ptr/unique_ptr事实上是一个类,在构造对象时获取对象的管理权,

无需考虑释放动态内存开辟的空间,在析构函数中直接释放,不会出现内存泄漏的问题。缺点:一个指针变量指向的空间不能由两个auto_ptr管理,不然会析构两次,使程序崩溃。

*shared_ptr加入了引用计数,从而很好的规避了auto_ptr 释放两次空间,调两次析构的情况

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值