本文主要針對(duì)那些有
C語(yǔ)言背景知識(shí),而現(xiàn)在開(kāi)始使用C++語(yǔ)言編程的程序員。事實(shí)上,C++繼承了大多數(shù)
c語(yǔ)言的功能,但有些方面還是不得不要留意的,如new和delete取代了malloc和free,且C++還使用了STL容器類(lèi)來(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)題,但如果想讓字符串首字符大寫(xiě),最簡(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)行初始化,具有讀寫(xiě)權(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;
這需要編寫(xiě)及維護(hù)更多的代碼,另外,在涉及到類(lèi)時(shí),情況會(huì)變得更加復(fù)雜。
確保類(lèi)中內(nèi)存的正確處理
如果有一個(gè)Person類(lèi),它存儲(chǔ)了人名,你的第一個(gè)反應(yīng)它可能會(huì)像下面這樣:
class Person
{
char* name;
};
好像看上去沒(méi)什么問(wèn)題,但這個(gè)類(lèi)還應(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類(lèi)就會(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類(lèi),其是std::basic_string的一個(gè)特化,它是一個(gè)容器類(lèi),可把字符串當(dāng)作普通類(lèi)型來(lái)使用,并支持比較、連接、遍歷、STL
算法、復(fù)制、賦值等等操作,這個(gè)類(lèi)定義在<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容器類(lèi)的一致性。
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)行大小寫(xiě)敏感的比較,以確定兩個(gè)字符串是否相等,或其中一個(gè)在詞典順序上小于另一個(gè)。它的返回值與strcmp()的返回值代表的意義一樣:負(fù)值表示操作數(shù)小于參數(shù)字符串,而正值表示
操作系統(tǒng)數(shù)大于它,0表示相等。另外,還有6個(gè)重載版本可允許比較字符串的某一部分:
if(name.compare(0, 3, "mar") == 0)
{
std::cout << "match";
}
5、 重載操作符 << 和 >>,可從流中讀寫(xiě)字符串。
std::string name;
std::cin >> name; // 從控制臺(tái)中讀name
std::cout << name; // 向控制臺(tái)寫(xiě)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";
// 使字符串全為大寫(xiě)
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類(lèi),如果用std::string替換了char*,那么剩下的工作只需編寫(xiě)一個(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)書(shū),只是懇求大家使用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)存管理),而集中精力于編程的重要方面,試下吧。