C++基础知识
1、C++内存分区?栈和堆的区别
2、静态局部变量和全局变量、局部变量的区别、在内存上是怎么分布的?
简要回答
- 静态局部变量:函数内定义,用
static
修饰,生命周期为整个程序,该变量无法在作用域外修改,只能被初始化一次,之后每次调用都保持上以上调用结束时的值,存储在数据段。 - 全局变量:函数外定义,生命周期是整个程序运行区间,程序中可以在任何地方访问,创建在堆上。
- 局部变量:函数内定义,作用域和生命周期只能在声明该变量的函数体内或者类内访问,每次调用重新创建,生命周期随着函数的返回而结束,创建在栈上。
静态局部变量,全局变量,局部变量的使用场景如下表所示
分类 | 局部变量 | 静态局部变量 | 全局变量 |
---|---|---|---|
作用域 | 当前函数或者代码块内 | 当前函数内部(外部不能访问) | 整个程序内 |
生命周期 | 每次进入函数创建,用完就没 | 程序一运行就存在,一直到程序结束 | 程序启动时创建,程序结束才销毁 |
初始化行为 | 不赋值的话值不确定(是随机的) | 初始化一次(默认0),之后值会保留 | 默认初始化为0 |
存储位置 | 栈区(stack),速度快但不持久 | 数据段或 BSS 段(非栈),可长期保存 | 数据段或 BSS 段,生命周期长 |
适合场景 | 做临时运算,比如循环里的变量、临时数组等 | 想在函数里“记住之前的值”,如统计次数、递归深度控制等 | 整个程序都需要共享的变量,比如配置信息、缓冲区等 |
详细回答
全局变量
全局变量定义在所有函数外面,整个程序都能访问,它从程序启动时就存在。
若在其他文件中使用,需要用extern关键字声明,默认也会被初始化为0,和静态局部变量一样存储在静态存储区。
若用”static”修饰的全局变量,仅限当前文件使用,避免与其他文件同名变量冲突。
静态局部变量
在函数内部定义,使用static关键字声明,比如”static int count”,其生命周期不随函数调用结束而销毁,可保留上次的值。
第一次调用函数时初始化后,之后每次调用都不会重新初始化,而是保持上次的值,这种变量被放在一个特殊的内存区域(静态存储区),所以函数执行完也不会被销毁。
它默认会被自动初始化为0,适合用来做计数器或者缓存一些需要在多次函数调用之间保持的数据。
局部变量
在函数或代码块内部定义。这种变量的特点是“随用随弃”,随函数调用创建、执行结束后销毁,存储在栈内存中,存取速度很快,如果不初始化,它的值会是随机的(这点和全局变量、静态变量不同)。
需要注意的是,如果局部变量和全局变量同名,在函数内部会优先使用局部变量,相当于暂时”屏蔽”了同名的全局变量。
局部变量最适合存放一些临时数据,比如循环计数器、函数内部的中间计算结果等,用完就自动释放,不会占用额外内存。
怎么用
全局变量是那种“很多地方都要用”的数据,比如配置参数、程序状态什么的,放在最外面,全局共享。但不建议乱用,容易让程序变得混乱、难维护,非必要别用它,用的话要写清楚、注释好。
静态局部变量适合在一个函数里需要“记住上一次的值”的情况,比如要在函数里做一个计数器、记录某个初始化状态,只想执行一次,就加个 static,函数每次运行,它都能记得上一次的结果。
局部变量最常用,能用就用它,它只在当前函数里生效,用完就销毁,最干净、安全、不容易出错,处理临时数据、普通运算,直接写局部变量就够了。
静态局部变量,全局变量,局部变量的代码演示
- 静态局部变量示例
|
- 全局变量示例
|
- 局部变量示例
void calculate() |
3、指针和引用的区别?
简要回答
- 指针:是一个变量,存储另一个变量的内存地址,使用时需要引用(
*
)访问目标值。可重新赋值指向其他对象,支持指针算术(如++),可为空(nullptr),指针可以有const。
占用独立的内存(通常是4或8字节),需要手动管理动态内存。
- 引用:是变量的别名,绑定后不能修改,且不能为空,使用时无需解引用,引用没有const。
- 区别:不存在指向空值的引⽤,但是存在指向空值的指针。
4、类、成员变量、成员函数、对象和实例?
5、static 关键字和 const 关键字的作用?
static 关键字
static
主要控制变量和函数的
生命周期、作用域。分为,
- 静态全局变量:使全局变量仅在当前源文件可见,程序启动时初始化,程序结束时销毁,存储在全局/静态存储区。
- 静态局部变量:使局部变量的声明周期延长至整个程序运行期,但作用域仍限制在函数/代码块内。
- 静态函数:使函数仅在当前源文件可见。
- 静态成员变量:使类的成员变量属于类本身,所有对象共享同一份数据。
- 静态成员函数:使类的成员函数不依赖对象实例,可直接通过类名调用,不能访问非静态成员。
1. 静态全局变量(Static Global Variable)
作用
- 使全局变量仅在当前源文件可见,程序启动时初始化,程序结束时销毁,存储在全局/静态存储区。
特性
特性 | 说明 |
---|---|
作用域 | 当前源文件(文件作用域) |
生命周期 | 程序启动时初始化,程序结束时销毁(存储在全局/静态存储区) |
链接性 | 内部链接(其他文件无法通过
extern 访问) |
初始化 | 默认初始化为
0 (基本类型),只能初始化一次 |
示例
// File1.c |
2. 静态局部变量(Static Local Variable)
作用
- 使局部变量的生命周期延长至整个程序运行期,但作用域仍限制在函数/代码块内。
特性
特性 | 说明 |
---|---|
作用域 | 函数/代码块内部 |
生命周期 | 程序启动时初始化,程序结束时销毁(存储在全局/静态存储区) |
初始化 | 只初始化一次,默认
0 (基本类型) |
示例
void counter() |
3. 静态成员变量(Static Member Variable)
作用
- 使类的成员变量属于类本身,所有对象共享同一份数据。
特性
特性 | 说明 |
---|---|
作用域 | 类作用域(通过
类名::变量 访问) |
生命周期 | 程序启动时初始化,程序结束时销毁 |
存储位置 | 全局/静态存储区(不在对象内存中) |
初始化 | 必须在类外单独定义(C++17
前)或使用 inline static (C++17+) |
示例
class MyClass |
4. 静态函数(Static Function)
作用
- 使函数仅在当前源文件可见。
特性
特性 | 说明 |
---|---|
作用域 | 当前源文件 |
链接性 | 内部链接(其他文件无法调用) |
示例
// utils.c |
5. 静态成员函数(Static Member Function)
作用
- 使类的成员函数不依赖对象实例,可直接通过类名调用,不能访问非静态成员。
特性
特性 | 说明 |
---|---|
调用方式 | 通过
类名::函数() 调用,无需对象实例 |
this 指针 |
无
this 指针,因此不能访问非静态成员 |
用途 | 操作静态成员变量、工具函数、工厂模式等 |
示例
class MathUtils |
const 关键字
const
关键字主要用于指定变量、指针、引用、成员函数等不可修改的的性质。
6、常量指针和指针常量有什么区别?
7、C++的成员访问限定符?
8、结构体 struct 和类 class 的区别?
9、new 和 malloc 有什么区别?
10、delete 和 free 有什么区别?
11、什么是智能指针,C++有哪几种智能指针?
C++中的智能指针是用于管理动态分配的内存资源,避免内存泄漏和指针悬空问题的一种工具,它利用了RAII技术,在对象构造时获取资源,在对象析构时自动释放资源。
C++中提供了以下三种类型的智能指针:
std::unique_ptr
是一个独占所有权的智能指针,意味着同一时间只能有一个unique_ptr
指向一个给定的对象,当std::unique_ptr
被销毁时,它所指向的对象也会被自动删除。
|
std::shared_ptr
是一个共享所有权的智能指针,多个shared_ptr
可以指向同一个对象,它使用引用计数机制来跟踪有多少个shared_ptr
共享同一个对象,当最后一个shared_ptr
被销毁或重置时,所拥有的对象也会被自动销毁。
class MyClass
{
public:
MyClass() { std::cout << "MyClass constructor" << std::endl; }
~MyClass() { std::cout << "MyClass destructor" << std::endl; }
void doSomething() { std::cout << "Doing something..." << std::endl; }
};
int main()
{
// 创建一个 std::shared_ptr 并指向一个新的 MyClass 对象
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
std::cout << "Reference count: " << ptr1.use_count() << std::endl;
// 复制 ptr1 到 ptr2,引用计数加 1
std::shared_ptr<MyClass> ptr2 = ptr1;
std::cout << "Reference count: " << ptr1.use_count() << std::endl;
ptr1->doSomething();
ptr2->doSomething();
return 0;
}std::weak_ptr
弱引用,不拥有对象的所有权,不会增加引用计数,主要用于解决std::shared_ptr
可能存在的循环引用问题,可以从std::shared_ptr
或另一个std::weak_ptr
构造,可以通过lock()
方法获取一个std::shared_ptr
来访问对象,如果对象已销毁,则返回一个空的std::shared_ptr。
循环引用
由于对象之间相互持有对方的
std::shared_ptr
,使得它们的引用计数永远不会降为
0,因此形成了循环引用,循环引用最主要的危害就是导致内存泄漏。
|
- 对象创建:在
main
函数中,使用std::make_shared
创建了Parent
和Child
类型的对象,并分别用std::shared_ptr
进行管理。此时,Parent
和Child
对象的初始引用计数都为 1。 - 循环引用形成:通过
parent->child = child;
和child->parent = parent;
这两条语句,parent
对象持有了对child
对象的引用,child
对象也持有了对parent
对象的引用。这样,child
和parent
对象的引用计数都变为 2。 - 引用计数无法归零:当
main
函数结束时,parent
和child
这两个std::shared_ptr
会离开作用域并被销毁,它们所指向对象的引用计数会减 1,但是由于循环引用的存在,parent
和child
对象的引用计数仍然为 1,不会降为 0,因此,这两个对象不会被销毁,析构函数也不会被调用,造成了内存泄漏。
修改后的代码
|
12、智能指针的实现原理是什么?
13、什么是内存泄漏,什么操作会导致内存泄漏,如何检测和防止?
14、什么是野指针?在什么情况下会产生野指针?如何避免?
15、C++面向对象三大特性?
16、简述一下C++的重载和重写,以及它们的区别和实现方式?
17、C++怎么实现多态?
18、虚函数和纯虚函数的区别?
19、虚函数怎么实现的?
20、虚函数表是什么?
21、什么是构造函数和析构函数?
22、构造函数、析构函数可以是虚函数吗?
23、C++构造函数有哪几种,分别什么作用?
24、深拷贝与浅拷贝的区别?
25、什么是 STL,包含哪些组件?
26、STL 容器了解哪些?
简要回答
C++ STL 中主要有三类容器:
顺序型容器(Sequence Containers):如 vector, list, deque, array, forward_list
关联型容器(Associative Containers):
- 有序关联容器,如 set, map, multiset, multimap
- 无序关联容器,如 unordered_map, unordered_set, unordered_multimap, unordered_multiset
STL的容器的特点如下
- 顺序容器
容器 | 特点 |
---|---|
vector |
动态数组,数组大小动态可变,随机访问快,尾部插入快 |
list |
双向链表,任意位置插入/删除快 |
deque |
双端队列,头尾都能快速插入/删除 |
array |
固定大小数组,随机访问更快 |
forward_list |
单向链表,仅支持单向遍历 |
- 关联容器(有序,基于红黑树)
容器 | 特点 |
---|---|
set |
键唯一,按键自动排序 |
multiset |
允许键重复 |
map |
键值对,键唯一,按键自动排序 |
multimap |
键值对,键可重复 |
- 无序容器(基于哈希表,C++11)
容器 | 特点 |
---|---|
unordered_set |
无序唯一集合 |
unordered_map |
无序键值对 |
unordered_multiset |
无序可重复集合 |
unordered_multimap |
无序可重复键值对 |
27、vector 和 list 的区别?
- vector,当需要频繁随机访问,或者主要在尾部添加/删除元素时使用。
- list,当需要在容器中间频繁插入和删除,并且不需要随机访问时使用。
28、vector 底层原理和扩容过程?
vector 底层通过动态数组实现,内部维护一个指针,指向堆上分配的一段连续内存。
vector 内部实现了一个内存分配函数,内存不够时会申请一块原内存1.5-2倍大小的新内存,再把旧的数据复制到新的内存中。
29、push_back() 和 emplace_back() 的区别?
push_back
:创建一个元素的副本或移动该元素,然后将其添加到向量的末尾。emplace_back
:在向量的末尾就地构造元素,避免了额外的复制或移动。
30、map、deque、list的实现原理
31、map && unordered_map 的区别和实现机制
map 底层是基于红⿊树实现的,因此 map 内部元素排列是有序的。 ⽽ unordered_map 底层则是基于哈希表实现的,因此其元素的排列顺序是杂乱⽆序的。
32、C++11新特性有哪些?
语法的改进
(1)统一的初始化方法
(2)成员变量默认初始化
(3)auto关键字:允许编译器自动推断变量的类型,减少类型声明的冗余。
(4)decltype 求表达式的类型
(5)智能指针 std::shared_ptr 和 std::unique_ptr
(6)空指针 nullptr: 提供了明确表示空指针的关键字,替代了传统的
NULL
。(7)基于范围的for循环: 简化遍历容器元素的语法
(8)右值引用和move语义 引入右值引用和移动构造函数,允许高效地将资源从一个对象移动到另一个对象,提高性能。
标准库扩充(往STL里新加进一些模板类)
(9)无序容器(哈希表) 用法和功能同map一模一样,区别在于哈希表的效率更高
(10)正则表达式 可以认为正则表达式实质上是一个字符串,该字符串描述了一种特定模式的字符串
(11)Lambda表达式: 允许在代码中定义匿名函数
33、说一下 lambda 表达式?
lambda 表达式,也叫做匿名函数,是在 C++11标准中引入的一种方便的函数编写方式。lambda 表达式允许你在需要一个函数对象的地方快速定义函数的行为,而不需要按照传统方式定义一个完整的函数。
34、移动语义有什么作用,原理是什么?
35、左值引用和右值引用的区别?
简要回答
左值引用(&
)绑定具名对象(可寻址),用于避免拷贝;右值引用(&&
)绑定临时对象(不可寻址),用于移动语义和资源转移。
底层绑定逻辑
- 左值引用:编译器生成指针,指向已存在的具名对象(内存有明确位置)。
- 右值引用:编译器生成指针,指向临时值(通常在寄存器或即将销毁的内存)。
典型场景
void process(int& lval); // 左值引用:修改传入的变量
void process(int&& rval); // 右值引用:可安全“掏空”临时值
int a = 10;
process(a); // 调用左值版本
process(10); // 调用右值版本
process(std::move(a)); // 强制转为右值引用(危险操作!a可能被掏空)汇编视角
- 左值引用:类似指针操作(
mov eax, [addr]
)。 - 右值引用:直接操作寄存器或优化后的临时内存(避免
memcpy
)。
- 左值引用:类似指针操作(
移动语义的底层实现 移动构造函数通过右值引用直接“窃取”资源指针(如
vector
内部数组指针),将源对象指针置nullptr
,避免双重释放。引用折叠规则 模板中
T&&
可能是通用引用(Universal Reference),根据传入参数推导类型:T = int&
→T&& = int&
T = int
→T&& = int&&
完美转发原理
std::forward<T>()
基于引用折叠,保留原始值类别(左值/右值),常用于泛型代码中参数传递。生命周期延长 const左值引用可绑定右值(
const string& s = "hello";
),但右值引用更高效(避免隐式拷贝)。