這篇文章首發於360doc http://www.360doc.com/content/21/0526/17/73755266_979099504.shtml ,其實360doc里面的那個也是我的帳號,發在那里沒什么人看,就發到這里來了。
文章原創,轉載需注明原作者。
前后總計兩個多月,總算是寫完了。完成日期:2021.8.1.
若本文有錯誤,請大家在評論區中指出,我會盡我所能加以改正。
目錄
第一章 前言和准備工作
1.1.前言
1.2.准備工作
第二章 string類函數——簡單版
2.1.最簡單的string
2.2.string類型的輸入和輸出
2.3.查找子串,插入,刪除和替換
2.4.at函數和size函數
第三章:運算符重載和構造函數
3.1.賦值運算符
3.2.“+”和“+=”的重載
3.3.比較運算符的重載
3.4.下標運算符的重載
3.5.構造函數
第四章:string類函數(續)
4.1.內存動態分配
4.2.append函數
4.3.begin和end迭代器
4.4.getline函數
第五章:拾遺
5.1.string.h和string
5.2.字符數組
5.3.string類的本質
第一章 前言和准備工作
1.1.前言
1.1.1.前言
C++語言相比C語言,比較大和好用的變化就是類(class)這一功能。這次,我們嘗試來根據類這一新功能,構造一些比較簡單的數據結構。這次,我們挑戰一下string這一類型。
1.1.2.string是什么
string是C++STL容器中自帶的一個庫,是關於字符串的。字符串的概念什么的這里就不細講了,比較簡單。有了string,在C++中,就可以更加方便的使用字符串了,相比較於C語言而言,不需要使用strcpy,strcmp等函數,直接使用運算符即可。它的讀入和輸出和cin,cout結合,也有專門的函數getline等。同時,string也可以和C語言的老版本字符串進行轉換,兼容一些老的函數。
1.2.准備工作
1.2.1.工具
一台電腦,一個C++編譯器(筆者這里使用的是Dev C++,各位讀者可以根據自己的喜好,例如Visual C++,CodeBlocks等)
1.2.2.學習資源
如果有不懂的地方,提供一些參考的在線學習資源。實際筆者也是從這里參考了很多用法。
http://c.biancheng.net/cplus/
https://www.runoob.com/cplusplus/cpp-tutorial.html
http://www.weixueyuan.net/cpp/rumen/
第二章:string類函數——簡單版
2.1.最簡單的string
string是一個類型,我們構造string類型,也就需要定義一個string類型。定義類型,我們一般在C語言會使用typedef struct的方式,我們這里既然用上了C++,就用一下C++的新功能——類。
類其實是一個類似於結構體的東西,把很多東西打包在一起。類中除了可以包含變量,同時也可以包含成員函數,構造函數,析構函數以及運算符重載等。這里,我們暫且先把這個字符串本身和他的大小信息包含在整個類里面。話不多說,我們看代碼。
class String{ public: char *str; int size; };
這里string的s是大寫為了避免和標准庫的string重名。可以看到,類的定義和結構體的定義類似。public說明這些成員是公共訪問的。
接下來,我們需要寫成員函數。我們先寫最簡單的也是最重要的init函數。事實上,string是沒有init的,但是在寫構造函數之前,我們先用最簡單的成員函數的形式寫一個init,用於初始化,給指針分配空間。(str是指針,為了使得str長度可變)
class String{ char *str; int size; void init(void){ str=(char*)malloc(100); } };
首先是成員函數的位置,寫在class里面。第二是寫法,無需指定所屬類,直接寫str即可。
如果比較了解C++特性的,可能會問為什么用malloc不用new。這里用malloc是為了方便擴大空間(擴大要用realloc),這樣數組就是可變的了。
本文之后為了方便和節省空間,就不把整個代碼貼出來了,只寫本次要寫的函數,大家注意一下函數的位置。
接下來,為了讓標准函數可以訪問(例如printf),需要一個函數用於把這個字符串轉換為C風格字符串,也就是c_str函數。
const char *c_str(void){ return str; }
這樣,以后用printf就方便了。
2.2.string類型的輸入和輸出
2.2.1.輸入
string自帶的是使用cin進行輸入,但是我們這里也沒法用cin,只能簡單的寫一個get函數來實現輸入。為了方便,get可以包含多種版本。各位讀者也可以根據自己的喜好來設計更多版本。
版本1 get()輸入字符串,空格停止
版本2 get(字符個數)輸入字符串,到達字符個數停止
版本3 get(停止字符)輸入字符串,輸入時遇到停止字符停止
C++有函數重載功能,多個函數只要參數不同,就會當作不同函數處理。C++編譯器會根據類型來判斷調用哪一個函數。
void get(void){ scanf("%s",str); } void get(int n){ fgets(str,n,stdin); } void get(char c){ int i=0; for(;;){ str[i]=getchar(); if(str[i]==c)break; ++i; } }
fgets函數是從文件讀入的函數,第二個參數用於指定字符總數。
多說一句,新版本的C++標准中是不支持gets函數的,因為不安全(容易數組越界),對於VC編譯器新增了gets_s函數,部分編譯器還是保留gets的,但是為了兼容性,建議把gets全部寫成fgets的形式。
2.2.2.輸出
字符串的輸出也有很多種方式。這里列出比較常見的兩種。
版本1 put()輸出字符串,遇到'\0’結束
版本2 put(字符數量)輸出字符串,到達指定數量結束
實現起來其實也很簡單。
void put(void){ for(int i=0;str[i]!='\0';i++) putchar(str[i]); } void put(int n){ for(int i=0;i<n;i++) putchar(str[i]); }
很簡單。兩個都是使用for循環來輸出,只不過循環條件不同,一個是不等於“\0”,另一個是小於n。
多說幾句,實際上,'\0’實際上就是0,所以也可以寫成str[i]!=0。但是根據習慣原因,我們一般寫作'\0’,表示這是一個字符串。但是,有人是這樣寫的:
str[i]!=NULL;
這樣寫就有點問題了。NULL其實也是0,但是一般用於指針較多。很多機器上面其實NULL在stdio.h是這樣定義的:
#define NULL ((void*)0)
這樣的話,0是void*類型的,而不是char類型的。所以,上面的代碼可能會報錯。C++對於類型轉換是很嚴格的。
2.3.查找子串,插入,刪除和替換
2.3.1.find函數
我們這一節來討論查找,插入,刪除和替換。想要閱讀這一節的話,最好有一點線性表的功底。
先說查找。標准庫的find,我們只用兩種。
find(String subs) find(int n,String subs)
當然,其實參數還可以是char*類型的c風格字符串。這里是一定要區分開來的,因為參數為c風格字符串的話,就不是sub.str而是直接寫成subs了。看到這里,我們其實知道,string和c風格字符串完全不是一個東西,(一個是類,一個是數組或者說是指針)必須寫兩種形式。
為了節約篇幅。這里只列出參數為String的東西了。至於參數為char*的話...把后面的.str刪掉即可。
int find(String subs){ for(int i=0;i<strlen(str)-strlen(subs.str);i++) if(strcmp(str+i,subs.str)==0)return i; return -1; }
反復查找,直到最后一個。避免比較時候數組越界,最終位置在str長度減去subs長度。
如果一直沒有,返回-1。實際上應該是string::npos,很多環境里面都定義為-1。
2.3.2.insert函數
insert函數用於往字符串里面插入一個字符串。函數的形式為:insert(插入位置,插入字串)。它將會在原字符串的插入位置后面插入字符串。例如。”abef”在第2個位置插入”cd”,結果為”abcdef”。
我們按照上面這個樣例,來分析一下算法。
首先,我們根據cd的長度,來把插入點后面的東西后移。假設插入點為ins,插入字符串為subs。
for(i=strlen(str)-1;i>ins;i--){ str[i+strlen(subs)]=str[i]; }
我們注意插入的操作是從后往前的移動。
然后,需要在最后放入’\0’這一結束符。應該是在往str[strlen(s)-1+t_size+1]這個位置。
然后,我們需要把字符串subs拷貝到這個位置去。但是,不可以用一般的strcpy,這樣會在最后放入一個’\0’,就結束了這個字符串,所以,我們不可以用strcpy。但是為了復制字符串,我們只能自制一個strcpy。因為沒有'\0’,就叫做nz_strcpy(no zero的縮寫)
void nz_strcpy(char *dest,const char *src){ while(*src!=0){ *dest=*src;++dest;++src; } } (中略) void insert(int ins,char *subs){ int i; ins--;//數組下標從0開始 int subs_size=strlen(subs);//取得subs長度,方便s元素后移 for(i=strlen(str)-1;i>ins;i--){ str[i+subs_size]=str[i];//移動元素 } str[strlen(str)-1+subs_size+1]='\0';//最終的結束符 nz_strcpy(str+ins+1,subs);//復制字符串 }
很簡單吧。這就是線性表插入的基本操作。
2.3.3.erase操作
erase(int p,int n)
刪除從p開始的n個字符。
void erase(int p,int n){ int front=p+1,rear=p+n; while(str[rear]!=’\0’){ str[front]=str[rear]; ++front;++rear; } str[front]=’\0’; }
我們使用覆蓋的方法,設置一頭一尾兩個指針,每次把尾指針的內容復制到頭指針,直到尾指針指向的字符為0。如果不為0,那么就繼續下一個字符。例如,把abcdefg的第三個字符到第五個字符刪除。我們用列表的方式來看一下。
1 2 3 4 5 6 7
a b c d e f g
a b F d e F g
a b f G e f G
a b f g 0 f g
其中,大寫字母表示頭指針和尾指針所在的位置。可以看到,把后面的字符逐個放到前面,最后添上0即可。因為添上了0,最后不用刪除,字符串自動結束。
2.3.4.replace操作
其實我感覺replace和insert非常類似。replace(int start,int end,char *str);把start至end的區間全部替換成str。相當於先刪除start-end的區間,然后再插入str。所以,偷懶的辦法如下。
void replace(int st,int en,char *str){ erase(st,en); insert(st,str); }
這樣做即可。
2.3.5.拾遺
事實上,類似於find,erase,insert,replace等函數的實現,實際上都有很多類型。就例如insert,這里就有大約七八種。
basic_string& insert (size_type p0 , const E * s); //在p0前面插入s basic_string& insert (size_type p0 , const E * s, size_type n); //將s的前n個字符插入p0位置 basic_string& insert (size_type p0, const basic_string& str); basic_string& insert (size_type p0, const basic_string& str,size_type pos, size_type n); //選取 str 的子串 basic_string& insert (size_type p0, size_type n, E c); //在下標 p0 位置插入 n 個字符 c iterator insert (iterator it, E c); //在 it 位置插入字符 c void insert (iterator it, const_iterator first, const_iterator last); //在字符串前插入字符 void insert (iterator it, size_type n, E c) ; //在 it 位置重復插入 n 個字符 c
(參考自http://c.biancheng.net/view/1449.html)
事實上,這些都使用了函數重載功能。如果要全部寫起來,比較麻煩,而且很多函數可能我們平時不會用到,本文只挑選了部分出來。下面講述一下對函數轉換的方法。
例如,
basic_string& insert (size_type p0 , const E * s, size_type n); //將s的前n個字符插入p0位置
這一個。
首先,對這類函數的編寫的步驟。第一步,對已知條件進行轉化,根據后面的參數得出要操作的字符串。例如,這里,根據s和n,我們需要對s取前n個字符,把結果存放入s。第二步,使用標准函數。我們把標准函數的代碼搬過來。
在例如,
basic_string& insert (size_type p0, size_type n, E c); //在下標 p0 位置插入 n 個字符 c
我們只需要先根據n和c,構建出要插入的字符串s,然后執行標准函數即可。
char s[n]; for(int i=0;i<n;i++)s[i]=c;
兩句話即可轉換。
關於其他的函數,我們可以參考筆者一開始給出的幾個網站進行了解,嘗試編寫出更多的函數。
2.4.at函數和size函數
2.4.1.at函數
聽前面的各種數組操作,有的人應該已經厭煩了吧。如果筆者這里繼續寫insert,erase,replace的各種新方法的話,估計各位又要犯困了。(笑)所以,這一節換換口味,講幾個簡單的函數:at和size。
at函數類似於取字符串的一個字符。一般我們更加常用的方法是用下標,但是下標涉及到運算符重載,比較復雜。所以,這里我們先進行at函數的制作。
char at(int i){ assert(i<=size); return str[i]; }
我們一般只會用到函數的第二句語句,第一句assert可能不太常用,這里我們就來講解一下。
assert(表達式);
如果表達式為真,不做任何操作。否則,如果表達式為0,那么就輸出異常。如果想看看assert效果的話,可以在程序里寫一個assert(0),看程序的反應。程序應該會輸出”asseration failed”一句話,然后直接終止運行。輸出的文字,根據環境不同,可能結果也會不同。
這里,為了不讓數組越界,這里就用了一個assert檢驗下標i是否小於等於size。
事實上,標准庫的at就有這個功能,筆者的dev c++環境會輸出這個。
terminate called after throwing an instance of 'std::out_of_range'
what(): basic_string::at: __n (which is 100) >= this->size() (which is 3)
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
2.4.2.size函數
其實寫到這里,筆者感覺之前的東西有問題。size之前做了成員變量,不可以做函數,所以這里必須先把size成員變量改掉。名字就叫做len吧。
class String{ int len;//這里! char *str; ... };
size很簡單,只需要調用strlen即可。
int size(){ return strlen(str); }
同樣,還有一個功能完全相同的函數length。
int length(){ return strlen(str); }
很簡單吧。
第三章:運算符重載和構造函數
3.1.賦值運算符
3.1.1.運算符重載
到這里,我們函數就做的差不多了。
我們一般使用的運算符,都是用自己的功能了。例如,+運算符是做加法。但是,對於字符串,+的作用完全不同,是字符串連接。所以,這就涉及到一個知識點,運算符重載。
為了方便,用成員函數很麻煩,所以,我們完全可以用運算符來完成這一功能,改變運算符自己的功能,叫做運算符重載。
運算符重載,在系統內部實際上是調用函數。例如,s=a+b,實際上就是s=a.operator+(b)。
operator+就是一個函數。
3.1.2.=的重載
與其啰啰嗦嗦說一大堆,不如自己動手去做做。
String operator=( String s){ strcpy(str,s.str); return *this; } String operator=(const char *s){ strcpy(str,s); return *this; }
運算符重載的一般格式如下:
類名 operator運算符(參數){ 操作; return *this; }
這是二元運算符的一般重載方式。
如果一個運算符R有X個參數,那么就稱R為X元運算符。例如*,/,=都是二元運算符。而+,-即可以做一元運算符(正負號),也可以做二元運算符(一般的加減法)。
例如,一個二元運算符R的參數為A,B,那么這個運算記作A.operatorR(B),當作一個成員函數使用。其實等號運算符重載的寫法為s.operator=(c)。而為了簡便,s.operator=(c)可以簡便寫做s=c。這是不是非常類似於真正的string類型了?
而return *this返回的是什么?this是一個指針,指向當前在操作的類對象。所以,我們需要執行return *this,返回當前對象,這樣才能正確執行我們的操作。
這樣,我們的函數就全部完成了。
3.1.3.運算符重載的廣泛運用
C++的標准輸入和輸出流中,都是用運算符重載來做的。
cin>>a;
cout<<p[i]<<endl;
中,<<和>>都是重載的運算符。此時,cin和cout肯定不是函數(因為函數不可以做計算),其實是一個變量。
<<符和>>符是左移位運算符和右移位運算符。至於cin和cout使用它的原因不知道,大概是為了看上去方便吧。
所以,上面的語句還可以寫成;
cout.operator<<(p[i]).operator<<(endl);
順便提一句,cout是ostream類的對象,cin是istream的對象。當然,本文不是講cin和cout的,是講string類型的,所以這里就不詳細描述了。
3.2.“+”和“+=”的重載
3.2.1.strcat函數
strcat是C語言的用於字符串連接的標准函數。
strcat(字符串1,字符串2)把字符串2連接到字符串1后面。
3.2.2.正題
String operator+(String s){ strcat(str,s.str); return *this; } String operator+=(String s){ strcat(str,s.str); return *this; }
加號運算符和“+=”運算符都可以起到字符串連接的作用。非常簡單。調用strcat即可。
3.3.比較運算符的重載
3.3.1.strcmp
慣例,我們先介紹c庫標准函數。
strcmp比較兩個字符串大小。
strcmp(a,b)
a>b 返回值>0
a<b 返回值<0
a=b 返回值=0
3.3.2.自制strcmp
這里先偏離正題,說說c庫函數strcmp的自制。
int strcmp(char *s1;char *s2){ while(*s1==*s2){ ++s1;++s2;//如果相等,就下一個字符 } return *s1-*s2;//這里一定是不相等的字符,相減即可 }
一開始的while循環不斷比較s1和s2,如果相等,指針++,指向下一個字符。
最后退出循環時,一定是不相等的,兩個數相減,用作差法比較大小。
但是有一個問題,如果字符串相等,那就會比較到數組后面去,使得數組越界。我們必須保證第一個循環條件為字符不等於'\0',也就是不結束。
int strcmp(char *s1,char *s2){ while(*s1==*s2 && *(s1+1)!=0 && *(s2+1)!=0){ ++s1;++s2; } return *s1-*s2; }
保證下一個字符不等於0,這樣做就可以了。
3.3.3.比較運算符的重載
比較運算符主要有大於,小於,等於三個。我們同樣按照strcmp來進行重載,單手注意返回值變成了真和假。
bool operator>(String s){ if(strcmp(str,s.str)>0))return 1; else return 0; } bool operator<(String s){ if(strcmp(str,s.str)<0))return 1; else return 0; } bool operator==(String s){ if(strcmp(str,s.str)==0))return 1; else return 0; }
當然,我們還需要考慮參數為char*的字符串的情況,這里就略了。
制作起來非常簡單,只需要調用strcmp即可。多說一下,如果是C語言用多的人可能不知道bool是什么,其實bool是一種特殊的1字節變量,只能存放1和0,表示真和假。邏輯運算符的返回就是真假。如果給bool類型賦值為任意一個非0值,那么視作賦值為1。所有不等於0的值都看作為真,這就是可以把if(a!=0)寫作if(a)的原因。
3.3.4.compare函數
compare也是一個string的成員函數,也用於比較字符串大小。我們只看代碼,猜一猜它的功能。
bool compare(String s){ if(strcmp(str,s.str)==0)return 1; else return -1; }
3.4.下標運算符的重載
3.4.1.什么是下標
我們之前在學習數組的時候,應該看到過這樣的描述:
數組中每一個數都有一個編號,這個編號就是下標。
我們在引用數組元素時,經常用到s[i]這種寫法,實際上i就是一個下標,而[]就是下標運算符。
3.4.2.下標運算符的重載
首先我們要搞清楚一點,下標是可以作為一個左值來進行賦值的。也就是說,我們是可以這樣寫的:s[i]='a’
而一般的函數是不能這樣寫的:s.at(i)='a’
所以,如何讓這個下標運算符可以被賦值是一個問題。我們先拋開這個問題,來寫程序。
char operator[] (int i){ return str[i]; }
好像很簡單...等等!如果寫一個類似於s[i]='a’的語句,會報錯嗎?
error: lvalue required as left operand of assignment
也就是說,這里是不可以作為一個左值來賦值的。因為這樣的語句,函數返回的是一個常量a,所以這個語句也是表示’a’='a’,是非法的。所以,我們需要在函數聲明的地方動點手腳。
char &operator[] (int i){ return str[i]; }
在前面加上&符號即可。&符號這里不是取地址的意思,而是表示引用。例如,交換兩個變量的值,C++語言可以用引用的方法,這樣寫。
void swap(int &a,int &b);
這樣,如果把a和b傳遞過去,形參就是對實參的引用,就實現了交換。同理,用引用的方式,就可以賦值了。
3.5.構造函數
3.5.1.構造函數
構造函數是在定義的時候執行的函數。例如,我們初始化string類型,一定看見過這樣的語句:
string s("abcd")
在定義的時候初始化。
3.5.2.寫法
構造函數沒有返回值,且構造函數的名稱與類名一致。
String(const char *s){ strcpy(str,s); }
但是注意構造函數只要定義了,我們就需要在初始化時寫上構造函數,不然會出錯。
String s;//錯誤
String s("");//正確
為了方便,我們寫一個空構造函數。
String(void){ }
這樣,在不寫括號的情況下,c++會自動執行空函數,也就是說,初始化時,不加括號默認執行空構造函數。
趁熱打鐵,還有幾個構造函數,我們一起實現一下。
String(int n,char c){ for(int i=0;i<n;i++) str[i]=c; }
初始化為若干相同字符。
不過突然想到一個問題,在初始化調用構造函數時,還沒有init,所以str是一個未初始化的指針,會出錯,構造函數也沒有意義了。我們需要把init的過程寫進構造函數里面,至於size先定義為100吧,如果不夠再realloc...等等!用new分配的空間無法realloc,我們還是用stdlib.h庫中的malloc分配,這樣還能realloc。這樣一來,init函數就沒用了,可以把它刪掉了,而且也更像真正的string類型了。
操作非常簡單,在每一個構造函數加上一句str=(char*)malloc(100);即可。運行結果一樣,真不錯。
第四章:string類函數(續)
4.1.內存動態分配
之前運算符重載的所有內容都講完了。我們換個口味,繼續說函數。
首先我們要知道,str=(int*)malloc(100)指給str分配100個空間。一開始,我們沒有寫成在類中直接寫char str[100]而是特意在構造函數這樣寫,就是為了以后可以重新擴大內存。現在,我們需要在內存不夠時重新擴大內存。
擴大內存,我們不能用new,必須使用realloc函數用於分配過的指針再次分配。所以,我們需要在string中新增一個成員變量size,說明現在字符串有幾個空間,以及變量free,就是size-strlen(str),說明剩余多少內存可用。
class String { public: char *str; int size; ... ... void insert(int ins,String t){ int tmp=strlen(str)+strlen(t.str); if(tmp>size){ str=(char*)realloc(str,tmp); size=tmp; } ... } String operator +=(String s){ if(strlen(s.str)+strlen(str)>size){ str=realloc(str,strlen(s.str)+strlen(str)); size=strlen(s.str)+strlen(str); } ... } String operator =(String s){ if(strlen(s.str)>size){ str=realloc(str,strlen(s.str)); size=strlen(s.str); } ... } };
這里在每一個函數之前都加上了字符長度判斷,至於構造函數只需要加上size的賦值即可,不用多說。
4.2.append函數
4.2.1.append函數簡介
在字符串后面追加字符串。
本文在這里只簡單地寫兩種函數重載。第一種:直接在string的后面加上string(或char*字符數組)
第二種:在string的后面加上string的前n個字符。
4.2.2.編寫函數
介紹完了,我們開始正式編寫。首先我們找到函數的結束位置,然后在結束位置后面把需要的字符串拷貝過來即可。

上圖是一張簡圖,表示函數的執行方式。
如果需要指定所加上的字符總數,那么我們使用strncpy就可以了。
void append(String s){ int pos; char *t=str; for(pos=0;t[pos]!='\0';++pos){} strcpy(t+pos,s.str); } void append(String s,int n){ int pos; char *t=str; for(pos=0;t[pos]!='\0';++pos){} strncpy(t+pos,s.str,n); }
4.3.begin和end迭代器
begin指向第一個字符,end指向最后一個字符。於是本節就這樣寫完了。
char begin(void){ return str[0]; } char end(void){ int i; for(i=0;str[i]!='\0';i++){} return str[i-1]; }
4.4.getline函數
getline讀入一整行。
void getline(istream cin,string s){ int i; for(i=0;s[i]!='\n';i++)s[i]=getchar();//讀入到行末尾 }
其實函數參數中的那個cin根本沒有用到,只是裝裝樣子而已。(cin定義在iostream中,是istream類型的變量)
第五章:拾遺
5.1.string.h和string
有些人根本不知道下面三個頭文件的區別,經常在使用的時候搞混。
#include<string.h> #include<cstring> #include<string>
解釋:
string.h是老版本C語言的頭文件,cstring是新版本C++新增加的頭文件,功能等同於string.h,包含的是字符數組(char*)相關的函數(例如strcpy,strcmp,strlen等)。
string是C++的頭文件,包含的是string類(即string,本文寫的那些函數)只有使用這個函數,才可以寫那些有關string的代碼。例如:
#include<iostream> #include<string> string s; int main(){ cin>>s; cout<<s.size(); }
其中定義的string類型和size函數都是這個頭文件里面的。
到現在我還見到很多人在這種時候#include<cstring>,在部分編譯器會編譯錯誤!
5.2.字符數組
5.2.1.字符數組常識
上一節我們說了string.h,那么老版本的字符數組如何使用?
字符串在這種情況下是一個數組。
char s[100];
如果需要對這個數組做輸入和輸出,可以使用scanf或printf的%s參數。cout可以輸出字符數組,但是cin不能輸入字符數組。
原因:
cout<<"hello";
用雙引號括起來的都是字符數組。雖然在使用string的時候,新的string可以和字符數組混用,但是這是兩個東西。(所以本文中,所有函數都要寫兩遍,一遍是針對string的,一遍是針對字符數組的)
5.2.2.進階——字符數組和指針
請看兩種數組的聲明,有什么區別?
char *s="abc"; char s[10]="abc";
不妨嘗試執行下面的代碼:
int main(){ s[0]='1'; cout<<s; }
運行結果:
上面的聲明:segmentation fault(即runtime error,運行時崩潰錯誤)
下面的聲明:輸出1bc
可以看到,使用指針進行初始化,得到的是一個字符串常量。但是使用數組進行初始化,效果相當於拷貝字符串。(只有在這種情況下'='可以做拷貝字符串的作用)
(2021.8.8補充:
使用指針形式初始化是使得這個指針指向那個abc,也就是指向字符串常量。而對於字符串常量,內存是受到特殊保護的,只能讀取不能寫入,因此會發生錯誤。
而使用數組的形式,是對字符串進行拷貝操作,數組所在的內存區域沒有受到特殊保護,因此可以寫入。)
並且,使用指針形勢初始化時,會出現一個warning。
warning: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
5.3.string類的本質
終於寫到最后一節了,最后我們來討論string類的本質。
本質非常簡單,這就是一個普通的類,和其他的stack,queue沒有什么區別,就是一個數組+一堆成員函數。
因此,也是5.2中的結論,雙引號括起來的不是string,而是const char*字符串。因此,string中,成員函數的參數,定義了兩種形式,一種string,一種字符數組。使用上去,就像是string和字符數組完全通用。string的本質,還是一個普通的類。
當然,如果C++允許重載雙引號的話(雙引號也不是運算符),string估計就可以完全和字符數組通用了。就像這樣:
String operator ""(const char *s){ String Str=s; return Str; }
雙引號也不是運算符,上面的只是瞎寫而已,如果真的要完全通用的話...就算到了宇宙毀滅,這也是不可能的吧。
(全文完,總字數7608字)

