C++面试题总结

1:new、delete、malloc、free关系

delete会调用对象的析构函数,和new对应,free只会释放内存。new调用构造函数。malloc和free是C/C++的标准库函数、new和delete是C++运算符。它们都可用于申请动态内存和释放内存。对于非内部数据类型的的对象而言,光用malloc、free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡前要自动执行析构函数。由于malloc、free是库函数而不是运算符,不在编译器的控制权限之内,不能够把执行构造函数和析构函数的人物强加于malloc/free。因此c++需要一个能完成动态分配内存和初始化工作的运算符new,以及一个能完成清理与释放内存的运算符delete,注意new/delete不是库函数。

2:C++有哪些性质

封装、继承、多态。

3:子类析构时要调用父类的析构函数吗

析构函数调用的次序是先调用派生类的析构函数,再调用基类的析构函数。也就是说在调用基类的析构函数时,派生类的信息已经全部销毁了。

定义一个对象时,先调用基类的构造函数,然后调用派生类的构造函数,析构的时候恰好相反:先调用派生类的析构函数,然后调用基类的析构函数。

4:求下面函数的返回值

int func(x) 

int countx = 0; 

while(x) 

countx ++; 

x = x&(x-1); 

return countx; 

假定x = 9999。 答案:8

思路:将x转化为2进制,看含有的1的个数。x二进制,同时对x-1也二进制,两个&运算,实际上是让x中的1的位数减少一位。

5:什么是引用,申明和使用引用要注意哪些问题

引用是某个变量的别名,对应的操作与对变量的直接操作效果完全相同。申明一个引用的时候,切记一定要对其进行初始化,引用申明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能把该引用名作为其他变量名的别名。引用只是一个变量的别名,不是一个新的数据类型,因此引用本身不占存储单元,系统也不给引用分配内存。不能建立数组的引用。

6:将引用作为函数参数有哪些特点

(1) 传递引用给函数与传递指针给函数的效果是一样的。这时,被调函数的形参就是原来主调函数中实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标函数对象(在主调函数中)操作。

(2) 使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参进行操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般的变量传递参数的效率和所占空间都好。

(3) 使用指针作为函数的参数时虽然也能达到引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用“*指针变量名”的形式进行运算,这很容易产生错误,且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

7:说一下拷贝构造函数

就类对象而言,相同类型的类对象是通过拷贝构造函数来完成整个复制过程的。

#include <iostream>  
using namespace std;  
  
class CExample {  
private:  
    int a;  
public:  
    //构造函数  
    CExample(int b)  
    { a = b;}  
      
    //拷贝构造函数  
    CExample(const CExample& C)  
    {  
        a = C.a;  
    }  
  
    //一般函数  
    void Show ()  
    {  
        cout<<a<<endl;  
    }  
};  
  
int main()  
{  
    CExample A(100);  
    CExample B = A; // CExample B(A); 也是一样的  
     B.Show ();  
    return 0;  
}   

CExample(const CExample& C) 就是我们自定义的拷贝构造函数。可见,拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它必须的一个参数是本类型的一个引用变量。

8:在什么时候需要使用“常引用”

既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。常引用声明方式:const 类型标识符 &引用名=目标变量名;

9:试写出程序结果

int  a=4;

int  &f(int  x)

{    a=a+x;

      return  a;

}

int main(void)

{    int   t=5;

     cout<<f(t)<<endl;  a = 9

    f(t)=20;             a = 20

    cout<<f(t)<<endl;     t = 5,a = 20  a = 25

     t=f(t);                a = 30 t = 30

    cout<<f(t)<<endl;  }    t = 60

}

10:重载(overload)和重写(override也翻译为覆盖)的区别

从定义上来讲:

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

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

从实现原理上来说:

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

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

11:引用和指针的区别

  • 引用必须初始化,指针不必;
  • 引用初始化后不能被改变,指针可以改变所指的对象。
  • 不存在指向空值的引用,但是存在指向空值的指针。

12:基类的析构函数不是虚函数,会带来什么问题

派生类的析构函数用不上,会造成资源的泄露。

13:析构函数为什么要用虚函数

这里转载一篇大神的博客,写得很详细也很容易明白:https://blog.csdn.net/zkangaroo/article/details/57000397

总结一句话就是:(基类对象的指针操作派生类对象时,防止析构函数只调用基类的,而不调用派生类的)派生类的析构函数用不上,会造成资源的泄漏。

//基类
 
class A{
   public :
       A(){ cout<<"A构造函数"<<endl; }
       ~A(){cout<<"A被销毁了"<<endl;}
       void Do(){
           cout<<"A要做点什么"<<endl;
       }
};
//派生类
 
class B :public A{
    public :
        B(){ cout<<"B构造函数"<<endl;}
        ~B(){ cout<<"B被销毁了"<<endl;}
        void Do(){ cout<<"B要做点什么"<<endl;}
};

(1)派生类 指针=new 派生类;

         B      *p   =new B;      那么就会执行基类构造函数,派生类构造函数

         p->Do();   通过派生类指针可以调用派生类的成员函数

         delete p;     先调用派生类析构函数,在调用基类构造函数

(2)派生类 指针=new 基类;  会出错,基类指针不能转换成派生类指针

(3)基类 指针 = new 派生类;  (派生类指针转化成基类指针)

       A    *p    = new B;     依旧要先调用基类构造函数,再派生类构造函数

       p ->Do();    通过基类指针调用基类成员函数,此处只能调用基类里面有的成员函数,当调用派生类中成员函数会提示基类中并没有这个成员

       delete p;      这里只会调用基类的析构函数,所以内存释放并不完全

(4)上面(3)知道了这样影响了内存的释放完整程度,所以我们通过引入虚函数机制,将基类的析构函数定义成虚函数

//基类
 
class A{
   public :
       A(){ cout<<"A构造函数"<<endl; }
       virtual ~A(){cout<<"A被销毁了"<<endl;}
       void Do(){
           cout<<"A要做点什么"<<endl;
       }
};
//派生类
 
class B :public A{
    public :
        B(){ cout<<"B构造函数"<<endl;}
        ~B(){ cout<<"B被销毁了"<<endl;}
        void Do(){ cout<<"B要做点什么"<<endl;}
};

然后 delete p;就会释放派生类,释放基类。

14:内存的分配方式有几种

  • 从静态存储区域分配,内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量。
  • 在栈上创建,在执行函数前时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
  • 从堆上分配,亦称动态内存分配,程序在运行的时候用malloc或new申请任意多少内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

15:初始化列表和构造函数初始化

初始化列表的一般形式如下:

Object::Object(int _x,int _y):x(_x),y(_y) {}

构造函数初始化一般形式如下:

Object::Object(int _x,int _y)

{

  x=_x;

  y=_y;

}

初始化列表初始化是显式初始化,而下边的是隐式初始化。而没有使用初始化列表的构造函数是对类的成员赋值,并没有进行显式的初始化。

初始化和赋值对内置类型的成员没有什么的的区别,在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的。对非内置类型成员变量,因为类类型的数据成员的数据成员对象在进入函数体前已经构造完成,也就是说在成员初始化列表处进行构造对象的工作,调用构造函数,在进入函数体之后,进行的是对已经构造好的类对象的赋值,又调用一个赋值赋值操作符才能完成(如果并未提供,则使用编译器提供的默认成员赋值行为)。为了避免两次构造,推荐使用类构造函数初始化列表。

但有很多场合必须使用带有初始化列表的构造函数。例如,成员类型是没有默认构造函数的类,若没有提供显示初始化时,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试调用默认构造函数将会失败。再例如const成员或者引用类型的成员,因为const对象或引用类型只能初始化,不能对它们进行赋值。

16:堆和栈的区别(堆溢出和栈溢出)

一个由c/c++编译的程序占用的内存分为一下几个部分

  1. 栈区 stack:由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
  2. 堆区 heap:由程序员分配释放,若程序员不释放,程序结束时,可能由OS回收。但是它与数据结构中的堆是两回事,分配方式倒是类似于链表。
  3. 全局区(静态区)static:全局变量和静态变量的存储是放在一块儿的。初始化的全局变量和静态变量在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域,程序结束后由系统释放。
  4. 文字常量区:常量字符串就是放在这里的,程序结束后由系统释放。
  5. 程序代码区:存放函数体的二进制代码。
  6. 堆溢出:不断的new 一个对象,一直创建新的对象。
  7. 栈溢出:死循环或者是递归太深,递归的原因,可能太大,也可能没有终止。

17:TCP和UDP的区别

  • TCP:传输控制协议,提供的是面向连接、可靠的字节流服务。

                当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。

                TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。

  • UDP:用户数据报协议,是一个简答的面向数据的运输层协议。

                UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能达到目的地。

                由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时超时重发等机制,故而传输速度很快。

18:C和C++的区别

  • C++是在C的基础上开发的一种面向对象编程的语言,应用广泛。C++支持多种编程范式——面向对象编程、泛型编程和过程化编程。其编程领域众广,常用于系统开发,引擎开发等应用领域,是最受广大程序员受用的最强大编程语言之一,支持类:类、封装、重载等特性。
  • C是一个结构化语言,它的重点在于算法和数据结构。C++程序的设计首先要考虑如何通过一个过程,对输入(或环境条件)进行运算处理得到输出,(或实现过程(事物)控制),而对于C++,首先要考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取的状态信息得到输出或实现过程(事物)控制。

19:什么是面向对象(OOP)

面向对象是一种对现实世界理解和抽象的方法、思想,通过将需求要素转化为对象进行问题处理的一种思想。

20:什么是多态

多态是指相同的操作函数、过程可作用于多钟类型的对象上并获得不同的结果。不同的对象,收到同一消息可以产生不同的结果,这种对象称为多态。

21:STL库,常见的STL容器有哪些,算法用过哪几个

  • STL包括两部分内容:容器和算法(重要的是还有融合这两者的迭代器)。
  • 在STL中,容器分为两类:序列式容器和关联式容器。
  • 序列式容器:其中的元素不一定有序,但可以被排序。如:vector,list,deque,stack,queue,heap,priority_queue,slist;
  • 关联式容器:内部结构基本上是一颗平衡二叉树。所谓关联,指每个元素都有一个键值和一个实值,元素按照一定的规则存放。如:RB-tree、set、map、multiset、multimap、hashtable、hash_set、hash_map、hash_multiset、hash_multimap。

下面各选取一个作为说明。

vector:它是一个动态分配存储空间的容器。区别于c++中的array,array分配的空间是静态的,分配之后不能被改变,而vector会自动重分配(扩展)空间。

set:其内部元素会根据元素的键值自动被排序。区别于map,它的键值就是实值,而map可以同时拥有不同的键值和实值。

算法,如排序,复制……以及个容器特定的算法。这点不用过多介绍,主要看下面迭代器的内容。

迭代器是STL的精髓,我们这样描述它:迭代器提供了一种方法,使它能够按照顺序访问某个容器所含的各个元素,但无需暴露该容器的内部结构。它将容器和算法分开,好让这二者独立设计。

22:const作用

  • const 修饰类的成员变量,表示成员常量,不能被修改。
  • const 修饰函数承诺在本函数内部不会修改类内的数据成员,不会调用其它非 const 成员函数。
  • 如果 const 构成函数重载, const 对象只能调用 const 函数,非 const 对象优先调用非 const 函数。
  • const 函数只能调用 const 函数,非 const 函数可以调用 const 函数。
  • 类体外定义的 const 成员函数,在定义和声明处都需要 const 修饰符。

23:封装、继承、多态

  • 封装:封装是实现面向对象编程的第一步,封装就是将数据或函数等集合在一个个的单元中(类)。封装的意义在于保护或者防止代码(数据)被我们无意中破坏。
  • 继承:继承主要实现代码重用,节省开发时间。子类可以继承父类的一些东西。
  • 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行效果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。

24:解释C++中的静态函数和静态变量

(1):类静态数据成员在编译时创建并初始化:在该类的任何对象建立之前就存在,不属于任何对象,而非静态类成员变量则是属于对象所有的。类静态数据成员只有一个拷贝,为所有此类的对象所共享。

(2):类静态成员函数属于整个类,不属于某个对象,由该类所有对象所共享。

  • static 成员变量实现了同类对象间信息共享
  • static 成员类外存储,求类大小,并不包含在内
  • static 成员是命名空间属于类的全局变量,存储在 data 区的 rw 段
  • static 成员只能类外初始化
  • 可以通过类名访问,也可以通过对象访问
  • 静态成员函数的意义,不在于信息共享,数据沟通,而在于管理静态数据成员,完成对静态数据成员的封装
  • 静态成员函数只能访问静态数据成员。原因:非静态成员函数,在调用时 this指针时被当作参数传进。而静态成员函数属于类,而不属于对象,没有 this 指针

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

HaoRenkk123

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

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

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

打赏作者

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

抵扣说明:

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

余额充值