Outline:
· 字符串特性
· 字符串聲明
· 初始化方法
· I/O
· 字符串函數(shù)
————————
1.字符串常量
是用引號(hào)包裹的,通常作為printf() puts()的參數(shù),也可以用define來(lái)定義
如果字符串常量之間沒(méi)有間隔或間隔為空格,則ANSI C會(huì)把它們串聯(lián)起來(lái)加上一個(gè)空格。
字符串常量中使用雙引號(hào)需要轉(zhuǎn)義字符
字符串常量屬于靜態(tài)存儲(chǔ)類(lèi)(static storage),如果函數(shù)中用到字符串常量,不管調(diào)用這個(gè)函數(shù)多少次,這個(gè)字符串常量在程序運(yùn)行期間只存儲(chǔ)一份
- 字符串?dāng)?shù)組及初始化
可以申請(qǐng)足夠大的空間來(lái)放字符串
const char m1[40] = "hannahs"; //會(huì)自動(dòng)加結(jié)束符,如果用一個(gè)一個(gè)字符來(lái)初始化,要記得自己加空字
// 符'\0',否則只是字符數(shù)組而不是字符串
規(guī)定數(shù)組大小時(shí)至少要比字符數(shù)大一,多出來(lái)的位置編譯器會(huì)初始化為空字符
也可以讓編譯器決定數(shù)組大小。
用指針?lè)?hào)和用數(shù)組符號(hào)建立字符串都可以,指針?lè)?hào)允許自增自減而數(shù)組名不可以,字符串都是在靜態(tài)存儲(chǔ)區(qū),在程序運(yùn)行時(shí)為數(shù)組分配空間并把字符復(fù)制到數(shù)組中,而指針的話(huà)在程序運(yùn)行時(shí)復(fù)制了字符串的地址。初始化分別如下
char heart[] = "I love China.";
char * head = "I love Suzhou.";
兩者都可以用數(shù)組符號(hào)[]索引來(lái)取值,都可以用指針加法來(lái)取值(*(heart+i), *(head+i)),但只有指針可以自增
heart = head; // illegal
head = heart; //legal指針指向數(shù)組是可以的
指針用數(shù)組符號(hào)和索引進(jìn)行修改時(shí),會(huì)有Bus error內(nèi)存訪問(wèn)錯(cuò)誤
字符串?dāng)?shù)組
char * mystrs[5] = {"dafew","2dsfw","3sdf","4ds","5cs"}; // 數(shù)組存5個(gè)指針,指針指向字符串
//的第一個(gè)字符。雙引號(hào)用來(lái)初始化一個(gè)指針
可以用mystrs[0][0]表示第一個(gè)字符串的首字母
于是,可以用二維數(shù)組來(lái)創(chuàng)建字符串?dāng)?shù)組
char mystrs2[5][81];
這樣內(nèi)存大小就固定了,指針數(shù)組的話(huà)長(zhǎng)度是不固定的由初始化字符串決定長(zhǎng)度。一個(gè)放5個(gè)完整的字符串,另一個(gè)放5個(gè)指針
- 字符串輸入
常用printf(), gets(), fgets()
(1) gets()
以換行符為分割,函數(shù)讀取換行符后把結(jié)束符代替換行符。下一次的讀取就從新的一行開(kāi)始. gets()返回值是char指針,它的函數(shù)原型在stdio.h中
gets()的構(gòu)造如下:
char * gets( char * s){
...
return s;
}// 如果讀取出錯(cuò)會(huì)返回NULL
// so the error detection could be
while(gets(name)!=NULL){...}
(2) fgets()
gets()不檢查存儲(chǔ)區(qū)能不能放下實(shí)際輸入的數(shù)據(jù),多出來(lái)的溢出到相鄰內(nèi)存區(qū),所以不安全。fgets()有參數(shù)指定大小,還有參數(shù)指定從哪個(gè)文件輸入。
fgets(name, MAX, stdin); // MAX指定最多可讀入MAX-1個(gè)字符,stdin指定從鍵盤(pán)讀數(shù)據(jù)
fgets()讀取直到換行符,會(huì)存下?lián)Q行符,which is different from gets(),所以需要額外的動(dòng)作來(lái)定位且刪除換行符
(3) scanf()
從第一個(gè)非空白字符開(kāi)始讀,讀到(但不包括)下一個(gè)空白字符.如果指定了字段寬度如%10s,則讀10個(gè)字符或先遇到了空白字符。scanf的返回值為成功讀取的項(xiàng)目數(shù),或者遇到文件結(jié)束時(shí)返回EOF
char name1[11], name2[11];
printf("Enter 2 names\n");
scanf("%5d %10d", name1, name2);
printf("There are %s and %s", name1, name2);
輸入>>>Portensia Callowit
輸出>>>There are Porte and nsia
- 字符串輸出
puts(), fputs(), printf()
(1) puts
#define DEF "I am a string."
int main(){
char * str1 = "A pointer was initialized.";
char str2[80] = "An array was initialized.";
puts("arguments to put"); // puts()會(huì)自動(dòng)在字符串結(jié)尾加換行符,雙引號(hào)中的字符是常量,并被看作地址
puts(DEF);
puts(str1);
puts(str1+4); // 從第5個(gè)符號(hào)開(kāi)始打印到最后
puts(str2);
puts(&str2[4]);
}
puts()在遇到空字符的時(shí)候停下來(lái),一般字符串初始化會(huì)有空字符,但是字符數(shù)組是沒(méi)有空字符的!
(2) fputs()
需要第二個(gè)參數(shù)說(shuō)明寫(xiě)到哪個(gè)文件,用stdout輸出到屏幕。
fputs不加換行符!
char line[81];
while(gets(line)){
puts(line);} //讀取一行,在下一行回顯
-------------------
char line[81];
while(fgets(line,81,stdin)){
fputs(line,stdout);} // 回顯在同一行
(3) printf()略
- 自定義字符串輸入輸出函數(shù)
++優(yōu)先級(jí)比*高
while(*string != '\0')
while(*string) // 等價(jià)且beautiful
- 字符串函數(shù)
string.h
(1) strlen(a)
不計(jì)'\0'
(2) strcat(a, b)
把b的副本添加到a的后面,達(dá)到字符串連接的效果,返回第一個(gè)字符串
(3) strncat(a, b, n)
strcat不檢查第一個(gè)參數(shù)是否能容納連接后的字符串。strncat規(guī)定最多可以添加n個(gè)字符
(4) strcmp(a, b)
如果直接拿str1==str2的話(huà),不能進(jìn)行字符串比較,因?yàn)檫@里比的是指針,除非指向同一個(gè)地方等號(hào)才會(huì)成立。所以字符串的比較通過(guò)strcmp(),如果相同就返回0, 返回正數(shù)說(shuō)明a的ascii值大于b
ascii 65 為A,97為a
字符是可以直接比較的
(5) strncmp(a, b, n)
對(duì)前n個(gè)字符進(jìn)行比較
(6) strcpy & strncpy(target, src)
字符串復(fù)制, 把src指向的字符串復(fù)制到target指向的數(shù)組中, target需要分配好空間,不能亂指。返回值是target(第一個(gè)參數(shù))的值(一個(gè)地址)
// 判斷字符串是否以s開(kāi)頭
if(temp[0] == 's')
if(strncmp(temp, "s", 1) == 0) // 等價(jià)
strcpy()和gets()一樣,不檢查目標(biāo)是否能容納源字符串。strncpy()用第三個(gè)參數(shù)來(lái)規(guī)定最大可復(fù)制的字符數(shù)
(7) sprintf(target, "...%...", list)
跟printf差不多,多了第一個(gè)參數(shù)目的字符串地址,為了輸出到該地址instead of 屏幕
(8) strchr(target, c)
在目標(biāo)字符串中尋找第一個(gè)字符c的位置,找到則返回該地址,否則返回空指針
(9)strrchr(target, c)
返回一個(gè)指針,指向目標(biāo)串中最后一次出現(xiàn)c的地方
(10) strpbrk(const * char s1, const * char s2)
返回一個(gè)指針,指向了s2中任意一個(gè)字符which最早出現(xiàn)在s1中的位置
(11) strstr(s1,s2)
返回一個(gè)指針,指向s2第一次出現(xiàn)在s1中的位置(在1中找2)
// use fgets and remove \n
printf("Enter your name:\n");
char name[MAX];
fgets(name, MAX-1, stdin);
char * find;
find = strchr(name,'\n');
*find = '\0';
puts(name);
// 輸入多個(gè)字符串,判斷輸入結(jié)束
char input[MAX][40];
int cnt_in=0;
char tmp[40];
while (cnt_in<MAX && gets(tmp)!=NULL && tmp[0]!='\0')
{
strcpy(input[cnt_in++], tmp);
}
for(int i=0;i<cnt_in;i++){
puts(input[i]);
}
- 字符串轉(zhuǎn)數(shù)字
int atoi(str);
atoi可以轉(zhuǎn)換字符串形式的數(shù)據(jù),甚至可以轉(zhuǎn)換"42dsa"這樣的,取開(kāi)頭數(shù)字部分。如果沒(méi)有可以轉(zhuǎn)換的數(shù)字,可能返回0
atof()轉(zhuǎn)為double
atol()轉(zhuǎn)為long
ANSI C提供更復(fù)雜的函數(shù),strtol(3個(gè)參數(shù))轉(zhuǎn)為long, strtoul(3個(gè)參數(shù))轉(zhuǎn)為unsigned long, strtod(2個(gè)參數(shù))轉(zhuǎn)為double,它們復(fù)雜在可以識(shí)別并報(bào)告字符串中非數(shù)字部分的第一個(gè)字符
long strtol(char * ptr, char ** endptr, int base);
char * end;
long num = strtol(argv[1], &end, 10); // 以10進(jìn)制看待字符串?dāng)?shù)字,第二個(gè)參數(shù)是將指向結(jié)束指針的地址
當(dāng)然,逆向有itoa, ftoa
====================================================================
第一部分中用重定向讓程序和文件進(jìn)行通信,但它完全和文件通信會(huì)失去與用戶(hù)交互的機(jī)會(huì)。所以需要文件通信方法讓我們可以在程序中打開(kāi)文件然后用專(zhuān)門(mén)的I/O函數(shù)來(lái)讀取寫(xiě)入文件
C將文件看成連續(xù)的字節(jié)序列,文件的兩種視圖:文本視圖、二進(jìn)制視圖
MS-DOS:/r/n
Macintosh: /r
C : /n
fopen()
有兩個(gè)參數(shù),第一個(gè)是文件名(包含文件名字符串的地址),第二個(gè)是打開(kāi)模式
| model string | meaning |
|---|---|
| "r" | open and read file |
| "w" | open and write, 先將文件長(zhǎng)度截為0,如果不存在就創(chuàng)建 |
| "a" | open and write, 在文件尾追加內(nèi)容,如果不存在就創(chuàng)建 |
| "r+" | open 可以更新也可以寫(xiě)入 |
//各種搭配+號(hào),我也不懂。。帶b的表示以二進(jìn)制模式打開(kāi)
如果用w打開(kāi)文件,會(huì)清空文件原來(lái)的內(nèi)容
fopen函數(shù)返回FILE指針,并不指向?qū)嶋H的文件,而是指向關(guān)于文件信息的數(shù)據(jù)包,可以知道緩沖區(qū)位置和緩沖區(qū)相關(guān)信息
磁盤(pán)不夠、文件名非法、存取權(quán)限不夠或硬件問(wèn)題都可能導(dǎo)致fopen函數(shù)執(zhí)行失敗
getc() & putc()
與getchar() putchar()相似,需要告訴getc() putc()所使用的文件
ch = getc(fp); // 從fp指向的文件中得到一個(gè)字符
putc(ch, fp); // 寫(xiě)入
putc(ch, stdout);
putchar(ch); // equal
char ch;
FILE * fp;
fp = fopen("words","r");
while((ch = getc(fp))!=NULL){
putchar(ch);
}
fclose(fp)
關(guān)閉成功返回0,否則返回EOF
標(biāo)準(zhǔn)文件指針,都是FILE指針類(lèi)型
stdin
stdout
stderr
文件IO函數(shù)
(1)fprintf()
char words[MAX];
fprintf(stderr,"Close file failed.\n");
while(fscanf(fp,"%s",words)==1){ // 會(huì)自動(dòng)掃描文件從頭到尾
puts(words); // 回顯
}
(2) fscanf()
while(fscanf(fp,"%s",words)==1){
/*code*/}
other: puts() fputs() gets() fgets()
以上都是順序存取的IO函數(shù)
隨機(jī)存取:fseek() & ftell()
fseek()有3個(gè)參數(shù),第一個(gè)是FILE指針,第二個(gè)參數(shù)是offset且是Long類(lèi)型的(數(shù)字加L)整數(shù),第三個(gè)參數(shù)是起點(diǎn)模式
SEEK_SET:文件開(kāi)始
SEEK_CUR:當(dāng)前位置
SEEK_END:文件結(jié)尾
第二個(gè)參數(shù)為負(fù)時(shí)表示從起點(diǎn)向開(kāi)頭走N個(gè)字節(jié)
正常的話(huà)fseek返回0,如果試圖移動(dòng)超出文件范圍,會(huì)返回-1
ftell返回long類(lèi)型的文件當(dāng)前位置。。/* 不會(huì) */
long count, last;
fseek(fp, 0L, SEEK_END); // 定位到文件尾
last = ftell(fp); // 返回文件的字節(jié)長(zhǎng)度
for(count; count<=last; count++){
fseek(fp, -count, SEEK_END);
ch = getc(fp); // fp會(huì)變???
}
在二進(jìn)制模式下,C不支持SEEK_END模式
/* 懵 */
fgetpos() & fsetpos()
Long的范圍大概在正負(fù)20億,fseek ftell處理大文件還是有問(wèn)題的
這倆函數(shù)用fpos_t這個(gè)新類(lèi)型來(lái)代表位置
fgetpos()函數(shù)原型:
int fgetpos(FILE * restrict stream, fpos_t * restrict pos);
函數(shù)在pos位置上放置一個(gè)fpos_t值,成功返回0,否則返回非零
int fsetpos(FILE *steam, const fpos_t *pos);
int ungetc(int c, FILE * fp);
char word[10];
getchar(); // 消耗一個(gè)字符
ungetc('a',stdin); // 把a(bǔ)放回輸入流, 嘗試文件指針不得行
scanf("%s",word);
puts(word); // 得到a開(kāi)頭的單詞
int fflush(FILE * fp);
把緩沖區(qū)未寫(xiě)的數(shù)據(jù)發(fā)到fp,如果fp是空指針,就刷新輸出緩沖
setvbuf()
二進(jìn)制IO
int fwrite(const void * restrict ptr, size_t size, size_t nmemb, FILE * fp);
// 保存一個(gè)256字節(jié)大小的數(shù)據(jù)對(duì)象
char buffer[256];
fwrite(buffer, 256, 1, fp);
// 保存10個(gè)double值的數(shù)組
double earnings[10];
fwrite(earnings, sizeof(double), 10, fp);
//fread
fread(earnings, sizeof (double), 10, fp);// 把文件中的值復(fù)制到數(shù)組中
函數(shù)原型中ptr 是void類(lèi)型因?yàn)閿?shù)組類(lèi)型不一定,void是可以cast到其他類(lèi)型的
if (feof(fp)){
//文件讀取結(jié)束(feof返回非0值)后執(zhí)行的代碼
}
if(ferror(fp)){
//文件讀取錯(cuò)誤(ferror返回非0值)后執(zhí)行的代碼
}
fread fwrite可以保留精度
getc fprintf保存文本信息