八股总结-----C++、数据结构、算法
1.内存基础
11.内存分区
代码区:存储可执行代码(程序指令)。
全局区:存储全局变量和静态变量(已初始化和未初始化)。
堆区:用于动态内存分配,由程序员管理。
栈区:存储函数调用中的局部数据(局部变量、参数、返回地址)。
12.堆和栈有什么区别
堆和栈是用来内存分配和释放的两种不同的内存区域,内存管理就是内存分配和释放。栈的存储空间比较小,用于存储局部变量、函数参数、返回值,适合小数据结构的存储,内存分配和释放速度快,自动管理(自动分配、自动销毁)。堆的存储空间较大,用于动态内存分配,适合大型数据结构存储,内存分配和释放速度较慢,手动管理(malloc、new、free、delete)。
2.指针
21.智能指针
智能指针是C++标准库中的一种工具,用于自动管理动态内存,避免内存泄漏和指针悬挂问题。智能指针主要有以下几种类型:
1. std::unique_ptr
- 独占所有权:
unique_ptr
表示独占的所有权,即一个指针对象在任何时刻只有一个所有者。 - 不可复制:
unique_ptr
不能被复制,只有通过std::move
转移所有权。 - 场景:适合用于需要严格的所有权语义的场合,如资源管理。
2. std::shared_ptr
- 共享所有权:
shared_ptr
允许多个智能指针共享同一块内存。当最后一个shared_ptr
释放时,内存才会被释放。 - 引用计数:每个
shared_ptr
都有一个引用计数,用于记录有多少shared_ptr
共享该内存块。 - 场景:适用于需要在多个地方共享资源的情况。
3. std::weak_ptr
- 弱引用:
weak_ptr
不会影响引用计数,不能直接访问资源,需要先提升为shared_ptr
。 - 防止循环引用:常用于打破
shared_ptr
之间的循环引用,避免内存泄漏。 - 场景:适用于需要观察但不拥有资源的场景,如缓存或观察者模式中。
智能指针的作用
- 自动内存管理:智能指针在离开作用域时自动释放资源,避免了手动释放内存的麻烦和错误。
- 防止内存泄漏:通过智能指针的自动内存管理机制,减少了忘记释放内存导致的内存泄漏问题。
- 简化代码:智能指针封装了内存管理细节,简化了资源管理的代码。
22.常量指针和指针常量有什么区别?
看const后面跟的什么,跟的int就是修饰数据,数据不可变,指针指向可变,称为指针变量const int *p。跟的p就是修饰指针,指针指向不可变,数据可变,称为变量指针int * const p。
23.define 和 const 的区别?
作用:用于记录程序中不可更改的数据
C++定义常量两种方式
1. #define 宏常量: #define 常量名 常量值
==通常在文件上方定义==,表示一个常量
2. const修饰的变量 const 数据类型 常量名 = 常量值
==通常在变量定义前加关键字const==,修饰该变量为常量,不可修改
24.static的作用
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员,可以通过对象和类名两种方式访问。静态成员分为:
静态成员变量:1.所有对象共享同一份数据;2.在编译阶段分配内存;3.类内声明,类外初始化;
静态成员函数:1.所有对象共享同一个函数;2.静态成员函数只能访问静态成员变量。(因为静态属于类,非静态属于对象 ,静态成员变量是由类调用的,不用区分是类中的哪个对象调用,但是非静态成员变量是由对象调用的,这时候用静态成员函数去调用非静态成员变量,无法区分到底是哪个对象调用的变量)
静态成员是指属于类本身而不是类的实例的成员。静态成员有以下意义:
共享性:静态成员被所有类的实例共享,可以在不创建类的实例的情况下访问和修改静态成员。
保存状态:静态成员可以用来保存类的状态信息,例如计数器、配置信息等,这些信息可被所有实例共享和访问。
简化访问:可以通过类名直接访问静态成员,无需创建类的实例。这样可以简化代码并提高代码的可读性。
与类相关联:静态成员通常与类本身相关联,而不是与类的实例相关联。这样可以更好地表达类的特性和行为。
3.虚函数
31.什么是虚函数?什么是纯虚函数?
- 虚函数 用于在基类中声明,并允许在派生类中重写,以实现多态性。
- 纯虚函数 是没有实现的虚函数,存在于抽象类中,必须在派生类中实现,否则派生类也将成为抽象类。纯虚函数常用于定义接口,强制派生类提供特定的功能。
析构函数可以是虚函数吗?
4.vector容器
std::vector
是 C++ 标准库中的动态数组,它会根据需要自动调整其容量以容纳更多的元素。std::vector
会在内部维护一个存储空间的容量(capacity
),这个容量通常会大于或等于当前元素的数量(size
)。当向 std::vector
中添加元素并且元素的数量超过当前容量时,vector
会重新分配更大的内存空间来容纳这些元素。这个过程包括:
- 分配新的内存块:
vector
会分配一个更大的内存块以容纳更多的元素。 - 复制现有元素:将旧内存块中的元素复制到新的内存块中。
- 释放旧内存块:释放旧的内存块以释放内存。
std::vector
的初始容量和扩容策略取决于具体的实现,但一般来说,std::vector
的初始容量为 0。也就是说,当你创建一个空的 vector
对象时,它通常没有分配任何内存用于存储元素。
初次扩容
std::vector
的初次扩容通常发生在以下几种情况:
-
添加元素:当你向一个空的
vector
中添加第一个元素时,vector
会进行初次扩容。此时,它会分配一个足够容纳至少一个元素的内存块。许多实现将初始容量设置为较小的值,比如 1 或 4,以提高性能。 -
构造函数:如果你使用特定构造函数创建
vector
,比如vector(size_type count, const T& value)
或vector(std::initializer_list<T> init)
,vector
会根据提供的初始元素数量预分配内存,以减少后续的扩容次数。
扩容策略
在添加更多元素并超过当前容量时,std::vector
会进行扩容。扩容通常遵循以下策略:
- 容量增加:每次扩容时,
vector
的容量通常会增加到当前容量的两倍,这样可以减少扩容的次数和内存分配的开销。(2倍策略通常在性能和内存利用之间提供了一个好的折衷) - 策略实现:不同的标准库实现可能会有不同的策略,但大多数都遵循类似的增量规则以确保高效的内存管理。
扩容过程中,std::vector
需要重新分配内存,复制现有元素到新的内存块中,并释放旧的内存块。这个过程是自动进行的,用户不需要手动处理。
5.介绍几种常用的排序算法
冒泡排序、选择排序、插入排序、快速排序、归并排序、堆排序