《C++知识梳理及常见面试题》

打印 上一主题 下一主题

主题 930|帖子 930|积分 2805

缺省参数

全缺省、半缺省
   

  • 缺省参数不能在函数声明和界说中同时存在(编译器无法确定使用哪个缺省值)。
  • 半缺省参数必须从右往左依次来给,不能隔断。
  函数重载

同一作用域的功能类似同名函数,同名函数的参数差异(参数个数、类型、类型次序)。
为什么C语言不支持函数重载而C++支持函数重载?
   C/C++程序运行起来,要颠末:预处置惩罚(.i)、编译(.s)、汇编(.o)、链接。
   

  • C 语言由于简单的函数名处置惩罚机制不支持函数重载,编译器对函数名的处置惩罚简单,只使用函数原名举行标识,如果有同名函数,编译器链接的时候就会出现多个同名符号,导致链接错误。
  • 而 C++ 是通过函数名修饰机制(编译器会根据函数名、参数类型和数量等信息对函数名举行修饰,生成一个唯一的标识符,如许在链接时就可以根据唯一标识符来区分差异参数的同名函数。)来区分只要参数差异修饰出来的函数名字就差异,从而是实现了函数重载。
  • 如果两个函数函数名和参数是一样的,返回值差异是不构成重载的,因为调用时编译器没办法区分。
  引用

给变量取别名,与引用的变量共用一块内存空间,引用类型和引用变量类型相同。
   

  • 界说时初始化
  • 一个变量可以有多个引用
  • 一个引用只能引用一个实体
  

  • 常引用
  1. const int c = 1;
  2. //int& rc = c;//error
  3. const int& rc = c;
复制代码
  1. int& Add(int a, int b) {
  2.    int c = a + b;
  3.    return c; }
  4. int main()
  5. {
  6.    int& ret = Add(1, 2);
  7.    Add(3, 4);
  8.    cout << "Add(1, 2) is :"<< ret <<endl;//返回一个已经销毁的局部变量的引用会导致未定义行为,因为引用指向的内存已经无效。
  9.    return 0; }
复制代码
如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用 引用返回,如果已经还给系统了,则必须使用传值返回。
   传值和传引用的服从比较        

  • 以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直 接返回,而是传递形参(形参是实参的一份暂时拷贝,涉及到内存的读写操纵)或者返回变量的一份暂时的拷贝,因此用值作为参数或者返回值类型,服从是非常低下的,尤其是当参数或者返回值类型非常大时,服从就更低。
  • 传引用,传的是实参的引用,引用和实参共用一份内存空间,也就是实参的内存地点,不涉及实参的复制操纵。避免了复制带来的时间开销。
    引用和指针的差异

      

  • 引用概念上界说一个变量的别名,指针存储一个变量地点。
  • 引用在界说时必须初始化,指针没有要求
  •  引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
  • 没有NULL引用,但有NULL指针
  •  在sizeof中寄义差异:引用结果为引用类型的大小,但指针始终是地点空间所占字节个数(32
  • 位平台下占4个字节)
  • 引用自加即引用的实体增长1,指针自加即指针向后偏移一个类型的大小
  • 有多级指针,但是没有多级引用
  • 访问实体方式差异,指针需要显式解引用,引用编译器自己处置惩罚
  • 引用比指针使用起来相对更安全
    内联函数

以inline修饰,一种特殊的函数,在编译时,编译器会实验将内联函数的函数体代码直接插入到调用该函数的地方,而不是像普通函数那样举行函数调用的跳转操纵。如许可以淘汰函数调用的开销(生存恢复寄存器、参数传递),进步程序的执行服从。
   

  • 如果内联函数的函数体比较大,或者在多个地方频仍调用内联函数,会导致代码量大幅增长,占用更多的内存空间。
  • inline是对编译器的发起,不是逼迫要求,编译器会根据现真相况决定是否将内联函数代码睁开。
  • 内联函数不发起声明和界说分离,编译器编译的时候会导致链接错误。inline被睁开没有函数地点了,链接找不到。
  面试题:宏的优缺点?

优点:
   1.增强代码的复用性。
  2.进步性能。
  缺点:
   1.不方便调试宏。(因为预编译阶段举行了替换)
  2.导致代码可读性差,可维护性差,轻易误用。
  3.没有类型安全的检查 。
  C++有哪些技术替代宏
   

  • 常量界说 换用const enum
  •  短小函数界说 换用内联函数
  auto(C++11)

   

  • 类型难于拼写
  • 寄义不明白导致轻易出错
     使用auto界说变量时必须对其举行初始化,在编译阶段编译器需要根据初始化表达式来推导auto 的现实类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量现实的类型。      

  • 不能作为函数参数
  • 不能直接声明数组
    C++实现封装的方式

          在C++语言中实现封装,可以通过类将数据以及操纵数据的方法举行有机结合,通过访问权限来 隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。class的默认访问权限为private,struct为public(因为struct要兼容C)访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。
         面试题:C++struct和class的区别

   解答:C++需要兼容C语言,所以C++中struct可以当成布局体使用。另外C++中struct还可以用来界说类。和class界说类是一样的,区别是struct界说的类默认访问权限是public,class界说的类默认访问权限是private。
  面试题:面向对象的三大特性

      封装、继承、多态。       封装:将数据和操纵数据的方法举行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象举行交互。封装本质上是一种管理,让用户更方便使用类
    类对象的大小

      一个类的大小,现实就是该类中”成员变量”之和,固然要留意内存对齐。
   留意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。
       面试题:布局体内存对齐

   布局体内存对齐规则
      

  • 第一个成员:第一个成员在与布局体变量偏移量为 0 的地点处。
  • 其他成员:其他成员变量要对齐到某个数字(对齐数)的整数倍的地点处。对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。在 VS 中,默认对齐数为 8。
  • 布局体总大小:布局体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
  • 嵌套布局体:如果嵌套了布局体,嵌套的布局体对齐到自己的最大对齐数的整数倍处,布局体的整体大小就是所有最大对齐数(含嵌套布局体的对齐数)的整数倍。
            1. 平台缘故原由(移植缘故原由)
   不是所有的硬件平台都能访问恣意地点上的恣意数据的,某些硬件平台只能在某些地点处取某些特
   2. 性能缘故原由
   数据布局(尤其是栈)应该尽可能地在自然边界上对齐。缘故原由在于,为了访问未对齐的内存,处置惩罚器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
   
  面试题:怎样让布局体按照指定的对齐参数举行对齐?

  能否按照3、4、5即恣意字节对齐?
     能。pragma pack(4);//设置默认对齐数 pragma pack();//恢复默认对齐数。虽然理论上有些编译器可能支持指定 3 或 5 字节对齐,但并不是所有编译器都能保证支持。因为在现实的硬件架构中,对齐通常是按照 2 的幂次方举行优化的,如许可以进步内存访问的服从。在 GCC 编译器中,#pragma pack主要支持 2 的幂次方值(如 1、2、4、8、16 等),对于非 2 的幂次方值(如 3、5),可能无法正常工作或者产生未界说举动。
    联合

  联合也是一种特殊的自界说类型这种类型界说的变量也包罗一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
              联合的大小至少是最大成员的大小。              当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。            
  面试题:什么是大小端?怎样测试某台机器是大端照旧小端,有没有遇到过要思量大小端的场景。

  在计算机系统中,数据通常以字节为单位存储。对于多字节的数据类型(如 int、long 等),它们由多个字节组成,这些字节在内存中的存储次序有两种方式,即大端字节序(Big - Endian)和小端字节序(Little - Endian)。
也称为大端序或大端模式,在大端字节序中,数据的高位字节存放在内存的低地点处,低位字节存放在内存的高地点处。这就犹如我们誊写数字一样,高位在前,低位在后。比方,对于一个 4 字节的整数 0x12345678,在大端字节序的内存中存储如下。
   
  也称为小端序或小端模式,在小端字节序中,数据的低位字节存放在内存的低地点处,高位字节存放在内存的高地点处。对于上述 4 字节的整数 0x12345678,在小端字节序的内存中存储如下:
   
  

  使用指针
  1. // 测试机器大小端的函数
  2. int check_endian_pointer() {
  3.     int num = 1;
  4.     char *ptr = (char *)&num;
  5.     // 如果低地址存储的是 1,说明是小端序
  6.     return (*ptr == 1);
  7. }
复制代码
   使用联合体   
  1. // 测试机器大小端的函数
  2. int check_endian() {
  3.     union {
  4.         int i;
  5.         char c;
  6.     } un;
  7.     un.i = 1;
  8.     // 如果低地址存储的是 1,说明是小端序
  9.     return (un.c == 1);
  10. }
复制代码
this指针

     

  • this指针的类型:类型* const,即成员函数中,不能给this指针赋值。
  • 只能在“成员函数”的内部使用。非静态成员函数没有this指针。
  • this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地点作为实参传递给this形参。所以对象中不存储this指针。
  • this指针是“成员函数”第一个隐含的指针形参,一样寻常情况由编译器通过ecx寄存器自动传递,不需要用户传递。
    面试题:this指针存在那里?

   (也就是存在进程地点空间的哪个区域?)
     答:栈上的,因为他是一个形参。(ps:vs下是在ecx这个寄存器来传递)
      this指针可以为空吗?   
  1. class A
  2. {
  3. public:
  4.    void PrintA()
  5.   {
  6.        cout<<_a<<endl;
  7.         //cout<<"PrintA()"<<endl;
  8.   }
  9. private:
  10. int _a;
  11. };
  12. int main()
  13. {
  14.    A* p = nullptr;
  15.    p->PrintA();
  16.    return 0; }
复制代码
当成员函数不访问成员变量时,this指针可以为空;当成员函数访问成员变量时,this指针不能为空,访问成员变量是,this->_a会解引用空指针,导致未界说举动,引发程序崩溃。
  默认成员函数

   用户没有表现实现,编译器会生成的成员函数。   
  构造函数

      编译器默认生成的默认的构造函数(无参构造函数),对内置类型没什么用,会对自界说类型成员,调用它的默认成员函数。      

  • 函数名与类名相同。
  • 无返回值。
  • 对象实例化时编译器自动调用对应的构造函数。
  • 构造函数可以重载。
       无参、全缺省、不写编译器默认生成的(无参)都是默认构造函数,默认构造函数只能有一个。    析构函数

      

  • 析构函数名是在类名前加上字符 ~。
  • 无参数无返回值类型。
  • 一个类只能有一个析构函数。若未显式界说,系统会自动生成默认的析构函数。留意:析构函数不能重载
  • 对象生命周期结束时,C++编译系统系统自动调用析构函数,编译器生成的默认析构函数,对自定类型成员调用它的析构函数。
       拷贝构造函数

  只有单个形参,该形参是对本类类型对象的引用(一样平常常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
      

  • 拷贝构造函数是构造函数的一个重载形式
  • 拷贝构造函数的参数只有一个必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。
       若未显式界说,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
      
                     留意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请              时,则拷贝构造函数是一定要写的,否则就是浅拷贝。            运算符重载

         C++为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数                          

  • 不能通过毗连其他符号来创建新的操纵符:比如operator@
  • 重载操纵符必须有一个类类型参数。
  • 用于内置类型的运算符,其寄义不能改变,比方:内置的整型+,不能改变其寄义。
  • 作为类成员函数重载时,其形参看起来比操纵数数目少1,因为成员函数的第一个参数为隐藏的this。
  • .* :: sizeof ?: . 留意以上5个运算符不能重载。这个常常在笔试选择题中出现。
            
  1.  bool operator==(const Date& d2)
  2. {
  3.        return _year == d2._year;
  4.            && _month == d2._month
  5.            && _day == d2._day;
  6. }
复制代码
     赋值运算符重载

            

  • 参数类型:const T&,传递引用可以进步传参服从
  • 返回值类型:T&,返回引用可以进步返回的服从,有返回值目的是为了支持一连赋值
  • 检测是否自己给自己赋值
  • 返回*this :要复合一连赋值的寄义
            
  1. Date& operator=(const Date& d)
  2. {
  3. if(this != &d)
  4.       {
  5.            _year = d._year;
  6.            _month = d._month;
  7.            _day = d._day;
  8.       }
  9.        
  10.        return *this;
  11. }
复制代码
    赋值运算符只能重载成类的成员函数不能重载成全局函数
         
  1. // 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
  2. Date& operator=(Date& left, const Date& right) {
  3. if (&left != &right)
  4. {
  5. left._year = right._year;
  6. left._month = right._month;
  7. left._day = right._day;
  8. }
  9. return left; }
  10. // 编译失败:
  11. // error C2801: “operator =”必须是非静态成员
复制代码
            缘故原由:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
                              用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注              意:内置类型成员变量是直接赋值的,而自界说类型成员变量需要调用对应类的赋值运算符              重载完成赋值。                       既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实              现吗?                           留意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。
              
         前置++和后置++重载

      
  1. // 前置++:返回+1之后的结果
  2. // 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
  3. Date& operator++()
  4. {
  5. _day += 1;
  6. return *this;
  7. }
  8. // 后置++:
  9. // 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
  10. // C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器
  11. 自动传递
  12. // 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
  13. 一份,然后给this+1
  14. //       而temp是临时对象,因此只能以值的方式返回,不能返回引用
  15. Date operator++(int)
  16. {
  17. Date temp(*this);
  18. _day += 1;
  19. return temp;
  20. }
复制代码
   取地点及const取地点操纵符重载

    这两个默认成员函数一样寻常不消重新界说 ,编译器默认会生成初始化列表。
                      类中包罗以下成员,必须放在初始化列表位置举行初始化:            

  • 引用成员变量
  • const成员变量
                自界说类型成员(且该类没有默认构造函数时成员变量在类中声明次序就是其在初始化列表中的初始化次序,与其在初始化列表中的先后次序无关。                      explicit关键字

    构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值别的均有默认值 的构造函数,还具有类型转换的作用。
        
  1. // 隐式类型转换:将 int 类型的值 10 转换为 MyClass 类型的对象
  2.     MyClass obj2 = 10;
  3.     obj2.printData();
复制代码
           用explicit修饰构造函数,将会克制构造函数的隐式转换
static

   

  • 静态成员变量一定要在类外举行初始化。
  • 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区。
     

  • 静态成员变量必须在类外界说,界说时不添加static关键字,类中只是声明
  • 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
  • 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  • 静态成员也是类的成员,受public、protected、private 访问限定符的限制
  • 静态成员函数可以通过类名调用,也可以通过对象调用。但在调用时并不会关联到某个特定的对象。
    面试题:实现一个类,计算程序中创建出了多少个类对象

  
  1. class A
  2. {
  3. public:
  4.     A()
  5.     {
  6.         ++_scount;
  7.     }
  8.     A(const A& t)
  9.     {
  10.         ++_scount;
  11.     }
  12.     ~A()
  13.     {
  14.         --_scount;
  15.     }
  16.     int getadd()
  17.     {
  18.         return _scount;
  19.     }
  20.     static int GetACount()
  21.         // 静态成员函数可以直接访问静态成员变量,而不需要通过对象来访问。
  22.         // 静态成员函数不与任何对象绑定,它没有隐含的 this 指针。
  23.         // 这意味着调用静态成员函数时不需要创建类的对象,可以直接通过类名来调用
  24.     {
  25.         return _scount;
  26.     }
  27. private:
  28.     static int _scount;
  29. };
  30. int A::_scount = 0;
  31. void TestA()
  32. {
  33.     cout << A::GetACount() << endl;
  34.     A a1, a2;
  35.     A a3(a1);
  36.     cout << A::GetACount() << endl;
  37. }
  38. int main()
  39. {
  40.     TestA();
  41.     return 0;
  42. }
复制代码
面试题:静态成员函数可以调用非静态成员函数吗?

     

  • 静态成员函数不可以直接调用非静态成员函数。
  • 静态成员函数属于整个类,不依靠于类的任何对象,它没有隐含的 this 指针。
    如果静态成员函数确实需要调用非静态成员函数,需要通过传递对象指针或引用的方式来实现。示比方下:
   
  1. #include <iostream>
  2. class MyClass {
  3. public:
  4.     static void staticFunc(MyClass& obj) {
  5.         obj.nonStaticFunc();
  6.     }
  7.     void nonStaticFunc() {
  8.         std::cout << "Non-static function called." << std::endl;
  9.     }
  10. };
  11. int main() {
  12.     MyClass obj;
  13.     MyClass::staticFunc(obj);
  14.     return 0;
  15. }
复制代码
  非静态成员函数可以调用类的静态成员函数吗?
      非静态成员函数可以直接使用类名或者省略类名来调用静态成员函数。静态成员函数属于整个类,而不是某个具体的对象实例。它不依靠于任何对象,没有隐含的 this 指针。
   面试题:sizeof与strlen

   

  • 计算内容

    • sizeof 计算的是数据类型或变量在内存中占用的字节数,对于数组,它返回整个数组占用的字节数;对于指针,它返回指针本身的大小。
    • strlen 计算的是以 '\0' 末端的字符串的现实长度,不包罗 '\0'。

  • 实用范围

    • sizeof 可以用于任何数据类型,包罗根本数据类型、自界说数据类型、数组、指针等。
    • strlen 只能用于以 '\0' 末端的字符串。

  • 执行时机

    • sizeof 是编译时运算符,其结果在编译时就确定了,不会对表达式举行现实计算。
    • strlen 是运行时函数,需要在程序运行时遍历字符串直到遇到 '\0' 才气确定长度。

           malloc/free和new/delete的区别

                      malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。差异的地方是:             

  • malloc和free是函数,new和delete是操纵符
  • malloc申请的空间不会初始化,new可以初始化
  • malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可
  • malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
  • malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
  • 申请自界说类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

大连全瓷种植牙齿制作中心

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表