原文:https://mp.weixin.qq.com/s/Onzk-a16uHG1QJ1VXSjsmA
前言
字符串可以說是C語言中最有用、最重要的數據類型了。
字符串是以空字符(\0)結尾的char類型數組。
用雙引號擴起來的內容是字符串字面量(或着叫字符串常量)。
字符串常量屬於靜態存儲類別,即只要在函數中使用字符串常量,這個字符串只會被存儲一次,並且存活在整個程序生命週期內。
//
// 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 函數只顯示字符串,並且自動在末尾加換行符
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'}; // 這是字符數組
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
字符串與數組回顧:
#include <stdio.h>
#define MSG "define a message"
int main() {
// 字符串存儲在靜態存儲區
// 在程序運行的時候纔給 a 數組分配內存,之後纔將字符串'拷貝'到數組中
char a[] = MSG;
// 字符串字面量是常量 const 數據
// 由於 pt 指向const數據,所以 pt 應該聲明爲 const 數據到指針
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 函數中出現了兩次,但是編譯器只使用了一個存儲位置,而且與MSG的地址相同。
編譯器可以把多次使用的相同字面量儲存在一處或多處。
數組的元素是變量(除非數組被聲明爲const),但是數組名是地址常量。
二維字符數組每個數組的分配空間是一樣的,指針數組的分配空間更靈活也更高效但是不能修改內容;
指針和字符串
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("已經接受到你到想法: %s\n", think);
puts(think);
請輸入你的想法
warning: this program uses gets(), which is unsafe.
i see you, balalalalal...
已經接受到你到想法: i see you, balalalalal...
i see you, balalalalal...
gets() 無法檢查數組是否能夠存儲該行,只能知道數組的開始處。如果輸入的字符串過長,就會造成緩衝區溢出(多餘的字符超出了指定空間).
輸入超過數組容量的字符串會提示 Process finished with exit code 11
, 說明該程序試圖訪問未分配的內存, 這樣的行爲是不安全的,可能會被插入一些破壞系統的代碼。
fgets()
char think[5];
puts("請輸入你的想法");
fgets(think, 5, stdin); // 第二個參數表示讀入字符的最大長度數量,第三個參數是讀入第文件 stdin 是標準輸入
puts(think);
fputs(think, stdout); // stdout 標準輸出
請輸入你的想法
abcdefgh
abcd
abcd
輸入的abcdefgh\n
, 其實只存儲了 abcd\0
fgets()
函數會存儲換行符,而gets()
不會:
請輸入你的想法
123
123
123
有時候不需要fgets()
最後存儲的換行符,那麼我們可以進行如下騷操作進行處理:
while(words[i] != '\n')//假設\n在words中
I++;
words[i] = '\0';
下面是個實用的示例:
char think[5];
int I;
puts("請輸入你的想法");
// 重複獲取輸入內容,直到文件末尾或該行第一個字符是換行符時結束
while (fgets(think, 5, stdin) != NULL && think[0] != '\n'){
i = 0;
// 找到該行的結尾,如果是換行符則該行內容沒有超出規定大小
while (think[i] != '\n' && think[i] != '\0')
I++;
if (think[i] == '\n'){
think[i] = '\0';
} else{
// 這個分支是該行內容多與規定大小,爲了捨棄多餘字符,讓剩餘的字符讀取但不存儲, 包括遇到但第一個換行符
while (getchar() != '\n'){}
}
puts(think);
}
\0
是空字符的意思,是整數類型,是一個字符佔用了1個字節,是用於標記C字符串末尾的字符,其對應字符編碼是0。由於其他字符的編碼不可能是0,所以不可能是字符串的一部。
NULL
是空指針的意思,是指針類型,是一個地址佔用了4個字節,該值不會與任何數據的有效地址對應。
gets_s()
- 只從標準輸入中讀取數據;
- 讀到換行符會丟棄不存儲;
- 如果讀到最大字符數但是沒有讀到換行符,會把最大字符數下的字符設置爲空字符,讀取並丟棄隨後的輸入直至讀到換行符或文件結尾,然後返回空指針,可能會中止或退出程序。
讀取整行輸入並用空字符代替換行符,或者讀取一部分輸入,並丟棄其餘部分
char * afra_gets(char* think, int n){
// 重複獲取輸入內容,直到文件末尾或該行第一個字符是換行符時結束
while (fgets(think, n, stdin) != NULL && think[0] != '\n'){
i = 0;
// 找到該行的結尾,如果是換行符則該行內容沒有超出規定大小
while (think[i] != '\n' && think[i] != '\0')
I++;
if (think[i] == '\n'){
think[i] = '\0';
} else{
// 這個分支是該行內容多與規定大小,爲了捨棄多餘字符,讓剩餘的字符讀取但不存儲, 包括遇到但第一個換行符
while (getchar() != '\n'){}
}
puts(think);
}
}
如果不捨棄多出來的字符,那些字符會留在緩衝區,成爲下一次讀取的輸入。
scanf()
語句 | 輸入內容 | temp 的內容 | 剩餘的內容 |
---|---|---|---|
scanf("%s", temp); | Hi Afra | Hi | Afra |
scanf("%5s", temp); | Hi Afra | Hi | Afra |
scanf("%5s", temp); | Afra55Blalal | Afra55 | Blalal |
scanf()
返回一個整數值 是讀取成功的總個數,或文件結尾時反回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()
在顯示字符串時會自動在其末尾添加一個換行符。該函數在遇到空字符時就停止輸出,所以必須確保有空字符。
fputs()
fputs()
不會再輸出的末尾加上換行符。
fputs(str2, stdout);
fputs("is fputs", stdout);
I come from your dream!is fputs
printf()
printf()
不會自動在每個字符串末尾加上一個換行符。因此,必須在參數中指明應該在哪裏使用換行符。
printf()
雖然更復雜,但是打印多個字符串更簡單。
自定義輸入輸出
可以使用 getchar()
putchar()
單個字符的輸入輸出來自定義。
字符串函數
strlen()
統計字符串長度:
char a[] = "I Love U\0Hi hahahahah";
puts(a);
printf("%lu\n", strlen(a));
puts(a + 9);
I Love U
8
Hi hahahahah
strcat()
拼接兩個字符串,接受兩個字符串作爲參數,把第2個字符串的備份附加在第1個字符串末尾,並把拼接後形成的新字符串作爲第1個字符串,第2個字符串不變。strcat()
函數的類型是char *
(即,指向char的指針)。strcat()
函數返回第1個參數,即拼接第2個字符串後的第1個字符串的地址。
char a[10] = "I u.";
char b[] = "you";
strcat(a, b);
puts(a);
puts(b);
I u.you
you
strcat()
函數無法檢查第一個數組是否容納第二個數組,如果分配給第一個數組的空間不大,多出來的字符溢出到相鄰存儲單元就會出現問題。
strncat()
函數可以傳入第三個參數,用來指定最大添加到字符數。
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()中就返回負數;反之,strcmp()則返回正數。
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標準規定,在字母表中,如果第1個字符串在第2個字符串前面,strcmp()返回一個負數;如果兩個字符串相同,strcmp()返回0;如果第1個字符串在第2個字符串後面,strcmp()返回正數。然而,返回的具體值取決於實現。
char
類型實際上是整數類型,可以使用關係運算符來比較字符。
strncmp()
strcmp()函數比較字符串中的字符,直到發現不同的字符爲止,這一過程可能會持續到字符串的末尾。而strncmp()函數在比較兩個字符串時,可以比較到字符不同的地方,也可以只比較第3個參數指定的字符數。例如,要查找以"victor"開頭的字符串,可以限定函數只查找這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個參數指向的字符串被拷貝至第1個參數指向的數組中。拷貝出來的字符串被稱爲目標字符串,最初的字符串被稱爲源字符串。參考賦值表達式語句,很容易記住strcpy()
參數的順序,即第1個是目標字符串,第2個是源字符串。
strcpy()
的返回類型是char*
,該函數返回的是第1個參數的值,即一個字符的地址;
第1個參數不必指向數組的開始。這個屬性可用於拷貝數組的一部分。
拷貝字符串用strncpy()
更安全,該函數的第3個參數指明可拷貝的最大字符數。
strncpy(target,source,n)把source中的n個字符或空字符之前的字符(先滿足哪個條件就拷貝到何處)拷貝至target中。因此,如果source中的字符數小於n,則拷貝整個字符串,包括空字符。但是,strncpy()拷貝字符串的長度不會超過n,如果拷貝到第n個字符時還未拷貝完整個源字符串,就不會拷貝空字符。所以,拷貝的副本中不一定有空字符。鑑於此,該程序把n設置爲比目標數組大小少1(TARGSIZE1),然後把數組最後一個元素設置爲空字符.
sprintf()
sprintf()函數聲明在stdio.h中,而不是在string.h中。該函數和printf()類似,但是它是把數據寫入字符串,而不是打印在顯示器上。因此,該函數可以把多個元素組合成一個字符串。sprintf()的第1個參數是目標字符串的地址。其餘參數和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);
該函數把s2指向的字符串(包括空字符)拷貝至s1指向的位置,返回值是s1。 -
char * strncpy(char * restrict s1, const char * restrict s2, size_tn);
該函數把s2指向的字符串拷貝至s1指向的位置,拷貝的字符數不超過n,其返回值是s1。該函數不會拷貝空字符後面的字符,如果源字符串的字符少於n個,目標字符串就以拷貝的空字符結尾;如果源字符串有n個或超過n個字符,就不拷貝空字符。 -
char * strcat(char * restrict s1, const char * restrict s2);
該函數把s2指向的字符串拷貝至s1指向的字符串末尾。s2字符串的第1個字符將覆蓋s1字符串末尾的空字符。該函數返回s1。 -
char * strncat(char * restrict s1, const char * restrict s2, size_tn);
該函數把s2字符串中的n個字符拷貝至s1字符串末尾。s2字符串的第1個字符將覆蓋s1字符串末尾的空字符。不會拷貝s2字符串中空字符和其後的字符,並在拷貝字符的末尾添加一個空字符。該函數返回s1。 -
int strcmp(const char * s1,const char * s2);
如果s1字符串在機器排序序列中位於s2字符串的後面,該函數返回一個正數;如果兩個字符串相等,則返回0;如果s1字符串在機器排序序列中位於s2字符串的前面,則返回一個負數。 -
int strncmp(const char * s1, const char * s2, size_tn);
該函數的作用和strcmp()
類似,不同的是,該函數在比較n個字符後或遇到第1個空字符時停止比較。 -
char * strchr(const char * s, int c);
如果s字符串中包含c字符,該函數返回指向s字符串首次出現的c字符的指針(末尾的空字符也是字符串的一部分,所以在查找範圍內);如果在字符串s中未找到c字符,該函數則返回空指針。 -
char * strpbrk(const char * s1, const char * s2);
如果s1字符中包含s2字符串中的任意字符,該函數返回指向s1字符串首位置的指針;如果在s1字符串中未找到任何s2字符串中的字符,則返回空字符。 -
char * strrchr(const char * s, int c);
該函數返回s字符串中c字符的最後一次出現的位置(末尾的空字符也是字符串的一部分,所以在查找範圍內)。如果未找到c字符,則返回空指針。 -
char * strstr(const char * s1, const char * s2);
該函數返回指向s1字符串中s2字符串出現的首位置。如果在s1中沒有找到s2,則返回空指針。 -
size_tstrlen(const char * s);
該函數返回s字符串中的字符數,不包括末尾的空字符。
關鍵字restrict限制了函數參數的用法。例如,不能把字符串拷貝給本身。
ctype.h
toupper()
函數用於大寫字符串:
void toUpperChar(char * a){
while (*a){
*a = toupper(*a);
a++;
}
}
char a[] ="abcdef";
toUpperChar(a);
puts(a);
ABCDEF
利用ispunct()
統計字符串中的標點符號個數:
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 個
命令行參數
C編譯器允許main()
沒有參數或者有兩個參數(一些實現允許main()
有更多參數,屬於對標準的擴展)。main()
有兩個參數時,第1個參數是命令行中的字符串數量。過去,這個int
類型的參數被稱爲argc
(表示參數計數(argumentcount)
)。系統用空格表示一個字符串的結束和下一個字符串的開始。
int main(int argc,char **argv)
int main(int argc,char *argv[])
假如有個程序 hello.c
, 輸入命令 hello i love you
, 那麼 main()
函數的 argc = 4
, argv 共有四個字符串,後三個用於 hello 程序去使用,char **argv = {"hello", "i", "love", "you"}
。
如果 對命令行參數加上雙引號,那麼就認爲這是一個單詞,例如:
hello "i love you"
那麼argc = 2
, char **argv = {"hello", "i love you"}
。
把字符串轉換爲數字
stdlib.h
有 atoi()
函數可以把字母數字轉換爲整數,該函數接受一個字符串作爲參數,返回相應的整數值。
printf("%d\n", atoi("333"));
printf("%d\n", atoi("333abcde"));
printf("%d\n", atoi("abced333"));
333
333
0
atof()
函數把字符串轉換成double
類型的值,atol()
函數把字符串轉換成long
類型的值.
srtol()
把字符串轉換成long
類型的值,strtoul()
把字符串轉換成unsigned long
類型的值,strtod()
把字符串轉換成double
類型的值。
long strtol(const char * restrict nptr, char ** restrict endptr, int base);
nptr是指向待轉換字符串的指針,endptr是一個指針的地址,該指針被設置爲標誌輸入數字結束字符的地址,base 表示以什麼進制寫入數字(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()
函數最多可以轉換三十六進制,'a'~'z'字符都可用作數字。strtoul()
函數與該函數類似,但是它把字符串轉換成無符號值。strtod()
函數只以十進制轉換,因此它值需要兩個參數。
小結
C字符串是一系列char類型的字符,以空字符('\0')結尾。字符串可以儲存在字符數組中。字符串還可以用字符串常量來表示,裏面都是字符,括在雙引號中(空字符除外)。編譯器提供空字符。因此,"joy"被儲存爲4個字符j、o、y和\0。strlen()函數可以統計字符串的長度,空字符不計算在內。