• C++类型转换方式总结


    索引目录

    再谈为何会有那四个转换运算符

    看起来,我应该把导师讲过、遗漏的有关C++类型转换方面的内容都总结成文了,主要内容都在以上几篇文章中阐述完毕。

    上边的每一篇文章,虽然都单独着重强调一种转换方式或运算符,但是也有提到跟其他运算符之间的差异性,以及使用建议,因此基本可以看出各个运算符的使用方式和作用。

    在文章也看到const_cast, reinterpret_cast, static_cast都可以用传统的转换方式基于指针进行替代。如果结合typeid运算符,那么dynamic_cast其实也可以用传统转换方式实现。

    因此不免会有疑惑:这四个转换运算符不是很多余。

    的确,我刚接触这些转换运算符的时候,我也又这样的疑虑。因为在跟着导师学习C++,做一些课程项目的过程中,我都不曾使用过这些转换运算符。一来我需要用到类型转换的地方很少,二来也还不熟悉这些运算符,所以就尽量避免使用。

    不过在花了这么多时间和精力研究和总结这些转换运算符之后,我以后一定会更多的去使用他们,因为传统的转换方式能够实现标准转换运算符的功能,主要还是基于"C++中的指针可以无条件互相转换"。因此,对于转换符的实现,其格式基本都是一致的:用强制转换的方式,直接转换指针类型

    正因如此,看到这些转换代码,有时候并不能马上理解其目的用意。而如果用转换运算符来操作,就可以一目了然地通过转换符的名称知道是在去除const,还是想进行指针的重新定义。

    另一个角度来说,编译器也对转换运算符做来限制、优化和异常处理,使用他们可以更好地减少错误的产生,以及避免传统转换没有达到预期的目的。

    所以,如果碰到需要类型转换的地方,就尽量思考,是否可以用转换运算符来替代,用哪个是最合适的。下边就来讲讲什么时候用什么样的转换符最合适。

    转换运算符的应用之所

    结合网络上各个站点看到的关于C++转换符的知识,以及前面那些文章得到的反馈,可以将各个转换运算符的使用总结如下:

    对于传统的转换方式(C式或函数式),只在数值类型(包括整型、浮点型、字符类型和枚举)上使用。这也是延续C的形式,当然这类转换也是可以用static_cast来替换,但是因为是基本类型,所以传统转换已经很直观。

    对于const_cast转换运算符,用在需要去除掉const限定的时候。其实这种情况出现的很少,可能的方法在const_cast一文中已经又举例,不过还是反复强调, 使用const_cast转换后,绝对不可试图修改结果的值。

    对于reinterpret_cast转换运算符,一般用在将对象指针类型转换到整数类型或者void * (空指针)。如同在文中举出的隐患,因此注意的是,若要使用其结果,一定要将类型转换回去后使用。也不要将随意的整数转换成指针类型。

    对于static_cast转换运算符,将其用在对象的转换之上(虽然static_cast也可以用在有继承关系的类型指针之间,但是还是将这方面的转换交给dynamic_cast来操作吧), static_cast会调用相应的构造函数或者重载的转换运算符。

    通过文章的留言反馈,以及Google C++ Style Guide的推荐格式,知道对于单参构造函数的存在可能会引发一些隐式的转换,因此用static_cast也可以明确的指出类型的转换过程,避免生成多余的临时对象造成效率下降。

    对于dynamic_cast转换运算符,将其用在具有继承关系的指针类型之间的转换。无论是从基类到子类的转换,还是子类到基类的转换,都将dynamic_cast套上去,也算是标识它们是一家子。

    如果任何一种基于指针或引用的转换,套上四个转换运算符之后都失败,那么所要进行的转换可能就触到了"雷区"了:进行了没意义的转换。比如,对于没有关系的两个类型的指针进行了转换,比如试图转换指向方法的指针了。所以转换运算符对于避免代码出错也很有帮助。

    基于引用(Reference)的转换运算符使用

    前面的文章中,所以对于转换运算符的讲述和举例,都是基于指针的。但实际上,这些转换运算符也可以基于引用来展开。准确说实际上引用类型应该是作为转换的目标类型,源类型则是对象变量(当然也可能用的是引用变量,或是取出指针所指的内容,它们用到的都是实际的类对象)。

    由于引用类型“定义时必须初始化”的特别,使得它不同于指针类型随时随地都调用转换运算符,基于引用的转换只在对引用进行初始化的时候才会出现。

    下边是const_cast和reinterpret_cast基于引用的运用:

    
        const int int_constant = 21;
        int& int_ref = const_cast<int&>(int_constant);
        cout << int_ref << endl;
        
        int int_value = 7;
        //long& long_ref = int_value; //Error, can not using reference cross types
        float& long_ref = reinterpret_cast<float&> (int_value);
        cout << long_ref << endl;  
        

    对于dynamic_cast的应用基本也是一致的,只是还是限制在具有继承关系的类型之间。不同于基于指针在转换时返回null,dynami_cast在基于引用转换失败时,会抛出std::bad_cast异常,因为不能将空值赋给引用类型。如果要抓住这个异常,则需要引入如下头文件:
    #include <typeinfo>

    而static_cast转换符前面已经说过推荐直接用在对象之上,不用在指针上,所以也不太会有需要用在引用类型上的情况出现。

    山寨C#的TryParse

    C#中有很多简洁实用的转换方法,比如从字符串到数值类型的ParseTryParse,还有包含了各种从object对象到数值类型、时间类型的方法的Convert类,以及检查继承关系的as运算符

    从返回的结果看,C++和dynamic_cast和C#的as很相似,两者都是在失败时候返回null。

    不过面向对象的关键点在于什么都是以对象为操作单位,如前所讲dynamic_cast看起来更像是一个全局方法。因此我便模仿C#的数值类的TryParse方法,写了一个包裹dynamic_cast的类型转换方法:

    
    /////////////////////////////////////////////////////////////////////////////
    // dynamic_cast_tryparse.cpp                                                      
    // Language:    C++                   
    // Complier:    Visual Studio 2010, Xcode3.2.6 
    // Platform:    MacBook Pro 2010
    // Application: none  
    // Author:      Ider, Syracuse University, ider.cs@gmail.com
    ///////////////////////////////////////////////////////////////////////////
    #include <string>
    #include <iostream>
    using namespace std;
    
    class Parents
    {
    public:
        Parents(string n="Parent"){ name = n;}
        virtual ~Parents(){}
        
        virtual void Speak()
        {
            cout << "\tI am " << name << ", I love my children." << endl;
        }
        void Work()
        {
            cout << "\tI am " << name <<", I need to work for my family." << endl;;
        }
        
        /************** TryParseTo **************/
        template<typename T> bool TryParseTo(T** outValue)
        {
            T* temp = dynamic_cast<T*> (this);
            if (temp == NULL) return false;
            
            *outValue = temp;
            return true;
        }
        
    protected:
        string name;
    };
    
    class Children : public Parents
    {
    public:
        Children(string n="Child"):Parents(n){ }
        
        virtual ~Children(){}
        
        virtual void Speak()
        {
            cout << "\tI am " << name << ", I love my parents." << endl;
        }
        /*
         **Children inherit Work() method from parents,
         **it could be treated like part-time job.
         */
        void Study()
        {
            cout << "\tI am " << name << ", I need to study for future." << endl;;
        }
        
    private:
        //string name; //Inherit "name" member from Parents
    };
    
    class Stranger 
    {
    public:
        Stranger(string n="stranger"){name = n;}
        virtual ~Stranger(){}
        
        void Self_Introduce()
        {
            cout << "\tI am a stranger" << endl;
        }
        void Speak()
        {
            //cout << "I am a stranger" << endl;
            cout << "\tDo not talk to "<< name << ", who is a stranger." << endl;
        }
    private:
        string name;
    };
    
    int main() 
    {
    
        Children * parsedChild;
        Parents * parsedParent;
        Stranger * parsedStranger;
        
        Parents * mother = new Parents("Mother who pretend to be a my daugher");
        if(mother->TryParseTo<Children>(&parsedChild))
            parsedChild->Speak();
        else
            cout << "Parents parse to Children failed" << endl;
        
        delete mother;
        
        mother = new Children("Daughter who pretend to be a my mother");
        if(mother->TryParseTo<Children>(&parsedChild))
            parsedChild->Speak();
        else
            cout << "Parents parse to Children failed" << endl;
        
        delete mother;
        
        Children * son = new Children("Son who pretend to be a my father");
        if(son->TryParseTo<Parents>(&parsedParent))
            parsedParent->Speak();
        else
            cout << "Children parse to Parents failed" << endl;
        
        if(son->TryParseTo<Stranger>(&parsedStranger))
            parsedStranger->Speak();
        else
            cout << "Children parse to Stranger failed" << endl;
        
        delete son;
        
        //pointer of child class could pointer to base class object
        
        /* 
        * son = new Parents("Father who pretend to be a my son");
        if(son->TryParseTo<Parents>(&parsedParent))
            parsedParent->Speak();
        else
            cout << "Parse failed" << endl;
        
        delete son;
        */
    
        return 0;
    }
    
    /********************* Result *********************/
    
    //Parents parse to Children failed
    //    I am Daughter who pretend to be a my mother, I love my parents.
    //    I am Son who pretend to be a my father, I love my parents.
    //Children parse to Stranger failed
      
      

    这段代码中使用到的类跟dynamic_cast一文中使用的基本一样,只是在基类中多了一个模板方法:

    
        template<typename T> bool TryParseTo(T** outValue)
        {
            T* temp = dynamic_cast<T*> (this);
            if (temp == NULL) return false;
            
            *outValue = temp;
            return true;
        }
        

    该方法需要指定的目标类型,将自身指针转换成目标指针。转换成功,则将结果赋值给相应的变量,并返回真值;若失败则返回假值,不改变指针变量。因为要让外部的指针变量能够接受到改值,因此不得不使用指向指针的指针。

    因为在基类中以公共结果的形式出现,所以每一个子类都继承了该方法,无论是基类的对象还是子类的对象都可以调用该方法。而该方法又不是虚方法,因此不并不希望子类去修改它。只是因为方法是模板方法,可能在编译的时候需要多花一些时间。

    由于引用必须在定义时就赋值,并且dynamic_cast对于基于引用的转换不成功时将抛出异常,因此对于基于引用的转换,我还没有想出有什么好的山寨形式。

    从测试代码的结果也可以看出,对于该发放的调用都是成功有效的。

    所以又应了导师常说的一句话:When use C++, the good news is that you can do everything you want, the bad news is that you have to do everything you want.

  • 相关阅读:
    fastapi+vue搭建免费代理IP网站部署至heroku
    如何选择免费代理ip,需要注意哪些指标值和基本参数
    如何部署MongoDB并开启远程访问Docker版
    Linux设置Frps Frpc服务开启启动
    Docker搭建VS Code Server ,设置访问密码随时随地写代码
    薅羊毛须及时 多平台微信线报提醒脚本
    python+selenium实现百度关键词搜索自动化操作
    用python selenium 单窗口单IP刷网站流量脚本
    杂记 内容会在留言中补充
    c#杂记
  • 原文地址:https://www.cnblogs.com/ider/p/cpp_cast_operator_part6.html
Copyright © 2020-2023  润新知