C++用户自定义转换(User-Defined Conversion)

338 篇文章 40 订阅
155 篇文章 25 订阅

C++用户自定义转换(User-Defined Conversion

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

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

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

隐式转换 (Implicit Conversion)

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

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

虽说:程序员只在意错误(Error),不关心警告(Warning),对于这样的隐式转换也应尽量避免。

显示转换 (Explicit Conversion)

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

  1. C语言格式(C-like Cast)(new_type) expression
  2. 函数式(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函数中进行的转换操作,可以看出这是一种隐式转换。不过三个类的对象之间能够实现转换的真正原因却并不相同。

首先,从ApeHuman的转换方式:

Human h = a;

其实是调用了Humanpromotion构造函数。

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, HumanProgrammer的之间的转换都是使用了不同的C++特性,调用的是不同的方法。但是深究起来,这些方法还是各有个的不同。

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

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

定义并初始化:

Programmer p = h;

赋值

p0 = h;

但是ApeHuman的转换调用的构造函数,因此它只有在类实例化对象并初始化的时候才有效,也因此下边的语句会得到编译器的错误:

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

ProgrammerApe是后天形成的,所以以下代码也是编译不过的:

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

在回过来讲讲HumanProgrammer,我们还可以用更多不同的形式来写,比如两种形式的显示转换:

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种的从HumanProgrammer的代码格式都得到编译错误。

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

但是为何h.operator Programmer();的格式也是错误就让我十分的不解了。

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值