1. shared_ptr 的循环引用以及解决方法
为了解决循环引用导致的内存泄露,引入了弱指针weak_ptr,weak_ptr的构造函数不会修改引用计数的值,从而不会对对象的内存进行管理,其类似于一个普通指针,但是不会指向引用计数的共享内存,但是可以检测到所管理的对象是否已经被释放,从而避免非法访问。
2. 构造函数可以是虚函数吗
构造函数不是虚函数。。
从存储空间角度:虚函数对应一个虚表,这个表的地址是存储在对象的内存空间中。如果将构造函数设置为虚函数,就需要到虚表中调用,可以对象还没有实例化,没有内存空间分配,如何调用。
从使用角度:虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有意义,所以构造函数没有必要是虚函数。虚函数的作用在于通过父类的指针或者引用来调用他的时候能够变成调用子类的那个成员函数,而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此就规定构造函数不是虚函数。
从实现角度:虚表在构造函数调用后次啊建立,因而构造函数不可能成为虚函数。从实际含义看,在调用构造函数时还不能确定对象的真实类型而且构造函数的作用是提供初始化,在生命周期中指执行一次。
3. struct 的内存对齐
- 对齐规则:大多数现代计算机系统要求数据类型的地址必须是某个特定边界(通常是数据类型大小的倍数)对齐的。例如,
int
类型通常要求按 4 字节对齐,double
类型通常要求按 8 字节对齐。 - 填充字节:为了保证结构体中每个成员的对齐要求,编译器可能会在结构体成员之间插入一些“填充字节”(Padding),这些字节不会被程序使用,但它们帮助保证结构体按适当的内存边界对齐。
-
结构体对齐:结构体的对齐要求通常是结构体中最大成员的对齐要求。例如,如果结构体包含一个
int
(4字节对齐)和一个double
(8字节对齐),那么整个结构体的对齐要求将是 8 字节(最大对齐要求)。 -
成员对齐:每个成员变量的对齐通常取决于其类型。例如,
char
通常按 1 字节对齐,int
按 4 字节对齐,double
按 8 字节对齐。对于每个成员,编译器会根据它的类型选择适当的对齐方式。
4. 迭代器失效问题
序列容器vector,deque来说,使用erase后,后面的每一个迭代器对象都会失效,后面的元素都往前移一位,erase返回下一个有效的迭代器。
关联容器map,set来说,使用erase后,当前元素的迭代器失效,但是其结构是红黑树,删除当前元素,不会影响下一个元素的迭代器。在调用erase之前,记录下一个元素的迭代器即可。
5. STL容器中哪些不支持迭代器
stack,queue,priority_queue,deque(部分情况)
stack
std::stack
是一个适配器容器,它基于另一个底层容器(通常是std::deque
或std::vector
),但它不提供直接的迭代器支持。std::stack
主要用于在栈顶进行元素操作(push
、pop
、top
),并不提供迭代器或遍历功能。- 原因:栈的特性要求只能在栈顶进行元素访问,不支持随机访问或遍历操作。
queue
std::queue
是一个队列容器适配器,也不支持迭代器。- 它提供
push
、pop
、front
和back
等操作来管理队列中的元素,但不允许直接遍历队列中的元素。 - 原因:队列的设计侧重于先入先出(FIFO)的操作,不支持任意位置的访问,因此没有提供迭代器。
priority_queue
std::priority_queue
是一个优先队列容器适配器,它基于其他底层容器(如std::vector
)实现,但同样不支持迭代器。std::priority_queue
允许根据优先级访问元素,但它的设计目标是保证能够快速插入和删除具有最高优先级的元素,而不是遍历元素。- 原因:优先队列强调优先级的快速访问与调整,而不是随机访问,因此没有提供迭代器。
6. set 存储自定义类型
在C++中,set是一个基于红黑树的有序容器,存储唯一的元素,并且默认情况下按照元素的 < 运算符来进行排序。如果你想在set中存储自定义类型,需要确保两个关键点:
- 自定义类型的比较函数:由于set需要知道如何比较元素以保持有序,你需要提供一个比较函数或者重载 < 运算符来决定元素的排序。
- 保证元素的唯一性:set会确保所有元素是唯一的,因此你需要确保元素的相等性(==运算符)是正确实现的。
7. cookie和session的区别
- cookie和session都是会话的一种方式。服务端通过为该用户创建cookie和session来获取这些信息
- cookie数据存放在客户的浏览器上,session数据放在服务器上。
- cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。
- session会在一定时间内保存在服务器上,当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie。
- 单个cookie保存的数据不能超过4k,很多浏览器都限制一个站点最多保存20个cookie。
8. 函数模版和类模版
在C++中,函数模版和类模版是泛型编程的核心,他们允许我们在编写代码时不关心具体的数据类型,从而提高代码的复用性和灵活性。
函数模版
函数模版使得函数可以操作任意类型的数据,而不需要重复编写多个版本的相似函数。模版通过占位符(通常是typename或者class)来表示一个类型。
template <typename T>
return_type function_name(T arg) {// 函数体
}
类模版
类模板使得类也可以操作任意类型的数据。类模板与函数模板类似,不同的是它是定义一个类模板,可以实例化为多种类型的对象。
template <typename T>
class ClassName {// 类体
};
9. mysql的命名规范
10. union和union all 的区别
11. 什么是构造函数委托
构造函数委托是C++11引入的一项特性,允许一个构造函数调用另一个构造函数来简化代码,避免重复代码。在构造函数委托中,一个构造函数可以直接调用同一个类中的另一个构造函数,以便共享构造代码,减少重复的初始化逻辑。
ClassName::ClassName(/* 参数列表 */): ClassName(/* 参数列表 */) {// 构造函数体
}
- 委托的构造函数通过
: ClassName(/* 参数列表 */)
这种语法调用另一个构造函数。这个调用会在当前构造函数的初始化列表阶段执行,意味着你可以将一些复杂的初始化逻辑委托给一个已有的构造函数。 - 委托的构造函数不能有自己的函数体,否则会导致编译错误。它只负责通过初始化列表调用其他构造函数来完成初始化。
#include <iostream>
using namespace std;class Point {
private:int x, y;public:// 委托构造函数Point() : Point(0, 0) { // 委托给另一个构造函数cout << "Default constructor called" << endl;}// 主构造函数Point(int x, int y) : x(x), y(y) {cout << "Parameterized constructor called" << endl;}void print() {cout << "x: " << x << ", y: " << y << endl;}
};int main() {Point p1; // 调用默认构造函数,实际是委托给参数化构造函数p1.print(); // 输出: x: 0, y: 0Point p2(10, 20); // 直接调用参数化构造函数p2.print(); // 输出: x: 10, y: 20return 0;
}
优点
- 减少代码重复:通过委托构造函数,可以避免在多个构造函数中重复相同的初始化代码。
- 简化维护:如果构造函数的初始化逻辑发生了变化,只需要修改一个地方,避免了多处修改。
12. 解释一下C++中的RAII原则
RAII(Resource Acquisition Is Initialization) 是 C++ 编程中非常重要的一个原则,它的核心思想是:资源的获取与对象的生命周期绑定。即,当一个对象被创建时,它会获取某种资源,而当该对象销毁时,所获取的资源会被自动释放。
RAII 的关键要素:
- 资源管理:RAII 主要用于管理各种资源,如内存、文件句柄、数据库连接、网络套接字等。资源在对象的构造函数中获取(初始化),在对象的析构函数中释放。
- 对象的生命周期:对象的生命周期由其作用域决定。当对象超出作用域时,它的析构函数会自动被调用,从而释放资源。
RAII 如何工作?
RAII 利用 C++ 的对象生命周期特性:当对象创建时,构造函数会进行资源的分配或初始化,当对象销毁时(离开作用域或者被显式删除时),析构函数自动释放资源。这样,我们无需手动管理资源的释放,避免了资源泄露或忘记释放的风险。
RAII 的优势
- 自动化资源管理:RAII 确保资源在对象生命周期内被正确管理,无需显式释放资源。这避免了内存泄漏、文件未关闭等问题。
- 异常安全:RAII 提供了异常安全的资源管理。在异常发生时,栈上的对象会被销毁,从而确保在异常时资源能够被正确释放。
- 简化代码:由于 RAII 将资源管理与对象的生命周期绑定,代码更加简洁,减少了手动管理资源的复杂性。
RAII 的应用场景
RAII 原则广泛应用于 C++ 中的资源管理,以下是一些常见的应用:
- 内存管理:
std::vector
和std::string
等标准库容器类会在对象销毁时自动释放其分配的内存。 - 文件处理:
std::ifstream
和std::ofstream
类会在文件流对象销毁时自动关闭文件。 - 互斥锁:
std::lock_guard
和std::unique_lock
类在对象销毁时会自动释放锁,避免死锁。
RAII 与异常安全
RAII 的另一个显著优点是它能提高程序的异常安全性。当抛出异常时,栈上的对象会被销毁,从而保证在异常发生时资源会被自动释放,无需担心忘记释放资源的错误。例如,假设我们在函数中申请了内存并打开了文件,而在某个地方抛出了异常,RAII 会确保即使在异常发生时,析构函数也会被调用,从而释放这些资源。
13. C++中的static和const的区别是什么
1. static
的作用
static
关键字用于控制变量或函数的生命周期和可见性。它的作用可以根据上下文不同而有所不同。
a. 局部变量中的 static
- 在函数内部声明为
static
的变量,会保持其值在函数调用之间的持续性,即变量的生命周期将延续至程序结束。 - 这意味着该变量不会在每次函数调用时重新初始化,而是只有在第一次调用时初始化一次,之后的调用会保持该变量的状态。
b. 全局或类成员中的 static
- 如果在全局或类成员变量中使用
static
,它意味着该变量的作用域仅限于定义它的文件或类中,而在其他文件中不可访问。 - 对于类的静态成员,它使得成员变量或函数不依赖于类的实例而存在,可以直接通过类名来访问。
2. const
的作用
const
关键字用于定义常量,表示该变量在初始化后不能修改。它也可以与其他关键字(如 static
)一起使用,来限定常量的作用域和生命周期。
a. 常量变量中的 const
- 使用
const
声明的变量表示该变量的值在初始化后不能修改。如果你尝试修改const
变量,编译器会报错。
b. 常量指针和指针常量
const
可以修饰指针,表示指针本身或指针指向的数据是不可变的。
3. static
和 const
组合
static const
通常用于定义文件作用域内不可修改的常量。它结合了static
(局部作用域)和const
(不可修改)的特性,意味着该常量的值在整个程序中不可修改,并且其作用域仅限于当前文件。
14. 解释一下C++中的类型推导
在 C++ 中,类型推导(type deduction)是编译器根据上下文自动推导出变量的类型的一种机制。C++11 引入了更强大的类型推导功能,使得程序员在声明变量时无需显式指定类型,编译器会根据初始化的值推导出正确的类型。
类型推导的常见方式
-
auto
关键字: C++11 引入了auto
关键字,允许编译器根据表达式的类型来自动推导变量的类型。这使得代码更加简洁,并且减少了出错的可能性,尤其在涉及复杂类型(如迭代器)时。 -
decltype
关键字:decltype
用于推导表达式的类型,但它不会执行任何赋值或计算,而是返回一个表达式的类型。decltype
适用于更复杂的情况,尤其当你需要基于某个表达式的类型来声明变量时。 -
模板中的类型推导: C++11 还允许在模板中通过类型推导来推断类型。例如,函数模板中,可以通过推导来确定模板参数的类型。