C++ Primer学习笔记

4.11 类型转换

相互转换 conversion,如果两种类型可以相互转换,那么它们是关联的。
例如,下面的表达式,ival会初始化为6

int ival = 3.541 + 3;

编译器会自动将double类型3.541转换为int类型3,然后让3+3得到6,再赋值给ival。这个过程的类型转换是自动执行的,无须程序员介入,称为隐式转换(implicit conversion)

何时发生隐式类型转换?
以下情形,编译器会自动转换运算对象的类型:

  • 大多数表达式中,比int类型小(指的是存储的位数少)的整型值,会提升为较大的整数类型。比如char 数据 + int 数据,会将char数据转换成int类型。
  • 在条件中,非布尔转换为布尔类型。如if (((int)a)){} ,这里a会自动转换为bool类型。
  • 初始化过程中,初值转换成变量的类型;在赋值语句中,右侧运算对象转换成左侧对象类型。如int a = 3.14,double 3.14会转换成int型;double b = 3.14; int a = b; ,b会转换成int类型。
  • 如果算术运算或关系运算的运算对象有多种类型,需要转换成同种类型。如double a = 1 + 2.0 + 3.1f; , 等式右侧会转换成double类型进行运算。
  • 函数调用也会发生类型转换(实参 -> 形参)。

4.11.1 算术转换

原则:不丢失数据信息

整型提升
负责把小整数转换成较大的整数类型。小整数优先转换成int,其次是unsigned int。
bool, char, signed char, unsigned char, short, unsiged short等类型,只要所有值都能存在int内,就会转换成int。否则,提升为unsigned int。

无符号类型的运算对象

  1. 如果一个运算对象是无符号类型,另外一个运算对象是带符号类型,其中的无符号类型不小于带符号类型(指的是存储位宽),那么带符号类型运算对象转换成无符号的。
    例如,假设两个类型分别为unsigned int和int,则int类型运算对象转换成unsinged int。

  2. 如果带符号类型大于无符号类型,此时转换结果依赖于机器。如果无符号类型所有值都能存在该带符号类型中,则无符号类型的运算对象转换成带符号类型。如果不能,那么带符号类型运算对象转换成无符号类型。
    例如,如果两个类型分别是long和unsigned int,且int和long大小相同(都是4byte),则long数转换成unsigned int。如果long类型占用空间比int多,则unsigned int转换成long。

理解算术转换
下面的例子,用typeid和sizeof来判断转换后的类型和占用字节。其中,typeid运算符获取类型信息,sizeof打印占用字节数。

bool flag;  char cval;
short sval; unsigned short usval;
int ival;   unsigned int uival;
long lval;  unsigned long ulval;
float fval; double  dval;

cout << "===basic type info==="<< endl;
cout << "bool flag: " << typeid(flag).name() << ", " << sizeof(flag) << endl;
cout << "char cval: " << typeid(cval).name() << ", " << sizeof(cval) << endl;
cout << "short sval: " << typeid(sval).name() << ", " << sizeof(sval) << endl;
cout << "unsigned short usval: " << typeid(usval).name() << ", " << sizeof(usval) << endl;
cout << "int ival: " << typeid(ival).name() << ", " << sizeof(ival) << endl;
cout << "unsinged int uival: " << typeid(uival).name() << ", " << sizeof(uival) << endl;
cout << "long lval: " << typeid(lval).name() << ", " << sizeof(lval) << endl;
cout << "unsigned long ulval: " << typeid(ulval).name() << ", " << sizeof(ulval) << endl;
cout << "float uival: " << typeid(uival).name() << ", " << sizeof(uival) << endl;
cout << "double ival: " << typeid(ival).name() << ", " << sizeof(ival) << endl;

cout << "
===conversion type exmpale info==="<< endl;
cout << "3.14159L + 'a': " << typeid(3.14159L + 'a').name() << ", " << sizeof(3.14159L + 'a') << endl; // 'a' 提升为int, 然后转换为long double
cout << "dval + ival: " << typeid(dval + ival).name() << ", " << sizeof(dval + ival) << endl; // ival转换成double
cout << "dval + fval: " << typeid(dval + fval).name() << ", " << sizeof(dval + fval) << endl; // fval转换成double
cout << "ival = dval: " << typeid(ival = dval).name() << ", " << sizeof(ival = dval) << endl; // dval转换成int
cout << "flag = dval: " << typeid(flag = dval).name() << ", " << sizeof(flag = dval) << endl; // dval转换成bool
cout << "cval + fval: " << typeid(cval + fval).name() << ", " << sizeof(cval + fval) << endl; // cval提升为int, 然后转化成float
cout << "sval + cval: " << typeid(sval + cval).name() << ", " << sizeof(sval + cval) << endl; // sval和cval都提升为int
cout << "cval + lval: " << typeid(cval + lval).name() << ", " << sizeof(cval + lval) << endl; // cval转换为long
cout << "ival + ulval: " << typeid(ival + ulval).name() << ", " << sizeof(ival + ulval) << endl; // ival转换为unsigned long
cout << "usval + ival: " << typeid(usval + ival).name() << ", " << sizeof(usval + ival) << endl; // 根据unsigned short和int所占空间大小进行提升
cout << "uival + ival: " << typeid(uival + ival).name() << ", " << sizeof(uival + ival) << endl; // 根据unsigned int和long所占空间大小进行提升

运行结果

===basic type info===
bool flag: b, 1
char cval: c, 1
short sval: s, 2
unsigned short usval: t, 2
int ival: i, 4
unsinged int uival: j, 4
long lval: l, 4
unsigned long ulval: m, 4
float uival: j, 4
double ival: i, 4

===conversion type exmpale info===
3.14159L + 'a': e, 12
dval + ival: d, 8
dval + fval: d, 8
ival = dval: i, 4
flag = dval: b, 1
cval + fval: f, 4
sval + cval: i, 4
cval + lval: l, 4
ival + ulval: m, 4
usval + ival: i, 4
uival + ival: j, 4

4.11.2 其他隐式转换

除算术转换为的几种隐式类型转换:
1. 数组转换成指针
大多数用到数组的表达式中,数组自动转换成指向数组首元素的指针:

int ia[10]; // ia是含有10个元素的数组
int* ip = ia; // ia转换成指向数组首元素的指针

当数组被用作decltype关键字参数时,或者作为取地址符(&),sizeof及typeid等运算符运算对象时,转换不会发生。而表达式使用函数类型时,会发生类似指针转换。

2. 指针的转换
常量整数值0或字面值nullptr能转换成任意指针类型;
指向任意非常量的指针能转换成void *;
指向任意对象的指针能转换成const void *;
在有继承关系的类型间还有另外一种指针转换的方式;

3. 转换成布尔类型
指针/算术类型 = 0 => flase;
指针/算术类型 = 1 => true;

char *cp = get_string();
if (cp) /*...*/
while (*cp) /*...*/

4. 转换成常量

int i;
const int &j = i; // 非常量转换成const int的引用
const int *p = &i; // 非常量的地址转换成const int的地址
int &r = j, *q = p; // 错误:不允许const转换成非常量

5. 类类型定义的转换
编译器自动执行,每次只能执行一种类型转换。如果同时提出多个转换请求,这些请求将被拒绝。

string s, t = "a value"; // 字符串字面量转换成string
while(cin >> s) {...} // istream类型cin转换成bool

4.11.3 显式转换

命名的强制类型转换
1. static_cast
任何具有明确定义的类型转换,只要不包含底层const, 都可以使用static_cast。 static_cast不能转换掉const性质。
另外,在确保基类向派生类转换是安全的情况下,可以使用staci_cast强制覆盖编译器的检查工作。
例,通过将一个int数转换成double进行浮点数运算

double slope = static_cast<double>(j) / i; // i, j 都是int类型

static_cast对于编译器无法自动执行的类型转换也非常有用。例如,可以使用static_cast找回存在于void *指针中的值。

void *p = &d;
double *dp = static_cast<double *>(p); // 将void *转换回初始化的指针类型。需要确保转换后的类型就是指针所指的类型,否则会产生未定义后果

2. dynamic_cast
一般情况下,不允许将基类指针或引用转换成派生类指针或引用。dynamic_cast让类型转换的安全检查在运行时执行,从而安全地将基类指针或引用,转换为派生类指针或引用。
参考C++中深入理解dynamic_cast

class Base {
public:
  Base();
  ~Base();
  void print();
};

class Inherit : public Base {
public:
  Inherit();
  ~Inherit();
  
  void show();
}

int main() {
  Base* pB = new Base();
  Inherit *pI = dynamic_cast<Inherit *>pB;
  pI->show();
  return 0;
}

3. const_cast
去掉底层const。const_cast只能改变常量属性

const char *pc;
char *p = const_cast<char *>(pc); // 正确:但通过p写值是未定义行为。能否通过p写值,取决于对象本身是否为一个常量

4. reinterpret_cast
通常为运算对象的位模式提供较低层次上的重新解释。
慎用,可能导致运行错误。

int *ip;
char *pc = reinterpret_cast<char *>(ip);

string str(pc); // 错误,可能导致运行时异常

需要牢记pc实际上是指向int的指针,如果将pc指向char的指针,可能导致异常。

何为顶层const和底层const?
顶层const:表示指针对象本身是一个常量,或者const修饰的任意对象本身是常量;
底层const:表示指针所指对象是一个常量;
简便记法:const距离谁近(指针,还是。如果是,要求const在*的前面),就表示对于对象不可变。

int i = 0;
int *const p1 = &i; // const距离p1近,表示指针p1是常量,也就是说p1是顶层const
const int ci = 42;  // const修饰的ci是常量,这是顶层const
const int *p2 = &ci; // p2能改变,但p2指向的对象不能改变,这是底层const
原文地址:https://www.cnblogs.com/fortunely/p/14453815.html