C++面试连环问-基础篇

104 阅读29分钟

基础篇

公众号:阿Q技术站

1、谈谈你对面向过程和面向对象的区别以及优缺点

  1. 面向过程
  • 分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
  • 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发,性能是最重要的因素。 缺点:没有面向对象易维护、易复用、易扩展。
  1. 面向对象
  • 把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为。
  • 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。 缺点:性能比面向过程低。

2、C和C++的区别

  • C是面向过程的语言,而C++是面向对象的语言。
  • C和C++动态管理内存的方法不一样,C是使用malloc/free函数,而C++除此之外还使用new/delete关键字。
  • C++的类是C里没有的,但是C中的struct是可以在C++中正常使用的,并且C++对struct进行了进一步的扩展,使得struct在C++中可以和class有一样的作用。而唯一和class不同的地方在于struct成员默认访问修饰符是public,而class默认的是private。
  • C++支持重载,而C语言不支持。
  • C++有引用,C没有。
  • C++全部变量的默认链接属性是外链接,而C是内链接。
  • C 中用const修饰的变量不可以用在定义数组时的大小,但是C++用const修饰的变量可以。

3、static关键字的作用

  1. 修饰局部变量

static修饰局部变量时,使得被修饰的变量成为静态变量,存储在静态区。存储在静态区的数据生命周期与程序相同,在main函数之前初始化,在程序退出时销毁。(无论是局部静态还是全局静态)

  1. 修饰全局变量

全局变量本来就存储在静态区,因此static并不能改变其存储位置。但是,static限制了其链接属性。被static修饰的全局变量只能被该包含该定义的文件访问(即改变了作用域)。

  1. 修饰函数

static修饰函数使得函数只能在包含该函数定义的文件中被调用。对于静态函数,声明和定义需要放在同一个文件夹中。

  1. 修饰成员变量

用static修饰类的数据成员使其成为类的全局变量,会被类的所有对象共享,包括派生类的对象,所有的对象都只维持同一个实例。 因此,static成员必须在类外进行初始化(初始化格式:int base::var=5;),而不能在构造函数内进行初始化,不过也可以用const修饰static数据成员在类内初始化。

  1. 修饰成员函数

用static修饰成员函数,使这个类只存在这一份函数,所有对象共享该函数,不含this指针,因而只能访问类的static成员变量。静态成员是可以独立访问的,也就是说,无须创建任何对象实例就可以访问。例如可以封装某些算法,比如数学函数,如ln,sin,tan等等,这些函数本就没必要属于任何一个对象,所以从类上调用感觉更好。

  1. 最重要的特性:隐藏

当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏。

注:不可以同时用const和static修饰成员函数。

4、const关键字的作用

  1. 修饰变量
  • C语言中采用const修饰变量,功能是对变量声明为只读特性,并保护变量值以防被修改。
  • const修饰变量还起到了节约空间的目的,通常编译器并不给普通const只读变量分配空间,而是将它们保存到符号表中,无需读写内存操作,程序执行效率也会提高。
  1. 修饰数组

数组元素与变量类似,具有只读属性,不能被更改;一旦更改,如程序将会报错。

  1. 修饰指针

C语言中const修饰指针要特别注意,共有两种形式:一种是用来限定指向空间的值不能修改;另一种是限定指针不可更改。

  1. 修饰函数参数

const关键字修饰函数参数,对参数起限定作用,防止其在函数内部被修改。所限定的函数参数可以是普通变量,也可以是指针变量。

5、synchronized 关键字和 volatile 关键字的区别

synchronized 关键字和 volatile 关键字是两个互补的存在,而不是对立的存在!

  • volatile 关键字是线程同步的轻量级实现,所以volatile 性能肯定比synchronized关键字要好。但是volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块
  • volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。
  • volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性。

6、C语言中struct和union的区别

  • 在存储多个成员信息时,编译器会自动给struct第个成员分配存储空间,struct 可以存储多个成员信息,而union每个成员会用同一个存储空间,只能存储最后一个成员的信息。
  • 都是由多个不同的数据类型成员组成,但在任何同一时刻,union只存放了一个被先选中的成员,而struct的所有成员都存在。
  • 对于union的不同成员赋值,将会对其他成员重写,原来成员的值就不存在了,而对于struct 的不同成员赋值是互不影响的。

7、c++中struct和class的区别

  • struct 一般用于描述一个数据结构集合,而 class 是对一个对象数据的封装。
  • struct 中默认的访问控制权限是 public 的,而 class 中默认的访问控制权限是 private 的,例如:
struct Base1 {
    int base1; // 默认访问控制权限是 public 
};
class Base2 {
    int base2; // 默认访问控制权限是 private 
};
  • 在继承关系中,struct 默认是公有继承,而 class 是私有继承。
  • class 关键字可以用于定义模板参数,就像 typename,而 struct 不能用于定义模板参数,例如:
template<typename T, typename Y> // 可以把typename 换成 class  
int Fun(const T& t, const Y& y) {      
    //todo everything
}

8、数组和指针的区别

  • 概念

数组:存储连续多个相同类型的数据;

指针:变量,存的是地址

  • 赋值

同类型的指针变量可以相互赋值,数组不行,只能一个一个元素的赋值或拷贝

  • 存储方式

数组:连续内存空间。

指针:灵活,可以指向任意类型的数据。指向的是地址空间的内存。

  • sizeof

数组的sizeof求的是占用的空间(字节)。

在32位平台下,无论指针的类型是什么,sizeof(指针名)都是4;在64位平台下,无论指针的类型是什么,sizeof(指针名)都是8。

  • 传参

作为参数时,数组名退化为常量指针。

9、一个程序执行的过程

  1. 预编译:处理源代码中的伪指令和一些特殊字符,并对一些相关的代码进行替换,源文件经过预处理后的结果扩展为 .i 。

    伪指令主要包括以下四个方面

    • 宏定义指令,如#define Name TokenString,#undef等。对于前一个伪指令,预编译所要做的是将程序中的所有Name用TokenString替换,但作为字符串常量的Name则不被替换;对于后者,则将取消对某个宏的定义,使以后该串的出现不再被替换。
    • 条件编译指令,如#ifdef,#ifndef,#else,#endif,等等。这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。
    • 头文件包含指令,如#include “FileName"或者#include 等。在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需加上一条#include语句即可,而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。
    • 特殊符号,预编译程序可以识别一些特殊的符号。例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。
    • 预编译程序所完成的基本上是对源程序的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,但内容有所不同。下一步,此输出文件将作为编译程序的输出而被翻译成为机器指令。
  2. 编译:检查语法并对代码进行优化,将文本文件 .i 翻译成 .s 文件,得到汇编语言程序。

    经过预编译得到的输出文件中,将只有常量。如数字、字符串、变量的定义,以及C语言的关键字,如main,if,else,for,while,{,},+,-,*,\,等等。编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。

  3. 汇编:将 .s 文件转换成机器语言指令也就是二进制代码,并将结果保存在目标文件 .o 中。

    • 汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。目标文件由段组成。通常一个目标文件中至少有两个段:
    • 代码段,该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。
    • 数据段,主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写,可执行的。
  4. 链接:将所有的目标文件链接到一起形成可执行文件,分为动态链接和静态链接。

    由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。

    链接处理可分为两种:

    • 静态链接,将链接库的代码复制到可执行程序中,使得可执行程序体积变大。
    • 动态链接,需要链接的代码放到一个共享对象中,共享对象映射到进程虚地址空间,链接程序记录可执行程序将来需要用的代码信息,根据这些信息迅速定位相应的代码片段。

10、C++中指针和引用的区别

  • 指针是存储变量地址的变量;引用是变量的别名。
  • 指针变量定义时不必初始化;引用定义时必须初始化,不然会报错。
  • 指针变量定义时可以初始化为NULL;引用不能初始化为NULL,不然报错。
  • const修饰指针变量,const放在之前,指针变量所指向变量的值不可改变,指针值可以改变;const放在之后,指针变量所指向变量的值可以改变,指针值不可以改变;const修饰引用,const放在&之前,不能修改引用所表示的变量的值;const放在&之后,const的作用被忽略,可以修改引用所表示的变量的值。
  • 非常指针在指针赋值后可以改变指针值;引用在初始化后不能再作为别的变量的别名。
  • sizeof运算符作用于指针变量得到指针变量自身大小;作用于引用,得到引用所指向的变量的大小。
  • 指针可以有多级,引用只有一级。
  • 指针的自增、自减表示指向下一个同类型变量的地址,一般用于指向数组的指针;引用的自增、自减表示指向变量值的增、减。

11、malloc/new、free/delete各自底层实现原理

在使用的时候 new和delete 搭配使用,malloc 和 free 搭配使用。

  • 属性:

    • malloc/free 是库函数,需要头文件的支持。
    • new/delete 是关键字,需要编译器的支持参数。
    • new 申请空间时,无需指定分配空间的大小,编译器会根据类型自行计算。
    • malloc 在申请空间时,需要确定所申请空间的大小返回值。
    • new 申请空间时,返回的类型是对象的指针类型,无需强制类型转换,符合类型安全的操作符。
    • malloc 申请空间时,返回的是 void* 类型,需要进行强制类型的转换,转换为对象类型的指针。分配失败:new 分配失败时,会抛出 bad_alloc 异常,malloc 分配失败时返回空指针
  • 重载:new/delete 支持重载,malloc/free 不能进行重载

  • 自定义类型实现:new 首先调用 operator new 函数申请空间(底层通过 malloc 实现),然后调用构造函数进行初始化,最后返回自定义类型的指针;delete 首先调用析构函数,然后调用 operator delete 释放空间(底层通过 free 实现)。malloc/free 无法进行自定义类型的对象的构造和析构。

  • 内存区域:new 操作符从自由存储区上为对象动态分配内存,而 malloc 函数从堆上动态分配内存。(自由存储区不等于堆)。

  • new类型是安全的的,而malloc不是。

  • malloc和free,称作C的库函数;new和delete,称作运算符。

  • new的种类:

int *p1 = new int(20);
int *p2 = new (nothrow) int; // 不抛出异常操作
const int *p3 = new const int(40); //在堆上开辟了一个常量
  • delete和delete[]的区别:

对于简单类型来说,使用new分配后,不管是数组形式还是非数组形式,两种方式都可以释放内存:

int *a = new int(1);
delete a;
int *b = new int (2);
delete[] b;
int *c = new int[3];
delete c;
int *d = new int[4];
delete[] d;

对于自定义类型来说,就需要对于单个对象使用delete,对于对象数组使用delete[],逐个调用数组中对象的析构函数,从而释放所有内存。如果反过来使用,即对于单个对象使用delete[],对于对象数组使用delete,其行为是未定义的。

最恰当的方式就是如果用了new,就用delete;如果用了new[],就用delete[]。

  • new和delete的实现原理

new和delete在C++中其实被定义为两个运算符,我们在使用这两个运算符的时候它就会在底层调用全局函数operator new 和 operator delete。

new

  1. 调用operator new()分配内存,operator new()调用malloc
  2. 进行类型转换,将void*转换成目标对象类型
  3. 调用构造函数进行初始化

operator new

operator new在底层实现的源代码

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc){
  // try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
    if (_callnewh(size) == 0){
     // report no memory
     // 如果申请内存失败了,这里会抛出bad_alloc 类型异常
     static const std::bad_alloc nomem;
     _RAISE(nomem);
     }
     return (p);
 }

delete

  1. 调用析构函数
  2. 调用operator delete(),operator delete()调用free释放内存

operator delete

operator delete在底层实现的源代码

void operator delete(void *pUserData){
    _CrtMemBlockHeader * pHead;
    RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
    if (pUserData == NULL)
        return;
    _mlock(_HEAP_LOCK); /* 阻止其它进程 */
    __TRY
    /* 获取一个指向内存块头的指针 */
    pHead = pHdr(pUserData);
    /* 验证块类型 */
    _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
    _free_dbg( pUserData, pHead->nBlockUse );
    __FINALLY
    _munlock(_HEAP_LOCK); /* 释放其他线程 */
    __END_TRY_FINALLY
    return;
}

从源码中能看出的是operator new和operator delete 在底层也是利用malloc和free分配内存的,因此可以说new和delete不过是malloc和free的一层封装。

12、++i与i++的区别

  • 赋值顺序不同

    • ++ i 是先加后赋值;i ++ 是先赋值后加;++i和i++都是分两步完成的。
    • 因为++i 是后面一步才赋值的,所以它能够当作一个变量进行级联赋值,++i = a =b,即 ++i 是一个左值;i++ 的后面一步是自增,不是左值。
    • 形象的理解可以是i++先做别的事,再自己加1,++i先自己加1,再做别的事情。
  • 效率不同

    比如i=2,b=i++就是说b=2,完成之后让i变成3,b=++i就是先让i++变成3,然后b=3,其中++i比i++效率要高些。一般来说在循环域里面,这两者并没有什么很大的区别,但是要注意其生存周期,以及i值在程序流中的变化。

  • i++ 不能作为左值,而++i 可以。

    左值是对应内存中有确定存储地址的对象的表达式的值,而右值是所有不是左值的表达式的值。一般来说,左值是可以放到赋值符号左边的变量。

13、指针函数和函数指针的区别

  1. 定义不同

指针函数本质是一个函数,其返回值为指针。

函数指针本质是一个指针,其指向一个函数。

  1. 写法不同

指针函数int* fun(int x,int y);

函数指针int (*fun)(int x,int y);

函数名带括号的就是函数指针,否则就是指针函数。

  1. 用法不同

一个是函数,一个是变量。

14、指针数组和数组指针的区别

  1. 从字面意思去理解他俩的区别:
  • 指针数组的实质是一个数组,这个数组中存储的内容全部是指针变量。换句通俗的话来讲,指针数组就是指针的数组,核心是一个数组,什么样的数组呢?装着指针的数组。
  • 数组指针的实质是一个指针,这个指针指向的是一个数组。也可以换句通俗的话语来理解,数组指针就是数组的指针,核心是一个指针,什么样的指针呢?指向数组的指针。
  1. 分析数组指针和指针数组的表达式:
  • 先看这个三个表达式:

int *p[5];

int (*p)[5];

int *(p[5]);

  • 我们先来看一下这个规律(不要下次还是死记硬背关于数组指针和指针数组的区别,关键还是理解为主):

    • 我们在定义一个符号时,关键在于:首先要搞清楚你定义的符号是谁:
    • 第一步:找核心(也就是谁是这个表达式里面的主体(变量))。
    • 第二步:找结合:看谁跟核心最近、谁跟核心结合。如果核心和*结合,表示核心是指针;如果核心和[]结合,表示核心是数组;如果核心和()结合,表示核心是函数。
    • 第三步:以后继续向外扩展。
  1. 用上面的规律来分析这3个符号:
  • 第一个,int *p[5];

    核心是p,p是一个数组,数组有5个元素,数组中的元素都是指针,指针指向的元素类型是int类型的;整个符号是一个指针数组。

  • 第二个,int (*p)[5];

    核心是p,p是一个指针,指针指向一个数组,数组有5个元素,数组中存的元素是int类型; 总结一下整个符号的意义就是数组指针。

  • 第三个,int *(p[5]);

解析方法和结论和第一个相同,()在这里是可有可无的。

注意:这里要知道[]符号比*符号的优先级高,()的优先级最高。

15、指针常量和常量指针的区别

  1. 常量指针

定义:具有只能够读取内存中数据,却不能够修改内存中数据的属性的指针,称为指向常量的指针,简称常量指针。

声明:const int * p; int const * p;

:可以将一个常量的地址赋值给一个对应类型的常量指针,因为常量指针不能够通过指针修改内粗数据。只能防止通过指针引用修改内存中的数据,并不保护指针所指向的对象。

  1. 指针常量

定义:指针常量是指指针所指向的位置不能改变,即指针本身是一个常量,但是指针所指向的内容可以改变。

声明:int * const p=&a;

:指针常量必须在声明的同时对其初始化,不允许先声明一个指针常量随后再对其赋值,这和声明一般的常量是一样的。

  • 内联函数比普通函数多了关键字inline。
  • 内联函数避免了函数调用的开销;普通函数有调用的开销。
  • 普通函数在被调用的时候,需要寻址(函数入口地址) ;内联函数不需要寻址。
  • 内联函数有一定的限制,内联函数体要求代码简单,不能包含复杂的结构控制语句;普通函数没有这个要求。

内联函数的作用:内联函数在调用时,是将调用表达式用内联函数体来替换。避免函数调用的开销。

16、值传递、指针传递、引用传递的区别

  1. 值传递:

形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递。

  1. 指针传递:

形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。

  1. 引用传递:

形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作,在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。

17、extern “c”的作用

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

18、大端对齐与小端对齐

  • 大端对齐:数据的低位存储在内存的高地址上,数据高位存储存储在内存的低地址上;(字符串存储)。
  • 小端对齐:数据的低位存储在内存的低地址上,数据高位存储存储在内存的高地址上。

19、深拷贝、浅拷贝、写时拷贝

  • 深拷贝:不仅拷贝指针,对指针指向的内容进行拷贝,深拷贝后两个指针指向两个不同的地址。
  • 浅拷贝:只是对指针的拷贝,拷贝后两个指针指向同一个内存空间。
  • 写时拷贝:内核只为新生成的子进程创建虚拟空间结构,它们来复制于父进程的虚拟地址结构,但是不为这段分配物理内存,它们共享父进程的物理空间,当父进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。

20、函数重载、重写、同名隐藏

  • 重载:两个或多个函数在同一作用域函数名相同、参数列表不同
  • 重写:两个函数分别在基类和派生类的作用域中,函数名、参数、返回值类型都必须相同(协变除外),基类虚函数必须为虚函数,派生类函数最好也为虚函数。
  • 同名隐藏:两个函数分别在基类和派生类的作用域中函数名相同,基类和派生类同名函数不是重写就是同名隐藏。

21、内联函数与普通函数的区别

  • inline内联函数:在编译过程中,就没有函数的调用开销了,在函数的调用点直接把函数的代码进行展开处理了。
  • inline 函数不再生成相应的函数符号。
  • inline 只是建议编译器把这个函数处理成内联函数,但是不是所有的inline都会被编译器处理成内联函数 - 递归。
  • debug版本上,inline是不 起作用的;inline只有在release版本下才能出现。

22、const和一级指针的结合、const和二级(多级)指针的结合

  • int * <= const int* //是错误的
  • const int* <= int* //是可以的
  • int* <= const int* //是错误的
  • const int** <=int** //是错误的
  • int ** <= int* const* //是错误的
  • int* const* <= int** //是可以的

23、数组和链表的区别

  1. 数组:
  • 逻辑结构:

    • 数组在内存中连续;
    • 使用数组之前,必须实现固定数组长度,不支持动态改变数组大小;
    • 数组元素增加时,有可能会数组越界;
    • 数组元素减少时,会造成内存浪费;
    • 数组增删时需要移动其他元素
  • 内存结构:数组从栈上分配内存,使用方便,但是自由度小

  • 访问效率:数组在内存中顺序存储,可通过下标访问,访问效率高

  • 越界问题:数组的大小是固定的,所以存在访问越界的风险

  • 使用场景:

    • 空间:数组的存储空间时栈上分配的,存储密度大,当要求存储的大小变化不大时,且可以事先确定大小,宜采用数组存储数据
    • 时间:数组访问效率高。当线性表的操作主要是进行查找,很少插入和删除时,宜采用数组结构
  1. 链表:
  • 逻辑结构:

    • 链表采用动态内存分配的方式,在内存中不连续
    • 支持动态增加或者删除元素
    • 需要时可以使用malloc或者new来申请内存,不用是使用free或者delete来释放内存
  • 内存结构:链表从对上分配内存,自由度大,但是要注意内存泄漏

  • 访问效率:链表访问效率低,如果想要访问某个元素,需要从头遍历

  • 越界问题:只要可以申请得到链表空间,链表就无月结风险

  • 使用场景:

    • 空间:链表的存储空间是堆上动态申请的,当要求存储的长度变化较大时,且事先无法估量数据规模,宜采用链表存储。
    • 时间:链表插入、删除效率高,当线性表要求频繁插入和删除时,宜采用链表结构。

24、野指针和悬空指针的区别

  1. 野指针:访问一个已删除或访问受限的内存区域的指针,野指针不能判断是否为NULL来避免。指针没有初始化,释放后没有置空,越界。
  2. 悬空指针:一个指针的指向对象已被删除。

25、静态局部变量,全局变量,局部变量的区别

  1. 作用域

    • C++里作用域可分为6种:全局,局部,类,语句,命名空间和文件作用域。
    • 全局变量:全局作用域,可以通过extern作用于其他非定义的源文件。
    • 静态全局变量 :全局作用域+文件作用域,所以无法在其他文件中使用。
    • 局部变量:局部作用域,比如函数的参数,函数内的局部变量等等。
    • 静态局部变量 :局部作用域,只被初始化一次,直到程序结束。
  2. 所在空间

    除了局部变量在栈上外,其他都在静态存储区。因为静态变量都在静态存储区,所以下次调用函数的时候还是能取到原来的值。

  3. 生命周期

    局部变量在栈上,出了作用域就回收内存;而全局变量、静态全局变量、静态局部变量都在静态存储区,直到程序结束才会回收内存。

  4. 使用场景

从它们各自特点就可以看出各自的应用场景,不再赘述。

26、内联函数和宏函数的区别

  1. 宏定义不是函数,但是使用起来像函数。预处理器用复制宏代码的方式代替函数的调用,省去了函数压栈退栈过程,提高了效率;而内联函数本质上是一个函数,内联函数一般用于函数体的代码比较简单的函数,不能包含复杂的控制语句,while、switch,并且内联函数本身不能直接调用自身。
  2. 宏函数是在预编译的时候把所有的宏名用宏体来替换,简单的说就是字符串替换 ;而内联函数则是在编译的时候进行代码插入,编译器会在每处调用内联函数的地方直接把内联函数的内容展开,这样可以省去函数的调用的开销,提高效率。
  3. 宏定义是没有类型检查的,无论对还是错都是直接替换;而内联函数在编译的时候会进行类型的检查,内联函数满足函数的性质,比如有返回值、参数列表等。

27、const和define的区别

  1. const生效于编译的阶段;define生效于预处理阶段。
  2. const定义的常量,在C语言中是存储在内存中、需要额外的内存空间的;define定义的常量,运行时是直接的操作数,并不会存放在内存中。
  3. const定义的常量是带类型的;define定义的常量不带类型。因此define定义的常量不利于类型检查。

28、左值引用和右值引用的区别

  1. 左值引用

T & a = 引用对象,显示的声明且初始化后,就相当于一个变量,由于拥有变量的相同的地址,使用也是与变量一样。

  1. 右值引用

T && a = 被引用的对象,一般不是显式的定义,用于传参和返回,与左值引用的区别,右值是一个临时变量且不变的,变量的所有权会转移,可以理解为,右值引用初始化后,被引用的对象消失,后面一般不再使用。

29、几种构造函数

C++中的构造函数可以分为4类:

  1. 默认构造函数。以Student类为例,默认构造函数的原型为

Student();//没有参数

  1. 初始化构造函数

Student(int num,int age);//有参数

  1. 复制(拷贝)构造函数

Student(Student&);//形参是本类对象的引用

  1. 转换构造函数

Student(int r) ;//形参时其他类型变量,且只有一个形参

30、如何避免野指针

  1. 当指针没有做初始化,即没有指向时,将指针指为NULL。一方面可以提醒自己这个指向NULL的指针不可操作不可访问,另一方面NULL这个标记便于我们检查和避免野指针;初始化为NULL的目的:一是出现段错误时易改错,二是(void *0) 是0地址,是不允许操作,不允许访问的。
  2. 当想给指针赋值时,检查是否已经给他分配了内存空间,如果没有分配就再用malloc分配。
  3. 给指针分配完内存后,不使用时再用free()函数清空内存空间(清空原来的缓冲区),并再将指针指为NULL。

31、include后面的<>和""有什么区别