C/C++:类型转换

C++:dynamic_cast、static_cast

实验环境:

[test1280@localhost ~]$ uname -a
Linux localhost.localdomain 2.6.18-371.el5 #1 SMP Thu Sep 5 21:21:44 EDT 2013 x86_64 x86_64 x86_64 GNU/Linux
……
gcc 版本 4.1.2 20080704 (Red Hat 4.1.2-54)

C/C++中有两种类型转换:

一种是隐式的类型转换,例如下面三种情况:

1.赋值表达式,右值转换为待赋值变量的类型;
2.调用函数时,虚实结合,实参转变为形参的类型;
3.函数返回时,返回的值(或表达式的值)转换为函数声明的返回类型。

一种是显式的类型转换,分为C风格以及C++风格:

1.C风格:

(target_type)varable

#include <stdio.h>
#include <stdlib.h>

int main()
{
    double d = 1.23;
    int i = (int)d;
    return 0;
}

2.C++风格:

C++风格共有5种(1+4):

target_type(varable)

#include <iostream>

using namespace std;

int main()
{
    double d = 1.23;
    int i = int(d);
    return 0;
}

其余四种为:dynamic_cast、static_cast、const_cast以及reinterpret_cast。

今天主要了解dynamic_cast以及static_cast。

upcast:向上转型,上行转型,指的是由一个子类对象转为父类对象的过程;
downcast:向下转型,下行转型,指的是由一个父类对象转为子类对象的过程。

一个子类对象转为其父类对象当然是安全的,因为子类对象拥有父类对象(继承);反之则不尽然。

苹果是水果的子类:

一个苹果当然是一个水果,水果的属性苹果都有,但是一个水果却未必是一个苹果,也可能是一个香蕉,一个梨,等等。

static_cast

编译器在编译时进行类型检查,如果转换失败则编译错误。

成功:

#include <iostream>

using namespace std;

int main()
{
    double d = 1.23;
    int i = static_cast<int>(d);
    return 0;
}

失败:

#include <iostream>

using namespace std;

class Fruit{};

int main()
{
    Fruit fruit;
    int i = static_cast<int>(fruit);
    return 0;
}
g++ -o main main.C
main.C: In function ‘int main()’:
main.C:10: 错误:从类型 ‘Fruit’ 到类型 ‘int’ 中的 static_cast 无效

通常用于转换数值类型,进行非多态的类型转换。

static_cast< type-id >(expression)

将expression转换成类型type-id,type-id和expression必须是指针、引用、算术类型或枚举类型。

表达式static_cast< type-id >(expression),expression的值转换为模板中指定的类型type-id。在运行时转换过程中,不进行类型检查来确保转换的安全性。

static_cast它能在内置的数据类型间互相转换,对于类只能在有联系的指针类型间进行转换。可以在继承体系中把指针转换来转换去,但是不能转换成继承体系外的一种类型。

#include <iostream>

using namespace std;

class Person
{
public:
    string name;
    int age;
    int male;
};

class Student:public Person
{
public:
    int classId;
    string major;
public:
    void display();
};

void Student::display()
{
    cout<<"name is :"<<name<<endl;
    cout<<"age is :"<<age<<endl;
    cout<<"male is :"<<male<<endl;
    cout<<"classId is :"<<classId<<endl;
    cout<<"major is :"<<major<<endl;
}

int main()
{
    Student *s = new Student;
    //向上转型成功,因为s指向的对象是Student类型,而其是Person的子类,在同一继承树中。
    Person *p = static_cast<Person *>(s);

    p = new Person;
    //向下转型成功,因为Person与Student在同一继承树中,但是是不安全的,因为p指向的是一个父类对象
    //将父类对象直接转为子类对象,调用子类特有的方法很可能出问题。
    s = static_cast<Student *>(p);
    s->display();

    return 0;
}
[test1280@localhost ~]$ !g
g++ -o main main.C
[test1280@localhost ~]$ ./main
name is :
age is :0
male is :0
classId is :0
段错误

上面的例子将一个父类对象转换为子类对象,因为他们在同一继承树中,所以转换没问题,编译时可以通过。

但是在运行时,发现转换后并没有major这样的子类特有的成员,调用子类方法(display)访问这个成员时就导致了段错误。

切记,static_cast是仅仅在编译期进行检查(转型失败时报编译错误),若向下转型可以直接编译过去,但可能在运行时错误。

dynamic_cast

dynamic_cast< type-id >(expression)

在运行期,会检查这个转换是否可能。

type-id与expression必须是一个指针或引用:

若expression是指针,那么type-id也应该是一个指针类型;
若expression是引用,那么type-id也应该是一个引用类型。

dynamic_cast仅能应用于指针或者引用,不支持内置数据类型。

当转换失败时,若type-id是一个指针,那么失败返回一个NULL,若type-id是一个引用,那么失败抛出异常。

它不仅仅像static_cast那样,检查转换前后的两个指针是否属于同一个继承树,它还要检查被指针引用的对象的实际类型,确定转换是否可行。

#include <iostream>

using namespace std;

class Person
{
public:
    Person(){};
    virtual ~Person(){};
    string name;
    int age;
    int male;
};

class Student:public Person
{
public:
    int classId;
    string major;
public:
    void display();
};

void Student::display()
{
    cout<<"name is :"<<name<<endl;
    cout<<"age is :"<<age<<endl;
    cout<<"male is :"<<male<<endl;
    cout<<"classId is :"<<classId<<endl;
    cout<<"major is :"<<major<<endl;
}

int main()
{
    Student *s = new Student;
    //向上转型成功,因为s指向的对象是Student类型,而其是Person的子类,在同一继承树中。
    //在向上转型时,dynamic_cast与static_cast一致。
    Person *p = dynamic_cast<Person *>(s);

    p = new Person;
    // 向下转型失败,这里将会返回一个空指针。
    // p实际指向的值一个父类对象,并不是Student对象,所以向下转型失败。
    s = dynamic_cast<Student *>(p);
    if (s == NULL)
    {
        cout<<"s is NULL"<<endl;
    }

    return 0;
}
[test1280@localhost ~]$ !g
g++ -o main main.C
[test1280@localhost ~]$ ./main
s is NULL

注意这里不仅仅修改了下面,还给上面的基类Person增加了一个virtual方法。

若基类没有virtual方法且使用dynamic_cast进行向下转型,则编译失败:

#include <iostream>

using namespace std;

class A
{
};

class B:public A
{
};

int main()
{
    A *pa = new A;
    B *pb = dynamic_cast<B *>(pa);
    return 0;
}
[test1280@localhost ~]$ !g
g++ -o main main.C
main.C: In functionint main()’:
main.C:16: 错误:无法将 ‘pa’ 从类型 ‘A*’ 动态转换到类型 ‘class B*’ (source type is not polymorphic)

这个时候我们给A增加一个virtual方法:

#include <iostream>

using namespace std;

class A
{
public:
    virtual void foo(){};
};

class B:public A
{
};

int main()
{
    A *pa = new A;
    B *pb = dynamic_cast<B *>(pa);
    return 0;
}

就可以编译过去啦。

dynamic_cast在帮助你浏览继承层次上是有限制的。它不能被用于缺乏虚函数的类型上。也即如果想将基类指针转换为派生类指针,如果基类不是虚类则无法实现。

这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中,只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的。

dynamic_cast被用于安全地沿着类的继承关系向下进行类型转换。这就是说,你能用dynamic_cast把指向基类的指针或引用转换成指向其派生类或其兄弟类的指针或引用,而且你能知道转换是否成功。

若是向上转型则不要求基类包含virtual:

#include <iostream>

using namespace std;

class A
{
};

class B:public A
{
};

int main()
{
    B *pb = new B;
    A *pa = dynamic_cast<A *>(pb);
    return 0;
}

直接编译就可以过,向上转型不需要基类包含virtual函数。

再看一个dynamic_cast对引用失败的例子:

#include <iostream>

using namespace std;

class A
{
public:
    virtual void foo(){};
};

class B:public A
{
};

int main()
{
    A a;
    A &ra = a;
    B &rb = dynamic_cast<B &>(ra);
    return 0;
}
[test1280@localhost ~]$ !g
g++ -o main main.C
[test1280@localhost ~]$ ./main
terminate called after throwing an instance of 'std::bad_cast'
  what():  St8bad_cast
已放弃

bad_cast!!!

关于dynamic_cast还有一点需要注意,是在多重继承时upcast的问题。

有类关系图如下(好吧,其实我画的不标准):

这里写图片描述

#include <iostream>

using namespace std;

class A
{
public:
    virtual void foo(){};
};

class B:public A
{
};

class C:public A
{
};

class D:public B, public C
{
};

int main()
{
    D *pd = new D;
    A *pa = dynamic_cast<A *>(pd);
    if (pa == NULL)
        cout<<"pa is NULL"<<endl;
    return 0;
}
[test1280@localhost ~]$ !g
g++ -o main main.C
main.C: In function ‘int main()’:
main.C:26: 错误:‘A’ 是 ‘D’ 的有歧义的基类

额,B和C都是A的子类,而D是继承自B/C,你说向上转型时它走D->B->A还是走D->C->A?

遇到这种情况要依次沿着类结构向上衍生:

#include <iostream>

using namespace std;

class A
{
public:
    virtual void foo(){};
};

class B:public A
{
};

class C:public A
{
};

class D:public B, public C
{
};

int main()
{
    D *pd = new D;
    B *pb = dynamic_cast<B *>(pd);
    A *pa = dynamic_cast<A *>(pb);
    if (pa == NULL)
        cout<<"pa is NULL"<<endl;
    return 0;
}

网上的Blog说遇到这样的upcast问题会导致转A为NULL,但我这里是直接编译器报错了,可能是编译器不同的行为吧~

参考自:

0.http://www.jellythink.com/archives/205
1.http://www.jianshu.com/p/a666132944ef
2.http://riddickbryant.iteye.com/blog/547361
3.http://www.cnblogs.com/5iedu/articles/5585895.html
4.http://blog.csdn.net/yfkiss/article/details/7277433
5.https://stackoverflow.com/questions/15114093/getting-source-type-is-not-polymorphic-when-trying-to-use-dynamic-cast

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值