【面试题】【C++】01-C/C++基础知识
- 4.1
- C语言和C++有什么区别?
- 简述下C++语言的特点
- 头文件""和<>的区别?
- 从代码到可执行文件的过程?
- 静态链接与动态链接?
- extern "C"的作用
- extern "C"带来的编译区别
- 简述宏的作用
- 为什么要少使用宏?有什么替代方案?
- 简述内联函数?
- 条件编译的作用
- 内存对齐
- 2 以下代码中的a和&a有什么区别?
- 3 static关键字有什么作用?
- 4 #define和const有什么区别?
- 5 对于⼀个频繁使用的短小函数,应该使用什么来实现?有什么优缺点?
- 6 什么是智能指针?智能指针有什么作用?分为哪几种?各自有什么样的特点?
- 7 shared_ptr是如何实现的?
- 8 右值引用有什么作用?
- 9 悬挂指针与野指针有什么区别?
- 11 变量的声明和定义有什么区别
- 13 写出int、bool、float、指针变量与“零值”比较的if语句
- 14 结构体可以直接赋值吗
- 15 sizeof和strlen的区别
- 16 C语言的关键字static和C++的关键字static有什么区别
- 17 volatile有什么作用
- 18 一个参数可以既是const又是volatile吗
- 19 全局变量和局部变量有什么区别?操作系统和编译器是怎么知道的?
- 20 简述strcpy、sprintf与memcpy的区别
- 21 请解析((void ()( ) )0)( )的含义
- 22 C语言的指针和引用和c++的有什么区别?
- 23 typedef和#define有什么区别
- 24 指针常量与常量指针区别
- 25 简述队列和栈的异同
- 26 设置地址为0x67a9 的整型变量的值为0xaa66
- 27 C语言的结构体和C++的有什么区别
- 28 如何避免“野指针”
- 29 句柄和指针的区别和联系是什么?
- 31 C++的顶层const和底层const ?
- 32 栈区和堆区的区别?栈区和堆栈的区别?
- 33 ⾯向对象的三⼤特征是哪些?各⾃有什么样的特点?
- 34 C++中类成员的访问权限
- 35 多态的实现有哪⼏种?
- 36 动态多态有什么作⽤?有哪些必要条件?
- 37 动态绑定是如何实现的?
- 38 纯虚函数有什么作⽤?如何实现?
- 39 虚函数表是针对类的还是针对对象的?同⼀个类的两个对象的虚函数表是怎么维护的?
- 40 为什么基类的构造函数不能定义为虚函数?
- 41 为什么基类的析构函数需要定义为虚函数?
- 42 构造函数和析构函数能抛出异常吗?
- 43 如何让⼀个类不能实例化?
- 44 多继承存在什么问题?如何消除多继承中的⼆义性?
- 45 如果类A是⼀个空类,那么sizeof(A)的值为多少?为什么?
- 46 覆盖和重载之间有什么区别?
- 47 拷⻉构造函数和赋值运算符重载之间有什么区别?
- 48 对虚函数和多态的理解
- 49 请你来说⼀下C++中struct和class的区别
- 50 说说强制类型转换运算符(类型转换、cast转换)
- 51 简述类成员函数的重写、重载和隐藏的区别
- 52 RTTI是什么?其原理是什么?
- 53 C++的空类有哪些成员函数
- 54 模板函数和模板类的特例化
- 55 拷⻉初始化和直接初始化,初始化和赋值的区别?
1 简述main函数的参数?
- int argc
程序运行时,传给main函数的命令行参数的个数。 - char **argv
argv[0]:指向程序运行的全路径名。
argv[1]:指向程序名后的第一个字符串。
argv[2]:指向程序名后的第二个字符串。
…
argv[argc - 1]:指向程序名后的最后一个字符串。
argv[argc]:NULL。
2 if-else易错点
以下程序运行后,i和j的值分别是多少?
int main(){
int i = -1, j = 2;
if (i > 0)
if (j > 3)
i++;
else
j++;
i -= j;
}
if-else要注意两个原则:
- if-else就近匹配原则:else与其上面最近的if语句匹配。
- 如果没有大括号,if-else的作用域仅是紧跟的第一条语句。
按照以上原则,将代码重新整理如下:
int main(){
int i = -1, j = 2;
if (i > 0)
if (j > 3)
i++;
else
j++;
i -= j;
}
不难看出,i的值为-3,j的值为2。
3 break的直通性
如果switch语句中,case结尾处未加break,程序会顺序执行接下来的case,直至遇到break或switch结尾。
譬如以下代码,result为10。
int main(){
int i = 2;
int result = 0;
switch (i) {
case 1:
result = result + i;
case 2:
result = result + i * 2;
case 3:
result = result + i * 3;
}
}
4 内外循环的效率问题
存在内外循环结构时,为了提高效率,应将大循环放在小循环里面,譬如:
for (int i=0; i<4; i++){
for (int j=0; j<100; j++){
;
}
}
5 do-while的妙用
使用do{…}while(0)进行宏定义。可以避免因宏直接替换的特性导致的意外语法错误。注意,while(0)后面不加;。
#define test() do { \
function_a(); \
function_b(); \
} while(0)
if (condition)
test();
else
...;
展开后,代码为以下形式:
if (condition)
do {
function_a();
function_b();
} while(0);
else
...;
6 简述枚举类型
所谓枚举,就是将变量的值一一列举出来,变量的值只能在列举出的范围内。如果一个变量只有几种可能的值,可以将其定义为枚举类型。
创建枚举类型的形式:
enum 枚举名 {
标识符[=整型常数],
标识符[=整型常数],
...
标识符[=整型常数]
} 枚举变量;
如果枚举没有初始化,默认情况下第一个名称的值为0,第二个名称的值为1,第三个名称的值为2,以此类推。
如果枚举中的某个值被初始化,则在其后定义的值要从这个初始值向后累加:
enum color {
red, // red的值为0
green = 5, // green的值为5
blue // blue的值为6
};
7 结构体与共用体的区别
- struct和union都是由多个不同的数据类型成员组成。struct的所有成员都存在。但在同一时刻,union中只存放了一个被选中的成员。
- 在不考虑字节对齐的前提下,struct变量的总长度等于所有成员长度之和。union变量的长度等于最长成员的长度。
- struct的不同成员赋值是互不影响的。对于union的不同成员赋值,则会将其他成员重写,原来成员的值就不存在了。
补充说明:
结构体是用户自定义的数据类型,其中可以存储不同类型的数据项。
共用体是一种特殊的数据类型,允许在相同的内存位置存储不同的数据类型。可以定义一个带有多成员的共用体,但任何时候都只能有一个成员带有值。每个成员都是读写同一个内存空间,所以内存空间中的内容不停被覆盖。因此,同一时刻,只能操作一个成员变量。
struct Books {
char title[50];
char author[50];
char subject[100];
int book_id;
} book;
union Data {
int i;
float f;
char str[20];
} data;
结构体和共用体最大的区别在于,两者使用内存的方式不同(此处描述暂时不考虑内存对齐)。
- 结构体占用的内存是所有成员各自占用的内存空间之和。
- 共用体占用的内存和最大的成员相同。
struct Data1 {
int i;
float f;
char str[20];
} data1;
union Data2 {
int i;
float f;
char str[20];
} data2;
sizeof(data1); // 4+4+20=28bytes
sizeof(data2); // str成员最大,20bytes
以下示例演示共用体的使用方式:
/*
* 本代码在同一时间三次操作不同的成员,从而导致成员值损坏,只有最后赋值的成员能够完好输出。
* 输出:
* data.i : 1917853763
* data.f : 4122360580327794860452759994368.000000
* data.str : C Programming
*/
#include <stdio.h>
#include <string.h>
typedef union Data {
int i;
float f;
char str[20];
} Data;
int main()
{
Data data;
data.i = 10;
data.f = 220.5;
strcpy(data.str, "C Programming");
printf("data.i: %d\n", data.i);
printf("data.f: %f\n", data.f);
printf("data.str: %s\n", data.str);
return 0;
}
/*
* 本代码在同一时间只用到一个成员,因此所有成员都能完好输出
* 输出:
* data.i : 10
* data.f : 220.500000
* data.str : C Programming
*/
#include <stdio.h>
#include <string.h>
union Data{
int i;
float f;
char str[20];
};
int main( ){
union Data data;
data.i = 10;
printf( "data.i : %d\n", data.i);
data.f = 220.5;
printf( "data.f : %f\n", data.f);
strcpy( data.str, "C Programming");
printf( "data.str : %s\n", data.str);
return 0;
}
4.1
C语言和C++有什么区别?
- C语言是C++的子集,C++可以很好地兼容C语言。C++新增了很多特性,如容器、模板、智能指针、引用、lambda表达式、auto变量、函数重载、异常处理、内联函数等。
- C++新增了面向对象的内容。C语言主要使用面向过程的编程范式,C++主要使用面向对象的编程范式(泛型编程和函数式编程使用相对较少)。
- C++新增了一些特性,用于改善安全性。如const常量、引用、cast转换、智能指针、异常处理等。
- C++可复用性高。C++引入了模板的概念,实现了标准模板库STL(Standard Template Library)。STL的一个重要特点是数据结构和算法的分离,体现了泛型化程序设计的思想。C++的STL库相对于C语言的函数库更灵活、更通用。
简述下C++语言的特点
- C++在C语言基础上引入了面向对象的机制,同时也兼容C语言。
- C++有三大特性(1)封装。(2)继承。(3)多态;
- C++语言编写出的程序结构清晰、易于扩充,程序可读性好。
- C++生成的代码质量高,运行效率高,仅比汇编语言慢10%~20%;
- C++更加安全,增加了const常量、引用、四类cast转换(static_cast、dynamic_cast、const_cast、reinterpret_cast)、智能指针、try—catch等等;
- C++可复用性高,C++引入了模板的概念,后面在此基础上,实现了方便开发的标准模板库STL(Standard Template Library)。
- C++是不断在发展的语言。C++后续版本更是发展了不少新特性,如C++11中引入了nullptr、auto变量、Lambda匿名函数、右值引用、智能指针。
头文件""和<>的区别?
- 尖括号<>的头文件是系统文件,双引号""的头文件是自定义文件。
- 编译器预处理阶段查找头文件的路径不一样。
- 使用尖括号<>的头文件的查找路径:编译器设置的头文件路径–>系统变量。
- 使用双引号""的头文件的查找路径:当前头文件目录–>编译器设置的头文件路径–>系统变量。
- 要养成良好习惯,系统文件就使用尖括号<>,自定义文件就使用双引号"",提高编译效率。
从代码到可执行文件的过程?
C++程序从源码到可执行文件,有四个过程,预编译、编译、汇编、链接。
- 预编译:
- 将所有的#define删除,并且展开所有的宏定义
- 处理所有的条件预编译指令,如#if、#ifdef
- #include预编译指令,将被包含的文件插入到该预编译指令的位置。
- 过滤所有的注释,如//、/**/
- 行号和文件名标识。
- 编译
- 词法分析:将源代码的字符序列分割成一系列的记号。
- 语法分析:对记号进行语法分析,产生语法树。
- 语义分析:判断表达式是否有意义。
- 代码优化:
- 目标代码生成:生成汇编代码。
- 目标代码优化
- 汇编
- 这个过程主要是将汇编代码转变成机器可以执行的指令。
- 链接
- 将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序。
静态链接与动态链接?
静态链接在编译的链接阶段将执行代码拷贝到调用位置。
优点:
- 程序独立运行,不依赖库文件;
- 前期执行效率比动态链接略高。
缺点:
- 程序体积较大。如有多个程序使用同一静态库,会有一定空间浪费;
- 静态库更新后,相关可执行文件均需重新链接。
静态库后缀:
- Windows:.lib
- Linux:.a
动态库不拷贝执行代码,而是在程序运行或加载时函数重定位信息传给操作系统,由操作系统将对应的动态库加载到内存中。需要调用相关代码时,再去共享内存中查找,从而实现动态链接。
优点:
- 多个程序可以共用一个动态库,节省资源;
- 动态库更新后,可执行文件不用重新链接;
缺点:
- 程序依赖动态库,如操作系统中动态库缺失,则无法使用;
- 由于是动态加载,前期执行效率会比静态库略慢。
动态库后缀:
- Windows:.dll
- Linux:.so
extern "C"的作用
extern "c"的作用是正确实现C++代码对C语言代码的调用。extern "C"指示编译器,这部分代码按C语言方式来编译。
主要场景:
- C++代码调用C语言代码。
//extern示例
//在C++程序里边声明该函数,会指示编译器这部分代码按C语言的进行编译
extern "C" int strcmp(const char *s1, const char *s2);
- 在C++头文件中使用。
//在C++程序里边声明该函数
extern "C"{
#include <string.h>//string.h里边包含了要调用的C函数的声明
}
- 多人协作开发时,某些人擅长C语言,某些人擅长C++。
看牛客网的示例。
extern "C"带来的编译区别
由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。
简述宏的作用
#define 是一个宏命令,用来将一个标识符(宏名)定义为一个字符串(替换文本)。
该命令有两种格式:一种是不带参数的宏定义,另一种是带参数的宏定义。
- 不带参数的宏定义的声明格式如下所示:
#define 宏名 字符串
例:#define PI 3.1415
- 带参数的宏定义的声明格式如下所示:
#define 宏名(参数列表) 宏
例:#define MAX(x,y) ((x)>(y)?(x):(y))
为什么要少使用宏?有什么替代方案?
宏是在预编译阶段被展开的,不会进行语法检查、语义分析。如果不注意细节,很容易出现问题。
- 不带参数的宏命令我们可以用常量const来替代,比如const int PI = 3.1415,可以起到同样的效果,而且还比宏安全,因为这条语句会在编译阶段进行语法检查。
- 而带参数的宏命令有点类似函数的功能,在C++中可以使用内联函数或模板来替代
简述内联函数?
在编译时,编译器会把内联函数的代码展开在每个调用该函数的地方。如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字inline。
内联函数的优点(为什么使用内联函数)?
- 内联函数省去了函数调用的开销,执行效率更高;
- 内联函数会进行语法检查,比宏函数更安全。
内联函数的缺点?
- 如果内联函数的函数体执行时间很长,则省略函数调用带来的效率提升效果不明显;
- 如果函数体内代码较长,会导致程序总代码量增大,消耗内存空间。
- 编译器是否按内联处理函数,是不确定的行为。
什么情况下不宜使用内联?
- 函数体内代码较长
- 函数体内出现循环
内联函数和宏函数的区别?
条件编译的作用
- 使用条件编译将功能模块包含进去,可以快捷打开/关闭某个功能;
- 可用于快速打开/关闭调试信息。
- 不同硬件平台的差异处理。
内存对齐
在结构体中,编译器为结构体的每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构体的地址相同。为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性。如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。
- 内存起始地址要是数据类型的整数倍
- 占用的内存空间大小需要是结构体中占用最大内存空间的类型的整数倍
为什么要字节对齐?
需要字节对齐的根本原因在于CPU访问数据的效率问题。如果变量在自然对齐位置上,只要访问一次内存就可以取出数据。
result的结果是多少?
union example {
int a[5];
char b;
double c;
};
int result = sizeof(example);
struct example {
int a[5];
char b;
double c;
}test_struct;
int result = sizeof(test_struct);
struct example {
char b;
double c;
int a;
}test_struct;
int result = sizeof(test_struct);
union和结构体中,占用的内存空间大小需要是结构体中占用最大内存空间的类型的整数倍。因此第一个答案为24,第二个答案为32,第三个答案为24。
不考虑字节对齐时的内存分布:(为直观一点,采用的十进制)
考虑字节对齐时的内存分布:(为直观一点,采用的十进制)
强制对齐
通过预编译命令#pragma pack(n),n=1,2,4,8,16来指定我们的对齐系数,结构体成员所占用内存的起始地址需要是n的整数倍。
- 对齐字节数 = min(成员起始地址应是n的倍数时填充的字节数, 自然对齐时填充的字节数)。
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#pragma pack(4)
using namespace std;
struct example{
char a;
double b;
int c;
}test_struct;
int main() {
int result = sizeof(test_struct);
cout << result << endl;
system("Pause");
return 0;
}
答案为16.
分割线:以下为待重新整理内容
2 以下代码中的a和&a有什么区别?
int a[10];
int (*p)[10] = &a;
- a是数组名,也是数组的首地址。a+1会向后移动sizeof(int),即*(a+1)就是a[1]。
- &a是指向数组的指针,类型为int(*)[10]。a+1会向后移动sizeof(int) * 10。即数组尾元素之后的位置
3 static关键字有什么作用?
- 修饰局部变量
(1)静态局部变量存储在静态存储区内。
(2)静态局部变量仅在第一次调用时创建并初始化,后续调用到其定义代码时,不会再次定义或初始化;
(3)静态局部变量的生命周期从其定义时开始,直到程序结束。
(4)静态局部变量仅在其定义域中可见,在其定义域之外不可见。 - 修饰全局变量
(1)静态全局变量存储在静态存储区内;
(2)静态全局变量的生命周期从其定义位置开始,直到程序运行结束。
(3)静态全局变量在其声明的文件内可见,在文件外不可见。 - 修饰函数
(1)static修饰的函数仅在其定义的文件中可见,文件外不可见。由此可以避免函数命名冲突。 - 修饰成员变量
(1)static修饰的成员变量只有一份拷贝,为该类的所有对象共享。
(2)static修饰的成员变量无需实例化即可访问;
(3)static修饰的成员变量在类外部初始化,且初始化时不加static。 - 修饰成员函数
(1)static修饰的成员函数无需实例化也可调用;
(2)static修饰的成员函数不接受this指针
(3)static修饰的成员函数不能使用非static的成员变量,只能使用静态成员。
4 #define和const有什么区别?
- 编译器处理方式不同
(1)#define在预处理阶段进行替换,不能调试。
(2)const常量在编译阶段处理。 - 类型和安全检查不同
(1)#define不做类型检查,仅是代码展开,可能产生边际效应等错误。
(2)const常量具有类型,在编译阶段会进行类型检查。 - 存储方式不同
(1)#define是单纯的替换,不分配内存,存储在程序的代码段中。
(2)const常量会分配内存,但仅维持一份拷贝,存储在程序的数据段中。 - 定义域不同
(1)#define不受定义域限制。
(2)const常量仅在定义域内有效。
5 对于⼀个频繁使用的短小函数,应该使用什么来实现?有什么优缺点?
应使用inline内联函数来实现。
优点:
- inline函数省略了函数调用的过程,效率相对更高;
- inline函数会进行语法检查和类型检查,安全性比宏函数更好。
缺点:
- inline函数在所有调用位置展开,代码膨胀;
- 如果inline函数的执行时间比调用时间长很多,则省略函数调用带来的效率提升并不明显。
- 如果inline函数修改,则所有使用此函数的源码文件都需要重新编译。
- inline函数只是建议,编译器是否内联是不确定的。
6 什么是智能指针?智能指针有什么作用?分为哪几种?各自有什么样的特点?
智能指针是为了解决手动申请、释放动态内存容易导致内存泄露和空悬指针的问题而出现的。智能指针会自动管理动态内存,在析构时释放资源。
auto_ptr:
- auto_ptr是C++98的概念,在C++11中已被淘汰;
- auto_ptr本质上是独占的智能指针,但将其赋值给另一个智能指针是不会报错,此时继续使用前者可能导致内存崩溃。
unique_ptr:
- unique_ptr是独占式的指针,同一时间只能有一个智能指针指向该对象。
- unique_ptr不能拷贝构造和拷贝赋值,但可以移动构造和移动赋值。
shared_ptr:
- shared_ptr是共享式的智能指针,允许多个智能指针同时指向同一对象。
- shared_ptr有引用计数,用于记录当前有多少智能指针指向该对象。
- 构造函数中,将引用计数初始化为1;
- 拷贝构造函数中,将引用计数+1;
- 赋值运算符中,将左侧对象的引用计数-1,右侧对象的引用计数+1;
- 析构函数中,引用计数-1;
- 在析构或赋值时,如果某个对象的引用计数减为0,则调用delete释放对象。
- 成员函数:
- use_count:返回引用计数的个数;
- unique:返回是否独占
- swap:交换两个shared_ptr对象
- reset
- get
weak_ptr:
- weak_ptr是不影响所指对象生命周期的智能指针。weak_ptr是对对象的弱引用,可以绑定到shared_ptr,但weak_ptr的构造和析构不会改变引用计数;
- weak_ptr解决shared_ptr相互引用时,两个指针的引用计数永远不会减为0,从而导致的死锁问题。
- weak_ptr和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给weak_ptr,weak_ptr可以通过lock函数获得shared_ptr。
- 不能通过weak_ptr直接访问对象的方法,要先转化为shared_ptr再访问。
7 shared_ptr是如何实现的?
- 构造函数中,引用计数初始化为1;
- 拷贝构造函数中,引用计数+1;
- 赋值时,左侧对象引用计数-1,右侧对象引用计数+1
- 析构函数中,引用计数-1。
- 在赋值和析构函数中,如果引用计数降为0,调用delete释放对象。
8 右值引用有什么作用?
9 悬挂指针与野指针有什么区别?
悬挂指针:指针所指的对象已经释放,但指针仍指向这块已经被回收的内存。
野指针:未经初始化的指针。
11 变量的声明和定义有什么区别
(1)变量定义时分配内存,声明则不分配内存;
(2)变量定义仅有一处,但声明可以有多处;
(3)使用extern修饰的是变量声明,说明该变量将在本文件后续位置或其他文件中定义。
13 写出int、bool、float、指针变量与“零值”比较的if语句
// int
if (i == 0)
if (i != 0)
// bool
if (b)
if (!b)
// 指针
if (p == nullptr)
if (p != nullptr)
// float
#define EPSINON 0.00001
if ((x >= -EPSINON) && (x <= EPSINON))
14 结构体可以直接赋值吗
结构体可以在定义时直接初始化,同一结构体的不同对象之间也可以相互赋值。但如果结构体内存在指针类型的变量,则在释放时需要注意是否还有其他对象使用这段内存。
15 sizeof和strlen的区别
(1)sizeof是操作符,strlen是库函数;
(2)sizeof的结果在编译阶段即可确定,strlen则在运行时确定;
(3)sizeof可以处理类型、变量、数组等,strlen仅能处理以’\0’结尾的字符数组;
(4)sizeof计算的是实际占用内存空间的大小,strlen计算的是字符个数。
(5)数组做sizeof的参数不退化,传递给strlen会退化为指针。
16 C语言的关键字static和C++的关键字static有什么区别
C语言的static关键字可以修饰局部变量、全局变量、函数。
C++的static关键字除了局部变量、全局变量、函数之外,还可以修饰成员变量和成员函数。
17 volatile有什么作用
volatile通知编译器不要优化该变量,每次使用变量时,都从内存重新读取,而不是从寄存器读取备份。
常见的应用场景:
- 状态寄存器一类的并行设备硬件寄存器;
- 中断服务子程序会访问到的非自动变量;
- 多线程间被几个任务共享的变量。
18 一个参数可以既是const又是volatile吗
可以。const只是说明在此程序中该参数是只读的,并不是实际禁止这段内存的读写特性。它仍有可能在程序外部条件变化下改变。volatile通知编译器不要优化该变量,每次使用变量时,都从内存重新读取,而不是从寄存器读取备份。
19 全局变量和局部变量有什么区别?操作系统和编译器是怎么知道的?
区别:
(1)全局变量定义在所有函数之外,定义位置之后均可使用。生命周期从程序运行到结束。
(2)局部变量定义在代码块中,仅在该代码块中可见。生命周期从定义位置到代码块结束。
操作系统和编译器应是根据内存中存储位置判断的。全局变量位于全局数据区,在程序运行时被加载。局部变量位于栈区。
20 简述strcpy、sprintf与memcpy的区别
(1)功能区别:
- strcpy用于两个字符串之间的拷贝;
- sprintf用于将其他类型数据格式化为字符串;
- memcpy是在两块内存之间拷贝。
(2)操作对象区别:
- strcpy的两个操作对象均为字符串;
- sprintf的源对象可以是多种类型,目标对象是字符串;
- memcpy的两个操作对象均是内存地址
(3)效率区别:
memcpy效率最高,strcpy次之,sprintf效率最低。
21 请解析((void ()( ) )0)( )的含义
22 C语言的指针和引用和c++的有什么区别?
(1)C语言没有引用的概念。
(2)指针本身占用内存,引用则是其他对象的别名,不占用内存。
(3)sizeof作用于指针,结果为4。sizeof作用于引用,结果是其所指对象的大小。
(4)指针有顶层const和底层const,引用只有底层const。
(5)非顶层const的指针可以修改,引用则不能修改;
(6)指针可以二级,引用则只能有一级;
(7)通过指针操作所指对象需要先解引用,引用则可以直接操作。
(8)对指针执行加法,会造成所指内存地址偏移。对引用执行加法,会导致其所指对象的数值改变。
(9)如果返回动态内存分配的对象或内存,必须使用指针。引用可能导致内存泄露。
23 typedef和#define有什么区别
(1)typedef通常用于定义数据类型别名,以增强程序可读性。#define则常用于定义常量、宏函数等。
(2)typedef在编译阶段处理,#define则在预处理阶段替换。typedef会进行类型检查,#define没有类型检查;
(3)typedef有作用域限制,#define没有作用域限制,在其声明后的使用都是正确的。
(4)typedef和#define对指针的操作有很大区别。
24 指针常量与常量指针区别
指针常量是顶层const。指该指针本身是常量,不能修改其所指的地址,但可以通过该指针修改其所指内存中的数据。
常量指针是底层const。该指针所指的地址可以改变,但不能通过该指针修改其所指内存中的数据。
25 简述队列和栈的异同
队列和栈都是线性存储结构。区别在于队列是先进先出,栈是后进先出。
26 设置地址为0x67a9 的整型变量的值为0xaa66
int *p = (int *)0x67a9;
*p = 0xaa66;
在任何硬件平台上,地址长度和整型变量的长度都是相同的。因此,整型数据可以强制转换为地址使用。
27 C语言的结构体和C++的有什么区别
(1)C语言的结构体中不能定义成员函数;
(2)C语言的结构体中没有访问说明符,不存在权限的概念。
(3)C语言的结构体没有继承关系。
28 如何避免“野指针”
(1)定义指针时执行初始化。如不明确应指向哪个对象,则初始化为nullptr。
(2)指针所指的对象被delete释放后,应将指针赋值为nullptr。
(3)指针所指变量的作用域结束前,释放变量并将指针赋值为nullptr。
29 句柄和指针的区别和联系是什么?
Windows操作系统用句柄来表示系统资源,是unsigned int类型。指针则是指向内存地址。两者是不同的概念。
31 C++的顶层const和底层const ?
顶层const:对象本身是常量。
底层const:所指对象是常量。
以指针为例说明。
- 顶层const:指针指向的地址是固定的,但该地址中的数据可以修改。
- 底层const:指针指向的地址可以改变,但不能通过该指针来改变所指地址内保存的数据。
32 栈区和堆区的区别?栈区和堆栈的区别?
栈区由编译器分配、释放。主要用于存储局部变量、函数参数等。堆区由编程者手动申请、释放。主要用于动态内存操作。
栈区、堆区和堆栈是两个层面的概念。栈区、堆区指的是内存的分区,堆栈指的是一种数据结构。
33 ⾯向对象的三⼤特征是哪些?各⾃有什么样的特点?
- 封装
- 类中的成员仅对可信的类可见,对不可信的类隐藏
- 继承
- 可以使用基类的功能,无需修改基类即可进行功能扩展
- 多态
- 一个类实例的相同方法在不同情形下有不同的表现形式,使得不同内部结构的对象可以共享相同的外部接口
34 C++中类成员的访问权限
访问权限有public、protected、private三种。
在类内,无论是哪种访问权限,都可以相互访问。在类外,则仅能通过对象访问public部分。
派生类可以访问基类的protected部分,但其他用户无法访问。
35 多态的实现有哪⼏种?
静态多态:
- 通过函数重载和模板实现
- 在编译阶段确定
动态多态:
- 通过虚函数和继承实现
- 在运行时确定
36 动态多态有什么作⽤?有哪些必要条件?
基类的指针或引用可以指向派生类的对象,调用虚函数时,会调用到派生类的版本。
必要条件:
- 继承
- 虚函数
- 指向基类的指针或引用
37 动态绑定是如何实现的?
在类的最开始部分是指向虚函数表的指针vptr,其中存放虚函数的地址。虚函数实际存放在代码段。派生类会继承父类的虚函数表,并将其中的虚函数地址换成自己重写的虚函数的地址。在调用时,会根据虚函数表查找当前对应的版本,实现动态绑定。
38 纯虚函数有什么作⽤?如何实现?
纯虚函数是一种函数规范,要求继承该类的派生类都要实现该函数。
在虚函数参数列表后添加=0即可将函数声明为纯虚函数。
39 虚函数表是针对类的还是针对对象的?同⼀个类的两个对象的虚函数表是怎么维护的?
虚函数表是针对类的。
同一个类的对象共用同一个虚函数表。每个对象内部都保存着一个指向该类虚函数表的指针vptr。
40 为什么基类的构造函数不能定义为虚函数?
虚函数的调用要依赖虚函数表。在对象中,指向虚函数表的指针vptr是在构造函数中初始化的。如果将构造函数定义为虚函数,则会形成死循环。
41 为什么基类的析构函数需要定义为虚函数?
如果基类的析构函数不定义为虚函数,则是动态绑定。其派生类对象销毁时,调用基类的析构函数,不能保证资源正确释放。将基类的析构函数定义为虚函数,则在销毁派生类对象时会调用派生类的析构函数,保证资源正确释放。
42 构造函数和析构函数能抛出异常吗?
从语法上来说,构造函数是可以抛出异常的。但从逻辑上来说,由于可能导致内存泄露,不能在构造函数中抛出异常。
析构函数中不能抛出异常,原因如下:
- 析构函数中如果在异常点后有释放资源的代码,抛出异常后这些代码不再执行,会导致内存泄露。
- 在代码抛出异常时,会调用析构函数回收资源。如果此时析构函数再次抛出异常,相当于前次异常尚未处理,又抛出新异常,会导致程序崩溃。
43 如何让⼀个类不能实例化?
方法有两种:
- 将类定义为抽象类
- 将类的构造函数权限设置为private
44 多继承存在什么问题?如何消除多继承中的⼆义性?
多继承增大了代码的复杂程度,难以维护,容易出错。
命名二义性:如果基类之间或基类和派生类之间存在相同命名的成员,则使用时会出现命名二义性的问题。解决方式如下:
- 使用作用域运算符,显式指定使用哪个类的成员;
- 在派生类中定义同名成员,实现覆盖
路径二义性:如果派生类有多个基类,这些基类又有共同的基类,则调用该共同基类中的成员时,产生路径二义性的问题。解决方式如下:
- 使用作用域运算符,显式指定使用哪个类的成员;
- 在派生类中定义同名成员,实现覆盖
- 使用虚继承
45 如果类A是⼀个空类,那么sizeof(A)的值为多少?为什么?
sizeof(A)的值是1。编译器要区分空类的不同对象,分配一个字节,可以让各对象具有独一无二的地址。
46 覆盖和重载之间有什么区别?
覆盖要求函数的返回类型、函数名、参数列表完全一致。覆盖仅发生在成员函数。
重载不关注函数的返回类型,函数名必须一致,参数列表必须不同。重载可以发生在成员函数,也可以发生在普通函数。
47 拷⻉构造函数和赋值运算符重载之间有什么区别?
拷贝构造函数是
48 对虚函数和多态的理解
49 请你来说⼀下C++中struct和class的区别
50 说说强制类型转换运算符(类型转换、cast转换)
51 简述类成员函数的重写、重载和隐藏的区别
52 RTTI是什么?其原理是什么?
53 C++的空类有哪些成员函数
- 合成默认构造函数
- 合成拷贝构造函数
- 合成拷贝赋值运算符
- 合成析构函数