字符串可以分配到內存的不同區域,通常使用指針來支持字符串操作。字符串是以ASCII字符NUL結尾的字符序列。ASCII字符NUL表示為\0。字符串通常存儲在數組或者從堆上分配的內存中。不過,並非所有的字符數組都是字符串。例如,字符數組可能沒有NUL字符。
C中有兩種類型的字符串。
* 單字節字符串。由char數據類型組成的序列。
* 寬字符串。由wchar_t數據類型組成的序列。
wchar_t數據類型用來表示寬字符串,可能是16位或32位寬。這兩種字符串都以NUL結尾。寬字符主要用來支持非拉丁字符集,對於支持外語的應用程序很有用。
字符串的長度是除了NUL字符之外的字符數。為字符串分配內存時,要記得為NUL字符預留空間。注意NUL是一個字符,定義為\0,與NULL(void*(0))不一樣。NULL用來表示一種特殊的指針。
字符常量是單引號引起來的字符序列。字符常量通常由一個字符組成,也可以包含多個字符,如轉義字符。在C中,它們的類型是int。char的長度是1字節,而一個字符字面量的長度是 sizeof(int) 個字節。
printf("%d\n", sizeof('a'));//4
聲明字符串的方式有3種:字面量、字符數組、字符指針。字符串字面量是用雙引號引起來的字符序列,常用來進行初始化,它們位於字符串字面量池中。如果聲明一個擁有32個字符的數組,那麼只能放31個字符串文本,因為字符串要以NUL結尾。字符串在內存中的位置取決於聲明的位置。
定義字面量時通常會將其分配在字面量池中,多次用到同一個字面量時,池中通常只有一份副本。通常認為字面量是不可變的。你可以關閉字面量池來生成多個副本。在哪裡使用字符串字面量不重要,它沒有作用域的概念。
有些編譯器允許修改字符串,因此把不希望被修改的字符串聲明為常量是個不錯的選擇。
字符串初始化方式取決於變量被聲明為數組還是指針,字符串所用的內存要麼是數組要麼是指針指向的一塊內存。我們可以從字符串字面量或其它地方(比如標准輸入)得到字符。
char head[] = "hello man"; printf("size of head is : %d\n",sizeof(head)); //size of head is : 10
可以看到 "hello man"有9個字符,但是長度是10,因為有NUL字符。還可以strcpy函數初始哈數組。
char head1[10]; strcpy(head1,"hello man");
注意不要用數組的名字作為左值。
動態內存分配可以提供更多靈活性,也可能讓內存存在更久。通常使用malloc和strcpy來初始化字符串。
char* head2;
head2 = (char*) malloc (strlen("hello man")+1); strcpy(head2,"hello man");
注意在使用malloc決定所需內存長度時,要為NUL預留空間,並且使用strlen而不是sizeof。sizeof會返回數組和指針的長度,而不是字符串的長度。如果用字符串字面量來初始化指針會導致指針指向字符串字面量池。注意不要把字符字面量賦給指針,因為它是int類型。可以把字符字面量賦給解引後的指針。
*(head2 + 7) = 'e'; printf("head2 is %s\n", head2); //head2 is hello men
總之字符串可能位於全局或靜態內存(global or static array),也可能位於字符串字面量池({……}),可能位於堆上(malloc),可能位於函數的棧幀裡(char array[])。字符串的位置決定它能存在多久以及哪些程序可以訪問它。全局內存的字符串會一直存在,可以被多個函數訪問;靜態字符串也一直存在,但只有定義它的函數能訪問;堆上的字符串可以被多個函數訪問,直到被釋放前都存在。
比較字符串的標准方法是strcmp函數。其原型如下。
int strcmp(const char* s1, const char* s2);
如果兩個字符串相等,返回0。s1大於s2,返回正數。s1小於s2,返回負數。
char command[16]; printf("enter a command :"); scanf("%s",command); if(strcmp(command,"quit")==0)
{ printf("you typed quit!\n"); } else { printf("i don't know what you typed!\n"); }
注意如果此處使用if(command == "quit")的話,被比較的實際是command的地址和字符串字面量的地址。
復制字符串通常使用strcpy函數實現,其原型如下:
char* strcpy(char* s1, const char* s2);
有一類應用程序會讀入一系列字符串,挨個存入占最少內存的數組。先創建一個足夠長的字符串數組,其長度足以容納用戶允許輸入的最長字符串,然後把字符串讀入這個數組。有了讀取的字符串,我們就能根據字符串的長度分配合適的內存。
char mynames[32]; char* myname[30]; size_t count = 0; printf("enter a name please:"); scanf("%s",mynames); myname[count] = (char*) malloc (strlen(mynames)+1); strcpy(myname[count], mynames); count++;
可以在一個循環裡重復這個操作。
兩個指針可以引用同一個字符串。兩個指針引用同一個地址稱為別名。把一個指針賦值給另一個指針只是復制了字符串的地址而已。
字符串拼接涉及兩個字符串的合並。通常用strcat來執行這種操作。這個函數的原型為:
char* strcat(char* s1, const char* s2);
下面是如何使用緩沖區拼接字符串。
char* error = "ERROR: "; char* errormsg = "not enough memory!"; char* _buffer = (char*) malloc (strlen(error) + strlen(errormsg) +1); strcpy(_buffer, error); strcpy(_buffer, errormsg); printf("%s\n", _buffer); printf("%s\n", error); printf("%s\n", errormsg);
如果直接使用strcpy(error, errormsg)的話,可能會覆蓋error字符串字面量地址後面某些未知的內容,因為我們沒有為新的字符串分配獨立內存。拼接字符串常見的錯誤就是沒有為新字符串額外分配空間。此外注意不要使用字符字面量代替字符串字面量作為該函數的參數。
先定義一個函數。
size_t strLength(char* string){ size_t length = 0; while(*(string++)){ length++; } return length; } char simpleArray[] = "simple string"; char* simplePtr = (char*) malloc (strlen("simple string")+1); strcpy(simplePtr, "simple string"); printf("%d\n", strLength(simplePtr));//13
對指針調用這個函數,只需要傳入指針的名字。對數組使用該函數也可以這麼做。在這裡數組的名字被解釋成了地址。
printf("%d\n", strLength(simpleArray));
你也可以對數組的0下標使用取地址操作符,但是那樣太繁瑣了:strLength(&simpleArray[0])。
把參數聲明為指向字符常量的指針,可以防止字符串被修改。假如要讓函數返回一個由該函數初始化的字符串,必須決定是否由函數調用者負責釋放分配的內存。如果在函數內部動態分配內存並返回指向該內存的指針,那麼調用者必須負責最終釋放該內存,這要求調用者必須清楚函數的使用方法。
main函數通常是應用程序第一個執行的函數。對基於命令行的程序來說,通過為其傳遞某些信息來打開某些功能的開關很常見。比如linux下的ls命令會通過接受 -la 等參數來執行不同行為。C通過argc和argv參數支持命令行參數。第一個參數argc是一個整數,用來指定傳遞參數的數量。系統至少會傳遞一個參數,這個參數是可執行文件的名字。第二個參數argv,通常被看做字符串指針的一維數組,每個指針引用一個命令行參數。
int main(int argc, char** argv){ int i =0; while(i<argc){ printf("argv[%d]is %s\n",i,argv[i]);//argv[0]is ./mysender i++; } }
可以看到,不附加任何參數,默認自帶的一個參數就是 ./mysender,是我的編譯後文件的名字。試試用以下方式執行:./mysender -f jack -la limit = 100,此時輸出為:
argv[0]is ./mysender argv[1]is -f argv[2]is jack argv[3]is -la argv[4]is limit argv[5]is = argv[6]is 100
由於我把 "=" 符號用空格分開了,結果 "=" 被當做一個參數了。實際上應該用空格把每個參數分開,參數本身不應包含空格了。
函數返回字符串時,返回的實際是字符串的地址。這可能是字符串字面量的地址,可能是動態內存的地址,可能是本地字符串變量的地址。
先看第一種情況。對於靜態的指向字符串字面量的指針,應該注意在不同地方重復使用會覆蓋上一次的結果。字符串並非總是被看做常量,可以通過命令關閉字符串常量池,把字符串聲明為常量可以防止字符串被修改。如果返回的是動態分配的內存,那麼一定要注意防止內存洩露。返回局部變量字符串的地址可能有問題,因為函數執行完畢後該處內存可能被別的棧幀覆寫。比如你在函數裡聲明一個字符串數組並初始化了,然後返回數組的地址,這個數組所占用的內存是不安全的,面臨被其它函數的棧幀覆寫的風險。
通過函數指針控制程序執行是一種非常靈活的方法。
#include <stdio.h> #include <stdlib.h> #include <string.h> char* stringToLower(const char* string){ char* tmp = (char*) malloc (strlen(string) + 1); char* start = tmp; while(*string != 0){ *(tmp++) = tolower(*(string++)); } *tmp = 0; return start; } main(){ typedef int (fptroperation)(const char*, const char*); int compare(const char* s1, const char* s2){ return strcmp(s1,s2); } int compareIgnoreCase(const char* s1, const char* s2){ char* t1 =stringToLower(s1); char* t2 =stringToLower(s2); int result = strcmp(t1,t2); free(t1); free(t2); return result; } void sort(char* array[], int size, fptroperation operation){ int swap = 1; while(swap){ swap = 0; int l = 0; while(l<size-1){ if(operation(array[l],array[l+1])>0) { swap = 1; char* tmp = array[l]; array[l] = array[l+1]; array[l+1] = tmp; } l++; } } } void display(char* names[], int size){ int i = 0; while(i<size){ printf("%s ",names[i]); i++; } printf("\n"); } char* names[] = {"jack","rose","Titanic","hello","World"}; char* newnames[] = {"jack","rose","Titanic","hello","World"}; sort(names, 5, compare); display(names,5);//Titanic World hello jack rose sort(newnames, 5, compareIgnoreCase); display(newnames, 5);//hello jack rose Titanic World }
這個示例通過使用函數指針來實現不同規則下的字符串比較工作。