程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 深入理解C指針之五:指針和字符串,深入理解之五

深入理解C指針之五:指針和字符串,深入理解之五

編輯:關於C語言

深入理解C指針之五:指針和字符串,深入理解之五


  基礎概念

  字符串可以分配到內存的不同區域,通常使用指針來支持字符串操作。字符串是以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通過argcargv參數支持命令行參數。第一個參數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
}

    這個示例通過使用函數指針來實現不同規則下的字符串比較工作。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved