C语言的类型转换
有过C语言基础的读者应该知道,C语言可以进行两种不同的类型转换:
- 自动类型转换
double
的优先级最高,即所有转换都朝向优先级高的方向进行,不会违反这个顺序
这是基本的类型转换方法,其过程是隐式完成的,不需要程序猿人为干预 - 强制类型转换
这类转换一般多见于嵌入式开发中,例如:
类似于上述例子的,违反自动类型转换优先级的,称为强制类型转换float x = 32.333; int y = (int)x; // 取变量x的整数部分,x = 32
C++ 完全继承了C语言的类型转换机制,而且在这个基础上,派生出更多更强大的类型转换方法
C++基本类型转换的新语法.
C++对于基本类型的转换,除了从C语言中继承的以外,也出现了新的语法糖,以上面的截尾操作为例:
float x = 32.333;
int y = int(x); // 语法风格上更接近python这样的动态语言
这种写法在各种脚本语言或者解释型语言中较为常见,归根结底这是一种函数形式的转换,即将int()
作为一个函数来调用;不过很可惜的是我并没有在STL中找到用于基本数据类型的函数声明,窃以为这种形式是直接在编译器中实现的
这种方法对于所有的基本数据类型都适用,只要给定typename()
函数就行了;不过相应的,推荐使用这种新的语法做转换,以便于和接下来介绍的转换方法保持形式上的一致
C++自定义数据类型与基本类型的转换
自定义数据类型主要是指class/struct
,这二者在c++中并无本质区别——虽然这二位也算是基本数据类型但是不同的类之间,是没办法拿它们作为一个数据类型来看待的
为了叙述方便,这里给出一个类,它接受浮点数并将该浮点数表示的小时数转换为x hours, y minutes
这样的格式:
class timeconv
{
private:
int hours; // 小时
int minutes; // 分钟
public:
timeconv() {} // 默认构造函数
timeconv(double h) // 转换构造函数
{
hours = int(h);
minutes = int((h - hours) * 60);
}
// 重载double() 转换函数
operator double() const { return hours + double(minutes) / 60.0f; }
// 重载<<,将这个类嵌入到标准流中(省略实现)
friend std::ostream& operator<<(std::ostream& os, const timeconv& s) const;
};
基本数据类型向自定义类型的转换
可以看到,上面代码中给出的类的声明中包含一个接受double
类型的构造函数。这意味着如果要创建一个timeconv
类,以下几种写法是等价的:
timeconv mytc;
mytc = 2.0; // the 1st way
mytc = timeconv(2.0); // the 2nd way
mytc = (timeconv)2.0; // the 3rd way
其中,第一种方式隐式的调用了在类声明中定义的转换构造函数,第二种和第三种方式都显式的调用了转换构造函数,而且第三种方式是继承自C语言的风格,因此不太推荐;但是这三种方法在语义上是完全等价的,也都完成了由基本类型向自定义类型的转换
观察上面的例子,转换构造函数至少应该拥有这样的特点:
- 是构造函数
- 仅含有一个参数,且该参数不是自定义类型的
const
引用
然而事实上,这样的函数也可以是转换构造函数:
timeconv(int hours, int minutes = 0);
跟上面的代码相比,不难发现,该函数虽然有两个参数,但是其中一个参数有默认值;因此,转换构造函数的特点应该是:
- 是构造函数
- 无默认值的函数参数有且只能有一个
这样我们就借助转换构造函数完成了由基本类型向自定义类型的转换
可能存在的转换二义性
虽然在上一小节完成了预期的目标,但是这样的转换是有一定风险的,考虑以下代码:
class timeconv
{
// 其他内容省略
public:
timeconv(long hours);
timeconv(double hours);
}
uint8_t xx = 16u;
timecove nytc = xx;
可以看到,这里给了两个构造转换函数,而且在最终构造类的时候,根据基本类型的自动转换规则,uint8_t
,也就是unsigned char
类型,既可以转换为long
,也可以转换为double
——这样就给编译器出了个老大难的问题,到底应该调用哪个转换构造函数?
一般这种现象被称之为转换的二义性,这样的代码必然会被编译器拒绝而报出编译错误,在实际的工程中,应当注意可以被隐式调动的转换构造函数的个数,使之不多于一个
小插曲:explicit
关键字
刚才提到了转换的二义性,而且提出解决方法是让可以被隐式调用的转换构造函数不得多于一个——那么在具体的工程中,真的不能定义多个转换构造函数吗?答案是能,这个时候就要请出expilicit
关键字了
可以在不希望被隐式调用的转换构造函数前加上该关键字,使得该转换构造函数必须被显式调用:
class timeconv
{
// 省略其他内容
public:
explicit timeconv(double hours); // 不希望隐式调用该函数
}
timeconv mytc;
mytc = 16.00; // 非法,未显式调用转换构造函数
mytc = timeconv(16.00); // 合法
mytc = (timeconv)16.00; // 合法
自定义类型向基本数据类型的转换
做到了基本类型向自定义类型的转换,自然也可以逆向该过程,实现自定义类型向基本类型的转换,这个过程需要依赖转换函数,而且该转换属于强制类型转换,即不可能隐式调用转换函数完成
转换函数:由用户定义的强制类型转换
前面timeconv
类的完整声明中已经给出了一个定义好的double()
转换函数,这些转换函数具有这样的特点:
- 声明中无返回值类型,但是函数体中必须带有
return
语句,且必须返回该转换函数声明的类型 - 声明中无函数参数,也不必使用
void
填充 - 声明中必须带
operator
关键字 - 函数名就是转换后的数据类型
- 必须声明为类成员函数
虽然讲了一大堆注意事项,但是转换函数的调用是非常简单的:
timeconv mytc = timeconv(17.00); // 定义一个待转换的类
double xx1 = double(mytc); // the 1st way
double xx2 = (double)mytc; // the 2nd way
如同这段示例代码展示的那样,两种书写方式都是合法且易用的,它们完整继承了C++的强制转换语法,因此只需花费一点心思在转换函数的声明与定义上即可
结束
本文粗浅的讨论了一下C++中基本的类型转换,对于自定义类的相互转换,例如static_cast
、dynamic_cast
等知识,将会在以后的文章中继续发布