首先來看一下賦值運算子過載。在實際應用中,我們經常遇到需要將乙個物件賦值給另外乙個物件的情況,那麼就需要使用賦值運算子=。跟預設的拷貝建構函式一樣,如果我們沒有顯式地定義乙個賦值運算子過載函式,那麼編譯器會提供乙個預設的函式實現賦值功能。大暖男再次出場,不出意外地再次不靠譜。編譯器提供的賦值運算函式只是將乙個物件的成員變數挨個賦值給另乙個物件的成員變數。假如有兩個mystring物件a和b,執行a=b相當於執行下面這段**:
a.p = b.p;
a.len = b.len;
a.size = b.size;
由此可見,預設拷貝建構函式存在的問題(也就是淺拷貝帶來的問題)同樣存在於預設賦值運算子函式中。
問題一樣,那麼解決辦法也一樣。我們過載一下賦值運算子,在函式中進行深拷貝即可。賦值運算子過載函式實現如下:
mystring& mystring::operator= (const mystring& s)
delete p;
size = s.size;
p = new char[size + 1];
strncpy(p, s.p, s.len);
p[s.len] = '\0';
len = s.len;
return *this;
}
賦值運算子函式不會改變右值,因此可以把傳入的引數設定為const。另外賦值運算子過載函式還需要注意兩個問題:
執行a=b,首先需要把a物件原有的記憶體塊釋放掉,重新申請記憶體。然後把b物件儲存的字串拷貝過來,同時記得更新size和len。
避免自己賦值給自己。如果寫了一行**a=a,那麼根據第一條,首先把a的記憶體塊給釋放掉了,然後執行拷貝,但是這個時候p已經變成野指標了,訪問野指標可能造成程式崩潰。因此在函式中,首先要判斷傳進來的mystring是不是自己本身。如果是的話,那麼什麼也不幹,直接返回*this。
我們希望mystring類像陣列一樣,支援下標操作,這樣就可以通過索引訪問和修改字串中的某個字元。實現這個功能就需要過載操作符。下標訪問符過載可以寫成下面這樣,這也是網上最常見的寫法:
char mystring::operator (const int index)
這個函式就是這麼簡單,短短一行,不爭不搶,歲月靜好!但是這樣的**提交上去,你一定會被你同事拿刀追著砍!為什麼?
因為這個版本存在非常嚴重的問題!看一下這個測試程式:
int main()
這個程式執行第1、2、3行沒有問題。第4行把s[0]修改為』h』,出問題了,因為過載的
函式返回的是char型別,是可讀不可寫的,如果給它賦值(a[0] = 『c』)編譯器會報錯。
第5行也有問題,顯然索引越界了。如果是陣列,索引越界會造成段錯誤,馬上引起程式崩潰。這其實是乙個相對較好的結果,因為我們可以迅速定位到程式崩潰的位置把這個bug修復掉。但是在我們的mystring類中記憶體是動態申請的,即使索引越界,編譯器編譯不會報錯,並且執行時也不會引起程式崩潰。但是它造成的後果更為嚴重,因為它會改寫其他記憶體位置的內容,破壞資料,造成莫名其妙的執行結果。這種bug找起來簡直要人命。
一行**引入了兩個bug,這是何等的臥槽啊!
要解決第乙個問題,函式必須返回乙個引用;要解決第二個問題,必須在函式中進行索引越界校驗。因此我們可以把過載函式改為下面這樣:
char& mystring::operator (const int index)
這個版本把返回值改為引用,同時在函式內部加了一行斷言。斷言可以檢查索引是否越界,如果越界就「溫柔」地結束程式,並列印出錯誤資訊。這樣可以大幅提高程式的健壯性。我們來測試一下看看:
int main()
執行這段**的結果是:
$ ./a.exe
hello,world
assertion failed: index >=0 && index < len, file mystring.cpp, line 93
可見執行到第4行,程式結束,並且列印出了結束時那**所在的檔案和行數,assert真是貼心呢!
string類提供了很多字串拼接操作,用起來非常方便。比如用a+="world"
就可以把"world"
字串拼接到a物件的末尾。我們也來實現一下這個功能。
我們定義兩個+=
函式,乙個支援拼接c風格字串,乙個支援拼接mystring物件。
mystring& operator+(const char* s);
mystring& operator+(const mystring& s);
實現這兩個函式需要注意一些問題:
如果傳入的字串過長,需要截斷。拼接後的字串長度不能超過max_size
如果this物件的空間足夠,那麼直接在原來的字串基礎上追加傳入的字串
如果this物件空間不夠,那麼需要重新申請空間,把原來空間的內容全部拷貝過來,然後在後面追加傳入的字串。
記得釋放原來的空間,並且把this物件的p指標指向新空間,更新size和len。
這兩個函式的實現如下:
// 過載+=
mystring& mystring::operator+= (const char* s)
while (size < len)
char *tmp = new char[size + 1];
strncpy(tmp, p, strlen(p));
tmp[strlen(p)] = '\0';
strncat(tmp, s, size - strlen(p));
tmp[size] = '\0';
delete p;
p = tmp;
return *this;
}mystring& mystring::operator+= (const mystring& s)
while (size < len)
char *tmp = new char[size + 1];
strncpy(tmp, p, strlen(p));
tmp[strlen(p)] = '\0';
strncat(tmp, s.p, size - strlen(p));
tmp[size] = '\0';
delete p;
p = tmp;
return *this;
}
這兩個函式的實現大部分是一致的,不再囉嗦了。現在,可以通過+=符號來拼接字串了,試一下看看!
int main()
這段**執行結果為:
$ ./a.exe
this is a test
this is a test program
this is a test program written by z.k.
嗯,看來可以正常工作! 如何實現乙個string類 1
string類是c 當中用的非常頻繁的乙個類,它提供了很多處理字串的函式,讓字串的使用變得像int float等built in型別一樣簡單。string類的實現包含了大量c 語言的知識,其中有很多值得討論的問題。自己動手實現乙個string類是學習c 語言的好方法,可以檢驗自己一下c 基礎知識掌握...
實現乙個string類
需要實現的基本功能 建構函式 拷貝建構函式 賦值函式 析構函式.以前合稱big three,現在叫做copy control 1 class string 1213 不簡潔版本 14string string const char str else 23 24 2526 string string ...
乙個string類的簡單實現
string類中使用到了賦值建構函式 複製建構函式 建構函式 預設建構函式 析構函式 過載操作符等一些類操作 class string string const char str string const char str,int n string const string src 拷貝建構函式 也...