仅供个人复习,
C语言IO占位符表
%d | 十进制整数(int) |
---|---|
%ld | long |
%lld | long long |
%u | unsigned int |
%o | 八进制整型 |
%x | 十六进制整数/字符串地址 |
%c | 单个字符 |
%s | 字符串 |
%f | float,默认保留6位 |
%lf | double |
%e | 科学计数法 |
%g | 根据大小自动选取f或e格式,去掉无效0 |
转义符表
转义符可以取消关键字符的特殊性,下面是常见的转义符使搭配
printf("\b");//退格符
printf("\n");//换行
printf("\a");//响铃,电脑真的响一下,不可思议
printf("\t");//水平制表符
printf("\v");//垂直制表符
printf("\130");//输出char对应130的字符
printf("%% %d",12);//%的转义使用%,而不是\
随机数生成
srand((unsigned int)time(NULL));
int ret1 = rand() % 10 + 1;//生成1~10的随机数
int ret2 = rand() % 100 + 1;//生成1~100的随机数
int ret3 = rand() % 34 + 66;//生成66~99的随机数
int ret4 = rand() % (n - m + 1) + m;//生成m~n的随机数
//规律是第一个数+第二个-1,是上界
//第二个数是下界
C/C++ 语言数据类型大小
ANSI/ISO规范
sizeof(short int)<=sizeof(int)
sizeof(int)<=sizeof(long int) short
int至少应为16位(2字节)
long int至少应为32位。
16位编译器
数据类型 | 字节数 |
---|---|
char | 1 |
short | 2 |
int | 2 |
long | 4 |
float | 4 |
double | 8 |
bool | 1 |
指针 | 2 |
32位编译器
数据类型 | 字节数 |
---|---|
char | 1 |
short | 2 |
int | 4 |
long | 4 |
long long | 8 |
float | 4 |
double | 8 |
bool | 1 |
long double | 12 |
指针 | 4 |
64位编译器
数据类型 | 字节数 |
---|---|
char | 1 |
short | 2 |
int | 4 |
long | 8 |
long long | 8 |
float | 4 |
double | 8 |
bool | 1 |
long double | 16 |
指针 | 8 |
struc和class的区别
1.struct和class都能被继承
2.struct和成员和继承默认都是public的,而class能够自己选择
3.在C++中struct可以定义函数,而C不能
内存分布
1.堆区(Heap):由开发者自己管理,主要通过new, delete, malloc, free进行分配和释放,空间往往比较大,但有可能会存在内存泄漏和内存碎片的情况
2.栈区(Stack):由编译器管理分配和释放,用于存放函数参数,局部变量,等等
3.只读数据段(.rodata - ReadOnly Data Segment):用于储存常量值,例如整数,字符串等字面量,一般不允许修改
4.代码段(Text Segment)): 存储程序执行所用的二进制指令
5.全局/静态区:未初始化部分称为BSS Segment,不会占用可执行文件的空间,但是在程序初始化后会为这里的变量分配内存。初始化部分(数据段 -.data - Data Segment)会直接把初始值写入可执行文件,在程序初始化后分配内存并赋值。
new和malloc的区别
4、new和malloc的区别
(1)new和delete是C++关键字,而malloc和free是C语言库函数。都用于申请动态内存和释放内存。new的实现也是基于malloc的
(2)new在调用时先分配内存,再调用构造函数,delete在调用时先使用析构函数,再释放内存,而malloc只分配内存,free只释放内存。
(3)new申请内存不需要计算申请内存的大小,且不需要强制类型转换,其返回的类型就是对应申请类型的指针,而malloc申请时,需要手动计算申请内存的大小,并且还需要强制类型转换为需要的类型,因为malloc返回的类型为 void*。
(5)new申请内存失败,返回NULL,而malloc申请内存失败,则返回异常。
为什么有了malloc,free还需要new和delete:因为malloc和free是C的库函数,不在编译器的控制范围内,所以C++把new和delete变成关键字。而且malloc和free不能满足OOP对构造和析构安全性的需要。
函数参数传递的方式
1.值传递:形参是实参的拷贝,形参的改变并不影响实参的值
2.引用传递:形参接收实参的地址,使得对形参所指对象进行操作等价于对实参的操作
3.指针传递:形参接收实参的指针,使得函数能够通过形参映射到实参进行修改和读取
指针和引用的区别
(1)指针是一个实体,而引用只是一个别名。
(2)指针指向的是一块内存地址,所指向的内容是内存的地址,但指针所指向的值是可以改变的,允许拷贝和赋值,可以为空,sizeof指针得到的也是指针类型的大小。
(3)引用是一块内存的别名,引用必须在定义时绑定到一块内存上,即引用必须初始化,后续不可更改,也不能为空,且没有const和非const的区别。sizeof引用得到的是所初始化对象的大小。
(4)指针必须在解引用后才能对对象进行操作,而引用可以直接对对象进行操作。
指针和数组的区别
1.指针指向的是一个内存地址,代表了该类型所对应的固定大小的一块内存空间,而数组指向的虽然是首元素地址,但是数组代表的是一个内存连续的数据集合,也就是n个指针代表的内存空间。
2.数组指向首元素地址,不能直接通过赋值来进行拷贝,而是需要逐个拷贝
函数指针的含义
编译后,每个函数都会有一个入口地址,函数指针指向函数的入口地址
空指针,野指针,悬挂指针
空指针:指向NULL或者nullptr或0的指针
野指针:指向未知或无效位置的指针,例如没有初始化,越界,释放后继续使用。
悬挂指针:指向了之前有效但是现在无效的地址,没有被置空,原地址仍然悬挂。
对这三种指针的访问均会产生各种异常和问题。
如何避免:
1.保持良好的习惯:例如在指针使用完毕后即时的置空,先对指针进行检查后再使用。
2.使用智能指针或其他内存管理策略:例如unique_ptr,shared_ptr等,这些指针能够通过引用计数自动确定释放时机,或者在C++中实现GC进行内存管理(例如UE中的GC和C# GC)
指针常量和常量指针
const int* p = &a; //常量指针,指向可以改,但指向的值不可以改。
int* const p = &a; //指针常量,指向不可以改,但指向的值可以改
const int* const p = &a; //常量指针常量 ,指向和指向的值都不能改
怎么记,先出现const就是常量,再出现int*就是指针
const在前是const int*,代表指针指向了一个常量。
const在后是分别修饰p,代表这个指针变量本身是常量。
宏函数和内联函数的区别
宏函数(Macro Functions):
-
替换规则:在编译预处理阶段的简单文本替换
-
参数展开:没有参数类型检查
-
适用场景:简单的、短小的代码片段,例如进行简单的数学计算、位操作等。
内联函数(Inline Functions):
-
替换规则:编译阶段处理。编译器会尝试将函数调用处直接替换为函数体。
-
参数类型检查:参数和返回值检查与正常函数无异
-
适用场景:内联函数适用于较短小的函数,包含一些简单的代码逻辑,且频繁调用的情况。
-
推荐用法:在类里定义的函数都是默认加上inline关键字的(是不是由编译器决定),或者在头文件中避免函数重定义问题。inline关键字只能建议编译器进行内联,是否采纳取决于编译器
内联函数可以优化函数进入和离开的开销,但内联可能会导致编译后的代码体积增大,宏函数的使用更需要小心,因为它在文本替换阶段可能会引发一些意想不到的问题。
差异案例
#include <iostream>
#define SQUARE_MACRO(x) x * x
inline int square_inline(int x) {
return x * x;
}
int main() {
int num = 5;
// result = (++num) * (++num) 直接把参数替换进函数
int result_macro = SQUARE_MACRO(++num); // 使用宏函数
num = 5;
//先自增再传入
int result_inline = square_inline(++num); // 使用内联函数
std::cout << "Number: " << num << std::endl;
std::cout << "Result (Macro): " << result_macro << std::endl;
std::cout << "Result (Inline): " << result_inline << std::endl;
return 0;
}
宏和const的区别
(1)类型和安全检查不同:宏定义只是字符的替换,没有数据类型的区别和类型安全检查,而const是常量的声明,有数据类型的区别,同时在编译阶段也会有类型的检查。
(2)编译器处理方式的不同:编译器在预处理阶段就把宏给展开,不能对宏进行调试;而编译器在编译阶段处理const变量,类似于一个只读数据。
(3)存储方式不同:宏定义是直接替换,没有内存分配,而const常量有内存分配
(4)作用域不同:宏可以在所有包含的文件里使用,而const允许局部定义,只能在作用域内使用
C++的编译过程
1.预处理:编译器展开宏和内联,展开条件编译
2.编译:编译器将预处理后的代码,编译为汇编代码,通常为.o和.obj格式
3.汇编:编译器将编译后的汇编代码转换为二进制机器码,输出到.o和.obj中
4.链接:编译器将以上生成的编译单元与库函数,必要信息结合生成可执行文件.
static相关的
**1.修饰类的成员函数:**该函数属于这个类,而不属于类的任意实例,只能访问静态成员。无需实例即可调用。
**2.修饰类的成员变量:**这个变量属于类,不属于实例,在所有实例之间共享,无需实例即可使用。
**3.修饰局部变量:**将局部变量的存储从栈区转移到静态区,例如在函数体内定义静态变量,在下次访问这个函数时,如果没有在别的地方进行修改,该变量的值与上次离开相同。
4.修饰全局变量:
首先明确概念,全局变量int x = 10默认就是extern int x = 10
哪怕其他文件不include,也能通过在本文件extern一个相同的变量来访问它。
但是声明了static全局变量后,必须进行include才能访问,禁止了extern的使用
在头文件中声明一个static变量后,在包含了该头文件的cpp文件修改了该变量值,对于其他也包含了该头文件的cpp文件并不影响.
**5.修饰函数:**会使得该函数只可以在其他文件实现,但是不能在其他文件调用
C++中的重载和重写、重定义
(1)重载:同一个作用域内,声明多个参数列表不同的同名函数(不包含返回值类型),C++会把函数的参数类型加到函数签名中,从而使得在编写时函数名一样,但在编译后函数名不一样,而C没有函数重载。
(2)重写:派生类中重新定义父类中函数签名相同的虚函数,重写的一定是虚函数,在子类中重写函数,其访问权限可以重新被定义。(动态多态)
(3)重定义:在派生类中,重新定义和父类函数名相同的非虚函数,其参数列表和返回值都可以不同。则父类中的同名函数被子类所隐藏,如果想要调用父类中的同名函数,则需要加上父类的作用域。
编译器为类生成的构造和析构函数
1.如果没有定义任何构造和析构,编译器生成公开的空构造和析构,以及拷贝构造函数
2.如果定义了拷贝构造函数,则仅生成析构
3.如果仅定义了普通构造函数,则生成拷贝构造和析构
4.如果仅定义了析构,则生成拷贝构造和默认构造
include时<>和""的区别
1.使用<>,编译器会从系统文件目录下,系统配置的库环境中去寻找对应的头文件,一般用于引用标准库的头文件。
2.使用"",编译器会先在当前目录下去寻找对应的文件,找不到的时候再去库环境中去寻找。一般用于引用个人编写的头文件
另外可以在IDE中配置inlcude内容,也是一个备选的查找项
面向对象的三(四)大特性
1.封装
2.继承
3.多态 : 对不同对象发送相同消息产生不同效果
静态多态的实现:运算符重载和函数重载
动态多态的实现:子类重写父类函数
(4.抽象)
STL标准库
通用的算法API
条件排序
#include <algorithm>
// 使用 lambda 表达式按照姓名升序排序
std::sort(v.begin(), v.end(),
[](const Person& a, const Person& b)
{
return a.name < b.name;
});
//运算符重载
struct Person {
int age;
Person(int a) : age(a) {}
// 重载 < 运算符,按照年龄升序排序
bool operator<(const Person& other) const {
return age < other.age;
}
};
sort(v.begin(),v.end());
移除符合条件的元素
#include <algorithm>
//返回的是移除后的逻辑结尾的迭代器,将符合条件的置于尾端
auto newEnd = std::remove_if(v.begin(), v.end(), [](T item)
{
return ...
});
//真正的移除
numbers.erase(newEnd, numbers.end()); // 删除废弃的元素
vector
优点:
- 内存是连续分配的,访问元素的速度较快。
- 在末尾插入和删除元素的时间复杂度为常数。
缺点:
- 在中间插入或删除元素的时间复杂度较高,需要移动后续元素。
- 在内存不足时,可能会导致重新分配内存和复制元素。
#include <vector>
std::vector<int> v;
v.push_back(10); // 在末尾插入元素
v.pop_back(); // 删除末尾的元素
v.size(); // 返回容器中的元素数量
v.empty(); // 检查容器是否为空
v.clear(); // 清空容器中的所有元素
v.at(index); // 访问指定索引的元素
v.begin(); // 返回指向容器第一个元素的迭代器
v.end(); // 返回指向容器最后一个元素之后的迭代器
list
优点:
- 支持在任意位置快速插入和删除元素。
- 在中间插入和删除元素的时间复杂度为常数。
缺点:
- 元素在内存中不连续存储,访问元素的速度慢。
- 占用更多内存,每个节点需要存储额外的指针。
#include <list>
std::list<int> lst;
lst.push_back(10); // 在末尾插入元素
lst.push_front(20); // 在开头插入元素
lst.pop_back(); // 删除末尾的元素
lst.pop_front(); // 删除开头的元素
lst.size(); // 返回容器中的元素数量
lst.empty(); // 检查容器是否为空
lst.clear(); // 清空容器中的所有元素
lst.front(); // 访问首元素
lst.back(); // 访问末尾元素
lst.begin(); // 返回指向容器第一个元素的迭代器
lst.end(); // 返回指向容器最后一个元素之后的迭代器
forawrd_list
与list类似,仅支持单向访问,效率更佳一些
#include <forward_list>
std::forward_list<T> fl;
fl.push_front(const value_type& value); // 在头部插入元素
fl.pop_front(); // 从头部删除元素
fl.insert_after(pos, const value_type& value); // 在指定位置后插入元素
fl.erase_after(pos); // 在指定位置后删除元素
fl.front(); // 访问第一个元素
fl.begin(); // 返回指向第一个元素的迭代器
fl.end(); // 返回指向最后一个元素之后的迭代器
deque
优点:
- 支持在两端快速插入和删除元素。
- 内存是分块分配的,访问元素的速度较快。
缺点:
- 难以在中间插入或删除元素
- 存储多个分块,占用较多内存
#include <deque>
std::deque<int> dq;
dq.push_back(10); // 在末尾插入元素
dq.push_front(20); // 在开头插入元素
dq.pop_back(); // 删除末尾的元素
dq.pop_front(); // 删除开头的元素
dq.size(); // 返回容器中的元素数量
dq.empty(); // 检查容器是否为空
dq.clear(); // 清空容器中的所有元素
dq.front(); // 访问首元素
dq.back(); // 访问末尾元素
dq.begin(); // 返回指向容器第一个元素的迭代器
dq.end(); // 返回指向容器最后一个元素之后的迭代器
map
优点:
- 存储键值对,支持按键进行高效的查找和插入。
- 根据键的顺序遍历元素。
缺点:
- 内存使用较多,每个键值对都需要额外的内存存储键。
- 没有连续存储,访问元素的速度相对较慢。
#include <map>
std::map<std::string, int> m;
m["one"] = 1; // 插入键值对
m["two"] = 2;
m.find(const key_type& k); // 查找键的位置
m.count(const key_type& k); // 计算具有特定键的元素数量
m.size(); // 返回容器中的键值对数量
m.empty(); // 检查容器是否为空
m.clear(); // 清空容器中的所有键值对
m.begin(); // 返回指向容器第一个键值对的迭代器
m.end(); // 返回指向容器最后一个键值对之后的迭代器
set
优点:
- 存储唯一的元素,支持按值进行高效的查找和插入。
缺点:
- 内存使用较多,每个元素都需要额外的内存存储。
- 不连续存储,访问元素的速度相对较慢。
#include <set>
std::set<int> s;
s.insert(const value_type& val); // 插入元素
s.find(const key_type& k); // 查找元素
s.size(); // 返回容器中的元素数量
s.empty(); // 检查容器是否为空
s.clear(); // 清空容器中的所有元素
s.begin(); // 返回指向容器第一个元素的迭代器
s.end(); // 返回指向容器最后一个元素之后的迭代器
unordered_map (C++11)
优点:
- 使用哈希表实现,支持快速的查找和插入操作,平均时间复杂度为常数。
- 对于大数据集,查找效率高于std::map。
缺点:
- 内存占用较高,需要存储哈希表和键值对。
- 不保证元素的顺序。
#include <unordered_map>
std::unordered_map<std::string, int> um;
um["one"] = 1; // 插入键值对
um["two"] = 2;
um.find(const key_type& k); // 查找键的位置
um.count(const key_type& k); // 计算具有特定键的元素数量
um.size(); // 返回容器中的键值对数量
um.empty(); // 检查容器是否为空
um.clear(); // 清空容器中的所有键值对
um.begin(); // 返回指向容器第一个键值对的迭代器
um.end(); // 返回指向容器最后一个键值对之后的迭代器
unordered_set (C++11)
优点:
- 使用哈希表进行实现,支持快速的查找和插入操作,平均时间复杂度为常数。
- 对于大数据集,查找效率高于std::set。
缺点:
- 内存占用较高,因为需要存储哈希表和元素。
- 不保证元素的顺序。
#include <unordered_set>
std::unordered_set<int> us;
us.insert(const value_type& val); // 插入元素
us.find(const key_type& k); // 查找元素
us.size(); // 返回容器中的元素数量
us.empty(); // 检查容器是否为空
us.clear(); // 清空容器中的所有元素
us.begin(); // 返回指向容器第一个元素的迭代器
us.end(); // 返回指向容器最后一个元素之后的迭代器
stack
#include <stack>
std::stack<T> s;
s.push(const value_type& value); // 将元素压入堆栈顶部
s.pop(); // 弹出堆栈顶部的元素
s.top(); // 访问堆栈顶部的元素
s.empty(); // 检查堆栈是否为空
s.size(); // 返回堆栈中元素的数量
queue
#include <queue>
std::queue<T> q;
q.push(const value_type& value); // 将元素推入队列尾部
q.pop(); // 从队列头部弹出元素
q.front(); // 访问队列头部元素
q.back(); // 访问队列尾部元素
q.empty(); // 检查队列是否为空
q.size(); // 返回队列中元素的数量
priority_queue
#include <queue>
std::priority_queue<T> pq;
pq.push(const value_type& value); // 将元素推入优先队列
pq.pop(); // 从优先队列中弹出元素
pq.top(); // 访问优先队列中优先级最高的元素
pq.empty(); // 检查优先队列是否为空
pq.size(); // 返回优先队列中元素的数量
#include <iostream>
#include <queue>
#include <vector>
struct MyStruct {
int value;
// 比较操作符,根据 value 来比较 ,越大优先级越高
bool operator<(const MyStruct& other) const {
return value < other.value;
}
};
int main() {
std::priority_queue<MyStruct> pq;
pq.push({5});
pq.push({2});
pq.push({8});
pq.push({1});
// 遍历优先队列按优先级输出
while (!pq.empty()) {
std::cout << pq.top().value << " ";
pq.pop();
}
return 0;
}
// 8 5 2 1
智能指针
- std::shared_ptr:允许多个智能指针共享同一个对象,通过引用计数来管理对象的生命周期。当最后一个引用被释放时,对象会被销毁。
auto sp = std::make_shared<int>(); // 分配堆空间,创建智能指针
auto sp2 = sp; // 创建另一个智能指针
-
std::unique_ptr:用于独占地拥有一个对象,不能被多个智能指针共享。它提供了更轻量级的智能指针,适用于不需要共享所有权的情况。
-
std::weak_ptr:用于解决std::shared_ptr的循环引用问题。它可以与std::shared_ptr一起使用,但不会增加对象的引用计数。
构造函数执行顺序
- 先成员的构造,再当前类型的构造
- 父类构造优先于子类构造
- 成员初始化按书写顺序,低于构造顺序
- 虚基类只构造一次,非虚构造两次
例题:问输出结果是多少
#include <iostream>
class A {
public:
A() {
std::cout << "A Constructor" << std::endl;
}
};
class B : public A {
public:
B() {
std::cout << "B Constructor" << std::endl;
}
};
class C : public A {
public:
C() {
std::cout << "C Constructor" << std::endl;
}
};
class D : public B, public C {
public:
D() {
std::cout << "D Constructor" << std::endl;
}
};
int main() {
D d;
return 0;
}
析构函数: 子类>子类成员>父类>父类成员
一个冷知识:一个类的构造函数和析构函数都是public才能被外界实例化
如果是protected只能在派生类实例化和析构
也就是说得在当前位置能对该类析构和构造才能被实例化(掂量一下访问权限在这里是否是同时有)
继承的理解
C++中
如果A派生于B,那么在创建A的实例时,会形成如下的结构,也就是说A持有一个B的实例
class B
{
继承方式(public,protected,private) A parent;
};
可能这样看起来很奇怪,但是实际上却是编译器为我们建立了一个特殊的关系,让我们能直接让B能拥有A的一切,而不是B.A.XXX,相当于一种缩略的调用方式。基于此我们能产生更加深入的理解,B间接持有了A的内存。
这样,我们创建一个B的实例,可以得知,构造和析构,初始化顺序,访问权限等一系列规则。
基类成员 | public 继承 | protected 继承 | private 继承 |
---|---|---|---|
public 成员 | public 成员 | protected 成员 | 不可访问 |
protected 成员 | protected 成员 | protected 成员 | 不可访问 |
private 成员 | 不可访问 | 不可访问 | 不可访问 |
实际上就是等同于通过B的实例,我们能否访问A中的成员。
B通过private继承,显然无法访问成员变量A,更不谈A内的访问权限了
B通过protected继承,显然是只有继承了B才能访问A成员,接着才能谈A内的权限问题
B通过public继承,能够完全访问A,此时访问权限就和A定义的一样,
只需要记住:何种继承方式决定能否访问基类,基类的何种修饰符决定能否访问基类的成员
public为公开,不设限
protected为继承关系可访问
private为当前类(友元也包含在内)可以访问
构造顺序则与上面提到的无异。
(virtual) 虚基类和虚函数原理
普通函数的继承覆盖
class B {
public:
void Test()
{
cout << "B"<<endl;
}
};
class C : public B {
public:
void Test()
{
cout << "C"<<endl;
}
};
int main() {
C* c = new C();
c->Test();
((B*)c)->Test();
return 0;
}
这样的执行结果是
证明普通函数在执行时与当前的数据类型相关(如指针类型),当前指针类型为什么,则执行哪个类型的函数。
虚函数
在函数前增加virtual关键字即可定义虚函数,虚函数在整个作用过程发生以下行为
- 编译器生成虚函数表(为每个函数定义了指向真正函数的指针称为虚表指针)
- 不论当前的指针是何种类型,通过查询虚函数表找到真正的函数
- 一个类只有一张虚函数表,所以类的对象共享一张虚函数表。在编译期间确定虚函数表。
- 如果没有重写父类中的虚函数,那么子类的虚函数指针则指向父类的虚函数表,子类不会生成虚函数表;
- 如果子类有增加虚函数,那么新增的虚函数地址将追加到虚函数表尾端。
- 多继承时,子类中有多个虚函数表和多个虚函数指针
原理非常简单,就是为同名函数建立了一个表格,一个函数指向了一个真正的实现,根据继承关系不断产生覆盖。像查字典一样
#include <iostream>
using namespace std;
class A {
private:
virtual void Test()
{
cout << "A";
}
};
class B : public A {
public:
void Test()
{
cout << "B";
}
};
class C : public B {
public:
void Test()
{
cout << "C";
}
};
int main() {
C* c = new C();
((B*)c)->Test();
return 0;
}
(可以去掉virtual得到CB输出)
且虚函数有如下特性
- 与访问权限关键字无关(无论何种权限都会建立虚函数表),与继承关系有关(在继承树中具备高深度优先,和唯一性)
- 查询虚函数表降低了函数效率
虚基类
虚基类旨在解决菱形继承问题,如果按照下面这张图的继承关系
如果我们不采用虚基类,则按照上面我们提到的继承的内存原理,实例应该是这样的
这样不仅导致了内存浪费,而且导致从B和C修改的数据不同步。
class A {
public:
int x;
};
class B : virtual public Base {
public:
int y;
};
class C : virtual public Base {
public:
int z;
};
class D : public B, public C {
public:
int w;
};
在这里千万注意构造函数的问题,如果是虚,则创建D的构造顺序是A->B->C->D
如果不是,则A->B->A->C->D
虚析构函数
如果A继承B,此时创建A类型的对象,转换为B类型的指针
- B的析构函数是虚的
那么在此对象被delete时,会先调用A的析构,然后调用B的析构 - B的析构非虚
在此对象被delete时,调用了B的析构,而没有调用A的析构
为什么?
因为此时编译器只知道这个对象的类型是B的指针,所有认为这是B的对象,只需要调用B的析构,所以导致生成的汇编,二进制都只调用了B的析构。
自定义类型转换函数
explict构造
显式构造函数
class MyClass {
public:
explicit MyClass(int x) {
this->x = x;
}
void print() {
std::cout << x << std::endl;
}
private:
int x;
};
int main() {
MyClass obj1 = 42; // 错误,不能隐式转换
MyClass obj2(42); // 正确,需要显式调用构造函数
obj2.print(); // 输出 42
return 0;
}
特别的,对于 non-explict-one-argument ctor
- 非explict
- 单参数(默认参数也计数)
这时我们可以
也要小心这种情况:
编译器把Fraction转为double与4进行运算,但是结果右值double没有向Fraction的转换
相反的我们把4放到前面则编译通过
模板特化
全特化
template <typename T>
class MyTemplate {
public:
void foo() {
// 通用实现
}
};
template <>
class MyTemplate<int> {
public:
void foo() {
// 专门为int类型的特化实现
}
};
偏特化
特定的偏
范围的偏
今天到这里,改日再更