C/C++后端开发八股文

一. C / C++ 编程

1. Main函数之前执行

(作为main,完成存储内容的构造)

设置栈指针

初始化静态变量(static)和全局变量(global)

赋值全局变量

(可能在完成以上过程中执行的内容)

调用构造函数

(main作为函数)

将main函数的参数 argc , argv 等传递给 main 函数 

【C的存储构造如下图

2. Main函数之后执行

(作为main结束)

atexit注册的函数(传递信息,处理等)-> 倒序执行

全局对象的析构函数

3. 结构体对齐

Alignas \ alignof

pragma pack(push,1)

4. 引用和指针区别

(引用某变量 = 对指向某变量地址的指针取值)

指针是⼀个变量,存储的是⼀个地址,引⽤跟原来的变量实质上是同⼀个东⻄,是原变量的别名
指针可以有多级,引⽤只有⼀级(多级引用也是取相同地址的值没有意义)
指针可以为空,引⽤不能为NULL且在定义时必须初始化(空指针不能取值)
指针在初始化后可以改变指向,⽽引⽤在初始化之后不可再改变
sizeof指针得到的是本指针的⼤⼩,sizeof引⽤得到的是引⽤所指向变量的⼤⼩
当把指针作为参数进⾏传递时,也是将实参的⼀个拷⻉传递给形参,两者指向的地址相同,但不是同⼀个变量,在函数中改变这个变量的指向不影响实参,⽽引⽤却可以。(因为引用直接改变了地址上的值)
引⽤本质是⼀个指针,同样会占4字节内存
引⽤⼀旦初始化之后就不可以再改变;指针变量可以指向新的变量。

5. 传递函数参数时使⽤引⽤和指针的场景

需要返回函数内局部变量的内存的时候⽤指针。使⽤指针传参需要开辟内存,⽤完要记得释放指针,不然会内存泄漏。⽽返回局部变量的引⽤是没有意义的
对栈空间⼤⼩⽐较敏感(⽐如递归)的时候使⽤引⽤。使⽤引⽤传递不需要创建临时变量,开销要更⼩
类对象作为参数传递的时候使⽤引⽤(不可能创建新类,日常使用也没有对类对象取值,因此是通过引用的方式)

6. 堆和栈的区别

(管理⽅式)
堆中资源由程序员控制(容易产⽣memory leak),栈资源由编译器⾃动管理,⽆需⼿⼯控制
(内存管理机制)
系统有⼀个记录空闲内存地址的链表,当系统收到程序申请时,遍历该链表,寻找第⼀个空间⼤于申请空间的堆结点,删除空闲结点链表中的该结点,并将该结点空间分配给程序。只要栈的剩余空间⼤于所申请空间,系统为程序提供内存,否则报异常提示栈溢出。
(空间⼤小)
堆是不连续的内存区域,堆⼤⼩受限于计算机系统中有效的虚拟内存,所以堆的空间⽐栈灵活,⽐栈⼤。栈是⼀块连续的内存区域,⼤小是操作系统预定好的,windows下栈⼤⼩是2M。
(碎片问题)
对于堆,频繁地new/delete会造成大量碎片,使程序效率降低。对于栈,它是有点类似于数据结构上的⼀个先进后出的栈,进出⼀⼀对应,不会产生碎片。
(生长方向)
堆向上,向⾼地址⽅向增⻓。栈向下,向低地址⽅向增⻓。
(分配方式)
堆都是动态分配。栈有静态分配和动态分配,静态分配由编译器完成(如局部变量分配),动态分配由alloca函数分配,但栈的动态分配的资源由编译器进⾏释放,⽆需程序员实现。
(分配效率)
堆由C/C++函数库提供,机制很复杂。所以堆的效率⽐栈低很多。栈是其系统提供的数据结构,计算机在底层对栈提供⽀持,分配专⻔寄存器存放栈地址,栈操作有专⻔指令。

7. 堆快⼀点还是栈快⼀点

栈快⼀点。
因为操作系统会在底层对栈提供⽀持,会分配专⻔的寄存器存放栈的地址,栈的⼊栈出栈操作也⼗分简单,并且有专⻔的指令执⾏,所以栈的效率⽐堆⾼也⽐堆快。
⽽堆的操作是由C/C++函数库提供的,在分配堆内存的时候需要⼀定的算法寻找合适⼤⼩的内存。并且获取堆的内容需要两次访问,第⼀次访问指针,第⼆次根据指针保存的地址访问内存,因此堆⽐栈慢。

8. 区别指针类型

xx(f1) xx(f2) xx(f3) -> fa fb的fc (默认从左到右,有括号放最后)

int *p[10]表示指针数组,每个元素都是指向int类型。
int (*p)[10]表示数组指针,指向的是⼀个int类型的数组
int *p(int)是函数声明,函数名是p,参数是int类型的,返回值是int *类型的。
int (*p)(int)是函数指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。

9. new / delete 与 malloc / free的异同

(相同点)

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

(不同点)

new是类型安全的,malloc不是(即new会进行类型检查,确保分配的内存类型与对象的类型相匹配。而malloc本来就是显式转换指针)

new / delete 是C++运算符, 不需要库⽂件⽀持,不能重载; malloc / free是C/C++语⾔标准库函数,需要库⽂件⽀持,⽀持覆盖。

new⾃动计算要分配的空间大小,malloc需要⼿⼯计算

new是封装了malloc,直接free不会报错,但是这只是释放内存,⽽不会析构对象。

new调⽤名为operator new的标准库函数分配⾜够空间并调⽤相关对象的构造函数,delete对指针所指对象运⾏适当的析构函数;然后通过调⽤名为operator delete的标准库函数释放该对象所⽤内存。后者均没有相关调⽤。

malloc和free返回的是void类型指针(必须进⾏类型转换),new和delete返回的是具体类型指针。

10. new和delete是如何实现的

new的实现过程是:⾸先调⽤名为operator new的标准库函数,分配⾜够⼤的原始为类型化的内存,以保存指定类型的⼀个对象;接下来运⾏该类型的⼀个构造函数,⽤指定初始化构造对象;最后返回指向新分配并构造后的的对象的指针
delete的实现过程:对指针指向的对象运⾏适当的析构函数;然后通过调⽤名为operator delete的标准库函数释放该对象所⽤内存

11. 既然有了malloc/free,C++中为什么还需要new/delete呢?直接⽤malloc/free不好吗?

malloc/free和new/delete都是⽤来申请内存和回收内存的。
在对⾮基本数据类型的对象使⽤的时候,对象创建的时候还需要执⾏构造函数,销毁的时候要执⾏析构函数。⽽malloc/free是库函数,是已经编译的代码,所以不能把构造函数和析构函数的功能强加给malloc/free,所以new/delete是必不可少的。

12. 被free回收的内存是⽴即返还给操作系统吗

不是的,被free回收的内存会⾸先被ptmalloc使⽤双链表保存起来,当⽤户下⼀次申请内存的时候,会尝试从这些内存中寻找合适的返回。这样就避免了频繁的系统调⽤,占⽤过多的系统资源。同时ptmalloc也会尝试对⼩块内存进⾏合并,避免过多的内存碎⽚。

13. 宏定义和函数有何区别

宏在编译时完成替换,之后被替换的⽂本参与编译,相当于直接插⼊了代码 ->
        宏定义没有返回值;函数调⽤具有返回值。
        宏定义参数没有类型,不进⾏类型检查;函数参数具有类型,需要检查类型。
        宏定义不要在最后加分号。

宏运⾏时不存在函数调⽤,执⾏起来更快;函数调⽤在运⾏时需要跳转到具体调⽤函数。

14. 宏定义和typedef区别

宏主要⽤于定义常量及书写复杂的内容;typedef主要⽤于定义类型别名。
宏替换发⽣在编译阶段之前,属于⽂本插⼊替换;typedef是编译的⼀部分。
宏不检查类型;typedef会检查数据类型,是类型安全的。

例如:

#define SQUARE(x) ((x) * (x))

int stringLength = strlen(SQUARE("test")); 

编译时不会报错,但明显不正确


宏不是语句,不在在最后加分号;typedef是语句,要加分号标识结束。
注意对指针的操作,typedef char * p_char和#define p_char char *区别巨⼤。

15. 变量声明和定义区别

声明仅仅是把变量的声明的位置及类型提供给编译器,并不分配内存空间;定义要在定义的地⽅为其分配存储空间。
相同变量可以在多处声明(外部变量extern),但只能在⼀处定义。

16. strlen和sizeof区别

sizeof是运算符,并不是函数,结果在编译时得到⽽⾮运⾏中获得;strlen是字符处理的库函数。
sizeof参数可以是任何数据的类型或者数据(sizeof参数不退化);strlen的参数只能是字符指针且结尾是'\0'的字符串。
因为sizeof值在编译时确定,所以不能⽤来得到动态分配(运⾏时分配)存储空间的⼤⼩。

17. 指针占多少字节

⼀个指针占内存的⼤⼩跟编译环境有关,⽽与机器的位数⽆关。

64位的编译环境下:8字节

32位的编译环境下:4字节

18. 常量指针和指针常量区别

指针常量(*左边 底层const)是⼀个指针,读成常量的指针,指向⼀个只读变量,也就是后⾯所指明的int const 和 const int,都是⼀个常量,可以写作int const *p或const int *p。
常量指针(*右边 顶层const)是⼀个不能给改变指向的指针。指针是个常量,必须初始化,⼀旦初始化完成,它的值(也就是存放在指针中的地址)就不能在改变了,即不能中途改变指向,如int *const p。

19. 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类型⼤⼩来读取。

20. C++与其他语言的区别

(与C语言)

C++中new和delete是对内存分配的运算符,取代了C中的malloc和free。
标准C++中的字符串类取代了标准C函数库头⽂件中的字符数组处理函数(C中没有字符串类型)。
C++中⽤来做控制态输⼊输出的iostream类库替代了标准C中的stdio函数库。
C++中的try/catch/throw异常处理机制取代了标准C中的setjmp()和longjmp()函数。
在C++中,允许有相同的函数名,不过它们的参数类型不能完全相同,这样这些函数就可以相互区别开来。⽽这在C语⾔中是不允许的。也就是C++可以重载,C语⾔不允许。
C++语⾔中,允许变量定义语句在程序中的任何地⽅,只要在是使⽤它之前就可以;⽽C语⾔中,必须要在函数开头部分。⽽且C++不允许重复定义变量,C语⾔也是做不到这⼀点的
在C++中,除了值和指针之外,新增了引⽤。引⽤型变量是其他变量的⼀个别名,我们可以认为他们只是名字不相同,其他都是相同的。
C++相对与C增加了⼀些关键字,如:bool、using、dynamic_cast、namespace等等

(与Python)

Python是⼀种脚本语⾔,是解释执⾏的,⽽C++是编译语⾔,是需要编译后在特定平台运⾏的。python可以很⽅便的跨平台,但是效率没有C++⾼。
Python使⽤缩进来区分不同的代码块,C++使⽤花括号来区分
C++中需要事先定义变量的类型,⽽Python不需要,Python的基本数据类型只有数字,布尔值,字符串,列表,元组等等
Python的库函数⽐C++的多,调⽤起来很⽅便

(与JAVA)

Java语⾔给开发⼈员提供了更为简洁的语法;完全⾯向对象,由于JVM可以安装到任何的操作系统上,所以说它的可移植性强
Java语⾔中没有指针的概念,引⼊了真正的数组。不同于C++中利⽤指针实现的“伪数组”,Java引⼊了真正的数组,同时将容易造成麻烦的指针从语⾔中去掉,这将有利于防⽌在C++程序中常⻅的因为数组操作越界等指针操作⽽对系统数据进⾏⾮法读写带来的不安全问题
C++也可以在其他系统运⾏,但是需要不同的编码(这⼀点不如Java,只编写⼀次代码,到处运⾏),例如对⼀个数字,在windows下是⼤端存储,在unix中则为⼩端存储。Java程序⼀般都是⽣成字节码,在JVM⾥⾯运⾏得到结果
Java⽤接⼝(Interface)技术取代C++程序中的抽象类。接⼝与抽象类有同样的功能,但是省却了在实现和维护上的复杂性

C++⽤析构函数回收垃圾,写C和C++程序时⼀定要注意内存的申请和释放
Java语⾔不使⽤指针,内存的分配和回收都是⾃动进⾏的,程序员⽆须考虑内存碎⽚的问题
应⽤场景

Java在桌⾯程序上不如C++实⽤,C++可以直接编译成exe⽂件。
指针是c++的优势,可以直接对内存的操作,但同时具有危险性 。(操作内存的确是⼀项⾮常危险的事情,⼀旦指针指向的位置发⽣错误,或者误删除了内存中某个地址单元存放的数据,后果是可想⽽知的)
Java在Web 应⽤上具有C++ ⽆可⽐拟的优势,具有丰富多样的框架。
对于底层程序的编程以及控制⽅⾯的编程,C++很灵活,因为有句柄的存在

21. C++中struct和class的区别

相同点
两者都拥有成员函数、公有和私有部分
任何可以使⽤class完成的⼯作,同样可以使⽤struct完成
不同点
两者中如果不对成员不指定公私有,struct默认是公有的,class则默认是私有的
class默认是private继承,⽽struct模式是public继承

22. 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++中被当作类的⼀种特例

即
struct Person {
    char name[50];
    int age;
};

使用时需要:
struct Person person1;

但若:
typedef struct {
    char name[50];
    int age;
} Person;

则只需:
Person person1;
23. define宏定义和const的区别

编译阶段
define是在编译的预处理阶段起作⽤,⽽const是在编译、运⾏的时候起作⽤
安全性
define只做替换,不做类型检查和计算,也不求解,容易产⽣错误,⼀般最好加上⼀个⼤括号包含住全部的内容,要不然很容易出错
const常量有数据类型,编译器可以对其进⾏类型安全检查
内存占⽤
define只是将宏名称进⾏替换,在内存中会产⽣多分相同的备份。const在程序运⾏中只有⼀份备份,且可以执⾏常量折叠,能将复杂的的表达式计算出结果放⼊常量表。
宏定义的数据没有分配内存空间,只是插⼊替换掉;const定义的变量只是值不能改变,但要分配内存空间。

(两句话看似结果相反,但说的内容并不一样,第一句是说define暴力替换导致的替换过程中出现相同的备份,const不需要。第二句话是说编译时分配内存空间,define暴力替换所以不需要内存空间)

24. C++中static的作用

(对变量和函数):
所有不加static的全局变量和函数具有全局可⻅性,可以在其他⽂件中使⽤,加了之后只能在该⽂件所在的编译模块中使用。
默认初始化为0,包括未初始化的全局静态变量与局部静态变量,都存在全局未初始化区。
静态变量在函数内定义,始终存在,且只进⾏⼀次初始化,具有记忆性,其作⽤范围与局部变量相
同,函数退出后仍然存在,但不能使⽤
(对类):
static成员变量:只与类关联,不与类的对象关联。定义时要分配空间,不能在类声明中初始化,
必须在类定义体外部初始化,初始化时不需要标示为static;可以被非static成员函数任意访问。
static成员函数:不具有this指针,⽆法访问类对象的非static成员变量和非static成员函数;不能被
声明为const、虚函数和volatile;可以被非static成员函数任意访问

25. C++中const的作用

(对变量和函数):
const常量在定义时必须初始化,之后⽆法更改
const形参可以接收const和⾮const类型的实参。

fun(const int& i){ } // i可以是int或者const int

(对类):
const成员变量:不能在类定义外部初始化,只能通过构造函数初始化列表进⾏初始化,并且必须有构造函数;不同类对其const数据成员的值可以不同,所以不能在类中声明时初始化
const成员函数:const对象不可以调⽤⾮const成员函数;⾮const对象都可以调⽤;不可以改变⾮mutable(⽤该关键字声明的变量可以在const成员函数中被修改)数据的值
const修饰变量与static有⼀样的隐藏作⽤。只能在该文件中使用,其他⽂件不可以引用声明使用。因此在头⽂件中声明const变量是没问题的,因为即使被多个⽂件包含,链接性都是内部的,不会出现符号冲突。

26. C++的顶层const和底层const

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

int j = 42;
const int *pj = &j; // 正确:非常量j可以赋值给指向常量的指针pj

const int cj = 42;
int *pj2 = &cj; // 错误:常量cj不能赋值给指向非常量的指针pj2,因为这违反了cj的底层const属性
27. 数组名和指针区别

⼆者均可通过增减偏移量来访问数组中的元素。
数组名不是真正意义上的指针,可以理解为常指针,所以数组名没有⾃增、⾃减等操作。
当数组名当做形参传递给调⽤函数后,就失去了原有特性,退化成⼀般指针,多了⾃增、⾃减操作,但sizeof运算符不能再得到原数组的⼤⼩了。

28. final和override

当在⽗类中使⽤了虚函数时候,可能需要在某个⼦类中对这个虚函数进⾏重写,可以添加/不添加override,添加则可以避免拼写错误(父类一定出现)

当不希望某个类被继承,或不希望某个虚函数被重写,可以在类名和虚函数后添加final关键字,添加final关键字后被继承或重写

29. explicit关键字

在C++中,explicit关键字用于修饰类的构造函数,以防止发生隐式类型转换。当不希望在某些情况下自动地调用构造函数时,就可以使用explicit关键字来修饰该构造函数。 

不使用explicit,可以隐式调用
class A {
public:
    A(int n) { /* ... */ }
};
void f(A a) { /* ... */ }
f(5); // 隐式调用A的构造函数,将int转换为A

使用explicit,不可以隐式调用
class A {
public:
    explicit A(int n) { /* ... */ }
};
f(5); // 错误:不能隐式地从int转换为A
f(A(5)); // 正确:显式地创建一个A对象
30. 拷贝初始化和直接初始化

直接初始化直接调⽤与实参匹配的构造函数,
拷⻉初始化总是调⽤拷⻉构造函数。拷⻉初始化⾸先使⽤指定构造函数创建⼀个临时对象,然后⽤拷⻉构造函数将那个临时对象拷⻉到正在创建的对象。

PS: 为了提⾼效率,允许编译器跳过创建临时对象这⼀步,直接调⽤构造函数构造要创建的对象,这样就完全等价于直接初始化了

31. 初始化和赋值的区别
class A{
public:
 int num1;
 int num2;
public:
 A(int a=0, int b=0):num1(a),num2(b){};
 A(const A& a){};
 A& operator=(const A& a){ //重载 = 号操作符函数
 num1 = a.num1 + 1;
 num2 = a.num2 + 1;
 return *this;
 };
};
int main(){
 A a(1,1);
 A a1 = a; //拷⻉初始化操作,调⽤拷⻉构造函数;num1 = 1,num2 = 1;
 A b;
 b = a;//num1 = 2,num2 = 2
 return 0;
}
32. extern"C"

用于正确的在C++代码中调⽤C语⾔(extrern“C”永远在C++中)。

# C++调⽤C函数:
//xx.h
extern int add(...)
//xx.c
int add(){
 
}
//xx.cpp
extern "C" {
 #include "xx.h"
}

# C调⽤C++函数
//xx.h
extern "C"{
 int add();
}
//xx.cpp
int add(){ 
}
//xx.c
extern int add();
33. 野指针和悬空指针

野指针,指的是没有被初始化过的指针

悬空指针,指的是最初指向的内存已经被释放了的⼀种指针

34. C和C++的类型安全

C只在局部上下文中表现出类型安全(例如类型不兼容的结构体指针转换会报错),但相当多的函数是类型不安全的,例如printf格式输出、malloc函数等

相比较来说,C++提供了一些新的机制保障类型安全,如new/const/inline函数/dynamic_cast等

35. C++中的重载、重写(覆盖)和隐藏的区别

重载:同名函数,参数个数/类型不同

重写:派生类的同名同参数函数

隐藏:屏蔽了基类的同名函数:两个同名函数参数相同,但是基类函数不是虚函数 / 两个同名函数参数不同,但同属与基类和派生类

36. C++构造函数种类

默认构造函数(没参数)
初始化构造函数(有参数)
拷贝构造函数
移动构造函数(move和右值引⽤)
委托构造函数(委托给另一个构造函数)
转换构造函数(转换构造函数形参是其他类型变量,且只有⼀个形参)

37. 内联函数和宏定义的区别

内联函数inline:编译器将函数调用直接替换为函数体中的代码,而不是在运行时跳转到函数。这个机制类似于宏替换,但内联函数提供了类型检查和作用域规则等函数的好处。

在使⽤时,宏只做简单字符串替换(编译前)。⽽内联函数可以进⾏参数类型检查(编译时),且具有返回值。
内联函数在编译时直接将函数代码嵌⼊到⽬标代码中,省去函数调⽤的开销来提⾼执⾏效率,并且进⾏参数类型检查,具有返回值,可以实现重载。
宏定义时要注意书写(参数要括起来)否则容易出现歧义,内联函数不会产⽣歧义
内联函数有类型检测、语法判断等功能,⽽宏没有

使⽤宏定义的地⽅都可以使⽤ inline 函数。
作为类成员接⼝函数来读写类的私有成员或者保护成员,会提⾼效率。

38. public,protected和private访问和继承权限的区别

public的变量和函数在类的内部外部都可以访问。
protected的变量和函数只能在类的内部和其派⽣类中访问。
private修饰的元素只能在类内访问。

对派生类来说,属性变化如下:

派生方式(下)基类成员(右)privateprotectedpublic
          private不可见privateprivate
        protected不可见protectedprotected
          public不可见protectedpublic
39. 大小端存储

⼤端存储:字数据的⾼字节存储在低地址中
⼩端存储:字数据的低字节存储在低地址中

(大 小指从高字节还是低字节开始存)

可以使用强制类型转换判断是大端还是小端存储

40. volatile、mutable和explicit关键字

volatile:系统总是重新从它所在的内存读取数据,多线程中被⼏个任务共享的变量需要定义为volatile类型,其指针用法和const类似(顶层 & 底层)

mutable:永远处于可变的状态,即使在⼀个const函数中

explicit:只能⽤于类内部的构造函数声明上且只能用于单个参数的构造函数;被修饰的构造函数的类,不能发⽣相应的隐式类型转换,只能以显式的方式进⾏类型转换

41. 什么情况下会调用拷贝构造函数

⽤类的⼀个实例化对象去初始化另⼀个对象的时候
函数的参数是类的对象时(⾮引⽤传递)
函数的返回值是函数体内局部对象的类的对象时

42. C++中new的类型

plain new:普通的new,在空间分配失败的情况下是抛出std::bad_alloc异常

nothrow new:在空间分配失败的情况下是不抛出异常,返回NULL

placement new:允许在⼀块已经分配成功的内存上重新构造对象或对象数组

char *p = new(nothrow) char[sizeof ADT + 1];
if (p == NULL) {
    cout << "alloc failed" << endl;
}
ADT *q = new(p) ADT;
43. C++的异常处理的方法

(try、throw和catch关键字)

(函数的异常声明列表)

int fun() throw(int,double,A,B,C){...};

(标准异常类 exception)

bad_typeid:使⽤typeid运算符,多态类的指针的值为 NULL,则会拋出此异常(typeid(*basePtr).name())
bad_cast:在⽤ dynamic_cast 进⾏从多态基类对象(或引⽤)到派⽣类的引⽤的强制类型转换时,如果转换是不安全的,则会拋出此异常(dynamic_cast <new_type> (expression))
bad_alloc:在⽤ new 运算符进⾏动态内存分配时,如果没有⾜够的内存,则会引发此异常
out_of_range:⽤ vector 或 string的at 成员函数根据下标访问元素时,如果下标越界,则会拋出此异常

44. static的用法和作用

隐藏:未加static前缀的全局变量和函数都具有全局可⻅性。

保持变量内容的持久:有记忆功能和全局⽣存期

默认初始化为0:静态数据区,内存中所有的字节默认值都是0x00

类成员声明 static:
需要注意
static 类对象必须要在类外进行初始化, static 修饰的变量先于对象存在,所以static修饰的变量要在类外初始化,且没有this指针;static成员函数不能被virtual 修饰
45. 形参与实参的区别

形参变量只有在被调⽤时才分配内存单元,在调⽤结束时, 即刻释放所分配的内存单元。因此,形参只有在函数内部有效。 函数调⽤结束返回主调函数后则不能再使⽤该形参变量。
实参可以是常量、变量、表达式、函数等, ⽆论实参是何种类型的量,在进⾏函数调⽤时,它们都必须具有确定的值, 以便把这些值传送给形参。 因此应预先用赋值,输⼊等办法使实参获得确定值,会产⽣⼀个临时变量。
实参和形参在数量上,类型上,顺序上应严格⼀致, 否则会发⽣“类型不匹配”的错误。
函数调⽤中发⽣的数据传送是单向的。 即只能把实参的值传送给形参,⽽不能把形参的值反向地传送给实参,他们在内存中位于不同的位置。 因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。

46. 值传递、指针传递、引用传递的区别和效率

值传递:有⼀个形参向函数所属的栈拷贝数据的过程,如果值传递的对象是类对象 或是⼤的结构体对象,将耗费⼀定的时间和空间。(传值)
指针传递:同样有⼀个形参向函数所属的栈拷贝数据的过程,但拷⻉的数据是⼀个固定为4字节的地址。(传值,传递的是地址值)
引⽤传递:同样有上述的数据拷贝过程,但其是针对地址的,相当于为该数据所在的地址起了⼀个别名。(传地址)
效率上讲,指针传递和引⽤传递比值传递效率⾼。⼀般主张使⽤引⽤传递,代码逻辑上更加紧凑、清晰。

47. 静态变量什么时候初始化

静态局部变量和全局变量⼀样,数据都存放在全局区域,在主程序之前,编译器已经为其分配好了内存
在C中,初始化发⽣在代码执⾏之前,编译阶段分配好内存之后,就会进⾏初始化,所以我们看到在C语⾔中⽆法使⽤变量对静态局部变量进⾏初始化,在程序运⾏结束,变量所处的全局内存会被全部回收。
在C++中,初始化时在执⾏相关代码时才会进⾏初始化,主要是由于C++引⼊对象后,要进行初始化必须执行相应构造函数和析构函数,在构造函数或析构函数中经常会需要进⾏某些程序中需要进⾏的特定操作,并非简单地分配内存。所以C++标准定为全局或静态对象是有首次⽤到时才会进⾏构造,并通过atexit()来管理。在程序结束,按照构造顺序反⽅向进⾏逐个析构。所以在C++中是可以使⽤变量对静态局部变量进⾏初始化的。

48. const关键字的作用有

49. 类的继承

50. 从汇编层去解释引用

二. 操作系统

1. 进程、线程和协程的区别和联系
进程线程协程
定义资源分配和拥有的基本单位程序执行的基本单位用户态的轻量级线程,线程内部调度的基本单位
切换情况进程CPU环境(栈、寄存器、页表和文件句柄等)的保存以及新调度的进程CPU环境的设置保存和设置程序计数器、少量寄存器和栈的内容先将寄存器上下文和栈保存,等切换回来的时候再进行恢复
切换者操作系统操作系统用户
切换过程用户态->内核态->用户态用户态->内核态->用户态用户态(且没有陷入内核)
调用栈内核栈内核栈用户栈
拥有资源CPU资源、内存资源、文件资源和句柄等程序计数器、寄存器、栈和状态字拥有自己的寄存器上下文和栈
并发性不同进程之间切换实现并发,各自占有CPU实现并行一个进程内部的多个线程并发执行同一时间只能执行一个协程,而其他协程处于休眠状态,适合对任务进行分时处理
系统开销切换虚拟地址空间,切换内核栈和硬件上下文,CPU高速缓存失效、页表切换,开销很大切换时只需保存和设置少量寄存器内容,因此开销很小直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快
通信方面进程间通信需要借助操作系统线程间可以直接读写进程数据段(如全局变量)来进行通信共享内存、消息队列

2. 线程与进程的比较

线程启动速度快,轻量级

线程的系统开销小

线程使用有一定难度,需要处理数据一致性问题

同一线程共享的有堆、全局变量、静态变量、指针,引用、文件等,而独自占有栈

三. 计算机网络

  • 18
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值