C++常见问题

1.C++有哪些性质(面向对象特点)

封装,继承和多态

封装:

封装是隐藏对象内部复杂性的过程,同时暴露出必要的功能。这可以防止外部代码直接访问对象内部的状态,减少了外部干扰和错误使用的可能性。在C++中,通常通过访问修饰符(private、protected、public)来实现封装。

继承:

继承允许新创建的类(称为子类)继承父类的属性和方法。继承可以实现代码复用,并且可以形成一个类的层次结构。

多态:

多态性意味着可以通过基类的指针或引用来调用派生类的方法。这使得程序可以在不知道对象确切类型的情况下对对象进行操作,从而使程序可以在运行时动态决定对象的行为。

2.引用与指针有什么区别?

1) 引用必须被初始化,指针不必。

2) 引用初始化以后不能被改变,指针可以改变所指的对象。

3) 不存在指向空值的引用,但是存在指向空值的指针。

3.new、delete、malloc、free关系

(1)内存分配与释放

new与delete是C++的运算符:

new:用于动态分配指定类型的对象,并返回指向该对象的指针。它会自动计算所需内存的大小,并调用对象的构造函数(如果有的话)进行初始化。

delete:用于释放通过new分配的对象所占用的内存,并调用对象的析构函数(如果有的话)进行清理。

malloc与free是C++/C语言的标准库函数:

malloc:用于动态分配指定大小的内存块,并返回一个指向该内存块的void指针。用户需要手动计算所需内存的大小,并在使用时将void指针转换为合适的类型。

free:用于释放通过malloc分配的内存块。它只负责释放内存,不会调用任何析构函数。

(2)类型安全性与构造函数/析构函数

new/delete:具有类型安全性,因为它们是在C++中定义的,与类型紧密相关。new会自动调用构造函数,delete会自动调用析构函数。

malloc/free:不具备类型安全性,因为它们是在C语言中定义的,以字节为单位进行内存管理。它们不会调用构造函数或析构函数。

4.子类析构时要调用父类的析构函数吗?

是的,在面向对象编程中,当子类(派生类)的析构函数被调用时,它会首先自动调用父类(基类)的析构函数。这个过程是自动发生的,不需要在子类的析构函数中显式调用父类的析构函数。

定义一个对象时先调用基类的构造函数、然后调用派生类的构造函数;

析构的时候恰好相反:先调用派生类的析构函数、然后调用基类的析构函数。

5.多态,虚函数,纯虚函数

多态:是对于不同对象接收相同消息时产生不同的动作。

      C++的多态性具体体现在运行和编译两个方面:编译时多态(主要通过函数重载和模板实现)和运行时多态(主要通过虚函数和继承实现)。

      

虚函数:在基类中冠以关键字virtual的成员函数。它提供了一种接口界面。允许在派生类中对基类的虚函数重新定义。

纯虚函数的作用:在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。作为接口而存在纯虚函数不具备函数的功能,一般不能直接被调用。

      从基类继承来的纯虚函数,在派生类中仍是虚函数。如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类(abstract class)。

      抽象类中不仅包括纯虚函数,也可包括虚函数。抽象类必须用作派生其他类的基类,而不能用于直接创建对象实例。但仍可使用指向抽象类的指针支持运行时多态性。

      

6.重载(overload)和重写(overried,有的书也叫做“覆盖”)的区别?

从定义上说:

重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。

重写:是指子类重新定义父类虚函数的方法。

从实现原理上来说:

重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。对于这两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!

重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。  

7.C++是不是类型安全的?

答案:不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)。C#是类型安全的。

8.main函数执行以前,还会执行什么代码?

答案:全局对象的构造函数会在main函数之前执行。    

9.const与#define相比,有何优点?

const作用:定义常量、修饰函数参数、修饰函数返回值三个作用。被Const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。

const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。

有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。

10.简述数组与指针的区别?

(1)数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。

指针可以随时指向任意类型的内存块。

(2)修改内容上的差别:指针指向的常量字符串不能修改

 char a[]=“hello”;

 a[0]=‘X’;

 char *p=“world”; //注意p指向常量字符串

 p[0]=‘X’; //编译器不能发现该错误,运行时错误

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

11.队列和栈有什么区别?

答案:队列先进先出,栈后进先出

12.动态多态有什么作用?有哪些必要条件?

动态多态是面向对象编程中的一个核心特性,它允许在运行时通过指向基类的指针或引用来调用派生类的方法,使得相同的操作可以作用于不同类型的对象上,从而表现出不同的行为。

动态多态的实现有几个必要条件:

继承:必须有两个类,一个基类和一个从基类派生出来的子类。

基类中的虚函数:在基类中必须有至少一个函数被声明为虚函数(使用virtual关键字)。派生类通常会重写(override)这个虚函数来提供特定的功能。

基类的指针或引用:需要通过基类的指针或引用来调用虚函数,这样C++运行时才能利用虚函数表(v-table)来动态决定调用哪个函数。

动态绑定:当通过基类的指针或引用调用虚函数时,发生的是动态绑定,这意味着直到程序运行时,才决定调用对象的哪个方法。

13.为什么基类的构造函数不能定义为虚函数?

在C++中,基类的构造函数不能被定义为虚函数,原因有两个:

构造函数的目的是初始化对象。当我们创建一个对象时,构造函数被调用来初始化对象的数据成员。在这个阶段,对象才刚刚开始被构建,还没有完全形成,因此它还不具备执行虚函数调用的条件(即,动态绑定)。因为执行虚函数调用需要通过对象的虚函数表指针,而这个指针在构造函数执行完毕后才会被设置。

虚函数通常在有继承关系的类中使用,用于实现多态。在子类对象的构造过程中,首先会调用基类的构造函数,然后才是子类的构造函数。如果基类的构造函数被定义为虚函数,那么在执行基类的构造函数时,由于子类的部分还没有被构造,所以无法正确地执行子类构造函数中对虚函数的重写。这就破坏了虚函数的目的,即允许子类重写基类的行为。

因此,基于以上原因,C++不允许构造函数为虚函数。

14.为什么基类的析构函数需要定义为虚函数?

在C++中,基类的析构函数应该被定义为虚函数,主要是为了能正确地释放动态分配的资源,确保当删除一个指向派生类对象的基类指针时,派生类的析构函数能被正确调用,避免资源泄露。

当我们使用基类指针指向派生类对象,并使用delete删除这个指针时,如果基类的析构函数不是虚函数,那么只有基类的析构函数会被调用。这样,派生类的析构函数就没有机会被调用,导致派生类中的资源没有被正确释放,造成内存泄漏。

而如果我们将基类的析构函数定义为虚函数,那么在删除基类指针时,就会根据这个指针实际指向的对象类型,调用相应的析构函数,先调用派生类的析构函数,然后再调用基类的析构函数。这样就能确保所有的资源都被正确释放,避免内存泄漏。

15.解释命名空间(Namespace)在C++中的作用和优势。

命名空间(Namespace)是C++中一种用于组织代码的机制,可以将全局作用域划分为不同的区域,以避免命名冲突,并提供更好的代码结构和可读性。

以下是命名空间在C++中的作用和优势:

避免命名冲突:

当我们在编写大型程序或使用多个库时,可能会出现相同名称的函数、变量或类等。使用命名空间可以将这些实体包装到特定的命名空间中,在不同的命名空间中定义相同名称的实体不会产生冲突。

提供更好的代码结构:

通过将相关功能或模块放置在相应的命名空间下,可以提供更清晰、组织良好的代码结构。这使得代码易于理解、维护和扩展。

支持重载和扩展:

使用命名空间可以支持函数、类等实体的重载。当我们需要为相似但功能稍有差异的对象创建多个版本时,可以利用命名空间来区分它们,并根据需要进行选择调用。

具备嵌套性:

C++中的命名空间可以嵌套定义,即在一个命名空间内部可以再定义其他子命名空间。这样可以进一步划分和组织相关联的代码。

可避免全局污染:

使用命名空间可以减少全局命名的使用,从而减少全局作用域的变量和函数的数量。这有助于避免不必要的全局变量和函数污染。

提高可读性和可维护性:

通过明确指定实体所属的命名空间,代码的可读性得到提高。开发人员可以更清楚地知道特定实体是在哪个命名空间下定义和使用的,从而增强了代码的可维护性。

16.宏和内联(inline)函数的比较?

1). 首先宏是C中引入的一种预处理功能;

2). 内联(inline)函数是C++中引入的一个新的关键字;C++中推荐使用内联函数来替代宏代码片段;

3). 内联函数将函数体直接扩展到调用内联函数的地方,这样减少了参数压栈,跳转,返回等过程;

4). 由于内联发生在编译阶段,所以内联相较宏,是有参数检查和返回值检查的,因此使用起来更为安全;

5). 需要注意的是, inline会向编译期提出内联请求,但是是否内联由编译器决定(当然可以通过设置编译器,强制使用内联);

6). 由于内联是一种优化方式,在某些情况下,即使没有显示的声明内联,比如定义在class内部的方法,编译器也可能将其作为内联函数。

7). 内联函数不能过于复杂,最初C++限定不能有任何形式的循环,不能有过多的条件判断,不能对函数进行取地址操作等,但是现在的编译器几乎没有什么限制,基本都可以实现内联。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI+程序员在路上

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值