1. 字符串操作
1.1 字符串遍歷
可以通過數(shù)組方式遍歷字符串拓挥。
char str[]="Hello World";
for(int i = 0;'\0' != str[i];++i){
printf("%c\n",str[i]);
}
也可以使用指針方式
char str[]="Hello World";
for(int i = 0;'\0' != *(str+i);++i){
printf("%c\n",*(str+i));
}
指針方式可以簡化成
char str[]="Hello World";
for(;'\0' != *str;++str){
printf("%c\n",*str);
}
甚至是
while('\0' != *str){
printf("%c\n",*str++);
}
while(*str){
printf("%c\n",*str++);
}
1.2 字符串賦值
char s[] = "Hello World";
char* t;
t = s;
printf("%s\n",t);
沒有產(chǎn)生新的字符串蝠检,只是s
和t
指向相同的字符串。下面是查看兩個字符串的地址颅和。
printf("%p\n",s);
printf("%p\n",t);
試一試
字符串的修改
char s[] = "Hello World";
char* t;
t = s;
printf("%s\n",t);
t[1] = 'o';
printf("%s\n",s);
printf("%s\n",t);
s[4] = 'e';
printf("%s\n",s);
printf("%s\n",t);
指針能否反向賦值給字符串名元莫?
char s[] = "Hello World";
char* t;
t = s;
s = t;
1.3 字符串輸入輸出
char str[8];
scanf("%s",str);
printf("%s\n",str);
scanf()
讀入一個單詞直到空白符(空格呜师、回車蓄愁、Tab)
scanf()
不安全双炕,因為不知道要讀入的內(nèi)容長度,容易溢出撮抓。
例如:輸入123456789
解決方式:指定讀取的長度妇斤。
char str[8];
scanf("%7s",str);
printf("%s\n",str);
%
與s
之間的數(shù)字表示最多允許輸入的字符數(shù),這個數(shù)字要比數(shù)組長度少1
丹拯。
printf()
輸出字符串的原理
char str[] = "Hello World";
for('\0'!=*str){
printf("%s\n",str++);
}
字符串作為特例(傳地址)存在站超,可以整體scanf()
和printf()
的原因在于結(jié)尾的\0
。
2. 字符串與函數(shù)
2.1 字符串傳參
字符串傳參方式與數(shù)組傳參方式一樣乖酬,只不過很多時候不需要傳遞字符串的長度(為什么死相?)。
void print_string(char str[]){
printf(str);
}
或者
void print_string(char* str){
printf(str);
}
2.2 字符串返回
字符串返回只能使用指針char*
3. 練習(xí)
- 交換
實現(xiàn)字符串交換函數(shù)void swap(char* s1,char* s2)
- 拼接
實現(xiàn)字符串拼接函數(shù)char* strcat(char* s1,char* s2)
- 拷貝
實現(xiàn)字符串拷貝函數(shù)char* strcpy(char* s1,char* s2)
- 比較
實現(xiàn)字符串相等判斷函數(shù)bool strcmp(char* s1,char* s2)
4. 字符串指針
數(shù)組可以用指針表示咬像。
字符數(shù)組可以直接初始化字符數(shù)組算撮,并且可以賦值給指針。
char s[]="Hello World";
char* p = s;
printf("%s\n",p);
字符串也可以直接賦值給指針县昂。這種指針稱為字符串指針肮柜。
char* str="Hello World";
printf("%s\n",str);
小結(jié)
字符數(shù)組和字符串指針初始化相似,只是形式上相似倒彰。
- 字符數(shù)組本質(zhì)是數(shù)組审洞,
char s[]="Hello World";
可以看作下面代碼的簡化版。char s[]={'H','e','l','l','o',' ','W','o','r','l','d','\0'};
- 字符串指針本質(zhì)是指針狸驳,
char* p="Hello World";
可以近似看作下面代碼的簡化版const char t[]="Hello World"; char* p = t;
字符串指針與字符數(shù)組的區(qū)別有以下三點:
-
sizeof
與strlen()
- 字符是否可修改
- 地址是否可修改
4.1 sizeof
與strlen()
#include <stdio.h>
#include <string.h>
int main(){
char arr[] = "Hello World";
char* ptr = "Hello World";
printf("sizeof(arr) = %ld\n",sizeof(arr));
printf("strlen(arr) = %ld\n",strlen(arr));
printf("sizeof(ptr) = %ld\n",sizeof(ptr));
printf("strlen(ptr) = %ld\n",strlen(ptr));
}
4.2 替換字符
試一下下面三個例子
- 修改字符數(shù)組
#include <stdio.h>
#include <string.h>
int main(){
char arr[] = "Hello World";
arr[0] = 'h';
arr[6] = 'w';
printf("%s\n",arr);
}
- 字符串指針
#include <stdio.h>
#include <string.h>
int main(){
char* ptr = "Hello World";;
*ptr = 'h';
*(ptr+6) = 'w';
printf("%s\n",ptr);
}
- 指向字符數(shù)組的字符串指針
#include <stdio.h>
#include <string.h>
int main(){
char arr[] = "Hello World";
char* ptr = arr;
*ptr = 'h';
*(ptr+6) = 'w';
printf("%s\n",ptr);
}
4.3 地址修改
char* p = "Hello";
p = "World";
char arr[] = "Hello";
arr = "World";
4.4 小結(jié)
字符串字面量初始化字符數(shù)組
字符串字面量初始化字符串指針
char* str1 = "Hello World";
char str2[] = "Hello World";
str1[5] = '\0'; // Error
str2[5] = '\0'; // OK
printf("str1=%s\n",str1);
printf("str2=%s\n",str2);
str1
是一個指針预明,初始化指向一個字符串常量。(在C99標準中耙箍,str1
報警告撰糠,提示應(yīng)該使用const char*
)
修改字符串常量可能會導(dǎo)致嚴重后果。
str2
是一個字符數(shù)組辩昆,初始化把字符串字面量自動復(fù)制到數(shù)組中阅酪。
如何選擇?
- 如果需要構(gòu)造字符串使用數(shù)組汁针;如果需要處理字符串使用指針(指向字符數(shù)組的指針)术辐。
- 字符串不需要修改使用字符串字面量初始化字符串指針。
- 字符串需要修改使用字符串字面量初始化字符數(shù)組施无。
決定字符串指針內(nèi)容能否修改的是指向的內(nèi)存辉词,而非指針。
5. 字符串const
-
const
字符數(shù)組
#include <stdio.h>
#include <string.h>
int main(){
const char arr[] = "Hello World";
arr[0] = 'h';
arr[6] = 'w';
printf("%s\n",arr);
}
- 指向
const
字符數(shù)組的字符串指針
#include <stdio.h>
#include <string.h>
int main(){
const char arr[] = "Hello World";
char* ptr = arr;
*ptr = 'h';
*(ptr+6) = 'w';
printf("%s\n",ptr);
}
決定能否修改的是指針指向的值能否修改猾骡。
const
的限制只針對定義為const
的變量瑞躺。
6. 字符串函數(shù)
6.1 字符串長度
size_t strlen(const char *s);
返回字符串長度不包含\0
敷搪。
6.2 字符串比較
int strcmp(const char *s1,const char *s2);
比較兩個字符串
返回0
,表示s1 == s2
返回>0
幢哨,表示s1 > s2
返回<0
赡勘,表示s1 < s2
為什么字符串不能直接比較?
為什么字符串比較會有大欣塘闸与?
6.3 字符串拷貝
char* strcpy(char* restrict dst,const char* restrict src);
把字符換src拷貝到dst。
restrict
是C99關(guān)鍵字岸售,表示指針指向內(nèi)存只使用當前指針修改践樱,便于編譯器優(yōu)化。在這里可表示dst
和src
內(nèi)存不存在重疊冰评,便于并行處理映胁。同時告知使用方不要使用重疊內(nèi)存的兩個指針使用該函數(shù)。
返回值為dst甲雅,便于連接解孙。
連續(xù)賦值。
復(fù)制一個字符串
// char* dst = (char*)malloc(strlen(src)+1);
char dst[strlen(src)+1];
strcpy(dst,src);
6.4 字符串連接
char* strcat(char* restrict s1,const char*restrict s2);
把s2拷貝到s1的后面抛人,拼接成一個長的字符串弛姜。
返回s1,注意:s1必須有足夠的空間。
char* a="Hello";
char* b="World";
char res[strlen(a)+strlen(b)+1] = {0};
strcat(strcat(res,a),b);
strcpy和strcat都會有安全問題:dst空間不足妖枚,出現(xiàn)越界廷臼。
6.5 字符查找
char* strchr(const char*s,int c);
char* strrchr(const char*s,int c);
返回找到字符的指針,沒找到返回NULL
如何查找第二個绝页?
6.6 子串查找
char* strstr(const char*s1,const char*s2);
char* strcasestr(const char*s1,const char*s2);
7. 文檔
學(xué)會通過例子荠商,學(xué)會函數(shù)的使用。
stdio.h
stdlib.h
string.h
復(fù)制
連接
比較
查找
- strchr:查找字符串中第一個出現(xiàn)指定字符的位置
- strrchr:查找字符串中最后一個出現(xiàn)指定字符的位置
- strstr:查找字符串中第一個出現(xiàn)指定子串的位置
- strtok:切分字符串
其他
練習(xí)
實現(xiàn)函數(shù)times(char* dst,int n,char* src)
8. 實踐
- 已知十天干和十二地支
天干:甲续誉、乙莱没、丙、丁酷鸦、戊饰躲、己、庚臼隔、辛嘹裂、壬、癸
地支:子摔握、丑寄狼、寅、卯氨淌、辰例嘱、巳狡逢、午、未拼卵、申、酉蛮艰、戌腋腮、亥
按順序打印出六十甲子
甲子、乙丑壤蚜、丙寅即寡、丁卯、戊辰袜刷、己巳聪富、庚午、辛未著蟹、壬申墩蔓、癸酉、
甲戌萧豆、乙亥奸披、丙子、丁丑涮雷、戊寅阵面、己卯、庚辰洪鸭、辛巳样刷、壬午、癸未览爵、
甲申置鼻、乙酉、丙戌拾枣、丁亥沃疮、戊子、己丑梅肤、庚寅司蔬、辛卯、壬辰姨蝴、癸巳俊啼、
甲午、乙未左医、丙申授帕、丁酉同木、戊戌、己亥跛十、庚子彤路、辛丑、壬寅芥映、癸卯洲尊、
甲辰、乙巳奈偏、丙午坞嘀、丁未、戊申惊来、己酉丽涩、庚戌、辛亥裁蚁、壬子矢渊、癸丑、
甲寅厘擂、乙卯昆淡、丙辰、丁巳刽严、戊午昂灵、己未、庚申舞萄、辛酉眨补、壬戌、癸亥
- 已知今年的紀年倒脓,輸入21世紀任意年份撑螺,打印出對應(yīng)的甲子。
- 已知今年的紀年崎弃,打印出21世紀所有年份的甲子甘晤。
- 已知小明同學(xué)的屬相,推斷出可能的年齡饲做。
9. 擴展
char*
一定是字符串嗎线婚?
char*
不一定是字符串,只有以0結(jié)尾的字符數(shù)組才是字符串盆均。
9.1 0
塞弊、'\0'
與'0'
0
與'\0'
'\0'
表示字符串的結(jié)束,但不是字符串的一部分。計算字符串長度時不包含'\0'
游沿。
字符串以數(shù)組方式存儲饰抒,可以用數(shù)組或者指針形式訪問。
9.2 空字符串
char str[10]="";
這是一個空字符串诀黍,str[0]
為\0
袋坑。
char str[]="";
這是也是一個空字符串,str
數(shù)組長度為1
眯勾。
常見錯誤:使用未初始化的char*
char* str;
printf("%s\n",str);
同常如果指針定義時無法確定初始值時咒彤,使用NULL
初始化指針。
9.3 字符串常量連接
兩個相鄰字符串常量會自動連接咒精。
char greeting = "Hello" "World";