十一、字符串
🎈C語言中的字符串
在C語言中 字符串是這樣的:
char msg[]={'H','e','l','l','o','\0'};
在字符數組的末尾有一個\0
在C語言中 字符串是以整數0結尾的一串字符
0和’\0’相同 標誌着字符串的結束 但只是作爲一個標記的作用 (’\0’代表的是整數0 和’0’是完全不一樣的)
但他並不是字符串的一部分 因此在計算字符串長度的時候也不包括這個0
在C語言中 字符串是以字符數組的形式存在的 (這才叫字符"串" 哈哈)
以數組或指針的形式來訪問字符串 但更多以指針訪問
🚩字符串變量
有好幾種方法可以定義字符串變量
char *str="Hello";
char strArr[]="Hello";
char strArr[10]="Hello";
在以字面量的形式定義字符串變量後 編譯器會自動在末尾生成一個0標識符
🚩字符串常量
比如 定義了一個字符串"Hello"
這個"Hello"會被編譯器轉換成一個字符數組放在內存中
這個數組的長度爲6 而不是5 因爲在末尾還有一個表示字符串結束的0 只是看不見而已
兩個相鄰的字符串常量會被自動連接(它們中間不能有任何符號)
比如:
printf("你是一個一個一個一個..."
"啊啊啊啊啊"); // 你是一個一個一個一個...啊啊啊啊啊
當然 也可以使用/
來連接 但此時行的連接處不能有引號 且換行後前面不能有縮進 否則縮進也會被算在字符串內
比如:
printf("你是一個一個一個一個...\
啊啊啊啊啊"); // 你是一個一個一個一個... 啊啊啊啊啊
這樣纔對:
printf("你是一個一個一個一個...\
啊啊啊啊啊"); // 你是一個一個一個一個...啊啊啊啊啊
C語言中 字符串是以字符數組的形式存在的 因此 不能用傳統的運算符來對字符串進行運算
(Java可以用加號+
來連接字符串 但是C語言不可以)
但是可以用字符串形式的字面量來初始化字符數組
比如:
char *str="Hello";
通過數組的方式可以遍歷字符串
🎈字符串的創建方式
✨字符串可以使用指針的創建方式:
char* str="Hello";
當創建了兩個相同的字符串 它們所指向的位置實際上是同一個地方:
char* s1="Hello";
char* s2="Hello";
printf("%p\n",s1); // 00405064
printf("%p\n",s2); // 00405064
在字符串被創建後 實際上該常量是一個指針 指向了內存中的一塊代碼段區域 字符數組就存放在這
這塊區域是只讀的 無法寫入 因此當寫入時候 程序會崩潰
因此 實際上char *str="Hello"是const char *str=“Hello”
✨若要使用可修改的字符串 應該用數組的創建方式char strArr[]="Hello";
char s[]="Hello";
printf("%c\n",s[0]); // H
s[0]='P';
printf("%c\n",s[0]); // P
🎈字符串的創建、賦值、輸入輸出
創建方式的選擇:
- 若創建爲數組 那麼字符串的存放位置就在當前位置
而且作爲本地變量 空間是會被自動回收的 - 若創建爲指針 那麼這個字符串的存放位置不知道在哪
通常用於作爲函數的指針參數 或者用於malloc的動態分配空間
🚩字符串的賦值
char *s1="Hello";
char *s2;
s2=s1;
由於採用的是指針方式創建的字符串 因此實際上在該賦值過程中 並沒有產生新的字符串
只是讓指針s2指向了指針s1所指的字符串的位置 即 對s2的任何操作 都是對s2做的
s1和s2是共享內存位置
🚩字符串的輸入和輸出
對於字符串 在C中 使用 %s
來輸入和輸出 (相信已經猜到了 s就是string)
在讀入字符串的時候 到空格 或 tab 或 回車爲止
空格 或 tab 或 回車是作爲分隔符 因此並不會被算在讀入的字符串中
在讀入的時候 有可能會遇到數組越界問題 有可能會導致程序崩潰
char word[8];
scanf("%7s",word); // 最多讀取7個 剩下留一個位置用於存放最後面的0
printf("%s##",word);
可以用 %ns
的格式來 限制最多能讀取多少個字符 比如%7s爲最多讀取7個字符 超過的部分就不讀了
因此 若同時讀入到多個數組 在到達讀取上限後 下個數組會從上次讀取上限的地方開始讀
比如:
char word1[3];
char word2[3];
scanf("%2s %2s",word1,word2); // 輸入123456789
printf("%s\n",word1); // 12
printf("%s",word2); // 34
❗注意
在用指針方式創建數組的時候要進行初始化
不要這麼用:
char *s;
scanf("%s",&s);
因爲 char*並不是字符串類型 而是指針 當指向的位置無法存入 那麼程序就會崩潰
因此 在創建指針的時候 要初始化爲0
像這樣:
char *s=0;
scanf("%s",&s);
🎈字符串數組
用一個數組來表示很多字符串
可以這麼寫:
char *arr[];
其意義是 有個數組 其中的每一項存放的都不是確切的值 而是一個指針 該指針指向的位置是所要存放的字符串單元
比如 arr[0]
存放的是Hello\0的指針 該指針指向一個位置 這個位置保存着Hello\0這麼一個字符串
arr[1]
存放的是World\0的指針 該指針指向一個位置 這個位置保存着World\0這麼一個字符串
以此類推
例子:
int i;
char *arr[]={"AA","BB","CC","DD","EE"};
for (i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
{
printf("%s\n",arr[i]);
}
🎈字符串函數
字符串處理函數放置於string.h中 因此若要使用字符串 則必須先引入#include <string.h>
裏面有:
- strlen
- strcmp
- strcpy
- strcat
- strchr
- strstr
🚩getchar & putchar / 讀 & 寫
【getchar】:
從標準輸入讀入一個字符
語法:
int getchar(void)
返回值是int 這是因爲方便返回EOF(-1)代表輸入結束
【putchar】:
向標準輸出(即黑窗口)寫一個字符
語法:
int putchar(int c)
返回值是 寫了幾個字符 若返回EOF(-1) 則代表寫失敗
鍵盤輸入的值是先交給shell 然後shell處理後再交給程序
當未按下回車前 所有輸入的值都在shell裏
然後一旦按下回車 就會將值存入shell中的緩衝區(回車符也會被存入緩衝區) 程序的getchar從shell的緩衝區中讀數據 然後進行處理
因此 當鍵盤按下Ctrl+V(Unix是Ctrl+D)時 在shell內部會通過一種方式 改變控制程序將要退出的狀態碼
然後再通過getchar去讀取的時候 讀到了程序將要退出的狀態碼 那麼便會退出
當鍵盤按下Ctrl+C 那麼直接關閉程序了
int ch;
while((ch=getchar())!=EOF)
{
putchar(ch);
}
printf("EOF\n");
🚩strlen / 獲取長度
strlen:string length
返回傳入的字符串的長度
語法:
size_t strlen(const char *s)
例:
char c[]="Hello";
printf("%lu\n",strlen(c)); // 5
printf("%lu",sizeof(c)); // 6 因爲後面還有一個\0代表字符串的結束
🚩strcmp / 比較
strcmp:string compare
比較兩個字符串 不僅能比較是否相等 還能比較大小
語法:
int strcmp(const char *s1,const char *s2)
返回的是兩個字符串之間的差值
因此 若相等 則返回 0
char s1[]="Hello";
char s2[]="Hello";
printf("%d",strcmp(s1,s2)); // 0
後面的若大於前面的 則返回 1
後面的若小於前面的 則返回 -1
char s1[]="abc";
char s2[]="CBc";
printf("%d\n",strcmp(s1,s2)); // 1
當然 該函數還有一個版本 可以限定只比較前面多少位字符
int *strncmp(char *restrict s1,const char *restrict s2,size_t n);
🚩strcpy / 複製
strcpy:string copy
將第二個參數的字符串拷貝到第一個參數的字符串空間中
語法:
char *strcpy(char *restrict dst,const char *restrict src)
// 參數是(目的,源),而不是(源,目的)
restrict是C99的關鍵字 意爲dst和src不能是重疊的(即字符空間不能有重疊)
返回值是dst 即拷貝後的值(或者可以說是複製品)
其目的是爲了能夠使用鏈式編程 使得代碼能夠串聯 返回值再次作爲其它函數的參數
複製一個字符串:
char src[]="Hello";
char *dst=(char*)malloc(strlen(src)+1); // 切記要加一 留給最末尾的0
strcpy(dst,src);
printf("%s",dst); // Hello
🚩strcat / 連接
strcat:string concat
將第二個參數的字符串接到第一個參數的字符串後面
返回拼接後的第一個字符串(前提是第一個字符串必須要具有足夠的空間)
語法:
char *strcat(char *restrict s1,const char *restrict s2)
其基本實現思路就是將第一個參數的最後一位(即結尾符0)替換爲第二個參數的字符串的第一位
連接也可以看作是一種拷貝
🚧安全問題
strcpy和strcat都可能產生安全問題
若被拷貝的"目的地"沒有足夠的空間 那麼可能會造成數組越界
解決方法就是使用安全版本:
char *strncpy(char *restrict dst,const char *restrict src,size_t n);
char *strncat(char *restrict s1,const char *restrict s2,size_t n);
安全版本加了個n
其意思爲 限制最多能夠拷貝/連接多少個字符 若超出限定值 那麼會去掉 超出的範圍不會被拷貝/連接
🚩str[r]chr / 字符串中查找字符
strchr:string character
strrchr:string rear/right character
搜索函數可以在字符串中查找指定的字符(可以從左找也可以從右找)
語法:
char *strchr(const char *s,int c); // 從左找
和
char *strrchr(const char *s,int c); // 從右找(right)
若返回NULL 則代表未找到
若找到了 則會返回要找的那個字符在該字符串中的指針
char s[]="Hello";
char *p=strchr(s,'e');
printf("%s",p); // ello
🚩str[case]str / 字符串中查找字符串
strstr:string string
strcasestr:string case string
在字符串中查找指定的字符串(可以忽略大小寫)
語法:
char *strstr(const char *s1,const char *s2); // 在字符串中查找指定的字符串
char *strcasestr(const char *s1,const char *s2); // 在字符串中忽略大小寫查找指定的字符串
若返回NULL 則代表未找到
若找到了 則會返回要找的那個字符在該字符串中的指針
char s1[]="Hello";
char s2[]="ell";
char *p=strstr(s1,s2);
printf("%s",p); // ello