精华内容
下载资源
问答
  • 2021最新C++面试题(附答案)

    千次阅读 2021-06-23 21:45:27
    今天分享给大家的是比较全面的C/C++面试题,也都是C++版本升级之后,重新整理归纳的最新答案,会让C++面试者少走很多不必要的弯路。同时每个C++面试题都尽量做到了详尽的面试解析文档,以确保每个阶段的读者都能看得...

    今天分享给大家的是比较全面的C/C++面试题,也都是C++版本升级之后,重新整理归纳的最新答案,会让C++面试者少走很多不必要的弯路。同时每个C++面试题都尽量做到了详尽的面试解析文档,以确保每个阶段的读者都能看得懂,同时这部分C++面试文档也是可以免费的提供给有需要的同学们学习的!

    博主已将大量C++相关面试题汇总整理成了一个PDF版的C++面试宝典,关注微 信 公 众 号 “C和C加加” 回复“面试题”即可获取!

    一、计算机基础

    1.C/C++内存有哪几种类型?

    C中,内存分为5个区:堆(malloc)、栈(如局部变量、函数参数)、程序代码区(存放二进制代码)、全局/静态存储区(全局变量、static变量)和常量存储区(常量)。此外,C++中有自由存储区(new)一说。
    全局变量、static变量会初始化为缺省值,而堆和栈上的变量是随机的,不确定的。

    2.堆和栈的区别?

    1).堆存放动态分配的对象——即那些在程序运行时动态分配的对象,比如 new 出来的对象,其生存期由程序控制;
    2).栈用来保存定义在函数内的非static对象,如局部变量,仅在其定义的程序块运行时才存在;
    3).静态内存用来保存static对象,类static数据成员以及定义在任何函数外部的变量,static对象在使用之前分配,程序结束时销毁;
    4).栈和静态内存的对象由编译器自动创建和销毁。

    3.堆和自由存储区的区别?

    总的来说,堆是C语言和操作系统的术语,是操作系统维护的一块动态分配内存;自由存储是C++中通过new与delete动态分配和释放对象的抽象概念。他们并不是完全一样。
    从技术上来说,堆(heap)是C语言和操作系统的术语。堆是操作系统所维护的一块特殊内存,它提供了动态分配的功能,当运行程序调用malloc()时就会从中分配,稍后调用free可把内存交还。而自由存储是C++中通过new和delete动态分配和释放对象的抽象概念,通过new来申请的内存区域可称为自由存储区。基本上,所有的C++编译器默认使用堆来实现自由存储,也即是缺省的全局运算符new和delete也许会按照malloc和free的方式来被实现,这时藉由new运算符分配的对象,说它在堆上也对,说它在自由存储区上也正确。

    4.程序编译的过程?

    程序编译的过程中就是将用户的文本形式的源代码(c/c++)转化成计算机可以直接执行的机器代码的过程。主要经过四个过程:预处理、编译、汇编和链接。具体示例如下。
    一个hello.c的c语言程序如下

    #include <stdio.h>
    int main()
    {
        printf("happy new year!\n");
        return 0;
    }
    

    其编译过程如下:

    5.计算机内部如何存储负数和浮点数?

    负数比较容易,就是通过一个标志位和补码来表示。
    对于浮点类型的数据采用单精度类型(float)和双精度类型(double)来存储,float数据占用32bit,double数据占用64bit,我们在声明一个变量float f= 2.25f的时候,是如何分配内存的呢?如果胡乱分配,那世界岂不是乱套了么,其实不论是float还是double在存储方式上都是遵从IEEE的规范的,float遵从的是IEEE R32.24 ,而double 遵从的是R64.53。更多可以参考浮点数表示。
    无论是单精度还是双精度在存储中都分为三个部分:

    1). 符号位(Sign) : 0代表正,1代表为负
    2). 指数位(Exponent):用于存储科学计数法中的指数数据,并且采用移位存储
    3). 尾数部分(Mantissa):尾数部分
    其中float的存储方式如下图所示:

    而双精度的存储方式如下图:

    6.函数调用的过程?

    如下结构的代码

    int main(void)
    {
      ...
      d = fun(a, b, c);
      cout<<d<<endl;
      ...
      return 0;
    }
    

    调用fun()的过程大致如下:

    main()========
    1).参数拷贝(压栈),注意顺序是从右到左,即c-b-a;
    2).保存d = fun(a, b, c)的下一条指令,即cout< 3).跳转到fun()函数,注意,到目前为止,这些都是在main()中进行的;
    fun()=====
    4).移动ebp、esp形成新的栈帧结构;
    5).压栈(push)形成临时变量并执行相关操作;
    6).return一个值;
    7).出栈(pop);
    8).恢复main函数的栈帧结构;
    9).返回main函数;
    main()========
    。。。

    7. 左值和右值

    不是很严谨的来说,左值指的是既能够出现在等号左边也能出现在等号右边的变量(或表达式),右值指的则是只能出现在等号右边的变量(或表达式)。举例来说我们定义的变量 a 就是一个左值,而malloc返回的就是一个右值。或者左值就是在程序中能够寻值的东西,右值就是一个具体的真实的值或者对象,没法取到它的地址的东西(不完全准确),因此没法对右值进行赋值,但是右值并非是不可修改的,比如自己定义的class, 可以通过它的成员函数来修改右值。

    归纳一下就是:

    可以取地址的,有名字的,非临时的就是左值
    不能取地址的,没有名字的,临时的,通常生命周期就在某个表达式之内的就是右值

    8. 什么是内存泄漏?面对内存泄漏和指针越界,你有哪些方法?你通常采用哪些方法来避免和减少这类错误?

    用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元即为内存泄露。

    1). 使用的时候要记得指针的长度.
    2). malloc的时候得确定在那里free.
    3). 对指针赋值的时候应该注意被赋值指针需要不需要释放.
    4). 动态分配内存的指针最好不要再次赋值.
    5). 在C++中应该优先考虑使用智能指针.

    二、C/C++的比较面试题

    1. C和C++的区别?

    1). C++是C的超集;
    2). C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现过程(事务)控制),而对于C++,首要考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程(事务)控制。

    2. int fun() 和 int fun(void)的区别?

    这里考察的是c 中的默认类型机制。

    在c中,int fun() 会解读为返回值为int(即使前面没有int,也是如此,但是在c++中如果没有返回类型将报错),输入类型和个数没有限制, 而int fun(void)则限制输入类型为一个void。
    在c++下,这两种情况都会解读为返回int类型,输入void类型。

    3. const 有什么用途

    主要有几点:

    1).定义只读变量,或者常量(只读变量和常量的区别参考下面一条);
    2).修饰函数的参数和函数的返回值;
    3).修饰函数的定义体,这里的函数为类的成员函数,被const修饰的成员函数代表不能修改成员变量的值,因此const成员函数只能调用const成员函数;
    4).只读对象。只读对象只能调用const成员函数。

    class Screen {
    public:
    const char cha; //const成员变量
    char get() const; //const成员函数
    };
    
    const Screen screen; //只读对象
    

    4. 在C中用const 能定义真正意义上的常量吗?C++中的const呢?

    不能。c中的const仅仅是从编译层来限定,不允许对const 变量进行赋值操作,在运行期是无效的,所以并非是真正的常量(比如通过指针对const变量是可以修改值的),但是c++中是有区别的,c++在编译时会把const常量加入符号表,以后(仍然在编译期)遇到这个变量会从符号表中查找,所以在C++中是不可能修改到const变量的。
    补充:

    1)c中的局部const常量存储在栈空间,全局const常量存在只读存储区,所以全局const常量也是无法修改的,它是一个只读变量。
    2)这里需要说明的是,常量并非仅仅是不可修改,而是相对于变量,它的值在编译期已经决定,而不是在运行时决定。
    3)c++中的const 和宏定义是有区别的,宏是在预编译期直接进行文本替换,而const发生在编译期,是可以进行类型检查和作用域检查的。
    4)c语言中只有enum可以实现真正的常量。
    5)c++中只有用字面量初始化的const常量会被加入符号表,而变量初始化的const常量依然只是只读变量。
    6)c++中const成员为只读变量,可以通过指针修改const成员的值,另外const成员变量只能在初始化列表中进行初始化。

    下面我们通过代码来看看区别。
    同样一段代码,在c编译器下,打印结果为*pa = 4, 4
    在c++编译下打印的结果为 *pa = 4, 8

    int main(void)
    {
        const int a = 8;
        int *pa = (int *)&a;
        *pa = 4;
        printf("*pa = %d, a = %d", *pa, a);
        return 0;
    }
    

    另外值得一说的是,由于c++中const常量的值在编译期就已经决定,下面的做法是OK的,但是c中是编译通不过的。

    int main(void)
    {
        const int a = 8;
        const int b = 2;
        int array[a+b] = {0};
        return 0;
    }
    

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

    1)首先宏是C中引入的一种预处理功能;
    2)内联(inline)函数是C++中引用的一个新的关键字;C++中推荐使用内联函数来替代宏代码片段;
    3)内联函数将函数体直接扩展到调用内联函数的地方,这样减少了参数压栈,跳转,返回等过程;
    4)  由于内联发生在编译阶段,所以内联相较宏,是有参数检查和返回值检查的,因此使用起来更为安全;
    5)  需要注意的是, inline会向编译期提出内联请求,但是是否内联由编译期决定(当然可以通过设置编译器,强制使用内联);
    6)  由于内联是一种优化方式,在某些情况下,即使没有显示的声明内联,比如定义在class内部的方法,编译器也可能将其作为内联函数。
    7)  内联函数不能过于复杂,最初C++限定不能有任何形式的循环,不能有过多的条件判断,不能对函数进行取地址操作等,但是现在的编译器几乎没有什么限制,基本都可以实现内联。

    6. C++中有了malloc / free , 为什么还需要 new / delete?

    1). malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
    2). 对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。
    由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。
    最后补充一点体外话,new 在申请内存的时候就可以初始化(如下代码), 而malloc是不允许的。另外,由于malloc是库函数,需要相应的库支持,因此某些简易的平台可能不支持,但是new就没有这个问题了,因为new是C++语言所自带的运算符。

    int *p = new int(1);
    

    特别的,在C++中,如下的代码,用new创建一个对象(new 会触发构造函数, delete会触发析构函数),但是malloc仅仅申请了一个空间,所以在C++中引入new和delete来支持面向对象。

    #include <cstdlib>
    class Test
    {
        ...
    }
    
    Test* pn = new Test;
    Test* pm = (Test*)malloc(sizeof(Test));
    

    7. C和C++中的强制类型转换?

    C中是直接在变量或者表达式前面加上(小括号括起来的)目标类型来进行转换,一招走天下,操作简单,但是由于太过直接,缺少检查,因此容易发生编译检查不到错误,而人工检查又及其难以发现的情况;而C++中引入了下面四种转换:

    1)static_cast

    • 用于基本类型间的转换
    • 不能用于基本类型指针间的转换
    • 用于有继承关系类对象间的转换和类指针间的转换

    2)dynamic_cast

    • 用于有继承关系的类指针间的转换
    • 用于有交叉关系的类指针间的转换
    • 具有类型检查的功能
    • 需要虚函数的支持

    3)reinterpret_cast

    • 用于指针间的类型转换
    • 用于整数和指针间的类型转换

    4)const_cast

    • 用于去掉变量的const属性
    • 转换的目标类型必须是指针或者引用

    在C++中,普通类型可以通过类型转换构造函数转换为类类型,那么类可以转换为普通类型吗?答案是肯定的。但是在工程应用中一般不用类型转换函数,因为无法抑制隐式的调用类型转换函数(类型转换构造函数可以通过explicit来抑制其被隐式的调用),而隐式调用经常是bug的来源。实际工程中替代的方式是定义一个普通函数,通过显式的调用来达到类型转换的目的。

    class test{
        int m_value;
        ...
    public:
        operator int()  //类型转换函数
        {
            return m_value;
        }
    
        int toInt() //显示调用普通函数来实现类型转换
        {
            return m_value
        }
    };
    
    int main()
    {
        ...
        test a(5);
        int i = a;
        ...
    
        return 0;
    }
    

    8. static 有什么用途

    • 静态(局部/全局)变量
    • 静态函数
    • 类的静态数据成员
    • 类的静态成员函数

    9. 类的静态成员变量和静态成员函数各有哪些特性?

    静态成员变量

    • 静态成员变量需要在类内声明(加static),在类外初始化(不能加static),如下例所示;
    • 静态成员变量在类外单独分配存储空间,位于全局数据区,因此静态成员变量的生命周期不依赖于类的某个对象,而是所有类的对象共享静态成员变量;
    • 可以通过对象名直接访问公有静态成员变量;
    • 可以通过类名直接调用公有静态成员变量,即不需要通过对象,这一点是普通成员变量所不具备的。
    class example{
    public:
    static int m_int; //static成员变量
    };
    
    int example::m_int = 0; //没有static
    
    cout<<example::m_int; //可以直接通过类名调用静态成员变量
    

    静态成员函数

    •  静态成员函数是类所共享的;
    • 静态成员函数可以访问静态成员变量,但是不能直接访问普通成员变量(需要通过对象来访问);需要注意的是普通成员函数既可以访问普通成员变量,也可以访问静态成员变量;
    • 可以通过对象名直接访问公有静态成员函数;
    • 可以通过类名直接调用公有静态成员函数,即不需要通过对象,这一点是普通成员函数所不具备的。
    class example{
    private:
    static int m_int_s; //static成员变量
    int m_int;
    static int getI() //静态成员函数在普通成员函数前加static即可
    {
      return m_int_s; //如果返回m_int则报错,但是可以return d.m_int是合法的
    }
    };
    
    cout<<example::getI(); //可以直接通过类名调用静态成员变量
    

    10. 在C++程序中调用被C编译器编译后的函数,为什么要加extern“C”?

    C++语言支持函数重载,C语言不支持函数重载,函数被C++编译器编译后在库中的名字与C语言的不同,假设某个函数原型为:

    void foo(int x, int y);
    

    该函数被C编译器编译后在库中的名字为 _foo, 而C++编译器则会产生像: _foo_int_int 之类的名字。为了解决此类名字匹配的问题,C++提供了C链接交换指定符号 extern “C”。

    11. 头文件中的 ifndef/define/endif 是干什么用的? 该用法和 program once 的区别?

    相同点:

    它们的作用是防止头文件被重复包含。

    不同点

    • ifndef 由语言本身提供支持,但是 program once 一般由编译器提供支持,也就是说,有可能出现编译器不支持的情况(主要是比较老的编译器)。
    • 通常运行速度上 ifndef 一般慢于 program once,特别是在大型项目上, 区别会比较明显,所以越来越多的编译器开始支持 program once。
    • ifndef 作用于某一段被包含(define 和 endif 之间)的代码, 而 program once 则是针对包含该语句的文件, 这也是为什么 program once 速度更快的原因。
    • 如果用 ifndef 包含某一段宏定义,当这个宏名字出现“撞车”时,可能会出现这个宏在程序中提示宏未定义的情况(在编写大型程序时特性需要注意,因为有很多程序员在同时写代码)。相反由于program once 针对整个文件, 因此它不存在宏名字“撞车”的情况, 但是如果某个头文件被多次拷贝,program once 无法保证不被多次包含,因为program once 是从物理上判断是不是同一个头文件,而不是从内容上。

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

    答:理论上++i更快,实际与编译器优化有关,通常几乎无差别。

    //i++实现代码为:
    int operator++(int)
    {
        int temp = *this;
        ++*this;
        return temp;
    }//返回一个int型的对象本身
    
    // ++i实现代码为:
    int& operator++()
    {
        *this += 1;
        return *this;
    }//返回一个int型的对象引用
    

    i++和++i的考点比较多,简单来说,就是i++返回的是i的值,而++i返回的是i+1的值。也就是++i是一个确定的值,是一个可修改的左值,如下使用:

    cout << ++(++(++i)) << endl;
    cout << ++ ++i << endl;
    

    可以不停的嵌套++i。
    这里有很多的经典笔试题,一起来观摩下:

    int main()
    {
        int i = 1;
        printf("%d,%d\n", ++i, ++i);    //3,3
        printf("%d,%d\n", ++i, i++);    //5,3
        printf("%d,%d\n", i++, i++);    //6,5
        printf("%d,%d\n", i++, ++i);    //8,9
        system("pause");
        return 0;
    }
    

    首先是函数的参数入栈顺序从右向左入栈的,计算顺序也是从右往左计算的,不过都是计算完以后再进行的压栈操作:
    对于第1个printf,首先执行++i,返回值是i,这时i的值是2,再次执行++i,返回值是i,得到i=3,将i压入栈中,此时i为3,也就是压入3,3;
    对于第2个printf,首先执行i++,返回值是原来的i,也就是3,再执行++i,返回值是i,依次将3,5压入栈中得到输出结果
    对于第3个printf,首先执行i++,返回值是5,再执行i++返回值是6,依次将5,6压入栈中得到输出结果
    对于第4个printf,首先执行++i,返回i,此时i为8,再执行i++,返回值是8,此时i为9,依次将i,8也就是9,8压入栈中,得到输出结果。
    上面的分析也是基于VS搞的,不过准确来说函数多个参数的计算顺序是未定义的(the order of evaluation of function arguments are undefined)。笔试题目的运行结果随不同的编译器而异。

    三、数组、指针和C++特性

    1.指针数组和数组指针的区别

    数组指针,是指向数组的指针,而指针数组则是指该数组的元素均为指针。

    数组指针,是指向数组的指针,其本质为指针,形式如下。如 int (*p)[10],p即为指向数组的指针,()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。数组指针是指向数组首元素的地址的指针,其本质为指针,可以看成是二级指针。

    类型名 (*数组标识符)[数组长度]

    指针数组,在C语言和C++中,数组元素全为指针的数组称为指针数组,其中一维指针数组的定义形式如下。指针数组中每一个元素均为指针,其本质为数组。如 int *p[n], []优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1时,则p指向下一个数组元素,这样赋值是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]…p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 *p=a; 这里*p表示指针数组第一个元素的值,a的首地址的值。

    类型名 *数组标识符[数组长度]

    2.左值引用与右值引用

    左值引用就是我们通常所说的引用,如下所示。左值引用通常可以看作是变量的别名。

    type-id & cast-expression 
    
    // demo
    int a = 10
    int &b = a
    
    int &c = 10	// 错误,无所对一个立即数做引用
    
    const int &d = 10	// 正确, 常引用引用常数量是ok的,其等价于 const int temp = 10; const int &d = temp	
    

    右值引用是 C++11 新增的特性,其形式如下所示。右值引用用来绑定到右值,绑定到右值以后本来会被销毁的右值的生存期会延长至与绑定到它的右值引用的生存期。

    type-id && cast-expression  
    
    // demo
    int &&var = 10;	// ok
    
    int a = 10
    int &&b = a	// 错误, a 为左值
    
    int &&c = var	// 错误,var 为左值
    
    int &&d = move(a)	// ok, 通过move得到左值的右值引用
    

    在汇编层面右值引用做的事情和常引用是相同的,即产生临时量来存储常量。但是,唯一 一点的区别是,右值引用可以进行读写操作,而常引用只能进行读操作。

    3.右值引用的意义

    • 右值引用支持移动语义的实现,可以减少拷贝,提升程序的执行效率。

      下面的代码时没有采用右值引用时的实现。

    class Stack
    {
    public:
        // 构造
        Stack(int size = 1000) 
    	:msize(size), mtop(0)
        {
    	cout << "Stack(int)" << endl;
    	mpstack = new int[size];
        }
    	
        // 析构
        ~Stack()
        {
    	cout << "~Stack()" << endl;
    	delete[]mpstack;
    	mpstack = nullptr;
        }
    	
        // 拷贝构造
        Stack(const Stack &src)
    	:msize(src.msize), mtop(src.mtop)
        {
    	cout << "Stack(const Stack&)" << endl;
    	mpstack = new int[src.msize];
    	for (int i = 0; i < mtop; ++i) {
    	    mpstack[i] = src.mpstack[i];
    	}
        }
    	
        // 赋值重载
        Stack& operator=(const Stack &src)
        {
    	cout << "operator=" << endl;
    	if (this == &src)
         	    return *this;
    
    	delete[]mpstack;
    
    	msize = src.msize;
    	mtop = src.mtop;
    	mpstack = new int[src.msize];
    	for (int i = 0; i < mtop; ++i) {
    	    mpstack[i] = src.mpstack[i];
    	}
    	return *this;
        }
    
        int getSize() 
        {
    	return msize;
        }
    private:
        int *mpstack;
        int mtop;
        int msize;
    };
    
    Stack GetStack(Stack &stack)
    {
        Stack tmp(stack.getSize());
        return tmp;
    }
    
    int main()
    {
        Stack s;
        s = GetStack(s);
        return 0;
    }
    

    运行结果如下

    Stack(int)             // 构造s
    Stack(int)             // 构造tmp
    Stack(const Stack&)    // tmp拷贝构造main函数栈帧上的临时对象
    ~Stack()               // tmp析构
    operator=              // 临时对象赋值给s
    ~Stack()               // 临时对象析构
    ~Stack()               // s析构
    

    执行代码的过程中调用拷贝构造,将内存中的内容逐个拷贝,在 C++ 11 中可以借助右值引用实现移动拷贝构造和移动赋值来解决这个问题。

    Stack(Stack &&src)
        :msize(src.msize), mtop(src.mtop)
    {
        cout << "Stack(Stack&&)" << endl;
    
        /*此处没有重新开辟内存拷贝数据,把src的资源直接给当前对象,再把src置空*/
        mpstack = src.mpstack;  
        src.mpstack = nullptr;
    }
    
    // 带右值引用参数的赋值运算符重载函数
    Stack& operator=(Stack &&src)
    {
        cout << "operator=(Stack&&)" << endl;
    
        if(this == &src)
            return *this;
    	    
        delete[]mpstack;
    
        msize = src.msize;
        mtop = src.mtop;
    
        /*此处没有重新开辟内存拷贝数据,把src的资源直接给当前对象,再把src置空*/
        mpstack = src.mpstack;
        src.mpstack = nullptr;
    
        return *this;
    }
    

    执行结果如下。可以看到,在有拷贝构造和移动拷贝构造函数的时候,优先调用了移动拷贝构造和移动赋值。在移动拷贝构造和移动赋值中直接把资源所有权进行了转移,而非拷贝,这就大大提高了执行效率。

    Stack(int)             // 构造s
    Stack(int)             // 构造tmp
    Stack(Stack&&)         // 调用带右值引用的拷贝构造函数,直接将tmp的资源给临时对象
    ~Stack()               // tmp析构
    operator=(Stack&&)     // 调用带右值引用的赋值运算符重载函数,直接将临时对象资源给s
    ~Stack()               // 临时对象析构
    ~Stack()               // s析构
    

    右值引用在可以使重载函数变得更加简洁。右值引用可以适用 const T& 和 T& 形式的参数。

    struct W  
    {  
       W(int&, int&) {}  
    };  
      
    struct X  
    {  
       X(const int&, int&) {}  
    };  
      
    struct Y  
    {  
       Y(int&, const int&) {}  
    };  
      
    struct Z  
    {  
       Z(const int&, const int&) {}  
    };
    
    
    template <typename T, typename A1, typename A2>  
    T* factory(A1& a1, A2& a2)  
    {  
       return new T(a1, a2);  
    } 
    
    
    template <typename T, typename A1, typename A2>  
    T* factory_new(A1&& a1, A2&& a2)  
    {  
       return new T(std::forward<A1>(a1), std::forward<A2>(a2));  
    }  
    
    // demo
    int a = 2;
    int b = 2;
    
    W* c = factory<w>(a, b);	// ok
    Z* d = factory<Z>(2, 2);	// 错误,2 是右值
    
    W* pw = factory_new<W>(a, b);	// ok
    X* px = factory_new<X>(2, b);	// ok
    Y* py = factory_new<Y>(a, 2);	// ok
    Z* e = factory_new<Z>(2, 2);	// ok
    W* f = factory_new<W>(2, 2);	// 错误, 
    

    4.什么时候生成默认构造函数(无参构造函数)?什么时候生成默认拷贝构造函数?什么是深拷贝?什么是浅拷贝?默认拷贝构造函数是哪种拷贝?什么时候用深拷贝?

    • 没有任何构造函数时,编译器会自动生成默认构造函数,也就是无参构造函数;当类没有拷贝构造函数时,会生成默认拷贝构造函数
    • 深拷贝是指拷贝后对象的逻辑状态相同,而浅拷贝是指拷贝后对象的物理状态相同;默认拷贝构造函数属于浅拷贝。
    • 当系统中有成员指代了系统中的资源时,需要深拷贝。比如指向了动态内存空间,打开了外存中的文件或者使用了系统中的网络接口等。如果不进行深拷贝,比如动态内存空间,可能会出现多次被释放的问题。是否需要定义拷贝构造函数的原则是,是类是否有成员调用了系统资源,如果定义拷贝构造函数,一定是定义深拷贝,否则没有意义。
      更多可以参考下面的代码,比较容易混淆的是赋值操作符,其实区分很简单,在出现等号的时候,如果有构造新的对象时调用的就是构造,不然就是赋值操作符。
    class A {
    public:
    	A() {
    		m = new int[4]{ 1,2,3,4 };
    		std::cout << "constructor" << std::endl;
    	}
    	~A() {
    		if (m != nullptr) {
    			delete[] m;
    		}
    	}
    	A(const A& a) {
    		this->m = new int[4];
    		memcpy(a.m, this->m, this->len * sizeof(int));
    		std::cout << "copy constructor" << std::endl;
    	}
    	// 移动构造
        A(A&& a) : m(a.m) {
    		a.m = nullptr; 
    		std::cout << "move constructor" << std::endl;
    	}
        // 赋值操作符重载
        A& operator= (const A& a) {
            memcpy(a.m, this->m, this->len * sizeof(int));
            std::cout << "operator" << std::endl;
            return *this;
        }
    ​
    private:
        int len = 4;
        int* m = nullptr;
    };
    ​
    A getA(A a) {
        return a;
    }
    ​
    int main(void)
    {
        A a;    // construct
        
        A b = a;    // copy construct
        A c(a); // copy construct
        
        A d;    // construct
        d = a;  // operate
    ​
        A e = getA(a);  // construct, move construct
    ​
        return 0;
    }
    

    5.虚析构函数的作用?

    基类采用虚析构函数可以防止内存泄漏。比如下面的代码中,如果基类 A 中不是虚析构函数,则 B 的析构函数不会被调用,因此会造成内存泄漏。

    class A{
    public:
      A(){}
      //~A(){}
      virtual ~A(){}  // 虚析构
    };
    class B : public A{
    public:
      B(){
        // new memory
      }
      ~B(){
        // delete memory
      }
    };
    int main(int argc, char *argv)
    {
      A *p = new B;
      
      // some operations
      // ...
      
      delete p;  // 由于基类中是虚析构,这里会先调用B的析构函数,然后调用A的析构函数
      
      return 0;
    }
    

    但并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。

    6.解释下封装、继承和多态?

    1)封装:
    封装是实现面向对象程序设计的第一步,封装就是将数据或函数等集合在一个个的单元中(我们称之为类)

    封装的意义在于保护或者防止代码(数据)被我们无意中破坏。

    从封装的角度看,public, private 和 protected 属性的特点如下

    不管那种属性,内类都是可以访问的

    public 是一种暴露的手段,比如暴露接口,类的对象可以访问

    private 是一种隐藏的手段,类的对象不能访问

    protected 成员:

    和 public 一样可以被子类继承

    和 private 一样不能在类外被直接调用

    特例:在衍生类中可以通过衍生类对象访问,如下代码所示

    class Base  
    {  
    public:  
        Base(){};  
        virtual ~Base(){};  
    protected:  
        int int_pro;  
    };
    class A : public Base  
    {  
    public:  
        A(){};  
        A(int da){int_pro = da;}  
        // 通过 obj 对象直接访问 protected 成员
        void Set(A &obj){obj.int_pro = 24;}	
        void PrintPro(){cout << "The proteted data is " << int_pro <<endl;}  
    };  
    

    2). 继承:

    继承主要实现重用代码,节省开发时间。

    子类可以继承父类的一些东西。

    a.**公有继承(public)**公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态(基类的私有成员仍然是私有的,不能被这个派生类的子类所访问)。

    b.**私有继承(private)**私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员(并且不能被这个派生类的子类所访问)。

    c.**保护继承(protected)**保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员(并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的)。

    这里特别提一下虚继承。虚继承是解决C++多重继承问题(其一,浪费存储空间;第二,存在二义性问题)的一种手段。比如菱形继承,典型的应用就是 iostream, 其继承于 istream 和 ostream,而 istream 和 ostream 又继承于 ios。

    3).多态:

    多态是指通过基类的指针或者引用,在运行时动态调用实际绑定对象函数的行为。与之相对应的编译时绑定函数称为静态绑定。多态是设计模式的基础,多态是框架的基础。

    博主已将大量C++相关面试题汇总整理成了一个PDF版的C++面试宝典,关注微 信 公 众 号 “C和C加加” 回复“面试题”即可获取!

    展开全文
  • C++面试题总结,一篇就够了

    千次阅读 2021-06-11 08:15:47
    C++面试题汇总1. C基础1.1 内存模型1.1.1 内存四区1.1.2 分配函数与释放函数1.1.2.1 malloc / free1.1.2.2 new / delete1.1.2.3 new/delete 与 malloc/free 区别1.1.2.5 calloc 、realloc1.2 预编译1.2.1 头文件问题...

    C++面试题汇总


    C++知识点总结快速跳转

    1. C基础

    1.1 内存模型

    1.1.0 内存四区

    意义在于:赋予其不同的生命周期,给编程带来更大的灵活性

    • 运行前
      • 代码区:存放函数体的二进制代码,由操作系统管理
        • 共享的
        • 只读的:防止程序意外修改其指令
      • 全局区:存放全局变量和静态变量以及常量,结束后由系统释放
        • 全局区还包括常量区(字符串常量,const修饰的全局常量)
    • 运行后
      • 栈区:由编译器自动分配和释放,存放函数体的参数值、局部变量
        • 不能返回局部变量的地址,当离开作用域后,开辟在栈区的局部变量会被编译器自动回收
      • 堆区:由程序员分配和释放,若不释放,程序结束后由操作系统释放
        • 分全局堆和局部堆
        • 全局堆就是所有没有分配的空间,局部堆就是用户分配的空间
        • 堆在操作系统对进程 初始化的时候分配,运行过程中也可以向系统要额外的堆

    1.1.1 简述C、C++程序编译的内存分配情况

    • 从静态存储区域分配
      内存在程序 编译 时 就已 经 分配 好,这块内 存在 程序 的整 个运行 期间 都存在 。速 度快、不容易出错 , 因 为 有系 统 会善 后。例 如全 局变 量, sta tic 变量, 常量 字符 串等。
    • 在栈上分配
      在执行函数时, 函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释放。 栈内存分配运算内置于处理器的指令集中, 效率很高, 但是 分配的内存容量有限 。大小为2M。
    • 从堆上分配
      即动态内存分配。程序在运行的时候用 malloc 或 new 申请任意大小的内存,程序员自己负责在何时用 free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活。如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏 ,另外频繁地分配和释放不同大小的堆空间将会产生堆内碎块 。

    一个C、C++ 程序****编译时内存分为5大存储区:堆区、栈区、全局区、文字常量区、程序代码区

    1.1.2 分配函数与释放函数

    • C:malloc、calloc、realloc / free
    • C++:new / delete

    1.1.2.1 malloc / free

    大多数实现所分配的存储空间比所要求的要稍大一些,额外的空间用来记录管理信息——分配块的长度,指向下一个分配块的指针等等

    • malloc函数向内存申请一块连续可用的空间
    • 开辟成功则返回一个指向该空间的void* 型指针,所以需要对其进行强制类型转换,转换成我们想要的类型
    • 开辟失败则返回 NULL,所以一定要对malloc的返回值进行检查
    • free 用来释放动态开辟的内存,而不是释放指针
    int* ptr = NULL;
    ptr = (int*)malloc(1000*sizeof(int));//开辟一千个int大小的内存,并强制类型转换
    if(NULL == ptr){
    	exit(1);
    }
    free(ptr);
    ptr = NULL;
    
    • 释放只能一次,如果释放两次及两次以上会出现错误
    • 释放空指针例外,释放空指针其实也等于什么都没做,所以释放空指针释放多少次都没有问题

    1.1.2.2 new / delete

    new分配内存步骤

    • 调用operator new 函数
    • 调用相应的构造函数构造对象,并传入初值
    • 对象构造完成后,返回一个指向该对象的指针

    delete释放内存步骤

    • 调用对象的析构函数
    • 调用operator delete 函数释放内存空间
    //开辟变量
    int* a = new int(10);
    delete a;
    
    //开辟数组
    int* arr = new int[10];
    delete[] arr;
    

    1.1.2.3 new/delete 与 malloc/free 区别

    • 开辟位置
      • 严格来说,malloc动态开辟的内存在堆区,new开辟的叫做自用存储区
      • 若不重载new操作符,c++编译器一般默认使用堆来实现自用存储,此时等价于堆区
      • 特别:new可以不为对象分配内存
    • 重载
      • new 、delete 是操作符,可以重载,只能在C++ 中使用。 malloc、free 是函数,可以覆盖,C、C++ 中都可以使用。
    • 是否调用构造与析构函数
      • new 可以调用对象的构造函数,对应的delete 调用相应的析构函数。malloc 仅仅分配内存,free 仅仅回收内存,并不执行构造和析构函数
    • 是否需要指定内存大小
      • malloc 需要显式指出开辟内存的大小,new 无需指定,编译器会自动计算
    • 返回值类型
      • new返回的是某种数据类型指针,malloc返回的是void 指针,new比malloc更安全
      • new内存分配失败时,会抛出bac_alloc异常,不会返回NULL;malloc开辟内存失败会返回NULL指针,所以需要判断

    1.1.2.5 calloc 、realloc

    calloc(number,size):为number个大小为size的元素开辟一块空间,并把每个字节初始化为0
    realloc(内存地址,大小):用于调整申请的空间大小

    1.1.2.6 在C++中,使用malloc申请的内存能否通过delete释放?使用new申请的内存能否用free?

    不能,malloc/free主要为了兼容C,new和 delete 完全可以取代malloc/free的。malloc/free 的操作对象都是必须明确大小的。而且不能用在动态类上。new 和 delete会自动进行类型检查和大小 ,malloc/free不能执行构造函数与析构函数 ,所 以动态对象它是不行的。当然从理论上说使用malloc 申请的内存是可以通过delete释放的 。不过一般不这样写的。而且也不能保证每个C++的运行时都能正常

    1.2 预编译

    1.2.1 头文件问题

    • #include < >:只搜索系统目录,不会搜索本地目录
    • #include " ":首先搜索本地目录,若找不到才会搜索系统目录
    • #include<>相较于#include" " 快一些

    1.2.2 const 与 #define相比有什么优点

    • const 常量有数据类型,而宏常量没有数据类型,编译器可以对前者进行安全检查。对后者只进行字符替换,没有安全类型检查,并且在字符替换可能会产生意想不到的错误
    • 有些集成化的调试工具可用对const进行调试,但是不能对宏常量进行调试

    1.3 宏,内联函数

    1.3.1 内联函数

    • 定义:在函数定义体前加入关键字inline,使函数成为内联函数
    • 增加空间消耗换取效率提高,这点与宏一样
    • 内联函数和普通函数相比可以加快程序运行的速度,因为不需要中断调用
    void fun(int x,int y);
    inline void fun(int x,int y){//必须放在定义体前面,不能放在声明前面
    	...
    }
    
    • 适用情况
      • 一个函数不断被重复调用
      • 函数只有简单几行,且函数内不包括for、while、switch语句

    1.3.2 内联函数与宏的差别

    • 内联函数要做类型检查,而宏不需要
    • 宏是在代码处不加任何验证的简单替代,而内联函数是将代码直接插入到调用处,而减少了普通函数调用时的资源消耗

    1.3.3 写一个 “标准”宏MIN

    #define min( a, b)( (a )<=(b) ?( a) :(b ))
    

    1.3.4 typedef 和define 有什么区别

    • 用法不同:typedef 用来定义一种数据类型的别名,增强程序的可读性。define 主要用来定义 常量,以及书写复杂使用频繁的宏。
    • 执行时间不同:typedef 是编译过程的一部分,有类型检查的功能。define 是宏定义,是预编译的部分,其发生在编译之前,只是简单的进行字符串的替换,不进行类型的检查。
    • 作用域不同:typedef 有作用域限定。define 不受作用域约束,只要是在define 声明后的引用 都是正确的。
    • 对指针的操作不同:typedef 和define 定义的指针时有很大的区别。
      注意:typedef 定义是语句, 因为句尾要加上分号。 而define不是语句,千万不
      能在句尾加分号

    1.4 指针

    (常量指针,数组指针,函数指针,this指针,指针传值,指向指针的指针,指针与引用的区别)

    1.4.1 指针常量和常量指针

    指针常量和常量指针

    1.4.2 指针函数和函数指针

    指针函数和函数指针

    1.4.3 指针数组数组指针

    • 指针数组:int *a[10];
      • 是一个数组,a[ ]里面存的是地址
    • 数组指针:int (*a)[10];
      • 是一个指针,指向整个数组

    1.4.4 函数传参

    函数传参的三种方式:值传递,地址传递,引用传递

    //值传递:就是函数调用时实参将数值传入给形参
    //值传递时,如果形参发生,并不会影响实参
    void swap01(int a,int b){
    	int temp = a;
    	a = b;
    	b = temp;
    }
    //地址传递:利用指针作函数参数,可以修改实参的值
    void swap02(int* a,int* b){
    	int temp = *a;
    	*a = *b;
    	*b = temp;
    }
    //引用传递
    //通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单
    void swap03(int& a;int& b){
    	int temp = a;
    	a = b;
    	b = temp;
    }
    

    1.4.5 一些定义

    定义说明
    int a;一个整型数
    int *a;一个指向整型的指针
    int **a;一个指向指针的指针,它指向的指针是一个整数类型
    int a[10];一个有10个整型的数组
    int *a[10];指针数组:一个有10个指针的数组,指针指向整型
    int (*a)[10];数组指针:一个指向有10个整型数数组的指针
    int (*a)(int);函数指针:一个指向函数的指针,该函数有一个整型参数,并返回一个整型
    int (*a[10])(int);一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型

    1.4.6 指针与引用的区别

    • 指针有自己的一块空间,而引用只是一个别名,所以不能创建引用的引用,引用必须初始化,而指针可用为空;
    • 使用sizeof看一个指针的大小是4,而引用的大小则是被引用对象的大小;
    • 指针和引用使用++运算符的意义不一样;引用自增自减时,是引用所代表的空间的值发生变化,而指针自增自减时是指针指向的位置发生变化
    • 作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引用的修改都会改变引用所指向的对象;
    • 可以有const指针,但是没有const引用;
    • 指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能被改变;
    • 指针可以有多级指针(**p),而引用止于一级;
    • 如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。
    • 引用本身不是一种数据类型,因此引用本身并不占存储单元,系统也不给引用分配存储单元,不能建立数组的引用

    1.4.7 this指针

    • this指针本质上是一个函数参数,只是编译器隐藏起形式的,语法层面上的参数
    • this 只能在成员函数中使用,全局函数和静态函数(属于类,不属于对象)都不能使用this
    • this 在成员函数的开始前构造,在成员的结束后清除。调用类成员函数时,编译器将类的指针作为参数传递进去
    • 用途:
      • 当形参与成员变量同名时,可以用this指针来区分
      • 在类的非静态成员函数中返回对象本身,可用return *this;
    class A{
    	public:
    	int func(int p){} //相当于 int func(A* const this,int p)
    };
    A a;
    a.func(10);//相当于 A::func(&a,10);
    
    • this 指针并不占用对象空间,所以成员函数的参数,不管是不是隐含的,都不会占用对象空间,只会占用参数传递时的栈空间,或者直接占用一个寄存器
    • this 会因编译器不同而有不同的存放位置,可能是堆、栈、也可能是寄存器
    • this 指针只有在成员函数中才有定义,不能通过对象使用this指针,无法知道一个对象的this指针位置(只有在成员函数里才有this指针的位置,可通过&this获取)

    1.4.8 指针和句柄

    • 句柄和指针其实是两个截然不同的概念,window系统用句柄标记系统资源,隐藏系统的信息,它一个一个32bit的整数
    • 而指针则标记某个物理内存地址,两者概念不同

    1.4.9 如何避免“野指针”

    • 指针变量声明时没有被初始化。解决办法:指针声明时初始化,可以是具体的地址值,也可让它指向NULL。
    • 指针p被free或者delete之后,没有置为NULL。解决办法:指针指向的内存空间被释放后指针应该指向NULL。
    • 指针操作超越了变量的作用范围。解决办法:在变量的作用域结束前释放掉变量的地址空间并且让指针指向NULL。

    1.4.10 空指针与迷途指针区别

    • 当delete一个指针的时候,实际上仅仅是让编译器释放内存,但指针本身依然存在,此时他就是一个迷途指针
    • 可令ptr = 0; 使迷途指针变为空指针

    1.5 const

    • 任何不会修改数据成员的函数都应该声明为const 类型
    • 在参数中使用const应该使用引用或指针,而不是一般的对象实例
    • 除了重载操作符外一般不要将返回值类型定为对某个对象的const引用

    1.5.1 const 使用

    • const使用:定义常量、修饰函数参数、修饰函数返回值
    • const 修饰类的成员变量,表示成员变量,不能被修改
    • 如果const构成函数重载,const对象只能调用const函数,非const对象优先调用非const函数
    • const 函数只能调用const函数,非const函数可以调用const函数
    • 类体外定义的const成员函数,在定义和声明处都需要const修饰符

    1.5.2 const 作用

    作用说明
    可以定义const常量
    便于进行类型检查const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误
    可以保护被修饰的东西防止意外的修改,增强程序的健壮性。
    可以很方便地进行参数的调整和修改同宏定义一样,可以做到不变则已,一变都变
    为函数重载提供了一个参考void f(int i) {…} //一个函数 void f(int i) const {…} //上一个函数的重载
    可以节省空间,避免不必要的内存分配const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝
    提高了效率编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高

    参考链接:https://blog.csdn.net/moli152_/article/details/45100717

    1.5.3 如何修改const成员函数

    用mutable修饰成员变量名后,就可以修改类成员变量

    1.5.4 将Const类型转化为非Const类型

    采用const_cast 进行转换。
    用法:const_cast <type_id> (expression)

    1.6 sizeof

    数据对齐原则:是指数据所在的内存地址必须是该数据长度的整数倍。

    1.6.1 sizeof 和strlen 的区别

    • sizeof是一个操作符,strlen是库函数。
    • sizeof的参数可以是数据的类型,也可以是变量、函数;而strlen只能用char*做参数且且以结尾为‘\0’的字符串。
    • 编译器在编译时就计算出了sizeof的结果,而strlen函数必须在运行时才能计算出来。并且sizeof计算的是数据类型占内存的大小,而strlen计算的是字符串实际的长度。
    • 数组做sizeof的参数不退化,传递给strlen就退化为指针了
    • sizeof不能返回被动态分配的数组或外部的数组的尺寸
    • sizeof不能作用于函数类型,不完全类型或位字段,不完全类型是指具有未知存储大小数据的类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void类型等

    1.6.2 sizeof 的使用场合

    • 其中一个主要用途就是与存储分配和I/O系统那样的例程通信
    • 可以查看某种类型的对象在内存中所占的单元字节
    • 在动态分配一个对象时,可以让系统知道要分配多少内存
    • 便于一些类型的扩充。在window中有很多结构类型就有一个专用的字段来存放该类型的字节大小
    • 如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小

    1.7 强制类型转换运算符

    static_cast

    • 用于非多态类型的转换
    • 不执行运行时类型检查(转换安全性不如 dynamic_cast)
    • 通常用于转换数值数据类型(如 float -> int)
    • 可以在整个类层次结构中移动指针,子类转化为父类安全(向上转换),父类转化为子类不安全(因为子类可能有不在父类的字段或方法)
    • 用于各种隐式转换,比如非const转const,void*转指针等, static_cast能用于多态向上转化,如果向下转能成功但是不安全,结果未知

    dynamic_cast

    • 用于多态类型的转换,只能用于含有虚函数的类
    • 执行行运行时类型检查
    • 只适用于指针或引用
    • 对不明确的指针的转换将失败(返回 nullptr),但不引发异常
    • 可以在整个类层次结构中移动指针,包括向上转换、向下转换

    const_cast

    • 用于将const变量转为非const
    • 用于删除 const、volatile 和 __unaligned 特性(如将 const int 类型转换为 int 类型 ) reinterpret_cast
    • 用于位的简单重新解释
    • 滥用 reinterpret_cast 运算符可能很容易带来风险。除非所需转换本身是低级别的,否则应- 使用其他强制转换运算符之一。
    • 允许将任何指针转换为任何其他指针类型(如 char* 到 int* 或 One_class* 到 Unrelated_class* 之类的转换,但其本身并不安全)
    • 也允许将任何整数类型转换为任何指针类型以及反向转换。
    • reinterpret_cast 运算符不能丢掉 const、volatile 或 __unaligned 特性。
    • reinterpret_cast 的一个实际用途是在哈希函数中,即,通过让两个不同的值几乎不以相同的索引结尾的方式将值映射到索引。

    bad_cast

    • 由于强制转换为引用类型失败,dynamic_cast 运算符引发 bad_cast 异常
    try {
    	Circle& ref_circle = dynamic_cast<Circle&>(ref_shape);
    }
    catch (bad_cast b) {
    	cout << "Caught: " << b.what();
    }
    

    为什么不使用C的强制转换?
    C的强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易出错。

    1.8 什么是右值引用,跟左值又有什么区别?

    左值和右值的概 念 :

    • 左值:能取地址,或者具名对象,表达式结束后依然存在的持久对象;
    • 右值:不能取地址,匿名对象,表达式结束后就不再存在的临时对象; 区别:
    • 左值能寻址,右值不能;
    • 左值能赋值,右值不能;
    • 左值可变,右值不能(仅对基础类型适用,用户自定义类型右值引用可以通过成员函数改变)

    1.9 变量的声明和定义有什么区别

    变量的定义为变量分配地址和存储空间,变量的声明不分配地址。一个变量可以在多个地方声明,但是只在一个地方定义。 加入extern 修饰的是变量的声明,说明此变量将在文件以外或在文件后面部分定义

    1.10 说一说extern“C”

    extern“C”的主要作用就是为了能够正确实现C++代 码调用其他C语言代码 。加 上
    extern“C” 后,会指示编译器这部分代码按C语言(而不是C++)的 方式进行编译。由于C++ 支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型, 一般只包括函数名。

    2. C++面向对象

    对象是类的实例,类不分配存储空间,对象才分配存储空间,空类对象大小为1字节

    2.1 面对对象的三大特性(基本特征)

    • 封装性:将客观事物抽象成类,每个类对自身的数据和方法实行 protection (private , protected ,public )。
    • 继承性:广义的继承有三种实现形式:
      • 实现继承(使用基类的属性和方法而无需额外编码的能力)
      • 可视继承(子窗体使用父窗体的外观和实现代码)
      • 接口继承(仅使用属性和方法,实现滞后到子类实现)。
    • 多态性:是将父类对象设置成为和一个或更多它的子对象相等的技术。用子类对象给父类对象赋值之后,父类对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。

    2.2 封装

    • 意义
      • 将属性和行为作为一个整体表现事物
      • 对属性和行为加以权限控制(公共、保护、私有)
      • 增加代码的内聚性,进而提高可复用性和可维护性
    • 封装手法
      • 通过文件:对头文件的包含,把相关定义,声明等封装到某个头文件中
      • 通过语法:C++的namespace、Java的package、Python的module等

    2.3 继承

    • 分类
      • 继承(泛化)
        • 可视继承
        • 实现继承
      • 组合(聚合)
        • 接口继承
        • 纯虚数

    2.4 多态

    • 静态多态(编译阶段,地址早绑定)
      • 函数重载:包括普通函数的重载和成员函数的重载
      • 函数模板的使用:通过将类型作为参数,传递给模板,可使编译器生成该类型的函数。
    • 动态多态(运行阶段,地址晚绑定)在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。
      • 派生类
      • 虚函数

    2.5 成员函数

    2.5.1 构造函数与析构函数

    • 构造函数(constructor):主要作用在于创建对象时为对象的成员属性赋值,由编译器自动调用,可以有参数,可以重载
    • 析构函数(destructor):主要在于对象销毁前系统自动调用。执行一些清理工作,无参数,且不能重载

    2.5.2 初始化列表方式

    构造函数( ) : 属性1(1),属性1(1)...{ }
    

    常量必须在构造函数的初始化列表里初始化,或者将其设置为static

    2.5.3 构造函数调用方式

    • 括号法:Person p1(10); //无参构造不加括号;Person p1;
    • 显式法:Person p2 = Person(10);Person p2 = Person(p1);
    • 隐式转换法:Person p3 = 10;
      • Person(10)单独写为匿名对象,当前行结束后立刻析构

    2.5.4 C++的空类默认产生哪些成员函数

    • 缺省构造函数
    • 缺省拷贝构造函数。
    • 缺省析构函数。
    • 缺省赋值运算符。
    • 缺省取址运算符。
    • 缺省取址运算符 const 。

    两个函数也是空类的默认函数,只有当实际使用这些函数的时候,编译器才会去定义它们。

    2.5.5 继承中子类和父类的构造和析构顺序

    • 构造:先调用父类的构造函数,然后再调用子类构造函数
    • 析构:先调用子类析构函数,然后再调用父类的析构函数

    2.5.6 深拷贝与浅拷贝

    浅拷贝:简单的赋值拷贝操作
    深拷贝:在堆区重新申请空间进行拷贝操作

    2.5.6 拷贝构造函数与赋值运算符

    拷贝构造函数和赋值运算符重载有以下两个不同之处:

    • 拷贝构造函数生成新的类对象,而赋值运算符不能。
    • 由于拷贝构造函数是直接构造一个新的类对象,所以在初始化这个对象之前不用检验源对象 是否和新建对象相同。而赋值运算符则需要这个操作,另外赋值运算中如果原来的对象中有内存分配要先把内存释放掉。

    注意:当有类中有指针类型的成员变量时 ,一 定要重写拷贝构造函数和赋值运算符,不要使用默认的 。

    拷贝构造函数调用时机:

    • 使用一个已经创建完毕的对象来初始化一个新对象
    • 值传递的方式给函数参数传值
    • 以值方式返回局部对象

    2.5.7 C 语言的关键字 static 和 C++ 的关键字 static 有什么区别

    • 在 C 中static 用来修饰局部静态变量和外部静态变量、函数而C++中除了上述功能外, 还用来定义类的成员变量和函数。 即静态成员和静态成员函数 。
    • 注意:编程时static 的记忆性和全局性的特点可以让在不同时期调用的函数进行通信,传递信息 ,而C++ 的静态成员则可以在多个对象实例间进行通信,传递信息

    2.5.8 静态成员

    • 静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
    • 静态成员变量:所有对象共享同一份数据;在编译阶段分配内存,类内声明,类外初始化
    • 静态成员函数:所有对象共享同一个函数;静态成员函数只能访问静态成员变量
    • 非静态成员变量占用对象空间,静态成员变量、函数不占用对象空间

    2.5.9 常函数与常对象

    • 常函数
      • 成员函数后加const 后,称这个函数为常函数
      • 常函数内不可以修改成员属性
      • 成员属性声明前加关键字mutable 后,在常函数中依然可用修改
    • 常对象
      • 声明对象前加const 称该对象为常对象
      • 常对象只能调用常函数

    2.6 引用

    2.6.1 引用

    引用是某个目标变量的别名:类型标识符 &引用名 = 目标变量;int &a = b;

    2.6.2 常引用

    • const 类型标识符 &引用名 = 目标变量;const int &a = b;
    • 可以提高程序的效率,保护传递给函数的数据不在函数中改变

    2.6.3 引用与指针区别

    见 1.4.6

    2.7 虚函数

    2.7.1 虚函数与纯虚函数

    • 虚函数:
      • virtual 返回值类型 函数名(参数列表){ }
      • 在基类中冠以virtual的成员函数,它提供了一个接口界面。允许在派生类中对基类的虚函数重新定义
      • 使用虚函数有一定的空间开销,当类中有虚函数时,编译器会为该类构造一个虚函数表
      • 虚函数表是一个指针数组用来存放每个虚函数的入口地址
    • 纯虚函数
      • virtual 返回值类型 函数名(参数列表)= 0;
      • 在基类中为其派生类保留一个函数的名字,以便派生类根据需要对他进行定义
      • 作为接口存在,纯虚函数不具备函数的功能,一般不能直接被调用。
      • 从基类继承来的纯虚函数,在派生类仍然是虚函数。

    2.7.2 抽象类

    • 如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类(abstract class)
    • 抽象类中不仅包括纯虚函数,也可包括虚函数。
    • 抽象类必须用作派生其他类的基类。且不能直接创建对象实例。但仍可使用指向抽象类的指针支持运行时多态性。
    • 特点
      • 无法实例化对象
      • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

    2.7.3 虚析构与纯虚析构

    • 多态使用时,若子类有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,此时将父类中的析构函数改为虚析构或者纯虚析构即可
    • 总结
      • 解决通过父类指针释放子类对象
      • 子类中没有堆区数据,可不写为虚析构或纯虚析构
      • 拥有纯虚析构的类也称为抽象类

    2.7.4 多态类中的虚函数表是compile-Time建立的还是Run-Time建立的

    虚拟函数表是在编译时期就建立了,各个虚函数这时候已经被组织成一个虚拟函数的入口地址的数组。而对象的隐藏成员----虚函数表指针是在运行期间,也就是构造函数被调用时进行初始化的,这是实现多态的关键

    2.7.5 析构函数可为virtual型,构造函数不能,为什么?

    虚函数采用一种虚调用的方法,虚调用是一种可以在只有部分信息的情况下工作的机制,特别允许我们调用一个只知道接口而不知道其准确对象类型的函数,但是如果要创建一个对象,势必要知道对象的准确类型,因此构造函数不能为虚

    2.7.6 能否把每个函数都声明为虚函数?

    不能,虚函数是有代价的:由于每个虚函数的对象都必须维护一个v表,因此在使用虚函数的时候都会产生一个系统开销。如果仅是一个很小的类,且不想派生其他类,那么根本没必要使用虚函数

    2.8 隐藏、重载与重写

    在这里插入图片描述

    • 重写/覆盖(override):函数返回值类型,函数名,参数列表完全一致称为重写
    • 重载(overload):同一作用域下函数名相同,参数类型、个数、顺序不同
      • 函数的返回值不可以做重载的条件
        (1)重写和重载主要有以下几点不同。
    • 范围的区别:被重写的和重写的函数在两个类中,而重载和被重载的函数在同一个类中。
    • 参数的区别:被重写函数和重写函数的参数列表一定相同,而被重载函数和重载函数的参数列表一 定不同。
    • virtual 的区别:重写的基类中被重写的函数必须要有virtual 修饰,而重载函数和被重载函数可以被 virtual
      修饰,也可以没有。

    (2)隐藏和重写、重载有以下几点不同 。

    • 与重载的范围不同:和重写一样,隐藏函数和被隐藏函数不在同一个类中。
    • 参数的区别:隐藏函数和被隐藏的函数的参数列表可以相同,也可不同,但是函数名肯定要相同。 当参数不相同时,无论基类中的参数是否被virtual 修饰,基类的函数都是被隐藏,而不是被重写。

    注意:虽然重载和覆盖都是实现多态的基础,但是两者实现的技术完全不相同 ,达到的目的也是完全不同的,覆盖是动态态绑定的多态 ,而重载是静态绑定的多态 。
    图片参考链接:https://blog.csdn.net/qq_37934101/article/details/81365449

    2.9 class 与struct 区别

    • 默认继承权限不同,class默认继承权限是private,struct默认继承权限是public
    • class 还可用于定义模板参数 ,像typename,但是关键字struct不能用于定义模板参数.

    2.10 友元

    • 友元是定义在类外部的普通函数
    • 需要在类体内进行说明,需加上关键字friend
    • 友元不是成员函数,但是它可以访问类中的私有成员
    • 作用在于提高程序运行效率,但是破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员
    • 友元可以是一个函数(全局函数、成员函数),被称为友元函数;可用是一个类,被称为友元类

    3. STL

    STL知识点总结:快速跳转

    3.1 vector的底层原理

    • vector底层是一个动态数组 ,包含三个迭代器, start和finish之间是已经被使用的空间范围 end _ of _ storage是整块连续空间包括备用空间的尾部 。
    • 当空间不够装下数据( vec . push_ back( val) )时,会自动申请另一片更大的空 间(1.5倍或者2倍) ,然后把原来的数据拷贝到新的内存空间,接着释放原来的 那片空间[vector内存增长机制] 。
    • 当释放或者删除( v ec . c l e ar ())里面的数据时,其存储空间不释放,仅仅是清 空了里面的数据。因此,对vector的任何操作一旦引起了空间的重新配置,指向原vector 的所有迭代器会都失效了 。

    3.2 vector中的reserve和resize的区别

    • reserve是直接扩充到已经确定的大小,可以减少多次开辟、释放空间的问题(优化push_back),就可以提高效率,其次还可以减少多次要拷贝数据的问题。reserve只是保证vector中的空间大小(capacity)最少达到参数所指定的大小n。reserve()只有一个参数。
    • resize()可以改变有效空间的大小,也有改变默认值的功能。capacity的大小也会随着改变。resize()可以有多个参数。

    3.3 vector中的size和capacity的区别

    • size表示当前vector中有多少个元素(finish - start);
    • capacity函数则表示它已经分配的内存中可以容纳多少元素(end_of_storage - start);

    3.4 vector中erase方法与algorithn中的remove方法区别

    • vector中erase方法真正删除了元素,迭代器不能访问了
    • remove只是简单地将元素移到了容器的最后面,迭代器还是可以访问到。因为algorithm通过迭代器进行操作,不知道容器的内部结构,所以无法进行真正的删除。

    3.5 vector迭代器失效的情况

    • 当插入一个元素到vector中,由于引起了内存重新分配,所以指向原内存的迭代器全部失效。
    • 当删除容器中一个元素后,该迭代器所指向的元素已经被删除,那么也造成迭代器失效。erase方法会返回下一个有效的迭代器,所以当我们要删除某个元素时,需要it=vec.erase(it);。

    3.6 正确释放vector的内存(clear(), swap(), shrink_to_fit())

    • vec.clear():清空内容,但是不释放内存。
    • vector().swap(vec):清空内容,且释放内存,想得到一个全新的vector。
    • vec.shrink_to_fit():请求容器降低其capacity和size匹配。
    • vec.clear();vec.shrink_to_fit();:清空内容,且释放内存。

    3.7 list的底层原理

    • list的底层是一个双向链表,使用链表存储数据,并不会将它们存储到一整块连续的内存空间中。恰恰相反,各元素占用的存储空间(又称为节点)是独立的、分散的,它们之间的线性关系通过指针来维持,每次插入或删除一个元素,就配置或释放一个元素空间。
    • list不支持随机存取,如果需要大量的插入和删除,而不关心随即存取

    3.8 什么情况下用vector,什么情况下用list,什么情况下用deque

    • vector可以随机存储元素(即可以通过公式直接计算出元素地址,而不需要挨个查找),但在非尾部插入删除数据时,效率很低,适合对象简单,对象数量变化不大,随机访问频繁。除非必要,我们尽可能选择使用vector而非deque,因为deque的迭代器比vector迭代器复杂很多。
    • list不支持随机存储,适用于对象大,对象数量变化频繁,插入和删除频繁,比如写多读少的场景。
    • 需要从首尾两端进行插入或删除操作的时候需要选择deque。

    3.9 priority_queue的底层原理

    priority_queue:优先队列,其底层是用堆来实现的。在优先队列中,队首元素一定是当前队列中优先级最高的那一个。

    3.10 map 、set、multiset、multimap的底层原理

    • map 、set 、multiset 、multimap的底层实现都是红黑树 ,epoll模型的底层数据结构也是红黑树 ,linux 系统中CFS 进程调度算法,也用到红黑树 。
    • 红黑树的特性:
      • 每个结点或是红色或是黑色;
      • 根结点是黑色;
      • 每个叶结点是黑的;
      • 如果一个结点是红的,则它的两个儿子均是黑色;
      • 每个结点到其子孙结点的所有路径上包含相同数目的黑色结点。

    3.11 为何map和set的插入删除效率比其他序列容器高

    因为不需要内存拷贝和内存移动

    3.12 为何map和set每次Insert之后,以前保存的iterator不会失效?

    因为插入操作只是结点指针换来换去,结点内存没有改变。而iterator就像指向结点的指针,内存没变,指向内存的指针也不会变。

    3.13 当数据元素增多时(从10000到20000),map的set的查找速度会怎样变化?

    RB-TREE用二分查找法,时间复杂度为logn,所以从10000增到20000时,查找次数从log10000=14次到log20000=15次,多了1次而已。

    3.14 map 、set、multiset、multimap的特点

    • set和multiset会根据特定的排序准则自动将元素排序,set中元素不允许重复,multiset可以重复。
    • map和multimap将key和value组成的pair作为元素,根据key的排序准则自动将元素排序(因为红黑树也是二叉搜索树,所以map默认是按key排序的),map中元素的key不允许重复,multimap可以重复。
    • map和set的增删改查速度为都是logn,是比较高效的。

    3.15 为何map和set的插入删除效率比其他序列容器高,而且每次insert之后,以前保存的iterator不会失效?

    • 存储的是结点,不需要内存拷贝和内存移动。
    • 插入操作只是结点指针换来换去,结点内存没有改变。而iterator就像指向结点的指针,内存没变,指向内存的指针也不会变。

    3.16 为何map和set不能像vector一样有个reserve函数来预分配数据?

    • 在map和set内部存储的已经不是元素本身了,而是包含元素的结点。也就是说map内部使用的Alloc并不是map<Key, Data, Compare, Alloc>声明的时候从参数中传入的Alloc。

    3.17 set的底层实现实现为什么不用哈希表而使用红黑树?

    • set中元素是经过排序的,红黑树也是有序的,哈希是无序的
    • 如果只是单纯的查找元素的话,那么肯定要选哈希表了,因为哈希表在的最好查找时间复杂度为O(1),并且如果用到set中那么查找时间复杂度的一直是O(1),因为set中是不允许有元素重复的。而红黑树的查找时间复杂度为O(lgn)

    3.18 hash_map与map的区别?什么时候用hash_map,什么时候用map?

    • 构造函数:hash_map需要hash function和等于函数,而map需要比较函数(大于或小于)。
    • 存储结构:hash_map以hashtable为底层,而map以RB-TREE为底层。
    • 总的说来,hash_map查找速度比map快,而且查找速度基本和数据量大小无关,属于常数级别。而map的查找速度是logn级别。但不一定常数就比log小,而且hash_map还有hash function耗时。
    • 如果考虑效率,特别当元素达到一定数量级时,用hash_map。
    • 考虑内存,或者元素数量较少时,用map。

    3.19 迭代器失效的问题

    插入操作:

    • 对于vector和string,如果容器内存被重新分配,iterators,pointers,references失效;如果没有重新分配,那么插入点之前的iterator有效,插入点之后的iterator失效;
    • 对于deque,如果插入点位于除front和back的其它位置iterators,pointers,references失效;当我们插入元素到front和back时,deque的迭代器失效,但reference和pointers有效;
    • 对于list和forward_list,所有的iterator,pointer和refercnce有效。 删除操作:
    • 对于vector和string,删除点之前的iterators,pointers,references有效;off-the-end迭代器总是失效的;
    • 对于deque,如果删除点位于除front和back的其它位置,iterators,pointers,references失效;当我们插入元素到front和back时,off-the-end失效,其他的iterators,pointers,references有效;
    • 对于list和forward_list,所有的iterator,pointer和refercnce有效。
    • 对于关联容器map来说,如果某一个元素已经被删除,那么其对应的迭代器就失效了,不应该再被使用,否则会导致程序无定义的行为。

    3.20 STL线程不安全的情况

    • 在对同一个容器进行多线程的读写、写操作时;
    • 在每次调用容器的成员函数期间都要锁定该容器;
    • 在每个容器返回的迭代器(例如通过调用begin或end)的生存期之内都要锁定该容器;
    • 在每个在容器上调用的算法执行期间锁定该容器。

    4. C++ 11

    4.1 NULL与nullptr

    4.1.1 C语言中的NULL

    • C语言中的NULL通常被定义为:#define NULL ((void *)0)
    • NULL实际上是一个空指针,C语言中把空指针赋给int和char指针的时候,发生了隐式类型转换,把void指针转换成了相应类型的指针。

    4.1.2 C++中的NULL

    C++是强类型语言,void*是不能隐式转换成其他类型的指针的,所以实际上编译器提供的头文件做了相应的处理:

    #ifdef __cplusplus
    #define NULL 0
    #else
    #define NULL ((void *)0)
    #endif
    

    实际上,用NULL代替0表示空指针在函数重载时会出现问题;

    void func(void* i);//func(nullptr)
    void func(int i);//func(NULL)
    

    4.1.3 C++中的nullptr

    • nullptr可以明确区分整型和指针类型,能够根据环境自动转换成相应的指针类型,但不会被转换为任何整型,所以不会造成参数传递错误。
    • nullptr的l另一种实现方式如下:
    #include <iostream>
    using namespace std;
     
    void func(void* i)
    {
    	cout << "func1" << endl;
    }
     
    void func(int i)
    {
    	cout << "func2" << endl;
    }
     
    void main(int argc,char* argv[])
    {
    	func(NULL);
    	func(nullptr);
    	getchar();
    }
    

    4.2 智能指针

    C+ + 里面的四个智能指针:

    • auto_ptr(C++11已弃用)
    • shared_ptr,
    • weak_ptr,
    • unique_ptr

    智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束
    忘记释放,造成内存泄漏 。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域时,类会自动调用析构函数,析构函数会自动释放资源。 所以智能指针的作用原理就是在函数结束时自动释放内存空间不 需要手动释放内存空间 。

    展开全文
  • C++常见面试题整理

    千次阅读 2021-03-02 14:05:54
    C/C++程序编译过程就是把C/C++代码百年城可执行文件的过程, 该过程分为4步 预处理阶段 进行宏展开和宏替换 处理条件编译指令, 如#ifdef, #endif等 去掉注释 添加行号和文件名标识 保留#pargma编译器指令(#Pragma...

    编译和调试

    C/C++程序编译过程

    • C/C++程序编译过程就是把C/C++代码百年城可执行文件的过程, 该过程分为4步
    • 预处理阶段
      1. 进行宏展开和宏替换
      2. 处理条件编译指令, 如#ifdef, #endif等
      3. 去掉注释
      4. 添加行号和文件名标识
      5. 保留#pargma编译器指令(#Pragma命令将设定编译器的状态或者是指示编译器完成一些特定的动作)
    • 编译阶段
      1. 编译程序所要作得工作就是通过词法分析, 语法和语义分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。
      2. 代码优化
      3. 重点关注函数压栈方式的编译处理
        1. __cdecl是C DECLaration的缩写, 表示C语言默认的函数调用方法: 所有参数从右到左依次入栈. 这些参数由调用者清除,称为手动清栈. 被调用函数不需要求调用者传递多少参数, 调用者传递过多或者过少的参数. 甚至完全不同的参数都不会产生编译阶段的错误。
        2. _stdcall 是StandardCall的缩写, 是C++的标准调用方式:所有参数从右到左依次入栈,如果是调用类成员的话, 最后一个入栈的是this指针。这些堆栈中的参数由被调用的函数在返回后清除, 使用的指令是 retnX,X表示参数占用的字节数. CPU在ret之后自动弹出X个字节的堆栈空间。称为自动清栈. 函数在编译的时候就必须确定参数个数. 并且调用者必须严格的控制参数的生成,不能多, 不能少, 否则返回后会出错。
    • 汇编阶段
      1. 汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程
      2. 对于被翻译系统处理的每一个C语言源程序, 都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。
      3. 目标文件由段组成. 通常一个目标文件中至少有两个段: 代码段和数据段
    • 链接阶段
      1. 将所有的目标文件代码拼接且重定位符号地址, 生成可执行文件
      2. 两种链接方式: 动态链接和静态链接

    动态链接和静态链接区别

    • 静态链接

      1. 在编译时静态库和程序链接
      2. 一般命名为:libxxx.a
      3. 运行时,可执行目标文件已经装载完毕,速度快
      4. 但是多个程序需要静态库时每个都会复制一份,造成内存浪费
      5. 更新后需要重新编译
    • 动态链接

      1. 在运行时链接

      2. 一般命名: libxxx.so

      3. 运行时加载链接, 速度相对慢

      4. 运行时多个程序共享同一份动态库, 不会造成内存浪费

      5. 易更新, 无需重新编译

    内存管理

    new/delete和malloc/free区别

    • new是C++关键字,需要编译器支持. 而malloc是C语言库函数
    • new失败时, 会抛出bad_alloc异常. malloc会返回NULL
    • new执行时, 先分配内存, 再调用类的构造函数初始化, malloc只会分配内存
    • new无需指定分配内存大小, 编译器会根据类型信息自行计算, malloc需要在参数里指出分配内存的大小
    • new成功会直接返回所分配的对象的指针, 而malloc只会返回void指针, 需要转化才能得到想要的对象的指针
    • C++允许重载new/delete操作符, 而malloc不允许重载, new从自由存储区上为对象动态分配内存空间, malloc从堆上分配内存

    C++中有几种类型的new

    • plain new: 普通new, 特点在new和malloc区别里说了

    • nothrow new: 在空间分配失败时不会抛出异常, 而是返回NULL. 使用:

      char *p=new(nothrow) char[10e11]
      
    • new operator: 只做两件事:(1)调用operator new (2)调用类的构造函数

    • operator new: 可以重载, 实际以标准C malloc()完成

    • placement new: 在一块已经分配成功的内存上重新构造对象或对象数组. 不会出现内存分配失败, 因为他只调用构造函数而不会分配内存. 用placement new构造和对象数组, 要显式调用它们的析构函数来销毁, 而不要用delete[], 因为构造起来的对象或数组大小不一定等于原来内存的大小, 用delete会造成内存泄漏或运行时出现错误. 使用:

      #include <iostream>
      #include <string>
      using namespace std;
      class ADT{
          int i;
          int j;
      public:
          ADT(){
              i = 10;
              j = 100;
              cout << "ADT construct i=" << i << "j="<<j <<endl;
          }
          ~ADT(){
              cout << "ADT destruct" << endl;
          }
      };
      int main()
      {
          char *p = new(nothrow) char[sizeof ADT + 1];
          if (p == NULL) {
              cout << "alloc failed" << endl;
          }
          ADT *q = new(p) ADT;  //placement new:不必担心失败,只要p所指对象的的空间足够ADT创建即可
          //delete q;//错误!不能在此处调用delete q;
          q->ADT::~ADT();//显示调用析构函数
          delete[] p;
          return 0;
      }
      

    malloc原理

    • Malloc一般是从heap分配. heap会事先分配n个页, 放在一起, 并在里面做些标记表明哪些块是使用中的哪些是空闲的. malloc只是从这里面找到一个空闲的块。
    • 当开辟的空间小于 128K 时, 调用 brk()函数
    • 当开辟的空间大于 128K 时,调用mmap()系统调用函数来在虚拟地址空间中(堆和栈中间,称为"文件映射区域"的地方)找一块空间来开辟

    C++内存分区

    • 自高地址到低地址依次是栈, 堆, 全局数据区, 常量区和代码区
    • 栈是程序的局部变量存储区域, 分配效率高, 但是内存容量有限, 同时离开变量作用域后会存储单元会自动释放
    • 堆是程序由malloc分配的内存块, 需要手动释放内存空间
    • 自由存储区: new操作符从自由存储区(free store)上为对象动态分配内存空间,自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。自由存储区通常是堆
    • 全局/静态存储区: 全局变量和静态变量存储的区域, 未初始化的静态变量会自动初始化为0
    • 代码区: 存放函数体的二进制代码

    面向对象

    面向对象编程的三大特征

    • 封装 继承 多态

    纯虚函数和虚函数

    • 纯虚函数在定义类不能被实现, 只有继承类才能被实现. 可以认为是一种接口函数含有纯虚函数的类被称为抽象类
    • 虚函数是为了重载和多态需要, 在基类中可以实现, 子类重写后会覆盖基类的虚函数

    构造函数中能调用其他成员函数吗

    • 可以, 但是不能调用类的静态成员函数, 虚函数以及以来类构造完成的函数

    构造函数和析构函数中能用虚函数吗

    • 都不能
    • 在构造函数中: 父类对象会在子类之前进行构造, 此时子类部分数据成员还没初始化, 调用子类的虚函数时不安全的
    • 析构函数: 构函数是用来销毁一个对象的,在销毁一个对象时,先调用子类的析构函数,然后再调用基类的析构函数。所以在调用基类的析构函数时,派生类对象的数据成员已经“销毁”,这个时再调用子类的虚函数已经没有意义了。

    构造函数和析构函数能成为虚函数吗

    • (1)构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的. 而在构造一个对象时, 由于对象还未构造成功. 编译器无法知道对象的实际类型, 是该类本身, 还是该类的一个派生类. (2)虚函数的执行依赖于虚函数表. 而虚函数表在构造函数中进行初始化工作,即初始化vptr, 让他指向正确的虚函数表. 而在构造对象期间, 虚函数表还没有被初 始化,将无法进行。
    • 有虚函数的类的析构函数必须为虚函数. 因为只有序析构函数才能自动调用基类的析构函数.

    重载, 覆盖和隐藏区别

    • 函数重载: 同一作用域内内, 一组具有不同参数列表的同名函数(静态绑定)

      1. C++编译器对函数名的处理: 作用域+返回类型+函数名+参数列表

      2. 参数列表必须不同(个数, 类型或参数排列顺序)

      3. 仅仅返回值不同不足以称为函数重载

      4. C语言不支持函数重载, 因为C语言在编译过程会保留原始的函数名

    • 覆盖: 重写指的是在派生类中覆盖基类中的同名函数,重写就是重写函数体**,**要求基类函数必须是虚函数且函数参数列表必须相同(动态绑定)

    • 隐藏: 指的是某些情况下,派生类中的函数屏蔽了基类中的同名函数.

      1. 两个函数参数相同, 但是基类函数不是虚函数
      2. 两个函数参数不同, 无论基类函数是不是虚函数, 都会被隐藏

    C++多态如何实现

    • 多态性: 在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据所指对象的实际类型来调用相应的函数,如果对象类型是派生类,就调用派生类的函数,如果对象类型是基类,就调用基类的函数

    • 虚函数表: 在有虚函数的类中,类的最开始部分是一个虚函数表的指针vptr,这个指针指向一个虚函数表,表中放了虚函数的地址,实际的虚函数在代码段(.text)中。当子类继承了父类的时候也会继承其虚函数表,当子类重写父类中虚函数时候,会将其继承到的虚函数表中的地址替换为重新写的函数地址。使用了虚函数,会增加访问内存开销,降低效率。

    • 虚函数表结构(通过命令g++ -fdump-class-hierarchy xxx.cpp 查看):

      1. 非空虚基类
      class A
      {
      public:
         int a;
         virtual void v();
      };
      sizeof(A ) = 16  ,align=8
      
      1. 单继承
      class A {
      public:
        int a;
        virtual void v();
      };
      class B : public A {
      public:
        int b;
      };
      sizeof(B) = 16, align = 8
      
      1. 简单多继承1
      class A {
      public:
        int a;
        virtual void v();
      };
      class B {
      public:
        int b;
        virtual void w();
      };
      class C : public A, public B {
      public:
        int c;
      };
      
      
      sizeof(C) = 32 ,align = 8
      
      1. 简单多继承2
      class A {
      public:
        int a;
        virtual void v();
      };
      class B {
      public:
        int b;
        virtual void w();
      };
      class C : public A, public B {
      public:
        int c;
        void w();
      };
      sizeof(C) = 32 ,align = 8
      
      1. 多重继承
      class A {
      public:
        int a;
        virtual void v();
      };
      class B : public A {
      public:
        int b;
        virtual void w();
      };
      class C : public A {
      public:
        int c;
        virtual void x();
      };
      class D : public B, public C {
      public:
        int d;
        virtual void y();
      };
      sizeof(D)  = 40 align = 8
      
      1. 虚继承
      class A {
      public:
        int a;
        virtual void v();
      };
      
      class B : public virtual A {
      public:
        int b;
        virtual void w();
      };
      
      class C : public virtual A {
      public:
        int c;
        virtual void x();
      };
      
      class D : public B, public C {
      public:
        int d;
        virtual void y();
      };
      
      
      sizeof(D) = 48,align = 8
      

    C++有几种构造函数

    • 拷贝构造函数
    • 默认构造函数
    • 移动构造函数
    • 委托构造函数
    • 继承构造函数

    标准模板库

    list和vector区别

    • vector: 连续存储的容器,动态数组,在堆上分配空间

      1. 底层实现:数组

      2. 两倍容量增长

      3. 查询时间复杂度为O(1), 插入删除平均时间复杂度为O(n)

    • list: 在堆上分配空间,每插入一个元数都会分配空间,每删除一个元素都会释放空间

      1. 插入删除时间复杂度O(1), 查询时间复杂度O(n)
      2. 底层实现: 双向链表

    迭代器失效的情况

    • set和map的操作不会使迭代器失效,因为删除只会导致被删除的元素的迭代器失效,因为被删除的节点不会影响其他节点
    • vector的删除会使后面的迭代器都失效
    • list的操作不会导致任何迭代器失效

    vector扩容原理

    • vector是一个能够存放任意类型的动态数组
    • vector每次容量满了后会扩容, 一般为当前容量的两倍. 旧内存空间的内容按照原来顺序放到新的内存中
    • 最后将旧内存的内容释放掉, 但是存储空间没有释放

    set和map区别

    • map和set都是C++的关联容器, 其底层实现都是红黑树(RB-Tree). 几乎所有的 map 和set的操作行为, 都只是转调 RB-tree 的操作行为。
    • map中的元素是key-value对, 而set只有value
    • set迭代器时const, 不允许修改元素的值. map允许修改value的值, 但是不允许修改key的值. 否则会使迭代器失效
    • map支持下标操作

    set和unordered_set区别

    • set时有序的, 底层数据结构是红黑树
    • unordered_set是无序的,底层数据结构是哈希表

    迭代器和指针区别

    • 迭代器不是指针, 是类模板, 表现的像指针. 他只是模拟了指针的一些功能, 通过重载了指针的一些操作符,->、*、++、–等.
    • 迭代器本质是封装了原生指针, 是指针概念的一种提升, 提供了比指针更高级的行为
    • 迭代器类的访问方式就是把不同集合类的访问逻辑抽象出来, 使得不用暴露集合内部的结构而达到循环遍历集合的效果

    基础

    static作用

    • static变量默认初始化为0
    • static全局变量: 表示该变量作用域只在该变量所在的文件中. 在程序运行期间一直存在
    • static局部变量: 作用域仍为局部作用域, 但是离开局部作用后变量并没有销毁, 而是依然驻留在内存中, 下次该局部变量在进入作用域时, 值不变, static局部变量只会被初始化一次.
    • static函数: 函数的定义和声明在默认情况下都是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。不要在头文件声明static函数
    • static成员变量: 静态成员可以实现多个对象之间的数据共享. 即无论对象有几个, 一个类中的static成员变量只有一个
    • static成员函数: 静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员. 没有this指针, 所以只能用类的静态成员变量和静态成员函数

    指针和引用区别

    • 指针有自己的一块空间. 引用只是个别名
    • sizeof(指针)=4/8字节, 取决于机器位数. sizeof(引用)=引用对象大小
    • 指针可以被初始化为nullptr, 引用必须被初始化为一个已有对象的引用
    • 可以有const指针, 没有const引用
    • 指针可以有多级(**p), 引用只有一级
    • 指针和引用使用++运算符意义不同, 前者
    • 返回东爱内存分配的对象, 必须使用指针, 否则可能引起内存泄漏
    • 指针可以任意改变指向对象, 引用一旦被初始化就不能改变只想指针
    • 指针使用时需要解引用(->), 引用可以直接使用

    结构体对齐

    • 第一个成员在结构体变量偏移量为0 的地址处,也就是第一个成员必须从头开始。
    • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数 为编译器默认的一个对齐数与该成员大小中的较小值。vs中默认值是8 Linux默认值为4**(当然可以通过#pragma pack()修改)**,但修改只能设置成1,2,4,8,16.
    • 结构体总大小为最大对齐数的整数倍。(每个成员变量都有自己的对齐数)
    • 如果嵌套结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(包含嵌套结构体的对齐数)的整数倍。

    区别一下指针类型

    int *p[10]   //指针数组, 强调数组概念, 是一个数组, 每个元素都是指向int*
    int (*p)[10] //数组指针, 强调指针概念, 只有一个变量, 指向int数组
    int *p(int)  //函数,返回值为int*
    int (*p)(int)//函数指针,返回值为int
    

    什么情况必须使用初始化成员列表

    • 初始化const成员
    • 初始化reference成员
    • 调用基类的构造函数
    • 调用数据成员对象的构造函数

    strlen和sizeof区别

    • sizeof是运算符, 而不是函数, 结果在编译时得到. strlen是C语言库函数
    • sizeof参数可以是任何数据类型, strlen参数只能是字符指针且结尾是’\0’的字符串
    • sizeof不能得到动态分配的变量的大小

    常量指针和指针常量区别

    • 常量指针是指向常量的指针: const char *str="zhanyi";
    • 指针常量是指不能改变指向的指针: char *const str=p;

    数组名和指针区别

    • sizeof得到结果不同
    • 字符数组内容可以改版, 字符指针内容不能改变

    野指针和悬空指针

    • 野指针: 指针指向了被delete/free的内存
    • 空指针: 没有指向的指针

    C和C++区别

    • C++是面向对象语言, C是面向过程语言
    • C++具有封装, 继承和多态三种特性
    • C++相比C, 增加许多类型安全的功能
    • C++支持方式编程, 不如模板函数和模板类

    extern"C"用法

    内联函数和宏定义区别

    • 根本区别宏定义只是字符替换, 内联函数是个函数(类型安全检查, 自动类型转换)
    • 代码展开发生在程序执行的不同阶段. 前者在预处理阶段, 后者在编译阶段
    • 内陆函数可以作为类的成员函数, 宏定义不能

    const和指针

    • 顶层const: const修饰的变量本身是个常量, 即const在*右边
    • 底层const: const修饰的变量指向的对象是个常量, const在*左边
    • const_cast只对底层const起作用

    define和const区别

    • define是在预处理阶段起作用, const在编译, 运行阶段起作用
    • define只做替换, 不做类型安全检查和计算, const相反
    • define在内存中有多份相同的备份, const只有一份

    #和##在define中的作用

    assert

    其他

    C和C++的类型安全

    • 类型安全很大程度上可以等价于内存安全,类型安全的代码不会试图访问自己没被授权的内存区域

    • C的类型安全: C只在局部上下文中表现出类型安全, 比如试图从一种结构体的指针转换成另一种结构体的指针时, 编译器将会报告错误, 除非使用显式类型转换. 然而, C中相当多的操作是不安全的. 比如:

      #include<stdio.h>
      int main(){
      	printf("%d\n",10);//正常
      	printf("%f\n",10);//输出错误
      	printf("%s\n",10);//运行时segmentation
      }
      
    • C++的类型安全: C++比C更有类型安全性.

      1. 操作符new返回的指针类型严格与对象匹配,而不是void*
      2. C中很多以void*为参数的函数可以改写为C++模板函数,而模板是支持类型检查的;
      3. 引入const关键字代替#define constants,它是有类型、有作用域的,而#define constants只是简单的文本替换
      4. 一些#define宏可被改写为inline函数,结合函数的重载,可在类型安全的前提下支持多种类型,当然改写为模板也能保证类型安全
      5. C++提供了dynamic_cast关键字,使得转换过程更加安全,因为dynamic_cast比static_cast涉及更多具体的类型检查。

    volatile关键字

    • 提示编译器不要对访问该变量的代码进行优化
    • 两个作用:保证变量的内存可见性;禁止指令重新排序
    • 变量的内存可见性: 一个线程修改了某个变量, 对其他线程时可见的

    explicit关键字

    • 只能用于修饰只有一个参数的类构造函数
    • 它的作用是表明该构造函数是显示的, 而非隐式的. 跟它相对应的另一个关键字是implicit, 意思是隐藏的, 类构造函数默认情况下即声明为implicit(隐式).

    C++异常处理

    • 用throw抛出异常, 在try作用域中运行可能抛出异常的代码, 通过catch捕捉异常

    内存泄漏

    展开全文
  • C++语言基础、语言特性、数据结构、操作系统知识甚至是一些内核相关的知识、网络相关知识、数据库操作、多线程多进程数据同步互斥、内存相关知识等等。 1.C中的malloc 和C++中的new 有什么区别 malloc 和new 有...

    C++语言基础、语言特性、数据结构、操作系统知识甚至是一些内核相关的知识、网络相关知识、数据库操作、多线程多进程数据同步互斥、内存相关知识等等。

    1.C中的malloc 和C++中的new 有什么区别

    malloc 和new 有以下不同:
    (1)new 、delete 是操作符,可以重载,只能在C++ 中使用。

    (2 )malloc、free 是函数,可以覆盖,C、C++ 中都可以使用。

    (3 )new 可以调用对象的构造函数,对应的delete 调用相应的析构函数。
    (4 )malloc 仅仅分配内存,free 仅仅回收内存,并不执行构造和析构函数

    (5)new 、delete 返回的是某种数据类型指针,malloc、free 返回的是void 指针。
    注意:malloc 申请的内存空间要用free 释放,而new 申请的内存空间要用delete 释放,不要混用。

    因为两者实现的机理不同。

    2…程序什么时候应该使用线程,什么时候单线程效率高。

    1 耗时的操作使用线程,提高应用程序响应

    2 并行操作时使用线程,如C/S架构的服务器端并发线程响应用户的请求。

    3 多CPU系统中,使用线程提高CPU利用率

    4 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。 其他情况都使用单线程。

    3.类相关的题目
    为什么要进行封装?
    什么是多态,多态是怎么实现的?多态的应用场景?
    虚函数表是属于类的还是属于成员的?虚指针呢?
    虚函数,纯虚函数?什么时候使用虚函数,什么时候使用纯虚函数?有了虚函数为什么还要用纯虚函数,可以用虚函数替代纯虚函数吗?
    类的构造函数可以定义为虚函数吗?析构函数呢?
    基类的析构函数为什么通常需要定义为虚函数?如果不定义为虚函数会有什么问题?什么场景下会产生这种问题?
    定义拷贝构造函数的时候参数有什么要求?为什么要将参数定义为引用,如果不定义为引用会产生什么问题?
    什么是深拷贝?怎么实现深拷贝?
    构造函数的成员初始化列表的使用场景?什么时候必须使用初始化列表?

    4.说一说C与C++的内存分配方式?

    1从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,如全局变量,static变量。

    2在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

    3从堆上分配(动态内存分配)程序在运行的时候用malloc或new申请任意多少的内存,程序员负责在何时用free或delete释放内存。动态内存的生存期自己决定,使用非常灵活。

    展开全文
  • 2021年华为c++面试题及答案

    千次阅读 2021-05-06 22:07:18
    c++面试题 3.python中 tuple 和 list的区别 Python 的元组与列表类似,不同之处在于元组的元素不能修改。 元组使用小括号 ( ),列表使用方括号 [ ]。 4.python 和 C++的区别,两者的应用场景有什么差别。 1、运行...
  • 星标/置顶公众号????,硬核文章第一时间送达!链接 |https://zhuanlan.zhihu.com/p/274473971很多,先上后上答案,便于大家思考问题点:1、C和C++...
  • 1、gcc 和 g++的区别(关于编译)简单来说,gcc与g++都是GNU(组织)的一个编译器。...编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,所以通常用g++来...
  • C++面试题合集

    2021-10-22 20:17:39
    C++面试题1.大端存储和小端储存?2. Virtual 关键字的作用 构造函数和析构函数能不能定义为虚函数?3. extern “C” 1.大端存储和小端储存? 大端模式: 数据的高字节保存在内存的低地址中。 小端模式:高字节保存在...
  • c++】经典面试题吐血整理

    万次阅读 多人点赞 2021-01-04 20:56:12
    c++ 程序从源程序到生成可执行文件的过程 源程序 编译预处理:处理宏定义,头文件,特殊符号之类的 编译:处理关键字,确认所有指令是否符合语法 优化:删除公共表达式,循环优化之类的,优化之后得到汇编程序 汇编...
  • c语言面试题大汇总 C++面试题大汇总目录1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题) ... 32 . 写一个"标准"宏MIN ,这个宏输入两个参数并返回较小的一个。 ..........................
  • Linux C/C++面试题汇总

    2021-08-01 07:46:59
    Linux C/C++面试题汇总前言计算机基础程序的内存空间进程和线程相关关键字conststaticvolatileC/C++指针 前言 最近面试的比较多,看了很多关于面试的内容,有些平时工作用的比较少或者没有主要到的地方,长时间不看...
  • **腾讯C++面试题一面** 说一下c++语言特点 重载和重写(前者是参数可改,后者参数一样) vector array区别 tcp 三次握手,四次挥手 计网七层协议 tcp udp优缺点 网址访问过程,dns查询迭代,递归查询 http和https的...
  • 文章目录C/C++基础知识1. static关键字的作用2. C++和C的区别3. C++中四种cast转换(待补充)4.C/C++中指针和引用的区别5. C++中的智能指针6. 数组和指针的区别7. 野指针是什么8. 为什么析构函数必须是虚函数?为...
  • #include <memory> #include <functional> #include <thread> #include <iostream> #include <vector> class Thread ... Thread(ThreadFunction func,int id):func_(func)
  • 当前位置:我的异常网» C++»【转】C/C++ 面试题汇总【转】C/C++ 面试题汇总www.myexceptions.net网友分享于:2013-08-12浏览:47次【转】C/C++ 面试题集锦【转】C/C++ 面试题集锦2011年07月10日1.多态类中的虚函数...
  • C++大厂面试真题

    2021-04-03 15:40:52
    C++标准库的map和set有什么区别,如何实现的? map和set都是C++的关联容器,其底层实现都是红黑树。 map和set区别在于: map中的元素是key-value(键-值)对:关键字起到索引的作用,值则表示与索引相关联的...
  • 常见的C++面试题

    2021-07-20 09:04:15
    目录new 和 malloc 的区别,delete 和 free 的区别malloc 的原理:、struct 和 union 的区别volatile 的作用?是否具有原子性,对编译器有什么影响?auto 类型推导的原理构造函数、析构函数是否需要定义成虚函数?...
  • 如果说求职是人生的一座山,那面试就是最难跨越的一道沟。有时候好不容易被通知去面试,结果被面试官虐得体无完肤,还有很多技术精湛、经验丰富的求职者屡次在面试环节被拒,一直没能拿到心仪的大厂高薪...
  • 总结了一些近几天遇到模糊的面试,随手记录一下。 时间:09-30 文章目录 智能指针 智能指针的作用 说说你了解的auto_ptr 智能指针的循环引用如何解决 手写实现智能指针类需要实现哪些函数? 宏定义最小值和最大值 ...
  • C++工程师校招面试题库导读 一、学习说明 面试题库中不包括面试中问到的项目,hr 面以及个人技术发展类。 ⚫ 项目是比较个性化的,没办法作为一个题库来给大家参考,但是如果你有一个非常有含 金量 的项目的话,是...
  • 面试题| C++11新特性

    2021-10-06 22:09:52
    C++11新增了很多新特性,这也成为了面试中非常常见的问题,这里介绍一些常用的新特性。C++11新特性有很多,这里就简单整理几个很常见的,应该足以应对面试中的问题了。 C++11新特性 ● 初始化列表 初始化列表,即用...
  • C++面试题及答案

    2021-04-15 20:58:14
    C++面试题及答案: 1、malloc 0个字节有数据吗? 答案: 2、有一个指针,大小等于其类型大小 连续malloc两次一个字节其地址差距多大 答案:一个字节 3、数据库合并两个表格 答案: 查到两张表字段和顺序一样的表直接...
  • C++\QT常见面试题

    千次阅读 2021-05-22 16:25:24
    1.C与C++的区别 2.深拷贝和浅拷贝的区别 3.指针和引用的区别 4.什么是面向对象,面向对象的三大特征是什么? 5.static关键字的用法 6.const关键字的用法 7.什么是函数重载 8.创建的对象有几种方式,有什么区别 9....
  • C++面试题(一)

    2021-11-07 10:47:11
    1、什么是虚函数?什么是纯虚函数? 答:虚函数声明如下: virtual ReturnType FunctionName(Parameter);...C++中的虚函数的作用主要是实现了多态的机制。基类定义虚函数,子类可以重写该函数;在派生类中对基类
  • 请你说一下数组和指针的区别 数组:是用来存储多个相同类型的数据的集合。 指针:指针相当于一个变量,它存放的是其他变量在内存中的地址。  区别 (1) 赋值:同类型指针变量可以相互赋值,数组不行,只能一个一个...
  • 各大厂算法岗面试题汇总之C++

    万次阅读 2021-04-02 20:44:20
    3.C++有哪些性质(面向对象特点) 4.子类析构时要调用父类的析构函数吗? 5.多态,虚函数,纯虚函数 7.什么是“引用”?申明和使用“引用”要注意哪些问题? 8.将“引用”作为函数参数有哪些特点? 9.在什么...
  • C++ 函数指针和指针函数的区别 o(╥﹏╥)o 堆和栈的区别 函数重载?为什么返回值不可以区分函数重载?o(╥﹏╥)o 封装、继承和多态的定义,自己描述一下这三者的区别和联系。 多态的构成(应该是想听虚函数,...
  • C++: new、malloc区别返回类型、分配失败返回值、分配大小、数组的处理方式、是否调用构造函数 多态编译时多态、运行时多态 虚函数实现vptr、vtable 重载原理符号表 map红黑树,定义,自旋 网络: UDP可靠...
  • C++ 常见面试题汇总

    2021-05-31 10:05:17
    C++之 extern C的作用详解 extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译...
  • 引用就是C++对C语言的重要扩充。引用就是某一变量的一个别名,对引用的操作与对变量直接操作完全一样。引用的声明方法:类型标识符 &引用名=目标变量名;引用引入了对象的一个同义词。定义引用的表示方法与定义指针...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 94,263
精华内容 37,705
关键字:

c++面试题大全

c++ 订阅