zoukankan      html  css  js  c++  java
  • C/C++数据类型的转换之终极无惑

    程序开发环境:VS2012+Win32+Debug


    数据类型在编程中常常遇到。尽管可能存在风险,但我们却乐此不疲的进行数据类型的转换。

    1. 隐式数据类型转换

    数据类型转换。究竟做了些什么事情呢?实际上。数据类型转换的工作相当于一条函数调用。若有一个函数撰文负责从double转换到int(假设函数是dtoi),则以下的转换语句:

    double d=4.48;
    int i=d;   //报告警告

    等价于i=dtoi(d)。

    函数dtoi的原型应该是:int dtoi(double)或者是int dtoi(const double&)。有些类型的数据转换时绝对安全的,所以能够自己主动进行。编译器不会给出不论什么警告,如由int型转换成double型。还有一些转换会丢失数据,编译器仅仅会给出警告。并不算一个语法错误,如上面的样例。各种基本数据类型(不包含void)之间的转换都属于以上两种情况。

    以上两种不显示指明数据类型的转换就是隐式数据类型转换。

    隐式数据类型转换无处不在,主要出如今以下几种情况。


    (1)算术运算式中,低类型能够转换为高类型。

    (2)赋值表达式中。右边表达式的值自己主动隐式转换为左边变量的类型,并赋值给他。

    (3)函数调用中參数传递时。系统隐式地将实參转换为形參的类型后,赋给形參。

    (4)函数有返回值时,系统将隐式地将返回表达式类型转换为返回值类型。赋值给调用函数。

    编程原则:请尽量不要使用隐式类型转换,即使是隐式的数据类型转换是安全的,由于隐式类型数据转换减少了程序的可读性。

    2. 显示数据类型转换

    显示数据类型转换是显示指明须要转换的类型。首先考察例如以下程序。

    #include <iostream>
    using namespace std;
    
    int main(int argc,char* argv[])
    {
        short arr[]={65,66,67,0};
        wchar_t *s;
        s=arr;
        wcout<<s<<endl;
        getchar();
    }

    由于short int和wchar_t是不同的数据类型。直接把arr代表的地址赋给s会导致一个编译错误:error C2440:“=”:无法从“short[4]”转换为“wchar_t”。

    为了解决这样的“跨度较大”的数据类型转换。能够说还用显示的“强制类型转换”机制,把语句s=arr;改为s=(wchar_t*)arr;就能顺利通过编译。并输出:ABC。

    强制类型转换在C语言中就已经存在。到了C++语言中能够继续使用。在C风格的强制类型转换中,目标数据类型被放在一堆圆括号里,然后置于源数据类型的表达式前。在C++语言中,同意将目标数据类型当做一个函数来使用,将源数据类型表达式置于一对圆括号里,这就是所谓的“函数风格”的强制类型转换。

    以上两种强制转换没有本质差别,仅仅是书写形式上略有不同。即:

    (T)expression  //C-style cast
    T(expression)  //function-style cast

    可将它们称为旧风格的强制类型转换。在上面的程序中,能够用以下两种书写形式实现强制类型转换:

    s=(wchar_t*)arr;
    typedef wchar_t* LSPTR; s= LSPTR(arr);

    3.C++中新式类型转换

    在C++语言中。添加了四中内置的类型转换操作符:const_cast、static_cast、dynamic_cast和reinterpret_cast。

    它们具有统一的语言形式:type_cast_operator(expresiion)。

    以下分别介绍。

    3.1 const_cast

    const_cast主要用于解除常指针和常量的const和volatile属性。也就是说,把cosnt type*转换成type*类型或将const type&转换成type&类型,可是要注意,一个变量本身被定义为仅仅读变量。那么它永远是常变量。

    const_cast取消的是对间接引用时的改写限制(即仅仅针对指针或者引用),而不能改变变量本身的const属性。如以下的语句就是错误的。

    const int i;
    int j=const_cast<int>(i);
    正确示比例如以下:
        void constTest{
            const int a=5;
            int *p=NULL;
            p=const_cast<int*>(&a);
            (*p)++;
            cout<<a<<endl;//输出6
        }

    程序正常执行。输出6。

    3.2 static_cast

    static_cast相当于传统的C语言中那些“较为合理”的强制类型转换,较多的使用于基本数据类型之间的转换、基类对象指针(或引用)和派生类对象指针(或引用)之间的转换、一般的指针和void*类型的指针之间的转换等。

    static_cast操作对于类型转换的合理性会作出检查,对于一些过于“无理”的转换会加以拒绝。比如以下的转换:

    double d=3.14double* p=static_cast<double*>(d);

    这是一种非常诡异的转换,在编译时会遭到拒绝。
    另外,对于一些看似合理的转换。也可能被static_cast拒绝。这时要考虑别的方法。如以下的程序。

    #include <iostream>
    using namespace std;
    
    class A{
        char ch;
        int n;
    public:
        A(char c,int i):ch(c),n(i){}
    };
    
    int main(int argc,char* argv[])
    {
        A a('s',2);
        char* p=static_cast<char*>(&a);
        cout<<*p;
        getchar();
    }

    这个程序无法通过编译,就是说,直接将A*类型转换为char*是不同意的。这时能够通过void*类型作为中介实现转换。

    改动后的程序例如以下。

    void* q=&a;
    char* p=static_cast<char*>(&q);

    这样,程序就能够通过编译。输出s。可见,假设指针类型之间进行转换。一定要注意转换的合理性。这一点必须由程序猿自己负责。指针类型的转换以为这对原数据实体的内容的又一次解释。

    尽管const_cast是用来去除变量的const限定,可是static_cast却不是用来去除变量的static引用。事实上这是非常easy理解的。static决定的是一个变量的作用域和生命周期。比方:在一个文件里将变量定义为static,则说明这个变量仅仅能在本Package中使用;在方法中定义一个static变量。该变量在程序開始存在直到程序结束;类中定义一个static成员,该成员随类的第一个对象出现时出现,并且能够被该类的全部对象所使用。

    对static限定的改变必定会造成范围性的影响。而const限定的仅仅是变量或对象自身。

    但不管是哪一个限定。它们都是在变量一出生(完毕编译的时候)就决定了变量的特性。所以实际上都是不容许改变的。这点在const_cast那部分就已经有体现出来。

    在实践中。static_cast多用于类类型之间的转换。

    这时,被转换的两种类型之间一定存在派生与继承的关系。见例如以下程序。

    #include <iostream>
    using namespace std;
    
    class A{};
    class B{};
    int main(int argc,char* argv[])
    {
        A* pa;
        B* pb;
        A a;
        pa=&a;
        pb=static_cast<B*>(pa);
        getchar();
    }

    改程序无法通过编译。原因是类A与类B没有不论什么关系。

    综上所述,使用static_cast进行类型转换时要注意例如以下几点。

    (1)static_cast操作符的语法形式是static_cast(expression),当中。expression外面的圆括号不能省略,哪怕expression是一个简单的变量。

    (2)通过static_cast仅仅能进行一些相关类型之间的“合理”转换。假设是类类型之间的转换,源类型和目标类型之间必须存在继承关系,否则会得到编译错误。

    (3)static_cast所进行的是一种静态转换。是在编译时决定的。通过编译后,空间和时间效率实际上等价于C方式的强制类型转换。

    (4)在C++中,仅仅想派生类对象的指针能够隐式转换为指向基类对象的指针。而要把指向积累对象的指针转换为指向派生类对象的指针,就须要借助static_cast操作符来完毕,其转换的风险是须要程序猿自己来承担。当然使用dynamic_cast更为安全。

    (5)static_cast不能转换掉expression的const、volitale、或者__unaligned属性。

    3.3 dynamic_cast

    dynamic_cast是一个全然的动态操作符,仅仅能用于指针或者引用间的转换。并且dynamic_cast运算符所操作的指针或引用的对象必须拥有虚函数成员。否则出现编译错误。

    原因是dynamic_cast牵扯到的面向对象的多态性,其作用就是在程序执行的过程中动态的检查指着或者引用指向的实际对象是什么以确定转换是否安全,而C++的类的多态性则依赖于类的虚函数。

    详细的说。dynamic_cast能够进行例如以下的类型转换。

    (1)在指向基类的指针(引用)与指向派生类的指针(引用)之间进行的转换。基类指针(引用)转换为派生类指针(引用)为向下转换。被编译器视为安全的类型转换。也能够使用static_cast进行转换。派生类指针(引用)转换为基类指针(引用)时, 为向上转换,被编译器视为不安全的类型转换,须要dynamic_cast进行动态的类型检測。

    当然。static_cast也能够完毕转换,仅仅是存在不安全性。

    (2)在多重继承的情况下。派生类的多个基类之间进行转换(称为交叉转换:crosscast)。

    如父类A1指针实际上指向的是子类。则能够将A1转换为子类的还有一个父类A2指针。

    3.3.1 dynamic_cast的向下转换

    dynamic_cast在向下转换时(downcast),即将父类指针或者引用转换为子类指针或者引用时,会严格检查指针所指的对象的实际类型。

    參见例如以下程序。

    #include <iostream>
    using namespace std;
    class A{
    public:
        int i;
        virtual void show(){
        cout<<"class A"<<endl;
        }
        A(){int i=1;}
    };
    class B:public A{
    public:
        int j;
        void show(){
        cout<<"class B"<<endl;
        }
        B(){j=2;}
    };
    class C:public B{
    public:
        int k;
        void show(){
            cout<<"class C"<<endl;
        }
        C(){k=3;}
    };
    int main(int argc,char* argv[])
    {
        A* pa=NULL;
        B b,*pb;
        C *pc;
        pa=&b;
        pb=dynamic_cast<B*>(pa);
        if(pb){
            pb->show();
            cout<<pb->j<<endl;
        }
        else
            cout<<"Convertion failed"<<endl;
    
        pc=dynamic_cast<C*>(pa);
        if(pc){
            pc->show();
            cout<<pc->k<<endl;
        }
        else
            cout<<"Convertion failed"<<endl;
        getchar();
    }

    程序输出结果是:
    class B
    2
    Convertion failed

    由于指针pa所指的对象的实际类型是class B,所以将pa转换为B*类型没有问题,而将pa转换成C*类型时则失败。当指针转换失败时,返回NULL。

    3.3.2 dynamic_cast的交叉转换

    交叉转换(crosscast)是在两个“平行”的类对象之间进行。本来它们之间没有什么关系。将当中的一种转换为还有一种是不可行的。可是,假设类A和类B都是某个派生类C的基类,而指针所指的对象本身就是一个类C的对象,那么该对象既能够被视为类A的对象,也能够被视为类B的对象,类型A*(A&)和B*(B&)之间的转换就成为可能。见以下的样例。

    #include <iostream>
    using namespace std;
    
    class A{
    public:
        int num;    
        A(){num=4;}
        virtual void funcA(){}
    
    };
    
    class B{
    public:
        int num;
        B(){num=5;}
        virtual void funcB(){}
    };
    
    class C:public A,public B{};
    
    int main(int argc,char* argv[])
    {
        C c;
        A* pa;
        B* pb;
        pa=&c;
        cout<<pa->num<<endl;
        pb=dynamic_cast<B*>(pa);
        cout<<"pa="<<pa<<endl;
        if(pb)
        {
            cout<<"pb="<<pb<<endl;
            cout<<"Conversion succeeded"<<endl;
            cout<<pb->num<<endl;
        }
        else
            cout<<"Conversion failed"<<endl;
        getchar();
    }

    程序输出结果是:
    这里写图片描写叙述
    能够看出,pa转换成pb之后,其值产生了变化。也就是说,在类C的对象中,类A的成员和类B的成员所占的位置(距离对象首地址的偏移量)是不同的。类B的成员要靠后一些。所以将A*转换为B*的时候。要对指针的位置进行调整。假设将在程序中的dynamic_cast替换成static_cast,则程序无法通过编译。由于这是编译器觉得类A和类B是两个“无关”的类。

    3.4 reinterpret_cast

    reinterpret_cast是一种最为“狂野”的转换。它在C++四中新的转换操作符中的能力是最强的,其转换能力与C的强制类型转换不分上下。正是由于其过于强大的转换能力,reinterpret_cast是C++语言中最不提倡使用的一种数据类型转换操作符,应该尽量避免使用。

    主要用于转换一个指针为其它类型的指针,也同意将一个指针转换为整数类型,反之亦然。这个操作符能够在非相关的类型之间进行。只是其存在必有其价值。在一些特殊的场合,在确保安全性的情况下,能够适当使用。它一般用于函数指针的转换。见例如以下程序。

    #include <iostream>
    using namespace std;
    
    typedef void (*pfunc)();
    void func1()
    {
        cout<<"this is func1(),return void"<<endl;
    }
    
    int func2()
    {
        cout<<"this is func2(),return int"<<endl;
        return 1;
    }
    
    int main(int argc,char* argv[])
    {
        pfunc FuncArray[2];
        FuncArray[0]=func1;
        FuncArray[1]=reinterpret_cast<pfunc>(func2);
        for(int i=0;i<2;++i)
            (*FuncArray[i])();
        getchar();
    }

    程序输出结果:
    this is func1(),return void
    this is func2(),return int

    由函数指针类型int(*)()转换为void(*)(),仅仅能通过reinterpret_cast进行,用其它的类型转换方式都会遭到编译器的拒绝。

    并且从程序的意图来看,这里的转换是“合理”的。

    只是,C++是一种强制类型安全的语言,即使是用interpret_cast,也不能随意地将某种类型转换为还有一种类型。C++编译器会设法保证“最低限度”的合理性。

    语言内置的类型转换操作符无法胜任的工作须要程序猿手动重载相关转换操作符来完毕类型转换。

    4. 手动重载相关类型转换操作符

    在各种各样的类型转换中。用户自己定义的类类型与其它数据类型间的转换要引起注意。

    这里要重点考察例如以下两种情况。

    4.1不同类对象的相互转换

    由一种类对象转换成还有一种类对象。

    这样的转换无法自己主动进行,必须定义相关的转换函数。事实上这样的转换函数就是类的构造函数,或者将类类型作为类型转换操作符函数进行重载。此外。还能够利用构造函数完毕类对象的相互转换。见例如以下程序。

    #include <iostream>
    using namespace std;
    class Student{
        char name[20];
        int age;
    public:
        Student(){};
        Student(char *s, int a){
            strcpy(name,s);
            age=a;
        }
        friend class Team;
    };
    
    class Team{
        int members;
        Student monitor;
    public:
        Team(){};
        Team(const Student& s):monitor(s),members(0){};
        void Display()const{
            cout<<"members' number :"<<members<<endl;
            cout<<"monitor's name :"<<monitor.name<<endl;
            cout<<"monitor's age :"<<monitor.age<<endl;
        }
    };
    ostream& operator<<(ostream& out,const Team &t){
        t.Display();
        return out;
    }
    
    int main(int argc,char* argv[])
    {
        Student s("吕吕",23);
        cout<<s;
        getchar();
    }

    程序输出结果:
    members’ number :0
    monitor’s name :吕吕
    monitor’s age :23

    本来,输出操作符operator<<并不接受Student类对象作为參数。但由于可通过类Team的构造函数将Student类对象转换成Team类对象,所以输出操作能够成功进行。类的单參数构造函数实际上充当了类型转换函数。

    4.2基本类型与类对象的相互转换

    4.2.1基本类型转换为类对象

    这样的转换仍能够借助于类的构造函数进行的。也就是说。在类的若干重载的构造函数中,有一些接受一个基本数据类型作为參数,这样就能够实现从基本数据类型到类对象的转换。

    4.2.2类对象转换为基本数据类型

    由于无法为基本数据类型定义构造函数,所以由对象想基本数据类型的转换必须借助于显示的转换函数。这些转换函数名由operator后跟基本数据类型名构成。以下是一个详细的样例。

    #include <iostream>
    using namespace std;
    class A{
    public:
        operator int(){
            return 1;
        }
        operator double(){
            return 0.5;
        }
    };
    int main(int argc,char* argv[])
    {
        A obj;
        cout<<"Treating obj as an interger, its value is: "<<(int)obj<<endl;
        cout<<"Treating obj as a double, its value is: "<<(double)obj<<endl;
        getchar();
    }

    程序输出结果:
    Treating obj as an interger, its value is: 1
    Treating obj as a double, its value is: 0.5

    在一个类中定义基本类型转换的函数,须要注意一下几点:
    (1)类型转换函数仅仅能定义为一个类的成员函数。而不能定义为外部函数。类型转换函数与普通成员函数一样,也能够在类体中声明。在类外定义。

    (2)类型转换函数一般是提供给类的客户使用的,所以应将訪问权限设置为public。否则无法被显示的调用。隐式的类型转换也无法完毕。

    (3)类型转换函数既没有參数,也不显示的给出返回类型。

    (4)转换函数必须有“return目的类型数据;”的语句,即必须返回目的类型数据最为函数的返回值。

    (5)一个类能够定义多个类型转换函数。

    C++编译器将依据目标数据类型选择合适的类型转换函数。在可能出现二义性的情况下,应显示地使用类型转换函数进行类型转换。

    5.总结

    (1)综上所述。数据类型转换相当于一次函数调用。调用的的结果是生成了一个新的数据实体。或者生成一个指向原数据实体但解释方式发生变化的指针(或引用)。

    (2)编译器不给出不论什么警告也不报错的隐式转换总是安全的,否则必须使用显示的转换。必要时还要编写类型转换函数。

    (3)使用显示的类型转换,程序猿必须对转换的安全性负责,这一点能够通过两种途径实现:一是利用C++语言提供的数据类型动态检查机制;而是利用程序的内在逻辑保证类型转换的安全性。

    (4)dynamic_cast转换符仅仅能用于含有虚函数的类。

    dynamic_cast用于类层次间的向上转换和向下转换,还能够用于类间的交叉转换。在类层次间进行向上转换。即子类转换为父类,此时完毕的功能和static_cast是同样的,由于编译器默认向上转换总是安全的。向下转换时,dynamic_cast具有类型检查的功能,更加安全。类间的交叉转换指的是子类的多个父类之间指针或引用的转换。

    dynamic_cast能够实现执行时动态类型检查,依赖于对象的RTTI(Run-Time Type Information),通过虚函数表找到RTTI确定基类指针所指对象的真实类型,来确定是否能转换。

    (5)interpre_cast相似于C的强制类型转换,多用于指针(和引用)类型间的转换。权利最大。也最危急。

    static_cast全力较小,但大于dynamic_cast,用于普通的转换。进行类层次间的下行转换假设没有动态类型检查。是不安全的。

    (6)const-cast仅仅用于去除指针或者引用类型的const和volatile属性。变量本身的属性不能被去除。

    在进行类型转换时,请坚持例如以下原则:
    (1)子类指针(或引用)转换为父类指针(或引用)编译器觉得总是是安全的,即向上转换。请使用static_cast,而非dynamic_cast,原因是static_cast效率高于dynamic_cast。

    (2)父类指针(或引用)转换为子类指针(或引用)时存在风险,即向下转换,必须使用dynamic_cast进行动态类型检測。

    (3)总领性原则:不要使用C风格的强制类型转换,而是使用标准C++中的四个类型转换符:static_cast、dynamic_cast、reinterpret_cast、和const_cast。


    參考文献

    [1]陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008.

  • 相关阅读:
    NSURLConnection和Runloop(面试)
    文件的上传
    CentOS 7防火墙快速开放端口配置方法
    国内常用源镜像地址:
    yum安装zabbix-web-mysql出现[Errno 256] No more mirrors to try.
    1251-Client does not support authentication protocol requested by server; consider upgrading MySQL client。
    http代理和SOCKS代理的区别
    windows下redis安装
    centeros7安装mysql
    nginx配置负载均衡分发服务器笔记
  • 原文地址:https://www.cnblogs.com/jzssuanfa/p/7099029.html
Copyright © 2011-2022 走看看