字符串可以分配到內存的不同區域,通常使用指針來支持字符串操作。字符串是以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
}
這個示例通過使用函數指針來實現不同規則下的字符串比較工作。