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

原文: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.hatoi() 函數(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)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容