秋招总结------C++面试题总结四

1. C++中类成员的访问权限和继承权限问题

  • 三种访问权限
  1. public:用该关键字修饰的成员表示公有成员,该成员不仅可以在类内可以被  访问,在类外也是可以被访问的,是类对外提供的可访问接口;
  2.  private:用该关键字修饰的成员表示私有成员,该成员仅在类内可以被访问,在类体外是隐藏状态;
  3.  protected:用该关键字修饰的成员表示保护成员,保护成员在类体外同样是隐藏状态,但是对于该类的派生类来说,相当于公有成员,在派生类中可以被访问。
  • 三种继承方式
  1. 若继承方式是public,基类成员在派生类中的访问权限保持不变,也就是说,基类中的成员访问权限,在派生类中仍然保持原来的访问权限;
  2.  若继承方式是private,基类所有成员在派生类中的访问权限都会变为私有(private)权限;
  3. 若继承方式是protected,基类的共有成员和保护成员在派生类中的访问权限都会变为保护(protected)权限,私有成员在派生类中的访问权限仍然是私有(private)权限。

2. cout和printf有什么区别?

  1. cout<<是一个函数,cout<<后可以跟不同的类型是因为cout<<已存在针对各种类型数据的重载,所以会自动识别数据的类型。输出过程会首先将输出字符放入缓冲区,然后输出到屏幕。

        cout是有缓冲输出:

       cout < < "abc " < 或cout < < "abc\n ";cout <        flush立即强迫缓冲输出。
     2. printf是无缓冲输出。有输出时立即输出

3. 重载运算符?

 

        类属关系运算符"."、成员指针运算符".*"、作用域分辨符"::"、sizeof运算符和三目运算符"?:"

 

  1. 我们只能重载已有的运算符,而无权发明新的运算符;对于一个重载的运算符,其优先级和结合律与内置类型一致才可以;不能改变运算符操作数个数
  2. .    ::   ?:  sizeof   typeid  **不能重载;
  3. 两种重载方式,成员运算符和非成员运算符,成员运算符比非成员运算符少一个参数;下标运算符、箭头运算符必须是成员运算符;
  4. 引入运算符重载,是为了实现类的多态性
  5. 当重载的运算符是成员函数时,this绑定到左侧运算符对象。成员运算符函数的参数数量比运算符对象的数量少一个;至少含有一个类类型的参数;
  6. 从参数的个数推断到底定义的是哪种运算符,当运算符既是一元运算符又是二元运算符(+,-,*,&);
  7. 下标运算符必须是成员函数,下标运算符通常以所访问元素的引用作为返回值,同时最好定义下标运算符的常量版本和非常量版本;
  8. 箭头运算符必须是类的成员,解引用通常也是类的成员;重载的箭头运算符必须返回类的指针;

4. 定义和声明的区别

  1. 如果是指变量的声明和定义
    编译原理上来说,声明是仅仅告诉编译器,有个某类型的变量会被使用,但是编译器并不会为它分配任何内存。而定义就是分配了内存
  2. 如果是指函数的声明和定义
    声明:一般在头文件里,对编译器说:这里我有一个函数叫function() 让编译器知道这个函数的存在。
    定义:一般在源文件里,具体就是函数的实现过程 写明函数体。

5. C++类型转换有四种

  • static_cast能进行基础类型之间的转换,也是最常看到的类型转换。它主要有如下几种用法:
  1. 1 . 用于类层次结构中父类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用转换成父类表示)是安全的;

    2 . 进行下行转换(把父类指针或引用转换成子类指针或引用)时,由于没有动态类型检查,所以是不安全的;

    3 . 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。

    4 . 把void指针转换成目标类型的指针(不安全!!)

    5 . 把任何类型的表达式转换成void类型。

  2. const_cast运算符用来修改类型的const或volatile属性。除了去掉const 或volatile修饰之外, type_id和expression得到的类型是一样的。但需要特别注意的是const_cast不是用于去除变量的常量性,而是去除指向常数对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用。
  3. reinterpret_cast它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。
  4. dynamic_cast 主要用在继承体系中的安全向下转型。它能安全地将指向基类的指针转型为指向子类的指针或引用,并获知转型动作成功是否。转型失败会返回null(转型对象为指针时)或抛出异常bad_cast(转型对象为引用时)。 dynamic_cast 会动用运行时信息(RTTI)来进行类型安全检查,因此 dynamic_cast 存在一定的效率损失。当使用dynamic_cast时,该类型必须含有虚函数,这是因为dynamic_cast使用了存储在VTABLE中的信息来判断实际的类型,RTTI运行时类型识别用于判断类型。typeid表达式的形式是typeid(e),typeid操作的结果是一个常量对象的引用,该对象的类型是type_info或type_info的派生。

6. 静态成员与普通成员的区别

  1. 生命周期:静态成员变量从类被加载开始到类被卸载,一直存在;普通成员变量只有在类创建对象后才开始存在,对象结束,它的生命期结束;
  2. 共享方式静态成员变量是全类共享;普通成员变量是每个对象单独享用的;
  3. 定义位置普通成员变量存储在栈或堆中,而静态成员变量存储在静态全局区;
  4. 初始化位置普通成员变量在类中初始化;静态成员变量在类外初始化;
  5. 默认实参可以使用静态成员变量作为默认实参,

7. 多继承的优缺点,作为一个开发者怎么看待多继承

  1. C++允许为一个派生类指定多个基类,这样的继承结构被称做多重继承。
  2. 多重继承的优点很明显,就是对象可以调用多个基类中的接口
  3. 如果派生类所继承的多个基类有相同的基类,而派生类对象需要调用这个祖先类的接口方法,就会容易出现二义性
  4. 加上全局符确定调用哪一份拷贝。比如pa.Author::eat()调用属于Author的拷贝。
  5. 使用虚拟继承,使得多重继承类Programmer_Author只拥有Person类的一份拷贝。

8. 迭代器++it,it++哪个好,为什么

  1. 前置返回一个引用,后置返回一个对象
  • // ++i实现代码为:

    int& operator++()

    {

        *this += 1;

        return *this;

    }

     

  1. 前置不会产生临时对象,后置必须产生临时对象,临时对象会导致效率降低
  • //i++实现代码为:                                 

    int operator++(int)                                 

    {

    int temp = *this;                                    

           ++*this;                                            

           return temp;                                    

    }

9. 模板和实现可不可以不写在一个文件里面?为什么?

  1. 因为在编译时模板并不能生成真正的二进制代码,而是在编译调用模板类或函数的CPP文件时才会去找对应的模板声明和实现,在这种情况下编译器是不知道实现模板类或函数的CPP文件的存在,所以它只能找到模板类或函数的声明而找不到实现,而只好创建一个符号寄希望于链接程序找地址。但模板类或函数的实现并不能被编译成二进制代码,结果链接程序找不到地址只好报错了。
  2. 《C++编程思想》第15章(第300页)说明了原因:模板定义很特殊。由template<…>处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。

10. ​​​​​​​class、union、struct的区别?

  1. C语言中,struct只是一个聚合数据类型没有权限设置,无法添加成员函数,无法实现面向对象编程,且如果没有typedef结构名,声明结构变量必须添加关键字struct。
  2. C++中,struct功能大大扩展,可以有权限设置(默认权限为public),可以像class一样有成员函数,继承(默认public继承),可以实现面对对象编程,允许在声明结构变量时省略关键字struct。
  3. C与C++中的union:一种数据格式,能够存储不同的数据类型,但只能同时存储其中的一种类型。C++ union结构式一种特殊的类。它能够包含访问权限、成员变量、成员函数(可以包含构造函数和析构函数)。它不能包含虚函数和静态数据变量。它也不能被用作其他类的基类,它本身也不能有从某个基类派生而来。Union中得默认访问权限是public。union类型是共享内存的,以size最大的结构作为自己的大小。每个数据成员在内存中的起始地址是相同的。
  4. 在C/C++程序的编写中,当多个基本数据类型或复合数据结构要占用同一片内存时,我们要使用联合体;当多种类型,多个对象,多个事物只取其一时(我们姑且通俗地称其为“n 选1”),我们也可以使用联合体来发挥其长处。在某一时刻,一个union中只能有一个值是有效的。union的一个用法就是可以用来测试CPU是大端模式还是小端模式。

11. ​​​​​​​动态联编与静态联编

  1. 在C++中,联编是指一个计算机程序不同部分彼此关联的过程。按照联编所进行的阶段不同,可以分为静态联编和动态联编;
  2. 静态联编是指联编工作在编译阶段完成的,这种联编过程是在程序运行之前完成的,又称为早期联编。要实现静态联编,在编译阶段就必须确定程序中的操作调用(如函数调用)与执行该操作代码间的关系,确定这种关系称为束定,在编译时的束定称为静态束定。静态联编对函数的选择是基于指向对象的指针或者引用的类型。其优点是效率高,但灵活性差。
  3. 动态联编是指联编在程序运行时动态地进行,根据当时的情况来确定调用哪个同名函数,实际上是在运行时虚函数的实现。这种联编又称为晚期联编,或动态束定。动态联编对成员函数的选择是基于对象的类型,针对不同的对象类型将做出不同的编译结果。C++中一般情况下的联编是静态联编,但是当涉及到多态性和虚函数时应该使用动态联编。动态联编的优点是灵活性强,但效率低。动态联编规定,只能通过指向基类的指针或基类对象的引用来调用虚函数,其格式为:指向基类的指针变量名->虚函数名(实参表)或基类对象的引用名.虚函数名(实参表)
  4. 实现动态联编三个条件:
  5. 必须把动态联编的行为定义为类的虚函数;
  6. 类之间应满足子类型关系,通常表现为一个类从另一个类公有派生而来;
  7.  必须先使用基类指针指向子类型的对象,然后直接或间接使用基类指针调用虚函数;

12. ​​​​​​​动态编译与静态编译

  1. 静态编译,编译器在编译可执行文件时,把需要用到的对应动态链接库中的部分提取出来,连接到可执行文件中去,使可执行文件在运行时不需要依赖于动态链接库
  2. 动态编译的可执行文件需要附带一个动态链接库,在执行时,需要调用其对应动态链接库的命令。所以其优点一方面是缩小了执行文件本身的体积,另一方面是加快了编译速度,节省了系统资源。缺点是哪怕是很简单的程序,只用到了链接库的一两条命令,也需要附带一个相对庞大的链接库;二是如果其他计算机上没有安装对应的运行库,则用动态编译的可执行文件就不能运行。

13. ​​​​​​​讲讲大端小端,如何检测(三种方法)

  1. 大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址端。

  2. 小端模式,是指数据的高字节保存在内存的高地址中,低位字节保存在在内存的低地址端。

  3. 直接读取存放在内存中的十六进制数值,取低位进行值判断
  4. int a = 0x12345678;

    int *c = &a;

    c[0] == 0x12   大端模式

    c[0] == 0x78   小段模式

  5. 用共同体来进行判断​​​​​​​
  6. union共同体所有数据成员是共享一段内存的,后写入的成员数据将覆盖之前的成员数据,成员数据都有相同的首地址。Union的大小为最大数据成员的大小。

    union的成员数据共用内存,并且首地址都是低地址首字节。Int i= 1时:大端存储1放在最高位,小端存储1放在最低位。当读取char ch时,是最低地址首字节,大小端会显示不同的值。

    union w                             w p;

    {                               p.i = 1;

    int i;                         if(ch == 1)    

    char ch;}

14. ​​​​​​​查看内存的方法

  1. 首先打开vs编译器,创建好项目,并且将代码写进去,这里就不贴代码了,你可以随便的写个做个测试;
  2. 调试的时候做好相应的断点,然后点击开始调试;
  3. 程序调试之后会在你设置断点的地方暂停,然后选择调试->窗口->内存,就打开了内存数据查看的窗口了。

15. ​​​​​​​空类会默认添加哪些东西?怎么写?

  1. Empty(); // 缺省构造函数//
  2. Empty( const Empty& ); // 拷贝构造函数//
  3. ~Empty(); // 析构函数//
  4. Empty& operator=( const Empty& ); // 赋值运算符//

16. ​​​​​​​ 为什么拷贝构造函数必须传引用不能传值?

  1. 拷贝构造函数的作用就是用来复制对象的,在使用这个对象的实例来初始化这个对象的一个新的实例。
    2) 参数传递过程到底发生了什么?
      将地址传递和值传递统一起来,归根结底还是传递的是"值"(地址也是值,只不过通过它可以找到另一个值)!
    i)值传递:
      对于内置数据类型的传递时,直接赋值拷贝给形参(注意形参是函数内局部变量);
      对于类类型的传递时,需要首先调用该类的拷贝构造函数来初始化形参(局部对象);如void foo(class_type obj_local){}, 如果调用foo(obj);  首先class_type obj_local(obj) ,这样就定义了局部变量obj_local供函数内部使用
    ii)引用传递:
        无论对内置类型还是类类型,传递引用或指针最终都是传递的地址值!而地址总是指针类型(属于简单类型), 显然参数传递时,按简单类型的赋值拷贝,而不会有拷贝构造函数的调用(对于类类型).
    上述1) 2)回答了为什么拷贝构造函数使用值传递会产生无限递归调用,内存溢出。

    拷贝构造函数用来初始化一个非引用类类型对象,如果用传值的方式进行传参数,那么构造实参需要调用拷贝构造函数,而拷贝构造函数需要传递实参,所以会一直递归

17. ​​​​​​​空类的大小是多少?为什么?

  1. C++空类的大小不为0,不同编译器设置不一样,vs设置为1;
  2. C++标准指出,不允许一个对象(当然包括类对象)的大小为0,不同的对象不能具有相同的地址
  3. 带有虚函数的C++类大小不为1,因为每一个对象会有一个vptr指向虚函数表,具体大小根据指针大小确定;
  4. C++中要求对于类的每个实例都必须有独一无二的地址,那么编译器自动为空类分配一个字节大小,这样便保证了每个实例均有独一无二的内存地址。

18. ​​​​​​​你什么情况用指针当参数,什么时候用引用,为什么?

  1. 使用引用参数的主要原因有两个:
  • 程序员能修改调用函数中的数据对象

    通过传递引用而不是整个数据–对象,可以提高程序的运行速度 

  1. 一般的原则: 
    对于使用引用的值而不做修改的函数:
  • 如果数据对象很小,如内置数据类型或者小型结构,则按照值传递;

    如果数据对象是数组,则使用指针(唯一的选择),并且指针声明为指向const的指针;

    如果数据对象是较大的结构,则使用const指针或者引用,已提高程序的效率。这样可以节省结构所需的时间和空间;

    如果数据对象是类对象,则使用const引用(传递类对象参数的标准方式是按照引用传递);

  1. 对于修改函数中数据的函数:
  • 如果数据是内置数据类型,则使用指针

    如果数据对象是数组,则只能使用指针

    如果数据对象是结构,则使用引用或者指针

    如果数据是类对象,则使用引用

19. ​​​​​​​大内存申请时候选用哪种?C++变量存在哪?变量的大小存在哪?符号表存在哪?

  1. 大内存申请时,采用堆申请空间,用new申请;
  2. 不同的变量存储在不同的地方,局部变量、全局变量、静态变量;
  3. C++对变量名不作存储,在汇编以后不会出现变量名,变量名作用只是用于方便编译成汇编代码,是给编译器看的,是方便人阅读的

20. ​​​​​​​静态函数能定义为虚函数吗?常函数?

  1. static成员不属于任何类对象或类实例,所以即使给此函数加上virutal也是没有任何意义的。
  2. 静态与非静态成员函数之间有一个主要的区别。那就是静态成员函数没有this指针。虚函数依靠vptr和vtable来处理。vptr是一个指针,在类的构造函数中创建生成,并且只能用this指针来访问它,因为它是类的一个成员,并且vptr指向保存虚函数地址的vtable.对于静态成员函数,它没有this指针,所以无法访问vptr. 这就是为何static函数不能为virtual.虚函数的调用关系:this -> vptr -> vtable ->virtual function

 

你可能感兴趣的:(C++,面试问题)