關於C 思考(二)

2021-06-29 08:57:49 字數 4118 閱讀 1070

c++很雜,很龐大,其中有乙個概念是非常重要的(之一)——拷貝建構函式,想當初的時候,對於它理解不夠,造成各種bug。其實拷貝建構函式的基礎是引用。

先上**:

int f (int x, char c);

int g = f (a,b);

彙編後(簡化)

push b

push a

call f ( )

add sp,4

mov g, register a

(為什麼是push b ,可以看看c 函式呼叫約定!堆疊!在c和c + +中,引數是從右向左進棧,然後呼叫函式,呼叫**負責清理棧中的引數(這

一點說明了 add sp,4的作用)

上面用的都是編譯器內建的型別,所以編譯器很好的工作,若是一種編譯器不知的型別呢?

struct big b ,b2;

big bigfun(big b)

int main(void)

彙編後的**

00df1438 sub esp,6ch

00df143b mov ecx,1bh

00df1440 mov esi,offset b (0df7138h)

00df1445 mov edi,esp

00df1447 rep movs dword ptr es:[edi],dword ptr [esi]

00df1449 lea eax,[ebp-134h]

00df144f push eax

00df1450 call bigfun (0df10dch)

00df1455 add esp,70h

00df1458 mov ecx,1bh

00df145d mov esi,eax

00df145f lea edi,[ebp-1a8h]

00df1465 rep movs dword ptr es:[edi],dword ptr [esi]

00df1467 mov ecx,1bh

00df146c lea esi,[ebp-1a8h]

00df1472 mov edi,offset b2 (0df71a8h)

00df1477 rep movs dword ptr es:[edi],dword ptr [esi]

上面大概的流程:

函式引數

返回位址

區域性變數

看把返回值的位址像乙個函式引數一樣壓棧,讓函式直接把返回值資訊拷貝到目的地。

class howmany

;

/#include 「howmany.h」

/#include

std::ofstream out(「hownamy.out」);

howmany::howmany()

howmany::~howmany()

void howmany::printf(const char *msg)

int main(void)

結果是:

after cunst of h: object_cout = 1

x argume inside f(): object_cout = 1

after call to f(): object_cout = 0

編譯器假定我們想使用位拷貝( b i t c o p y)來建立物件。在許多情況下,這是可行的。但在h o w m a n y類中就行不通,因為初始化不僅僅是簡單的拷貝。如果類中含有指標又將出現問題:它們指向什麼內容,是否拷貝它們或它們是否與一些新的記憶體塊相連?

防止編譯器進行位拷貝 ( b i t c o p y )。每當編譯器需要從現有的物件建立新物件時,我們可以通過定義我們自己的函式做這些事。因為我們是在

建立新物件,所以,這個函式應該是建構函式,並且傳遞給這個函式的單一引數必須是我們創立的物件的源物件。但是這個物件不能傳入建構函式,因為我們試圖定義處理傳值方式的函式按句法構造傳遞乙個指標是沒有意義的,畢竟我們正在從現有的物件建立新物件。這裡,引用就起作用了,可以使用源物件的引用。這個函式被稱為拷貝建構函式,它經常被提及為 x ( x & )(它是被稱為 x的類的外在表現)。

如果設計了拷貝建構函式,當從現有的物件建立新物件時,編譯器將不使用位拷貝( b i t c o p y )。編譯器總是呼叫我們的拷貝建構函式。所以,如果我們沒有設計拷貝函式,編譯器將做一些判斷,但我們完全可以接管這個過程的控制。

位拷貝拷貝的是位址,而值拷貝則拷貝的是內容。

繼續碼上**:

#include 

#include

std::ofstream out("howmany2.out");

class howmany2

; char id[bufsize];

static

int object_cout;

public:

howmany2(const

char *id)

else

++object_cout;

print("hownamy2()");

}howmany2(const howmany2 &h)

void print(const

char *msg)

out<<'\t'

<": "

<<"object_count = "

}~howmany2()

};int howmany2::object_cout = 0;

howmany2 func( howmany2 &x)

int main(void)

結果:

hownamy2()

h: object_count = 1

entering f()

hownamy2(howmany2 &)

hcopy: object_count = 2

x argume inside f()

hcopy: object_count = 2

hownamy2(howmany2 &)

hcopycopy: object_count = 3

~hownamy2()

hcopy: object_count = 2

h2 after call f()

hcopycopy: object_count = 2

call f(),no return value!

hownamy2(howmany2 &)

hcopy: object_count = 3

x argume inside f()

hcopy: object_count = 3

hownamy2(howmany2 &)

hcopycopy: object_count = 4

~hownamy2()

hcopy: object_count = 3

~hownamy2()

hcopycopy: object_count = 2

after call to f()

~hownamy2()

hcopycopy: object_count = 1

~hownamy2()

h: object_count = 0

現在,我們可能已頭暈了。我們可能想,怎樣才能不必了解拷貝建構函式就能寫乙個具有一定功能的類。但是我們別忘了:僅當準備用傳值的方式傳遞類物件時,才需要拷貝建構函式。如果不需要這麼做,就不要拷貝建構函式。

我們也許會說: 「如果我自己不寫拷貝建構函式,編譯器將為我建立。所以,我怎麼能保證乙個物件永遠不會被通過傳值方式傳遞呢?」

有乙個簡單的技術防止通過傳值方式傳遞:宣告乙個私有( p r i v a t e)拷貝建構函式。我們甚至不必去定義它,除非我們的成員函式或友元( f r i e n d)函式需要執行傳值方式的傳遞。如果使用者試圖用傳值方式傳遞或返回物件,編譯器將會發出乙個出錯資訊。這是因為拷貝構造函

數是私有的。因為我們已顯式地宣告我們接管了這項工作,所以編譯器不再建立預設的拷貝建構函式。

C 關於記憶體的思考

對面試既愛又恨,每次面試都像是經歷了一次洗禮,緊張又興奮,大腦高速運轉,最重要的是每次都會發現自身的許多不 足,這樣才能不斷的提高自我。下面是面試遇到的乙個問題,還挺有意思的。include void getmemory char p p char malloc 100 void test void...

關於C 多型的思考

c 中,可以如下定義 base father new derived father 是基類引用,指向子類物件,這一點有點類似c 該引用的使用模式如下 先說結論 測試 如下 using logging class base public virtual void show2 public void m...

關於C 資訊封裝的思考

這幾天看書,頗有些收穫。在c 中,資訊的隱藏和封裝似乎有些問題,比如我在乙個靜態連線褲 動態鏈結庫中定義了我的乙個類,但是我又不想把我的任何的實現細節提供給使用者,這個時候該怎麼做呢?我所能想到的辦法,似乎就是把頭檔案中的private和pritected宣告的函式和變數全都刪除掉。於是便自己寫了幾...