220. static关键字的作用
static关键字在编程中有多种作用:
- 在类的成员变量前使用,表示该变量属于类本身,而不是任何类的实例。
- 在类的成员函数前使用,表示该函数不需要对象实例即可调用,且只能访问类的静态成员变量和其他静态成员函数。
- 在局部变量前使用,表示该变量在函数调用结束后不会被销毁,而是保持其值不变。
- 在全局变量或函数前使用,限制其作用范围仅在定义的文件内,对其他文件不可见。
221. extern关键字的作用
- 允许在多个文件中访问同一个全局变量或者函数
- 表明变量或函数的定义存在于其他文件中
222. 静态内存分配和动态内存分配的区别,静态分配的优缺点
静态内存分配与动态内存分配的区别:
- 静态内存分配在编译时就确定了存储空间的大小和生命周期,通常用于全局变量和静态局部变量。
- 动态内存分配在运行时根据需要动态的分配和释放内存,通常使用new和delete。
静态内存分配的优点:
- 管理简单,没有额外的运行开销。
- 生命周期长,随程序启动创建,程序结束时销毁。
静态内存分配的缺点:
- 灵活性低,必须提前预知并定义所需内存大小。
- 可能会导致内存浪费,若预分配内存未被充分利用。
223. 互斥锁和自旋锁的区别?
互斥锁:
当一个线程获得互斥锁后,其他尝试获得该锁的线程会被挂起(阻塞),直到锁被释放。适用于线程执行时间较长的情况。
自旋锁:
当一个线程尝试获取自旋锁而锁已被占用时,线程会循环等待(自旋),直到锁被释放。适用于线程执行时间非常短的情况,避免了线程挂起的开销。
224. 线程和进程的区别
进程是操作系统进行资源分配和调度的基本单位,每个进程拥有独立的地址空间和系统资源。线程是进程中的执行单元,是CPU调度的基本单位,同一进程中的线程共享该进程的地址空间和资源。
225. 如何进行线程切换的?
线程切换是操作系统的调度器通过保存当前线程的状态到线程的上下文中,然后加载另一个线程的上下文并恢复其状态,这样CPU就可以继续执行新线程的处理。
226. IP寄存器的作用,是通用寄存器么?
IP寄存器,即指令寄存器(在x86架构中称为EIP,在x64架构中称为RIP),其作用是存储下一跳要执行的命令。他不是通用寄存器,因为他有特定的用途,即指向程序的下一条指令,而不能用于通用数据存储或算术逻辑运算。
227. LR寄存器了解么?
LR寄存器是链接寄存器,在ARM架构中常见。他用于存储子程序调用返回后执行的下一条指令的地址,返回地址会存入LR寄存器。这样,在子程序执行完毕后可以通过LR寄存器找到并返回到调用点继续执行。LR寄存器不是通用寄存器。
228. 线程有哪几种状态?
创建(New),就绪(Runnable)、运行(Running)、阻塞(Blocked)、等待(Waiting)、超时等待(Timed Waiting)、终止(Terminated)。
229. 自旋锁等待时线程处于什么状态?互斥锁呢?
自旋锁等待时线程处于运行状态,因为他在忙等待,不停的检查锁是否可用。
互斥锁等待时,线程处于阻塞状态,不占用CPU资源,直到锁成为可用状态。
230. 讲一下你了解的进程调度算法
进程调度算法是决定哪个进程将接下来使用CPU的规则集合。常见的进程调度算法包括:
- 先来先服务(FCFS):按照进程到达的顺序进行调度
- 最短作业优先(SJF):先执行预计运行时间最短的进程。
- 优先级调度:优先执行优先级最高的进程
- 轮转调度(Round Robin,RR):每个进程分配时间片,轮流执行
- 多级反馈队列(Multilevel Feedback Queue,,MFQ):动态调整进程的优先级和时间片。
231. 讲一下C++的智能指针
unique_ptr
:独占所有权,不支持复制和赋值操作。
shared_ptr
:引用计数机制,多个智能指针可以共享一个对象。
weak_ptr
:不对对象的所有权计数,用于解决shared_ptr的循环引用的问题。
232. shared_ptr的底层实现了解么?
shared_ptr底层实现通常包括两个主要部分:
- 控制块:存储引用计数和指向动态分配的对象的指针。
- 智能指针对象:包含对控制块的引用。
233. 讲一下lambda表达式,lambda表达式优点和应用场景
Lambda表达式是C++中的匿名函数,让你能够写出内联的、可调用的代码块,可以捕获并使用其所在作用域中的变量。
优点:
- 简洁:减少了编写单独函数或函数对象的必要
- 方便:可直接在需要的地方定义和使用
- 灵活:能够捕获所在作用域的变量
应用场景:
- 作为回调函数,例如给线程或算法传参
- 在STL算法中,用于自定义排序准则或操作
- 用于定义局部的小功能块,避免编写多余的函数
234. map和unordered_map的区别
map和unordered_map的区别:
map:基于红黑树实现,元素按键自动排序,查找、插入和删除操作的时间复杂度为O(logn)
unordered_map:基于哈希表实现,元素不排序,按哈希值存储;平均情况下查找、插入和删除操作的时间复杂度为O(1),最坏情况为O(n)
235. unordered_map实现了解么?
unordered_map通过哈希表实现。它使用一个哈希函数将键值映射到桶中,并在桶内使用链表处理哈希冲突。当发生哈希冲突时,即多个元素映射到同一个桶,这些元素会以链表的形式存储在该桶中。这样可以通过键的哈希值快速访问到对应的桶,从而进行元素的查找、插入和删除。
236. 哈希冲突是指什么?
哈希冲突是指不同的键通过哈希函数计算后得到相同的哈希值,因此他们被映射到同一个哈希表的存储位置上。
237. 讲一下TCP三次握手
TCP三次握手的过程:
- 客户端发送一个含有
SYN
标志的数据包给服务器,请求连接 - 服务器收到
SYN
包,回应一个含有SYN/ACK
标志的数据包,表示确认 - 客户端收到
SYN/ACK
包,再发送一个ACK
包给服务器,完成连接建立
238. http协议和TCP协议的关系
HTTP协议运行在TCP协议之上,使用TCP提供可靠传输服务来确保数据正确无误的从客户端传输到服务器,或者反过来。
239. https协议和http协议的关系
HTTPS是HTTP协议的安全版本,它通过SSL/TLS
协议提供加密处理数据的功能,保证数据传输的安全性和完整性。
240. 内存分配情况,存放在哪里?
- 堆:动态分配内存,用于存放动态分配的对象
- 栈:自动分配释放,用于存放函数局部变量和函数参数。
- 数据区:存放全局变量和静态变量
- 代码区:存放程序的执行代码
241. 函数参数传递的方式和特点
- 值传递:函数接收参数值的一个版本,原始数据不会被函数修改
- 引用传递:函数接收参数的内存地址(引用),可以直接修改原始数据
242. static和const的区别
static
关键字用来定义静态变量,其生命周期为程序执行期间,但它的作用域限定于定义它的文件或函数内。
const
关键字定义常量,其值在定义后不能被修改,用来保证数据的不变性。他只影响它所修饰的变量的可变性,而不影响其生命周期。
243. static修饰局部变量、全局变量、函数和类各有什么特点?
- 静态局部变量:存储在数据区,生命周期贯穿程序执行期,但只在定义它的函数内可见。
- 静态全局变量:作用域限定于定义它的文件内,对其他文件不可见。
- 静态函数:其链接属性为内部,只能在定义它的文件内使用。
- 静态类成员:属于类本身,而不是类的任何对象,所有对象共享同一个静态成员。
解释:
可能你跟我一样,有种疑问,静态局部变量(static
本地变量)在程序的执行期间都保持存活(生命周期),但只在定义它的函数内可见(作用域)。它们听上去似乎会占用过多的系统资源,但它们有其特定的用途和优点:
静态局部变量的特性
- 生命周期:静态局部变量的生命周期是从程序开始执行到程序结束,生命周期超级长。
- 作用域:它的作用域局限于定义它的函数,换句话说,只能在函数内访问。
为什么需要静态局部变量
- 保持状态:
静态局部变量用于保持函数内部的状态信息。例如,可以用它们来记住函数被调用的次数:
#include <iostream>
void counter() { static int count = 0; count++; std::cout << "Function called " << count << " times\n";
}
int main() { counter(); counter(); counter(); return 0;
} // 输出: // Function called 1 times // Function called 2 times // Function called 3 times
-
优化性能:
对于某些计算密集的任务,静态局部变量可以用作缓存以避免重复计算,从而提高性能。 -
数据持久性:
数据需要在多次函数调用之间保持一致而不需要全局变量时,静态局部变量是一个好用的选择。它能保护局部状态,而无需暴露在全局作用域。
内存占用考虑
-
静态局部变量的内存分配:
静态局部变量在程序的全局数据区(通常称为BSS段或数据段)中分配内存,与堆栈和堆上的变量不同。这种内存分配在编译时进行,因此程序启动时其大小是固定的。 -
资源占用:
虽然它们确实在整个程序生命周期内占用资源,但它们的使用方式通常非常有限,并且在大多数场景中相比较其带来的好处(如状态保持、性能提升等),这点内存消耗是可以接受的。
何时使用静态局部变量
-
需要在多次调用之间保持函数内部状态:
当你希望一个函数保持一些数据或状态信息,以便在下次调用时继续使用这些信息,静态局部变量是理想的选择。 -
不希望全局变量污染命名空间:
静态局部变量提供了一种在函数内部保持状态信息而不需要全局变量的方法,避免了命名冲突和潜在的全局变量滥用。 -
需要持久化缓存或中间结果:
当某些中间计算结果需要被多次使用时,静态局部变量能减少不必要的重新计算,例如在复杂算法或递归过程中。
#include <iostream>
int heavyComputation() { // 假设这是一个很复杂的初始化计算 return 42;
}
int computeSomething() { static int cachedResult = heavyComputation(); // 只会在第一次调用时初始化 return cachedResult + 10; // 假设一个计算操作
}
int main() { std::cout << computeSomething() << "\n"; // 输出: 52 std::cout << computeSomething() << "\n"; // 输出: 52,再次调用时,cachedResult不会被重新计算 return 0;
}
在这个例子中,cachedResult
静态局部变量保留了 heavyComputation()
的计算结果,因此后续 computeSomething()
的调用不需要再次进行复杂的计算。
244. new和malloc、free和delete的区别
- new和delete是C++中用于动态内存分配和释放的操作符,new在分配内存的同时调用构造函数初始化对象,delete释放内存前调用对象的析构函数
- malloc和free是C语言中的用于动态内存分配和释放的函数,malloc只分配内存,不初始化,free只释放内存,不调用析构函数。
245. 指针和引用的区别
- 指针是一个变量,其值为另一个变量的内存地址,通过地址,可以直接直接访问和修改对应内存中的值。
- 引用是别名,他为对象提供了一个新的名字,对引用的操作等同于对对象本身的操作。
- 指针可以为空,引用必须得绑定到一个对象。
- 指针的值(即所指对象的地址)可以改变,但引用一旦与某个对象绑定,就不能再改变引用到其他对象。
246. 深拷贝和浅拷贝
浅拷贝指的是复制对象的引用,而不是对象本身,因此原始对象和副本对象共享同一块内存地址。
深拷贝则是复制对象及其包含的所有对象,副本和原始对象在内存中完全独立。
247. 进程间通信,管道的特点
- 半双工通信:数据只能单向流动,如果需要双向通信,需要创建两个管道。
- 数据流动是顺序的和阻塞的:数据按照发送的顺序接受,如果管道空则读操作阻塞,如果管道满则写操作阻塞。
- 管道是用于有亲缘关系的进程间通信,如父子进程通信。
248. define特点,怎么定义一个a+b的宏
特点:
- 预处理阶段进行文本替换,不涉及类型检查
- 可以定义常量和宏,提高代码复用性
- 宏可以包含参数,但不进行正确性检查
定义一个执行a+b的宏,如下所示:
#define ADD(a+b) ((a)+(b))
这里的(a)和(b)被括起来是为了避免展开时出现的运算优先级问题
249. sizeof和strlen区别
sizeof
是一个编译时运算符,用来得到某个类型或变量在内存中的大小,单位是字节strlen
是一个运行时函数,用来计算字符串的长度,直到遇到第一个空字符’\0’,不包括’\0’。
250. 虚函数的特点
- 支持动态多态性:允许通过基类指针或引用调用派生类的函数。
- 运行时绑定:函数调用在运行时解析,而非编译时
- 存在虚表(vtable):每个有虚函数的类都有一个虚表
- 可以被派生类重写:派生类可以提供自己的虚函数实现
- 必须至少有一个函数体,除非声明为纯虚函数
251. 父类子类构造析构函数调用顺序
- 构造函数调用顺序:首先调用父类构造函数,然后调用子类构造函数。
- 析构函数调用顺序:首先调用子类析构函数,然后调用父类析构函数。
252. 构造析构能否抛出异常?能否是虚函数?
构造函数和析构函数都可以抛出异常,但应该谨慎处理,以避免可能导致的资源泄露或不一致状态。
构造函数不能是虚函数,因为虚函数表在构造时尚未建立。析构函数可以是虚函数,通常在基类中将析构函数声明为虚析构函数,以确保通过基类指针删除派生类对象时,能正确调用派生类的析构函数。
253. 内存对齐
C++内存对齐是为了提高内存访问效率,确保数据结构按照某个固定长度(对齐界限)存储。编译器会自动添加填充字节(padding),使得结构体的每个成员相对于结构体开始位置的偏移量是成员大小或某个特定数值(通常是2的幂)的整数倍。