C/C++的面试题汇总_c++面试题
目录
1. 解释 C 语言中#include指令的作用、工作机制?
2. 解释 C 语言中#define的作用、应用场景?
3. 请解释 C 语言中预处理器中的 # 和 ## 操作符的作用及区别。
4. C语言的数据类型有哪些?
5. 数据类型及其字节大小,还有表示范围?
6. C语言中三种基本结构?
7. 全局变量和局部变量能否重名?
8. static关键字的作用?
9. extern关键字的作用?
10. 变量的作用域 ?
11. 什么是指针?
12. 什么是条件编译?
13. const和#define都可用于定义常量,它们有什么区别?
14. 什么是volatile关键字?
15. C 中的左值(Lvalues)和右值(Rvalues)?
16. 说一下什么是结构体(struct)和联合体(union)?
17. 内存对齐是什么?为什么要进行内存对齐?内存对齐有什么好处?
18. 变量的声明和定义有什么区别?
19. C 语言中函数参数的两种传递方式及其区别?
20. 请解释递归函数的概念,并说明其实现原理和关键注意事项?
21. 递归和循环在编程中如何选择?
22. inline函数有什么作用?
23. sizeof 和 strlen 的区别?
24. 解释大小端字节序的概念?写一个判断大小端序的程序
25. volatile和const能否一起使用?举例说明
26. 如何用C语言实现布尔类型?
27. 什么是野指针,使用野指针会有造成什么后果?
28. 什么是内存泄漏?内存泄露的原因有哪些?内存泄漏如何去避免和解决?
29. 解释数组指针和指针数组的区别。
30. 解释函数指针和指针函数的区别?
31. 解释指针常量和常量指针的区别?
32. 解释双重常量指针(const pointer to const data)的概念。
33. C语言中void关键字的用途是什么?
34. 请阐述 C 标准库函数 strcpy 与 memcpy 之间的主要区别。
35. 请简要说明 C 语言程序从源码到可执行文件的四个编译阶段?
36. C 语言中“数组名”与“指针”之间的区别。
37. 请简要列出 strcpy、strcat、strncat 与 strcmp 的核心用途。
38. memcpy 与 strcpy 的区别?
39. C 语言程序的内存模型(Memory Layout)及各段的作用。
40. 栈溢出的原因及解决方法?
41. new/delete和malloc/free的区别
42. 传入一个指针,free 是如何知道要释放多少空间的?
43. malloc的底层实现?
43.1 malloc的内存分配的方式,有什么缺点?
43.2为什么不全部使用mmap来分配内存?
43.3为什么不全部都用brk?
44. 在只有 1GB 物理内存的计算机中,能否成功执行 malloc(1.2GB)?为什么?
45. malloc、calloc和realloc有什么区别?
46. 什么是不可重入函数?
47. C 语言中的 restrict 关键字的作用。
48. 回调函数是什么,为什么要有回调函数?有什么优缺点?回调的本质是什么?
49. 函数调用的底层过程是怎样的?编译器/CPU 都做了什么?
50. C 和 C++ 有什么联系和区别?
51. 请解释面向对象三大特性——封装、继承、多态分别是什么,有什么作用?
52. C++中多态的实现原理是什么?
53. new的底层原理?
54. C++11 中 final 关键字的作用是什么?
55. C++ 中命名空间的作用是什么?
56. C++ 中的“引用”是什么,它用于哪些地方?
57. 什么是常量引用?
58. 指针和引用有什么区别?
59. C++ 中智能指针的本质是什么?它的实现原理是什么?创建方式有哪些?
60. 堆和栈在内存管理上有什么区别?
61. C++ 中有哪些类型的强制类型转换,它们有什么区别?
62. 函数参数传递时,指针、引用以及值传递有什么区别?
63. class和struct的区别?C++保留struct的原因
64. 请问一个类的对象在 C++ 中的生命周期是怎样的?
65. C++ 中虚函数和纯虚函数有什么区别?
66. C++ 中虚函数的实现原理是什么?虚函数表(vtable)和虚指针(vptr)在其中起什么作用?
67. C++ 中匿名函数(Lambda)的本质是什么?它的优点有哪些?
68. 请简单描述面向过程和面向对象的区别。
69. C++ 中的重载(Overload)、重写(Override)、隐藏(Hide)分别是什么?
70. 什么是 this 指针?为什么需要它?
71. 空对象指针(nullptr)为什么可以调用成员函数?
72. C++ 编译器是如何实现函数重载的?
73. 动态链接和静态链接的区别?
74. 请解释 extern \"C\" 关键字的作用及其存在的原因。
75. C++ 中空类的大小是多少?为什么?
76. C++ 中空类默认包含哪些函数?
77. C++ 中 explicit 关键字用在哪里?有什么作用?
78. C++ 中 delete 关键字有哪些用法?
79.delete 和 delete[] 的区别?
80.delete 与 free() 的区别?
81. C++ 中一个指针变量本身占用多少字节?和它指向的数据类型有关吗?
82. C++中左值和右值是什么?++i是左值还是右值,++i和i++哪个效率更高?
83. 请写出 bool、int、float 和指针变量与“零值”比较的 if 语句示例。
84. 请简要说说 C++ STL(标准模板库)的基本组成部分有哪些?
85. 请简要说一下STL的六大组件的作用?
86.std::map 和 std::unordered_map 的区别?
87. 什么是迭代器失效?它的常见原因和解决方法有哪些?
88. C++ 中 vector 的扩容机制是怎样的?resize 和 reserve 有什么区别?
89.std::vector 中的 push_back() 和 emplace_back() 有什么区别?各自适用于哪些场景?
90.std::vector 是如何判断是否需要扩容的?扩容的时机与 size 和 capacity 有什么关系?
91. C++ 中 std::vector 和 std::list 的底层实现原理,以及它们各自的优缺点。
92.std::map 和 std::set 的底层实现及其区别?
93.map 中 find、operator[] 和 at 三种访问元素的方法的异同?
94. 对比迭代器与指针的本质区别?
95. 什么是右值引用?为什么 C++ 要引入右值引用?
96. C++11中移动语义是什么意思?
97.std::move 和移动语义之间是什么关系?
98. 什么是完美转发(Perfect Forwarding)?它的原理是什么?
99. 友元函数的作用是什么?
100. 友元函数的继承性?
101. 友元类与友元函数的区别?
102. 父类的构造函数和析构函数是否能为虚函数?这样操作导致的结果?
103. 静态成员函数可以是虚函数吗?
104. 静态成员函数能否访问非静态成员?为什么?
105. 静态成员函数与类外普通函数的区别?
106. 静态成员变量的内存布局?
107. C++ 中,哪些函数不能被声明为虚函数?为什么?
108. C++ 中的深拷贝和浅拷贝有什么区别?
109. 类的普通成员函数和静态成员函数分别能否访问类内的静态变量和普通变量?为什么?
110. C++ 中,什么是运算符重载?哪些运算符是不能被重载的?
111. 如何设计一个类,使得它的对象只能在堆上创建,不能在栈上创建?
112. 在 C++ 项目中,如何编译和调用 C 语言代码?
113. C++ 中 Lambda 表达式的捕获列表有哪些捕获方式?它们各自有什么特点?
114. C++ 中拷贝构造函数和移动构造函数的区别?
115. 一个函数func(int a,int b),其中a和b的地址关系是什么?
116.讲一下单例设计模式?常见使用场景?
117.std::move 的底层是如何实现的?
118. 什么是尾递归?
119. 什么是函数调用约定(Calling Convention),它包含哪些内容?
120. 常见的调用约定举例?
121. 进程和线程的关系是什么?
122. 进程之间的通信方式有哪些?
123. 线程之间的通信方式有哪些?
124. C++中如何实现多进程?
125. 如何实现多线程?
126. 多线程会发生什么问题?线程同步有哪些手段?
127. 线程有哪些状态?线程锁有哪些?
128. 锁的底层实现原理是什么?请从操作系统或硬件层面简要说明。
129. 什么是原子操作?它在多线程编程中起什么作用?
130. 什么是死锁?多线程为什么会发生死锁?
131. 死锁产生的条件有哪些?如何解决死锁?
132. 如何实现线程安全?除了加锁,还有哪些方式可以实现线程安全?
133. C 语言常用库函数分类总结
134. C++ 常用库函数分类总结
135. 常用 C 语言第三方库
136. 常用 C++ 语言第三方库
补充:
1. C++ 中 public、protected 和 private 有什么区别?
2. 继承方式有哪些?
3. 当一个子类中有对象成员时,他们的构造和析构顺序是怎样的?
4. 什么是菱形继承?菱形继承存在哪些问题?如何解决?
5.什么是抽象基类?抽象基类的特点?
6. 说一下map的插入方式?
7. 什么是适配器?
8. 什么是函数对象(仿函数)?
9. string如何和c-style字符串相互转换?
10. vector删除元素,它的迭代器是如何变化的?
11. 你了解哪些迭代器?
12. 说一下类的内存布局?
13. C++模板的作用?
14. 拷贝构造和“=”赋值运算的区别?
15. 如果子类中包含其他成员对象,构造和析构的调用顺序是怎样的?
16. map、set怎么实现?红黑树为什么能同时实现这两种容器?为什么使用红黑树?
17. string和C字符串如何相互转换?
18. C语言中字符串可用什么标准库函数将其转换成 int 型?
19. C++中,一个空类的 sizeof 结果是多少?哪些情况会影响空类的 sizeof 结果?
20. 析构函数中抛出异常会怎样?怎么避免呢?
21. C++11引入了大量新特性,请列出你了解的?
22.说一下 C / C++ 中的断言?
23.说一下C语言中关键字static和C++中关键字static有什么区别?
24.内存泄漏和内存溢出的区别?
25.了解栈的工作原理吗?
26.什么是迭代器?
27.C++中迭代器是怎么用的?
28. C++中如何实现一个定时器?
29. 堆的删除操作如何实现?
30. lambda表达式实现原理?
31. 你能说说单链表、双向链表、多级链表的区别吗?
32. 说一下快速排序的思想?
33. 进程通信方式
34. 线程通信/同步机制
35. 线程池大小是怎么确定的?为什么不是越多越好?
36. 定义一个MIN的宏,然后传入两个参数,返回较小的哪一个
37.main()函数原型有哪些?
38. 在 C 语言中,main 函数的参数 argc 和 argv 是什么含义?其中 argc 最小可能是多少?为什么?
39. 没有初始化全局变量和局部变量它的默认值是多少?
40. 怎么理解一个变量的声明和定义?
41. static修饰全局变量和局部变量的区别?
42. static修饰全局变量和全局变量的区别?
43. 函数能不能加static?
44. 求字符串长度的函数?字符串比较用什么函数?
45. 日志等级有哪些?日志文件怎么导出???
参考文章和AI工具:
1. 解释 C 语言中#include
指令的作用、工作机制?
#include
是 C 语言的预处理指令,用于将指定文件的内容插入到当前文件中。其工作机制是:在编译前,预处理器会替换
#include
或#include \"file\"
为文件的实际内容。尖括号用于包含标准库头文件(如
stdio.h
),编译器会在系统指定目录(如/usr/include
)中搜索;双引号用于包含用户自定义头文件,优先在当前目录查找。
2. 解释 C 语言中#define
的作用、应用场景?
#define
是一个预处理指令,用于创建宏(Macro)。宏是一种文本替换机制,在编译前由预处理器对源代码进行处理,将宏名称替换为对应的替换文本。其核心作用包括:
1)定义常量(如
#define PI 3.14
),提高代码可读性;2)创建函数式宏(如
#define MAX(a,b) ((a)>(b)?(a):(b))
),避免函数调用开销;3)条件编译(如
#ifdef DEBUG
),控制代码块是否参与编译;4)简化复杂语法(如
#define BEGIN {
)。
3. 请解释 C 语言中预处理器中的 #
和 ##
操作符的作用及区别。
在 C 语言的宏定义中,
#
用于将宏参数转换为字符串,称为“字符串化操作符”;
##
是宏参数的连接操作符,用于把两个标记(token)拼接成一个新标记。简单来说,
#
是把参数变成\"参数\"
字符串,##
是把两个参数连成一个新变量名或关键字。#define STR(x) #x
#define JOIN(a, b) a##bSTR(abc) // 结果是 \"abc\"
JOIN(var, 1) // 结果是 var1
4. C语言的数据类型有哪些?
基本数据类型:它们是算术类型,包括整型(int)、字符型(char)、浮点型(float)和双精度浮点型(double)。
枚举类型:它们也是算术类型,被用来定义在程序中只能赋予其一定的离散整数值的变量。
void类型:类型说明符 void 表示没有值的数据类型,通常用于函数返回值。
派生类型:包括数组类型、指针类型和结构体类型。
5. 数据类型及其字节大小,还有表示范围?
类型 关键字 字节大小(典型) 有符号范围 无符号范围 短整型 short
2 字节 -32,768 ~ 32,767 0 ~ 65,535 整型 int
4 字节 -2,147,483,648 ~ 2,147,483,647 0 ~ 4,294,967,295 长整型 long
4 字节(32 位)
8 字节(64 位)取决于平台 取决于平台 更长整型 long long
8 字节 -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 0 ~ 18,446,744,073,709,551,615 类型 关键字 字节大小 精度范围 单精度浮点 float
4 字节 约 6-7 位有效数字,范围 ±3.4×10^38 双精度浮点 double
8 字节 约 15-16 位有效数字,范围 ±1.8×10^308 扩展双精度 long double
8/10/16 字节 取决于平台,精度高于 double
类型 关键字 字节大小 范围 有符号字符 signed char
1 字节 -128 ~ 127 无符号字符 unsigned char
1 字节 0 ~ 255 普通字符 char
1 字节 有符号或无符号取决于编译器(通常有符号)
6. C语言中三种基本结构?
C 语言的三种基本结构是顺序结构、选择结构和循环结构。
顺序结构:程序按照语句的先后顺序依次执行,从上到下,从左到右,直到程序结束。
选择结构:选择结构也称为分支结构,它允许程序根据条件的真假来选择执行不同的代码块。
循环结构:程序在满足特定条件的情况下重复执行一段代码。C 语言提供了三种主要的循环结构:
while
循环、do-while
循环和for
循环。
7. 全局变量和局部变量能否重名?
可以,局部变量会屏蔽同名的全局变量。访问全局变量需使用作用域运算符
::
(C++特性)或外部声明extern
8. static
关键字的作用?
1.修饰局部变量时使其生命周期延长至程序结束;
2.修饰全局变量或函数时限制其作用域为当前文件
9. extern关键字的作用?
extern
是 C/C++ 中的关键字,用于声明一个变量或函数是在别的文件或作用域中定义的,告诉编译器这个标识符的定义在别处,但我现在想引用它。
10. 变量的作用域 ?
全局变量:在所有函数体外部定义的,程序所在部分都可以使用,不受作用域的影响(生命期一直到程序的结束)
局部变量:局限于作用域内,默认为auto关键字修饰,即进入作用域时自动生成,离开作用域时自动消失;
局部变量可以和全局变量重名,在局部变量作用域范围内,全局变量失效,采用的是局部变量的值
11. 什么是指针?
指针是一个变量,它存储另一个变量的内存地址。通过指针可以间接访问和操作该变量的值。
12. 什么是条件编译?
条件编译是指根据预处理器指令(如
#ifdef
、#ifndef
、#if
等)来决定是否编译某段代码。
13. const
和#define
都可用于定义常量,它们有什么区别?
const
#define
实现方式 由编译器处理,创建只读变量。 由预处理器处理,文本替换(宏)。 内存分配 存储在内存中(栈、全局区或常量区)。 不分配内存,编译时直接替换文本。 类型安全 有明确的数据类型,编译器会检查类型匹配。 无类型信息,仅做文本替换,可能导致类型不匹配。 作用域 取决于定义位置(局部 / 全局)。 全局有效(从定义到文件结束或 #undef
)。只读性 理论上不可修改,但可通过指针绕过(未定义行为)。 不可修改,替换后无法更改。
14. 什么是volatile
关键字?
volatile
是一个类型修饰符,表示变量可能被意外修改(如硬件寄存器、多线程共享变量),禁止编译器优化,确保每次访问都从内存读取,而不是使用缓存或寄存器中的副本。
15. C 中的左值(Lvalues)和右值(Rvalues)?
左值:代表内存中的对象,有地址,可修改(除非被
const
修饰)。右值:代表临时值或表达式结果,无持久地址,不可直接赋值。
16. 说一下什么是结构体(struct)和联合体(union)?
结构体:是一种聚合数据类型,它将多个不同类型的变量组合在一起,每个成员占用独立的内存空间。结构体的大小是所有成员大小之和(可能包含填充字节以满足对齐要求)。
联合体:也是一种聚合数据类型,但所有成员共享同一块内存空间。联合体的大小由最大的成员决定,同一时间只能存储一个成员的值。修改一个成员会覆盖其他成员的数据。
17. 内存对齐是什么?为什么要进行内存对齐?内存对齐有什么好处?
内存对齐是处理器为了提高处理性能而对存取数据的起始地址所提出的一种要求。
有些CPU可以访问任意地址上的任意数据,而有些CPU只能在特定的地址访问数据,因此不同硬件平台具有差异性,这样的代码就不具有移植性,如果在编译时将进行对齐,这就具有平台的移植性。CPU每次寻址有时需要消耗时间的,并且CPU访问内存的时候并不是逐个字节访问,而是以字长为单位访问,所以数据结构应该尽可能地在自然边界上对齐,如果访问未对齐内存,处理器需要做多次内存访问,而对齐的内存访问可以减少访问次数,提升性能。
优:提高程序的运行效率,增强程序的可移植性。
18. 变量的声明和定义有什么区别?
变量的声明仅向编译器说明变量的类型和名称,不分配内存,可多次声明。而定义会为变量分配内存空间,且整个程序中只能定义一次。
19. C 语言中函数参数的两种传递方式及其区别?
C 语言中函数参数的传递方式分为值传递和地址传递。 值传递是将实参的副本传递给函数,函数内部对形参的修改不影响原始实参;而地址传递是将实参的内存地址(指针)传递给函数,函数可通过解引用直接操作原始数据。
20. 请解释递归函数的概念,并说明其实现原理和关键注意事项?
递归函数是指在函数定义中直接或间接调用自身的函数。
其核心思想是将复杂问题分解为规模更小的同类子问题,直到达到终止条件(递归基)。
递归函数包含两个关键部分:递归条件(调用自身的逻辑)和终止条件(停止递归的边界)。
21. 递归和循环在编程中如何选择?
问题本质:如果问题天然递归(比如树遍历、分治算法、汉诺塔),递归写起来更清晰、直观。
性能和空间:循环通常比递归节省栈空间,递归调用会占用额外的栈空间,可能导致栈溢出。
可读性和维护性:递归代码简洁易懂,适合复杂的嵌套逻辑;循环代码通常执行效率更高,适合简单的迭代。
语言和编译器支持:某些语言支持尾递归优化,递归不会带来性能损失,反之则可能不适合深度递归
22. inline函数有什么作用?
如果一些较短的函数被频繁的调用,不断地有函数入栈和出栈,会造成栈空间的大量消耗以及降低程序执行效率,这时就可以使用内联函数。在函数定义时使用 inline 关键字进行修饰可以把函数定义为内联函数,编译器会把内联函数内的代码直接插入到函数调用的位置,减少了函数调用的开销。
23. sizeof 和 strlen 的区别?
sizeof
是编译期运算符,返回数据类型或变量占用的内存字节数(如sizeof(\"abc\")
为 4,包含\\0
);
strlen
是运行期库函数,返回以\\0
结尾的字符串长度(如strlen(\"abc\")
为 3)。
sizeof
可用于任何数据类型(如sizeof(int)
),而strlen
仅适用于字符串。需注意:对指针使用
sizeof
返回指针大小(如char* p = \"abc\"; sizeof(p)
为 8),而非字符串长度。
24. 解释大小端字节序的概念?写一个判断大小端序的程序
大小端指多字节数据在内存中的存储顺序。
大端序将高位字节存于低地址(如 0x1234 存储为 0x12|0x34),小端序则将低位字节存于低地址(如 0x34|0x12)。
#include int main() { unsigned int x = 1; char *c = (char*)&x; if (*c) { printf(\"Little Endian\\n\"); } else { printf(\"Big Endian\\n\"); } return 0;}
解释:
-
unsigned int x = 1;
创建一个整型变量x
,其值为 1。 -
(char*)&x
将x
的地址强制转换为char*
类型(1字节指针),以便检查最低有效字节的内容。 -
if (*c)
如果第一个字节是1
,说明最低有效字节存储在低地址——小端。
如果是0
,则最低有效字节存储在高地址——大端。
25. volatile
和const
能否一起使用?举例说明
可以。例如硬件寄存器声明:
const volatile uint32_t *reg = 0x1234;
,表示寄存器地址不变但值可能随时变化
26. 如何用C语言实现布尔类型?
C99之前用
typedef enum {false, true} bool;
,C99之后可直接包含使用
bool
27. 什么是野指针,使用野指针会有造成什么后果?
野指针是指指针变量指向了一块非法或不可预知的内存区域,这通常发生在指针未初始化,或者指向的内存已经被释放之后仍然使用该指针。
使用野指针会导致程序行为不可预测,比如崩溃、数据错误甚至安全漏洞。
28. 什么是内存泄漏?内存泄露的原因有哪些?内存泄漏如何去避免和解决?
内存泄漏:指的是程序运行过程中动态分配的内存未被正确释放,导致该内存无法被程序继续使用,也无法被操作系统回收,从而导致可用内存不断减少,最终可能导致程序崩溃或系统变慢。
泄漏原因:1.忘记释放内存;2.指针丢失(悬空指针);3.重复分配未释放;4.在异常处理时未释放内存;
如何避免:1.使用free释放内存;2.避免丢失指针(在释放之前,不要覆盖指向已分配内存的指针,否则无法
free
。);3.使用智能指针(C++中)
29. 解释数组指针和指针数组的区别。
数组指针是指向数组的指针,语法为类型
(*指针名)[数组大小]
(如int (*p)[5]
),用于指向包含特定元素个数的数组,常见于二维数组传参;指针数组是存储多个指针的数组,语法为
类型 *数组名[数组大小]
(如int *p[5]
),用于存放多个相同类型的指针。
30. 解释函数指针和指针函数的区别?
函数指针(function pointer) 是一种指针,指向函数的地址,可以通过它调用函数。
指针函数(pointer function) 是一种函数,返回值是一个指针。
函数指针常用于实现回调机制或函数数组,比如排序时自定义比较函数传入
qsort
;指针函数则常用于返回数组指针或动态申请的内存地址。
31. 解释指针常量和常量指针的区别?
常量指针(pointer to const):(常量的指针)指针指向的内容是常量,不能通过该指针修改内容,但指针本身可以指向别的地址。
指针常量(const pointer):(指针是常量)指针本身是常量,不能改变它指向的地址,但可以通过它修改所指内容。
语法上,
const
是修饰它左边的东西(如果左边没有,则修饰右边),所以:
const int *p
,const
修饰int
,表示“常量指针”。
int * const p
,const
修饰p
,表示“指针常量”。
32. 解释双重常量指针(const pointer to const data)的概念。
是指指针本身的值和它所指向的数据都不可被修改的指针。语法:const int* const p
33. C语言中void
关键字的用途是什么?
函数无返回值(如
void func()
);函数参数为空(如
int main(void)
);通用指针类型(
void*
可指向任意数据类型)
34. 请阐述 C 标准库函数 strcpy
与 memcpy
之间的主要区别。
strcpy
用于复制以\'\\0\'
结尾的字符串,它会从源字符串开始逐字节复制直到遇到第一个空字符,并在目标缓冲区中自动添加终止符。
memcpy
则按指定的字节数直接从源地址复制内存块,不检查内容是否包含空字符或字符串终止符,因此可用于复制任意类型的数据,并且通常比逐字检查终止符的strcpy
更高效。
35. 请简要说明 C 语言程序从源码到可执行文件的四个编译阶段?
预编译:由预处理器展开
#include
、宏替换、条件编译并移除注释。输出.i文件编译:编译器将预处理后的文件翻译成汇编语言,进行词法、语法与语义分析,并可选地做代码优化。输出汇编代码
.s
文件汇编:汇编器将汇编文件转换为机器码,生成.o文件
链接:链接器将一个或多个目标文件及所需的静态或动态库合并,解析所有符号引用,并插入运行时启动/终止代码。生成可执行.exe文件
36. C 语言中“数组名”与“指针”之间的区别。
数组名是编译时固定分配的一段连续内存空间的标签,其类型为“数组”,并在大多数表达式中会隐式转换(退化)为指向首元素的常量指针,但不能对其重新赋值(例如
arr = ptr;
会编译报错)。指针则是一种变量,用于存储地址,可在运行时修改其指向,并且其自身大小与所指向数据的长度无关。
使用
sizeof
操作时,sizeof(arr)
返回整个数组所占的字节数,而sizeof(p)
返回指针本身的字节数;
37. 请简要列出 strcpy
、strcat
、strncat
与 strcmp
的核心用途。
strcpy
:拷贝整个字符串(Replace)
strcat
:拼接字符串(Append)
strncat
:拼接但限制长度(Safer Append)
strcmp
:判断两个字符串是“等于、前、小”关系
38. memcpy
与 strcpy
的区别?
memcpy
是通用的内存拷贝函数,可以复制任意类型的数据,包括字符串、结构体、二进制块等,它根据指定的字节数进行复制,不会检查内容;
strcpy
是专用于字符串复制的函数,它会从源地址开始,一直复制到遇到字符串结束符\\0
为止,适用于以\\0
结尾的字符数组。
39. C 语言程序的内存模型(Memory Layout)及各段的作用。
C语言中内存布局(内存模型)是怎样的?-CSDN博客
40. 栈溢出的原因及解决方法?
原因:
1、局部数组变量空间太大;
2、函数出现无限递归调用或者递归层次太深。解决方法:
1、减少局部变量大小或动态内存分配
2、调整栈空间大小(谨慎使用)
3、优化递归调用
41. new/delete和malloc/free的区别
1.malloc/free是C/C++的库函数,需要stdlib.h;new/delete是C++的关键字;
2.都可用于申请动态内存和释放内存,new/delete在对象创建的时候自动执行构造函数,对象消亡前自动执行析构函数,底层实现其实也是malloc/free
3.new无需指定内存块的大小,编译器会根据类型信息自行计算;malloc需要显式地支持所需内存的大小
4.new返回指定类型的指针,无需进行类型转换;malloc默认返回类型为void*,必须强行转换为实际类型的指针
5.new内存分配失败时会抛出bad_alloc异常;malloc失败时返回NULL
42. 传入一个指针,free 是如何知道要释放多少空间的?
当我们使用
malloc
申请内存时,系统实际上会额外分配一小段内存(通常是 16 字节,具体取决于实现和平台)用于存储该内存块的元信息,例如分配大小、使用状态等。这段信息被保存在返回给用户的指针之前。
当调用free(ptr)
时,系统会将指针向前偏移,读取这段元数据,从而得知该块的实际大小和管理信息,并据此完成内存回收操作。
43. malloc的底层实现?
malloc 底层实现主要是通过维护一块堆内存,将这块内存划分成多个小块,每个小块前面会有元数据记录大小和使用状态;当申请内存时,malloc 会从空闲块链表里找到合适大小的块,必要时拆分块返回给用户;释放时,会将空闲块合并减少碎片;底层通过系统调用(如 brk 或 mmap)来申请或回收大块内存,实现高效动态内存管理。
43.1 malloc的内存分配的方式,有什么缺点?
malloc并不是系统调用,而是C库中的函数,用于动态内存分配,在使用malloc分配内存的时候会有两种方式向操作系统申请堆内存
方式1:当用户分配的内存小于128KB时通过brk()系统调用从堆分配内存,实现方式:将堆顶指针向高地址移动,获取内存空间,如果使用free释放空间,并不会将内存归还给操作系统,而是会缓存在malloc的内存池中,待下次使用
方式2:当用户分配的内存大于128KB时通过mmap()系统调用在文件映射区域分配内存,实现方式为:使用私有匿名映射的方式,在文件映射区分配一块内存,也就是从文件映射区拿了一块内存,free释放内存的时候,会把内存归还给操作系统,内存得到真正释放
缺点:容易造成内存泄漏和过多的内存碎片,影响系统正常运行,还得注意判断内存是否分配成功,而且内存释放后(使用free函数之后指针变量p本身保存的地址并没有改变),需要将p的赋值为NULL拴住野指针。
43.2为什么不全部使用mmap来分配内存?
因为向操作系统申请内存的时候,是要通过系统调用的,执行系统调用要进入内核态,然后再回到用户态,状态的切换会耗费不少时间,所以申请内存的操作应该避免频繁的系统调用,如果都使用mmap来分配内存,等于每次都要执行系统调用。另外,因为mmap分配的内存每次释放的时候都会归还给操作系统,于是每次mmap分配的虚拟地址都是缺页状态,然后在第一次访问该虚拟地址的时候就会触发缺页中断。
43.3为什么不全部都用brk?
如果全部使用brk申请内存那么随着程序频繁的调用malloc和free,尤其是小块内存,堆内将产生越来越多的不可用的内存碎片。
44. 在只有 1GB 物理内存的计算机中,能否成功执行 malloc(1.2GB)
?为什么?
在只有 1GB 物理内存的计算机上,执行
malloc(1.2GB)
有可能成功,因为现代操作系统使用虚拟内存机制,malloc
分配的是虚拟地址空间,不会立刻分配实际的物理内存,而且大多数系统允许内存超分配(overcommit)。但如果实际访问这块内存时,系统无法提供足够的物理内存或 swap,就可能导致程序被操作系统终止。因此,malloc
成功并不等于真的能用上那么多内存。
45. malloc、calloc和realloc有什么区别?
malloc
用于分配指定字节数的未初始化内存,返回一个void*
指针;
calloc
也是内存分配函数,但会额外将分配的内存全部初始化为 0,适用于需要干净内存的场景;
realloc
用于调整已分配内存的大小,可以扩展或缩小原有内存,如果内存不足,可能会搬移数据到新地址。例如:
int* p1 = (int*)malloc(10 * sizeof(int)); // 分配10个int,不初始化
int* p2 = (int*)calloc(10, sizeof(int)); // 分配10个int,全部置0
p1 = (int*)realloc(p1, 20 * sizeof(int)); // 扩展/缩小为20个int
46. 什么是不可重入函数?
不可重入函数是指在同一时间不能被多个线程或多次中断安全调用的函数。它们通常依赖于共享的全局变量、静态变量或非线程安全的资源,如果在执行过程中被中断或被另一个线程同时调用,可能会引发数据错误或程序崩溃。常见的不可重入函数有
strtok()
、malloc()
、printf()
等。
47. C 语言中的 restrict
关键字的作用。
restrict
是 C99 标准引入的关键字,用于指针声明中,告诉编译器:通过这个指针访问的内存地址,在它的生命周期内不会被其他指针访问或修改。这给了编译器优化空间,可以生成更高效的代码。但如果违反了这个约定(即多个指针指向同一块内存),行为就是未定义的。
48. 回调函数是什么,为什么要有回调函数?有什么优缺点?回调的本质是什么?
是什么:回调函数是将函数作为参数传入另一个函数中,在某个时刻由后者调用的函数。
为什么:调用方和被调用逻辑独立,便于复用和维护。
优点:提高代码复用性和模块化程度;
缺点:程序流程不直观;多层回调嵌套导致“回调地狱”
本质:函数指针机制
49. 函数调用的底层过程是怎样的?编译器/CPU 都做了什么?
1.参数传递
小型数据通常通过寄存器传递(如 x86 的
eax, ecx
,ARM 的r0-r3
)参数过多或结构体等复杂数据会通过栈传递
2.保存上下文
保存当前函数的返回地址(保存在栈上,或
ra
寄存器中)保存部分寄存器值(如调用者/被调用者保存约定)
3.跳转到目标函数地址执行
4.建立栈帧(函数的局部执行环境)
为局部变量、临时变量开空间
保存旧的栈帧指针(
ebp
/rbp
)5.函数执行结束时清理栈帧、恢复现场
6.跳回原函数继续执行
小提示(面试亮点):
不同平台和编译器有不同的调用约定(如
cdecl
、stdcall
、fastcall
),它们决定参数从左到右还是右到左压栈、谁负责清理栈、是否使用寄存器等。
50. C 和 C++ 有什么联系和区别?
C++ 是在 C 的基础上发展而来的,是 C 的超集。
C++ 保留了 C 的全部语法和特性,同时引入了面向对象编程(OOP)、模板、异常处理、STL 等高级功能。
C 强调过程化编程,适合底层开发;C++ 既支持过程化又支持面向对象,更适合复杂系统设计。
51. 请解释面向对象三大特性——封装、继承、多态分别是什么,有什么作用?
封装是把数据(属性)和操作(方法)封装在类中,并通过访问控制(如
private
,public
)来隐藏内部实现。继承是子类复用父类的属性和行为,提高代码复用性;其中构造函数、析构函数、友元函数、静态数据成员、静态成员函数都不能被继承。
多态则是通过统一接口表现出不同的实现方式,分为编译时多态(静态)和运行时多态(动态)。
52. C++中多态的实现原理是什么?
多态分为动态多态(动态多态是利用虚函数实现运行时的多态,即在系统编译的时候并不知道程序将要调用哪一个函数,只有在运行到这里的时候才能确定接下来会跳转到哪一个函数。)和静态多态(又称编译期多态,即在系统编译期间就可以确定程序将要执行哪个函数)。
动态多态是通过虚函数实现的,虚函数是类的成员函数,存在存储虚函数指针的表叫做虚函数表,虚函数表是一个存储类成员虚函数的指针,每个指针都指向调用它的地方,当子类调用虚函数时,就会去虚表里面找自己对应的函数指针,从而实现“谁调用、实现谁”从而实现多态。
静态多态则是通过函数重载(同一作用域中,多个函数名称相同,参数不同(类型、数量、顺序)),模板(模板在编译阶段根据传入的类型生成具体代码,属于“泛型静态多态”。)和 CRTP(Curiously Recurring Template Pattern,一种高级用法,让基类在编译时调用派生类的行为。)等机制在编译期实现的多态。
53. new的底层原理?
new
操作符底层其实分两步完成:1.调用内存分配函数:首先调用的是
operator new
函数(注意,它是一个函数而不是关键字),这个函数默认会调用malloc
来申请足够大小的堆内存。2.调用构造函数:分配到内存后,
new
会在这块内存上调用对应对象的构造函数,完成对象初始化。所以,
new
比malloc
更高级,不仅分配内存,还自动初始化对象。
54. C++11 中 final
关键字的作用是什么?
用于类的声明后面,表示这个类不能被继承,适用于某个类作为“最终实现”时使用;
用于虚函数后面,表示这个函数不能在派生类中被重写,用来防止接口被误修改。
55. C++ 中命名空间的作用是什么?
命名空间(
namespace
)是 C++ 为了解决名字冲突(命名污染)问题而引入的一种机制。它的作用主要有以下几点:
避免名称冲突:在大型项目或多个库共同使用时,可能会出现函数或变量同名的问题。命名空间可以将标识符封装在自己的作用域内,从而避免冲突。
提高代码可读性和组织性:可以将相关功能划分到不同的命名空间中,增强模块化设计,逻辑更清晰。
支持嵌套与别名:命名空间可以嵌套定义,也支持通过
namespace alias
创建简写,提高代码简洁度。
56. C++ 中的“引用”是什么,它用于哪些地方?
引用(reference)是某个变量的一个别名,它本质上是对原变量的一个间接访问方式。引用必须在定义时初始化,并且一旦绑定到一个对象后,就不能再改变绑定关系。
1.用于函数参数传递,避免值拷贝带来的性能开销,同时支持通过参数修改原始数据;
2.用于函数返回值,避免临时对象或多次拷贝。
3.在类设计中,引用还可以用于实现运算符重载、链式调用等高级特性
57. 什么是常量引用?
常量引用(const reference)是引用一个不可被修改的对象。语法:const int& ref
58. 指针和引用有什么区别?
指针和引用都可以实现对变量的间接访问,但它们有几个核心区别:
是否可为空:指针可以为
nullptr
,引用必须绑定到一个有效对象。是否可更改指向:指针可以在运行过程中改变指向不同的地址,而引用一旦初始化就不能再更改绑定。
语法使用差异:引用的语法更简洁,使用时不需要解引用符
*
,更接近直接使用变量。是否支持运算:指针可以进行地址运算(如偏移),引用不支持。
底层实现:引用在底层通常通过指针实现,但编译器会对其进行优化,使其更安全、效率更高。
59. C++ 中智能指针的本质是什么?它的实现原理是什么?创建方式有哪些?
智能指针的本质是一个类对象,它封装了普通指针,并通过析构函数自动释放资源,从而实现资源的自动管理(RAII),防止内存泄漏。
C++11 中主要有三种智能指针:
std::unique_ptr
、std::shared_ptr
和std::weak_ptr
,它们的核心区别在于所有权管理。实现原理简述:
RAII 机制
智能指针是类,构造时接管资源,析构时释放资源,因此生命周期由作用域控制,符合 RAII 原则。unique_ptr(独占所有权)
内部只保存一个原始指针。
禁止拷贝,只允许移动(move semantics)。
析构时自动调用
delete
释放资源。shared_ptr(共享所有权)
内部维护一个引用计数控制块(control block)。
每次拷贝引用计数 +1,析构 -1。
当引用计数为 0 时,释放资源。
weak_ptr(弱引用)
不影响引用计数。
与
shared_ptr
共享控制块。用于解决循环引用问题,需调用
lock()
转换成shared_ptr
才能访问资源。如何初始化?
它们都可以通过构造函数或推荐的
std::make_xxx
方式来初始化。
60. 堆和栈在内存管理上有什么区别?
分配方式:
栈由编译器自动分配和释放,主要用于存储函数的局部变量、参数等;
堆需要程序员手动申请(如
malloc
或new
),并且需要显式释放(如free
或delete
)。生命周期:
栈上的内存在函数调用结束后自动释放,生命周期短;
堆上的内存手动控制释放,生命周期更灵活,直到程序释放它或程序结束。
大小和速度:
栈空间通常较小,访问速度快;
堆空间较大,但分配和释放相对慢,容易产生碎片或内存泄漏。
错误风险:
栈可能导致“栈溢出”(如深递归);
堆容易产生内存泄漏、野指针等问题。
61. C++ 中有哪些类型的强制类型转换,它们有什么区别?
static_cast:
用于大多数普通类型转换,比如基本类型之间(int→float)、类层次结构中向上或安全的向下转换;
编译期检查类型安全,速度快,适用于“明确、合法”的类型转换。
dynamic_cast:
专用于有虚函数的多态类之间的转换;
通常用于向下转型(父类转子类),在运行时会进行类型检查,失败时返回
nullptr
(用于指针)或抛出异常(用于引用);有运行时开销。
const_cast:
用于去除 const/volatile 限定符;
比如用于将
const char*
转为char*
,适用于需要修改只读参数的情况(注意:若原本是只读对象,修改会引发未定义行为)。
reinterpret_cast:
用于底层位模式的强制解释;
可以在指针类型之间、指针和整数之间转换,适用于非常规、低层次的操作;
危险、平台相关,慎用。
62. 函数参数传递时,指针、引用以及值传递有什么区别?
值传递:复制实参的值给形参,函数内部操作的是副本,不影响原始值。
引用传递:形参是原始实参的别名,函数内部操作影响原始值。
指针传递:形参是原始实参的地址,函数内部通过解引用操作影响原始值。
63. class和struct的区别?C++保留struct的原因
“在 C++ 中,
class
和struct
的核心区别在于:
默认访问权限:
class
的成员默认是private
,而struct
的成员默认是public
。默认继承权限:
class
默认private
继承,而struct
默认public
继承。此外,两者均可用于定义类和模板参数(
template
),但语法上template
是非法的(需用class
或typename
)。C++ 保留
struct
主要是为了兼容 C 语言,并通过语义区分简单数据结构(struct
)和复杂对象(class
)。”
64. 请问一个类的对象在 C++ 中的生命周期是怎样的?
C++ 类对象的生命周期完全由构造函数和析构函数控制,其行为取决于对象的存储类型和创建方式。
不同存储类型的对象生命周期
全局对象(Global Object)
创建时机:程序启动时(在
main()
函数执行前),由系统自动构造。销毁时机:程序结束时(
main()
函数返回后),按构造的逆序析构。静态对象(Static Object)
作用域内静态对象:
创建时机:首次进入定义该对象的作用域时构造(如函数内的
static
对象)。销毁时机:程序结束时析构(与全局对象同阶段)。
类静态成员对象:
创建时机:类首次被使用时(如调用类方法、访问静态成员)构造。
销毁时机:程序结束时析构。
局部对象(Local Object)
创建时机:进入其作用域时(如函数体、代码块
{}
)构造。销毁时机:退出作用域时析构(按构造的逆序)。
动态对象(Dynamic Object)
创建时机:使用
new
运算符时构造。销毁时机:使用
delete
运算符时析构;若未显式释放,程序结束时由系统回收内存(但析构函数不一定执行,存在内存泄漏风险)。
65. C++ 中虚函数和纯虚函数有什么区别?
虚函数(
virtual
function)是指在基类中使用virtual
关键字声明的成员函数,允许在继承体系中实现运行时多态。当通过基类指针或引用调用时,会根据实际对象类型动态决定调用哪个版本的函数。纯虚函数(pure virtual function)是在基类中声明但不提供实现的虚函数,其语法是在函数声明末尾加
= 0
。一个类只要包含一个纯虚函数,就是抽象类,不能被实例化,只能被继承和实现。
66. C++ 中虚函数的实现原理是什么?虚函数表(vtable)和虚指针(vptr)在其中起什么作用?
在 C++ 中,为了实现运行时多态,编译器通过**虚函数表(vtable)和虚指针(vptr)**来支持虚函数机制。
vtable(虚函数表)
每个包含虚函数的类,编译器会生成一张虚函数表(vtable),表中是该类所有虚函数的函数指针;
子类如果重写了虚函数,表中对应项会被替换为子类的实现;
表的生成由编译器自动完成,程序员无法直接访问。
vptr(虚指针)
每个对象在内存中会隐藏一个虚指针(vptr),指向该类的 vtable;
虚指针通常在对象构造时由编译器自动初始化;
通过基类指针调用虚函数时,编译器实际是通过 vptr 查找 vtable 中的函数地址,从而实现动态绑定。
67. C++ 中匿名函数(Lambda)的本质是什么?它的优点有哪些?
匿名函数通常指的是 Lambda 表达式,它的本质是一个由编译器自动生成的类(闭包类)对象,该对象重载了
operator()
,使其像函数一样可以被调用。Lambda 的优点:
简洁灵活:可以就地定义短小函数,避免写冗长的函数名或类;
可捕获上下文变量:支持按值/引用捕获外部变量,实现闭包功能;
便于回调或算法使用:在
std::sort
、std::for_each
等算法中使用非常方便;类型推导支持强:搭配
auto
和泛型编程使用流畅;性能好:比传统函数指针更高效(编译器内联优化能力更强)。
68. 请简单描述面向过程和面向对象的区别。
面向过程(Procedural Programming) 是一种以“过程”和“函数”为中心的编程思想,强调按照步骤来组织代码,通过调用函数一步步解决问题。代表语言如 C。
面向对象(Object-Oriented Programming, OOP) 则以“对象”和“类”为中心,强调将数据与行为封装在一起,通过封装、继承、多态等机制来提高代码的可重用性和可维护性。
69. C++ 中的重载(Overload)、重写(Override)、隐藏(Hide)分别是什么?
函数重载(Overload)
发生在同一作用域内,函数名相同,但参数类型或个数不同。
编译器根据参数列表区别不同的函数,属于编译时多态(静态多态)。
函数重写(Override)
又称覆盖,发生在子类继承父类的场景中。
要求函数名、参数列表完全相同,且基类中函数为
virtual
。子类实现会替代父类的虚函数实现,属于运行时多态(动态多态)。
函数隐藏(Hide)
当子类中定义了一个与父类同名但参数不同的函数时,会隐藏父类中所有同名函数,不管参数是否相同。
不是重写,也不是重载,而是作用域规则下的“名字屏蔽”现象。
70. 什么是 this 指针?为什么需要它?
this
是一个隐含在非静态成员函数中的指针,它指向调用该成员函数的当前对象的地址。也就是说,this
指针表示“当前对象的身份”。为什么?
因为类的成员函数在编译时只有一份代码,而这份代码可能被多个对象共享调用,因此在执行时必须有一种方式来区分是哪个对象在调用这个函数,
this
指针就起到了这个作用。
71. 空对象指针(nullptr)为什么可以调用成员函数?
空对象指针可以调用非虚成员函数,是因为这些函数不依赖于对象内部数据,也不访问成员变量。
编译器实际上把成员函数当作普通函数,并把指针作为隐式的
this
参数传入;只要函数内部没有使用
this
或访问成员变量,就不会触发空指针解引用,因此不会崩溃。
72. C++ 编译器是如何实现函数重载的?
C++ 支持函数重载(函数名相同、参数不同),而底层的机器语言或链接器(如汇编、C 链接器)并不支持这种“语法糖”。为了实现重载,编译器通过“名称修饰(Name Mangling)”机制来区分每个重载函数。
例如:
void print(int);
void print(double);编译后可能被编译器改写为:
_Z5printi // print(int)
_Z5printd // print(double)
73. 动态链接和静态链接的区别?
静态链接和动态链接的最大区别在于链接时机不同。
静态链接在编译阶段将所有依赖的库代码整合进可执行文件中,生成一个独立的大文件。静态链接运行效率更高但资源占用大。
动态链接只在编译时记录符号信息,程序运行时由系统的动态链接器将所需的共享库(如
.so
或.dll
)加载到内存中并完成符号绑定。动态链接能共享库资源、节省内存并支持库的独立升级,底层通过如.plt
、.got
等结构实现延迟绑定与动态重定位。
74. 请解释 extern \"C\"
关键字的作用及其存在的原因。
extern \"C\"
用于告诉编译器采用 C 语言的函数链接方式(C linkage),即关闭 C++ 的名称改编(name mangling),使得 C++ 代码能与 C 代码兼容、互相调用。这个关键字主要解决了 C++ 函数名经过编译器修饰后导致的链接问题,确保链接时能找到正确的函数符号,方便跨语言或跨编译器的接口调用。
75. C++ 中空类的大小是多少?为什么?
C++ 中空类的大小通常是 1 字节。这是因为即使类没有成员,编译器也会给它分配至少 1 字节的内存,用来保证每个对象都有唯一的地址,方便指针和引用操作。若空类大小为 0,会导致不同对象地址相同,破坏了对象唯一性的基本要求。
76. C++ 中空类默认包含哪些函数?
C++ 中的空类虽然没有数据成员,但编译器会自动为它生成一组默认的特殊成员函数,包括:
默认构造函数
拷贝构造函数
拷贝赋值运算符
默认析构函数
这些函数都是隐式定义的,保证空类对象可以正常创建、复制和销毁。
77. C++ 中 explicit
关键字用在哪里?有什么作用?
explicit
关键字用在类的构造函数前,主要目的是禁止构造函数的隐式类型转换,避免编译器自动把某个参数类型的值转换成类对象,防止因隐式转换带来的潜在错误。例如,有了
explicit
,只能显式调用构造函数(如A a(10);
),不能用A a = 10;
这种隐式转换形式,从而提高代码安全性和可读性。
78. C++ 中 delete
关键字有哪些用法?
一方面,它配合
new
用来释放堆上分配的内存(delete p;
对单个对象、delete[] p;
对数组),调用析构函数后归还内存;另一方面,从 C++11 起可以在函数或构造声明后加
= delete
,禁止该函数被生成或调用(如A(const A&)=delete;
用来禁用拷贝构造),以增强类型安全和防止误用。
79.delete
和 delete[]
的区别?
delete
:调用单个对象的析构函数并释放内存。
delete[]
:调用数组中每个元素的析构函数,再释放内存。
80.delete
与 free()
的区别?
delete
:C++ 运算符,自动调用析构函数。
free()
:C 函数,仅释放内存,不调用析构函数。
81. C++ 中一个指针变量本身占用多少字节?和它指向的数据类型有关吗?
在 C++ 中,指针本身的大小通常是 4 字节或 8 字节,具体取决于系统的位数(32 位系统是 4 字节,64 位系统是 8 字节)。
这个大小与指针指向的数据类型无关,不管是
int*
、char*
还是double*
,在同一平台上占用的字节数是一样的,因为指针本质上只是存储一个内存地址。
82. C++中左值和右值是什么?++i是左值还是右值,++i和i++哪个效率更高?
在 C++ 中,左值(lvalue)是指可以出现在赋值号左边、具有持久地址的表达式,而右值(rvalue)是指不能取地址、通常是临时值的表达式。例如,变量是左值,常量和表达式结果是右值。
++i
是一个左值,因为它返回的是自增后的变量本身;而i++
是右值,因为它返回的是一个临时值(原值的副本)。从效率上看,
++i
通常比i++
更高效,特别是在自定义类型(如迭代器)中,因为i++
需要额外创建一个临时对象保存旧值,而++i
是原地修改,不涉及拷贝。
83. 请写出 bool
、int
、float
和指针变量与“零值”比较的 if
语句示例。
bool b = false;
if (b == false) {
// bool 变量等于零值(false)
}int i = 0;
if (i == 0) {
// int 变量等于零值(0)
}float f = 0.0f;
if (f == 0.0f) {
// float 变量等于零值(0.0)
}int* p = nullptr;
if (p == nullptr) {
// 指针变量等于零值(空指针)
}总结一下,零值就是:
bool
的零值是false
;
int
的零值是0
;
float
的零值是0.0f
;指针的零值是
nullptr
(C++11 后,或者NULL
)。
84. 请简要说说 C++ STL(标准模板库)的基本组成部分有哪些?
STL(Standard Template Library)是 C++ 标准库的核心组件,由 容器(Containers)、算法(Algorithms)、迭代器(Iterators)、仿函数(Functors)、适配器(Adapters) 和 空间配置器(Allocators) 六大组件构成。
85. 请简要说一下STL的六大组件的作用?
容器(Containers)
作用:存储和管理数据。
分类:
序列容器:
vector
、list
、deque
、array
、forward_list
。关联容器:
set
、map
、multiset
、multimap
。无序容器(C++11):
unordered_set
、unordered_map
。算法(Algorithms)
作用:操作容器中的数据(如排序、查找、转换)。
分类:
非修改型:
find
、count
、for_each
。修改型:
copy
、sort
、transform
。数值型:
accumulate
、partial_sum
。迭代器(Iterators)
作用:连接容器和算法的桥梁,提供统一的访问接口。
分类:
输入 / 输出迭代器:单向、只读 / 只写。
前向迭代器:单向、读写。
双向迭代器:双向、读写(如
list
)。随机访问迭代器:随机访问(如
vector
)。仿函数(Functors)
作用:行为类似函数的对象(重载
operator()
),用于自定义算法逻辑。适配器(Adapters)
作用:修改容器或仿函数的接口,使其适配特定需求。
分类:
容器适配器:
stack
、queue
、priority_queue
。迭代器适配器:
reverse_iterator
、back_inserter
。函数适配器:
std::bind
(C++11)。空间配置器(Allocators)
作用:管理容器的内存分配和释放。
默认实现:
std::allocator
,可自定义(如内存池)。
86.std::map
和 std::unordered_map
的区别?
std::map
是有序关联容器,底层基于 红黑树(RB-tree) 实现,键值会自动按升序排列,支持有序遍历,插入、查找、删除的时间复杂度为 O(log n)。
std::unordered_map
是无序关联容器,底层基于 哈希表(Hash Table) 实现,元素无序排列,插入、查找、删除的平均时间复杂度为 O(1),但最坏情况可能退化为 O(n)。
87. 什么是迭代器失效?它的常见原因和解决方法有哪些?
迭代器失效指的是当对容器进行修改(如插入、删除或重新分配内存)后,之前获取的迭代器变得无效,继续使用可能导致未定义行为。
常见原因:
容器扩容导致内存搬移,迭代器指向的地址变了;
删除元素使迭代器指向的元素不存在;
对某些容器的特定操作(如
vector
的插入或删除)会失效所有或部分迭代器。解决方法:
修改容器后重新获取迭代器;
使用稳定迭代器的容器(如
list
、forward_list
);在循环中谨慎操作,避免使用已失效的迭代器;
使用智能指针或索引替代迭代器跟踪元素。
88. C++ 中 vector
的扩容机制是怎样的?resize
和 reserve
有什么区别?
vector 扩容:当插入元素超过当前容量时,
vector
会自动申请更大内存(通常是当前容量的两倍),然后拷贝原有元素到新空间,释放旧空间,这个过程称为扩容,会带来性能开销。
reserve(size)
:预先申请至少能容纳size
个元素的内存空间,改变容量但不改变元素数量,避免多次扩容,提高插入效率,但不初始化新元素。
resize(size)
:改变vector
的元素数量。如果扩大,则会添加默认值初始化的新元素;如果缩小,则会删除多余元素,容量可能不变。
89.std::vector
中的 push_back()
和 emplace_back()
有什么区别?各自适用于哪些场景?
push_back()
会将传入的对象作为参数拷贝或移动到容器中,因此它需要一个已构造好的对象。而
emplace_back()
是 C++11 引入的函数,直接在容器的尾部原地构造对象,避免了额外的拷贝或移动操作,性能更高,特别适用于构造成本较高的类对象。使用场景上,如果你已经有了一个对象实例,就使用
push_back()
;如果你想在容器中就地构造一个对象,应使用
emplace_back()
。
90.std::vector
是如何判断是否需要扩容的?扩容的时机与 size
和 capacity
有什么关系?
size()
:当前 vector 中已有的元素个数。
capacity()
:当前 vector 在不重新分配内存的情况下,最多可以容纳的元素数量。扩容判断的原理
判断
size()
是否等于capacity()
:
如果
size < capacity
:可以直接插入,不扩容。如果
size == capacity
:当前内存已满,会自动触发扩容。
91. C++ 中 std::vector
和 std::list
的底层实现原理,以及它们各自的优缺点。
vector
底层是一个连续的动态数组,内存连续分配。它维护三个指针:指向数组开始、当前元素尾后位置、容量尾后位置。扩容时会重新分配更大内存,拷贝原有元素。优点:
支持随机访问,时间复杂度 O(1)
内存连续,利于缓存优化,提高访问速度
支持动态增长,方便存储大量数据
缺点:
在中间或开头插入/删除元素效率低,时间复杂度 O(n),因为需要移动大量元素
扩容时可能涉及内存重新分配和拷贝,性能开销较大
list
通常是 双向链表 实现,每个节点包含数据和指向前后节点的指针,节点在内存中不连续。优点:
插入和删除操作时间复杂度为 O(1),适合频繁修改的场景
不会发生扩容问题,内存分配灵活
缺点:
不支持随机访问,只能顺序遍历,访问时间复杂度 O(n)
每个节点额外存储指针,空间开销较大
内存不连续,缓存友好性差,访问速度较慢
92.std::map
和 std::set
的底层实现及其区别?
底层实现:
std::map
和std::set
都通常基于 红黑树(Red-Black Tree) 实现,保证有序且自平衡,插入、查找、删除操作时间复杂度均为 O(log n)。区别:
std::map
是键值对容器,存储,键唯一且有序,允许通过键访问对应的值。
std::set
是只存储唯一键的集合,没有对应的值,主要用于存储有序的唯一元素。
93.map
中 find
、operator[]
和 at
三种访问元素的方法的异同?
方法 功能描述 是否插入元素 越界检查(异常处理) 返回类型 find(key)
查找是否存在键为 key 的元素 不插入 无(不存在返回 end()) 迭代器(指向元素或 end) operator[](key)
访问或插入元素 如果不存在则插入一个默认值 无(不存在会插入默认元素) 值的引用(可修改) at(key)
访问元素 不插入 有(键不存在时抛出 std::out_of_range
异常)值的引用(可修改)
94. 对比迭代器与指针的本质区别?
虽然迭代器在语法上和指针类似(比如可以使用
*
解引用、++
遍历),但本质上它不是指针,而是一个模板类对象,通过重载操作符来模拟指针的行为,如operator*
、operator++
等。它返回的是对象的引用,而不是对象的值。指针是对内存地址的直接操作,能指向任意类型的数据,包括函数、基本类型或对象等;
迭代器是容器专用的访问机制,它只能指向容器中的元素,不能像指针那样指向函数或任意内存位置。
95. 什么是右值引用?为什么 C++ 要引入右值引用?
右值引用是 C++11 引入的一种新类型引用,用
&&
表示,例如:int&& a = 10。它可以绑定到右值(即临时对象或将要被销毁的对象)。为什么引入右值引用?
传统的左值引用(
&
)只能绑定左值,无法绑定临时对象,导致:
不能高效地复用临时资源;
无法实现完美转发和移动语义,临时对象只能通过拷贝,效率低。
右值引用的两大作用:
移动语义(Move Semantics)
允许资源从一个对象“移动”到另一个对象,避免不必要的深拷贝,提高性能。
如:
std::move
和std::vector
的扩容操作。完美转发(Perfect Forwarding)
结合
std::forward
,可以将参数的类型特性(左值或右值)精准地传递给其他函数,构造高效、通用的函数封装。
96. C++11中移动语义是什么意思?
移动语义允许通过右值引用将对象的资源所有权转移到另一个对象,避免深拷贝,提升性能。它依赖移动构造函数和移动赋值函数,常用于处理临时对象或大型资源类,如容器扩容、函数返回值等场景。
97.std::move
和移动语义之间是什么关系?
std::move
是一个类型转换工具,它将左值转换成右值引用,从而触发移动语义;移动语义则是在移动构造函数或移动赋值函数中实现资源的转移。简单来说,
std::move
负责标记对象可被移动,移动语义负责真正执行资源搬移。
98. 什么是完美转发(Perfect Forwarding)?它的原理是什么?
完美转发是指:在模板函数中,将接收到的参数完整地原样转发(包括左值/右值属性)给另一个函数,避免拷贝或错误的重载匹配。
它的实现依赖两个关键技术:
万能引用(Forwarding Reference):
T&&
在模板中能同时绑定左值和右值。std::forward(arg):用于根据
T
的值类别,保持参数的左值或右值特性进行转发。
99. 友元函数的作用是什么?
友元函数是被类授权访问其私有(
private
)和保护(protected
)成员的非成员函数。它的作用是允许外部函数访问类的内部细节,实现类与函数之间的紧密协作,同时保持类接口的封装性。
100. 友元函数的继承性?
友元关系 不继承。基类的友元函数无法自动访问派生类的私有成员,除非派生类也显式声明该函数为友元。
101. 友元类与友元函数的区别?
友元函数:单个函数访问类的私有成员。
友元类:整个类的所有成员函数都可访问另一个类的私有成员。
102. 父类的构造函数和析构函数是否能为虚函数?这样操作导致的结果?
在 C++ 中,构造函数不能是虚函数,因为虚函数机制依赖的虚表是在构造过程中初始化的,而对象尚未完全构建,不支持虚调度;但析构函数可以且应该是虚函数,尤其在有继承的场景中,确保通过基类指针删除子类对象时能触发正确的析构链,避免资源泄漏。
103. 静态成员函数可以是虚函数吗?
静态成员函数不能是虚函数。因为虚函数的动态绑定依赖于对象的虚函数表(vtable)和
this
指针,而静态函数不属于任何对象,没有this
指针,也没有虚函数表支持,因此无法实现虚函数的多态机制。
104. 静态成员函数能否访问非静态成员?为什么?
静态成员函数不能直接访问类的非静态成员。因为静态函数属于类,不依赖具体对象,没有
this
指针,无法知道要操作哪个对象的成员。而非静态成员属于对象实例,必须通过对象才能访问。如果需要访问非静态成员,静态函数必须通过传入的对象引用或指针来访问。
105. 静态成员函数与类外普通函数的区别?
作用域:静态函数属于类作用域(
Class::func
),普通函数属于全局 / 命名空间作用域。访问权限:静态函数可被声明为
private/protected/public
,普通函数默认全局可见。关联性:静态函数逻辑上属于类,体现 “类相关工具” 的语义(如
std::string::npos
)。
106. 静态成员变量的内存布局?
静态成员变量存储在 全局 / 静态存储区,不随对象创建而复制,所有对象共享同一块内存。对象的内存仅包含非静态成员变量。
107. C++ 中,哪些函数不能被声明为虚函数?为什么?
函数类型 是否可以为虚函数 原因解释 构造函数 ❌ 否 构造过程中对象尚未完整构建,虚表尚未建立,无法进行虚调用 静态成员函数 ❌ 否 静态函数不依赖对象实例,不存在 this
指针,也不参与虚表机制友元函数 ❌ 否 友元函数不属于类的成员函数,仅具有访问权限,无法虚拟化 内联函数(inline) ✅ 可 虚函数可以是 inline,最终是否内联由编译器决定 析构函数 ✅ 是 推荐基类析构为虚函数,支持多态删除,避免资源泄漏 模板函数 ✅ 可 模板函数可以是虚函数,但使用复杂,一般用于高级场景
108. C++ 中的深拷贝和浅拷贝有什么区别?
浅拷贝是简单复制对象的所有成员变量,包括指针地址,多个对象共享同一块内存,容易引发资源冲突;
深拷贝则会为指针成员分配新的内存并复制内容,保证对象之间资源独立,防止意外修改和内存释放错误,是管理动态资源时推荐的做法。
109. 类的普通成员函数和静态成员函数分别能否访问类内的静态变量和普通变量?为什么?
类的普通成员函数可以访问静态成员变量,因为它们有对象上下文,可以通过
this
指针访问所有成员;而静态成员函数没有对象实例,缺少
this
指针,不能访问普通成员变量,只能访问静态成员和静态函数。
110. C++ 中,什么是运算符重载?哪些运算符是不能被重载的?
运算符重载允许我们为自定义类型(如类或结构体)定义特定运算符的行为,让它们像内置类型一样使用。本质上是通过函数重定义已有运算符。
以下不能被重载:
::
(作用域解析符)
.
(成员访问运算符)
.*
(成员指针访问运算符)
?:
(三元条件运算符)
sizeof
(尺寸运算符)
typeid
(运行时类型识别)
alignof
(类型对齐)
111. 如何设计一个类,使得它的对象只能在堆上创建,不能在栈上创建?
要保证类的对象只能在堆上创建,通常将构造函数声明为私有或受保护,并提供一个公共的静态成员函数用
new
动态分配对象,禁止外部通过栈上声明。同时删除拷贝构造函数和赋值运算符,防止对象被复制到栈上。这种设计能强制对象只能在堆上管理生命周期。
112. 在 C++ 项目中,如何编译和调用 C 语言代码?
C++ 中调用 C 代码时,一般用 C 编译器编译 C 代码,生成目标文件;
在 C++ 中通过
extern \"C\"
声明引用 C 函数,防止名字修饰;最后将 C 和 C++ 目标文件一同链接,保证兼容性和调用正确。
113. C++ 中 Lambda 表达式的捕获列表有哪些捕获方式?它们各自有什么特点?
按值捕获(
[=]
)
把外部变量的值复制到 Lambda 内部,Lambda 内部有自己的副本。
不能修改外部变量(除非使用
mutable
关键字)。按引用捕获(
[&]
)
捕获外部变量的引用,Lambda 内部操作的是外部变量本身。
可以修改外部变量。
指定按值捕获(
[x, y]
)
只捕获指定变量的副本。
指定按引用捕获(
[&x, &y]
)
只捕获指定变量的引用。
混合捕获(
[=, &x]
或[&, x]
)
默认按值捕获,但对某些变量按引用捕获,或反之。
捕获
this
指针([this]
)
捕获当前对象的指针,访问成员变量和成员函数。
114. C++ 中拷贝构造函数和移动构造函数的区别?
拷贝构造函数通过复制源对象资源创建新对象,可能带来性能开销;
移动构造函数则通过“窃取”源对象的资源(如指针、句柄)来初始化新对象,避免复制,提高效率,尤其在处理临时对象和右值时显著优化性能。
115. 一个函数func(int a,int b),其中a和b的地址关系是什么?
函数形参如
func(int a, int b)
通常在栈上按顺序连续分配,地址彼此相邻,但具体地址顺序和增长方向受编译器、架构和调用约定影响,且部分参数可能通过寄存器传递,地址关系不一定固定,不应依赖具体地址。
116.讲一下单例设计模式?常见使用场景?
单例模式是一种设计模式,确保一个类只有一个实例,并提供全局访问点。其实现方式有两种:
• 懒汉式(Lazy Singleton):延迟实例化,直到第一次使用时才创建实例。这种方式适合资源消耗大的场景,因为它避免了不必要的初始化。但是,懒汉式在多线程环境下可能会遇到同步问题,因此需要额外的线程安全措施。
• 饿汉式(Eager Singleton):在程序启动时就创建实例。这种方式保证了线程安全,但可能导致资源浪费,因为即使该实例从未被使用过,也会占用一定的资源。使用场景?
配置管理器 / 系统参数加载器
比如读取配置文件的类,只需要加载一次,所有模块都共享这个实例。日志系统(Logger)
整个系统通常只需要一个日志记录对象,集中写日志文件,避免多线程下资源竞争。线程池、连接池(如数据库连接池)
为了资源复用与性能优化,只需要一个全局池来统一管理。驱动接口类(嵌入式常见)
比如一个串口/网口驱动对象,底层硬件唯一,整个系统只需要一个实例来读写数据。状态管理器(如系统运行状态)
有些系统需要统一记录运行状态(如是否进入省电模式、故障状态等),也可以用单例管理。
117.std::move
的底层是如何实现的?
std::move
本质只是一个类型转换工具,通过static_cast
把对象转成右值引用,它本身不移动资源。真正的移动操作发生在对象随后调用的移动构造或移动赋值函数中。它是为了启用移动语义,从而避免资源拷贝、提升性能。
118. 什么是尾递归?
尾递归是指函数在最后一步调用自身,且调用结果直接返回,没有额外操作。它的特点是递归调用发生在函数末尾,编译器可以优化成循环,避免栈溢出,提高性能。
119. 什么是函数调用约定(Calling Convention),它包含哪些内容?
函数调用约定是指编译器在生成函数调用和返回代码时遵循的一套规则,主要规范了以下几个方面:
参数传递方式
参数是通过寄存器传递还是通过栈传递。
传递参数的顺序(从左到右,还是从右到左)。
返回值传递
函数返回值是通过哪个寄存器或内存位置传递给调用者。
调用者和被调用者的职责
谁负责保存哪些寄存器(调用者保存寄存器Caller-saved,还是被调用者保存Callee-saved)。
谁负责清理参数栈空间。
栈帧结构
函数调用时栈的布局和管理细节。
120. 常见的调用约定举例?
cdecl:参数从右到左压栈,调用者负责清理栈,常见于 C 语言。
stdcall:参数从右到左压栈,被调用者负责清理栈,多用于 Windows API。
fastcall:部分参数通过寄存器传递,提高调用效率。
121. 进程和线程的关系是什么?
进程是操作系统分配资源的基本单位,拥有独立的地址空间和系统资源;
线程是进程中的执行单位,一个进程可以包含多个线程。
线程共享进程的资源,但每个线程有自己的栈和寄存器上下文,线程之间切换开销小,适合并发执行。
122. 进程之间的通信方式有哪些?
进程间通信(IPC)主要有以下几种方式:
管道(Pipe):用于具有亲缘关系的进程间的单向通信。
有名管道(Named Pipe/FIFO):支持无亲缘关系进程间的通信。
消息队列(Message Queue):通过消息的方式异步传递数据。
共享内存(Shared Memory):多个进程共享同一块内存区域,速度最快。
信号量(Semaphore):主要用于进程间的同步和互斥。
套接字(Socket):支持网络通信,也可用于本地进程通信。
123. 线程之间的通信方式有哪些?
线程间通信主要依赖于共享内存(因为同一进程内的线程共享地址空间),常用的同步与通信机制包括:
互斥锁(Mutex):防止多个线程同时访问共享资源。
条件变量(Condition Variable):线程间等待和通知机制。
信号量(Semaphore):控制访问资源的线程数量。
读写锁(Read-Write Lock):支持多个线程读共享资源,写时互斥。
事件(Event)或信号(Signal):线程间的通知机制(平台相关)。
124. C++中如何实现多进程?
C++ 本身没有内置多进程支持,通常通过调用操作系统提供的接口实现多进程:
在 Linux/Unix 上,可以使用
fork()
,它会复制当前进程生成子进程。在 Windows 上,可以使用
CreateProcess()
API 创建新进程。
125. 如何实现多线程?
多线程可以通过操作系统提供的线程库或语言标准库实现,比如在 C++11 以后可以使用
std::thread
创建线程;在 POSIX 系统可以使用
pthread_create()
;Windows 则用
CreateThread()
。通过启动多个线程并发执行任务,实现程序的并行或并发处理。
126. 多线程会发生什么问题?线程同步有哪些手段?
多线程编程中常见的问题包括数据竞争(Race Condition)、死锁(Deadlock)、资源饥饿(Starvation)和可见性问题(内存不一致)等。这些问题通常由于多个线程并发访问共享资源而缺乏有效同步导致。
为实现线程同步,常用手段包括:互斥锁(mutex)、读写锁(shared_mutex)、条件变量(condition_variable)、原子变量(std::atomic)、信号量(semaphore)、线程局部存储(thread_local),以及更高级的无锁数据结构与并发容器等机制。
127. 线程有哪些状态?线程锁有哪些?
线程的状态通常包括以下几种:
新建(New):线程被创建但尚未开始执行。
就绪(Ready):线程已准备好执行,等待 CPU 调度。
运行(Running):线程正在 CPU 上执行。
阻塞(Blocked/Waiting):线程因等待某个事件(如锁、IO等)而暂停执行。
终止(Terminated/Dead):线程执行完成或被终止。
线程同步机制(锁)常见的包括:
互斥锁(Mutex):保证同一时刻只有一个线程访问共享资源。
读写锁(Read-Write Lock):允许多个线程同时读,但写时独占。
自旋锁(Spinlock):线程循环等待锁释放,适合锁持有时间短的场景。
递归锁(Recursive Mutex):同一线程可以多次加锁,防止死锁。
信号量(Semaphore):控制多个线程对有限资源的访问数量。
条件变量(Condition Variable):线程间的等待与通知机制。
128. 锁的底层实现原理是什么?请从操作系统或硬件层面简要说明。
锁的底层原理主要依赖于原子操作和CPU指令支持,例如
Test-and-Set
、Compare-and-Swap(CAS)
等原子指令,它们可以确保在多核处理器下修改共享数据时不会被中断。同时,操作系统在实现如 mutex、spinlock 等锁时,会结合 内核调度机制,如线程阻塞、唤醒和上下文切换,以保证线程安全和系统性能。
129. 什么是原子操作?它在多线程编程中起什么作用?
原子操作(Atomic Operation)指的是不可被中断的操作,即一旦开始执行,就会一直运行到结束,中间不会被任何线程或中断打断。在多线程环境中,原子操作用于保证对共享变量的读写是安全的,防止竞态条件的发生,是实现线程同步和锁的基础。
130. 什么是死锁?多线程为什么会发生死锁?
死锁是指两个或多个线程在运行过程中因争夺资源而造成互相等待、彼此阻塞,导致程序无法继续执行。
多线程发生死锁的根本原因是多个线程同时占有资源并相互等待对方释放资源。
131. 死锁产生的条件有哪些?如何解决死锁?
产生死锁需要同时满足四个必要条件:①互斥条件、②占有且等待、③不剥夺、④循环等待。
解决死锁的方法包括:破坏四个条件之一,如使用一次性申请所有资源(破坏占有且等待)、资源有序分配策略(破坏循环等待);或采用超时重试、死锁检测与恢复机制等手段。
132. 如何实现线程安全?除了加锁,还有哪些方式可以实现线程安全?
实现线程安全的常见方式包括加锁机制(如
mutex
、spinlock
、std::lock_guard
等)来保护共享资源,避免竞争条件。除了加锁,还可以使用原子操作(如
std::atomic
)、线程局部存储(Thread Local Storage)、无锁编程(Lock-Free Programming)、使用不可变对象(Immutable Object)、消息队列通信等方式来实现线程安全。
133. C 语言常用库函数分类总结
一、字符串处理
1. 字符串操作
strlen(s)
:计算字符串长度(不包括\\0
)。
strcpy(dest, src)
:复制字符串。
strncpy(dest, src, n)
:复制最多n
个字符(更安全)。
strcat(dest, src)
:拼接字符串。
strncat(dest, src, n)
:拼接最多n
个字符。
strcmp(s1, s2)
:比较字符串(按 ASCII 码)。
strncmp(s1, s2, n)
:比较前n
个字符。2. 字符串查找与分割
strchr(s, c)
:查找字符c
首次出现的位置。
strrchr(s, c)
:查找字符c
最后一次出现的位置。
strstr(s1, s2)
:查找子串s2
首次出现的位置。
strtok(s, delim)
:按分隔符delim
分割字符串(需循环调用)。3. 类型转换
atoi(s)
:字符串转整数(不安全,建议用strtol
)。
atof(s)
:字符串转浮点数。
strtol(s, endptr, base)
:字符串转长整型(支持进制)。
strtod(s, endptr)
:字符串转双精度浮点数。二、内存管理
malloc(size)
:分配size
字节的内存,返回指向该内存的指针。
calloc(n, size)
:分配n
个大小为size
的内存块,初始化为 0。
realloc(ptr, size)
:调整已分配内存的大小(可能移动内存)。
free(ptr)
:释放malloc
等分配的内存。
memcpy(dest, src, n)
:复制n
字节内存(不重叠内存)。
memmove(dest, src, n)
:复制n
字节内存(支持重叠内存)。
memset(ptr, value, n)
:将ptr
开始的n
字节设置为value
(常用0
初始化)。三、输入输出(I/O)
1. 标准 I/O
printf(format, ...)
:格式化输出到标准输出。
scanf(format, ...)
:从标准输入读取格式化数据。
fprintf(fp, format, ...)
:格式化输出到文件。
fscanf(fp, format, ...)
:从文件读取格式化数据。
puts(s)
:输出字符串到标准输出(自动加换行)。
gets(s)
:从标准输入读取字符串(不安全,避免使用)。
fgets(s, n, fp)
:从文件读取最多n-1
个字符(安全替代gets
)。2. 文件操作
fopen(path, mode)
:打开文件(mode
如\"r\"
、\"w\"
、\"a\"
)。
fclose(fp)
:关闭文件。
fread(ptr, size, n, fp)
:从文件读取n
个大小为size
的元素。
fwrite(ptr, size, n, fp)
:向文件写入n
个大小为size
的元素。
fseek(fp, offset, origin)
:移动文件指针(origin
如SEEK_SET
、SEEK_CUR
)。
ftell(fp)
:返回文件指针当前位置。四、数学函数
abs(n)
:整数绝对值(stdlib.h
)。
fabs(x)
:浮点数绝对值。
sqrt(x)
:平方根。
pow(x, y)
:计算x^y
。
exp(x)
:计算e^x
。
log(x)
:自然对数(底数e
)。
log10(x)
:常用对数(底数 10)。
sin(x)
、cos(x)
、tan(x)
:三角函数(弧度制)。
ceil(x)
:向上取整(如ceil(3.2) = 4.0
)。
floor(x)
:向下取整(如floor(3.8) = 3.0
)。五、时间日期
time(&t)
:返回当前时间(秒数,自 1970-01-01)。
ctime(&t)
:将时间转换为字符串(如\"Sat Jun 12 14:30:00 2025\"
)。
localtime(&t)
:将时间转换为本地时间结构体(struct tm
)。
gmtime(&t)
:将时间转换为 UTC 时间结构体。
strftime(buf, size, format, &tm)
:格式化时间为字符串。六、错误处理
errno
:全局错误码变量(errno.h
)。
perror(s)
:打印错误信息(如perror(\"open failed\");
)。
strerror(errnum)
:返回错误码对应的字符串描述。七、实用工具
system(cmd)
:执行系统命令(如system(\"ls\");
)。
rand()
:生成伪随机数(范围0
~RAND_MAX
)。
srand(seed)
:设置随机数种子(常用time(NULL)
)。
exit(status)
:终止程序(status
为退出码,如EXIT_SUCCESS
)。八、内存对齐与位操作
offsetof(type, member)
:计算结构体成员的偏移量(stddef.h
)。
bzero(ptr, n)
:将ptr
开始的n
字节置为 0(非标准,可用memset
替代)。
bcopy(src, dest, n)
:复制n
字节内存(非标准,可用memcpy
替代)。
134. C++ 常用库函数分类总结
一、STL 容器与算法
1. 容器(Containers)
序列容器:
vector
:动态数组,支持随机访问。
list
:双向链表,插入删除高效。
deque
:双端队列,支持首尾快速插入删除。关联容器:
set
/multiset
:有序集合,元素自动排序。
map
/multimap
:键值对映射,按键排序。无序容器(C++11):
unordered_set
/unordered_map
:基于哈希表,查找 O (1)。2. 算法(Algorithms)
查找:
find(begin, end, val)
:查找元素。
binary_search(begin, end, val)
:二分查找(有序容器)。排序:
sort(begin, end)
:快速排序(随机访问迭代器)。
stable_sort(begin, end)
:稳定排序。修改:
copy(src_begin, src_end, dest_begin)
:复制元素。
transform(src, dest, func)
:对元素应用函数。
replace(begin, end, old_val, new_val)
:替换元素。二、字符串处理
C++ 风格:
string
类:
s.length()
/s.size()
:返回长度。
s.substr(pos, len)
:截取子串。
s.find(str)
:查找子串(返回位置或string::npos
)。
s.replace(pos, len, new_str)
:替换子串。
to_string(val)
:数值转字符串(C++11)。C 风格兼容:
strcpy
、strcat
、strcmp
等(需包含)。
三、输入输出(I/O)
1. 标准 I/O
iostream
库:
cin >> var
:从标准输入读取。
cout << val
:输出到标准输出。
cerr << err_msg
:输出错误信息(无缓冲)。
clog << log_msg
:输出日志(有缓冲)。2. 文件 I/O
fstream
库:
ifstream fin(\"file.txt\")
:读文件。
ofstream fout(\"file.txt\")
:写文件。
fstream f(\"file.txt\", ios::in | ios::out)
:读写模式。
fin.getline(buf, size)
:读取一行(C 风格)。
getline(fin, str)
:读取一行到string
(C++ 风格)。四、智能指针(C++11)
unique_ptr
:独占所有权的智能指针。
shared_ptr
:共享所有权,引用计数管理。
weak_ptr
:弱引用,不增加引用计数,避免循环引用。五、内存管理
new
/delete
:动态分配 / 释放单个对象。
new[]
/delete[]
:动态分配 / 释放数组。
malloc
/free
:C 风格内存分配(不推荐,除非与 C 库交互)。六、智能指针
std::unique_ptr
,std::shared_ptr
,std::weak_ptr
七、线程和并发(C++11起)
线程:
std::thread
互斥锁:
std::mutex
,std::lock_guard
条件变量:
std::condition_variable
原子操作:
std::atomic
八、时间库
std::chrono
提供时间点和时间段处理九、异常处理
标准异常类如
std::exception
,std::runtime_error
135. 常用 C 语言第三方库
libcurl:用于网络请求和数据传输,支持 HTTP、FTP 等多种协议,广泛应用于网络编程。
zlib:数据压缩库,提供高效的压缩和解压功能,常用于文件和网络传输数据处理。
OpenSSL:实现安全通信的加密库,提供 SSL/TLS 协议支持,常用于安全网络编程。
SQLite:轻量级嵌入式数据库,易于集成,适合小型数据存储。
pthread(POSIX 线程库):多线程编程基础库,提供线程管理、同步等功能。
136. 常用 C++ 语言第三方库
Boost:功能全面,包含智能指针、正则表达式、文件系统、多线程等扩展,增强标准库功能。
Google Test (gtest):单元测试框架,帮助编写自动化测试用例,保障代码质量。
OpenCV:计算机视觉和图像处理库,广泛应用于视觉识别和机器学习。
protobuf:高效的数据序列化库,方便跨平台通信和数据存储。
spdlog:高性能、多线程日志库,适合大型项目的日志需求。
asio:跨平台的异步 I/O 库,支持网络通信和底层系统调用。
补充:
1. C++ 中 public、protected 和 private 有什么区别?
public
:对所有用户(包括类外部、子类)完全开放。
protected
:对子类开放,但对类外部不可见。
private
:仅当前类内部可以访问,子类和外部都无法访问。
2. 继承方式有哪些?
C++提供了三种主要的继承方式,每种方式决定了基类成员在派生类中的访问权限:
public继承:基类中的public成员在派生类中仍然是public;protected成员仍然是protected;private成员不可见。
protected继承:基类中的public成员在派生类中变为protected;protected成员仍然是protected;private成员不可见。
private继承:基类中的public和protected成员在派生类中都变成private;private成员不可见。
3. 当一个子类中有对象成员时,他们的构造和析构顺序是怎样的?
在C++中,当一个类包含其他类的对象作为成员时,构造和析构的顺序如下:
构造顺序:先构造基类,然后按照它们在派生类中声明的顺序构造成员对象,最后构造派生类本身的构造函数体。
析构顺序:与构造顺序相反,先析构派生类的构造函数体,然后按照成员对象声明的逆序析构成员对象,最后析构基类。
4. 什么是菱形继承?菱形继承存在哪些问题?如何解决?
菱形继承(Diamond Inheritance)是指多继承中出现的一种结构,其中两个或更多的派生类都继承自同一个基类,而另一个类又同时继承自这两个派生类。这种结构会导致二义性问题,即当访问基类成员时,编译器无法确定应该使用哪一个路径。
例如,假设我们有如下类结构:
在这个例子中,D类继承自B和C,而B和C都继承自A。如果D试图访问A的成员,编译器不知道应该使用B还是C的路径。
解决这个问题的方法之一是使用虚继承(Virtual Inheritance)。虚继承确保了所有间接继承路径都指向同一个基类实例,从而消除了二义性。当使用虚继承时,派生类只有一份基类的成员,而不是每条继承路径各有一份。
5.什么是抽象基类?抽象基类的特点?
抽象基类至少包含一个纯虚函数,这意味着它不能被实例化,只能作为其他类的基类。抽象基类的主要用途是定义接口,强制派生类实现某些特定的方法。
抽象基类的特点包括:
纯虚函数:声明为virtual void func() = 0;的形式,表示没有实现的虚函数。派生类必须提供具体的实现,否则它们也会成为抽象类。
不能实例化:由于至少有一个成员没有实现,因此无法创建抽象基类的对象。
可以有实现:尽管是纯虚函数,也可以为其提供实现,派生类可以选择调用基类的实现。
支持多态:通过基类指针或引用,可以调用派生类的具体实现,体现了多态性。
6. 说一下map的插入方式?
一:用insert函数插入pair数据;
二:用insert函数插入value_type数据;
三:在insert函数中使用make_pair()函数;
四:用数组方式插入数据,会覆盖原value的值;
7. 什么是适配器?
在C++中,适配器(adapter)是一种设计模式的实现,用于解决接口不兼容的问题。适配器模式的主要目的是将一个类的接口转换成客户端所期望的另一种接口。这种模式通常用于将现有代码中的接口转换为符合新需求的接口,从而使得原本不兼容的接口能够协同工作。
8. 什么是函数对象(仿函数)?
函数对象就是重载了“()”操作符的对象,也就是说如果一个类重载了“()”操作符,由它创建的对象就是函数对象。
因为函数对象本身是一个类的实例,因此它可以有自己的成员,这样,可以用这些成员保存一些普通函数不能轻易保存的(但可以通过静态局部变量和全局变量保存)的信息。同时,通过这个类的其他方法,可以对它的成员变量进行初始化和检查。
函数对象是比函数更加通用的概念,因为函数对象可以定义跨越多次调用的可持久的部分(类似静态局部变量),同时又能够从对象的外面进行初始化和检查(和静态局部变量不同)。
9. string如何和c-style字符串相互转换?
std::string
--> C 字符串:使用.c_str()
方法;C 字符串-->
std::string
:直接构造或赋值;
std::string
的内容复制到可写的char
数组中(比如为了修改),可以使用strcpy(buffer, str.c_str())
来完成。
10. vector删除元素,它的迭代器是如何变化的?
使用erase函数删除单个元素:当使用erase函数删除vector中的一个元素时,例如vec.erase(it)(其中it是指向要删除元素的迭代器),被删除元素之后的所有元素会向前移动一个位置。而此时,erase函数返回的迭代器指向的是被删除元素之后的元素(原来位置的下一个元素)。
删除多个元素:如果要删除一个范围内的元素,比如vec.erase(first, last)(其中first是指向范围开始的迭代器,last是指向范围结束的迭代器),从first到last之间(不包括last)的元素会被删除,后面的元素会向前移动相应的位置。erase函数返回的迭代器指向最后一个被删除元素之后的元素。
11. 你了解哪些迭代器?
1.输入迭代器:从容器中读取元素。输入迭代器只能一次读入一个元素向前移动,输入迭代器只支持一遍算法,同一个输入迭代器不能两遍遍历一个序列。
2.输出迭代器:向容器中写入元素。输出选代器只能一次一个元素向前移动。输出迭代器只支持一遍算法,同一输出送代器不能两次遍历一个序列。
3.正向迭代器:组合输入迭代器和输出迭代器的功能,并保留在容器中的位置。
4.双向迭代器:组合正向迭代器和逆向迭代器的功能,支持多遍算法。
5.随机迭代器:组合双向迭代器的功能与直接访问容器中任何元素的功能,即可向前向后跳过任意个元素。
12. 说一下类的内存布局?
类对象在内存里存了啥?
成员变量:非静态的成员变量按声明顺序存,比如先声明
int a
再声明char b
,内存里a
在前,b
在后。内存对齐:为了 CPU 读取效率,变量会按规则对齐(比如 4 字节、8 字节),中间可能留 “空洞”。比如
char(1字节)+ int(4字节)
,实际占 8 字节(1+3 空洞 + 4)。虚函数指针(vptr):如果类有虚函数,对象开头会存一个指针,指向虚函数表(表里存着所有虚函数的地址)。
父类成员:继承时,父类的成员变量排在子类成员前面(多继承按继承顺序排)。
静态成员和函数放哪?占对象内存吗?
静态成员变量存在全局数据区,成员函数存在代码段,都不占对象本身的内存。对象大小只和非静态成员、对齐、虚指针有关。
13. C++模板的作用?
模板是C++实现泛型编程的机制。它允许编写与类型无关的函数或类,使用时传入具体类型,编译器生成对应代码。这样避免了重复代码,提高了复用性和效率。模板在编译期完成类型替换,广泛应用于标准库容器和算法。
14. 拷贝构造和“=”赋值运算的区别?
拷贝构造函数用于创建新对象时用已有对象初始化,比如
A a2 = a1;
,此时调用拷贝构造函数;
赋值运算符=
用于已有对象之间赋值操作,比如a2 = a1;
,这时调用赋值运算符。拷贝构造是在对象生命周期开始时调用,赋值运算符是在对象已经存在时修改内容。
此外,赋值运算符通常需要处理自赋值和释放旧资源,而拷贝构造函数则不需要。
15. 如果子类中包含其他成员对象,构造和析构的调用顺序是怎样的?
构造顺序:
先调用基类的构造函数;
再按成员对象声明的顺序调用它们的构造函数;
最后调用子类自身的构造函数体。
析构顺序(与构造相反):
先执行子类自身的析构函数体;
再按成员对象声明的逆序调用它们的析构函数;
最后调用基类的析构函数。
16. map、set怎么实现?红黑树为什么能同时实现这两种容器?为什么使用红黑树?
实现方式:
std::set
存储的是键值(key),每个元素是唯一的;
std::map
存储的是键值对(key-value pair),根据 key 进行排序;二者本质上都是通过红黑树维护一个有序集合,并支持对元素的高效插入、删除和查找,时间复杂度为 O(log n)。
为什么 map 和 set 都能用红黑树?
红黑树是一种自平衡二叉搜索树,能够在插入和删除后保持近似平衡,因此:
能保持元素有序(满足 map/set 的特性);
可以按 key 比较元素,实现自动排序;
每个节点只存储一个键(set)或一对键值(map),结构一致,逻辑通用,只需略微修改模板参数即可复用同一套红黑树逻辑。
为什么选择红黑树?
选择红黑树的原因主要有三点:
性能稳定:
红黑树在最坏情况下也能保持 O(log n) 的性能,而不像 AVL 树那样可能因旋转次数多导致操作变慢。插入删除效率更高:
红黑树相比 AVL 树插入和删除操作更快,旋转次数更少,调整逻辑更简单,更适合通用场景。空间开销较低:
红黑树不需要记录太多额外信息,只需一个颜色标记,空间成本较小,便于标准库实现。
17. string和C字符串如何相互转换?
std::string
--> C 字符串:使用.c_str()
方法;C 字符串-->
std::string
:直接构造或赋值;
std::string
的内容复制到可写的char
数组中(比如为了修改),可以使用strcpy(buffer, str.c_str())
来完成。
18. C语言中字符串可用什么标准库函数将其转换成 int 型?
atoi
会把字符串中的数字部分转换为
int
;不做错误检查,字符串非法时返回 0,可能与合法的 \"0\" 混淆。
strtol
支持错误检测,
endptr
指向未被转换的部分;可以检查是否整个字符串都被成功转换;
支持不同进制(参数 10 是十进制)。
19. C++中,一个空类的 sizeof
结果是多少?哪些情况会影响空类的 sizeof
结果?
在C++中,空类的
sizeof
是1字节,因为每个对象必须有唯一地址,不能为0。空类作为基类时,编译器可能会进行空基类优化(Empty Base Optimization,EBO),使它不占空间。
如果类中有虚函数,即使其它内容为空,类也会包含虚函数表指针,导致
sizeof
变大(如8字节)。在多继承或虚继承中,
sizeof
可能更大,具体取决于编译器实现和对齐要求。
20. 析构函数中抛出异常会怎样?怎么避免呢?
析构函数中抛出异常是危险的,特别是在栈展开过程中(例如另一个异常正在传播时)。
如果一个异常正在传播,又有另一个异常从析构函数中抛出,程序会直接调用
std::terminate()
并终止执行(因为C++不允许同时存在两个未处理异常)。例如:
struct B {
~B() {
throw std::runtime_error(\"Destructor exception\"); //析构函数抛异常
}
};void func() {
B b;
throw std::runtime_error(\"Function error\"); // 栈展开开始
}int main() {
try {
func();
} catch (...) {
std::cout << \"Caught\" << std::endl; //程序终止,不会打印 \"Caught\"。
}
}运行结果:程序终止,不会打印 \"Caught\"。
这是因为:
func()
抛出异常。
b
被销毁,调用~B()
。
~B()
又抛出异常。两个异常都“在路上”,C++ 不允许这种“双异常”,于是直接终止程序。
如何避免?
方法1:不在析构函数中抛异常
这是最推荐的方式。析构函数应该是
noexcept
的,即 绝不抛异常。方法2:捕获异常
如果非得调用可能抛异常的函数,必须用 try-catch 包住。
21. C++11引入了大量新特性,请列出你了解的?
1. 自动类型推导
auto
:自动推导变量类型
decltype
:根据表达式推导类型2. 右值引用与移动语义
减少不必要的拷贝,通过
&&
定义右值引用,支持移动构造函数和移动赋值运算符。3. 智能指针
自动化内存管理,减少内存泄漏:
std::unique_ptr
:独占所有权。
std::shared_ptr
:共享所有权(引用计数)。
std::weak_ptr
:弱引用,避免循环引用。4. Lambda 表达式
匿名函数对象,支持闭包捕获,增强了 STL 算法的易用性。
5. 并发支持
std::thread
用于创建线程;
std::mutex
和锁机制用于同步;
std::condition_variable
支持线程间通信;
std::atomic
实现无锁并发;同时引入了任务式并发模型,如
std::async
、std::future
和std::promise
,让异步编程更现代、更安全。6. 初始化列表
统一的初始化语法,支持自定义类型的列表初始化
7. 范围for循环
for (auto x : container)
简化迭代容器8.nullptr 替代 NULL
类型安全,避免指针与整型混用问题
9. 强类型枚举
enum class
:不隐式转换为整型,更安全10.静态断言
static_assert(condition, \"error message\");
编译时断言11.委托构造函数
构造函数可调用同一类的其他构造函数。
12.默认和删除函数
= default
、= delete
控制特殊成员函数生成13.模板改进
可变参数模板、模板别名(
using
)14.constexpr
在编译期求值的函数或变量,提升性能
22.说一下 C / C++ 中的断言?
C 和 C++ 中的断言都是通过
assert(表达式)
来使用的,定义在头文件(C)或
(C++)中。
它在运行时检查某个条件是否成立,若为假,程序会打印出错信息并立即终止。
通常用于调试阶段捕捉程序逻辑错误,在发布版本中会通过#define NDEBUG
自动禁用,避免性能损耗。
23.说一下C语言中关键字static和C++中关键字static有什么区别?
在 C 和 C++ 中,
static
都用于控制变量的生命周期和作用域,但 C++ 在类中扩展了它的功能,主要作用如下:
static
修饰局部变量:使变量具有静态存储期,只初始化一次,值在函数调用间保留。
static
修饰全局变量或函数:限制其作用域为当前文件(内部链接),防止链接冲突。在 C++ 中,
static
可用于类中,定义静态成员变量:属于整个类共享,不依赖对象。在 C++ 中,
static
也可修饰静态成员函数:只能访问静态成员,没有this
指针。C++11 起,类内
static
常量整型可直接在类内初始化,其他类型需在类外定义。
24.内存泄漏和内存溢出的区别?
内存泄漏
定义:程序在分配内存后,因代码逻辑错误导致该内存无法被释放或回收,最终形成 “无法访问的内存块”。
本质:内存资源未被正确释放,属于资源管理问题。
内存溢出
定义:程序申请的内存超过系统可用内存,导致无法分配新内存,进而引发程序崩溃或异常。
本质:内存需求超过系统供给,属于资源不足问题。
25.了解栈的工作原理吗?
栈(Stack)是一种先进后出(LIFO)的数据结构,在程序运行中,主要用于函数调用管理和局部变量存储。
每当函数调用发生时,系统会将返回地址、参数、局部变量等信息压入栈中,函数返回时再将这些数据弹出,恢复现场。
由于栈空间是由操作系统分配的一段连续内存,栈操作速度快、管理自动,但空间有限,过多递归会导致栈溢出。
26.什么是迭代器?
迭代器是一种用于访问容器中元素的对象,不需要了解容器的底层结构。它类似于一个“指针”,可以通过它顺序遍历容器中的元素。
27.C++中迭代器是怎么用的?
迭代器常用于遍历 STL 容器,比如
vector
、list
、map
等。我们可以通过
begin()
获取容器起始位置的迭代器,通过end()
获取终止位置,然后使用++
遍历,每次用*
解引用访问元素。
28. C++中如何实现一个定时器?
使用
std::this_thread::sleep_for
实现简单延时#include
#include
#includeint main() {
std::cout << \"Start\\n\";
std::this_thread::sleep_for(std::chrono::seconds(2)); // 延时 2 秒
std::cout << \"End after 2 seconds\\n\";
return 0;
}
使用线程 + 回调函数模拟定时器(周期性执行)#include
#include
#include
#include
#include
std::atomic running(true); // 全局变量,控制定时器运行状态// 定时器函数:每隔 interval_ms 毫秒执行一次 callback 函数
void timer(int interval_ms, std::function callback) {
while (running) {
std::this_thread::sleep_for(std::chrono::milliseconds(interval_ms)); // 等待指定时间
callback(); // 调用回调函数
}
}int main() {
// 启动一个线程,设置每 1000 毫秒(1 秒)执行一次
std::thread t(timer, 1000, [ ]() {
std::cout << \"Tick every 1 second\" << std::endl;
});
std::this_thread::sleep_for(std::chrono::seconds(5)); // 主线程等待 5 秒后停止定时器
running = false; // 停止定时器
t.join(); // 等待定时器线程退出std::cout << \"Timer stopped\" << std::endl;
return 0;
}使用
std::async
实现一次性定时回调#include
#include
#includevoid delayedTask() {
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << \"Task runs after 3 seconds\\n\";
}int main() {
std::future fut = std::async(std::launch::async, delayedTask);
std::cout << \"Waiting...\\n\";
fut.get();
return 0;
}
29. 堆的删除操作如何实现?
删除操作通用流程(适用于大根堆和小根堆):
步骤 1:交换堆顶与堆尾元素
步骤 2:移除堆尾(原堆顶元素)
步骤 3:对新堆顶执行“向下调整”操作(Heapify-down)
对大根堆:保持每个父节点 ≥ 子节点;
对小根堆:保持每个父节点 ≤ 子节点。
30. lambda表达式实现原理?
Lambda 表达式本质上是编译器自动生成的一个匿名类(仿函数对象)的实例。
编译器将 lambda 表达式转换为一个带有operator()
成员函数的类,并根据是否捕获变量生成相应的成员变量和构造函数。
31. 你能说说单链表、双向链表、多级链表的区别吗?
单链表:
每个节点包含一个数据域和一个指针域
只能从前往后遍历,插入和删除效率高,但查找效率低。
双向链表:
双向链表每个节点有两个指针:
next
和prev
,可以双向遍历。在需要频繁回退或删除某个节点时更方便,但占用内存多一些。
多级链表:
多级链表指的是节点中除了
next
,还有一个指向子链表的指针(如child
),常见于复杂结构如文件夹树结构、跳表、页面嵌套结构等。(遍历时要考虑递归或堆栈展开)
32. 说一下快速排序的思想?
快速排序基于“分治” 的思想。
它通过选择一个 “基准值(pivot)”,把数组分成两部分:小于 pivot 的放左边,大于 pivot 的放右边,然后对这两部分分别递归排序。
33. 进程通信方式
不同进程间数据不共享,需要借助操作系统提供的通信机制。
34. 线程通信/同步机制
线程间可以直接访问共享内存,但要防止“竞争条件”,需要同步机制。
35. 线程池大小是怎么确定的?为什么不是越多越好?
线程池大小的确定要根据系统的硬件资源(CPU核心数、内存)、任务类型(计算密集 / IO密集)和并发量来综合考虑。
一般来说:
CPU 密集型任务:线程数 ≈ CPU 核心数
n
或n+1
IO 密集型任务:线程数可以设置为
2n
~4n
,因为线程大部分时间在等待
36. 定义一个MIN的宏,然后传入两个参数,返回较小的哪一个
#define MIN(a,b) (((a) > (b)) ? (b) : (a))
37.main()函数原型有哪些?
1.无参数形式,表示程序不接收命令行参数:
int main(void){
//程序入口
return 0;
}
2. 带命令行参数的标准形式:
int main(int argc,char *argv[]){
//argc 表示参数数量(包含程序名)
//argv 是字符串数组,保存各个参数
return 0;
}
等价写法(更现代):
int main(int argc,char **argv){
return 0;
}
补充说明:
main()
必须返回int
,因为返回值通常会传递给操作系统;
return 0;
表示程序正常退出;
return 非0值;
表示异常退出,可以传递错误码。
38. 在 C 语言中,main
函数的参数 argc
和 argv
是什么含义?其中 argc
最小可能是多少?为什么?
在 C 语言中,
main
函数通常定义为int main(int argc, char *argv[])
,用于接收命令行参数。其中,
argc
表示参数的数量,argv
是一个字符串数组,用于存储每个参数的内容。无论是否传入额外参数,操作系统始终会默认传入程序本身的路径作为
argv[0]
,因此argc
的最小值为 1。也就是说,即使用户没有输入任何命令行参数,程序在启动时也至少会接收到一个参数,即程序名称本身。
39. 没有初始化全局变量和局部变量它的默认值是多少?
在 C 语言中,未初始化的全局变量和静态变量会自动初始化为 0,
而局部变量不会初始化,默认值是未定义的随机值,因此必须手动初始化才能安全使用。
40. 怎么理解一个变量的声明和定义?
在 C 语言中,“声明”表示告诉编译器某个变量或函数的名称和类型,但并不分配内存;
而“定义”不仅声明了它,还分配了实际的内存空间。
声明可以出现多次,但定义只能出现一次,否则会造成重复定义错误。
41. static修饰全局变量和局部变量的区别?
修饰全局变量时,限制其作用域为当前源文件(文件私有),防止外部访问;
修饰局部变量时,会延长变量生命周期,使其在多次函数调用间保持值不变。
42. static修饰全局变量和全局变量的区别?
普通全局变量在整个工程范围内都可见,其他文件可以通过
extern
来访问它;
static
修饰的全局变量,其作用域被限制在定义它的.c
文件内部,其他文件无法引用。
43. 函数能不能加static?
函数可以使用
static
修饰。它的作用是将函数的链接属性变为内部链接,使该函数只能在当前源文件中使用,其他文件无法通过extern
调用。
44. 求字符串长度的函数?字符串比较用什么函数?
在 C 语言中,
strlen()
用于获取字符串的长度,不包括结束符\'\\0\'
;
strcmp()
用于比较两个字符串的内容,s1==s2返回值为 0 ,s1s2返回值为正数。
45. 日志等级有哪些?日志文件怎么导出???
参考文章和AI工具:
史上最全C/C++面试、C++面经八股文,一文带你彻底搞懂C/C++面试、C++面经!_c++八股-CSDN博客
C++ 面试题常用总结 详解(满足c++ 岗位必备,不定时更新)_c++面试知识点总结-CSDN博客
C语言基础八股文_c语言八股文-CSDN博客
嵌入式八股文之C语言高频知识点-CSDN博客
ChatGPT 和 豆包