• C++面试常见问题


    指针和引用的区别

    • 指针是一个变量,存储的是一个地址,引用跟原来的变量实质上是同一个东西,是原变量的别名
    • 指针可以有多级,引用只有一级
    • 指针可以为空,引用不能为NULL且在定义时必须初始化
    • 指针在初始化后可以改变指向,而引用在初始化之后不可再改变
    • sizeof指针得到的是本指针的大小,sizeof引用得到的是引用所指向变量的大小
    • 当把指针作为参数进行传递时,也是将实参的一个拷贝传递给形参,两者指向的地址相同,但不是同一个变量,在函数中改变这个变量的指向不影响实参,而引用却可以。
    • 引用只是别名,不占用具体存储空间,只有声明没有定义;指针是具体变量,需要占用存储空间。
    • 引用在声明时必须初始化为另一变量,一旦出现必须为typename refname &varname形式;指针声明和定义可以分开,可以先只声明指针变量而不初始化,等用到时再指向具体变量。
    • 引用一旦初始化之后就不可以再改变(变量可以被引用为多次,但引用只能作为一个变量引用);指针变量可以重新指向别的变量。
    • 不存在指向空值的引用,必须有具体实体;但是存在指向空值的指针。

    堆和栈的区别

    • 申请方式不同:栈由系统自动分配;堆是自己申请和释放的。
    • 申请大小限制不同:栈顶和栈底是之前预设好的,栈是向栈底扩展,大小固定,可以通过ulimit -a查看,由ulimit -s修改;堆向高地址扩展,是不连续的内存区域,大小可以灵活调整。
    • 申请效率不同:栈由系统分配,速度快,不会有碎片;堆由程序员分配,速度慢,且会有碎片。

    区别以下指针类型

    int *p[10]
    int (*p)[10]
    int *p(int)
    int (*p)(int)
    
    • int *p[10]表示指针数组,强调数组概念,是一个数组变量,数组大小为10,数组内每个元素都是指向int类型的指针变量。
    • int (*p)[10]表示数组指针,强调是指针,只有一个变量,是指针类型,不过指向的是一个int类型的数组,这个数组大小是10。
    • int *p(int)是函数声明,函数名是p,参数是int类型的,返回值是int *类型的。
    • int (*p)(int)是函数指针,强调是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。

    new / delete 与 malloc / free的异同

    相同点

    • 都可用于内存的动态申请和释放

    不同点

    • 前者是C++运算符,后者是C/C++语言标准库函数
    • new自动计算要分配的空间大小,malloc需要手工计算
    • new是类型安全的,malloc不是。例如:
    int *p = new float[2]; //编译错误
    int *p = (int*)malloc(2 * sizeof(double));//编译无错误
    
    • new调用名为operator new的标准库函数分配足够空间并调用相关对象的构造函数,delete对指针所指对象运行适当的析构函数;然后通过调用名为operator delete的标准库函数释放该对象所用内存。后者均没有相关调用
    • 后者需要库文件支持,前者不用
    • new是封装了malloc,直接free不会报错,但是这只是释放内存,而不会析构对象

    宏定义和typedef区别?

    • 宏主要用于定义常量及书写复杂的内容;typedef主要用于定义类型别名。
    • 宏替换发生在编译阶段之前,属于文本插入替换;typedef是编译的一部分。
    • 宏不检查类型;typedef会检查数据类型。
    • 宏不是语句,不在在最后加分号;typedef是语句,要加分号标识结束。
    • 注意对指针的操作,typedef char * p_char和#define p_char char *区别巨大。

    strlen和sizeof区别?

    • sizeof是运算符,并不是函数,结果在编译时得到而非运行中获得;strlen是字符处理的库函数。
    • sizeof参数可以是任何数据的类型或者数据(sizeof参数不退化);strlen的参数只能是字符指针且结尾是''的字符串。
    • 因为sizeof值在编译时确定,所以不能用来得到动态分配(运行时分配)存储空间的大小。
      int main(int argc, char const *argv[]){   
          const char* str = "name";
          sizeof(str); // 取的是指针str的长度,是8
          strlen(str); // 取的是这个字符串的长度,不包含结尾的 。大小是4
          return 0;
      }
    

    常量指针和指针常量区别?

    • 常量指针是一个指针,读成常量的指针,指向一个只读变量。如int const *p或const int *p。
    • 指针常量是一个不能给改变指向的指针。指针是个常量,不能中途改变指向,如int *const p。

    a和&a有什么区别?

    假设数组int a[10];
    int (*p)[10] = &a;
    
    • a是数组名,是数组首元素地址,+1表示地址值加上一个int类型的大小,如果a的值是0x00000001,加1操作后变为0x00000005。*(a + 1) = a[1]。
    • &a是数组的指针,其类型为int (*)[10](就是前面提到的数组指针),其加1时,系统会认为是数组首地址加上整个数组的偏移(10个int型变量),值为数组a尾元素后一个元素的地址。
    • 若(int *)p ,此时输出 *p时,其值为a[0]的值,因为被转为int *类型,解引用时按照int类型大小来读取。

    C++中struct和class的区别

    相同点

    • 两者都拥有成员函数、公有和私有部分
    • 任何可以使用class完成的工作,同样可以使用struct完成

    不同点

    • 两者中如果不对成员不指定公私有,struct默认是公有的,class则默认是私有的
    • class默认是private继承,而struct模式是public继承
    • class可以作为模板类型,struct不行

    引申:C++和C的struct区别

    • C语言中:struct是用户自定义数据类型(UDT);C++中struct是抽象数据类型(ADT),支持成员函数的定义,(C++中的struct能继承,能实现多态)
    • C中struct是没有权限的设置的,且struct中只能是一些变量的集合体,可以封装数据却不可以隐藏数据,而且成员不可以是函数
    • C++中,struct增加了访问权限,且可以和类一样有成员函数,成员默认访问说明符为public(为了与C兼容)
    • struct作为类的一种特例是用来自定义数据结构的。一个结构标记声明后,在C中必须在结构标记前加上struct,才能做结构类型名(除:typedef struct class{};);C++中结构体标记(结构体名)可以直接作为结构体类型名使用,此外结构体struct在C++中被当作类的一种特例

    define宏定义和const的区别

    编译阶段

    • define是在编译的预处理阶段起作用,而const是在编译、运行的时候起作用

    安全性

    • define只做替换,不做类型检查和计算,也不求解,容易产生错误,一般最好加上一个大括号包含住全部的内容,要不然很容易出错
    • const常量有数据类型,编译器可以对其进行类型安全检查

    内存占用

    • define只是将宏名称进行替换,在内存中会产生多分相同的备份。const在程序运行中只有一份备份,且可以执行常量折叠,能将复杂的的表达式计算出结果放入常量表
    • 宏替换发生在编译阶段之前,属于文本插入替换;const作用发生于编译过程中。
    • 宏不检查类型;const会检查数据类型。
    • 宏定义的数据没有分配内存空间,只是插入替换掉;const定义的变量只是值不能改变,但要分配内存空间。

    C++的顶层const和底层const

    概念区分

    • 顶层const:指的是const修饰的变量本身是一个常量,无法修改,指的是指针,就是 * 号的右边
    • 底层const:指的是const修饰的变量所指向的对象是一个常量,指的是所指变量,就是 * 号的左边

    举个例子

    int a = 10;
    int* const b1 = &a;        //顶层const,b1本身是一个常量
    const int* b2 = &a;        //底层const,b2本身可变,所指的对象是常量
    const int b3 = 20;         //顶层const,b3是常量不可变
    const int* const b4 = &a;  //前一个const为底层,后一个为顶层,b4不可变
    const int& b5 = a;         //用于声明引用变量,都是底层const
    

    区分作用

    • 执行对象拷贝时有限制,常量的底层const不能赋值给非常量的底层const
    • 使用命名的强制类型转换函数const_cast时,只能改变运算对象的底层const

    拷贝初始化和直接初始化

    • 当用于类类型对象时,初始化的拷贝形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,拷贝初始化总是调用拷贝构造函数。拷贝初始化首先使用指定构造函数创建一个临时对象,然后用拷贝构造函数将那个临时对象拷贝到正在创建的对象。举例如下
    string str1("I am a string");//语句1 直接初始化
    string str2(str1);//语句2 直接初始化,str1是已经存在的对象,直接调用构造函数对str2进行初始化
    string str3 = "I am a string";//语句3 拷贝初始化,先为字符串”I am a string“创建临时对象,再把临时对象作为参数,使用拷贝构造函数构造str3
    string str4 = str1;//语句4 拷贝初始化,这里相当于隐式调用拷贝构造函数,而不是调用赋值运算符函数
    
    • 为了提高效率,允许编译器==跳过创建临时对象这一步,直接调用构造函数构造要创建的对象,这样就完全等价于直接初始化了(语句1和语句3等价)。但是需要辨别两种情况。
      • 当拷贝构造函数为private时:语句3和语句4在编译时会报错
      • 使用explicit修饰构造函数时:如果构造函数存在隐式转换,编译时会报错

    内联函数和宏定义的区别

    内联(inline)函数和普通函数相比可以加快程序运行的速度,因为不需要中断调用,在编译的时候内联函数可以直接嵌入到目标代码中。

    内联函数适用场景

    • 使用宏定义的地方都可以使用inline函数
    • 作为类成员接口函数来读写类的私有成员或者保护成员,会提高效率

    为什么不能把所有的函数写成内联函数

    内联函数以代码复杂为代价,它以省去函数调用的开销来提高执行效率。所以一方面如果内联函数体内代码执行时间相比函数调用开销较大,则没有太大的意义;另一方面每一处内联函数的调用都要复制代码,消耗更多的内存空间,因此以下情况不宜使用内联函数:

    • 函数体内的代码比较长,将导致内存消耗代价
    • 函数体内有循环,函数执行时间要比函数调用开销大

    主要区别

    • 内联函数在编译时展开,宏在预编译时展开

    • 内联函数直接嵌入到目标代码中,宏是简单的做文本替换

    • 内联函数有类型检测、语法判断等功能,而宏没有

    • 内联函数是函数,宏不是

    • 宏定义时要注意书写(参数要括起来)否则容易出现歧义,内联函数不会产生歧义

    • 内联函数代码是被放到符号表中,使用时像宏一样展开,没有调用的开销,效率很高;

    • 在使用时,宏只做简单字符串替换(编译前)。而内联函数可以进行参数类型检查(编译时),且具有返回值。

    • 内联函数本身是函数,强调函数特性,具有重载等功能。

    • 内联函数可以作为某个类的成员函数,这样可以使用类的保护成员和私有成员,进而提升效率。而当一个表达式涉及到类保护成员或私有成员时,宏就不能实现了。

    构造函数、析构函数、虚函数可否声明为内联函数

    首先,将这些函数声明为内联函数,在语法上没有错误。因为inline同register一样,只是个建议,编译器并不一定真正的内联。

    register关键字:这个关键字请求编译器尽可能的将变量存在CPU内部寄存器中,而不是通过内存寻址访问,以提高效率

    举个例子:

    #include <iostream>
    using namespace std;
    class A
    {
    public:
        inline A() {
            cout << "inline construct()" <<endl;
        }
        inline ~A() {
            cout << "inline destruct()" <<endl;
        }
        inline virtual void  virtualFun() {
            cout << "inline virtual function" <<endl;
        }
    };
    
    int main()
    {
        A a;
        a.virtualFun();
        return 0;
    }
    //输出结果
    //inline construct()
    //inline virtual function
    //inline destruct()
    

    构造函数和析构函数声明为内联函数是没有意义的

    《Effective C++》中所阐述的是:将构造函数和析构函数声明为inline是没有什么意义的,即编译器并不真正对声明为inline的构造和析构函数进行内联操作,因为编译器会在构造和析构函数中添加额外的操作(申请/释放内存,构造/析构对象等),致使构造函数/析构函数并不像看上去的那么精简。其次,class中的函数默认是inline型的,编译器也只是有选择性的inline,将构造函数和析构函数声明为内联函数是没有什么意义的。

    将虚函数声明为inline,要分情况讨论

    有的人认为虚函数被声明为inline,但是编译器并没有对其内联,他们给出的理由是inline是编译期决定的,而虚函数是运行期决定的,即在不知道将要调用哪个函数的情况下,如何将函数内联呢?

    上述观点看似正确,其实不然,如果虚函数在编译器就能够决定将要调用哪个函数时,就能够内联,那么什么情况下编译器可以确定要调用哪个函数呢,答案是当用对象调用虚函数(此时不具有多态性)时,就内联展开

    综上,当是指向派生类的指针(多态性)调用声明为inline的虚函数时,不会内联展开;当是对象本身调用虚函数时,会内联展开,当然前提依然是函数并不复杂的情况下

    auto、decltype和decltype(auto)的用法

    auto

    C++11新标准引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型。和原来那些只对应某种特定的类型说明符(例如 int)不同,

    auto 让编译器通过初始值来进行类型推演。从而获得定义变量的类型,所以说auto 定义的变量必须有初始值。举个例子:

    //普通;类型
    int a = 1, b = 3;
    auto c = a + b;// c为int型
    
    //const类型
    const int i = 5;
    auto j = i; // 变量i是顶层const, 会被忽略, 所以j的类型是int
    auto k = &i; // 变量i是一个常量, 对常量取地址是一种底层const, 所以b的类型是const int*
    const auto l = i; //如果希望推断出的类型是顶层const的, 那么就需要在auto前面加上cosnt
    
    //引用和指针类型
    int x = 2;
    int& y = x;
    auto z = y; //z是int型不是int& 型
    auto& p1 = y; //p1是int&型
    auto p2 = &x; //p2是指针类型int*
    

    decltype

    有的时候我们还会遇到这种情况,我们希望从表达式中推断出要定义变量的类型,但却不想用表达式的值去初始化变量。还有可能是函数的返回类型为某表达式的值类型。在这些时候auto显得就无力了,所以C++11又引入了第二种类型说明符decltype,它的作用是选择并返回操作数的数据类型。在此过程中,编译器只是分析表达式并得到它的类型,却不进行实际的计算表达式的值。

    int func() {return 0};
    
    //普通类型
    decltype(func()) sum = 5; // sum的类型是函数func()的返回值的类型int, 但是这时不会实际调用函数func()
    int a = 0;
    decltype(a) b = 4; // a的类型是int, 所以b的类型也是int
    
    //不论是顶层const还是底层const, decltype都会保留   
    const int c = 3;
    decltype(c) d = c; // d的类型和c是一样的, 都是顶层const
    int e = 4;
    const int* f = &e; // f是底层const
    decltype(f) g = f; // g也是底层const
    
    //引用与指针类型
    //1. 如果表达式是引用类型, 那么decltype的类型也是引用
    const int i = 3, &j = i;
    decltype(j) k = 5; // k的类型是 const int&
    
    //2. 如果表达式是引用类型, 但是想要得到这个引用所指向的类型, 需要修改表达式:
    int i = 3, &r = i;
    decltype(r + 0) t = 5; // 此时是int类型
    
    //3. 对指针的解引用操作返回的是引用类型
    int i = 3, j = 6, *p = &i;
    decltype(*p) c = j; // c是int&类型, c和j绑定在一起
    
    //4. 如果一个表达式的类型不是引用, 但是我们需要推断出引用, 那么可以加上一对括号, 就变成了引用类型了
    int i = 3;
    decltype((i)) j = i; // 此时j的类型是int&类型, j和i绑定在了一起
    

    decltype(auto)

    decltype(auto)是C++14新增的类型指示符,可以用来声明变量以及指示函数返回类型。在使用时,会将“=”号左边的表达式替换掉auto,再根据decltype的语法规则来确定类型。举个例子:

    int e = 4;
    const int* f = &e; // f是底层const
    decltype(auto) j = f;//j的类型是const int* 并且指向的是e
    

    volatile、mutable和explicit关键字的用法

    volatile

    volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

    当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。

    volatile定义变量的值是易变的,每次用到这个变量的值的时候都要去重新读取这个变量的值,而不是读寄存器内的备份。多线程中被几个任务共享的变量需要定义为volatile类型。

    volatile 指针

    volatile 指针和 const 修饰词类似,const 有常量指针和指针常量的说法,volatile 也有相应的概念

    修饰由指针指向的对象、数据是 const 或 volatile 的:

    const char* cpch;
    volatile char* vpch;
    

    指针自身的值——一个代表地址的整数变量,是 const 或 volatile 的:

    char* const pchc;
    char* volatile pchv;
    

    注意:

    • 可以把一个非volatile int赋给volatile int,但是不能把非volatile对象赋给一个volatile对象。
    • 除了基本类型外,对用户定义类型也可以用volatile类型进行修饰。
    • C++中一个有volatile标识符的类只能访问它接口的子集,一个由类的实现者控制的子集。用户只能用const_cast来获得对类型接口的完全访问。此外,volatile向const一样会从类传递到它的成员。

    多线程下的volatile

    有些变量是用volatile关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值。

    mutable

    mutable的中文意思是“可变的,易变的”,跟constant(既C++中的const)是反义词。在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中。我们知道,如果类的成员函数不会改变对象的状态,那么这个成员函数一般会声明成const的。但是,有些时候,我们需要在const函数里面修改一些跟类状态无关的数据成员,那么这个函数就应该被mutable来修饰,并且放在函数后后面关键字位置。

    explicit

    explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换,注意以下几点:

    • explicit 关键字只能用于类内部的构造函数声明上
    • explicit 关键字作用于单个参数的构造函数
    • 被explicit修饰的构造函数的类,不能发生相应的隐式类型转换
  • 相关阅读:
    eclipse中文乱码问题解决方案
    修改Tomcat的JDK目录
    Tomcat 5.5 修改服务器的侦听端口
    HTML DOM教程 27HTML DOM Button 对象
    HTML DOM教程 24HTML DOM Frameset 对象
    Navicat for MySQL v8.0.27 的注册码
    HTML DOM教程 25HTML DOM IFrame 对象
    Tomcat 5.5 的下载和安装
    android manifest相关属性
    ubuntu10.04 下 eclipse 小结
  • 原文地址:https://www.cnblogs.com/lihello/p/14440770.html
Copyright © 2020-2023  润新知