0 簡介
在C語言中,字符串和數(shù)組有很多相似之處,且官方提供了很多的庫函數(shù)可供調用。那么字符串和數(shù)組這對姐妹花,究竟有著什么樣的親密關系,而作為我們本期的重點角色,字符串又有何獨特之處呢?
C語言并沒有顯式的字符串數(shù)據(jù)類型,因為字符串以字符串常量的形式出現(xiàn)或者存儲于字符數(shù)組中。字符串常量很適用于那些程序不會對它們進行修改的字符串。所有其他字符串都必須存儲于字符數(shù)組或動態(tài)分配的內存中。
本篇著重介紹了一些字符串常用的庫函數(shù),使大家在不同的情況下去選擇最適合的庫函數(shù)。下面是本文的內容概覽
1 字符串基礎
字符串就是一串0個
或多個
字符,并且以一個位模式全為0
的NUL
字節(jié)結尾。
例如:
char message[] = "hello word";
2 字符串長度
字符串的長度就是字符串所包含的字符個數(shù),并不包括最后一個終止符。這個在面試中會經??疾?。
可以通過庫函數(shù)strlen()來自動計算字符串的長度。
例如:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char message[] = "hello word";
printf("字符串的長度為:%d\n",strlen(message));
system("pause");
return 0;
}
打印輸出:
需要注意的是,該函數(shù)返回的是無符號數(shù),因此通過該函數(shù)比較兩個字符串的長度的時候需要格外注意:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char message1[] = "hello word";
char message2[] = "hello Shanghai";
//比較方式1
if(strlen(message1) >= strlen(message2))
printf("字符串1更長\n");
else
printf("字符串2更長\n");
//比較方式2
if (strlen(message1) - strlen(message2) >= 0)
printf("字符串1更長\n");
else
printf("字符串2更長\n");
system("pause");
return 0;
}
打印輸出:
因為返回的是無符號數(shù),所以比較方式2中,條件判斷的結果永遠為真,導致判斷結果出現(xiàn)錯誤。
3 不受限制的字符串函數(shù)
所謂的不受限制的字符串函數(shù),是指在使用的時候,不需要指定字符串(實參)的長度,函數(shù)即可順利運行。
3.1 復制字符串
復制字符串在開發(fā)中經常會用到,但是在復制到新的字符串中時,會覆蓋掉原來的部分,所以需要格外注意。
例如:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char message1[] = "hello word";
char message2[] = "hello Shanghai";
int message2_len = strlen(message2);
printf("字符串2的長度為:%d\n", strlen(message2));
strcpy(message2,message1);
printf("字符串2的長度為:%d\n",strlen(message2));
for(int i = 0; i < message2_len; i++)
printf("%c",message2[i]);
system("pause");
return 0;
}
打印輸出:
可以看到,在將字符串message1
復制到message2
之后,message2
的長度居然都不一樣了,這是為什么呢?
這是因為,在復制字符串的時候,順便也將終止符復制了過來,在strlen()
函數(shù)進行處理的時候,肯定就會返回10
,從打印結果來看,message2
的其余部分仍然被保留了下來。
而將較長字符串復制到較短的字符串中時,常常會報錯,這是因為沒有足夠的空間去容納需要復制的字符。
3.2 連接字符串
連接字符串的時候,可以使用strcat()函數(shù)。它的原型如下:
char *strcat(char *dst, char const *src);
舉個例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char message1[] = "hello word";
char message2[] = "hello Shanghai";
strcat(message1,message2);
printf("%s\n", message1);
system("pause");
return 0;
}
打印輸出:
可以看到,直接將兩個字符串進行了拼接。新字符串的長度值是原來兩個字符串的長度之和。
3.3 函數(shù)的返回值
這些函數(shù)的返回值有時候是第一個參數(shù)的一份拷貝,因此可以嵌套使用,因為當字符串做實參的時候,傳遞的也是第一個元素的地址。所以這些函數(shù)經??梢郧短椎卣{用。
例如:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char message1[] = "hello ";
char message2[] = "word ";
char message3[] = "Shanghai";
strcat(strcat(message1, message2), message3);
printf("%s\n", message1);
system("pause");
return 0;
}
打印輸出:
但是為了程序的可讀性,不嵌套也可以。
3.4 字符串比較
字符串比較,常用的庫函數(shù)只有一個,就是strcmp
。它的原型如下:
int strcmp(char const *s1, char const *s2);
這個函數(shù)的比較規(guī)則比較 有意思,該函數(shù)對兩個字符串的字符逐個進行比較,直到發(fā)現(xiàn)不匹配為止,這里有兩種情況:
- 最先不匹配的字符中在ASCII中排名靠前的那個字符所在的字符串被認為是較小的字符串;
- 如果開始部分的兩個字符串都相等,那么較短的字符串被認為是較小的字符串。
字符串發(fā)現(xiàn)有不匹配的某個字符,即可得到對比結果,而無須比較剩余部分。
如下圖所示:
看看實際的代碼:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char temp1[] = "hello";
char temp2[] = "hello world";
char temp3[] = "hello worLd";
//字符串temp1和temp2作比較
if(strcmp(temp1,temp2) == 0)
{
printf("temp1 = temp2\n");
}
else if (strcmp(temp1, temp2) > 0)
{
printf("temp1 > temp2\n");
}
else if (strcmp(temp1, temp2) < 0)
{
printf("temp1 < temp2\n");
}
printf("------------------\n");
//字符串temp2和temp3作比較
if (strcmp(temp2, temp3) == 0)
{
printf("temp2 = temp3\n");
}
else if (strcmp(temp2, temp3) > 0)
{
printf("temp2 > temp3\n");
}
else if (strcmp(temp2, temp3) < 0)
{
printf("temp2 < temp3\n");
}
printf("\n");
system("pause");
return 0;
}
打印輸出:
4 長度受限的字符串函數(shù)
有的庫函數(shù)在調用的時候,需要傳入待處理的字符串的長度,因此稱為長度受限的字符串函數(shù)。
這些函數(shù)提供了一種方便的機制,可以防止難以預料的長字符串從它們的目標數(shù)組溢出。
常見的有一下幾個函數(shù):
char *strncpy(char *dst, char const *src, size_t len);
char *strncat(char *dst, char const *src, size_t len);
int strncmp(char const *s1, char const *s2, size_t len);
舉個例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char message1[] = "hello ";
char message2[] = "hello Beijing ";
char message3[] = "Shanghai";
char message_all[] = "hello Beijing Shanghai";
if(strncmp(strncpy(strncat(message2, message3, strlen(message3)), message1, strlen(message1)), message_all,strlen(message_all)) == 0)
printf("二者相等\n");
else
printf("二者不相等\n");
system("pause");
return 0;
}
打印輸出:
這個例子舉得并不十分恰當。因為長度都是按最大進行傳入的,但也可以說明問題。
5 字符串查找基礎
標準庫中存在很多函數(shù),它們用各種不同的方法查找字符串。這些各種各樣的工具給了C程序員很大的靈活性。
5.1 查找一個字符串
在一個字符串中查找特定字符有兩個庫函數(shù)可用。
char *strchr(char const *str, int ch);
char **strrchr(char const *str, int ch);
前者用來查找某字符第一次出現(xiàn)的位置(返回指向該地址的指針),后者用來查找某字符最后一次出現(xiàn)的位置(返回指向該地址的指針)。
這兩個函數(shù)可以這樣用:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char message1[] = "hello ";
char message2[] = "hello Beijing ";
char message3[] = "Shanghai";
char message_all[] = "hello Beijing Shanghai";
char *first_site, *last_site;
first_site = strchr(message_all, 'h');
last_site = strrchr(message_all, 'h');
printf("字符串的長度是:%d\n",strlen(message_all));
printf("h第一次出現(xiàn)的位置是:%d\n", first_site - message_all);
printf("h最后一次出現(xiàn)的位置是:%d\n", last_site - message_all);
system("pause");
return 0;
}
打印輸出:
需要注意的是,該函數(shù)返回的并不是目標元素位置的值,而是指針,所以需要與該字符串的第一個元素指針作差,才可以得出結果。
注意:在查找的時候是區(qū)分大小寫的。
5.2 查找任何幾個字符
strpbrk
是一個更為常見的函數(shù),用來查找某個字符串中任意字符第一次在目標字符串中出現(xiàn)的位置。它的原型如下:
char *strpbrk(char const *str, char const *group);
可以這樣用:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char message_all[] = "hello Beijing Shanghai";
char *first_site;
first_site = strpbrk(message_all, "abcde");
printf("字符串的長度是:%d\n",strlen(message_all));
printf("abcde第一次出現(xiàn)匹配字符的位置是:%d\n", first_site - message_all);
system("pause");
return 0;
}
打印輸出:
容易看出,第一個匹配到的字符是e
,位置是1
。
5.3 查找一個子串
為了在字符串中查找一個子串,我們可以使用strstr函數(shù),它的原型如下:
char *strstr(char const *s1, char const *s2);
舉個實際使用中的例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char message_all[] = "hello Beijing Shanghai";
char *first_site;
first_site = strstr(message_all, "Beijing");
printf("字符串的長度是:%d\n",strlen(message_all));
printf("Beijing第一次出現(xiàn)的位置是:%d\n", first_site - message_all);
system("pause");
return 0;
}
打印輸出:
可以看到,在此次查找中,需要匹配到所有的字符,而不是某個或者局部。
6 高級字符串查找
接下來的一組函數(shù)簡化了從一個字符串的起始位置中查找和抽取一個子串的過程。
6.1 查找一個字符串前綴
strspn
和strcspn
函數(shù)用于在字符串中的起始位置對字符串計數(shù),它們的原型如下所示:
size_t strspn( char const *str, char const *group);
size_t strcspn( char const *str, char const *group);
需要注意的是,這兩個函數(shù)返回的 并不是元素指針,而是實際匹配字符的數(shù)量。
具體的使用方法可以來看個例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int len1, len2;
char buffer[] = "25,142,330,smith,J,239-4123";
len1 = strspn(buffer, "0123456789");
len2 = strcspn(buffer, ",");
printf("0123456789的起始匹配數(shù)是:%d\n", len1);
printf(",的起始不匹配數(shù)是:%d\n", len2);
system("pause");
return 0;
}
打印輸出:
從上面例子中可以看出,strspn
函數(shù)是從頭開始找符合所找字符串的字符,直到找不到為止。在該例中,,
已經不合適了,所以連續(xù)查找的情況下,合適的只有2
個。
strcspn函數(shù)卻恰好相反,找的是不符合的,開頭的2和5顯然都不符合,,
是符合的,所以連續(xù)查找的情況下,合適的有2
個。
6.2 查找標記
一個字符串常常包含好幾個單獨的部分,他們彼此被分隔開。每次為了處理這些部分,首先必須把它們從字符串中抽取出來。
strtok函數(shù)就可以實現(xiàn)這樣的功能。它從字符串中隔離各個單獨的稱為標記的部分。并丟棄分隔符。它的原型如下:
char *strtok( char *str, char const *sep);
注意:
- 當strtok函數(shù)執(zhí)行任務時,它會修改它所處理的字符串。如果源字符串不能被修改,那就復制一份,將這份拷貝傳遞給strtok函數(shù)。
- 如果strtok函數(shù)的第1個參數(shù)不是NULL,函數(shù)將找到字符串的第1個標記。strtok同時將保存它在字符串中的位置。如果strtok函數(shù)的第1個參數(shù)是NULL,函數(shù)就在同一個字符串中從這個被保存的位置開始像前面一樣查找下一個標記。
舉個例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int add = 0;
char buffer[] = "25,142,330,smith,J,239-4123";
char *token = NULL;
for (token = strtok(buffer, ","); token != NULL; token = strtok(NULL, ","))
{
printf("%s\n", token);
add++;
}
printf("--------------------------\n");
printf("add的值為:%d\n",add);
system("pause");
return 0;
}
打印輸出:
從上面的例子中可以看出,以我們需要尋找的標記為分界,每循環(huán)一次,得到一個分割的子串,直到全部分割完畢。共分割6
次。
7 錯誤信息
C語言的庫函數(shù)在執(zhí)行失敗時,都會有一個錯誤碼(0 1 2 3 4 5 6 7 8 9 …),操作系統(tǒng)是通過設置一個外部的整型變量errno
進行錯誤代碼報告的。也就是說,一個錯誤碼,對應了一種錯誤類型,strerror
函數(shù)把其中一個錯誤代碼作為參數(shù)并返回一個指向用于描述錯誤的字符串的指針。這個函數(shù)的原型如下:
char *strerror(int error_number);
舉個例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
for(int i = 0; i < 10; i++)
printf("%s\n",strerror(i));
system("pause");
return 0;
}
打印輸出:
可以看到,不同的操作碼,對應著不同的錯誤類型,而錯誤碼0
表示沒有錯誤。其他均表示各種各樣的錯誤。這部分內容了解即可。不需要掌握。也不需要知道每個操作碼究竟代表哪種錯誤。
8 字符操作
標準庫包含了兩組函數(shù),用于操作單獨的字符,它們的原型位于頭文件ctype.h。第1組函數(shù)用于對字符串分類,而第2組函數(shù)用于字符轉換。
8.1 字符分類
每個分類函數(shù)接受一個包含字符值的整形參數(shù)。函數(shù)測試這個字符并返回一個整形值,表示真或假。下面的表格列出了每個函數(shù)以及返回真所需要的條件。
函數(shù) | 返回真所需要的條件 |
---|---|
iscntrl | 控制字符 |
isspace | 空白字符:空格,換頁’\f’,換行’\n’,回車’\r’,制表符’t’或垂直制表符’\v’ |
isdigit | 十進制數(shù)字 |
isxdigit | 十六進制數(shù)字,包含大小寫形式的a~f |
islower | 小寫字母 |
isupper | 大寫字母 |
isalpha | 字母(大小寫皆可) |
isalnum | 字母或數(shù)字 |
ispunct | 任何不屬于數(shù)字或字母的圖形字符(可打印符號) |
isgraph | 任何圖形字符 |
isprint | 任何可打印字符,包括圖形字符和空白字符 |
所以這些函數(shù)是用來判定字符串元素的, 舉個例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
int main()
{
char temp[] = "To carry things with great virtue";
for (int i = 0; i < strlen(temp); i++)
{
if (islower(temp[i]))
printf("temp[%d] : %c是小寫字母\n", i, temp[i]);
else if (isupper(temp[i]))
printf("temp[%d] : %c是大寫字母\n", i, temp[i]);
else if(isspace(temp[i]))
printf("temp[%d] : %c是空格\n", i, temp[i]);
}
printf("\n");
system("pause");
return 0;
}
打印輸出:
可以看到,此時已經將temp
每個元素究竟是大寫字母還是小寫字母,或者是空格進行了判定。
8.2 字符轉換
轉換函數(shù)白大寫字母轉換為小寫字母或者把小寫字母轉換為大寫字母。有兩個函數(shù)可供調用。toupper
函數(shù)返回其參數(shù)對應的大寫形式,tolower
函數(shù)返回其參數(shù)對應的小寫形式。
int tolower(int ch);
int toupper(int ch);
舉個實際的例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
int main()
{
char temp1[] = "To carry things with great virtue";
char temp2[] = "To carry things with great virtue";
//全轉換為大寫
for (int i = 0; i < strlen(temp1); i++)
{
if (islower(temp1[i]))
temp1[i] = toupper(temp1[i]);
printf("%c",temp1[i]);
}
printf("\n-----------------------------\n");
//全轉換為小寫
for (int i = 0; i < strlen(temp2); i++)
{
if (isupper(temp2[i]))
temp2[i] = tolower(temp2[i]);
printf("%c", temp2[i]);
}
printf("\n");
system("pause");
return 0;
}
打印輸出:
可以看到,我們可以按照自己的意愿去調整字符串中字母的大小寫。
9 內存操作
字符串一般以NUL結尾,但是如果我們想要處理中間包含NUL的字符串,或者任意長度的字節(jié)序列的時候,前面的函數(shù)就顯得比較乏力,或者說根本沒法用。不過我們可以有另外一組函數(shù),供我們使用,去完成實際開發(fā)中的一些需求。下面是它們的原型。
注意:這些函數(shù)能夠處理的不僅僅是字符串,還可以是結構體或數(shù)組等數(shù)據(jù)類型,具體可以處理的數(shù)據(jù)類型要根據(jù)具體函數(shù)來定。
void *memcpy(void *dst, void const *src, size_t length);
void *memmove(void *dst, void const *src, size_t length);
void *memcmp(void const *a, void const *b, size_t length);
void *memchr(void const *a, int ch, size_t length);
void *memset(void *a, int ch, size_t length);
舉個例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 10
int main()
{
char temp1[] = "hello world";
char temp2[] = "hello world";
char temp3[] = "hello world";
char temp4[] = "hello world";
unsigned int int_array[SIZE];
char *p = NULL;
//復制字符串
memcpy(temp1 + 2, temp1, 5);
memmove(temp2 + 2, temp2, 5);
printf("temp1 = %s\n", temp1);
printf("temp2 = %s\n", temp2);
printf("---------------------------------------\n");
//比較字符串
if(!memcmp(temp1, temp2, 6))
printf("temp1 = temp2\n");
else
printf("temp1 != temp2\n");
printf("---------------------------------------\n");
//查找字符
p = (char *)memchr(temp3, 'e', strlen(temp3));
if(p != NULL)
printf("字符e在temp3中的位置是:%d\n", p - &temp3[0]);
printf("---------------------------------------\n");
//初始化數(shù)組
memset(int_array, 0, sizeof(int_array));
for (int i = 0; i < SIZE; i++)
printf("int_array[%d]的值為:%d\t", i, int_array[i]);
printf("\n", sizeof(int));
printf("---------------------------------------\n");
//初始化數(shù)組
memset(temp4, 'a', sizeof(temp4) - 1);
printf("字符串temp4為:%s\n", temp4);
system("pause");
return 0;
}
打印輸出:
9.1 memcpy和memmove真的不一樣嗎?
有一個很值得探討的問題:
memcpy和memmove函數(shù)真的一樣嗎?《C和指針》以及網上的很多說法都是:兩者不一樣,如果src和dst出現(xiàn)了重疊,則memcpy會出現(xiàn)問題,而memmove總能按照理想的情況去運行,但我們的程序運行結果卻是,這兩個函數(shù)都可以按照理想情況運行,這是為什么呢?
唯一的解釋就是,軟件的運行環(huán)境不一樣,程序的底層庫出現(xiàn)了差異,所以會出現(xiàn)這樣的情況。但這并不影響我們對以前版本的(也就是兩者不同)的memcpy和memmove進行一番研究!
先來看看所謂的重疊是什么意思,為什么重疊的時候,字符串復制會出現(xiàn)問題。
以上就是我們程序中復制字符串操作的示意圖??梢钥吹絪rc子串和dst子串有三個字母出現(xiàn)了重疊,如果我們按照常規(guī)的操作方法,復制之后就會出現(xiàn)下面這樣的結果。
若區(qū)域重疊,就會導致復制出錯,也就是說,想要取的值被新值所覆蓋,導致無法順利取值,復制后temp1
變成了hehehehorld
。這就是之前memcpy
字符串的復制方法。
然后讓我們來看看memmove
(以及優(yōu)化后的memcpy
)是如何巧妙地解決這個問題的。
可以看到,復制的順序出現(xiàn)了變化,這次是從后往前復制的,很好地避免了這個問題。
那么問題來了,這次是要復制的目的位置在后面,如果在前面又該如何處理呢?答案是復制順序也反過來??纯磮?zhí)行過程:
這個時候,也不會出現(xiàn)將要取的值被原來的值覆蓋的情況,會按照預想的結果去執(zhí)行,結果是:llo w world
。
9.2 memcmp:簡單的比較
memcmp
比較內存區(qū)域a和的前l(fā)ength個字節(jié)。比較方法,返回值都和strcmp
基本一致,詳情可以參考本文的strcmp
部分。
9.3 memchr:簡單的查找
memchr從a的起始位置開始查找字符ch的第一次出現(xiàn)的位置,并返回一個指向該位置的指針。查找方法,返回值都和strchr
基本一致,詳情可以參考本文的strchr
部分。
9.4 memset:初始化的值只能是0和-1?
從該函數(shù)的介紹來看,該函數(shù)可以對某連續(xù)的內存區(qū)域設置相同的任何值(理論上),但在實際的開發(fā)中,基本都設置為0
或者-1
,這是為什么呢?
這是因為,這個函數(shù)對于內存的賦值,是以字節(jié)為單位的,一般用來對字符串進行賦值沒有任何問題,因為字符串的元素只占一個字節(jié),而數(shù)組則不同,常見的short,int,long類型的數(shù)組元素都不止一個字節(jié),所以初始化才不會出現(xiàn)我們預想的結果。當我們設置為0的時候,各個字節(jié)都為0(若為-1,則各個位都為1),所以每個元素無論幾個字節(jié)都會初始化為0,但是為其他值,結果就不一樣了。參考以下程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 10
int main()
{
unsigned int int_array[SIZE];
//初始化數(shù)組
printf("--------------初始化值設為0-------------------------\n");
memset(int_array, 0, sizeof(int_array));
for (int i = 0; i < SIZE; i++)
printf("int_array[%d]的值為:%d\t", i, int_array[i]);
printf("\n");
printf("--------------初始化值設為1-------------------------\n");
memset(int_array, 1, sizeof(int_array));
for (int i = 0; i < SIZE; i++)
printf("int_array[%d]的值為:%d\t", i, int_array[i]);
printf("\n");
system("pause");
return 0;
}
打印輸出:
這是為什么呢?
這是因為,memset
是按字節(jié)賦值的,而int
類型的數(shù)據(jù)在內存中占了4
個字節(jié),所以該值應該以4
個字節(jié)為單位,即:0x01010101
,轉換成十進制正好是16843009
。
所以,一般如果想用memse
對一段內存區(qū)域設定相同的值,初始化為0
或者-1
是最好的選擇。
10 總結
字符串本身并不是很復雜,為了方便開發(fā),提供了很多的庫函數(shù),所以我們只需要掌握C語言的那些庫函數(shù)即可。尤其是要注意,有的函數(shù)返回的并不是數(shù)值,而是指針;有的函數(shù)使用起來比較特殊,比方說strtok
等。文章來源:http://www.zghlxwxcb.cn/news/detail-474133.html
------------------------------------------------------------------------end-------------------------------------------------------------------------文章來源地址http://www.zghlxwxcb.cn/news/detail-474133.html
到了這里,關于《C和指針》讀書筆記(第九章 字符串、字符和字節(jié))的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!