重溫C語言(5)之字符串

原文: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.hatoi() 函數可以把字母數字轉換爲整數,該函數接受一個字符串作爲參數,返回相應的整數值。

    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()函數可以統計字符串的長度,空字符不計算在內。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章