C++ 学习笔记3
Cpp12 之后的查漏补缺
原始字面量
在字符串前加 R 就可以输出原本的字符串,不需要转义。
语法:R”(string)”
1 | cout << R"abc(c/fjk/)abc" << endl; // abc 是标签,需要前后相同,不被输出 |
通常在代码中很长的一行可以用反斜杠 \ 来进行多行显示,此时用 R 也可以分多行进行显示。
使用不同的分隔符(标签)可以帮助避免在原始字符串中不小心结束了字符串。例如,如果你选择的分隔符在原始字符串中从未出现,那么编译器就能够清楚地知道原始字符串的开始和结束位置。
内存空间
释放堆区内存后,要把指针指向的内存置为空。
内存空间:
void 关键字
- 函数形参用
void*
代表接受任意数据类型的指针。 - 在向一个有
void*
形参的函数传入一个指针后,不能直接在函数中解引用,需要在函数中将形参转换为具体的类型,如int*
。 - 当有一个 char 类型变量 a,使用 & 取地址值显示的是乱码,此时用 (void*)& a 转换,即可正常输出。
数组排序
qsort 函数
原型:
1 |
|
使用实例如下:
void* 类型不能直接解引用,需要具体转换后。
1 |
|
二分查找
1 |
|
单向链表实现
1 |
|
枚举
枚举也就是除去 #define 和 const 定义常量的另一种方式。
1 |
|
引用
如果引用的数据对象类型不匹配,当引用为 const 时,c++ 将创建临时变量,让引用指向临时变量。
1 | void func(const int&, const string&); |
何时创建临时变量:
- 引用是 const
- 数据对象的类型是正确的,但不是左值
- 数据对象的类型不正确,但可以转换为正确的类型。
将引用形参声明为 const 的理由:
- 使用 const 可以避免无意中修改数据的编程错误
- 使用 const 使函数能够处理 const 和 非 const 实参,否则将只能接受非 const 实参。
- 使用 const ,函数能正确生成并使用临时变量。
引用用于函数返回值
- 当函数要返回一个引用时,返回值应该是引用、静态变量、全局变量等,不能是局部变量(因为销毁后是野指针)。
- 因为返回引用后,依旧可以修改原本传入引用参数的实参的值,因此可以在返回值前加 const 约束,避免被修改。
1 | cosnt int &func(int &num); |
形参使用场景(值传递、地址、引用)
- 不需要在函数中修改实参
- 实参很小,如 c++ 内置数据类型或小型结构体,则按值传递
- 实参是数组,使用 const 指针(因为数组没有建立引用的说法)
- 实参是较大的结构,使用 const 指针或 const 引用
- 数据实参是类,则使用 const 引用,传递类的标准方式是按引用传递
- 需要在函数中修改实参
- 实参是内置数据类型,使用指针。
因为可读性更好,看到func(&x)
就知道传递指针,需要修改值。 - 实参是数组,只能使用指针。
- 实参是结构体,使用指针或引用。
- 实参是类,使用引用。
- 实参是内置数据类型,使用指针。
练习
p2:
C 风格字符串二维数组输出月份如下:
1 | int main() |
使用 C++ 的 string 则只需要一维数组即可。
p6:
实现 strlen 的方法:
1 |
|
p7:
逆序输出字符串
1 |
|
p114:
memcpy(效率更高,但是不能处理内存重叠的问题) 和 memmove(用于处理内存重叠的问题)。
课后作业四
静态顺序表
1 |
|
简单对象模型
- 类对象的大小是内存对齐后的非静态成员变量大小之和。成员函数和静态成员不算在类大小中,分散在内存的其它区域,相当于类有一个内存表将各种成员整合起来。
- 类没有非静态成员变量时,编译器会分配一个占位变量,大小为 1 字节。类的对象的地址是第一个非静态成员变量的地址。
- 将一个类的对象初始化为 nullptr ,该空指针对象可以调用没有用到 this 指针的非静态成员变量,如果用到 this 指针,则意味着访问了空指针,程序将崩溃。
重载运算符
重载 new 和 delete 运算符,默认是 static 存储类的,所以定义内部不能使用非静态成员变量。
类的自动类型转换
构造函数自动类型转换:
- 有参构造可以将赋给类对象的值通过该构造函数转换。
- 使用 explicit 关闭隐式类型转换,必须显式类型转换。
将类类型转换为默认类型:
仅仅是将类类型中的某个成员返回,可以用运算符重载,也可以单独定义一个函数。
c11 也可以使用 explicit。
继承
构造函数和析构函数不能被继承。
派生类中的using
在派生类中使用 public 继承基类的 public 或 protected 成员,可以在派生类中使用 using 改变继承的成员权限。
内存模型
A 是基类,B 是派生类,16 字节代表着 A 中的 12 字节 + B 中新增的 4 字节。
派生类和基类的构造函数
- 当新建一个派生类对象,该对象对于基类的成员变量会使用基类的构造函数(因为基类中的私有成员不能在派生类初始化),对于派生类的成员变量会使用派生类的成员函数。
- 可以在派生类的构造函数中,委托构造给基类的构造函数。
- 在C++中,当创建派生类的对象时,基类的构造函数会被自动调用以初始化基类部分的成员变量。如果派生类的构造函数没有显式地调用基类的构造函数,那么基类的默认构造函数(无参数的构造函数)会被自动调用。如果基类没有默认构造函数,或者你需要调用一个带参数的基类构造函数,那么你必须在派生类的构造函数初始化列表中显式地调用它。
基类和派生类变量及函数重名
重名变量和重名函数(也不会触发重载,也就是说基类同名函数有参数,派生类同名函数无参数,那么派生类对象调用该函数就算加了参数,编译器也会报错。)会使用派生类的,也就是基类的成员被遮蔽。
解决方案:可以通过 类名+作用域解析符 指定调用哪个变量或函数。
如下:C 是 孙类,B 是子类,A 是父类。
基类的指针可以指向派生类对象
- 基类指针可以在不进行显式转换的情况下指向派生类对象:
1 | class A; |
- 基类引用可以在不进行显式转换的情况下引用派生类对象。
注意:
- 基类指针或引用只能调用基类的方法,不能调用派生类的方法。(即数据类型决定了操作数据的方法)
多态
析构派生类
派生类的析构函数执行完后,c++ 编译器会强制执行基类的析构函数。(手动 delete 情况)
解决方案:将基类的析构函数声明为 virtual,即虚析构函数。
对于基类,即使不需要析构函数,也应该提供一个空的虚析构函数。
在销毁基类指针指向的派生类对象时,不会调用派生类的析构函数,因为基类指针不能调用派生类成员。
纯虚类
普通函数设置为纯虚函数可以给代码实现也可以不给。
而析构函数,调用派生类的析构函数后,也会调用基类的析构函数。
因此将基类的析构函数设置为纯虚析构后,要加上代码实现(类外定义给空实现)。
纯虚析构函数作用:为了使一个类中没有任何纯虚函数的类变成抽象类,此时只需要声明一个纯虚析构函数。
dynamic_cast
用于将基类指针转换为派生类指针,用于使基类指针能够调用派生类的成员。类似 c 语言的强转,但是更安全。用于判断是否可以安全的将对象的地址赋值给特定类型的指针。
成功则返回对象的地址,失败返回空指针。
- dynamic_cast 只适用于包含虚函数的类。(因为需要查虚函数表)
- dynamic_cast 可以将派生类指针转换为基类指针,但是没有意义,因为派生类指针可以直接赋值给基类指针。
- dynamic_cast 可以用于引用,但是,没有与空指针对应的引用值,如果转换请求不正确,会出现 bad_cast 异常。
typeid 运算符
用于判断两个数据类型是否相同。
需要包含头文件
#include <typeinfo>
- type_info 类构造函数时 private,只能由编译器内部实例化。
- 不建议使用 typeid().name() 返回的字符串判断数据类型,因为不同编译器结果不同。
- typeid 运算符可以用于多态场景,可以比较基类对象和派生类对象,基类对象和派生类对象是相同类型的,但是指向派生类对象的基类指针和派生类指针的比较结果是不同的。
- typeid(*ptr) ,当 ptr 是空指针,如果 ptr 是多态类型,将引发 bad_typeid 异常。
函数模板
模板要求明确数据类型,可以用 swap<int> ();
尖括号的形式。
注意事项
可以为类的成员函数创建模板,但不能是虚函数和析构函数。
使用函数模板时,必须明确数据类型,确保实参与函数模板能匹配上。也就是说函数不能没有使用模板类型。
使用函数模板时,推导的数据类型必须适应函数模板中的代码。假如一个类使用了一个函数模板进行加的功能,会报错。
使用函数模板时,如果是自动类型推导,不会发生隐式类型转换,如果显式指定了函数模板的数据类型,可以发生隐式类型转换。也就是在尖括号中指定类型。
函数模板支持多个通用数据类型的参数。
1
template <typename T1, typename T2>
函数模板支持重载,可以有非通用数据类型的参数。
模板具体化
语法:
1 | template<> 与模板相同的返回类型 函数模板名 (参数列表) |
函数模板可以将函数声明与定义分离:
1 |
|
编译器使用各种函数的规则
具体化优先于常规模板,普通函数优先于具体化和常规模板。
如果希望使用函数模板,可以用空模板参数强制使用函数模板。
Swap<> (1, 2);
如果函数模板能产生更好的匹配,将优先于普通函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using namespace std; // 指定缺省的命名空间。
void Swap(int a, int b) // 普通函数。
{
cout << "使用了普通函数。\n";
}
template <typename T>
void Swap(T a, T b) // 函数模板。
{
cout << "使用了函数模板。\n";
}
template <>
void Swap(int a, int b) // 函数模板的具体化版本。
{
cout << "使用了具体化的函数模板。\n";
}
int main()
{
Swap(1, 2); // 使用了普通函数
Swap('c', 'd'); // 使用了函数模板,因为函数模板可以更匹配。
}
函数模板的分文件编写
- 函数模板只是函数的描述,没有实体,创建函数模板的声明和定义放在头文件中。
- 函数模板的具体化有实体,编译的原理和普通函数一样,所以,函数模板具体化的声明放在头文件中,定义放在源文件中。
public.h
1 |
|
public.cpp
1 |
|
demo.cpp
1 |
|
C++ 11 关于函数模板的拓展
decltype 关键字
decltype 不会执行表达式,也不会调用函数。
auto 会执行右边的表达式或者函数。
如果需要多次使用decltype,可以结合typedef和using。
简单来说,decltype 的类型要么是表达式的类型,要么是表达式的引用。用错了编译器会报错。
decltype推导规则(按步骤):
如果expression是一个没有用括号括起来的标识符,则var的类型与该标识符的类型相同,包括const等限定符。
如果expression是一个函数调用(也就是 func() 带小括号的),则var的类型与函数的返回值类型相同(函数不能返回void,但可以返回void *)。
如果expression是一个函数名(不带小括号,也就是函数指针),decltype(func) *ptr = func; ptr();
,这里通过ptr()
就调用了 func() 函数。如果expression是一个左值(能取地址)(要排除第一种情况)、或者用括号括起来的标识符,那么var的类型是expression的引用。
1
2
3
4
5
6
7short a = 5;
short b = 10;
short &ra = a;
decltype(++a) da = b; // 这里的da类型是 short&
decltype(a) da; // 这里的da类型是 short
decltype((a)) da = b; // 这里的da类型是 short&
decltype((func)) da = func; // 这里的da类型是 函数的引用,da()也可以调用函数如果上面的条件都不满足,则var的类型与expression的类型相同。
函数后置返回类型(c++14 可以用auto)
语法:auto func(T1 x, T2 y) -> decltype(x + y)
1 |
|
类模板
语法:
1 | template <class T1, class T2 = std::string> // 可以指定默认类型 |
注意:
在创建对象的时候,必须指明具体的数据类型(在尖括号内)。
Tclass<int, string> t1;
使用类模板时,数据类型必须适应类模板中的代码。
如:将整型赋值给 string 类型的代码就会报错。类模板可以为通用数据类型指定缺省的数据类型(C++11标准的函数模板也可以)。
模板类的成员函数可以在类外实现。
类外实现时,函数上一行加模板声明(不含默认参数),函数声明则要加尖括号以及作用域:T1 Tclass<T1, T2>::get_a(){}
可以用new创建模板类对象。
Tclass<int, string>* ptr = new Tclass<int, string>();
在程序中,模板类的成员函数使用了才会创建。
若是只是Tclass<int, string>* ptr;
,则都不会调用构造函数,构造函数中就算有错误代码也不会报错,运行时也不会报错。
创建模板类
- 先写一个普通类,用具体的数据类型,可以 typedef 定义一个名称,方便后续修改。
- 调试普通类
- 将普通类改为模板类
栈模板类示例:
1 |
|
类模板注意事项
类模板可以有非通用类型参数:
如 template <class T, int len = 10>
通常是整型(C++20标准可以用其它的类型);
实例化模板时必须用常量表达式;
模板中不能修改参数的值;
可以为非通用类型参数提供默认值。
C++ 11 之前,嵌套使用模板类的时候,>>
前要加空格。
如:vector<stack<string> >
类模板
具体化的模板类,成员函数类外实现的代码应该放在源文件中。
类的具体化分为:
- 完全具体化
- 部分具体化
1 |
|
模板类与继承
模板类继承普通类
正常继承即可,注意在派生类调用基类的构造函数(委托构造)普通类继承模板类的实例化版本
普通类继承模板类:普通类前加模板声明,派生类使用基类构造函数时要写成模板类型的基类构造形式。创建 AA 的实例化对象时,也需要给出模板类型,如
AA<int,string> aa(1, "2", 3);
模板类继承模板类:在派生类中增加基类的通用参数即可。
模板类继承模板参数给出的基类(不能是模板类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using namespace std; // 指定缺省的命名空间。
class AA {
public:
AA() { cout << "调用了AA的构造函数AA()。\n"; }
AA(int a) { cout << "调用了AA的构造函数AA(int a)。\n"; }
};
class BB {
public:
BB() { cout << "调用了BB的构造函数BB()。\n"; }
BB(int a) { cout << "调用了BB的构造函数BB(int a)。\n"; }
};
class CC {
public:
CC() { cout << "调用了CC的构造函数CC()。\n"; }
CC(int a) { cout << "调用了CC的构造函数CC(int a)。\n"; }
};
template<class T>
class DD {
public:
DD() { cout << "调用了DD的构造函数DD()。\n"; }
DD(int a) { cout << "调用了DD的构造函数DD(int a)。\n"; }
};
template<class T>
class EE : public T { // 模板类继承模板参数给出的基类。
public:
EE() :T() { cout << "调用了EE的构造函数EE()。\n"; } // 可以使用继承的模板参数的类的构造函数
EE(int a) :T(a) { cout << "调用了EE的构造函数EE(int a)。\n"; }
};
int main()
{
EE<AA> ea1; // AA作为基类。
EE<BB> eb1; // BB作为基类。
EE<CC> ec1; // CC作为基类。
EE<DD<int>> ed1; // EE<int>作为基类。 因为 DD 是模板类,需要给出具体类型。
// EE<DD> ed1; // DD作为基类,错误。
}
模板类与函数
传入的数据类型是什么不重要,只要适应模板中的代码就行。
模板类可以用于函数的参数和返回值,有三种形式:
普通函数,参数和返回值是模板类的实例化版本。
函数模板,参数和返回值是某种的模板类。不推荐,因为仅限于一种模板类。
函数模板,参数和返回值是任意类型(支持普通类和模板类和其它类型)。推荐,可传入任意类型(包括任意模板类型)。
1 |
|
模板类与友元
模板类的友元函数有三类:
非模板友元:友元函数不是模板函数,而是利用模板类参数生成的函数。
类内声明并定义友元函数,仅限于该模板类使用,不能被其它类声明为友元函数,同时,也不能同时提供该友元函数的具体化版本,因为编译器会自动在类外生成对应的具体类型版本的友元函数,导致重定义。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
using namespace std; // 指定缺省的命名空间。
template<class T1, class T2>
class AA
{
T1 m_x;
T2 m_y;
public:
AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
// 非模板友元:友元函数不是模板函数,而是利用模板类参数生成的函数,只能在类内实现。
friend void show(const AA<T1, T2>& a)
{
cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
}
/* friend void show(const AA<int, string>& a);
friend void show(const AA<char, string>& a);*/
};
//void show(const AA<int, string>& a)
//{
// cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
//}
//
//void show(const AA<char, string>& a)
//{
// cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
//}
int main()
{
AA<int, string> a(88, "我是一只傻傻鸟。");
show(a);
AA<char, string> b(88, "我是一只傻傻鸟。");
show(b);
}约束模板友元:模板类实例化时,每个实例化的类对应一个友元函数。
最好的模板友元解决方案。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
using namespace std; // 指定缺省的命名空间。
// 约束模板友元:模板类实例化时,每个实例化的类对应一个友元函数。
template <typename T>
void show(T& a); // 第一步:在模板类的定义前面,声明友元函数模板。
template<class T1, class T2>
class AA // 模板类AA。
{
friend void show<>(AA<T1, T2>& a); // 第二步:在模板类中,再次声明友元函数模板。
T1 m_x;
T2 m_y;
public:
AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};
template<class T1, class T2>
class BB // 模板类BB。
{
friend void show<>(BB<T1, T2>& a); // 第二步:在模板类中,再次声明友元函数模板。
T1 m_x;
T2 m_y;
public:
BB(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};
template <typename T> // 第三步:友元函数模板的定义。
void show(T& a)
{
cout << "通用:x = " << a.m_x << ", y = " << a.m_y << endl;
}
template <> // 第三步:具体化版本。
void show(AA<int, string>& a)
{
cout << "具体AA<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
}
template <> // 第三步:具体化版本。
void show(BB<int, string>& a)
{
cout << "具体BB<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
}
int main()
{
AA<int, string> a1(88, "我是一只傻傻鸟。");
show(a1); // 将使用具体化的版本。
AA<char, string> a2(88, "我是一只傻傻鸟。");
show(a2); // 将使用通用的版本。
BB<int, string> b1(88, "我是一只傻傻鸟。");
show(b1); // 将使用具体化的版本。
BB<char, string> b2(88, "我是一只傻傻鸟。");
show(b2); // 将使用通用的版本。
}非约束模板友元:模板类实例化时,如果实例化了n个类,也会实例化n个友元函数,每个实例化的类都拥有n个友元函数。
一般不用。不科学,实例化类应该只需要一个友元函数。但是语法很简单啊。。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using namespace std; // 指定缺省的命名空间。
// 非类模板约束的友元函数,实例化后,每个函数都是每个每个类的友元。
template<class T1, class T2>
class AA
{
template <typename T> friend void show(T& a); // 把函数模板设置为友元。
T1 m_x;
T2 m_y;
public:
AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};
template <typename T> void show(T& a) // 通用的函数模板。
{
cout << "通用:x = " << a.m_x << ", y = " << a.m_y << endl;
}
template <>void show(AA<int, string>& a) // 函数模板的具体版本。
{
cout << "具体<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
}
int main()
{
AA<int, string> a(88, "我是一只傻傻鸟。");
show(a); // 将使用具体化的版本。
AA<char, string> b(88, "我是一只傻傻鸟。");
show(b); // 将使用通用的版本。
}
模板类中创建模板类成员和模板函数成员
示例:
1 |
|
将模板类用作参数
函数模板不支持模板的模板参数。
该方法主要用于数据结构中。
示例:
1 |
|
编译、预处理、链接和命名空间
头文件(*.h):#include头文件、函数的声明、结构体的声明、类的声明、模板的声明、内联函数、#define和const定义的常量等。
源文件(*.cpp):函数的定义、类的定义、模板具体化的定义。
主程序(main函数所在的程序):主程序负责实现框架和核心流程,把需要用到的头文件用#include包含进来。
预处理
#include、#ifdef、#ifndef、#endif 等,进行文本复制。
预处理的包括以下方面:
1)处理#include头文件包含指令。
2)处理#ifdef #else #endif、#ifndef #else #endif条件编译指令。
3)处理#define宏定义。
4)为代码添加行号、文件名和函数名。
5)删除注释。
6)保留部分#pragma编译指令(编译的时候会用到)。
编译、链接
编译:将预处理生成的文件,经过词法分析、语法分析、语义分析以及优化和汇编后,编译成若干个目标文件(二进制文件)。
链接:将编译后的目标文件,以及它们所需要的库文件链接在一起,形成一个整体。
使用全局变量时,最好在 .cpp 文件中定义,并在对应的 .h 中 extern 声明。然后 include 就可以使用了。 不能将定义放在头文件中,因为会重复包含,导致多个定义。
其它关键知识点
分开编译的好处:每次只编译修改过的源文件,然后再链接,效率最高。
编译单个*.cpp文件的时候,必须要让编译器知道名称的存在,否则会出现找不到标识符的错误。(直接和间接包含头文件都可以)
编译单个*.cpp文件的时候,编译器只需要知道名称的存在,不会把它们的定义一起编译。
如果函数和类的定义不存在,编译不会报错,但链接会出现无法解析的外部命令。
链接的时候,变量、函数和类的定义只能有一个,否则会出现重定义的错误。(如果把变量、函数和类的定义放在*.h文件中,*.h会被多次包含,链接前可能存在多个副本;如果放在*.cpp文件中,*.cpp文件不会被包含,只会被编译一次,链接前只存在一个版本)
把变量、函数和类的定义放在*.h中是不规范的做法,如果*.h被多个*.cpp包含,会出现重定义。
用#include包含*.cpp也是不规范的做法,原理同上。
尽可能不使用全局变量,如果一定要用,要在*.h文件中声明(需要加extern关键字),在*.cpp文件中定义。
全局的const常量在头文件中定义(const常量仅在单个文件内有效)。在头文件中extern声明,源文件中定义,并在需要使用的文件中extern后,使用可行。
.h文件重复包含的处理方法只对单个的.cpp文件有效,不是整个项目。
如果你的项目包含多个源文件(
.cpp
文件),并且这些源文件都包含了同一个头文件,那么头文件的包含保护只对单个源文件有效。这是因为每个源文件是独立编译的单元,它们在编译时是相互独立的。函数模板和类模板的声明和定义可以分开书写,但它们的定义并不是真实的定义,只能放在*.h文件中;函数模板和类模板的具体化版本的代码是真实的定义,所以放在*.cpp文件中。
Linux下C++编译和链接的原理与VS一样。
命名空间
创建命名空间语法:
1 | namespace Test |
在 main 函数中可以使用 using Test::a;
,但是这样 main 中再定义同名变量就会导致重定义。使用 using namespace Test
的话是可以再定义同名变量的,但是会遮蔽命名空间的变量。如果要使用命名空间的变量,则要加 Test::a
作用域和解析符。
注意点
命名空间是全局的,可以分布在多个文件中。
命名空间可以嵌套。
在命名空间中声明全局变量,而不是使用外部全局变量和静态变量。
对于using声明,首选将其作用域设置为局部而不是全局。
不要在头文件中使用using编译指令,如果非要使用,应将它放在所有的#include之后。
匿名的命名空间,从创建的位置到文件结束有效(也就是当前文件有效,因为没有名字,其它地方也调用不了)。
1
2
3
4namespace
{
int b;
}
c++强制转换语法
static_cast<目标类型>(表达式)、reinterpret_cast、const_cast、dynamic_cast
static_cast
static_cast不能用于转换不同类型的指针(引用)(不考虑有继承关系的情况),必须借助
void*
。一般用于向函数中传递一个指针,函数参数是
void*
,然后转为void*
后再在函数内部使用 static_cast 转换为其它类型指针。
- 内置普通类型转换,与 c 语法强转没有区别。
- 指针类型转换。如 int* 转换为 double* ,需要先将 int* 类型赋值给 void* 变量,再通过 static_cast 将这个 void* 变量转换为 double*。C 风格可以直接强转。
1 |
|
reinterpret_cast
reinterpret_cast的意思是重新解释,能够将一种对象类型转换为另一种,不管它们是否有关系。
转换指针类型时不需要借助 void*。
reinterpret_cast<目标类型>(表达式); <目标类型>和(表达式)中必须有一个是指针(引用)类型。
不能丢掉 const 和 volitale 属性。
应用场景:
reinterpret_cast的第一种用途是改变指针(引用)的类型。引用本质还是指针。
reinterpret_cast的第二种用途是将指针(引用)转换成整型变量。整型与指针占用的字节数必须一致,否则会出现警告,转换可能损失精度。
reinterpret_cast的第三种用途是将一个整型变量转换成指针(引用)。
1 |
|
const_cast
用于丢掉 const 和 volitale 属性。
static_cast不能丢掉指针(引用)的const和volitale属性,const_cast可以。
1 |
|