日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

C ++ 用戶自定義轉(zhuǎn)換

 WUCANADA 2011-12-06

C++用戶自定義轉(zhuǎn)換(User-Defined Conversion)

在計算機語言中,類型的存在讓我們可以更有針對性的進行數(shù)據(jù)和功能的處理,但是卻又存在了類型轉(zhuǎn)化的問題。C++如同其他計算機語言一樣,也同樣都 這些問題。不過它相對于C而言多了引用類型(Reference);相對與C#來講,又多了指針類型(Point)。這似乎讓它的類型轉(zhuǎn)化變得更加撲朔迷 離。

也許是C方面知識的延續(xù),我對C++的基礎(chǔ)類型之間的轉(zhuǎn)換還算比較清楚的,但是由于C#的Convert這個類庫是那么的好用,以至于我一直不太適應(yīng)C++的轉(zhuǎn)換方式。不過經(jīng)過導(dǎo)師的教授,發(fā)現(xiàn)C++的也可以構(gòu)建禇類似Convert的轉(zhuǎn)換方式。

在導(dǎo)師經(jīng)過一堂課關(guān)于C++類型轉(zhuǎn)換的課后,我又在網(wǎng)上查閱相關(guān)資料,以便更清晰詳細的總結(jié)C++在類型轉(zhuǎn)換的方式。

傳統(tǒng)轉(zhuǎn)換方式(Traditional Type-Casting)

C++作為C語言的超集,完全繼承了C語言所具有的類型轉(zhuǎn)換方法和能力,因此對于這部分在基礎(chǔ)數(shù)值類型上的轉(zhuǎn)換是比較容易理解的。但是因為C++是面向?qū)ο蟮恼Z言,有類的概念,因此讓又多一層需要理解的內(nèi)容。

隱式轉(zhuǎn)換 (Implicit Conversion)

隱式轉(zhuǎn)換不需要任何轉(zhuǎn)換運算符,編譯器會自動根據(jù)類型兼容性進行不同類型之間的轉(zhuǎn)換。一般情況下,在C/C++中這種轉(zhuǎn)換多出現(xiàn)在基本數(shù)值類型上,其基本原則就是所需內(nèi)存小的類型可以直接轉(zhuǎn)換成內(nèi)存大相同的或者。

內(nèi)存大小相同的類型之間也可以互相轉(zhuǎn)換,但是得到的結(jié)果可能不是預(yù)期的,因而可能會得到編譯器的警告。比如 unsigned int uintVariable = -1; 。

雖說:程序員只在意錯誤(Error),不關(guān)心警告(Warning),但是導(dǎo)師也是嚴令禁止在程序中出現(xiàn)的,所以對于這樣的隱式轉(zhuǎn)換也會盡量避免。

顯示轉(zhuǎn)換 (Explicit Conversion)

顯示轉(zhuǎn)換要表明所要轉(zhuǎn)換的目標對象是什么樣的類型,然后編譯器會做出轉(zhuǎn)換,它有兩種格式:

    • C語言格式(C-like Cast)
(new_type) expression
    • 函數(shù)式(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++的類而言,也可以在其實例對象上使用傳統(tǒng)的類型轉(zhuǎn)換,這是利用了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;
}

在這個例子中,我定義了三個類,這三個類之間沒有繼承和被繼承的關(guān)系,也沒有friend關(guān)系,其基本聯(lián)系就是:Ape可以進化成為Human,Human經(jīng)過不斷的訓(xùn)練就可以成為Programmer,不過Programmer也有可能退化變成Ape。

分析

從main函數(shù)中他們進行的轉(zhuǎn)換操作,可以看出這是一種隱式的轉(zhuǎn)換。不過三個類的對象之間能夠?qū)崿F(xiàn)轉(zhuǎn)換的真正原因卻并不相同。

首先,從Ape到Human的轉(zhuǎn)換方式
Human h = a;

其實是調(diào)用了Human的promotion構(gòu)造函數(shù)
Human(Ape& a);

這個函數(shù)接受了Ape作為構(gòu)造參數(shù),實例化了一個Human對象。

從Human到 Programmer,則是因為我在Human中定義了一個到Programmer的轉(zhuǎn)換運算符operator Programmer()

因此,在main函數(shù)中的兩個賦值語句:Programmer p = h; p0 = h;

都是調(diào)用了這個轉(zhuǎn)換函數(shù)。

從Programmer退化到Ape是一件很不情愿的事情(就因為在中文里,我們是程序猿),在代碼中的實現(xiàn)方式,則是在Ape類中定義了一個接受Programmer引用作為參數(shù)的,Assignment運算符的重載形式。 Ape& operator =(Programmer& p)

于是下邊的語句a2 = p;

就得以在程序中運行了

進一步分析

已經(jīng)看到了Ape, Human,Programmer的之間的轉(zhuǎn)換都是使用了不同的C++特性,調(diào)用的是不同的方法。但是深究起來,這些方法還是各有個的不同。

以Human到Programmer為基準,這個轉(zhuǎn)換用的是用戶自定義轉(zhuǎn)換(user-defined cast),因此可以說這種方式才是真正的類型之間的轉(zhuǎn)換。

也因此我們在main中看到了兩種語法格式的轉(zhuǎn)換都是有效的:

    • 定義并初始化
Programmer p = h;
    • 賦值
p0 = h;

但是Ape到Human的轉(zhuǎn)換調(diào)用的構(gòu)造函數(shù),因此它只有在類實例化對象并初始化的時候才有效,也因此下邊的語句會得到編譯器的錯誤: Human h2; h2 = a; // Error, no match assignment opeartor

因為Human從出生就知道自己是怎么來的,不應(yīng)該后來才發(fā)現(xiàn)自己不是媽生的(當(dāng)然,這些情況不是不可能的,比如“人猿泰山”)。

而Programmer到Ape是后天形成的,不然一出生就變成猴子了,那就只能用腳趾了Coding了。所以以下代碼也是編譯不過的:Ape a3 = p; // Error, no match constructor

在回過來講講Human到Programmer,我們還可以用更多不同的形式來寫,比如兩種形式的顯示轉(zhuǎn)換: Programmer p1 = Programmer(h); Programmer p2 = (Programmer)h;

(是初始化還是賦值都無所謂)

但是真正編譯之后,其格式應(yīng)該是: Programmer p3 = h.operator Programmer();

對于Assignment運算符其實也是如此,真正調(diào)用的還是: a2.operator =(p);

后記

其實在實際編程中,可能受到了C#的影響(因為C#的初始化并不是得到一個全新的對象,而只是獲得引用),并不會經(jīng)常使用到用戶自定義轉(zhuǎn)換,也很少重載一個接受非自身對象引用的Assignment運算符。

真正要轉(zhuǎn)換的時候,多數(shù)還是通過構(gòu)造函數(shù)進行。或者是,實例化好對象,通過兩者的接口對數(shù)據(jù)進行賦值。畢竟以上講到各種方式中,也只能調(diào)用到接收到對象的外部接口,不能進行私有數(shù)據(jù)的操作。

關(guān)于數(shù)值類型的轉(zhuǎn)換和類對象的轉(zhuǎn)換,前面都已經(jīng)提到了,但似乎還遺漏了什么?

是的,C++還有引用類型(reference)和指針類型(pointer)。這兩者在不同類型之間的轉(zhuǎn)換還沒有說。

在C++中,指針類型似乎是被視為是沒有差異的,想想的確如此,因為它只是存放所指對象的地址,因此所需內(nèi)存空間、格式都是一致的,也因此在C++ 不同類型 之間的指針是可以隨意轉(zhuǎn)換的,一般需要用顯示轉(zhuǎn)換。但是這種轉(zhuǎn)換是沒有意義,因為地址所指的類型并非所需要的類型,通過該地址進行偏移找到的數(shù)據(jù)或方法就 不會是我們所需要的了,在運行的時候,程序就會發(fā)生異常。

對于引用類型,在沒有繼承關(guān)系的類型之間進行轉(zhuǎn)換似乎也并不合理,引用其實在實現(xiàn)上可以被視為指針,只是在語法格式上,它被當(dāng)做對象使用。如果進行引用類型的轉(zhuǎn)換,我們到底是想要一個新的對象呢,還是只要地址?讓人迷糊。

另外,指針和引用的應(yīng)該是用在已經(jīng)存在的對象或?qū)ο笞兞可?。因此如果是轉(zhuǎn)換中返回轉(zhuǎn)換運算符的方法之內(nèi)的一個局部變量(像Human類的 operator Programmer()方法中我注釋掉的那行代碼),那么在離開轉(zhuǎn)換運算符的方法之后,那些變量就會被回收,在指向那些地址也是沒有意義了;如果是在內(nèi) 部new一個心的對象,這個對象的管理就變得沒有約束性,我們不知道該在何時會去delete它;即使像我實現(xiàn)的那樣,在Human類中帶著一個 Programmer的對象Heart,但是這個設(shè)計似乎也并不是非常好的,因為不能保證每個人都有一顆作為程序員的心。

遺留問題

前面也提到了指針和引用在類型轉(zhuǎn)換上的問題,因此對于用戶自定義轉(zhuǎn)換符,在我的代碼中,我所使用的是基于對象的轉(zhuǎn)換:operator Programmer();

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

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

在我看來,這是合理的,也是合適的。

但是如果我在Programmer類中定義了一個copy構(gòu)造函數(shù),那么無論以上提到4種的從Human到Programmer的代碼格式都得到編譯錯誤。

這似乎可以理解:編譯器會從構(gòu)造函數(shù)中發(fā)現(xiàn)合適的入口,但是它失敗了,所以就錯誤了。

但是為何h.operator Programmer();

的格式也是錯誤就讓我十分的不解了。

再進一步,如果我把基于對象的轉(zhuǎn)換改成基于引用的轉(zhuǎn)換,編譯器就沒有報出錯誤了。但是我認為這個在邏輯上應(yīng)該是不對的。

C++真的一門復(fù)雜的語言(當(dāng)然跟JavaScript還是沒得比的),對于這個問題,如果讀者你知道這方面的原因,還請你能跟我分享一下你的理解和看法。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多