如何攻克 C++ 中复杂的类型转换?
作者 | 樱雨楼
责编 | 胡雪蕊
出品 | CSDN(ID:CSDNnews)
引言
不同的数据在计算机内存中的存储方式不同,导致了“类型”这一抽象概念的出现。
对于一个变量而言,其必须要回答三个问题:
1. 在哪可以访问到这个变量的起点?
2. 从起点向后需要读取多少内存?
3. 应该如何解析读取到的二进制数据?
上述的三个问题中,问题 1,由内存地址回答,问题 2 和 3,均由类型回答。
由此可见,类型与内存地址共同构成了一个变量的完整组分。之所以不能对 void 取值,也是由于无法回答问题 2 和 3 导致。
进一步的,我们可以得到一条十分重要的结论:对于两个不同类型的变量,由于其对问题 2 和 3 的答案不同,故如果将这样的两个变量直接进行运算,在绝大多数情况下都将无法产生有价值的计算结果。
故在几乎所有的编程语言中都有一条重要的规定:不同类型的两个变量无法直接进行运算。
虽然不同类型的两个变量无法进行运算,但显然,我们可将其中的一个变量通过类型转换,转为与另一个变量类型一致,此时就满足“同类型变量才能进行运算”这一规定了。
同时,由于某些类型转换是“理所应当”的,而另一些不是,故由此又派生出两个概念:隐式类型转换与显式类型转换。
隐式类型转换指不通过专门的类型转换操作,而是通过其它规定或代码上下文隐式发生的类型转换,而显式类型转换则通过专门的类型转换操作进行转换,显式类型转换具有强制性,其将不受任何类型转换以外的因素影响,故显式类型转换又称为强制类型转换 。
在 C++ 中,类型转换是一个非常复杂的话题。本文将先从隐式类型转换入手,逐步讨论各类 C++ 的类型转换话题。
类型提升与算术类型转换
算术类型转换专指 C++ 提供的各种内置算术类型之间的隐式类型转换。
内置算术类型主要包括以下类型:
1. bool
2. char, signed char, unsigned char
3. short, int, long, long long, unsignedshort, unsigned int, unsigned long, unsigned long long
4. float, double, long double
5. size_t, ptrdiff_t, nullptr_t 等其它特殊类型
算术类型转换是一类不完全明确的,且与底层密切相关的隐式类型转换。
其遵循以下几条主要原则:
1. 对于同类算术类型,如short 与 int,float 与 double,占用较小内存的类型将转换成另一类型。如 short+ int将被转换为 int + int。此种类型转换称为类型提升。
2. 整形将转为浮点型。如 int+ double 将被转换为 double + double。
3. 仅当无符号类型占用的内存小于有符号类型时,无符号类型才发生类型提升从而转为有符号类型,否则,有符号类型将转为无符号类型。这是一个非常需要注意的点。
参考以下代码:
{
unsigned short a = 1;
unsigned b = 1;
cout << (a > -1) << " " << (b > -1) << endl; // 1 0!
}
3.1 定义转换构造函数
构造函数只有一个形参
构造函数有不止一个形参,但只有第一形参无默认值
构造函数有不止一个形参,但全部形参均有默认值
2. 第一形参的类型不为类本身或其附加类型(否则此构造函数将成为拷贝构造函数或移动构造函数)
如果一个类定义了某种转换构造函数,则被定义的类型将可以通过任何类型转换方式转为当前类类型。这常见于以下几种情况:
1. 赋值时发生的隐式类型转换
2. 实参传递时发生的隐式类型转换
3. 基于 static_cast 的显式类型转换
参考以下代码:
struct A { A (int) {} }; // 转换构造函数
void test(A) {}
int main()
{
A _ = 0; // 赋值时发生的隐式类型转换
test(0); // 实参传递时发生的隐式类型转换
}
void test(A) {}
int main()
{
A _ = 0; // Error!禁止赋值时发生的隐式类型转换!
test(0); // Error!禁止实参传递时发生的隐式类型转换!
static_cast<A>(0); // explicit不影响强制类型转换
A(0); // explicit不影响对转换构造函数的正常调用
}
转换构造函数定义了其它类型向类类型的转换方案,类型转换运算符则定义了与之相反的过程:其用于定义类类型向其它类型的转换方案。当类定义了某种类型的类型转换运算符后,类类型将可以向被定义类型发生类型转换。
参考以下代码:
void test(int) {}
int main()
{
test(A()); // 发生了A -> int的隐式类型转换
}
void test(int) {}
int main()
{
test(A()); // Error!禁止A -> int的隐式类型转换
test(static_cast<int>(A())); // explicit不影响强制类型转换
}
int main()
{
if (A()) {} // 即使operator bool()被声明为explicit,其在if中也能发生隐式类型转换
}
struct A {};
struct B: A {};
int main()
{
A a1 = B(); // 值向上转换
A *a2 = new B; // 指针向上转换
A &a3 = a1; // 左值引用向上转换
A &&a4 = B(); // 右值引用向上转换
}
说明如下:
public:当用于访问说明符时,表示对类的一切用户可见;用于继承时,表示继承时不修改基类的一切访问说明符
protected:当用于访问说明符时,表示仅对类的继承用户可见,对类的实例用户不可见;用于继承时,表示将基类的一切 public 访问说明符在继承类中修改为 protected
private:当用于访问说明符时,表示对一切类的用户均不可见;用于继承时,表示将基类的一切 public 和 protected 访问说明符在继承类中修改为 private
上述描述中,“将基类的xxx访问说明符在继承类中修改为xxx”是一个很奇怪且魔幻的描述,我们不禁要思考,为什么 C++ 会给出这样的三种继承模式?又为什么要“伴随着继承修改访问说明符”呢?
如果我们从向上类型转换这一角度思考,就能得出答案:
public:不阻止任何用户进行向上类型转换
protected:阻止类的实例用户进行向上类型转换
private:阻止一切用户进行向上类型转换
struct A {};
struct B: A {}; // 不阻止任何B类的用户向A进行类型转换
struct C: protected A {}; // 阻止C类的实例用户向A进行类型转换
struct D: private A {}; // 阻止D类的一切用户向A进行类型转换
struct E: B { void test() { static_cast<A *>(this); } }; // B类的继承类用户可以向A进行类型转换
struct F: C { void test() { static_cast<A *>(this); } }; // C类的继承类用户可以向A进行类型转换
struct E: D { void test() { static_cast<A *>(this); } }; // Error!D类的继承类用户不可以向A进行类型转换
int main()
{
static_cast<A *>(new B); // B类的实例用户可以向A进行类型转换
static_cast<A *>(new C); // Error!C类的实例用户不可以向A进行类型转换
static_cast<A *>(new D); // Error!D类的实例用户不可以向A进行类型转换
}
上述代码中,类 B、C、D 分别以三种不同的访问说明符继承自类 A,同时,我们分别为类B、C、D 各定义了一个继承类用户和一个实例用户。
由此可见,public 继承将不阻止类的任何用户进行向上类型转换,而 private 继承将阻止类的一切用户进行向上类型转换,protected 继承只阻止类的实例用户进行向上类型转换,但不阻止类的继承类用户进行向上类型转换。
5.3 多重继承与向上类型转换
对于多重继承,其向上类型转换对于同一继承层的多个基类是全面进行的。
参考以下代码:
struct B { int i; };
struct C: A, B { int i; };
struct D: A, B {};
int main()
{
C().i; // 访问C::i
D().i; // Error!存在二义性!
}
C++ 中还定义了一些特殊的类型转换,以下列举出一些常见的情况:
1. 0 转换为空指针
{
int *p = 0;
}
2. 数组退化为指针
{
int a[10];
int *p = a;
}
3. 空指针或数字 0 转为 false,其它指针或数字转为 true
{
if(nullptr) {}
if (2) {}
}
4. T转换为 void
{
void *p = new int;
}
5. 非 const 转换为 const
{
int *a;
const int * const b = a;
}
作者简介:樱雨楼,毕业于生物信息学专业,是一枚Python/C++/Perl开发,自称R语言黑粉,Github勾搭:https://github.com/yingyulou
【END】
热 文 推 荐
☞华为宣布方舟编译器将开源;苹果秋季发布会定档9月10日;TypeScript 3.6 发布 | 极客头条
☞99年少年12岁时买下100枚比特币, 如今却将所有积蓄压在一个不知名的代币上,还放话将超越Libra!