原文:https://mp.weixin.qq.com/s/Onzk-a16uHG1QJ1VXSjsmA
前言
字符串可以說是C語言中最有用、最重要的數(shù)據(jù)類型了。
字符串是以空字符(\0)結(jié)尾的char類型數(shù)組。
用雙引號擴起來的內(nèi)容是字符串字面量(或著叫字符串常量)。
字符串常量屬于靜態(tài)存儲類別,即只要在函數(shù)中使用字符串常量,這個字符串只會被存儲一次,并且存活在整個程序生命周期內(nèi)。
//
// Created by Victor on 2019/10/22.
//
#include <stdio.h>
#define MSG "define a message"
#define MAX_LENGTH 81
int main() {
char hi[MAX_LENGTH] = "Hi, i am victor yang!";
const char * pt = "i do not know, why you dismiss.";
puts("your message:"); // puts 函數(shù)只顯示字符串,并且自動在末尾加換行符
puts(MSG);
puts(hi);
puts(pt);
hi[3] = 'H';
puts(hi);
return 0;
}
your message:
define a message
Hi, i am victor yang!
i do not know, why you dismiss.
Hi,Hi am victor yang!
記住字符串最后都會有個空字符:
char a[] = {'a', 'b', 'c'}; // 這是字符數(shù)組
char b[] = {'a', 'b', 'c', '\0'}; // 這是字符串
字符串與指針緊密相連:
char hi[MAX_LENGTH] = "Hi, I miss u";
printf("%p\n", hi); // 0x7ffee2023ab0
printf("%p\n", &hi[0]); // 0x7ffee2023ab0
printf("%c\n",*hi); // H
printf("%c\n", hi[0]); // H
printf("%c\n", *(hi + 1)); // I
printf("%c\n", hi[1]); // I
字符串與數(shù)組回顧:
#include <stdio.h>
#define MSG "define a message"
int main() {
// 字符串存儲在靜態(tài)存儲區(qū)
// 在程序運行的時候才給 a 數(shù)組分配內(nèi)存,之后才將字符串'拷貝'到數(shù)組中
char a[] = MSG;
// 字符串字面量是常量 const 數(shù)據(jù)
// 由于 pt 指向const數(shù)據(jù),所以 pt 應該聲明為 const 數(shù)據(jù)到指針
const char * pt = MSG;
printf("%p\n", "define a message"); // 0x103f1aeb0
printf("%p\n", a); // 0x7ffeebce5af0
printf("%p\n", pt); // 0x103f1aeb0
printf("%p\n", MSG); // 0x103f1aeb0
printf("%p\n", "define a message"); // 0x103f1aeb0
return 0;
}
雖然字符串字面量"defin a message" 在 printf 函數(shù)中出現(xiàn)了兩次,但是編譯器只使用了一個存儲位置,而且與MSG的地址相同。
編譯器可以把多次使用的相同字面量儲存在一處或多處。
數(shù)組的元素是變量(除非數(shù)組被聲明為const),但是數(shù)組名是地址常量。

二維字符數(shù)組每個數(shù)組的分配空間是一樣的,指針數(shù)組的分配空間更靈活也更高效但是不能修改內(nèi)容;
指針和字符串
const char * a = "I Love U";
const char * b;
b = a;
printf("%s\n", b);
printf("a = %s, &a = %p, a指針變量存儲的地址值 = %p\n", a, &a, a);
printf("b = %s, &b = %p, b指針變量存儲的地址值 = %p\n", b, &b, b);
I Love U
a = I Love U, &a = 0x7ffee4126b00, a指針變量存儲的地址值 = 0x10bad9e30
b = I Love U, &b = 0x7ffee4126af8, b指針變量存儲的地址值 = 0x10bad9e30
字符串輸入
gets()
char think[81];
puts("請輸入你的想法");
gets(think);
printf("已經(jīng)接受到你到想法: %s\n", think);
puts(think);
請輸入你的想法
warning: this program uses gets(), which is unsafe.
i see you, balalalalal...
已經(jīng)接受到你到想法: i see you, balalalalal...
i see you, balalalalal...
gets() 無法檢查數(shù)組是否能夠存儲該行,只能知道數(shù)組的開始處。如果輸入的字符串過長,就會造成緩沖區(qū)溢出(多余的字符超出了指定空間).
輸入超過數(shù)組容量的字符串會提示 Process finished with exit code 11, 說明該程序試圖訪問未分配的內(nèi)存, 這樣的行為是不安全的,可能會被插入一些破壞系統(tǒng)的代碼。
fgets()
char think[5];
puts("請輸入你的想法");
fgets(think, 5, stdin); // 第二個參數(shù)表示讀入字符的最大長度數(shù)量,第三個參數(shù)是讀入第文件 stdin 是標準輸入
puts(think);
fputs(think, stdout); // stdout 標準輸出
請輸入你的想法
abcdefgh
abcd
abcd
輸入的abcdefgh\n, 其實只存儲了 abcd\0
fgets()函數(shù)會存儲換行符,而gets()不會:
請輸入你的想法
123
123
123
有時候不需要fgets()最后存儲的換行符,那么我們可以進行如下騷操作進行處理:
while(words[i] != '\n')//假設\n在words中
I++;
words[i] = '\0';
下面是個實用的示例:
char think[5];
int I;
puts("請輸入你的想法");
// 重復獲取輸入內(nèi)容,直到文件末尾或該行第一個字符是換行符時結(jié)束
while (fgets(think, 5, stdin) != NULL && think[0] != '\n'){
i = 0;
// 找到該行的結(jié)尾,如果是換行符則該行內(nèi)容沒有超出規(guī)定大小
while (think[i] != '\n' && think[i] != '\0')
I++;
if (think[i] == '\n'){
think[i] = '\0';
} else{
// 這個分支是該行內(nèi)容多與規(guī)定大小,為了舍棄多余字符,讓剩余的字符讀取但不存儲, 包括遇到但第一個換行符
while (getchar() != '\n'){}
}
puts(think);
}
\0 是空字符的意思,是整數(shù)類型,是一個字符占用了1個字節(jié),是用于標記C字符串末尾的字符,其對應字符編碼是0。由于其他字符的編碼不可能是0,所以不可能是字符串的一部。
NULL 是空指針的意思,是指針類型,是一個地址占用了4個字節(jié),該值不會與任何數(shù)據(jù)的有效地址對應。
gets_s()
- 只從標準輸入中讀取數(shù)據(jù);
- 讀到換行符會丟棄不存儲;
- 如果讀到最大字符數(shù)但是沒有讀到換行符,會把最大字符數(shù)下的字符設置為空字符,讀取并丟棄隨后的輸入直至讀到換行符或文件結(jié)尾,然后返回空指針,可能會中止或退出程序。
讀取整行輸入并用空字符代替換行符,或者讀取一部分輸入,并丟棄其余部分
char * afra_gets(char* think, int n){
// 重復獲取輸入內(nèi)容,直到文件末尾或該行第一個字符是換行符時結(jié)束
while (fgets(think, n, stdin) != NULL && think[0] != '\n'){
i = 0;
// 找到該行的結(jié)尾,如果是換行符則該行內(nèi)容沒有超出規(guī)定大小
while (think[i] != '\n' && think[i] != '\0')
I++;
if (think[i] == '\n'){
think[i] = '\0';
} else{
// 這個分支是該行內(nèi)容多與規(guī)定大小,為了舍棄多余字符,讓剩余的字符讀取但不存儲, 包括遇到但第一個換行符
while (getchar() != '\n'){}
}
puts(think);
}
}
如果不舍棄多出來的字符,那些字符會留在緩沖區(qū),成為下一次讀取的輸入。
scanf()
| 語句 | 輸入內(nèi)容 | temp 的內(nèi)容 | 剩余的內(nèi)容 |
|---|---|---|---|
| scanf("%s", temp); | Hi Afra | Hi | Afra |
| scanf("%5s", temp); | Hi Afra | Hi | Afra |
| scanf("%5s", temp); | Afra55Blalal | Afra55 | Blalal |
scanf() 返回一個整數(shù)值 是讀取成功的總個數(shù),或文件結(jié)尾時反回EOF。
int count;
char think1[10], think2[10];
printf("請輸入你的想法:\n");
count = scanf("%5s %3s", think1, think2);
printf("%d %s %s", count, think1, think2);
請輸入你的想法:
123456789 abcdefghijk
2 12345 678
請輸入你的想法:
abcde 1234567890
2 abcde 123
請輸入你的想法:
123 abcdefghijk
2 123 abc
請輸入你的想法:
123456 abcdefg
2 12345 6
字符串輸出
puts()
char str[80] = "Hi i am Afra55 from victor";
char str2[80] = "I come from your dream!";
puts(str);
puts(str2);
Hi i am Afra55 from victor
I come from your dream!
puts()在顯示字符串時會自動在其末尾添加一個換行符。該函數(shù)在遇到空字符時就停止輸出,所以必須確保有空字符。
fputs()
fputs()不會再輸出的末尾加上換行符。
fputs(str2, stdout);
fputs("is fputs", stdout);
I come from your dream!is fputs
printf()
printf()不會自動在每個字符串末尾加上一個換行符。因此,必須在參數(shù)中指明應該在哪里使用換行符。
printf() 雖然更復雜,但是打印多個字符串更簡單。
自定義輸入輸出
可以使用 getchar() putchar() 單個字符的輸入輸出來自定義。
字符串函數(shù)
strlen()
統(tǒng)計字符串長度:
char a[] = "I Love U\0Hi hahahahah";
puts(a);
printf("%lu\n", strlen(a));
puts(a + 9);
I Love U
8
Hi hahahahah
strcat()
拼接兩個字符串,接受兩個字符串作為參數(shù),把第2個字符串的備份附加在第1個字符串末尾,并把拼接后形成的新字符串作為第1個字符串,第2個字符串不變。strcat()函數(shù)的類型是char *(即,指向char的指針)。strcat()函數(shù)返回第1個參數(shù),即拼接第2個字符串后的第1個字符串的地址。
char a[10] = "I u.";
char b[] = "you";
strcat(a, b);
puts(a);
puts(b);
I u.you
you
strcat()函數(shù)無法檢查第一個數(shù)組是否容納第二個數(shù)組,如果分配給第一個數(shù)組的空間不大,多出來的字符溢出到相鄰存儲單元就會出現(xiàn)問題。
strncat()
函數(shù)可以傳入第三個參數(shù),用來指定最大添加到字符數(shù)。
char a[10] = "I u.";
char b[] = "you";
strncat(a, b, 2);
puts(a);
puts(b);
I u.yo
you
strcmp()
用來比較兩個字符串。如果兩個字符串相同,則返回0,否則返回非0值。
char a[] = "I miss u";
char b[] = "I miss u";
char c[] = "U miss I";
printf("a 與 b 比較: %d\n", strcmp(a, b));
printf("a 與 c 比較: %d", strcmp(a, c));
a 與 b 比較: 0
a 與 c 比較: -12
非0值都是“真”。
如果在字母表中第1個字符串位于第2個字符串前面,strcmp()中就返回負數(shù);反之,strcmp()則返回正數(shù)。
printf("A 與 A 比較: %d\n", strcmp("A", "A"));
printf("A 與 B 比較: %d\n", strcmp("A", "B"));
printf("A 與 C 比較: %d\n", strcmp("A", "C"));
printf("C 與 A 比較: %d\n", strcmp("C", "A"));
printf("D 與 A 比較: %d\n", strcmp("D", "A"));
A 與 A 比較: 0
A 與 B 比較: -1
A 與 C 比較: -1
C 與 A 比較: 1
D 與 A 比較: 1
ASCII標準規(guī)定,在字母表中,如果第1個字符串在第2個字符串前面,strcmp()返回一個負數(shù);如果兩個字符串相同,strcmp()返回0;如果第1個字符串在第2個字符串后面,strcmp()返回正數(shù)。然而,返回的具體值取決于實現(xiàn)。
char類型實際上是整數(shù)類型,可以使用關系運算符來比較字符。
strncmp()
strcmp()函數(shù)比較字符串中的字符,直到發(fā)現(xiàn)不同的字符為止,這一過程可能會持續(xù)到字符串的末尾。而strncmp()函數(shù)在比較兩個字符串時,可以比較到字符不同的地方,也可以只比較第3個參數(shù)指定的字符數(shù)。例如,要查找以"victor"開頭的字符串,可以限定函數(shù)只查找這6個字符。
const char * list[3] = {"victor blallalal", "bbbbvictorcccc", "ssssss victor"};
printf("查找 list[0]: %d\n", strncmp(list[0], "victor", 6));
printf("查找 list[1]: %d\n", strncmp(list[1], "victor", 6));
printf("查找 list[2]: %d\n", strncmp(list[2], "victor", 6));
查找 list[0]: 0
查找 list[1]: -20
查找 list[2]: -3
strcpy(), strncpy()
用于拷貝整個字符串,
char temp[8];
char a[] = "afra55";
strcpy(temp, a);
puts(a);
puts(temp);
afra55
afra55
strcpy()第2個參數(shù)指向的字符串被拷貝至第1個參數(shù)指向的數(shù)組中??截惓鰜淼淖址环Q為目標字符串,最初的字符串被稱為源字符串。參考賦值表達式語句,很容易記住strcpy()參數(shù)的順序,即第1個是目標字符串,第2個是源字符串。
strcpy()的返回類型是char*,該函數(shù)返回的是第1個參數(shù)的值,即一個字符的地址;
第1個參數(shù)不必指向數(shù)組的開始。這個屬性可用于拷貝數(shù)組的一部分。
拷貝字符串用strncpy()更安全,該函數(shù)的第3個參數(shù)指明可拷貝的最大字符數(shù)。
strncpy(target,source,n)把source中的n個字符或空字符之前的字符(先滿足哪個條件就拷貝到何處)拷貝至target中。因此,如果source中的字符數(shù)小于n,則拷貝整個字符串,包括空字符。但是,strncpy()拷貝字符串的長度不會超過n,如果拷貝到第n個字符時還未拷貝完整個源字符串,就不會拷貝空字符。所以,拷貝的副本中不一定有空字符。鑒于此,該程序把n設置為比目標數(shù)組大小少1(TARGSIZE1),然后把數(shù)組最后一個元素設置為空字符.
sprintf()
sprintf()函數(shù)聲明在stdio.h中,而不是在string.h中。該函數(shù)和printf()類似,但是它是把數(shù)據(jù)寫入字符串,而不是打印在顯示器上。因此,該函數(shù)可以把多個元素組合成一個字符串。sprintf()的第1個參數(shù)是目標字符串的地址。其余參數(shù)和printf()相同,即格式字符串和待寫入項的列表。
char a[20];
sprintf(a, "%s %d $%6.2f", "hahah", 12, 987.123);
puts(a);
hahah 12 $987.12
其他字符串
-
char * strcpy(char * restrict s1, const char * restrict s2);該函數(shù)把s2指向的字符串(包括空字符)拷貝至s1指向的位置,返回值是s1。 -
char * strncpy(char * restrict s1, const char * restrict s2, size_tn);該函數(shù)把s2指向的字符串拷貝至s1指向的位置,拷貝的字符數(shù)不超過n,其返回值是s1。該函數(shù)不會拷貝空字符后面的字符,如果源字符串的字符少于n個,目標字符串就以拷貝的空字符結(jié)尾;如果源字符串有n個或超過n個字符,就不拷貝空字符。 -
char * strcat(char * restrict s1, const char * restrict s2);該函數(shù)把s2指向的字符串拷貝至s1指向的字符串末尾。s2字符串的第1個字符將覆蓋s1字符串末尾的空字符。該函數(shù)返回s1。 -
char * strncat(char * restrict s1, const char * restrict s2, size_tn);該函數(shù)把s2字符串中的n個字符拷貝至s1字符串末尾。s2字符串的第1個字符將覆蓋s1字符串末尾的空字符。不會拷貝s2字符串中空字符和其后的字符,并在拷貝字符的末尾添加一個空字符。該函數(shù)返回s1。 -
int strcmp(const char * s1,const char * s2);如果s1字符串在機器排序序列中位于s2字符串的后面,該函數(shù)返回一個正數(shù);如果兩個字符串相等,則返回0;如果s1字符串在機器排序序列中位于s2字符串的前面,則返回一個負數(shù)。 -
int strncmp(const char * s1, const char * s2, size_tn);該函數(shù)的作用和strcmp()類似,不同的是,該函數(shù)在比較n個字符后或遇到第1個空字符時停止比較。 -
char * strchr(const char * s, int c);如果s字符串中包含c字符,該函數(shù)返回指向s字符串首次出現(xiàn)的c字符的指針(末尾的空字符也是字符串的一部分,所以在查找范圍內(nèi));如果在字符串s中未找到c字符,該函數(shù)則返回空指針。 -
char * strpbrk(const char * s1, const char * s2);如果s1字符中包含s2字符串中的任意字符,該函數(shù)返回指向s1字符串首位置的指針;如果在s1字符串中未找到任何s2字符串中的字符,則返回空字符。 -
char * strrchr(const char * s, int c);該函數(shù)返回s字符串中c字符的最后一次出現(xiàn)的位置(末尾的空字符也是字符串的一部分,所以在查找范圍內(nèi))。如果未找到c字符,則返回空指針。 -
char * strstr(const char * s1, const char * s2);該函數(shù)返回指向s1字符串中s2字符串出現(xiàn)的首位置。如果在s1中沒有找到s2,則返回空指針。 -
size_tstrlen(const char * s);該函數(shù)返回s字符串中的字符數(shù),不包括末尾的空字符。
關鍵字restrict限制了函數(shù)參數(shù)的用法。例如,不能把字符串拷貝給本身。
ctype.h
toupper()函數(shù)用于大寫字符串:
void toUpperChar(char * a){
while (*a){
*a = toupper(*a);
a++;
}
}
char a[] ="abcdef";
toUpperChar(a);
puts(a);
ABCDEF
利用ispunct()統(tǒng)計字符串中的標點符號個數(shù):
int punchCount(char * a){
int count = 0;
while (*a){
if (ispunct(*a)) {
count++;
}
a++;
}
return count;
}
char a[] = "a, bc, c, c!@#$";
printf("a 的字符有 %d 個", punchCount(a));
a 的字符有 7 個
命令行參數(shù)
C編譯器允許main()沒有參數(shù)或者有兩個參數(shù)(一些實現(xiàn)允許main()有更多參數(shù),屬于對標準的擴展)。main()有兩個參數(shù)時,第1個參數(shù)是命令行中的字符串數(shù)量。過去,這個int類型的參數(shù)被稱為argc(表示參數(shù)計數(shù)(argumentcount))。系統(tǒng)用空格表示一個字符串的結(jié)束和下一個字符串的開始。
int main(int argc,char **argv)
int main(int argc,char *argv[])
假如有個程序 hello.c, 輸入命令 hello i love you, 那么 main() 函數(shù)的 argc = 4, argv 共有四個字符串,后三個用于 hello 程序去使用,char **argv = {"hello", "i", "love", "you"}。
如果 對命令行參數(shù)加上雙引號,那么就認為這是一個單詞,例如:
hello "i love you"
那么argc = 2, char **argv = {"hello", "i love you"}。
把字符串轉(zhuǎn)換為數(shù)字
stdlib.h 有 atoi() 函數(shù)可以把字母數(shù)字轉(zhuǎn)換為整數(shù),該函數(shù)接受一個字符串作為參數(shù),返回相應的整數(shù)值。
printf("%d\n", atoi("333"));
printf("%d\n", atoi("333abcde"));
printf("%d\n", atoi("abced333"));
333
333
0
atof()函數(shù)把字符串轉(zhuǎn)換成double類型的值,atol()函數(shù)把字符串轉(zhuǎn)換成long類型的值.
srtol()把字符串轉(zhuǎn)換成long類型的值,strtoul()把字符串轉(zhuǎn)換成unsigned long類型的值,strtod()把字符串轉(zhuǎn)換成double類型的值。
long strtol(const char * restrict nptr, char ** restrict endptr, int base);
nptr是指向待轉(zhuǎn)換字符串的指針,endptr是一個指針的地址,該指針被設置為標志輸入數(shù)字結(jié)束字符的地址,base 表示以什么進制寫入數(shù)字(10:十進制,16:十六進制,8:八進制)。
char a[] = "1234defg";
char * end;
long value = strtol(a, &end, 10);
printf("%ld, %s, %d", value, end, *end);
333
333
0
1234, defg, 100
strtol()函數(shù)最多可以轉(zhuǎn)換三十六進制,'a'~'z'字符都可用作數(shù)字。strtoul()函數(shù)與該函數(shù)類似,但是它把字符串轉(zhuǎn)換成無符號值。strtod()函數(shù)只以十進制轉(zhuǎn)換,因此它值需要兩個參數(shù)。
小結(jié)
C字符串是一系列char類型的字符,以空字符('\0')結(jié)尾。字符串可以儲存在字符數(shù)組中。字符串還可以用字符串常量來表示,里面都是字符,括在雙引號中(空字符除外)。編譯器提供空字符。因此,"joy"被儲存為4個字符j、o、y和\0。strlen()函數(shù)可以統(tǒng)計字符串的長度,空字符不計算在內(nèi)。