c++ 面经

1. 指针、引用

指针: 一个实体,存放指向对象地址的变量。指向一块内存,指向可以改变,有const和非const的区别,甚至可以为空(NULL)。
引用: 变量(内存)的别名,一经定义不可修改,且必须初始化。

int A=10;
int& rA = A;  //引用
int* pA = &A; // 指针
char* p = "hello"; //字符指针
int* arr[10];      //指针数组
int  (*p)[10];     //数组指针
int *p = &A;      //整形指针
float f = 1.0; float* pf = &f;    //单精度浮点型指针
double d = 2.00; double* pd = &d; //双精度浮点型指针

注:指针与对应的变量类型保持一致。若类型不匹配可能存在以下问题:

  1. 错误的数据解释。解引用出错。
  2. 内存访问错误。触发违例,访问错误。
  3. 类型安全问题。破坏类型安全性,难以调试和理解。

野指针(wild): 没有经过初始化的指针。
悬空指针(dangling): 指针指向已经被释放的的内存空间的指针。
规避野指针:

  1. 指针创建立即初始化。
  2. 使用指针过程中,防止指针越界访问。
  3. 指针指向的空间释放,指针立即置为NULL。
  4. 使用指针前进行安全检查。

🔥 指针和引用的区别:

指针引用
NULL存在空(NULL)指针无空引用
初始化定义可不初始化,使用再进行初始化定义必须初始化
指向指向可以改变初始化完毕,指向不可更改,不可引用其他实体
多级存在多级指针(如:二级指针等)无多级引用
访问需要显式解引用,才能获取值(如:*p)编译器处理,无须显式解引用
参数实质是传值,传递的值是指针的地址实质是传地址,传递的是变量的地址
sizeof32位操作系统:4Byte
64位操作系统:8Byte
实体类型的大小(如:int 4Byte)
自增+1指针向后偏移一个类型大小实体自增+1
安全性存在野指针,安全性比引用差安全性好

2. 数组

定义:数组是一种数据结构,用于存储固定大小的同类型元素的集合。每个元素可以通过索引访问,索引从0开始。
特点:

  1. 固定大小:数组的大小在声明时指定,并且在运行时不能更改。
  2. 内存连续性:数组中的元素在内存中是连续存储的。
  3. 索引访问:通过索引可以快速访问数组中的任何元素(索引访问:O(1))。

注:
1.越界访问:访问数组的非法索引会导致未定义行为,C++不会自动检查数组边界。
2.数组与指针:在C++中,数组名通常被当作指针来处理,指向数组的第一个元素。

3. 缺省参数

缺省参数: 声明或者定义函数时,函数的参数有默认值。

// C语言不支持,C++支持
void func1(int x=1, int y=2){} //全缺省
void func2(int x,   int y=2){} //半缺省
int main()
{
func1();
func2(1);
}

规则:
5. 缺省参数必须是从右往左,连续给值,不能间隔。
6. 缺省参数不能同时出现在声明和定义中。
7. 缺省值是常量 /全局变量。

4. 函数重载

定义:同一作用域内,函数名称相同,参数列表(参数类型,参数个数,顺序)不同,构成函数重载。
原理:由于C++ 底层的重命名机制,将函数根据参数的个数,类型,返回值类型做了重命名。
C++底层重命名机制(Name Mangling)
为了支持函数重载,C++编译器采用了一种称为“名称重整”(Name Mangling)的技术。名称重整是指在编译过程中,编译器将每个函数的名称和参数类型编码为一个唯一的标识符,这样在生成目标代码时,即使是同名的函数,也会有不同的符号名,以避免冲突。

5. 内联函数

定义:inline修饰的函数,编译时代码展开,提升程序运行的效率(以空间换时间)。
适用性:不适合长代码,递归,循环。(不建议声明和定义分开,会导致链接错误)。

6. 宏

优点缺点
1. 增强代码复用性
2. 提高性能
1. 不方便调试(预编译宏替换)
2. 导致代码可读性差,可维护性差,容易误用
3. 没有类型安全检查

其他技术替换宏:

  1. 常量定义使用const。
  2. 函数定义使用内联函数。

7. auto

auto: auto 关键字是 C++11 引入的一项功能,它用于自动推导变量的类型。处理 STL 容器和迭代器时特别有用,因为它可以简化代码并减少冗长的类型声明。

类型推导规则:
1.单一变量:根据初始化表达式的类型来推导变量的类型。
2.多个变量:所有变量的类型都将根据第一个变量的初始化表达式进行推导。

注:
1.如果初始化值是 const或引用,auto 会推导成相应的 const 或引用类型。
2.auto 默认情况下推导出的类型是值类型,如果需要引用类型,可以使用 auto&const auto&

8. const

const: 在C++中,const关键字用于定义常量,表示值不可修改。它可以用于变量、指针、函数参数和类成员变量,成员函数等不同场景。

  1. 使用const关键字定义的变量在初始化后不能被修改。
  2. 指针和指针指向的值都可以使用const关键字。
    指向常量的指针: 指针本身可以修改,但不能通过指针修改它指向的值。
    const int* ptr = &x; // 指向常量的指针
    // *ptr = 20; // 错误:不能修改指向的值
    int y = 30;
    ptr = &y; // 合法:可以改变指针指向
    
    常量指针: 指针本身是常量,不能修改指向的地址,但可以通过指针修改指向的值。
    int z = 40;
    int* const ptr2 = &z; // 常量指针
    *ptr2 = 50; // 合法:可以修改指向的值
    // ptr2 = &y; // 错误:不能修改指针指向
    
    指向常量的常量指针: 指针本身和它指向的值都不能修改。
    const int* const ptr3 = &x; // 指向常量的常量指针
    // *ptr3 = 60; // 错误:不能修改指向的值
    // ptr3 = &y; // 错误:不能修改指针指向
    
  3. const成员函数表示不会修改对象的状态,即成员变量的值。
  4. const可以用于函数参数和返回类型,以确保在函数内部不修改传入的参数。
  5. 类中使用const定义的成员变量必须在初始化列表中初始化。

9. 类和对象

类: 类是一个用户定义的数据类型,它描述了对象的属性和行为。类定义了对象的结构和方法,是对象的蓝图。类描述了一组有相同特性(属性)和相同行为的对象。
对象: 对象是类的实例化,是实际存在的实体。通过对象可以访问类的属性和方法。
在C++中,类成员可以有不同的访问权限。
访问控制:

  • public: 公有成员,类外部可以访问。
  • private: 私有成员,只有类的内部可以访问。
  • protected: 受保护成员,只有类的内部和子类可以访问。

注:class 默认是privatestruct 默认是public

10. 类的6个默认成员函数

  • 默认构造函数(Default Constructor):不带参数的构造函数。
    说明:默认构造函数是在没有参数的情况下创建对象时调用的。如果类没有定义任何构造函数,编译器会自动生成一个默认构造函数。
    构造函数的作用:初始化对象,当对象创建时调用构造函数。
    class MyClass {
    public:
    	MyClass() {} // 默认构造函数
    };
    int main() {
     	MyClass obj; // 调用默认构造函数
    	return 0;
    }
    
  • 析构函数(Destructor):用于在对象生命周期结束时清理资源。
    说明:析构函数用于在对象生命周期结束时执行清理操作。它的名称前有一个波浪号(~),并且没有参数和返回值。
    调用阶段
    • 当对象生命周期结束时调用析构函数。
    • 对于栈上的对象(局部变量),当离开作用域时调用析构函数。
    • 对于堆上的对象,当使用delete运算符时调用析构函数。
    • 对于全局对象和静态对象,在程序结束时调用析构函数。
    class MyClass {
    public:
     ~MyClass() {}// 析构函数
    };
    int main() {
    	MyClass obj; // 对象生命周期结束时调用析构函数
    	return 0;
    }
    
  • 拷贝构造函数(Copy Constructor):用于通过另一个同类型对象初始化新对象。
    说明:拷贝构造函数用于通过另一个同类型的对象初始化新对象。它的参数是一个对同类型对象的常量引用。
    class MyClass {
    public:
    	MyClass(const MyClass& other) {} // 拷贝构造函数
    };
    int main() {
    	MyClass obj1;
    	MyClass obj2 = obj1; // 调用拷贝构造函数
    	return 0;
    }
    
  • 赋值运算符重载(Copy Assignment Operator):用于将一个对象赋值给另一个同类型对象。
    说明:赋值运算符用于将一个对象赋值给另一个同类型的对象。它返回对当前对象的引用。
    class MyClass {
    public:
    	MyClass& operator=(const MyClass& other) {
        	if (this != &other) { 
        		// 拷贝赋值逻辑 
        	}
        	return *this;
    	}
    };
    int main() {
    	MyClass obj1;
    	MyClass obj2;
    	obj2 = obj1; // 调用拷贝赋值运算符
    	return 0;
    }
    
  • 移动构造函数(Move Constructor):用于通过另一个同类型的右值对象(临时对象)初始化新对象。
    说明:移动构造函数用于通过另一个同类型的右值对象(临时对象)初始化新对象。它的参数是一个对同类型对象的右值引用。
    class MyClass {
    public:
    	MyClass(MyClass&& other) noexcept {
        	// 移动构造函数
    	}
    };
    int main() {
    	MyClass obj1;
    	MyClass obj2 = std::move(obj1); // 调用移动构造函数
    	return 0;
    }
    
  • 移动赋值运算符(Move Assignment Operator):用于将一个同类型的右值对象(临时对象)赋值给另一个对象。
    说明:移动赋值运算符用于将一个同类型的右值对象(临时对象)赋值给另一个对象。它返回对当前对象的引用。
    class MyClass {
    public:
    	MyClass& operator=(MyClass&& other) noexcept {
        	if (this != &other) {
            	// 移动赋值逻辑
        	}
        	return *this;
    	}
    };
    int main() {
    	MyClass obj1;
    	MyClass obj2;
    	obj2 = std::move(obj1); // 调用移动赋值运算符
    	return 0;
    }
    

11. 初始化列表

初始化列表:在C++中,初始化列表是一种用于在构造函数中初始化类成员的语法。

class A{
public:
	A(int b, int c)
	:_b(b), _c(c)
	{}
private:
	int _b;
	int _c;
};

优点

  • 提高效率:通过初始化列表,成员变量在对象创建时直接初始化,而不是先调用默认构造函数然后再赋值,这样可以避免不必要的赋值操作,提高效率。
  • 支持常量成员和引用成员的初始化:常量成员和引用成员必须在初始化列表中初始化,因为它们在创建后不能被赋值。
  • 支持无默认构造函数的成员初始化:如果成员变量的类型没有默认构造函数,它们必须在初始化列表中显式初始化。

12. this指针

this 指针:C++中的一个特殊指针,它指向调用成员函数的对象本身。每个成员函数都有一个隐含的参数 this,这个参数是一个指向当前对象的指针。通过 this 指针,成员函数可以访问调用它的对象的成员变量和其他成员函数。

特性

  • 在成员函数内部使用。
  • 本质是成员函数的一个形参。(对象调用函数时,将地址作为实参传递给this形参,对象内部不存储this 指针)
  • this 指针是成员函数第一个隐含的指标形参,一般情况下由编译器通过ecx寄存器自动传递,不需要用户传递。

13. C/C++的区别

  • C语言是面向过程语言,注重通过函数解决问题,C++是面向对象语言,注重模块化结构化,可维护性高。
  • C++ 关键字增多,是对C语言的扩展。
  • 源文件后缀名不同:C语言后缀为(.c),C++后缀为(.cpp)。
  • 返回值类型不同:C语言默认为int整型,返回一个随机数(0xcccccccc),C++返回值默认为void。
  • 参数列表,C++必须与声明的参数类型,个数保持一致,C语言无限制。
  • C++支持函数重载,C语言不支持。
  • C++支持指针和引用,C语言支持指针(传值/传址)。
  • C++ 增加命名空间,作用域(A::a),输入输出和C语言也不同。

14. C++ 三大特性

C++的三大特性,封装,继承,多态。
封装:是将数据(成员变量)和操作数据的函数(成员函数)包装在一个类中,从而实现对数据的保护和操作的统一。封装提供了数据隐藏和接口暴露两个重要功能,确保对象的内部状态只能通过公开的接口进行操作,从而提高了代码的安全性和可维护性。

继承:是从一个已有的类(基类或父类)创建一个新类(派生类或子类)的机制。派生类继承了基类的所有非私有成员(属性和方法),并可以扩展或重写这些成员。继承允许代码的重用和类的扩展。

多态:是指同一操作或方法调用在不同对象上可以表现出不同的行为。多态可以通过虚函数和继承实现。主要有两种多态:

  • 静态多态(编译时多态):通过函数重载和运算符重载实现。
  • 动态多态(运行时多态):通过虚函数和继承实现。

🔥 总结:

  1. 封装:通过隐藏类的内部实现细节,只暴露公共接口,提高了数据安全性和代码的可维护性。
  2. 继承:允许从已有的类创建新类,支持代码重用和类的扩展。
  3. 多态:允许通过相同的接口调用不同的实现,支持运行时动态绑定和扩展功能。

15. 结构体内存对齐规则

结构体的内存对齐规则旨在提高内存访问效率。内存对齐涉及如何将结构体的成员变量排列在内存中的问题,以满足特定的对齐要求。

内存对齐的基本概念:

  1. 对齐(Alignment):每个数据类型都有一个对齐要求,即它们在内存中必须按特定的字节边界对齐。例如,4字节对齐意味着数据的地址必须是4的倍数。

  2. 填充(Padding):为了满足对齐要求,编译器在结构体成员之间和末尾可能会插入一些未使用的字节,这些字节称为填充字节。

对齐规则:

  1. 每个成员的偏移量必须是其对齐大小的倍数。对齐大小通常是成员大小,但有时也可以是编译器指定的对齐值。
  2. 结构体的总大小必须是其最大对齐成员的倍数:为了确保数组中的每个结构体实例都正确对齐,结构体的总大小也会被填充到其最大对齐成员的倍数。
    struct Example {
    	char a;   // 1字节
    	int b;    // 4字节
    	short c;  // 2字节
    	float d;  // 4字节
    	double e; // 8字节
    }; //24
    

优缺点

优点缺点
1. 提高内存访问效率,尤其在硬件对齐要求严格的体系结构上。
2. 减少缓存未命中(cache miss)的概率,提高缓存利用率。
1. 增加内存消耗,填充字节占用额外的内存空间。

用途

  1. 访问成员变量和成员函数:this 指针可以用于在成员函数内部访问对象的成员变量和成员函数。
  2. 返回对象本身:this 指针可以用于在成员函数中返回当前对象的引用或指针,以支持链式调用。
  3. 区分成员变量和参数:在成员函数的参数名与成员变量名相同时,可以使用 this 指针区分它们。

初始化顺序,按照声明顺序,而不是初始化列表中的书写顺序。

16. explicit

在C++中,explicit关键字用于修饰构造函数,目的是 防止编译器在不经意间进行隐式类型转换默认情况下,C++允许通过构造函数进行隐式类型转换,这可能导致一些潜在的错误或不明确的行为。

作用

  • 防止隐式转换:explicit关键字告诉编译器,构造函数不应该被用作隐式转换的手段,只能通过显式调用来使用。
  • 增强代码可读性:使用explicit可以使代码更加清晰,避免在类型转换时产生意外的结果。

17. static

在C语言和C++中,static关键字都有多个作用,但在C++中的应用更广泛。

相同与差异

  1. 静态局部变量:定义在函数内部的局部变量,使用static关键字修饰后,这个变量在函数调用之间保持其值不变。

  2. 静态全局变量:定义在文件内部的全局变量,使用static关键字修饰后,这个变量的作用域仅限于定义它的文件。

  3. 静态函数:定义在文件内部的函数,使用static关键字修饰后,这个函数的作用域仅限于定义它的文件。

  4. 类的静态成员变量:属于整个类,而不是某个特定的对象。所有对象共享同一个静态成员变量。
    特性

    • 静态成员在类外定义,初始化。
    • 静态成员没有this指针。
    • 静态成员为所有类对象所共享,不属于某个具体实例。
  5. 类静态成员函数:类的静态成员函数不依赖于具体对象,可以通过类名直接调用。

CC++
1. 静态局部变量
2. 静态全局变量
3. 静态函数
1. 静态局部变量
2. 静态全局变量
3. 静态函数
4. 类的静态成员变量
5. 类的静态成员函数

总结C++中做了扩展,增加了定义类的静态成员变量和静态成员函数。

18. 友元类、友元函数

C++中,友元函数(Friend Function)和友元类(Friend Class)是用于访问类的私有和保护成员的机制。它们提供了一种方式来允许特定的函数或类访问其他类的私有和保护成员。

友元类:是一个被特定类声明为友好的类,这样友元类的所有成员函数都可以访问被声明为友好的类的私有和保护成员

友元函数:是一个被特定类声明为友好的函数,声明时需要加friend关键字。它可以访问该类的所有私有(private)和保护(protected)成员

  • 友元函数不能用const修饰。
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
  • 友元函数不是该类的成员函数,但它们可以访问该类的内部数据。
  • 一个函数可以是多个类的友元函数。
  • 友元函数的调用与普通函数的调用和原理相同。

友元关系的特点

  • 友元关系是单向的,不具有交换性,不能传递。(A是B的友元函数,A同时是C的友元函数,但是B和C不是友元关系)。
  • 避免滥用友元函数和友元类,在一定程度上破坏了类的封装特性。

19. 内部类

定义:可以说两个类符合嵌套关系时,内部类是外部类的友元。
特性:

  • 内部类可以直接访问外部类的static,enum(不需要类名)。
  • sizeof(外部类)只是外部类大小,和内部类无关。

20. 内存管理

  1. 栈(Stack):用于存放函数调用时的局部变量、返回地址、参数等。每调用一个函数,就会在栈上分配一块内存(称为栈帧),当函数返回时,这块内存会被释放。
    特点栈是自动管理的,当函数调用完成,栈帧会自动销毁。栈的内存空间有限,如果函数调用层次太深(例如递归调用过多),可能会发生栈溢出。
  2. 内存映射段(Memory-Mapped Segment):允许程序将文件或设备的内容映射到内存中。程序可以直接访问这些内容而不必调用读写函数,从而提高了I/O操作的效率。
    特点:处理大文件,共享内存,进程间通信等场景。
  3. 堆(Heap):用于动态内存分配,程序可以在运行时使用new和delete(或malloc和free)在堆上分配和释放内存。
    特点:堆的大小并不固定,可以根据程序的需要动态增长或收缩;但必须手动管理,防止内存泄漏。
  4. BSS段(Block Started by Symbol)存放未初始化的全局变量和静态变量。在程序启动时,这些变量会被初始化为零。
    特点:与数据段类似,但这些变量初始值为零。
  5. 数据段(Data Segment)存放已初始化的全局变量和静态变量。这些变量在程序的整个生命周期中都存在。
    特点:在程序启动时,这部分内存已经分配,并在程序结束时释放。
  6. 代码段(Code Segment)存放程序的机器指令,即编译后的代码。代码段是只读的,不会在程序运行时发生变化。
    特点:在程序执行期间,这部分内存通常是只读的,防止程序意外修改指令。
    内存管理

21. 堆上开辟空间(malloc、calloc、realloc、free)

malloc:返回开辟内存大小,需要强转,不强转返回(void*),不初始化,开辟成功返回空间首地址,开辟失败返回NULL。

void* malloc(size_t size);

calloc:同malloc,需要强转,返回(void*),参数不同,给定num,size返回空间大小(num * size )Byte,返回前会初始化空间为0 byte。

void* calloc (size_t num, size_t size);

realloc:重新对空间分配内存大小,若连续空间不够,则重新申请一块新的空间,返回新的内存的地址,否则在原来空间上追加,无合适的空间返回NULL。

void* realloc(void* memblock, size_t size);

free:释放空间,联合使用。

void free(void* memblock);

22. new、delete操作符

作用:申请自定义类型的空间,new会调用构造函数,delete会调用析构函数。底层也是使用malloc/free实现。
特性

  • new/delete 申请/释放单个空间,new[], delete[] 申请/释放连续空间。
  • new 申请失败会抛异常。

malloc/free与new/delete的区别

共同点不同点
1. 都是从堆上申请空间,需要手动释放。1. malloc/free 是函数,new/delete 是操作符。
2. malloc 申请的空间不会初始化,new 会初始化。
3. malloc 申请空间需要计算空间大小并传递,new 只需要跟上相应类型即可。
4. malloc 申请返回值为(void*),需要强转,申请失败须判空(NULL),new 不需要强转,只需要捕获异常即可。
5. malloc/free 只开辟空间,new/delete 会调用构造函数和析构函数,完成对象的初始化和资源清理。

23. 内存泄漏

内存泄漏:导致程序占用越来越多的内存,最终可能导致系统资源耗尽。
堆内存泄漏

  • 动态内存分配后未释放:使用malloc没有free,或者new没有delete,导致空间一直被占用。
    void memoryLeakExample() {
    int* ptr = new int[10];  // 分配了内存
    // 没有对应的 delete[],导致内存泄漏
    }
    
  • 早期返回导致未释放内存:由于条件分支或异常,导致没有到达释放内存的代码。
    void earlyReturnExample(bool condition) {
    	int* ptr = new int[10];
    	if (condition) {
        	return;  // 在返回之前未释放 ptr
    	}
    	delete[] ptr;
    }
    
  • 未释放的对象或资源:类中分配的内存或资源未在析构函数中释放。
    class Example {
    public:
    	Example() { ptr = new int[10]; }
    	~Example() { /* 忘记了 delete[] ptr */ }
    private:
    	int* ptr;
    };
    
  • 循环引用:使用智能指针(如 std::shared_ptr)时,如果存在循环引用,会导致对象无法正确释放。
    struct Node {
    	std::shared_ptr<Node> next;
    };
    
    void circularReferenceExample() {
    	auto node1 = std::make_shared<Node>();
    	auto node2 = std::make_shared<Node>();
    	node1->next = node2;
    	node2->next = node1;  // 循环引用
    }
    

🔥 处理方式:

  • 手动检查:确保每一个 new 或 malloc 都有对应的 delete 或 free。
  • 使用 RAII(Resource Acquisition Is Initialization)模式来确保资源在不再需要时自动释放。
  • 使用智能指针:使用 std::unique_ptr 或 std::shared_ptr 来自动管理动态内存,避免手动释放的麻烦。

系统资源泄漏:比如套接字,文件描述符,管道没有使用对应的函数释放,造成资源浪费。
解决方式:使用检测工具,检测内存泄漏。

  • Linux:使用Valgrind工具,可以在程序运行时检测内存泄漏 。
    valgrind --leak-check=full ./your_program
    
  • Windows:VLD 工具。

24. 智能指针

🔥 智能指针:C++ 标准库中的一种工具,用来自动管理动态内存,避免手动管理内存时容易出现的内存泄漏和指针悬挂等问题。

  1. std::auto_ptr:是C++98 引入的一种智能指针,用于管理动态分配的内存,以避免内存泄漏问题。然而,std::auto_ptr 存在一些严重的缺陷,导致它在 C++11 中被废弃,并在 C++17 中被完全移除。(以下简称:auto_ptr

    特点

    • 独占所有权:auto_ptr 采用独占所有权模型,一个 auto_ptr 对象只能拥有它所指向的资源,无法进行资源共享。
    • 所有权转移:auto_ptr 支持赋值操作,但在赋值过程中,所有权会被转移:
      • 当一个 auto_ptr 赋值给另一个auto_ptr 源指针会丧失对资源的所有权并变为空指针。这种行为可能导致意外的资源转移和潜在的悬挂指针。
      • 不安全的复制语义:因为 auto_ptr 在复制时会转移所有权,这意味着 auto_ptr 不支持普通的复制语义,这种行为很容易导致编程错误,特别是在函数参数传递时。

    缺陷:

    • 不安全的赋值行为:当 auto_ptr 进行赋值操作时,所有权转移的方式是隐式的,容易导致难以预料的行为。

    • 无法与标准容器兼容:因为 auto_ptr 的复制语义,无法安全地将它存储在标准容器(如 std::vector、std::map)中。

      #include <memory>
      #include <iostream>
      
      void autoPtrExample() {
      	std::auto_ptr<int> ptr1(new int(10));
      	std::auto_ptr<int> ptr2 = ptr1;  // ptr1 失去所有权
      
      	std::cout << "ptr1: " << (ptr1.get() ? "Not null" : "Null") << "\n";  // 输出 "Null"
      	std::cout << "ptr2: " << *ptr2 << "\n";  // 输出 "10"
      }
      //ptr1 将失去对所指对象的所有权,ptr2 接管所有权。这种所有权转移的行为是不安全的,可能会导致误用。
      
  2. std::unique_ptr:C++11 引入了std::unique_ptr是一种独占所有权的智能指针,它保证一个对象只能由一个 std::unique_ptr 所拥有。对象的生命周期由 std::unique_ptr 自动管理,当 std::unique_ptr被销毁时,所指向的对象也会被自动销毁。(以下简称:unique_ptr
    特点

    • 如果你需要独占所有权的智能指针,unique_ptr 是 auto_ptr 的现代、安全替代品。
    • unique_ptr 明确要求通过 std::move 来转移所有权,这避免了auto_ptr 那样的隐式错误。

    使用方式:

    #include <memory>
    #include <iostream>
    
    class MyClass {
    public:
    	MyClass() { std::cout << "MyClass constructed\n"; }
    	~MyClass() { std::cout << "MyClass destructed\n"; }
    };
    
    void uniquePtrExample() {
    	std::unique_ptr<MyClass> ptr1(new MyClass());
    	// 或者使用 std::make_unique(C++14 引入)
    	auto ptr2 = std::make_unique<MyClass>();
    
    	// std::unique_ptr 不允许复制
    	// std::unique_ptr<MyClass> ptr3 = ptr1;  // 错误
    
    	// 可以通过 std::move 转移所有权
    	std::unique_ptr<MyClass> ptr3 = std::move(ptr1);
    }
    
  3. std::shared_ptr: 是一种共享所有权的智能指针,多个 std::shared_ptr 可以共同管理同一个对象。当最后一个 std::shared_ptr 被销毁时,所指向的对象才会被释放。

    使用方式

    #include <memory>
    #include <iostream>
    
    class MyClass {
    public:
    	MyClass() { std::cout << "MyClass constructed\n"; }
    	~MyClass() { std::cout << "MyClass destructed\n"; }
    };
    
    void sharedPtrExample() {
    	std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
    	std::shared_ptr<MyClass> ptr2 = ptr1;  // 共享所有权
    
    	std::cout << "Reference count: " << ptr1.use_count() << "\n";  // 输出 2
    }
    
  4. std::weak_ptr:是一种不拥有对象所有权的(弱引用计数)智能指针,它用于解决 std::shared_ptr 的循环引用问题。std::weak_ptr 不会影响对象的引用计数,因此即使所有 std::shared_ptr 都被销毁,对象也会被正确释放。

    使用方式:

    #include <memory>
    #include <iostream>
    
    class MyClass {
    public:
    	std::shared_ptr<MyClass> ptr;  // 循环引用
    	MyClass() { std::cout << "MyClass constructed\n"; }
    	~MyClass() { std::cout << "MyClass destructed\n"; }
    };
    
    void weakPtrExample() {
    	std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
    	std::weak_ptr<MyClass> weakPtr = ptr1;  // 不会增加引用计数
    
    	if (auto sharedPtr = weakPtr.lock()) {  // 需要转换为 shared_ptr 才能使用
        	std::cout << "Object is still alive\n";
    	} else {
        	std::cout << "Object has been destroyed\n";
    	}
    }
    

    作用:解决 shared_ptr 的循环引用问题。

🔥 总结:

  • 使用 std::unique_ptr 管理独占资源。
  • 使用 std::shared_ptr 管理共享资源。
  • 使用 std::weak_ptr 解决 std::shared_ptr 的循环引用问题。

25. 四种转换

26. 继承

27. 静态多态和动态多态

28. 模版类、模版函数

29. 深、浅拷贝

浅拷贝(位拷贝):浅拷贝只会复制指针的值,而不会复制指针所指向的内存。多个对象共用同一份资源,共用一片地址空间。当一个对象销毁,资源被释放,会引起访问出错。

深拷贝:拷贝数据时,先开辟一片新的地址空间,再将数据拷贝过来。
应用场景

  • 动态分配内存:如果你的类包含指针并且动态分配了内存,那么应该使用深拷贝,确保每个对象都拥有独立的资源。
  • 避免共享资源:当你希望对象之间不共享资源,特别是当这些资源会被修改或释放时,应使用深拷贝。

写时拷贝:在构造时,拷贝的资源采用了引用计数,当计数变为0/1时,资源才被释放。触发条件:在修改数据时才触发,不修改就共享(利用拷贝构造)。

缺陷:C++标准认为,当你通过迭代器或者[]获取到string内部地址时,string分不清你是要读还是要写,当你获取到内部引用时,为了避免不能捕获你的操作,它会在此时停止写时拷贝。
:在使用copy on write 时,不要获取string 内部的修改,千万不要通过[]和迭代器获取字符串内部地址引用,否则可能引用失效。

30. 二叉搜索树

31. 红黑树

32. AVL树

33. 哈希

34. STL

35. C++ 11

36. lambda 表达式

37. 进程与线程

38. C++ 线程库

39. B/B+树

40. 异常

41. 四个阶段

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值