本文原創,僅在部落格園釋出,若在其他平臺發現均為盜取!!!
-1、寫作目的
昨天我寫過一個版本的字串相關,淺談指標系列:https://www.cnblogs.com/jisuanjizhishizatan/p/15545229.html
這篇文章由於時間倉促,寫的比較水,因此今天有時間我來重置一篇,稍微寫詳細一點。
0、前言
字串一直是C++的一個難點。昨天某個同學問我,關於字串的比較問題:
char s[10];
...
if(s=="123456789")...
沒有語法錯誤,但是執行結果不太對。
大家可以在這個地方停一停,想想是為什麼。
1、字串的比較
1.1.歷史原因
最初,C語言剛剛面世的時候,還沒有我們現在經常寫C++時使用的string型別。當時的人們使用字串,必須使用char的陣列模擬字串。
例如,輸出hello world,是這樣寫的:
char s[]={'H','e','l','l','o',' ','w','o','r','l','d','\0'};
for(int i=0;i<11;i++)putchar(s[i]);
這樣的寫法是很麻煩的,對此,C語言使用了""運算子,以及printf的%s引數。
char s[]={"Hello world"};
printf("%s",s);
這裡注意,""運算子,返回的是一個char陣列,如果在C++這樣寫:
string s="hello";
其本質是string類的運算子過載,過載了賦值運算子=。而並非表示""返回的是string型別。
1.2.陣列
我之前應該寫過(不過我忘記在哪篇裡面提到過),陣列名在表示式中,某些情況是可以解釋做指標的。
也就是說,例如:
int a[10];
int *p;
p=a;
在這裡,a表示指標,也就是陣列a的首個元素的地址。
因此,執行這樣的比較:
s=="123456789"
本質是對s的地址和"123456789"的地址進行比較。
我們來看一個簡單的程式:
#include<bits/stdc++.h>
using namespace std;
char str[3];
int main(){
printf("%p\n",str);
printf("%p\n","abc");
printf("%d\n",sizeof("abcd"));
}
輸出在我的Dev c++環境內顯示是:
我們可以看到,"abc"字串常量也是儲存在記憶體中的,儲存在只讀儲存區中。並且,sizeof("abcd")返回5,表示"abcd"的本質是陣列(而不是char的指標,否則會返回4)
1.3.比較函式
既然直接比較s和字串常量會比較地址,那麼,我們只能逐字元進行比較了。
標準庫有個函式叫做strcmp,這個函式我們在下一章討論。
2、strcmp函式
2.1.函式規範
很多字串處理的函式都包含在<string.h>中。strcmp就是這樣的,使用前需要#include<string.h>。
strcmp用於比較字串的大小,只能比較char陣列,返回值如下:
strcmp(a,b);
/*
a=b 返回0
a<b 返回負數
a>b 返回正數
*/
對於部分處理環境,a<b返回-1,a>b返回1,但是如果僅僅對1和-1判斷,在部分環境還是過不去的,因為標準庫沒有這樣規定。
2.2.自制strcmp函式
int strcmp(char *s1,char *s2){
while(*s1==*s2 && *s1 && *s2){
s1++;s2++;
}
return *s1-*s2;
}
上面的strcmp函式是我自己寫的,其中使用了簡單的指標運算。
在這裡,s1和s2就是s1和s2指向的字元,如果字元相等,且兩個字元不為0,那麼就繼續進行比較,也就是*s1 && *s2
的含義。
s1-s2就是對字元作差,也就是對字元進行比較,如果大於返回正數,小於則返回負數。
2.3.指標運算
實際上,對指標進行加減運算(例如s1++一類的語句)叫做指標運算。在有些情況下,指標運算是晦澀難懂的,例如:
while(*p++);
相信大家都沒看懂這句話是什麼意思,實際這是strlen的實現程式碼中的一句,應該都沒想到吧。
下面是我仿照的strlen實現,不太容易看懂的版本
int strlen(char *s){
char *p=s;
while(*p++);
return p-s-1;
}
那麼,我們為什麼需要指標運算呢?首先,在很久以前的C語言中,遍歷陣列,需要使用到如下的語句:
for(int i=0;i<n;i++){
//對a[i]進行操作
}
之前也一定提到過,a[i]是*(a+i)的縮略形式,那麼,在迴圈體內,編譯器需要計算多次a+i。
但是,如果使用指標運算:
for(int *p=a;*p!=a+n;p++){
//對p進行操作
}
這樣,使用p來代替陣列中多次出現的a[i],a+i中的加法運算只會在結束時執行一次。
因此,在以前的C語言中,使用指標運算可以加快程式的執行速度。但是,現在已經不是這樣了。以下引用自《征服C指標》:
如今,編譯器在不斷地被優化,對於迴圈內部重複出現的表示式的集中處理,是編譯器優化的基本內容。對於現在一般的 C 編譯器,無論你使用陣列還是指標,效率上都不會出現明顯的差距。基本上都是輸出完全相同的機器碼。
總的來說,C 的指標運算功能的出現,源自於早期的 C 自身沒有優化手段。這一點並不奇怪,請大家回想一下在前面介紹過的內容,C 本來只是為了解決開發現場的人們眼前的問題而出現的一種語言。Unix 之前的 OS 幾乎都是使用匯編寫的,即使晦澀難懂,人們也不會大驚小怪。對於當時的環境,追求什麼編譯器優化實在有點勉為其難。因此,當初開發 C語言的時候,是完全有必要提供指標運算功能的。可是……
這應該就是大家認為“指標很難懂”或是“指標容易出錯”的根本原因,都是複雜的指標運算所致的。
當然,凡事都有特例,比如,在“一個巨大的 char 陣列中,參雜了各種型別的資料,並且我們試圖讀取第多少位元組的資料”這樣的情況下,還是使用指標運算寫的程式比較容易理解。
無論如何,作為一個C++的程式設計師,我們應該學會閱讀一些基本的指標運算。至於寫指標運算,在非特殊情況下,我們還是一般不要使用指標運算,這樣會降低程式的可讀性。
3、strlen函式
3.1.函式規範
strlen(s)返回s的長度,不計'\0',只能用於char陣列。
3.2.自制strlen
int strlen(char *s){
int ret;
for(ret=0;s[ret]!='\0';ret++);
return ret;
}
之前那個指標運算的strlen可能比較難懂,我們使用陣列再寫一個出來。這次,我們只對ret進行加法運算,如果s[ret]等於結束符'\0'表示字串結束,那麼就把他作為迴圈的條件。這樣使用for迴圈來寫,可能更加容易懂一些。
4、strcpy函式
4.1.函式規範
如果想要對字串進行拷貝,例如把char陣列的字串s賦值為"abc",那麼,
s="abc";
一句,如果s是陣列將會報錯,如果s為指標,那麼會使得s指向字串常量"abc",從而導致s是不可變的。
那麼,我們需要使用strcpy函式:
strcpy(s,"abc");
4.2.自制strcpy
void strcpy(char *dest,const char *src){
int i=0;
while(src[i]!='\0'){
dest[i]=src[i];
++i;
}
}
這次我們同樣沒有使用指標運算。事實上,不使用指標運算的程式,在長度上並沒有很長,使用指標運算也不太能夠使程式簡潔。
5、wchar_t
wchar_t是C95新增的一個功能,表示寬字元,可以儲存中文。對應的字串函式,需要使用wcscpy,wcslen等函式,使用方法與char類似。
這一部分只是簡略提到一下,例如如下的程式:
#include<bits/stdc++.h>
int main(){
wchar_t s[]= L"中文";
for(int i=0;i<wcslen(s);i++)printf("%d ",s[i]);
}
輸出ascii碼。注意,對於DEV C++編譯器,需要調整編譯選項,不然報錯:
今天我們講解了幾個常見的C語言字串函式,以及指標運算。
完。