zoukankan      html  css  js  c++  java
  • 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修饰的构造函数的类,不能发生相应的隐式类型转换
  • 相关阅读:
    HDU2732 Leapin' Lizards 网络流 最大流 SAP
    POJ1459 Power Network 网络流 最大流
    HDU3718 Similarity KM
    HDU3488 Tour KM
    HDU2853 Assignment KM
    HDU1507 Uncle Tom's Inherited Land* 二分图匹配 匈牙利算法 黑白染色
    POJ1469 COURSES 二分图匹配 匈牙利算法
    HDU4185 Oil Skimming 二分图匹配 匈牙利算法
    POJ3041 Asteroids 二分图匹配 匈牙利算法
    BZOJ2553 [BeiJing2011]禁忌 AC自动机 矩阵
  • 原文地址:https://www.cnblogs.com/lihello/p/14440770.html
Copyright © 2011-2022 走看看