台州网站建设方案策划,拼客多网站多少钱可以做,聊城网站那家做的好,如何建设手机网站首页基础知识 1. 指针、引用2. 数组3. 缺省参数4. 函数重载5. 内联函数6. 宏7. auto8. const9. 类和对象10. 类的6个默认成员函数11. 初始化列表12. this指针13. C/C的区别14. C 三大特性15. 结构体内存对齐规则16. explicit17. static18. 友元类、友元函数19. 内部类20. 内存管理的区别14. C 三大特性15. 结构体内存对齐规则16. explicit17. static18. 友元类、友元函数19. 内部类20. 内存管理虚拟地址空间21. 堆上开辟空间malloc、calloc、realloc、free22. new、delete操作符23. 内存泄漏24. 智能指针25. 四种转换26. 继承27. 多态28. 模版类、模版函数29. 深、浅拷贝30. 二叉搜索树31. 红黑树32. AVL树33. 哈希34. STL35. C 1136. lambda 表达式37. 进程与线程38. C 线程库39. B/B树40. 异常41. 程序运行四个阶段 1. 指针、引用
指针 一个实体存放指向对象地址的变量。指向一块内存指向可以改变有const和非const的区别甚至可以为空NULL。 引用 变量内存的别名一经定义不可修改且必须初始化。
int A10;
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; //双精度浮点型指针注指针与对应的变量类型保持一致。若类型不匹配可能存在以下问题
错误的数据解释。解引用出错。内存访问错误。触发违例访问错误。类型安全问题。破坏类型安全性难以调试和理解。
野指针(wild) 没有经过初始化的指针。 悬空指针(dangling) 指针指向已经被释放的的内存空间的指针。 规避野指针
指针创建立即初始化。使用指针过程中防止指针越界访问。指针指向的空间释放指针立即置为NULL。使用指针前进行安全检查。 指针和引用的区别
指针引用NULLnullptr存在空NULL指针无空引用初始化定义可不初始化使用再进行初始化定义必须初始化指向指向可以改变初始化完毕指向不可更改不可引用其他实体多级存在多级指针如二级指针等无多级引用访问需要显式解引用才能获取值如*p编译器处理无须显式解引用参数实质是传值传递的值是指针的地址实质是传地址传递的是变量的地址sizeof32位操作系统4Byte 64位操作系统8Byte实体类型的大小如int 4Byte自增1指针向后偏移一个类型大小实体自增1安全性存在野指针安全性比引用差安全性好
2. 数组
定义数组是一种数据结构用于存储固定大小的同类型元素的集合。每个元素可以通过索引访问索引从0开始。
特点
固定大小数组的大小在声明时指定并且在运行时不能更改。内存连续性数组中的元素在内存中是连续存储的。索引访问通过索引可以快速访问数组中的任何元素索引访问O(1)。
注 1越界访问访问数组的非法索引会导致未定义行为C不会自动检查数组边界。 2数组与指针在C中数组名通常被当作指针来处理指向数组的第一个元素。
3. 缺省参数
缺省参数 声明或者定义函数时函数的参数有默认值。
// C语言不支持C支持
void func1(int x1, int y2){} //全缺省
void func2(int x, int y2){} //半缺省
int main()
{
func1();
func2(1);
}规则
缺省参数必须是从右往左连续给值不能间隔。缺省参数不能同时出现在声明和定义中。缺省值是常量 /全局变量。
4. 函数重载
定义同一作用域内函数名称相同参数列表参数类型参数个数顺序不同构成函数重载。 原理由于C 底层的重命名机制将函数根据参数的个数类型返回值类型做了重命名。 C底层重命名机制Name Mangling 为了支持函数重载C编译器采用了一种称为“名称重整”Name Mangling的技术。名称重整是指在编译过程中编译器将每个函数的名称和参数类型编码为一个唯一的标识符这样在生成目标代码时即使是同名的函数也会有不同的符号名以避免冲突。
5. 内联函数
定义inline修饰的函数编译时代码展开提升程序运行的效率以空间换时间。 适用性不适合长代码递归循环。不建议声明和定义分开会导致链接错误。
6. 宏
优点缺点1. 增强代码复用性 2. 提高性能1. 不方便调试预编译宏替换 2. 导致代码可读性差可维护性差容易误用 3. 没有类型安全检查
其他技术替换宏 1常量定义使用const。 2函数定义使用内联函数。
7. auto
auto: auto 关键字是 C11 引入的一项功能它用于自动推导变量的类型。处理 STL 容器和迭代器时特别有用因为它可以简化代码并减少冗长的类型声明。
类型推导规则 1单一变量根据初始化表达式的类型来推导变量的类型。 2多个变量所有变量的类型都将根据第一个变量的初始化表达式进行推导。
注 1如果初始化值是 const或引用auto 会推导成相应的 const 或引用类型。 2auto 默认情况下推导出的类型是值类型如果需要引用类型可以使用 auto 或 const auto。
8. const
const: 在C中const关键字用于定义常量表示值不可修改。它可以用于变量、指针、函数参数和类成员变量成员函数等不同场景。
使用const关键字定义的变量在初始化后不能被修改。指针 和指针指向的值都可以使用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; // 错误不能修改指针指向const成员函数表示不会修改对象的状态即成员变量的值。const可以用于函数参数和返回类型以确保在函数内部不修改传入的参数。类中使用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寄存器自动传递不需要用户传递。static 成员不含this指针。
用途
访问成员变量和成员函数this 指针可以用于在成员函数内部访问对象的成员变量和成员函数。返回对象本身this 指针可以用于在成员函数中返回当前对象的引用或指针以支持链式调用。区分成员变量和参数在成员函数的参数名与成员变量名相同时可以使用 this 指针区分它们。
13. C/C的区别
C语言是面向过程语言注重通过函数解决问题C是面向对象语言注重模块化结构化可维护性高。C 关键字增多是对C语言的扩展。源文件后缀名不同C语言后缀为.cC后缀为.cpp。返回值类型不同C语言默认为int整型返回一个随机数0xccccccccC返回值默认为void。参数列表C必须与声明的参数类型个数保持一致C语言无限制。C支持函数重载C语言不支持。C支持指针和引用C语言支持指针传值/传址。C 增加命名空间作用域A::a输入输出和C语言也不同。
14. C 三大特性
C的三大特性封装继承多态。 封装是 将数据成员变量和操作数据的函数成员函数包装在一个类中从而实现对数据的保护和操作的统一。封装提供了数据隐藏和接口暴露两个重要功能确保对象的内部状态只能通过公开的接口进行操作从而提高了代码的安全性和可维护性。 继承是从一个已有的类基类或父类创建一个新类派生类或子类的机制。 派生类继承了基类的所有非私有成员属性和方法并可以扩展或重写这些成员。继承允许代码的重用和类的扩展。
多态是指 同一操作或方法调用在不同对象上可以表现出不同的行为。多态可以通过虚函数和继承实现。主要有两种多态
静态多态编译时多态通过函数重载和运算符重载实现。动态多态运行时多态通过虚函数和继承实现。 总结
封装通过隐藏类的内部实现细节只暴露公共接口提高了数据安全性和代码的可维护性。继承允许从已有的类创建新类支持代码重用和类的扩展。多态允许通过相同的接口调用不同的实现支持运行时动态绑定和扩展功能。
15. 结构体内存对齐规则
结构体的内存对齐规则旨在提高内存访问效率。内存对齐涉及如何将结构体的成员变量排列在内存中的问题以满足特定的对齐要求。
内存对齐的基本概念:
对齐Alignment每个数据类型都有一个对齐要求即它们在内存中必须按特定的字节边界对齐。例如4字节对齐意味着数据的地址必须是4的倍数。填充Padding为了满足对齐要求编译器在结构体成员之间和末尾可能会插入一些未使用的字节这些字节称为填充字节。
对齐规则:
每个成员的偏移量必须是其对齐大小的倍数。对齐大小通常是成员大小但有时也可以是编译器指定的对齐值。结构体的总大小必须是其最大对齐成员的倍数。为了确保数组中的每个结构体实例都正确对齐结构体的总大小也会被填充到其最大对齐成员的倍数。struct Example {char a; // 1字节int b; // 4字节short c; // 2字节float d; // 4字节double e; // 8字节
}; //24优缺点
优点缺点1. 提高内存访问效率尤其在硬件对齐要求严格的体系结构上。2. 减少缓存未命中cache miss的概率提高缓存利用率。1. 增加内存消耗填充字节占用额外的内存空间。
16. explicit
在C中explicit关键字用于修饰构造函数目的是 防止编译器在不经意间进行隐式类型转换 。默认情况下C允许通过构造函数进行隐式类型转换这可能导致一些潜在的错误或不明确的行为。
作用
防止隐式转换explicit关键字告诉编译器构造函数不应该被用作隐式转换的手段只能通过显式调用来使用。增强代码可读性使用explicit可以使代码更加清晰避免在类型转换时产生意外的结果。
17. static
在C语言和C中static关键字都有多个作用但在C中的应用更广泛。
相同与差异 静态局部变量定义在函数内部的局部变量使用static关键字修饰后这个变量在函数调用之间保持其值不变。 静态全局变量定义在文件内部的全局变量使用static关键字修饰后这个变量的作用域仅限于定义它的文件。 静态函数定义在文件内部的函数使用static关键字修饰后这个函数的作用域仅限于定义它的文件。 类的静态成员变量属于整个类而不是某个特定的对象。所有对象共享同一个静态成员变量。 特性 静态成员在类外定义初始化。静态成员没有this指针。静态成员为所有类对象所共享不属于某个具体实例。 类静态成员函数类的静态成员函数不依赖于具体对象可以通过类名直接调用。
CC1. 静态局部变量 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. 内部类
定义可以说两个类符合嵌套关系时内部类是外部类的友元。 特性
内部类可以直接访问外部类的staticenum不需要类名。sizeof外部类只是外部类大小和内部类无关。
20. 内存管理虚拟地址空间
栈Stack 用于存放函数调用时的局部变量、返回地址、参数等。每调用一个函数就会在栈上分配一块内存称为栈帧当函数返回时这块内存会被释放。 特点栈是自动管理的当函数调用完成栈帧会自动销毁。栈的内存空间有限如果函数调用层次太深例如递归调用过多可能会发生栈溢出。内存映射段Memory-Mapped Segment 允许程序将文件或设备的内容映射到内存中。程序可以直接访问这些内容而不必调用读写函数从而提高了I/O操作的效率。 特点处理大文件共享内存进程间通信等场景。堆Heap 用于动态内存分配程序可以在运行时使用new和delete或malloc和free在堆上分配和释放内存。 特点堆的大小并不固定可以根据程序的需要动态增长或收缩但必须手动管理防止内存泄漏。BSS段Block Started by Symbol 存放未初始化的全局变量和静态变量。在程序启动时这些变量会被初始化为零。 特点与数据段类似但这些变量初始值为零。数据段Data Segment存放已初始化的全局变量和静态变量。这些变量在程序的整个生命周期中都存在。 特点在程序启动时这部分内存已经分配并在程序结束时释放。代码段Code Segment存放程序的机器指令即存放编译后的代码。代码段是只读的不会在程序运行时发生变化。 特点在程序执行期间这部分内存通常是只读的防止程序意外修改指令。
21. 堆上开辟空间malloc、calloc、realloc、free
malloc返回开辟内存大小需要强转不强转返回void*不初始化开辟成功返回空间首地址开辟失败返回NULL。
void* malloc(size_t size);calloc同malloc需要强转返回void*参数不同给定numsize返回空间大小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*需要强转申请失败须判空NULLnew 不需要强转只需要捕获异常即可。 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_ptrNode next;
};void circularReferenceExample() {auto node1 std::make_sharedNode();auto node2 std::make_sharedNode();node1-next node2;node2-next node1; // 循环引用
}处理方式
手动检查确保每一个 new 或 malloc 都有对应的 delete 或 free。使用 RAIIResource Acquisition Is Initialization模式来确保资源在不再需要时自动释放。使用智能指针使用 std::unique_ptr 或 std::shared_ptr 来自动管理动态内存避免手动释放的麻烦。
系统资源泄漏比如套接字文件描述符管道没有使用对应的函数释放造成资源浪费。 解决方式使用检测工具检测内存泄漏。
Linux使用Valgrind工具可以在程序运行时检测内存泄漏 。valgrind --leak-checkfull ./your_programWindowsVLD 工具。
24. 智能指针 智能指针C 标准库中的一种工具用来自动管理动态内存避免手动管理内存时容易出现的内存泄漏和指针悬挂等问题。 std::auto_ptr 弃用是C98 引入的一种智能指针用于管理动态分配的内存以避免内存泄漏问题。然而std::auto_ptr 存在一些严重的缺陷导致它在 C11 中被废弃并在 C17 中被完全移除。以下简称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 iostreamvoid autoPtrExample() {std::auto_ptrint ptr1(new int(10));std::auto_ptrint ptr2 ptr1; // ptr1 失去所有权std::cout ptr1: (ptr1.get() ? Not null : Null) \n; // 输出 Nullstd::cout ptr2: *ptr2 \n; // 输出 10
}
//ptr1 将失去对所指对象的所有权ptr2 接管所有权。这种所有权转移的行为是不安全的可能会导致误用。std::unique_ptr替换auto_ptrC11 引入了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 iostreamclass MyClass {
public:MyClass() { std::cout MyClass constructed\n; }~MyClass() { std::cout MyClass destructed\n; }
};void uniquePtrExample() {std::unique_ptrMyClass ptr1(new MyClass());// 或者使用 std::make_uniqueC14 引入auto ptr2 std::make_uniqueMyClass();// std::unique_ptr 不允许复制// std::unique_ptrMyClass ptr3 ptr1; // 错误// 可以通过 std::move 转移所有权std::unique_ptrMyClass ptr3 std::move(ptr1);
}std::shared_ptr 是一种共享所有权的智能指针多个 std::shared_ptr 可以共同管理同一个对象。当最后一个 std::shared_ptr 被销毁时所指向的对象才会被释放。 使用方式 #include memory
#include iostreamclass MyClass {
public:MyClass() { std::cout MyClass constructed\n; }~MyClass() { std::cout MyClass destructed\n; }
};void sharedPtrExample() {std::shared_ptrMyClass ptr1 std::make_sharedMyClass();std::shared_ptrMyClass ptr2 ptr1; // 共享所有权std::cout Reference count: ptr1.use_count() \n; // 输出 2
}std::weak_ptr是一种不拥有对象所有权的弱引用计数智能指针它用于解决 std::shared_ptr 的循环引用问题。std::weak_ptr 不会影响对象的引用计数因此即使所有 std::shared_ptr 都被销毁对象也会被正确释放。 使用方式 #include memory
#include iostreamclass MyClass {
public:std::shared_ptrMyClass ptr; // 循环引用MyClass() { std::cout MyClass constructed\n; }~MyClass() { std::cout MyClass destructed\n; }
};void weakPtrExample() {std::shared_ptrMyClass ptr1 std::make_sharedMyClass();std::weak_ptrMyClass 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. 四种转换
C11 中的四种类型转换方式包括 static_cast、dynamic_cast、const_cast 和 reinterpret_cast。这些转换方式提供了类型安全或特定目的的转换机制。 static_cast用于在 类型之间进行标准转换例如基本数据类型的转换、父类和子类之间的转换等。 适用场景用于没有运行时类型检查的转换例如数值类型间的转换或者类层次结构中已知类型的安全转换。 int a 10;
double b static_castdouble(a); // int 转换为 doubledynamic_cast主要用于带有 多态的类层次结构之间的转换能够在运行时进行类型检查确保安全转换。 适用场景用于将基类指针或引用转换为派生类指针或引用并且该基类需要有至少一个虚函数。 class Base { virtual void foo() {} };
class Derived : public Base {};Base* base new Derived();
Derived* derived dynamic_castDerived*(base); // 安全转换const_cast用于在 类型上添加或移除 const 或 volatile 限定符。 适用场景用于需要修改常量对象的情况下但需确保没有违反 const 限定的语义。 const int a 10;
int* p const_castint*(a); // 移除 const 限定
*p 20;reinterpret_cast用于对位模式进行重新解释的转换它 可以将指针类型转换为不同类型的指针也可以转换为整数类型。 适用场景在需要直接修改对象的位模式或类型完全不相关的指针转换时使用但需小心因为可能导致未定义行为。 int a 42;
void* p a;
int* pInt reinterpret_castint*(p); // 强制转换回 int* 类型26. 继承
继承Inheritance 是面向对象编程的一个重要概念允许一个类派生类从另一个类基类获取属性和行为。 继承使得代码复用成为可能还能增强代码的扩展性和灵活性。体现了面向对象程序设计的层次结构。
基本概念 基类Base Class也称父类或超类是被继承的类。它包含一些派生类可以复用的成员变量和方法。 派生类Derived Class也称子类是继承了基类的类。派生类可以复用基类中的数据成员和成员函数还可以添加自己的成员。继承类型 基类的private成员在派生类中无论以什么方式继承都是不可见的。不可见指基类的私有成员还是会被继承到派生类对象中但是语法上限制派生类的访问无论是类内还是类外class Base {
public:int a;
protected:int b;
private:int c;
};class Derived : public Base {// a 为 publicb 为 protectedc 不可访问
};继承方式的可见性public protected private。 class的默认继承方式为 privatestruct的默认继承方式是 public。 继承时要显示写出继承方式public最多其他方式较少可维护性差。 赋值时通过切片派生类可以给基类的引用指针对象进行赋值。 直接强转赋值定义一个基类指针强转为子类指针。会出现越界访问的情况 继承的作用域均拥有独立的作用域。 隐藏、重定义基类和派生类内拥有同名成员派生类将屏蔽基类成员直接访问自己的成员。 派生类的默认成员 构造函数构造顺序是先基类再子类。拷贝构造析构函数析构顺序是先子类再基类。赋值运算符重载取地址运算符重载 不能被继承的类 // C98 构造函数私有化.
class A{static A init(){return A();}
private:A(){}
};//final关键字.
class B final {};友元关系不能被继承友元关不能访问子类的保护和私有成员。static静态成员一个继承体系里面只有一个。 类外初始化.cpp与.h分开时在cpp内部初始化否则会报错Link 2001不能在main内定义否则会报编译器错误Error C2665const static 一经定义不能修改
单继承多继承菱形继承 单继承一个子类只有一个父类。多继承一个子类具有多个直接父类。菱形继承多继承的一种特例。具有数据冗余二义性问题可使用虚拟继承来解决。class A { /* 基类 */ };
class B : virtual public A { /* 虚继承 */ };
class C : virtual public A { /* 虚继承 */ };
class D : public B, public C { /* 菱形结构A 的成员只有一份 */ };原理 使用了多态让类D在寻找A中的成员变量时通过虚表指针 偏移量来获取地址/值。
27. 多态
多态Polymorphism 是面向对象编程的重要特性它允许不同类的对象通过统一的接口表现出不同的行为。多态是实现类间共享接口的基础使得程序能够通过基类指针或引用操作派生类对象。
静态多态 是指在编译时确定调用哪个函数通常使用函数重载或模板来实现 。静态多态不涉及运行时的动态分派因此效率较高。 函数重载通过相同函数名但参数不同数量、类型或顺序不同来实现多态。 class Printer {
public:void print(int i) { std::cout Printing integer: i std::endl; }void print(double d) { std::cout Printing double: d std::endl;}
};Printer p;
p.print(42); // 调用 print(int)
p.print(3.14); // 调用 print(double)模板通过模板参数的不同类型来实现多态。 template typename T
void print(T value) {std::cout Printing value: value std::endl;
}print(42); // print(int)
print(3.14); // print(double)
print(Hello); // print(const char*)动态多态 使用 virtual 关键字修饰基类中的函数派生类通过重写override这些虚函数来提供具体实现。 多态实现 #include iostream
using namespace std;class Base {
public:virtual void speak() { // 声明虚函数cout Base speaking endl;}virtual ~Base() {} // 虚析构函数
};class Derived : public Base {
public:void speak() override { // 重写基类的虚函数cout Derived speaking endl;}
};int main() {Base* b new Derived(); // 基类指针指向派生类对象b-speak(); // 运行时调用派生类的实现输出: Derived speakingdelete b;return 0;
}虚函数virtual基类的成员函数在派生类中可以被重写以实现多态。virtual 关键字确保了通过基类指针或引用调用该函数时实际调用的是派生类中的函数。 运行时绑定当程序运行时根据对象的实际类型派生类类型决定调用哪个版本的 speak 函数。 虚析构函数如果基类有虚函数最好将析构函数也声明为虚函数以确保通过基类指针删除派生类对象时能够正确调用派生类的析构函数避免内存泄漏。 多态的对象切换通过基类指针或引用操作派生类对象是多态的核心应用。 class Shape {
public:virtual void draw() 0; // 纯虚函数接口方法virtual ~Shape() {} // 虚析构函数
};class Circle : public Shape {
public:void draw() override {cout Drawing Circle endl;}
};class Rectangle : public Shape {
public:void draw() override {cout Drawing Rectangle endl;}
};int main() {Shape* shape1 new Circle();Shape* shape2 new Rectangle();shape1-draw(); // 输出: Drawing Circleshape2-draw(); // 输出: Drawing Rectangledelete shape1;delete shape2;return 0;
}协变 基类与派生类虚函数的返回值类型不同。 class A{
public:virtual int* print(){}
};class B: public A{virtual float* print(){}
};基类的析构函数是虚函数派生类只要定义析构函数就构成覆盖重写无论加不加virtual由编译器统一处理。 class A{virtual A(){};virtual ~A(){};
};class B: public A{
public:B(){}~B(){}
};接口继承虚函数的继承目的是为了重写达成多态。 实现继承普通函数的继承。 静态绑定编译期间确定程序行为重载。 动态绑定程序运行期间根据行为调用对应函数。动态多态 图片转自C多态之虚函数表详解。 虚函数表是指在每个包含虚函数的类中都存在着一个虚函数地址的数组。当我们用父类的指针来操作一个子类的时候这张虚函数表指明了实际所应该调用的函数。位置虚函数表在编译阶段生成存储于只读数据段.rodata。 class A {
public:virtual void v_a(){}virtual ~A(){}int64_t _m_a;
};int main(){A* a new A();return 0;
}图片转自虚函数和多态。 不能成为虚函数的函数 1普通函数非成员函数)只有类的成员函数才有可能被声明为虚函数。 2静态函数静态成员函数是编译时确定的无法动态绑定不支持多态。 3构造函数调用构造函数才能初始化对象和虚表指针调用虚函数前必须要先知道虚表指针自相矛盾。 4内联函数编译时展开虚函数是运行时动态绑定差异大。 5友元函数友元函数无法被继承。 对象访问普通函数和虚函数的效率 1普通对象一样快。 2指针/引用对象访问普通函数更快。虚函数要在虚表内查询访问效率降低。
28. 模版类、模版函数
C 中的模板template是一种泛型编程技术它允许我们编写对多种数据类型适用的代码。模板主要包括两种类型模板函数和模板类。
29. 深、浅拷贝
浅拷贝位拷贝浅拷贝只会复制指针的值而不会复制指针所指向的内存。多个对象共用同一份资源共用一片地址空间。当一个对象销毁资源被释放会引起访问出错。
深拷贝拷贝数据时先开辟一片新的地址空间再将数据拷贝过来。 应用场景 1动态分配内存如果你的类包含指针并且动态分配了内存那么应该使用深拷贝确保每个对象都拥有独立的资源。 2避免共享资源当你希望对象之间不共享资源特别是当这些资源会被修改或释放时应使用深拷贝。
写时拷贝在构造时拷贝的资源采用了引用计数当计数变为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. 程序运行四个阶段
程序运行的四个阶段预处理编译汇编链接。
预处理 展开所有代码。
删除define展开所有的宏定义。处理所有的条件预编译指令比如#ifdef#elif#else以及#endlif等。引入所有的#include指令头文件将包含文件插入到预编译指令的位置。去掉所有的注释。添加行号和文件标识以便编译的时候产生调试的行号以及编译错误警告行号。保留所有的#pragma编译指令以便编译器使用。
编译 进行代码分析没有错误则转为汇编语言。
词法分析扫描扫描器scanner将源代码的字符序列分割成一系列记号token。lex工具可以实现扫描词法扫描。语法分析语法分析器将记号token产生语法树syntax Tree。YACCYet Another Compiler Compiler工具实现语法分析。语义分析静态语义在编译阶段可以确定的语义动态语义在运行期间才能确定的语义。源代码优化源代码优化器source code optimizer将整个语法树转为中间代码Intermediate code。中间代码使得编译器被分为前端和后端。编译器前端负责产生中间代码编译器后端负责将中间代码转化为目标机器代码。 汇编将汇编指令解释为机器可识别指令。 将这些指令打包成一种叫做可重定位目标程序的格式将结果保存在二进制目标文件中。 链接将所有使用到的代码.o文件库文件等打包在一起生成可执行文件。 静态链接动态链接说明生成可执行程序的时候将库中的代码直接写入可执行程序这样程序运行就不需要加载了。生成可执行程序时不写入库中的代码而是记录为函数符号信息表生成的可执行程序较小程序运行时加载这些库到内存中。优点启动运行速度快1.节省磁盘和内存2. 模块更新速度快 3. 有利于程序扩展 4. 代码冗余小可执行文件占用空间小缺点1. 代码冗余若干程序使用一个库 2. 内存和磁盘浪费 3. 模块更新成本大损失部分运行时的性能