C/C++常考习题

1.什么是虚函数?什么是纯虚函数?

虚函数:允许被其子类重新定义的成员函数。
虚函数的声明:virtual returntype func(parameter);引入虚函数的目的是为了动态绑定;
纯虚函数声明:virtual returntype func(parameter)=0;引入纯虚函数是为了派生接口。

2.基类为什么需要虚析构函数?

防止内存泄漏。想去借助父类指针去销毁子类对象的时候,不能去销毁子类对象。
假如没有虚析构函数,释放一个由基类指针指向的派生类对象时,不会触发动态绑定,则只会调用基类的析构函数,不会调用派生类的。派生类中申请的空间则得不到释放导致内存泄漏。

3.当i是一个整数的时候i++和++i那个更快?它们的区别是什么?

几乎一样。i++返回的是i的值,++i返回的是i+1的值,即++i是一个确定的值,是一个可以修改的左值。

4.指针和引用的区别

  • 指针是一个新的变量,存储了另一个变量的地址,我们可以通过访问这个地址来修改另一个变量;
    引用只是一个别名,还是变量本身,对引用的任何操作就是对变量本身进行操作,以达到修改变量的目的
  • 引用只有一级,而指针可以有多级
  • 引用只能在初始化时被赋值,其他时候值不能被改变,指针的值可以在任何时候被改变
  • 引用不能为NULL,指针可以为NULL
  • 引用变量内存单元保存的是被引用变量的地址
  • “sizeof 引用" = 指向变量的大小 , “sizeof 指针”= 指针本身的大小
  • 引用可以取地址操作,返回的是被引用变量本身所在的内存单元地址
  • 引用使用在源代码级相当于普通的变量一样使用,做函数参数时,内部传递的实际是变量地址;指针传参的时候,还是值传递,指针本身的值不可以修改,需要通过解引用才能对指向的对象进行操作

5. static关键字的作用

  • 修饰局部变量
    static修饰局部变量时,使得被修饰的变量成为静态变量,存储在静态区。存储在静态区的数据生命周期与程序相同,在main函数之前初始化,在程序退出时销毁。(无论是局部静态还是全局静态)
  • 修饰全局变量
    全局变量本来就存储在静态区,因此static并不能改变其存储位置。但是,static限制了其链接属性。被static修饰的全局变量只能被该包含该定义的文件访问(即改变了作用域)。
  • 修饰函数
    static修饰函数使得函数只能在包含该函数定义的文件中被调用。对于静态函数,声明和定义需要放在同一个文件夹中。
  • 修饰成员变量
    用static修饰类的数据成员使其成为类的全局变量,会被类的所有对象共享,包括派生类的对象,所有的对象都只维持同一个实例。 因此,static成员必须在类外进行初始化(初始化格式:int base::var=10;),而不能在构造函数内进行初始化,不过也可以用const修饰static数据成员在类内初始化。
  • 修饰成员函数
    用static修饰成员函数,使这个类只存在这一份函数,所有对象共享该函数,不含this指针,因而只能访问类的static成员变量。静态成员是可以独立访问的,也就是说,无须创建任何对象实例就可以访问。

6. 为什么不可以同时用const和static修饰成员函数?

static的作用是表示该函数只作用在类型的静态变量上,与类的实例没有关系;而const的作用是确保函数不能修改类的实例的状态,与类型的静态变量没有关系。因此不能同时用它们。

7. const的作用

定义变量为只读变量,不可修改。修饰函数的参数和返回值(后者应用比较少,一般为值传递)
const成员函数(只需要在成员函数参数列表后加上关键字const,如char get() const;)可以访问const成员变量和非const成员变量,但不能修改任何变量。
在声明一个成员函数时,若该成员函数并不对数据成员进行修改操作,应尽可能将该成员函数声明为const成员函数。
const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数。使用const关键字修饰的变量,一定要对变量进行初始化。

8.C++的多态性

多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数。C++多态性主要是通过虚函数实现的。
多态与非多态的实质区别就是函数地址是早绑定还是晚绑定。

  • 早绑定:函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的。
  • 晚绑定:函数调用的地址不能在编译器期间确定,需要在运行时才确定。

在绝大多数情况下,程序的功能是在编译的时候就确定下来的,我们称之为静态特性。反之,如果程序的功能是在运行时刻才能确定下来的,则称之为动态特性。
C++中,最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。

  • 编译时多态性:通过重载函数实现
  • 运行时多态性:通过虚函数实现

9.面向对象和面向过程的区别

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;

  • 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
  • 缺点:没有面向对象易维护、易复用、易扩展

面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

  • 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
  • 缺点:性能比面向过程低

10. C和C++的区别

  1. C是面向过程的语言,是一个结构化的语言,考虑如何通过一个过程对输入进行处理得到输出;
    C++是面向对象的语言,首要考虑的是如何构造一个契合问题域的对象模型,主要特征是“封装、继承和多态”。
  • 封装隐藏了实现细节,使得代码模块化;
  • 派生类可以继承父类的数据和方法,扩展了已经存在的模块,实现了代码重用;
  • 多态则是“一个接口,多种实现”,通过派生类重写父类的虚函数,实现了接口的重用。
  1. C和C++动态管理内存的方法不一样,C是使用malloc/free,而C++除此之外还有new/delete关键字。
  2. C++支持函数重载,C不支持函数重载
  3. C++中有引用,C中不存在引用的概念

11.虚函数表是针对类的还是针对对象的?同一个类的两个对象的虚函数表是怎么维护的?

编译器为每一个类维护一个虚函数表(本质是一个函数指针数组,数组里面存放了一系列函数地址 ),每个对象的首地址保存着该虚函数表的指针,同一个类的不同对象实际上指向同一张虚函数表。
在类内部添加一个虚拟函数表指针,该指针指向一个虚拟函数表,该虚拟函数表包含了所有的虚拟函数的入口地址,每个类的虚拟函数表都不一样,在运行阶段可以循此脉络找到自己的函数入口。
纯虚函数相当于占位符,先在虚函数表中占一个位置由派生类实现后再把真正的函数指针填进去。除此之外和普通的虚函数没什么区别。

12.内联函数,宏定义和普通函数的区别

内联函数要做参数类型检查,这是内联函数跟宏相比的优势
宏定义是在预编译的时候把所有的宏名用宏体来替换,简单的说就是字符串替换。
内联函数则是在编译的时候进行代码插入,编译器会在每处调用内联函数的地方直接把内联函数的内容展开,这样可以省去函数的调用的压栈出栈的开销,提高效率。
const与#define的区别:宏在预处理阶段替换,const在编译阶段替换;宏没有类型,不做安全检查,const有类型,在编译阶段进行安全检查

13.栈与堆的区别:

  • 管理方式不同: 栈是编译器自动管理的,堆需手动释放
  • 空间大小不同: 在32位OS下,堆内存可达到4GB的的空间,而栈就小得可怜.(VC6中,栈默认大小是1M,当然,你可以修改它)
  • 能否产生碎片不同:对于栈来说,进栈/出栈都有着严格的顺序(先进后出),不会产生碎片;而堆频繁的new/delete,会造成内存空间的不连续,容易产生碎片.
  • 生长方向不同:栈向下生长,以降序分配内存地址;堆向上生长,以升序分配内在地址.
  • 分配方式不同:堆动态分配,无静态分配;栈分为静态分配和动态分配,比如局部变量的分配,就是动态分配(alloca函数),函数参数的分配就是动态分配(我想的…).
  • 分配效率不同:栈是系统提供的数据结构,计算机会在底层对栈提供支持,进栈/出栈都有专门的指令,这就决定了栈的效率比较高.堆则不然,它由C/C++函数库提供,机制复杂,堆的效率要比栈低得多.
    可以看出,栈的效率要比堆高很多,所以,推荐大家尽量用栈.不过,虽然栈有如此多的好处,但远没有堆使用灵活.

14.C++中类与结构体的区别?

最本质的一个区别就是默认的访问控制: struct作为数据结构的实现体,它默认的数据访问控制是public的,而class作为对象的实现体,它默认的成员变量访问控制是private的。
“class”这个关键字还用于定义模板参数,就像“typename”。但关键字“struct”不用于定义模板参数。

15.析构函数的作用?

析构函数是用来释放所定义的对象中使用的指针,默认的析构函数不用显示调用,自建的析构函数要在程序末尾调用。
如果你的类里面只用到的基本类型,如int char double等,系统的默认析构函数其实什么都没有做。
但如果你使用了其他的类如vector,string等,系统的默认析构函数就会调用这些类对象的析构函数。
如果是自己写析构函数的话,如果你的类里面分配了系统资源,如new了内存空间,打开了文件等,那么在你的析构函数中就必须释放相应的内存空间和关闭相关的文件;
这样系统就会自动调用你的析构函数释放资源,避免内存泄漏。

16.操作系统和编译器如何区分全局变量和局部变量?

操作系统只管调度进程,编译器通过内存分配的位置来知道的,全局变量分配在全局数据段并且在程序开始运行的时候被加载。局部变量则分配在栈里面 。

17.结构体和联合体的区别?

结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。

18 重载与重写的区别?

从定义上来说:

  • 重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
  • 重写:是指子类重新定义父类虚函数的方法。

从实现原理上来说:

  • 重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数。
  • 重写:当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。

19.有关纯虚函数的理解?

纯虚函数是为你的程序制定一种标准,纯虚函数只是一个接口,是个函数的声明而已,它要留到子类里去实现。

class A{
protected:
void foo();//普通类函数
virtual void foo1();//虚函数
virtual void foo2() = 0;//纯虚函数
}

带纯虚函数的类抽象类,这种类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。
虚函数是为了继承接口和默认行为。
纯虚函数只是继承接口,行为必须重新定义。
(在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常 理。所以引入了纯虚函数的概念)

20.内存溢出,内存泄漏的原因?

内存溢出是指程序在申请内存时,没有足够的内存空间供其使用。原因可能如下:

  • 内存中加载的数据量过于庞大,如一次从数据库取出过多数据
  • 代码中存在死循环或循环产生过多重复的对象实体
  • 递归调用太深,导致堆栈溢出等
  • 内存泄漏最终导致内存溢出
    内存泄漏是指向系统申请分配内存进行使用(new),但是用完后不归还(delete),导致占用有效内存

21. 虚基类

在C++中,如果在多条继承路径上有一个公共的基类,那么在这些路径中的某几条路径的汇合处,这个公共的基类就会产生多个实例(从而造成二义性)。
如果想使这个公共的基类只产生一个实例,则可将这个基类说明为虚基类. 这要求在从base类派生新类时,使用关键字virtual将base类说明为虚基类.

22. C++文件编译与执行的四个阶段

  • 1)预处理:根据文件中的预处理指令来修改源文件的内容
  • 2)编译:编译成汇编代码
  • 3)汇编:把汇编代码翻译成目标机器指令
  • 4)链接:链接目标代码生成可执行程序

23. 定义和声明的区别

  • 变量定义:用于为变量分配存储空间,还可为变量指定初始值。程序中,变量有且仅有一个定义。
  • 变量声明:用于向程序表明变量的类型和名字。
  • 定义也是声明:当定义变量时我们声明了它的类型和名字。
  • extern关键字:通过使用extern关键字声明变量名而不定义它。

24. C++内存分配方式

  1. 从静态存储区分配内存,编译时分配,程序运行的整个周期都存在。
  2. 从栈中分配内存,函数内的局部变量在栈上分配内存,系统自动分配和释放。
  3. 从堆中分配内存,程序员来分配和释放,并指定大小。

25. 什么情况下会调用拷贝构造函数(三种情况)

系统自动生成的构造函数:普通构造函数和拷贝构造函数 (在没有定义对应的构造函数的时候)
生成一个实例化的对象会调用一次普通构造函数,而用一个对象去实例化一个新的对象所调用的就是拷贝构造函数
调用拷贝构造函数的情形:

  • 1)用类的一个对象去初始化另一个对象的时候
  • 2)当函数的参数是类的对象时,又是值传递的时候,如果是引用传递则不会调用
  • 3)当函数的返回值是类的对象或者引用的时候

26. 虚函数是怎么实现的

每一个含有虚函数的类都至少有一个与之对应的虚函数表,其中存放着该类所有虚函数对应的函数指针(地址),类的示例对象不包含虚函数表,只有虚指针;派生类会生成一个兼容基类的虚函数表。

27. 虚函数的作用?

虚函数可以让成员函数操作一般化,用基类的指针指向不同的派生类的对象时,基类指针调用其虚成员函数,则会调用其真正指向对象的成员函数,而不是基类中定义的成员函数(只要派生类改写了该成员函数)。若不是虚函数,则不管基类指针指向的哪个派生类对象,调用时都会调用基类中定义的那个函数。虚函数是C++多态的一种表现,可以进行灵活的动态绑定。

你可能感兴趣的:(C/C++常考习题)