本文主要針對(duì)那些有C語(yǔ)言背景知識(shí),,而現(xiàn)在開(kāi)始使用C++語(yǔ)言編程的程序員,。事實(shí)上,,C++繼承了大多數(shù)c語(yǔ)言的功能,但有些方面還是不得不要留意的,,如new和delete取代了malloc和free,且C++還使用了STL容器類來(lái)靜態(tài)或動(dòng)態(tài)地分配數(shù)組,。本文中要講的是用std::string來(lái)取代char*,將會(huì)演示C風(fēng)格數(shù)組帶來(lái)的一系列問(wèn)題,,及如何使用std::string來(lái)避免這些問(wèn)題。 避免“病態(tài)”的char數(shù)組聲明 當(dāng)聲明一個(gè)char數(shù)組時(shí),,許多程序員都會(huì)這樣做: char* name = "marius"; 乍看起來(lái)好像沒(méi)什么問(wèn)題,,但如果想讓字符串首字符大寫,最簡(jiǎn)單的實(shí)現(xiàn)方法是: name[0] = 'M'; 代碼生成時(shí)沒(méi)有問(wèn)題,,但在運(yùn)行時(shí)會(huì)崩潰,因?yàn)檫@是未定義的行為,,且依賴于編譯器的實(shí)現(xiàn)(在VS2005中,,可通過(guò)編譯,,但在運(yùn)行時(shí)會(huì)崩潰)。對(duì)此的解答是:“marius”是一個(gè)文字上的字符串,,且存儲(chǔ)于程序的數(shù)據(jù)區(qū),“name”只是一個(gè)指向數(shù)組的指針,,因?yàn)榇鎯?chǔ)字符串的數(shù)據(jù)區(qū)為只讀,所以不允許你修改它,。正確的聲明形式應(yīng)該像下面這樣: const char* name = "marius"; 這樣一來(lái),只要試圖修改其中的一個(gè)字符,,都會(huì)被編譯器發(fā)現(xiàn),并拋出一個(gè)錯(cuò)誤:cannot modify a constant variable,。 “令人討厭”的C風(fēng)格方法 可用char[]來(lái)定義一個(gè)定長(zhǎng)的字符數(shù)組: char name[] = "marius"; name[0] = 'M'; 在本例中,,name是一個(gè)7字符的數(shù)組(包括終止符),,其由字符串“marius”進(jìn)行初始化,,具有讀寫權(quán)限,。 現(xiàn)在,試著用strcat()銜接一個(gè)字符串: char name[] = "marius"; strcat(name, " bancila"); 但程序只要一運(yùn)行就會(huì)崩潰,,因?yàn)閟trcat不能確定緩沖區(qū)是否可以裝下追加的字符串,,導(dǎo)致數(shù)組越界破壞了內(nèi)存。 當(dāng)然了,,你也可聲明一個(gè)更大的數(shù)組來(lái)解決這個(gè)問(wèn)題,只要保證它能放下所有的字符就行了,,比如說(shuō),50個(gè)字符長(zhǎng)度應(yīng)該可以放下一個(gè)英文名了: char name[50] = "marius"; strcat(name, " bancila"); 這就行了,,但如果有Carlos Marìa Eduardo García de la Cal Fernàndez Leal Luna Delgado Galván Sanz這樣的名字呢,,而且這只是單個(gè)西班牙名,另外還有內(nèi)存空間浪費(fèi)的問(wèn)題,,如果聲明了100個(gè)字符長(zhǎng)度,平均使用只有20,,那一份十萬(wàn)個(gè)名字的列表,,要浪費(fèi)800萬(wàn)字節(jié)了。 動(dòng)態(tài)分配內(nèi)存 那么接下來(lái)就是尋找動(dòng)態(tài)分配內(nèi)存最合適的方法: char* name = new char[strlen("marius")+1]; strcpy(name, "marius"); 在此例中,,你可重新分配所需的內(nèi)存,,如下所示: char* temp = new char[strlen(name) + strlen(" bancila") + 1]; strcpy(temp, name); strcat(temp, " bancila"); delete [] name; name = temp; 這需要編寫及維護(hù)更多的代碼,另外,,在涉及到類時(shí),情況會(huì)變得更加復(fù)雜,。 確保類中內(nèi)存的正確處理 如果有一個(gè)Person類,它存儲(chǔ)了人名,,你的第一個(gè)反應(yīng)它可能會(huì)像下面這樣: class Person { char* name; }; 好像看上去沒(méi)什么問(wèn)題,,但這個(gè)類還應(yīng)有: ? 一個(gè)構(gòu)造函數(shù),它可以接受一個(gè)字符串來(lái)初始化name,; ? 一個(gè)自定義的拷貝構(gòu)造函數(shù),以確保深拷貝(默認(rèn)的拷貝構(gòu)造函數(shù)由編譯器提供,,它是淺拷貝,也就是說(shuō),,當(dāng)從一個(gè)對(duì)象中復(fù)制全部屬性的值到一個(gè)對(duì)象時(shí),它只復(fù)制了指針,,而不是指向的所有對(duì)象)。 ? 一個(gè)自定義的 operator= ? 一個(gè)析構(gòu)函數(shù),,負(fù)責(zé)清理動(dòng)態(tài)分配的內(nèi)存 把這些整合起來(lái)之后,Person類就會(huì)像下面這樣: class Person { char* name; public: Person(const char* str) { name = new char [strlen(str)+1]; strcpy(name, str); } Person(const Person& p) { name = new char [strlen(p.name)+1]; } Person& operator=(const Person& p) { if(this != &p) { delete [] name; name = new char [strlen(p.name)+1]; strcpy(name, p.name); } return *this; } ~Person() { delete [] name; } }; 還是std::string省事 標(biāo)準(zhǔn)模板庫(kù)(STL)提供了一個(gè)std::string類,,其是std::basic_string的一個(gè)特化,它是一個(gè)容器類,,可把字符串當(dāng)作普通類型來(lái)使用,并支持比較,、連接、遍歷,、STL算法,、復(fù)制,、賦值等等操作,這個(gè)類定義在<string>頭文件中,。 使用std::string的好處在于: 1、 易于分配,、復(fù)制及連接。 std::string name = "marius"; // 由賦值進(jìn)行初始化 name += " bancila"; // 連接 std::string copy = name; // 復(fù)制 2,、 可用length()或size()方法確定字符串的長(zhǎng)度,,這兩個(gè)方法是一樣的,,第二個(gè)方法只是為了保持STL容器類的一致性,。 std::string name = "marius"; std::cout << "length=" << name.length() << std::endl; std::cout << "length=" << name.size() << std::endl; 3、 檢查是否為空值,。 std::string name; if(name.empty()) std::cout << "empty string"; 4,、 支持比較。 if(name == "marius") { } if(name.compare("marius") == 0) { } 方法campre進(jìn)行大小寫敏感的比較,,以確定兩個(gè)字符串是否相等,或其中一個(gè)在詞典順序上小于另一個(gè),。它的返回值與strcmp()的返回值代表的意義一樣:負(fù)值表示操作數(shù)小于參數(shù)字符串,而正值表示操作系統(tǒng)數(shù)大于它,,0表示相等,。另外,,還有6個(gè)重載版本可允許比較字符串的某一部分: if(name.compare(0, 3, "mar") == 0) { std::cout << "match"; } 5,、 重載操作符 << 和 >>,可從流中讀寫字符串,。 std::string name; std::cin >> name; // 從控制臺(tái)中讀name std::cout << name; // 向控制臺(tái)寫name 6,、 易于訪問(wèn)字符串中的字符。 std::string name = "marius"; name[0] = 'M'; name[name.length()-1] = 'S'; 7,、 遍歷所有字符,這可由C風(fēng)格的索引或STL迭代子來(lái)完成(如果無(wú)需修改,,應(yīng)使用const_iterator)。 std::string name = "marius"; for(size_t i = 0; i < name.length(); ++i) std::cout << name[i]; for(std::string::const_iterator cit = name.begin(); cit != name.end(); ++cit) std::cout << *cit; for(std::string::iterator it = name.begin();it != name.end(); ++it) *it = toupper(*it); 8,、 刪除字符串的某一部分,。 std::string name = "marius bancila"; // 刪除第6個(gè)元素之后的所有東西 name.erase(6, name.length() - 6); 9、 在指定位置插入字符串或字符,。 std::string name = "marius"; // 在結(jié)尾插入 name.insert(name.length(), " bancila"); name.insert(name.length(), 3, '!'); 10,、在字符串結(jié)尾插入其他元素。 std::string name = "marius"; name.push_back('!'); 11,、兩個(gè)字符串值的快速交換。 std::string firstname = "bancila"; std::string lastname = "marius"; firstname.swap(lastname); std::cout << firstname << ' ' << lastname << std::endl; 12,、使用c_str()方法只讀訪問(wèn)其內(nèi)部字符數(shù)組緩沖區(qū),可在接受字符指針(是否const都行)作參數(shù)的函數(shù)中使用std::string對(duì)象,。 void print(const char* name) { std::cout << name << std::endl; } std::string name = "marius"; print(name.c_str()); void makeupper(char* array, int len) { for(int i = 0; i < len; ++i) array[i] = toupper(array[i]); } std::string name = "marius"; makeupper(&name[0], name.length()); 13,、使用STL算法 std::string name = "marius"; // 使字符串全為大寫 std::transform(name.begin(), name.end(), name.begin(),toupper); std::string name = "marius"; // 升序排列字符串 std::sort(name.begin(), name.end()); std::string name = "marius"; // 反轉(zhuǎn)字符串 std::reverse(name.begin(), name.end()); bool iswhitespace(char ch) { return ch == ' ' || ch == '/t' || ch == '/v' || ch == '/r' || ch == '/n'; } std::string name = " marius "; // 刪除空白字符 std::string::iterator newend = std::remove_if(name.begin(), name.end(), iswhitespace); name.erase(newend); 14、也可用頭文件<sstream>中的std::stringstream來(lái)構(gòu)建字符串,。 std::stringstream strbuilder; strbuilder << "1 + 1 = " << 1+1; std::string str = strbuilder.str(); 來(lái)回顧一下前面的Person類,,如果用std::string替換了char*,那么剩下的工作只需編寫一個(gè)構(gòu)造函數(shù)就行了,,其他的由編譯器來(lái)完成,在本例中,,復(fù)制字符串時(shí)使用了淺拷貝,,這足夠了,因?yàn)檫@個(gè)動(dòng)作觸發(fā)了std::string的operator=,,它會(huì)正確地復(fù)制字符串,。 class Person { std::string name; public: Person(const std::string& str) { name = str; } }; Person p1("marius"); // works because std::string has a constructor that takes a const // char* Person p2("bancila"); p1 = p2; 結(jié)論 本文既不是std::string的文檔,,也不是其輔導(dǎo)書,,只是懇求大家使用std::string。用標(biāo)準(zhǔn)模板庫(kù)中的std::string來(lái)取代C風(fēng)格數(shù)組可使代碼看上去更簡(jiǎn)潔,、更自然、更易于閱讀及維護(hù),,也不必?fù)?dān)心動(dòng)態(tài)內(nèi)存分配等問(wèn)題,由此可忽略一些不必要的細(xì)節(jié)問(wèn)題(如內(nèi)存管理),,而集中精力于編程的重要方面,試下吧,。 |
|
來(lái)自: 綠巨人大兵 > 《C 語(yǔ)言基礎(chǔ)》