出處: 天極網
假設我們要開發乙個string類,它可以方便地處理
字串資料。我們可以在類中宣告乙個陣列,考慮到有時候字串極長,我們可以把陣列大小設為200,但一般的情況下又不需要這麼多的空間,這樣是浪費了記憶體。對了,我們可以使用new操作符,這樣是十分靈活的,但在類中就會出現許多意想不到的問題,本文就是針對這一現象而寫的。現在,我們先來開發乙個wrong類,從名稱上看出,它是乙個不完善的類。的確,我們要刻意地使它出現各種各樣的問題,這樣才好對症下藥。好了,我們開始吧!
wrong.h:
#ifndef wrong_h_
#define wrong_h_
class wrong
;#endif
wrong.cpp:
#include <iostream>
#include <cstring>
#include "wrong.h"
using
namespace std;
wrong::wrong(const char * s)
//拷貝資料
wrong::wrong()
wrong::~wrong()
ostream & operator<<(ostream & os, const wrong & st)
test
_right.cpp:
#include <iostream>
#include <stdlib.h>
#include "wrong.h"
using namespace std;
int main()
執行結果:
天極網請按任意鍵繼續. . .
大家可以看到,以上程式十分正確,而且也是十分有用的。可是,我們不能被表面現象所迷惑!下面,請大家用test_wrong.cpp檔案替換test_right.cpp檔案進行編譯,看看結果。有的
編譯器可能就是根本不能進行編譯!
test_wrong.cpp:
#include <iostream>
#include <stdlib.h>
#include "wrong.h"
using namespace std;
void show_right(const wrong&);
void show_wrong(const wrong);//注意,引數非引用,而是按值傳遞。
int main()
void show_right(const wrong& a)
void show_wrong(const wrong a)
執行結果:
下面分別輸入三個範例:
第乙個範例。
第二個範例。
第三個範例。
第乙個範例。
這個字串將被刪除:第乙個範例。
使用正確的函式:
第二個範例。
第二個範例。
使用錯誤的函式:
第二個範例。
這個字串將被刪除:第二個範例。
這個字串將被刪除:?= ?=
wrong2: 第三個範例。
wrong3: 第四個範例。
下面,程式結束,析構函式將被呼叫。
這個字串將被刪除:第四個範例。
這個字串將被刪除:第三個範例。
這個字串將被刪除:?=
這個字串將被刪除:x =
這個字串將被刪除:?=
這個字串將被刪除:
現在,請大家自己試試執行結果,或許會更加慘不忍睹呢!下面,我為大家一一分析原因。
一:複製建構函式。
二:賦值函式。
我們先來講複製建構函式。什麼是複製建構函式呢?比如,我們可以寫下這樣的**:wrong test1(test2);這是進行初始化。我們知道,初始化物件要用建構函式。可這兒呢?按理說,應該有宣告為這樣的建構函式:wrong(const wrong &);可是,我們並沒有定義這個建構函式呀?答案是,c++提供了預設的複製建構函式,問題也就出在這兒。
(1):什麼時候會呼叫複製建構函式呢?(以wrong類為例。)
在我們提供這樣的**:wrong test1(test2)時,它會被呼叫;當函式的引數列表為按值傳遞,也就是沒有用引用和指標作為型別時,如:void show_wrong(const wrong),它會被呼叫。其實,還有一些情況,但在這兒就不列舉了。
(2):它是什麼樣的函式。
它的作用就是把兩個類進行複製。拿wrong類為例,c++提供的預設複製建構函式是這樣的:
wrong(const wrong& a)
在平時,這樣並不會有任何的問題出現,但我們用了new操作符,涉及到了動態記憶體分配,我們就不得不談談淺複製和深複製了。以上的函式就是實行的淺複製,它只是複製了指標,而並沒有複製指標指向的資料,可謂一點兒用也沒有。打個比方吧!就像乙個朋友讓你把乙個程式通過網路發給他,而你大大咧咧地把快捷方式發給了他,有什麼用處呢?我們來具體談談:
假如,a物件中儲存了這樣的字串:「c++」。它的位址為2000。現在,我們把a物件賦給b物件:wrong b=a。現在,a和b物件的str指標均指向2000位址。看似可以使用,但如果b物件的析構函式被呼叫時,則位址2000處的字串「c++」已經被從記憶體中抹去,而a物件仍然指向位址2000。這時,如果我們寫下這樣的**:cout<<a<<endl;或是等待程式結束,a物件的析構函式被呼叫時,a物件的資料能否顯示出來呢?只會是亂碼。而且,程式還會這樣做:連續對位址2000處使用兩次delete操作符,這樣的後果是十分嚴重的!
本例中,有這樣的**:
wrong* wrong1=new wrong(test1);
cout<<*wrong1<<endl;
delete wrong1;
假設test1中str指向的位址為2000,而wrong中str指標同樣指向位址2000,我們刪除了2000處的資料,而test1物件呢?已經被破壞了。大家從執行結果上可以看到,我們使用cout<<test1時,一點反應也沒有。而在test1的析構函式被呼叫時,顯示是這樣:「這個字串將被刪除:」。
再看看這段**:
cout<<"使用錯誤的函式:"<<endl;
show_wrong(test2);
cout<<test2<<endl;//這一段**出現嚴重的錯誤!
show_wrong函式的引數列表void show_wrong(const wrong a)是按值傳遞的,所以,我們相當於執行了這樣的**:wrong a=test2;函式執行完畢,由於生存週期的緣故,物件a被析構函式刪除,我們馬上就可以看到錯誤的顯示結果了:這個字串將被刪除:?=。當然,test2也被破壞了。解決的辦法很簡單,當然是手工定義乙個複製建構函式嘍!人力可以勝天!
wrong::wrong(const wrong& a)
我們執行的是深複製。這個函式的功能是這樣的:假設物件a中的str指標指向位址2000,內容為「i am a c++ boy!」。我們執行**wrong b=a時,我們先開闢出一塊記憶體,假設為3000。我們用strcpy函式將位址2000的內容拷貝到位址3000中,再將物件b的str指標指向位址3000。這樣,就互不干擾了。
wrong wrong3;
wrong3=test4;
經過我前面的講解,大家應該也會對這段**進行尋根摸底:憑什麼可以這樣做:wrong3=test4???原因是,c++為了使用者的方便,提供的這樣的乙個操作符過載函式:operator=。所以,我們可以這樣做。大家應該猜得到,它同樣是執行了淺複製,出了同樣的毛病。比如,執行了這段**後,析構函式開始大展神威^_^。由於這些變數是後進先出的,所以最後的wrong3變數先被刪除:這個字串將被刪除:第四個範例。很正常。最後,刪除到test4的時候,問題來了:這個字串將被刪除:?=。原因我不用贅述了,只是這個賦值函式怎麼寫,還有一點兒學問呢!大家請看:
平時,我們可以寫這樣的**:x=y=z。(均為整型變數。)而在類物件中,我們同樣要這樣,因為這很方便。而物件a=b=c就是a.operator=(b.operator=(c))。而這個operator=函式的引數列表應該是:const wrong& a,所以,大家不難推出,要實現這樣的功能,返回值也要是wrong&,這樣才能實現a=b=c。我們先來寫寫看:
wrong& wrong::operator=(const wrong& a)
是不是這樣就行了呢?我們假如寫出了這種**:a=a,那麼大家看看,豈不是把a物件的資料給刪除了嗎?這樣可謂引發一系列的錯誤。所以,我們還要檢查是否為自身賦值。只比較兩物件的資料是不行了,因為兩個物件的資料很有可能相同。我們應該比較位址。以下是完好的賦值函式:
wrong& wrong::operator=(const wrong& a)
把這些**加入程式,問題就完全解決,下面是執行結果:
下面分別輸入三個範例:
第乙個範例
第二個範例
第三個範例
第乙個範例
這個字串將被刪除:第乙個範例。
第乙個範例
使用正確的函式:
第二個範例。
第二個範例。
使用錯誤的函式:
第二個範例。
這個字串將被刪除:第二個範例。
第二個範例。
wrong2: 第三個範例。
wrong3: 第四個範例。
下面,程式結束,析構函式將被呼叫。
這個字串將被刪除:第四個範例。
這個字串將被刪除:第三個範例。
這個字串將被刪除:第四個範例。
這個字串將被刪除:第三個範例。
這個字串將被刪除:第二個範例。
這個字串將被刪除:第乙個範例。
關於動態記憶體分配的問題就介紹到這兒,希望大家都能熱愛程式設計,熱愛c++!
C 中動態記憶體分配引發問題的解決方案
假設我們要開發乙個string類,它可以方便地處理字串資料。我們可以在類中宣告乙個陣列,考慮到有時候字串極長,我們可以把陣列大小設為200,但一般的情況下又不需要這麼多的空間,這樣是浪費了記憶體。對了,我們可以使用new操作符,這樣是十分靈活的,但在類中就會出現許多意想不到的問題,本文就是針對這一現...
C 中動態記憶體分配引發問題的解決方案
假設我們要開發乙個string類,它可以方便地處理字串資料。我們可以在類中宣告乙個陣列,考慮到有時候字串極長,我們可以把陣列大小設為200,但一般的情況下又不需要這麼多的空間,這樣是浪費了 記憶體。對了,我們可以使用new操作符,這樣是十分靈活的,但在類中就會出現許多意想不到的問題,本文就是針對這一...
c語言動態記憶體分配 C 動態記憶體分配
動態記憶體分配 雖然通過陣列就可以對大量的資料和物件進行有效地管理,但是很多情況下,在程式執行之前,我們並不能確切地知道陣列中會有多少個元素。這種情況下,如果陣列宣告過大,就會造成浪費 宣告過小,就會影響處理。在c 中,動態記憶體分配技術可以保證程式在執行過程中按照需要申請適量記憶體,使用後釋放,從...