C++八股
https://github.com/ZhanghHaoDev/interview
C++八股文
由于要期末考试,还是看看八股文()
第一章 C++基础篇
1. C++ 中内存分配情况
在计算机程序的内存管理中,内存通常被划分为几个不同的区域,每个区域负责存储不同类型的数据。以下是各个内存区域的详细解释:
1. 栈(Stack)
- 管理方式:由编译器自动管理分配和回收。
- 存储内容:存储局部变量和函数参数。
- 特点:
- 先进后出(LIFO)数据结构。
- 栈的空间相对较小,且大小在编译时确定。
- 存取速度快,因为是直接在内存中分配和释放的。
- 随着函数调用和返回自动分配和释放内存,不需要程序员手动管理。
- 栈溢出(Stack Overflow):如果函数调用层次过深或者局部变量过大,会导致栈溢出。
2. 堆(Heap)
- 管理方式:由程序员手动管理分配和回收。
- 分配:
new
或malloc
- 回收:
delete
或free
- 存储内容:动态分配的内存,例如动态数组、链表等。
- 特点:
- 空间相对较大,但管理复杂。
- 内存的分配和回收不定,因此可能会导致内存碎片化。
- 内存泄漏(Memory Leak):如果程序员忘记释放分配的内存,会导致内存无法被再次使用。
- 分配和释放速度较慢,因为需要查找合适的内存块,并且可能涉及操作系统的系统调用。
3. 全局/静态存储区
- 管理方式:由编译器在程序加载时分配,并在程序退出时释放。
- 存储内容:存储全局变量和静态变量。
- 初始化区域:存储已初始化的全局变量和静态变量。
- 未初始化区域(BSS段):存储未初始化的全局变量和静态变量,在程序加载时被清零。
- 特点:
- 变量的生命周期从程序开始到程序结束。
- 内存地址固定,存取速度快。
- 适合存储需要在整个程序运行过程中保持状态的变量。
4. 常量存储区
- 管理方式:由编译器在程序加载时分配,并在程序退出时释放。
- 存储内容:存储常量,例如字符串常量、
const
修饰的变量等。- 特点:
- 通常只读,试图修改这些区域的内容会导致运行时错误。
- 生命周期与程序相同。
5. 代码区
- 管理方式:由操作系统和编译器管理。
- 存储内容:存储程序的二进制代码,即编译后的机器指令。
- 特点:
- 通常是只读的,防止程序修改自身的代码(自修改代码)。
- 存储的是程序的执行代码,从主函数开始到所有被调用的函数。
图示
为了更直观地理解内存布局,可以参照以下图示:
1
2
3
4
5
6
7
8
9
10
11
12
13 +--------------------+
| 栈 (向下增长) |
|--------------------|
| 空闲区 |
|--------------------|
| 堆 (向上增长) |
+--------------------+
| BSS段(未初始化) |
|--------------------|
| 数据段(已初始化) |
|--------------------|
| 代码段(只读) |
+--------------------+例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int global_var = 10; // 全局变量,初始化区域
static int static_var; // 静态变量,BSS段
int main() {
int local_var = 20; // 局部变量,栈
int *heap_var = (int *)malloc(sizeof(int)); // 动态分配内存,堆
const int const_var = 30; // 常量,常量存储区
printf("Global variable: %d\n", global_var);
printf("Static variable: %d\n", static_var);
printf("Local variable: %d\n", local_var);
printf("Heap variable: %d\n", *heap_var);
printf("Constant variable: %d\n", const_var);
free(heap_var); // 释放堆内存
return 0;
}
2. 堆和栈区别
栈
由编译器进⾏管理,在需要时由编译器⾃动分配空间,在不需要时候⾃动回收空间,⼀般保存的是局部变量和函数参数等。 连续的内存空间,在函数调⽤的时候,⾸先⼊栈的主函数的下⼀条可执⾏指令的地址,然后是函数的各个参数。
⼤多数编译器中,参数是从右向左⼊栈(原因在于采⽤这种顺序,是为了让程序员在使⽤C/C++的“函数参数⻓度可变”这个特性时更⽅便。如果是从左向右压栈,第⼀个参数(即描述可变参数表各变量类型的那个参数)将被放在栈底,由于可变参的函数第⼀步就需要解析可变参数表的各参数类型,即第⼀步就需要得到上述参数,因此,将它放在栈底是很不⽅便的。)本次函数调⽤结束时,局部变量先出栈,然后是参数,最后是栈顶指针最开始存放的地址,程序由该点继续运⾏,不会产⽣碎⽚。 栈是⾼地址向低地址扩展,栈低⾼地址,空间较⼩。
堆
由程序员管理,需要⼿动 new malloc delete free 进⾏分配和回收,如果不进⾏回收的话,会造成内存泄漏的问题。 不连续的空间,实际上系统中有⼀个空闲链表,当有程序申请的时候,系统遍历空闲链表找到第⼀个⼤于等于申请⼤⼩的空间分配给程序,⼀般在分配程序的时候,也会空间头部写⼊内存⼤⼩,⽅便 delete 回收空间⼤⼩。当然如果有剩余的,也会将剩余的插⼊到空闲链表中,这也是产⽣内存碎⽚的原因。 堆是低地址向⾼地址扩展,空间交⼤,较为灵活。
3. C++中如何申请和释放内存/C语言
C++中通过new来申请内存,通过delete来释放内存 C语言中是通过 malloc来申请内存,free来释放内存
4. new和malloc区别是什么?
malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。 new 是c++中的操作符,malloc是c语言中的一个函数 new 不止是分配内存,而且会调用类的构造函数,同理delete会调用类的析构函数,而malloc则只分配内存,不会进行初始化类成员的工作,同样free 也不会调用析构函数 内存泄漏对于malloc或者new都可以检查出来的,区别在于new可以指明是那个文件的那一行,而malloc没有这些信息。 都可以⽤来在堆上分配和回收空间。new /delete 是操作符,malloc/free 是库函数。
执⾏ new 实际上执⾏两个过程: 1.分配未初始化的内存空间(malloc); 2.使⽤对象的构造函数对空间进⾏初始化;返回空间的⾸地址。如果在第⼀步分配空间中出现问题,则抛出 std::bad_alloc 异常,或被某个设定的异常处理函数捕获处理;如果在第⼆步构造对象时出现异常,则⾃动调⽤ delete 释放内存。
执⾏ delete 实际上也有两个过程: 1.使⽤析构函数对对象进⾏析构;2.回收内存空间(free)。
以上也可以看出 new 和 malloc 的区别,new 得到的是经过初始化的空间,⽽ malloc 得到的是未初始化的空间。所以 new 是 new ⼀个类型,⽽ malloc 则是malloc ⼀个字节⻓度的空间。delete 和 free 同理,delete 不仅释放空间还析构对象,delete ⼀个类型,free ⼀个字节⻓度的空间。
为什么有了 malloc/free 还需要 new/delete?因为对于⾮内部数据类型⽽⾔,光⽤ malloc/free ⽆法满⾜动态对象的要求。对象在创建的同时需要⾃动执⾏构造函数,对象在消亡以前要⾃动执⾏析构函数。由于 mallo/ free 是库函数⽽不是运算符,不在编译器控制权限之内,不能够把执⾏的构造函数和析构函数的任务强加于 malloc/free,所以有了 new/delete 操作符。
5. 函数传递参数的⼏种⽅式
值传递:形参是实参的拷⻉,函数内部对形参的操作并不会影响到外部的实参。 指针传递:也是值传递的⼀种⽅式,形参是指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进⾏操作。 引⽤传递:实际上就是把引⽤对象的地址放在了开辟的栈空间中,函数内部对形参的任何操作可以直接映射到外部的实参上⾯。
6. C++ 中的指针参数传递和引⽤参数传递
指针参数传递本质上是值传递, 它所传递的是⼀个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,会在栈中开辟内存空间以存放由主调函数传递进来的实参值,从⽽形成了实参的⼀个副本(替身)。值传递的特点是,被调函数对形式参数的任何操作都是作为局部变量进⾏的,不会影响主调函数的实参变量的值(形参指针变了,实参指针不会变)。
引⽤参数传递过程中,被调函数的形式参数也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参(本体)的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量(根据别名找到主调函数中的本体)。因此,被调函数对形参的任何操作都会影响主调函数中的实参变量。
引⽤传递和指针传递是不同的, 虽然他们都是在被调函数栈空间上的⼀个局部变量,但是任何对于引⽤参数的处理都会通过⼀个间接寻址的⽅式操作到主调函数中的相关变量。⽽对于指针传递的参数,如果改变被调函数中的指针地址,它将应⽤不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量(地址),那就得使⽤指向指针的指针或者指针引⽤。
从编译的⻆度来讲, 程序在编译时分别将指针和引⽤添加到符号表上,符号表中记录的是变量名及变量所对应地 址。指针变量在符号表上对应的地址值为指针变量的地址值,⽽引⽤在符号表上对应的地址值为引⽤对象的地址值(与实参名字不同,地址相同)。符号表⽣成之后就不会再改,因此指针可以改变其指向的对象(指针变量中的值可以改),⽽引⽤对象则不能修改。
7. 指针和引⽤的区别
指针和引⽤都是⼀种内存地址的概念,区别呢,指针是⼀个实体,引⽤只是⼀个别名。
指针它指向⼀块内存,指针的内容是所指向的内存的地址,在编译的时候,则是将“指针变量名-指针变量的地址”添加到符号表中,所以说,指针包含的内容是可以改变的,允许拷⻉和赋值,有 const 和⾮ const 区别,甚⾄可以为空,sizeof 指针得到的是指针类型的⼤⼩。
⽽对于引⽤来说,它只是⼀块内存的别名,在添加到符号表的时候,是将"引⽤变量名-引⽤对象的地址"添加到符号表中,符号表⼀经完成不能改变,所以引⽤必须⽽且只能在定义时被绑定到⼀块内存上,后续不能更改,也不能为空,也没有 const 和⾮ const 区别。
sizeof 引⽤得到代表对象的⼤⼩。⽽ sizeof 指针得到的是指针本身的⼤⼩。另外在参数传递中,指针需要被解引⽤后才可以对对象进⾏操作,⽽直接对引⽤进⾏的修改会直接作⽤到引⽤对象上。
作为参数时也不同,传指针的实质是传值,传递的值是指针的地址;传引⽤的实质是传地址,传递的是变量的地址。
8. 简单说⼀下函数指针
从定义和⽤途两⽅⾯来说⼀下理解: ⾸先是定义:
函数指针是指向函数的指针变量。函数指针本身⾸先是⼀个指针变量,该指针变量指向⼀个具体的函数。这正如⽤指针变量可指向整型变量、字符型、数组⼀样,这⾥是指向函数。 在编译时,每⼀个函数都有⼀个⼊⼝地址,该⼊⼝地址就是函数指针所指向的地址。有了指向函数的指针变量后,可⽤该指针变量调⽤函数,就如同⽤指针变量可引⽤其他类型变量⼀样,在这些概念上是⼤体⼀致的。
其次是⽤途:
调⽤函数和做函数的参数,⽐如回调函数。
9. 野(wild)指针与悬空(dangling)指针有什么区别?如何避免?
野指针(wild pointer):就是没有被初始化过的指针。⽤ gcc -Wall 编译, 会出现useduninitialized 警告。
悬空指针:是指针最初指向的内存已经被释放了的⼀种指针。
⽆论是野指针还是悬空指针,都是指向⽆效内存区域(这⾥的⽆效指的是"不安全不可控")的指针。 访问"不安全可控"(invalid)的内存区域将导致"Undefined Behavior"。
如何避免使⽤野指针?在平时的编码中,养成在定义指针后且在使⽤之前完成初始化的习惯或者使⽤智能指针。
10. 说⼀下 const 修饰指针如何区分?
下⾯都是合法的声明,但是含义⼤不同:
1 | const int * p1; //指向整形常量的指针,它指向的值不能修改 |
理解这些声明的技巧在于,查看关键字const右边来确定什么被声明为常量,如果该关键字的右边是类型,则值是常量;如果关键字的右边是指针变量,则指针本身是常量。
11. C++ 中 const 和 static 关键字(定义,⽤途)
static关键字:含义及实现机制
static 作⽤:控制变量的存储⽅式和可⻅性。
作⽤⼀:修饰局部变量:⼀般情况下,对于局部变量在程序中是存放在栈区的,并且局部的⽣命周期在包含语句块执⾏结束时便结束了。但是如果⽤ static 关键字修饰的话,该变量便会存放在静态数据区,其⽣命周期会⼀直延续到整个程序执⾏结束。但是要注意的是,虽然⽤ static 对局部变量进⾏修饰之后,其⽣命周期以及存储空间发⽣了变化,但其作⽤域并没有改变,作⽤域还是限制在其语句块。
作⽤⼆:修饰全部变量:对于⼀个全局变量,它既可以在本⽂件中被访问到,也可以在同⼀个⼯程中其它源⽂件被访问(添加 extern进⾏声明即可)。⽤ static 对全局变量进⾏修饰改变了其作⽤域范围,由原来的整个⼯程可⻅变成了本⽂件可⻅。
作⽤三:修饰函数:⽤ static 修饰函数,情况和修饰全局变量类似,也是改变了函数的作⽤域。
作⽤四:修饰类:如果 C++ 中对类中的某个函数⽤ static 修饰,则表示该函数属于⼀个类⽽不是属于此类的任何特定对象;如果对类中的某个变量进⾏ static 修饰,则表示该变量以及所有的对象所有,存储空间中只存在⼀个副本,可以通过;类和对象去调⽤。
const 关键字:含义及实现机制
作用一:修饰基本类型数据类型:基本数据类型,修饰符 const 可以⽤在类型说明符前,也可以⽤在类型说明符后,其结果是⼀样的。在使⽤这些常量的时候,只要不改变这些常量的值即可。
作用二:修饰指针变量和引⽤变量:如果 const 位于⼩星星的左侧,则 const 就是⽤来修饰指针所指向的变量,即指针指向为常量;如果 const 位于⼩星星的右侧,则 const 就是修饰指针本身,即指针本身是常量。
作用三:应⽤到函数中:作为参数的 const 修饰符:调⽤函数的时候,⽤相应的变量初始化 const 常量,则在函数体中,按照 const 所修饰的部分进⾏常量化,保护了原对象的属性。 [注意]:参数 const 通常⽤于参数为指针或引⽤的情况; 作为函数返回值的 const 修饰符:声明了返回值后,const 按照"修饰原则"进⾏修饰,起到相应的保护作⽤。
作用四:在类中的⽤法:const 成员变量,只在某个对象⽣命周期内是常量,⽽对于整个类⽽⾔是可以改变的。因为类可以创建多个对象,不同的对象其 const 数据成员值可以不同。所以不能在类的声明中初始化 const 数据成员,因为类的对象在没有创建时候,编译器不知道 const 数据成员的值是什么。const 数据成员的初始化只能在类的构造函数的初始化列表中进⾏。const 成员函数:const 成员函数的主要⽬的是防⽌成员函数修改对象的内容。要注意,const 关键字和 static 关键字对于成员函数来说是不能同时使⽤的,因为 static 关键字修饰静态成员函数不含有 this 指针,即不能实例化,const 成员函数⼜必须具体到某⼀个函数。
作用五: 修饰类对象,定义常量对象,常量对象只能调⽤常量函数,别的成员函数都不能调⽤。
补充:const 成员函数中如果实在想修改某个变量,可以使⽤ mutable 进⾏修饰。成员变量中如果想建⽴在整个类中都恒定的常量,应该⽤类中的枚举常量来实现或者 static const。
12. 说⼀下 C++ ⾥是怎么定义常量的?常量存放在内存的哪个位置?
对于局部常量,存放在栈区; 对于全局常量,编译期⼀般不分配内存,放在符号表中以提⾼访问效率;字⾯值常量,⽐如字符串,放在常量区。
13. 宏定义 #define 和常量 const 的区别
类型和安全检查不同
宏定义是字符替换,没有数据类型的区别,同时这种替换没有类型安全检查,可能产生边际效应等错误; const常量是常量的声明,有类型区别,需要在编译阶段进行类型检查
编译器处理不同
宏定义是一个"编译时"概念,在预处理阶段展开,不能对宏定义进行调试,生命周期结束与编译时期; const常量是一个"运行时"概念,在程序运行使用,类似于一个只读行数据
存储方式不同
宏定义是直接替换,不会分配内存,存储于程序的代码段中; const常量需要进行内存分配,存储于程序的数据段中
定义域不同
宏定义是全局定义,可以在整个程序中使用; const常量是局部定义,只能在定义的地方使用
14. C 和 C++ 区别 (函数/类/struct/class)
C++ 有新增的语法和关键字
语法的区别有头⽂件的不同和命名空间的不同, C++ 允许我们⾃⼰定义⾃⼰的空间, C 中不可以。 关键字⽅⾯⽐如 C++ 与 C 动态管理内存的⽅式不同,C++ 中在 malloc 和 free 的基础上增加了 new和 delete, ⽽且 C++ 中在指针的基础上增加了引⽤的概念, 关键字例如 C++中还增加了 auto,explicit 体现显示和隐式转换上的概念要求,还有 dynamic_cast 增加类型安全⽅⾯的内容。
函数⽅⾯ C++ 中有重载和虚函数的概念
C++ ⽀持函数重载⽽C语言不⽀持,是因为 C++ 函数的名字修饰与 C 不同,C++函数名字的修饰会将参数加在后⾯,例如,int func(int,double)经过名字修饰之后会变成_func_int_double,⽽ C 中则会变成 _func,所以 C++ 中会⽀持不同参数调⽤不同函数。
C++ 还有虚函数概念,⽤以实现多态。
类⽅⾯,C 的struct 和 C++ 的类也有很⼤不同:
C++ 中的 struct 不仅可以有成员变量还可以成员函数,⽽且对于 struct 增加了权限访问的概念,struct 的默认成员访问权限和默认继承权限都是 public, C++ 中除了 struct 还有 class 表示类,struct 和 class 还有⼀点不同在于 class 的默认成员访问权限和默认继承权限都是 private。
C++ 中增加了模板还重⽤代码,提供了更加强⼤的 STL 标准库。 C 的 struct 更适合看成是⼀个数据结构的实现体,⽽ C++ 的 class 更适合看成是⼀个对象的实现体。
15. C++ 和 Java 区别(语⾔特性,垃圾回收,应⽤场景等)
指针:Java 语⾔让程序员没法找到指针来直接访问内存,没有指针的概念,并有内存的⾃动管理功能,从⽽有效的防⽌了 C++ 语⾔中的指针操作失误的影响。但并⾮ Java 中没有指针,Java 虚拟机内部中还是⽤了指针,保证了 Java 程序的安全。
多重继承:C++ ⽀持多重继承但 Java 不⽀持,但⽀持⼀个类继承多个接⼝,实现 C++ 中多重继承的功能,⼜避免了 C++ 的多重继承带来的不便。
数据类型和类:Java 是完全⾯向对象的语⾔,所有的函数和变量必须是类的⼀部分。除了基本数据类型之外,其余的都作为类对象,对象将数据和⽅法结合起来,把它们封装在类中,这样每个对象都可以实现⾃⼰的特点和⾏为。 Java 中取消了 C++ 中的 struct 和 union 。
⾃动内存管理:Java 程序中所有对象都是⽤ new 操作符建⽴在内存堆栈上,Java ⾃动进⾏⽆⽤内存回收操作,不需要程序员进⾏⼿动删除。⽽ C++ 中必须由程序员释放内存资源,增加了程序设计者的负担。Java 中当⼀个对象不再被⽤到时, ⽆⽤内存回收器将给他们加上标签。Java ⾥⽆⽤内存回收程序是以线程⽅式在后台运⾏的,利⽤空闲时间⼯作来删除。
Java 不⽀持操作符重载。操作符重载被认为是 C++ 的突出特性。
Java 不⽀持预处理功能。C++ 在编译过程中都有⼀个预编译阶段,Java 没有预处理器,但它提供了 import 与 C++预处理器具有类似功能。
类型转换:C++ 中有数据类型隐含转换的机制,Java 中需要限时强制类型转换。
字符串:C++中字符串是以 Null 终⽌符代表字符串的结束,⽽ Java 的字符串 是⽤类对象(string 和stringBuffer)来实现的。
Java 中不提供 goto 语句,虽然指定 goto 作为关键字,但不⽀持它的使⽤,使程序简洁易读。
Java 的异常机制⽤于捕获例外事件,增强系统容错能⼒。
16. C++ 中重载和重写,重定义的区别
重载
是指同⼀可访问区内被声明的⼏个具有不同参数列表的同名函数,依赖于 C++函数名字的修饰会将参数加在后⾯,可以是参数类型,个数,顺序的不同。根据参数列表决定调⽤哪个函数,重载不关⼼函数的返回类型。
重写
派⽣类中重新定义⽗类中除了函数体外完全相同的虚函数,注意被重写的函数不能是 static 的, ⼀定要是虚函数,且其他⼀定要完全相同。要注意,重写和被重写的函数是在不同的类当中的,重写函数的访问修饰符是可以不同的,尽管 virtual 中是 private 的,派⽣类中重写可以改为 public。
重定义(隐藏) 派⽣类重新定义⽗类中相同名字的⾮ virtual 函数,参数列表和返回类型都可以不同,即⽗类中除了定义成 virtual 且完全相同的同名函数才不会被派⽣类中的同名函数所隐藏(重定义)。
在c++中函数名称相同,参数列表相同,返回值不同,算重载吗?
重载最重要的是函数名称相同,函数的重载区分主要是靠参数列表来进行区分 返回值不同是不能重载的,因为当调用的时候无法区分调用的是哪个函数
17. 介绍 C++ 所有的构造函数
构造函数的作⽤:初始化对象的数据成员。 类的对象被创建时,编译系统为对象分配内存空间,并⾃动调⽤构造函数,由构造函数完成成员的初始化⼯作。
⽆参数构造函数:
即默认构造函数,如果没有明确写出⽆参数构造函数,编译器会⾃动⽣成默认的⽆参数构造函数,函数为空,什么也不做,如果不想使⽤⾃动⽣成的⽆参构造函数,必需要⾃⼰显示写出⼀个⽆参构造函数。
⼀般构造函数:
也称重载构造函数,⼀般构造函数可以有各种参数形式,⼀个类可以有多个⼀般构造函数,前提是参数的个数或者类型不同,创建对象时根据传⼊参数不同调⽤不同的构造函数。
拷⻉构造函数:
拷⻉构造函数的函数参数为对象本身的引⽤,⽤于根据⼀个已存在的对象复制出⼀个新的该类的对象,⼀般在函数中会将已存在的对象的数据成员的值⼀⼀复制到新创建的对象中。如果没有显示的写拷⻉构造函 数,则系统会默认创建⼀个拷⻉构造函数,但当类中有指针成员时,最好不要使⽤编译器提供的默认的拷⻉构造函数,最好⾃⼰定义并且在函数中执⾏深拷⻉。
类型转换构造函数:
根据⼀个指定类型的对象创建⼀个本类的对象,也可以算是⼀般构造函数的⼀种,这⾥提出来,是想说有的时候不允许默认转换的话,要记得将其声明为 explict 的,来阻⽌⼀些隐式转换的发⽣。
赋值运算符的重载:
注意,这个类似拷⻉构造函数,将=右边的本类对象的值复制给=左边的对象,它不属于构造函数,=左右两边的对象必需已经被创建。如果没有显示的写赋值运算符的重载,系统也会⽣成默认的赋值运算 符,做⼀些基本的拷⻉⼯作。 这⾥区分
1 | A a1,A a2;a1 = a2;// 调用赋值运算符 |
18. C++ 的四种强制转换
C++ 的四种强制转换包括:static_cast, dynamic_cast, const_cast, reinterpret_cast
static_cast:
明确指出类型转换,⼀般建议将隐式转换都替换成显示转换,因为没有动态类型检查,上⾏转换(派⽣类->基类)安全,下⾏转换(基类->派⽣类) 不安全,所以主要执⾏⾮多态的转换操作;
dynamic_cast:
专⻔⽤于派⽣类之间的转换,type-id 必须是类指针,类引⽤或 void*,对于下⾏转换是安全的,当类型不⼀致时,转换过来的是空指针,⽽static_cast,当类型不⼀致时,转换过来的事错误意义的指针,可能造成⾮法访问等问题。
const_cast:
专⻔⽤于 const 属性的转换,去除 const 性质,或增加 const 性质, 是四个转换符中唯⼀⼀个可以操作常量的转换符。
reinterpret_cast: 不到万不得已,不要使⽤这个转换符,⾼危操作。使⽤特点: 从底层对数据进⾏重新解释,依赖具体的平台,可移植性差; 可以将整形转 换为指针,也可以把指针转换为数组;可以在指针和引⽤之间进⾏肆⽆忌惮的转换。
19. ⾯向对象的三⼤特性
C++ ⾯向对象的三⼤特征是:封装、继承、多态。
封装
就是把客观事物封装成抽象的类,并且类可以把⾃⼰的数据和⽅法只让信任的类或者对象操作,对不可信的进⾏信息隐藏。 ⼀个类就是⼀个封装了数据以及操作这些数据的代码的逻辑实体。 在⼀个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种⽅式,对象对内部数据提供了不同级别的保护,以防⽌程序中⽆关的部分意外的改变或错误的使⽤了对象的私有部分。
继承
是指可以让某个类型的对象获得另⼀个类型的对象的属性的⽅法。它⽀持按级分类的概念。 继承是指这样⼀种能⼒:它可以使⽤现有类的所有功能,并在⽆需重新编写原来的类的情况下对这些功能进⾏扩展。通过继承创建的新类称为“⼦类”或者“派⽣类”,被继承的类称为“基类”、“⽗类”或“超类”。继承的过程,就是从⼀般到特殊的过程。要实现继承,可以通过“继承”和“组合”来实现。 继承概念的实现⽅式有两类: 实现继承:实现继承是指直接使⽤基类的属性和⽅法⽽⽆需额外编码的能⼒。 接⼝继承:接⼝继承是指仅使⽤属性和⽅法的名称、但是⼦类必需提供实现的能⼒。
多态
就是向不同的对象发送同⼀个消息,不同对象在接收时会产⽣不同的⾏为(即⽅法)。即⼀个接⼝,可以实现多种⽅法。 多态与⾮多态的实质区别就是函数地址是早绑定还是晚绑定的。如果函数的调⽤,在编译器编译期间就可以确定函数的调⽤地址,并产⽣代码,则是静态的,即地址早绑定。⽽如果函数调⽤的地址不能在编译器期间确定,需要在运⾏时才确定,这就属于晚绑定。
20. 说一下c++中类权限的区别
public:公有类权限,可以被任何类或者函数访问。 protected:受保护类权限,可以被本类或子类访问。 private:私有类权限,只能被本类访问。
21. C++中另一文件可以调用其他文件中的函数
可以使用#include <>来引入头文件, 并且可以使用using namespace std;来引入命名空间。
22. 如何实现C++中的多态
可以使用virtual关键字来实现多态, 并且可以使用override关键字来实现重写。
23. 虚函数相关(虚函数表,虚函数指针),虚函数的实现原理
虚函数表:指向虚函数的指针数组,每个指针指向一个虚函数。 虚函数指针:指向虚函数的指针,指向虚函数表中的某个指针。 虚函数的实现原理: 当一个对象调用一个虚函数时,编译器会把虚函数的地址保存在虚函数指针中,然后调用虚函数指针。
24. virtual关键字
virtual关键字是用来实现多态的,它的作用是:在类中定义虚函数,在类的实现中调用虚函数,实现多态。
25. 哪些函数不能是虚函数
构造函数,构造函数初始化对象,派⽣类必须知道基类函数⼲了什么,才能进⾏构造;当有虚函数时,每⼀个类有 ⼀个虚表,每⼀个对象有⼀个虚表指针,虚表指针在构造函数中初始化; 内联函数,内联函数表示在编译阶段进⾏函数体的替换操作,⽽虚函数意味着在运⾏期间进⾏类型确定,所以内联 函数不能是虚函数; 静态函数,静态函数不属于对象属于类,静态成员函数没有this指针,因此静态函数设置为虚函数没有任何意义。 友元函数,友元函数不属于类的成员函数,不能被继承。对于没有继承特性的函数没有虚函数的说法。 普通函数,普通函数不属于类的成员函数,不具有继承特性,因此普通函数没有虚函数。
25. 简单说一下C++中程序从编写到运行的过程
程序从编写到运行的过程:
- 编译:编译器编译程序,生成二进制文件。
- 汇编:汇编器汇编程序,生成二进制文件。
- 链接:链接器链接程序,生成可执行文件。
- 运行:运行器运行程序,执行程序。
26. 简单说一下什么是编译器,都有哪些C++编译器,区别是什么?
编译器
编译器是一个程序,它的作用是将源代码编译成二进制代码。
都有哪些编译器
- GCC
- Visual Studio
- Clang
- LLVM
27. 析构函数⼀般写成虚函数的原因
直观的讲:是为了降低内存泄漏的可能性。 举例来说就是,⼀个基类的指针指向⼀个派⽣类的对象,在使⽤完毕准备销毁时,如果基类的析构函数没有定义成虚函数,那 么编译器根据指针类型就会认为当前对象的类型是基类,调⽤基类的析构函数 (该对象的析构函数的函数地址早就被绑定为基类的析构函数),仅执⾏基类的析构,派⽣类的⾃身内容将⽆法被析构,造成内存泄漏。如果基类的析构函数定义成虚函数,那么编译器就可以根据实际对象,执⾏派⽣类的析构函数,再执⾏基类的析构函数,成功释放内存。
28. 构造函数为什么⼀般不定义为虚函数
虚函数调⽤只需要知道“部分的”信息,即只需要知道函数接⼝,⽽不需要知道对象的具体类型。但是,我们要创建⼀个对象的话,是需要知道对象的完整信息的。特别是,需要知道要创建对象的确切类型,因此,构造函数不应该被定义成虚函数; ⽽且从⽬前编译器实现虚函数进⾏多态的⽅式来看,虚函数的调⽤是通过实例化之后对象的虚函数表指针来找到虚函数的地址进⾏调⽤的,如果说构造函数是虚的,那么虚函数表指针则是不存在的,⽆法找到对应的虚函数表来调⽤虚函数,那么这个调⽤实际上也是违反了先实例化后调⽤的准则。
29. 构造函数或析构函数中调⽤虚函数会怎样
实际上是不应该在构造函数或析构函数中调⽤虚函数的,因为这样的调⽤其实并不会带来所想要的效果。 举例来说就是,有⼀个动物的基类,基类中定义了⼀个动物本身⾏为的虚函数 action_type(),在基类的构造函数中调⽤了这个虚函数。
30. 构造函数析构函数可否抛出异常
C++ 只会析构已经完成的对象,对象只有在其构造函数执⾏完毕才算是完全构造妥当。在构造函数中发⽣异常,控制权转出构造函数之外。因此,在对象 b 的构造函数中发⽣异常,对象b的析构函数不会被调⽤。因此会造成内存泄漏。 如果异常从析构函数抛出,⽽且没有在当地进⾏捕捉,那个析构函数便是执⾏不全的。如果析构函数执⾏不全,就是没有完成他应该执⾏的每⼀件事情。
31. 析构函数的作⽤,如何起作⽤?
构造函数只是起初始化值的作⽤,但实例化⼀个对象的时候,可以通过实例去传递参数,从主函数传递到其他的函数⾥⾯,这样就使其他的函数⾥⾯有值了。规则,只要你⼀实例化对象,系统⾃动回调⽤⼀个构造函数,就是你不写,编译器也⾃动调⽤⼀次。 析构函数与构造函数的作⽤相反,⽤于撤销对象的⼀些特殊任务处理,可以是释放对象分配的内存空间;特点:析构函数与构造函数同名,但该函数前⾯加~。 析构函数没有参数,也没有返回值,⽽且不能重载,在⼀个类中只能有⼀个析构函数。 当撤销对象时,编译器也会⾃动调⽤析构函数。 每⼀个类必须有⼀个析构函数,⽤户可以⾃定义析构函数,也可以是编译器⾃动⽣成默认的析构函数。⼀般析构函数定义为类的公有成员。
32. 构造函数的执⾏顺序?析构函数的执⾏顺序?
基类构造函数:
如果有多个基类,则构造函数的调⽤顺序是某类在类派⽣表中出现的顺序,⽽不是它们在成员初始化表中的顺序。
成员类对象构造函数:
如果有多个成员类对象则构造函数的调⽤顺序是对象在类中被声明的顺序,⽽不是它们出现在成员初始化表中的顺序。
33. 纯虚函数 (应⽤于接⼝继承和实现继承)
声明⼀个纯虚函数的⽬的就是为了让派⽣类只继承函数的接⼝,⽽且派⽣类中必需提供⼀个这个纯虚函数的实现,否则含有纯虚函数的类将是抽象类,不能进⾏实例化。
34. 深拷⻉和浅拷⻉的区别(举例说明深拷⻉的安全性)
浅拷⻉
当出现类的等号赋值时,会调⽤拷⻉函数,在未定义显示拷⻉构造函数的情况下, 系统会调⽤默认的拷⻉函数-即浅拷贝,它能够完成成员的⼀⼀复制。当数据成员中没有指针时,浅拷⻉是可⾏的。
深拷贝
但当数据成员中有指针时,如果采⽤简单的浅拷⻉,则两类中的两个指针指向同⼀个地址,当对象快要结束时,会调⽤两次析构函数,⽽导致指野指针的问题。 所以,这时必需采⽤深拷⻉。深拷⻉与浅拷⻉之间的区别就在于深拷⻉会在堆内存中另外申请空间来存储数据,从⽽也就解决来野指针的问题。简⽽⾔之,当数据成员中有指针时,必需要⽤深拷⻉更加安全。
35. 什么情况下会调⽤拷⻉构造函数(三种情况)
类的对象需要拷⻉时,拷⻉构造函数将会被调⽤,以下的情况都会调⽤拷⻉构造函数: ⼀个对象以值传递的⽅式传⼊函数体,需要拷⻉构造函数创建⼀个临时对象压⼊到栈空间中。 ⼀个对象以值传递的⽅式从函数返回,需要执⾏拷⻉构造函数创建⼀个临时对象作为返回值。 ⼀个对象需要通过另外⼀个对象进⾏初始化。
36. 为什么拷⻉构造函数必需时引⽤传递,不能是值传递?
为了防⽌递归调⽤。当⼀个对象需要以值⽅式进⾏传递时,编译器会⽣成代码调⽤它的拷⻉构造函数⽣成⼀个副 本,如果类 A 的拷⻉构造函数的参数不是引⽤传递,⽽是采⽤值传递,那么就⼜需要为了创建传递给拷⻉构造函数的参数的临时对象,⽽⼜⼀次调⽤类 A 的拷⻉构造函数,这就是⼀个⽆限递归。
37. 结构体内存对⻬⽅式
规则1:
结构体中第一个成员的偏移量是0,以后每个成员的位置是x的倍数; x = min(#pragma pack(), 该成员自身的长度)
规则2:
成员对齐后,结构体自身也要对齐,按照y的倍数进行; y = min(#pragma pack(), 最大成员尺寸)。
38. 那么内存对⻬的作⽤是什么呢?
CPU 的内存访问速度⼤⼤提升
经过内存对⻬之后,CPU 的内存访问速度⼤⼤提升。因为 CPU 把内存当成是⼀块⼀块的,块的⼤⼩可以是 2,4,8,16 个字节,因此 CPU 在读取内存的时候是⼀块⼀块进⾏读取的,块的⼤⼩称为内存读取粒度。⽐如说 CPU 要读取⼀个 4 个字节的数据到寄存器中(假设内存读取粒度是 4),如果数据是从 0 字节开始的,那么直接将 0-3 四个字节完全读取到寄存器中进⾏处理即可。
有的 CPU 遇到未进⾏内存对⻬的处理直接拒绝处理
另外,还有⼀个就是,有的 CPU 遇到未进⾏内存对⻬的处理直接拒绝处理,不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。所以内存对⻬还有利于平台移植。
39. 内存泄漏的定义,如何检测与避免?
定义:
内存泄漏简单的说就是申请了⼀块内存空间,使⽤完毕后没有释放掉。 它的⼀般表现⽅式是程序运⾏时间越⻓,占⽤内存越多,最终⽤尽全部内存,整个系统崩溃。由程序申请的⼀块内存,且没有任何⼀个指针指向它,那么这块内存就泄漏了。
如何检测内存泄漏
⾸先可以通过观察猜测是否可能发⽣内存泄漏,Linux 中使⽤ swap 命令观察还有多少可⽤的交换空间,在⼀两分钟内键⼊该命令三到四次,看看可⽤的交换区是否在减少。 还可以使⽤ 其他⼀些 /usr/bin/stat ⼯具如 netstat、vmstat 等。如发现波段有内存被分配且从不释放,⼀个可能的解释就是有个进程出现了内存泄漏。 当然也有⽤于内存调试,内存泄漏检测以及性能分析的软件开发⼯具 valgrind 这样的⼯具来进⾏内存泄漏的检测。
40. 说⼀下 define、const、typedef、inline 使⽤⽅法?
1、const 与 #define
的区别
const 定义的常量是变量带类型,⽽ #define 定义的只是个常数不带类型; define 只在预处理阶段起作⽤,简单的⽂本替换,⽽ const 在编译、链接过程中起作⽤; define 只是简单的字符串替换没有类型检查。⽽const是有数据类型的,是要进⾏判断的,可以避免⼀些低级错误; define 预处理后,占⽤代码段空间,const 占⽤数据段空间; const 不能重定义,⽽ define 可以通过 #undef 取消某个符号的定义,进⾏重定义; define 独特功能,⽐如可以⽤来防⽌⽂件重复引⽤。
2、#define
和别名 typedef 的区别
执⾏时间不同,typedef 在编译阶段有效,typedef 有类型检查的功能;#define 是宏定义,发⽣在预处理阶段,不进⾏类型检查; 功能差异,typedef ⽤来定义类型的别名,定义与平台⽆关的数据类型,与 struct 的结合使⽤等。
#define
不只是可以为类型取别名,还可以定义常量、变量、编译开关等。 作⽤域不同,#define没有作⽤域的限制,只要是之前预定义过的宏,在以后的程序中都可以使⽤。 ⽽ typedef 有⾃⼰的作⽤域。
3、define 与 inline 的区别
#define
是关键字,inline是函数; 宏定义在预处理阶段进⾏⽂本替换,inline 函数在编译阶段进⾏替换; inline 函数有类型检查,相⽐宏定义⽐较安全; inline 函数不能被调用,只能被调用;
41. 预处理,编译,汇编,链接程序的区别
⼀段⾼级语⾔代码经过四个阶段的处理形成可执⾏的⽬标⼆进制代码。 预处理器→编译器→汇编器→链接器:最难理解的是编译与汇编的区别。
预处理阶段:写好的⾼级语⾔的程序⽂本⽐如 hello.c,预处理器根据 #开头的命令,修改原始的程序,如 #include<stdio.h> 将把系统中的头⽂件插⼊到程序⽂本中,通常是以 .i 结尾的⽂件。
编译阶段:编译器将 hello.i ⽂件翻译成⽂本⽂件 hello.s,这个是汇编语⾔程序。⾼级语⾔是源程序。所以注意概念之间的区别。汇编语⾔程序是⼲嘛的?每条语句都以标准的⽂本格式确切描述⼀条低级机器语⾔指令。不同的⾼级语⾔翻译的汇编语⾔相同。
汇编阶段:汇编器将 hello.s 翻译成机器语⾔指令。把这些指令打包成可重定位⽬标程序,即 .o⽂件。hello.o是⼀个⼆进制⽂件,它的字节码是机器语⾔指令,不再是字符。前⾯两个阶段都还有字符。
链接阶段:⽐如 hello 程序调⽤ printf 程序,它是每个 C 编译器都会提供的标准库 C 的函数。这个函数存在于⼀个名叫 printf.o 的单独编译好的⽬标⽂件中,这个⽂件将以某种⽅式合并到 hello.o 中。链接器就负责这种合并。得到的是可执⾏⽬标⽂件。
42. 动态链接和静态链接区别
静态连接库就是把 (lib) ⽂件中⽤到的函数代码直接链接进⽬标程序,程序运⾏的时候不再需要其它的库⽂件;动态链接就是把调⽤的函数所在⽂件模块(DLL)和调⽤函数在⽂件中的位置等信息链接进⽬标程序,程序运⾏的时候再从 DLL 中寻找相应函数代码,因此需要相应 DLL ⽂件的⽀持。
静态链接库与动态链接库都是共享代码的⽅式,如果采⽤静态链接库,则⽆论你愿不愿意,lib 中的指令都全部被直接包含在最终⽣成的 EXE ⽂件中了。但是若使⽤ DLL,该 DLL 不必被包含在最终 EXE ⽂件中,EXE ⽂件执⾏时可以“动态”地引⽤和卸载这个与 EXE 独⽴的 DLL ⽂件。
静态链接库和动态链接库的另外⼀个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,⽽在动态链接库中还可以再包含其他的动态或静态链接库。
动态库就是在需要调⽤其中的函数时,根据函数映射表找到该函数然后调⼊堆栈执⾏。如果在当前⼯程中有多处对 dll⽂件中同⼀个函数的调⽤,那么执⾏时,这个函数只会留下⼀份拷⻉。但如果有多处对 lib ⽂件中同⼀个函数的调⽤,那么执⾏时该函数将在当前程序的执⾏空间⾥留下多份拷⻉,⽽且是⼀处调⽤就产⽣⼀份拷⻉。
43. 动态联编与静态联编
在 C++ 中,联编是指⼀个计算机程序的不同部分彼此关联的过程。按照联编所进⾏的阶段不同,可以分为静态联编和动态联编;
静态联编是指联编⼯作在编译阶段完成的,这种联编过程是在程序运⾏之前完成的,⼜称为早期联编。要实现静态联编,在编译阶段就必须确定程序中的操作调⽤(如函数调⽤)与执⾏该操作代码间的关系,确定这种关系称为束定,在编译时的束定称为静态束定。静态联编对函数的选择是基于指向对象的指针或者引⽤的类型。其优点是效率⾼,但灵活性差。
动态联编是指联编在程序运⾏时动态地进⾏,根据当时的情况来确定调⽤哪个同名函数,实际上是在运⾏时虚函数的实现。这种联编⼜称为晚期联编,或动态束定。动态联编对成员函数的选择是基于对象的类型,针对不同的对象类型将做出不同的编译结果。
C++中⼀般情况下的联编是静态联编,但是当涉及到多态性和虚函数时应该使⽤动态联编。动态联编的优点是灵活性强,但效率低。动态联编规定,只能通过指向基类的指针或基类对象的引⽤来调⽤虚函数,其格式为:指向基类的指针变量名->虚函数名(实参表)或基类对象的引⽤名.虚函数名(实参表)
实现动态联编三个条件:
必须把动态联编的⾏为定义为类的虚函数; 类之间应满⾜⼦类型关系,通常表现为⼀个类从另⼀个类公有派⽣⽽来; 必须先使⽤基类指针指向⼦类型的对象,然后直接或间接使⽤基类指针调⽤虚函数;
44.静态库和动态库
1. 什么是静态库,什么是动态库
静态库 一般扩展名为(.a或.lib),这类的函数库通常扩展名为libxxx.a或xxx.lib 。 这类库在编译的时候会直接整合到目标程序中,所以利用静态函数库编译成的文件会比较大, 这类函数库最大的优点就是编译成功的可执行文件可以独立运行,而不再需要向外部要求读取函数库的内容;但是从升级难易度来看明显没有优势,如果函数库更新,需要重新编译。
动态库 动态函数库的扩展名一般为(.so或.dll),这类函数库通常名为libxxx.so或xxx.dll 。 与静态函数库被整个捕捉到程序中不同,动态函数库在编译的时候,在程序里只有一个“指向”的位置而已,也就是说当可执行文件需要使用到函数库的机制时,程序才会去读取函数库来使用;也就是说可执行文件无法单独运行。这样从产品功能升级角度方便升级,只要替换对应动态库即可,不必重新编译整个可执行文件。
2. 静态库和动态库的区别是什么?
静态连接库就是把(lib)文件中用到的函数代码直接链接进目标程序,程序运行的时候不再需要其它的库文件;动态链接就是把调用的函数所在文件模块(DLL)和调用函数在文件中的位置等信息链接进目标程序,程序运行的时候再从DLL中寻找相应函数代码,因此需要相应DLL文件的支持。
静态编译只需要所需的头文件和对应的lib库文件。
动态编译则需要所需的头文件、对应的lib库文件以及对应的dll库文件。
事实上,静态编译用到的库文件和动态编译用到的库文件有本质区别:前者静态库是一个或者多个obj文件的打包,所以有人干脆把从obj文件生成lib的过程称为Archive,即合并到一起。比如你链接一个静态库,如果其中有错,它会准确的找到是哪个obj有错,即静态lib只是壳子。而后者为动态连接库(DLL)的导入库,它告诉链接器调用的函数在哪个DLL中,函数执行代码在DLL中的什么位置,这也就是为什么需要附加依赖项 .LIB文件,它起到桥梁的作用。
45. 动态编译与静态编译
静态编译
编译器在编译可执⾏⽂件时,把需要⽤到的对应动态链接库中的部分提取出来,连接到可执⾏⽂件中去,使可执⾏⽂件在运⾏时不需要依赖于动态链接库;
动态编译
可执⾏⽂件需要附带⼀个动态链接库,在执⾏时,需要调⽤其对应动态链接库的命令。所以其优点⼀⽅⾯是缩⼩了执⾏⽂件本身的体积,另⼀⽅⾯是加快了编译速度,节省了系统资源。缺点是哪怕是很简单的程序,只⽤到了链接库的⼀两条命令,也需要附带⼀个相对庞⼤的链接库;⼆是如果其他计算机上没有安装对应的运⾏库,则⽤动态编译的可执⾏⽂件就不能运⾏。
46. VS中的DeBug和Realease区别
Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。 Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。
47. strlen和sizeof的区别
strlen是字符串的长度,而sizeof是类型的大小。 sizeof是一个运算符,strlen是一个函数。 strlen后是不加最后的结束标志'\0',其表示字符串的长度。 而sizeof求的是字符串所在内存中的长度,所以它是加上最最后的结束标志'\0'的。 sizeof可以用类型做参数,可以用函数做参数。而strlen只能用char*做参数。
48. a和&a的区别
a是一个指针,&a是一个引用
49. static关键字在C语言和C++中有什么区别
- static在C语言中的作用(C++通用)
修饰变量
a、修饰全局变量(全局变量与静态全局变量的对比) 修饰全局变量:作用域仅限于变量被定义的文件中,其他文件即使用extern声明也无法直接使用此变量,extern用法详见这里。(可以间接访问,即通过本文件的非static函数返回static变量的值,类似于C++类中的private变量外界没有权限访问,但是可以通过public函数返回private变量的值)。
对比:全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同,且都只初始化一次,但静态全局变量对其他文件不可见,全局变量是可见的。 总结:把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。
修饰局部变量
修饰局部变量:在函数内定义的局部变量被修饰,可以延长变生命周期,但是作用域不变,只初始化一次。 对比:普通局部变量定义后,出了作用域就会被释放,静态局部变量生命周期到程序结束才结束。 总结:把局部变量改变为静态变量后是改变了它的存储方式,让静态局部变量出了作用域依然存在,到程序结束,生命周期才结束。
修饰函数
和全局变量一样,函数的定义和声明默认情况下是extern的,但静态函数只是在声明它的文件当中可见,不能被其他文件所用。
- static在C++中的作用
C+ +重用了这个关键字,并赋予它与前面不同的含义:表示属于一个类而不是属于此类的任何特定对象的变量和函数。
静态数据成员
静态数据成员是属于整个类的,而不是属于某个对象。即不管实例多少个对象,它们都公用一个静态数据成员(如:使用静态数据成员统计类已经实例化了多少对象)。
在c++中,普通数据成员在构造函数的函数体或初始化表中初始化;常量数据成员(const int a )必须在构造函数的初始化表中初始化(const对象或引用只能初始化但是不能赋值。构造函数的函数体内只能做赋值而不是初始化);而静态数据成员(static int b )则必须在类外初始化(int 类名::b=100),这是因为静态数据成员不属于任何一个对象,而是属于整个类的。
静态成员函数
静态成员函数是属于整个类的,而不是属于某个对象。静态成员函数可以被该类的所有对象直接访问;静态成员函数本身只能访问静态成员,不可以访问非静态成员。
- 常见面试题--为什么static变量值只初始化一次?
对于所有的对象(不仅仅是静态对象),初始化都只有一次,而由于静态变量具有“记忆”功能,初始化后,一直都没有被销毁,都会保存在内存区域中,所以不会再次初始化。存放在静态区的变量的生命周期一般比较长,它与整个程序“同生死、共存亡”,所以它只需初始化一次。而auto变量,即自动变量,由于它存放在栈区,一旦函数调用结束,就会立刻被销毁。
50. 不使用第三方变量交换两个变量的值
代码实现
1 | int num1 = 10, num2 = 20; |
51. C++都有哪些版本
C++98,C++03,C++11,C++14,C++17,C++20
C++11 的特性主要包括下⾯⼏个⽅⾯:
提⾼运⾏效率的语⾔特性:右值引⽤、泛化常量表达式 原有语法的使⽤性增强:初始化列表、统⼀的初始化语法、类型推导、范围 for 循环、Lambda 表达式、final和 override、构造函数委托 语⾔能⼒的提升:空指针 nullptr、default 和 delete、⻓整数、静态 assert C++ 标准库的更新:智能指针、正则表达式、哈希表等