固然,最基本的您已經知道了:字符串(character string)是以空字符(\o)結尾的char數組。所以,您所學的數組和指針就能夠用在字符串上。可是因爲字符串的使用很是普遍,C提供了不少專爲字符串設計的函數。本章將討論字符串的特性、聲明和初始化方法、如何在程序中輸入輸出字符串,以及字符串的操做。swift
程序清單11.1 string.c程序數組
//string.c --使用字符串與用戶交互 #include <stdio.h> #define MSG "You must have many talents. Tell me some. " #define LIM 5 #define LINELEN 81 //最大字符串長度加1 int main(void) { char name[LINELEN]; char talents[LINELEN]; int i; const char m1[40]="Limit yourself to one line's worth. "; //初始化一個大小已肯定的char數組 const char m2[]="If you can't think of anything,fake it. ";//讓編譯器計算數組大小 const char *m3="\nEnough about me - what's your name? "; //初始化一個指針 const char *mytal[LIM]={"Adding numbers swiftly", "Multiplying accurately","Stashing date", "Following instructions to the letter", "Understanding the C language"}; //初始化一個字符串指針的數組 printf("Hi!I'm clyde the computer. "" I have many talents.\n"); printf("Let me tell you some of them.\n"); puts("What were they? Ah,yes,here's a partial list. "); for (i=0;i<LIM;i++) puts(mytal[i]); //打印計算機功能的列表 puts(m3); gets(name); printf("Well,%s,%s\n",name,MSG); printf("%s\n%s\n",m1,m2); gets(talents); puts("Let's see if I've got that list: "); puts(talents); printf("Thanks for the information,%s.\n",name); return 0; }
11.1.1 在程序中定義字符串函數
閱讀程序清單11.1時您可能已經注意到,定義字符串的方法不少。基本的辦法是使用字符串常量、char數組、char指針和字符串數組。程序應該確保有存儲字符串的地方,這一點咱們稍後也會討論到。spa
1、字符串常量設計
/*quotes.c 把字符串看做指針*/ #include <stdio.h> int main(void) { printf("%s, %p, %c\n","we","are",*"space farers"); return 0; }
%s格式將輸出字符串we。%p格式產生一個地址,所以,若是「are」是一個地址,那麼%p應該輸出字符串中第一個字符的地址(ANSI以前可能用%u 或 %lu)。最後,*「space farers」應該產生所指向的地址中的值,即字符串「space farers」中的第一個字符。真的是這樣嗎?下面是輸出結果:指針
we, 0x0040c010, scode
2、字符串數組及其初始化orm
定義一個字符串數組時,您必須讓編譯器知道它須要多大空間。一個辦法就是指定一個足夠大的數組來容納字符串,下面的聲明用指定字符串中字符數初始化一個數組m1:ip
const char m1[40] = "Limit yourself to one line's worth. " ;內存
const代表這個字符串不能夠改變。
注意標誌結束的空字符。若是沒有它,獲得的就只是一個字符數組而不是一個字符串。
指定數組大小時,必定要確保數組元素數比字符串長度至少大1(多出來的一個元素用於容納空字符)。
未被使用的元素均被自動初始化爲0.這裏的0是char形式的空字符,而不是數字字符0.
一般,讓編譯器決定數組大小 是很方便的。回憶一下,在進行初始化聲明時若是活力了數組大小,則該大小由編譯器決定。
const char m2 [ ] = "If you can't think of anything,fake it . ";
初始化字符數組是體現由編譯器決定數組大小 的優勢的又一個例子。這是由於字符串處理函數通常不須要知道數組的大小,由於它們可以簡單的經過查找空字符來肯定字符串的結束。
請注意程序必須爲數組name明確分配大小:
#defeine LINELEN 81
......
char name [LINELEN];
因爲直到程序運行時才能讀取name的內容,因此除非您說明,編譯器沒法預先知道須要爲它預留多大空間。聲明一個數組時,數組 的大小必須爲整形常量,而不能是運行時獲得的變量值。
和任何數組同樣,字符數組名也是數組首元素的地址。所以下面的式子對於數組m1成立:
m1 == &m1[0] , *m1 == 'L' , and *(m1+1) == m1[1] == 'i'
的確,可使用指針符號創建字符串。例如,程序清單11.1中使用了下面的聲明:
const char *m3 = "\nEnough about me - what's your name? " ;
這個聲明和下面的聲明的做用幾乎相同:
char m3[ ] = "\nEnough about me - what's your name? " ;
上面兩個聲明m3是一個指向給定字符串的指針。在兩種狀況下,都是被引用的字符串自己決定了爲字符串預留的存儲空間的大小。儘管如此,這兩個形式並不徹底相同。
3、數組和指針
那麼,數組和指針形式的不一樣之處是什麼呢?
數組形式(m3 [ ])在計算機內存中被分配 一個有38個元素的數組,每一個元素都被初始化爲相應的字符。一般,被引用的字符存儲在可執行文件的數據段部分;當程序被加載到內存中時,字符串也被加載到內存中。被引用的字符串被稱爲位於靜態存儲區。可是在程序開始運行後才爲數組分配存儲空間。這時候,把被引用的字符串複製到數組中。此後,編譯器會把數組 名m3看做是數組首元素&m3[0]的同義詞。這裏重要的一點是,在數組形式中m3是個地址常量,您不能更改m3,由於這意味着更改數組存儲的位置(地址)。可使用運算符m3 + 1 來標識下一個元素,可是不容許使用++m3。增量運算符只能用在變量名前,而不能用在常量名前。
指針形式(*m3)也在靜態存儲區爲字符串預留38個元素的空間。此外,一旦程序開始執行,還要爲指針變量m3另外預留一個存儲位置,以在該指針變量中存儲字符串的地址。這個變量初始時指向字符串的第一個字符,可是它是能夠變的。所以,能夠對它使用增量運算符。例如,++m3將指向第二個字符E。
總之,數組初始化是從靜態存儲區把一個字符串複製給數組,而指針初始化只是複製字符串的地址。
這些區別重要與否,主要取決於作什麼。
4、數組和指針的差異
咱們研究一下初始化一個存放字符串的數組和初始化一個指向字符串的指針這二者的不一樣(指向字符串實際上是指向字符串的第一個字符)。例如,考慮下面的兩個聲明:
char heart [ ] = "I love Tillie!" ;
char *head = "I love Millie!" ;
主要差異在於,數組名heart是個常量,而指針heart是個變量。實際使用中又有什麼不一樣呢?
首先,二者均可以使用數組符號:
for(i=0;i<6;i++) putchar(heart[i]); putchar('\n'); for(i=0;i<6;i++) putchar(head[i]); putchar('\n');
如下是輸出:
I love I love
其次二者均可以使用指針加法:
for(i=0;i<6;i++) putchar(*(heart+i)); putchar('\n'); for(i=0;i<6;i++) putchar(*(head+i)); putchar('\n');
輸出不變
可是,只有指針可使用增量運算符:
while(*(head)!='\o') putchar(*(head++));
產生以下輸出: I love Millie!
假定但願head與heart相同,能夠這樣作:
head = heart ; /*如今head指向數組heart*/
可是不能這樣作:
heart = head ; /*非法語句*/
賦值語句的左邊必須是一個變量或者更通常的說是一個左值(lvalue),好比*p_int。順便提一下,head = heart;不會使Millie字符串消失,它只是改變了head中存儲的地址。可是,除非已在別處保存了「I love Millie!"的地址,不然當head指向另外一個地址時就沒有辦法訪問這個字符串了。
能夠改變heart中的信息,方法是訪問單個的數組元素:
heart[7] = 'M' ; 或者 *(heart + 7) = 'M' ;
數組的元素是變量(除非聲明時帶有關鍵字const),可是數組名不是變量。
讓咱們回到對指針初始化的討論:
char * word = "frame" ;
能夠用指針改變這個字符串嗎?
word[1] = 'l' ;
您的編譯器可能會容許上廁所狀況,但按照當前的C標準,編譯器不該該容許這樣作。這種語句可能會致使內存訪問錯誤。緣由在於編譯器可能選擇內存中的同一個單個的拷貝,來表示全部相同的字符串文字。例如,下面的語句都指向字符串「Klingon"的同一個單獨的內存位置。
char * p1 = "Klingon" ; p1[0] = 'F' ;//ok? printf("Klingon"); printf(": Beware the %ss!\n","Klingon");
這就是說,編譯器能夠用柵的地址來替代每一個「Klingon"實例。若是編譯器使用這種單個拷貝法而且容許把p1[0]改成‘F‘的話,那將影響到全部對這個字符串的使用。因而,打印字符串文字「Klingon"的語句將爲顯示爲「Flingon"。
所以,建議的作法是初始化一個指向字符串文字的指針時使用const修飾符:
const char * p1 = "Klingon" ; //推薦作法
用一個字符串文字來初始化一個非const數組,則不會致使此類問題,由於數組從最初的字符串獲得了一個拷貝。
5、字符串數組
字符串的初始化遵循數組初始化的規則。花括號裏那部分的形式以下:
{{...},{...},...,{...}} ;
省略號表明咱們懶得鍵入的內容。關鍵之處是第一對雙引號對應着一對花括號,用於初始化第一個字符串指針。第二對雙引號初始化第二個指針,等等。相鄰字符串要用逗號隔開。
另外一個方法就是創建一個二維數組:
char mytal_2[LIM] [LINLIM] ;
在這裏mytal_2是一個5個元素的數組,每個元素自己又是一個81個char的數組。在這種狀況下,字符串自己也被存儲在數組裏。二者差異之一就是第二種方法選擇創建了一個全部行的升序都相同的矩形(rectangular)數組。也就是說,每個字符串都用81個元素來存放。而指針數組創建的是一個不規則的數組,每一行的長度由初始化字符串決定:
char * mytal [LIM] ;
這個不規則數組不浪費任何存儲空間。
另一個區別就是mytal和mytal_2的類型不一樣:mytal是一個指向char的指針的數組,而mytal_2是一個char數組的數組。一句話說,mytal存放5個地址,而mytal_2存放5個完整的字符數組。
11.1.2 指針和字符串
絕大多數的C字符串操做事實上使用的都是指針。例如,考慮一下程序清單11.3所示的用於起到指示做用的程序。
程序清單 11.3 p_and_s.c程序
/* p_and_s.c --指針和字符串 */ #include <stdio.h> int main (void) { char * mesg = "Don't be a fool! "; char * copy; copy = mesg; printf("%s\n",copy); printf("mesg = %s; &mesg = %p; value = %p\n", mesg,&mesg,mesg); printf("copy = %s; © = %p; value = %p\n", copy,©,copy); return 0; }
輸出結果以下:
Don't be a fool! mesg = Don't be a fool! ; &mesg = 0022FF4C; value = 00403024 copy = Don't be a fool! ; © = 0022FF48; value = 00403024
首先,mesg和copy以字符串形式輸出(%s)。這裏並無發生奇怪的事。
每一行的下一項是指定指針的地址。mesg和copy這兩個指針分別存放在位置0022FF4C和0022FF48。
注意最後一項,即value。它是指定指針的值。指針的值是該指針中存放的地址,能夠看到mesg指向00403024,copy也是如此。所以,字符串自己沒有被複制。語句copy = mesg ;所作的事情就是產生指向同一個字符串的第二個指針。
爲何如此謹慎行事?爲何不乾脆複製整個字符串?好了,問一下本身哪種方式更有效率?複製一個地址仍是複製50個單個的元素?一般只有地址纔是程序執行所須要的。