C++类型转换

Wesley13
• 阅读 909

隐式转换

在赋值给一个兼容类型会出现隐式类型转换.比如下面这个例子.

short a=2000;
int b;
b=a;

在以上例子中.值从short自动提升到int,这是标准转换。标准转换影响基本数据类型,它在类型数字类型之间(short to intint to floatdouble to int...),

布尔类型和一些指针之间执行。

从小的数字类型转换成int,或者float to double叫做类型提升。这样的转换保证生成相同的值。但是其他一些转换不保证一定生成同样的值。

    1.如果负数转换成unsigned 类型。-1转换成最大无符号值。

    2.其他类型转bool或者由bool转换成其他类型。false转成0(数值类型)nullptr(指针类型)。true转换成1。

    3.由浮点数转换成整数类型.值被截断(小数部分直接被移除).如果剩余的部分超出整数能表示的范围,结果未知。

    4.如果转换发生在相同的数值类型.整数-整数,浮点数-浮点数.转换是合法的.但是具体的值是多少由实现着指定(可能不具有移植性)。

某些转换可能会丢失精度,编译器会通知出现精度丢失,但是显式的转换不会出现通知。

对于非基本类型,数组和函数隐式转换成指针类型。指针之间转换根据以下规则

    1.null指针允许转换成任意类型。

    2.任何类型指针都能转换成void类型指针。

    3.指针向上转换,派生类指针能够转换成任意基类指针,前提是没有const,volatile修饰。

类的隐式转换

在类的世界里,类的转换由以下三个成员函数控制。

    1.只有一个参数的构造函数:允许从一个特定类型隐式转换来初始化对象。

    2.赋值操作:允许在赋值的时候出现隐式转换。

    3.类型转换操作:允许隐式转换一个特定类型。

// implicit conversion of classes:
#include <iostream>
using namespace std;

class A {};

class B {
public:
  // conversion from A (constructor):
  B (const A& x) {}
  // conversion from A (assignment):
  B& operator= (const A& x) {return *this;}
  // conversion to A (type-cast operator)
  operator A() {return A();}
};

int main ()
{
  A foo;
  B bar = foo;    // calls constructor
  bar = foo;      // calls assignment
  foo = bar;      // calls type-cast operator
  return 0;
}

类型转换操作使用特殊的语法:它使用operator关键字后面跟上目标类型然后是一对圆括号。注意返回的是特定对象的类型,并且也没有在operator关键字之前指定。

explicit关键字

在函数调用时,C++允许隐式转换参数,这可能会引起错误。比如下面这个函数(来自上面的例子)

void fn (B arg) {}

这个函数的参数类型是B,但是它可以用A来调用。

fn(foo)

我们可以在构造函数上使用explicit关键字消除这个影响。

// explicit:
#include <iostream>
using namespace std;

class A {};

class B {
public:
  explicit B (const A& x) {}
  B& operator= (const A& x) {return *this;}
  operator A() {return A();}
};

void fn (B x) {}

int main ()
{
  A foo;
  B bar (foo);
  bar = foo;
  foo = bar;
  
//  fn (foo);  // not allowed for explicit ctor.
  fn (bar);  

  return 0;
}

另外,在使用explicit标记构造函数后,不能使用类似赋值的方法隐式调用构造函数。比如下面这个不允许。

B bar = foo;

类型转换函数也可以加上explicit关键字,效果和在构造函数上加一致。

foo = bar; //转换函数加上explicit后,这个调用是错误的.

类型转换

C++是一个强类型语言.有许多类型转换不能隐式进行。特别是表示对值有不同解释的转换,这些类型转换都需要显示指定。

主要有俩种风格,函数型风格和C语言风格,如下。

double x = 10.3;
int y;
y = int (x);    // functional notation
y = (int) x;    // c-like cast notation

函数风格转换满足大部分基本类型转换。但在类和指针转换到类的时候会混淆不清。这样容易引起运行时错误,比如下面这个代码,编译时没有任何错误。

// class type-casting
#include <iostream>
using namespace std;

class Dummy {
    double i,j;
};

class Addition {
    int x,y;
  public:
    Addition (int a, int b) { x=a; y=b; }
    int result() { return x+y;}
};

int main () {
  Dummy d;
  Addition * padd;
  padd = (Addition*) &d;
  cout << padd->result();
  return 0;
}

程序声明一个指针指向Addition,但是它被赋值了一个不相关的对象。

padd = (Addition*) &d;

自由的显式类型转换允许一个指针类型转换成任何一个指针类型。上面的调用会导致一个运行时错误或者是一个不期望的结果。

为了控制这种不受控制的转换,我们新增了四种特定的类型转换。dynamic_cast,static_cast,reinterpret_cast,const_cast。

语法如下。

dynamic_cast <new_type> (expression)
reinterpret_cast <new_type> (expression)
static_cast <new_type> (expression)
const_cast <new_type> (expression)

等价于传统的类型转换

(new_type) expression
new_type (expression)

但是以上的每一种类型转换都有自己独特的特点。

dynamic_cast

dynamic_cast仅能在指针或者引用上使用,当然也包含void*。它用来保证转换的目标类型是一个完全合法的类型。这个转换包括指针向上转换(基类指针转换成父类指针),有时候这个也叫做隐式转换。同样,dynamic_cast也被用来实现向下转换,但也只是在多态类的时候。如下

// dynamic_cast
#include <iostream>
#include <exception>
using namespace std;

class Base { virtual void dummy() {} };
class Derived: public Base { int a; };

int main () {
  try {
    Base * pba = new Derived;
    Base * pbb = new Base;
    Derived * pd;

    pd = dynamic_cast<Derived*>(pba);
    if (pd==0) cout << "Null pointer on first type-cast.\n";

    pd = dynamic_cast<Derived*>(pbb);
    if (pd==0) cout << "Null pointer on second type-cast.\n";

  } catch (exception& e) {cout << "Exception: " << e.what();}
  return 0;
}

Null pointer on second type-cast.

兼容提示: dynamic_cast需要运行时信息追踪动态类型。某些编译器这个功能默认是关闭的。为了保证dynamic_cast运行正确,请打开运行时类型检查选项。

以上代码尝试执行俩次类型转换,都是从基类转换成子类,但是只有第一次是成功的。注意他们的初始化。

Base * pba = new Derived;
Base * pbb = new Base;

虽然他们都是Base* 类型的指针,但是pba实际上指向的是Derived类型的指针,pbb的实际类型是Base*。因此当使用dynamic_cast转换的时候,pba成功了,因为pbb指向的是Base,不是一个完整的Delived对象,所以转换时失败。

当转换失败的时候,dynamic_cast返回一个null指针表示转换失败。如果dynamic_cast转换一个引用失败,将会抛出bad_cast类型的异常。

dynamic_cast同样也允许在指针上执行隐式转换,null指针在俩个类型之间转换(即使是没有任何关联的类型),转换任何指针类型变成void* 类型指针。 

static_cast

static_cast在俩个相关类型之间执行转换,可以是往上转换,也可以是往下转换。转换时不执行任何运行时检查,因此也就不保证目标类型一定正确。所以,static_cast需要程序员保证转换是安全的。与dynamic_cast相比,static_cast转换更快。

class Base {};
class Derived: public Base {};
Base * a = new Base;
Derived * b = static_cast<Derived*>(a);

这是一个合法的代码,但是b指向的对象不是一个完整的对象,所以运行时解引用会抛出一个错误。

static_cast同样也能用于隐式转换(不仅仅是指向对象的指针)。

    1.把void*转换成任何类型的指针。这样的转换保证x->void*->x,即保证把void*转换成以前的类型。
    2.转换数值,浮点数,枚举类型到枚举类型。

另外,static_cast也能在以下场景中使用。

    1.显式调用只有一个参数的构造函数或者赋值操作。
    2.转换成右值引用。
    3.把enum转换成int或者float。
    4.把任何类型转换成void,evaluating and discarding the value。

reinterpret_cast

reinterpret_cast转换任意指针类型到其他指针类型,即使是不相关的类型也可以转换。它转换的结果仅仅只是复制二进制数据到目标类型,任何类型的转换都可以使用reinterpret_cast。它既不检查内容,也不检查类型。它可以把指针转换成一个数字或者把一个数字转换成指针。当把一个数字转换成指针时的结果由平台来决定。

它只保证把指针转换成数值时,数值的宽度可以完全包含指针的内容。同样,再次转换回指针时也是完全合法的指针。

通过interpret_cast,而不是static_cast来转换。这是一种根据类型来重新解释二进制数据的低级别操作。大多数场景下结果根据平台而定,因此也就是失去了可移植性。

class A { /* ... */ };
class B { /* ... */ };
A * a = new A;
B * b = reinterpret_cast<B*>(a);

代码能编译,但是没有什么意义,因为a被转换成了一个完全不相关类型,当b解引用的时候是不安全的。

const_cast

这个转换类型操作指针的常量属性,可能是添加常量属性,也可能是移除常量属性。比如下面这个为了传递一个常量指针到一个非常量指针。

// const_cast
#include <iostream>
using namespace std;

void print (char * str)
{
  cout << str << '\n';
}

int main () {
  const char * c = "sample text";
  print ( const_cast<char *> (c) );
  return 0;
}

sample text

以上样例保证安全,因为函数没有写指针指向的内容。但是请注意。在移除指针的常量属性后进行写入操作,这个结果是未知的。

typeid

typeid (expression)

typeid检查表达式的类型,返回头文件中定义的常量对象的引用。所有typeid的返回值可以通过==或者!=来比较,或者可以通过name()方法来返回类型名字。

// typeid
#include <iostream>
#include <typeinfo>
using namespace std;

int main () {
  int * a,b;
  a=0; b=0;
  if (typeid(a) != typeid(b))
  {
    cout << "a and b are of different types:\n";
    cout << "a is: " << typeid(a).name() << '\n';
    cout << "b is: " << typeid(b).name() << '\n';
  }
  return 0;
}

a and b are of different types:
a is: int *
b is: int

typeid在类上使用时,它使用运行时类型信息来跟踪动态对象(RTTI)。如果在多态类上使用时,它返回子类的类型。

// typeid, polymorphic class
#include <iostream>
#include <typeinfo>
#include <exception>
using namespace std;

class Base { virtual void f(){} };
class Derived : public Base {};

int main () {
  try {
    Base* a = new Base;
    Base* b = new Derived;
    cout << "a is: " << typeid(a).name() << '\n';
    cout << "b is: " << typeid(b).name() << '\n';
    cout << "*a is: " << typeid(*a).name() << '\n';
    cout << "*b is: " << typeid(*b).name() << '\n';
  } catch (exception& e) { cout << "Exception: " << e.what() << '\n'; }
  return 0;
}

a is: class Base *
b is: class Base *
*a is: class Base
*b is: class Derived

注意,typeid返回类型的name()方法的结果,根据使用的编译器和库的不同而不同,它有可能不是一个简单的字符串。
注意typeid是怎么考虑指针类型(a和b指针的是Base* 类型),当typeid在对象上使用时(*a和*b),typeid返回的是动态类型(*a是Base类型,*b是Derived类型)。
如果传入的指针等于null,会抛出bad_typeid异常.

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
小万哥 小万哥
1年前
C 语言:类型转换与常量的细致理解
C语言中的类型转换有时,您必须将一种数据类型的值转换为另一种类型。这称为类型转换隐式转换当您将一种类型的值分配给另一种类型的变量时,编译器会自动进行隐式转换。例如,如果您将一个int值分配给一个float类型:c//自动转换:inttofloatfloat
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
小万哥 小万哥
6个月前
Kotlin 数据类型详解:数字、字符、布尔值与类型转换指南
Kotlin中变量类型由值决定,如Int、Double、Char、Boolean、String。通常可省略类型声明,但有时需指定。数字类型分整数(Byte,Short,Int,Long)和浮点(Float,Double),默认整数为Int,浮点为Double。布尔值是true或false,Char用单引号,字符串用双引号。数组和类型转换将在后续讨论,转换需用特定函数。