zoukankan      html  css  js  c++  java
  • C++用户自定义转换(UserDefined Conversion)

    在计算机语言中,类型的存在让我们可以更有针对性的进行数据和功能的处理,但是却又存在了类型转化的问题。C++如同其他计算机语言一样,也同样都这些问题。不过它相对于C而言多了引用类型(Reference);相对与C#来讲,又多了指针类型(Point)。这似乎让它的类型转化变得更加扑朔迷离。

    也许是C方面知识的延续,我对C++的基础类型之间的转换还算比较清楚的,但是由于C#的Convert这个类库是那么的好用,以至于我一直不太适应C++的转换方式。不过经过导师的教授,发现C++的也可以构建禇类似Convert的转换方式。

    在导师经过一堂课关于C++类型转换的课后,我又在网上查阅相关资料,以便更清晰详细的总结C++在类型转换的方式。

    传统转换方式(Traditional Type-Casting)

    C++作为C语言的超集,完全继承了C语言所具有的类型转换方法和能力,因此对于这部分在基础数值类型上的转换是比较容易理解的。但是因为C++是面向对象的语言,有类的概念,因此让又多一层需要理解的内容。

    隐式转换 (Implicit Conversion)

    隐式转换不需要任何转换运算符,编译器会自动根据类型兼容性进行不同类型之间的转换。一般情况下,在C/C++中这种转换多出现在基本数值类型上,其基本原则就是所需内存小的类型可以直接转换成内存大相同的或者。

    内存大小相同的类型之间也可以互相转换,但是得到的结果可能不是预期的,因而可能会得到编译器的警告。比如 unsigned int uintVariable = -1; 。

    虽说:程序员只在意错误(Error),不关心警告(Warning),但是导师也是严令禁止在程序中出现的,所以对于这样的隐式转换也会尽量避免。

    显示转换 (Explicit Conversion)

    显示转换要表明所要转换的目标对象是什么样的类型,然后编译器会做出转换,它有两种格式:

      • C语言格式(C-like Cast)
    (new_type) expression
      • 函数式(Function-style Cast)
    new_type (expression)

    示例代码

    #include <iostream>
    using namespace std;

    int main() {
    int x0 = 100;
    float num0 = x0;
    float num = 98.76;
    int x1 = (int) num;
    int x2 = int(num);

    cout
    << "num0 = " << num0 << endl;
    cout
    << "x1 = " << x1 << endl;
    cout
    << "x2 = " << x2 << endl;
    cout
    << "x3 = " << x3 << endl;
    }

    对于C++的类而言,也可以在其实例对象上使用传统的类型转换,这是利用了C++的一些语言特性。

    下边就以例子来做解释

    代码

    #include<iostream>
    #include
    <string>
    using namespace std;

    //macro definitions
    #define IDER_DEBUG 1
    #define FUNC_TRAC(info) {if(IDER_DEBUG)cout<<"----"<<info<<"----"<<endl;}

    //class declaration
    class Human;
    class Ape;
    class Programmer;

    //class definition
    class Programmer
    {
    public:
    Programmer(
    string where = "genius")
    {
    FUNC_TRAC(
    "Programmer Default Constructor");
    from
    = where;
    }
    /*Programmer(Programmer& p)
    {
    FUNC_TRAC("Programmer Copy Constructor");
    from = p.from;
    }
    */
    void Speach(){cout<<"I am a Programmer, I am "<< from <<endl;}
    private:
    string from;
    };

    class Human
    {
    public:
    Human(
    string where = "delivered by Parents"):heart("Human with Training")
    {
    FUNC_TRAC(
    "Human Default Constructor");
    from
    = where;
    }
    Human(Ape
    & a):heart("Human with Practice")
    {
    FUNC_TRAC(
    "Hummer Ape-Promotion Constructor");
    from
    = "Evolution from an Ape";
    }
    operator Programmer() //here is weird, it is really different whether we have "&" or not
    {
    FUNC_TRAC(
    "Hummer Programmer-Cast Operator");
    return heart;
    //Programmer("Human with Practice"); // it is not good to return temporary variable
    }
    Human
    & operator =(Human& h)
    {
    FUNC_TRAC(
    "Hummer Assignment Operator");
    cout
    <<"Call assignment"<<endl;
    return *this;
    }
    void Speach(){cout<<"I am a Human, I am "<< from <<endl;}
    private:
    string from;
    Programmer heart;
    //Every one has a heart to be a programmer
    };

    class Ape
    {
    public:
    Ape(
    string where = "from Nature")
    {
    FUNC_TRAC(
    "Ape Default Constructor");
    from
    = where;
    }
    Ape
    & operator =(Programmer& p)
    {
    FUNC_TRAC(
    "Ape Programmer-Assignment Operator");
    from
    ="Degeneration from a Programmer";
    return *this;
    }
    /*Ape& operator =(Ape& p)
    {
    FUNC_TRAC("Ape Assignment Operator");
    cout<<"Ape assign"<<endl;
    return *this;
    }
    */
    void Speach(){cout<<"#(*%^, !@#$&)( "<< from <<endl;}
    private:
    string from;
    };


    //main function
    int main(void) {
    Ape a;
    //a.Speach();
    Human h = a; // using promtion constructor
    //h.Speach();

    Human h2;
    h2
    = a; // Error, no match assignment opeartor

    Programmer p
    = h; // using Programmer-cast operaotor
    //p.Speach();
    Programmer p0;
    p0
    = h; // using Programmer-cast operaotor

    Programmer p1
    = h.operator Programmer();
    Programmer p2
    = Programmer(h);
    Programmer p3
    = (Programmer)h;

    Ape a2;
    a2
    = p; //using assignment operator
    //a2.Speach();

    Ape a3
    = p; // Error, no match constructor

    return 0;
    }

    在这个例子中,我定义了三个类,这三个类之间没有继承和被继承的关系,也没有friend关系,其基本联系就是:Ape可以进化成为Human,Human经过不断的训练就可以成为Programmer,不过Programmer也有可能退化变成Ape。

    分析

    从main函数中他们进行的转换操作,可以看出这是一种隐式的转换。不过三个类的对象之间能够实现转换的真正原因却并不相同。

    首先,从Ape到Human的转换方式
    Human h = a;

    其实是调用了Human的promotion构造函数
    Human(Ape& a);

    这个函数接受了Ape作为构造参数,实例化了一个Human对象。

    从Human到 Programmer,则是因为我在Human中定义了一个到Programmer的转换运算符operator Programmer()

    因此,在main函数中的两个赋值语句:Programmer p = h; p0 = h;

    都是调用了这个转换函数。

    从Programmer退化到Ape是一件很不情愿的事情(就因为在中文里,我们是程序猿),在代码中的实现方式,则是在Ape类中定义了一个接受Programmer引用作为参数的,Assignment运算符的重载形式。 Ape& operator =(Programmer& p)

    于是下边的语句a2 = p;

    就得以在程序中运行了

    进一步分析

    已经看到了Ape, Human,Programmer的之间的转换都是使用了不同的C++特性,调用的是不同的方法。但是深究起来,这些方法还是各有个的不同。

    以Human到Programmer为基准,这个转换用的是用户自定义转换(user-defined cast),因此可以说这种方式才是真正的类型之间的转换。

    也因此我们在main中看到了两种语法格式的转换都是有效的:

      • 定义并初始化
    Programmer p = h;
      • 赋值
    p0 = h;

    但是Ape到Human的转换调用的构造函数,因此它只有在类实例化对象并初始化的时候才有效,也因此下边的语句会得到编译器的错误: Human h2; h2 = a; // Error, no match assignment opeartor

    因为Human从出生就知道自己是怎么来的,不应该后来才发现自己不是妈生的(当然,这些情况不是不可能的,比如“人猿泰山”)。

    而Programmer到Ape是后天形成的,不然一出生就变成猴子了,那就只能用脚趾了Coding了。所以以下代码也是编译不过的:Ape a3 = p; // Error, no match constructor

    在回过来讲讲Human到Programmer,我们还可以用更多不同的形式来写,比如两种形式的显示转换: Programmer p1 = Programmer(h); Programmer p2 = (Programmer)h;

    (是初始化还是赋值都无所谓)

    但是真正编译之后,其格式应该是: Programmer p3 = h.operator Programmer();

    对于Assignment运算符其实也是如此,真正调用的还是: a2.operator =(p);

    后记

    其实在实际编程中,可能受到了C#的影响(因为C#的初始化并不是得到一个全新的对象,而只是获得引用),并不会经常使用到用户自定义转换,也很少重载一个接受非自身对象引用的Assignment运算符。

    真正要转换的时候,多数还是通过构造函数进行。或者是,实例化好对象,通过两者的接口对数据进行赋值。毕竟以上讲到各种方式中,也只能调用到接收到对象的外部接口,不能进行私有数据的操作。

    关于数值类型的转换和类对象的转换,前面都已经提到了,但似乎还遗漏了什么?

    是的,C++还有引用类型(reference)和指针类型(pointer)。这两者在不同类型之间的转换还没有说。

    在C++中,指针类型似乎是被视为是没有差异的,想想的确如此,因为它只是存放所指对象的地址,因此所需内存空间、格式都是一致的,也因此在C++不同类型 之间的指针是可以随意转换的,一般需要用显示转换。但是这种转换是没有意义,因为地址所指的类型并非所需要的类型,通过该地址进行偏移找到的数据或方法就不会是我们所需要的了,在运行的时候,程序就会发生异常。

    对于引用类型,在没有继承关系的类型之间进行转换似乎也并不合理,引用其实在实现上可以被视为指针,只是在语法格式上,它被当做对象使用。如果进行引用类型的转换,我们到底是想要一个新的对象呢,还是只要地址?让人迷糊。

    另外,指针和引用的应该是用在已经存在的对象或对象变量上。因此如果是转换中返回转换运算符的方法之内的一个局部变量(像Human类的operator Programmer()方法中我注释掉的那行代码),那么在离开转换运算符的方法之后,那些变量就会被回收,在指向那些地址也是没有意义了;如果是在内部new一个心的对象,这个对象的管理就变得没有约束性,我们不知道该在何时会去delete它;即使像我实现的那样,在Human类中带着一个Programmer的对象Heart,但是这个设计似乎也并不是非常好的,因为不能保证每个人都有一颗作为程序员的心。

    遗留问题

    前面也提到了指针和引用在类型转换上的问题,因此对于用户自定义转换符,在我的代码中,我所使用的是基于对象的转换:operator Programmer();

    不是基于指针:operator Programmer*();

    也不是基于引用operator Programmer&()

    在我看来,这是合理的,也是合适的。

    但是如果我在Programmer类中定义了一个copy构造函数,那么无论以上提到4种的从Human到Programmer的代码格式都得到编译错误。

    这似乎可以理解:编译器会从构造函数中发现合适的入口,但是它失败了,所以就错误了。

    但是为何h.operator Programmer();

    的格式也是错误就让我十分的不解了。

    再进一步,如果我把基于对象的转换改成基于引用的转换,编译器就没有报出错误了。但是我认为这个在逻辑上应该是不对的。

    C++真的一门复杂的语言(当然跟JavaScript还是没得比的),对于这个问题,如果读者你知道这方面的原因,还请你能跟我分享一下你的理解和看法。

  • 相关阅读:
    《C# to IL》第一章 IL入门
    multiple users to one ec2 instance setup
    Route53 health check与 Cloudwatch alarm 没法绑定
    rsync aws ec2 pem
    通过jvm 查看死锁
    wait, notify 使用清晰讲解
    for aws associate exam
    docker 容器不能联网
    本地运行aws lambda credential 配置 (missing credential config error)
    Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?
  • 原文地址:https://www.cnblogs.com/ider/p/cpp_cast_operator_part1.html
Copyright © 2011-2022 走看看