emmmmm.......
詳解:
問題:
對讀入的某個文本文件input.txt中,拆出英文單詞,輸出一個按字典順序排列的單詞表,結(jié)果輸出在文本文件output.txt中,每個單詞一行,并在單詞后輸出該單詞出現(xiàn)的個數(shù),兩個字段之間用逗號分隔。約定單詞僅由英文字母組成,單詞間由非英文字母分隔,相同單詞只輸出一個,大小寫不區(qū)分。
例如文本文件input.txt為:
Hello world.
Hello every one.
Let us go.
則輸出文本文件output.txt為:
every,1
go,1
hello,2
let,1
one,1
us,1
world,1
給的結(jié)構(gòu)體
struct myword
{
int num;
char *word;
struct myword *next; /* 指向下一個節(jié)點 */
};
這個問題拿到了之后,可以發(fā)現(xiàn)程序涉及兩個文件一個input.txt,這個在程序里用的test.txt進行測試,輸出文件out.txt。
我們從文件input文件中讀單詞,然后統(tǒng)計所有的單詞個數(shù),輸出到output。
所以整個程序的流程可以劃分為:
main()
1.打開文件input.txt
2.找單詞,并將其存入鏈表 struct myword *find_words(FILE *input)
3.將鏈表輸出到文件 void print_link_to_file(struct myword *head)
4.釋放鏈表所占的空間 void destory_word(struct myword *head)
我們可以看下這個main()函數(shù)
void main()
{
/*讀取infile_name文件,用find_words找到單詞,存儲到head鏈表,最后將鏈表輸出到文件*/
void print_link_to_file(struct myword *head); //輸出鏈表到文件
struct myword *find_words(FILE *input); //找單詞,輸出單詞鏈表
void destory_word(struct myword *head); //釋放鏈表內(nèi)存
char infile_name[]={"word.txt"};
struct myword *head;
FILE *input;
input = fopen(infile_name, "r"); //打開文件
if (input == NULL)
{
printf("打開文件%s失敗\n",infile_name);
exit(0);
}
head = find_words(input); //找單詞
fclose(input); //關(guān)閉文件
print_link_to_file(head); //將鏈表輸出到文件
}
程序的主要功能有三個,對應上面的找單詞并將其存入鏈表、輸出到文件、釋放鏈表空間。
定義的是返回空類型的void main()函數(shù),前三行是聲明我們的三個函數(shù)。最后三行是使用。
具體實現(xiàn)我們到后面說:
- find_words(FILE *input) 是一個輸入文件指針類型的函數(shù),輸出的結(jié)構(gòu)體指針
- print_link_to_file(struct myword *head) 輸入的是結(jié)構(gòu)體指針,這里我們輸入的是鏈表的頭結(jié)點,空類型函數(shù)
- destory_word(struct myword *head) 輸入結(jié)構(gòu)體指針,也是鏈表頭節(jié)點,釋放鏈表所占的空間
其中最復雜的就是find_words()
我們倒著來,從簡單的開始:
destory_word(struct myword *head) //釋放內(nèi)存
我們在使用完鏈表之后,程序結(jié)束了,但是這些鏈表還是在內(nèi)存中,所以我們在用完了之后要將內(nèi)存釋放掉。方法很簡單,遍歷整個鏈表,將每一個節(jié)點的空間都是釋放掉。每一個節(jié)點都是一個myword結(jié)構(gòu)體。
void destory_word(struct myword *head)
{
printf("start free!\n");
struct myword *tmp;
while(head!=NULL)
{
tmp = head;
head = head->next;
free(tmp);
}
printf("Free Done!\n");
}
遍歷整個鏈表,用tmp保存當前頭結(jié)點,然后,頭結(jié)點指向下一個節(jié)點,然后釋放tmp的內(nèi)存,繼續(xù)遍歷下一個。
然后是輸出到鏈表。
void print_link_to_file(struct myword *head)
這是個空類型函數(shù),輸入鏈表頭,和剛才一樣遍歷鏈表,不過把釋放空間的操作換成輸出到文件。這里我們打開一個output.txt文件來保存文件。
void print_link_to_file(struct myword *head)
{
/*輸入鏈表,將鏈表內(nèi)容輸出到input.txt*/
printf("NOW,print LINK to File......\n");
struct myword *p;
FILE *output;
char outfile_name[]={"output.txt"};
output = fopen(outfile_name, "w");
if (output == NULL)
{
printf("打開文件%s失敗\n",outfile_name);
exit(0);
}
p = head->next;
do
{
fprintf(output,"%s,%d\n",p->word,p->num);
p = p->next;
}while(p!=NULL);
fclose(output);
printf("print to file Done!\n");
}
這里也是非常簡單的,不過這里我用的do...while語句,這是書上看來的,也可以改寫成while語句。這里不多說,下面開始找單詞環(huán)節(jié)。
find_words(FILE *input)
在開始之前,我們先想一想如何找單詞?根據(jù)題目來看,兩個單詞之間是用的非字符分割的,所以這里我們把類似于“point-to-point”這種看做三個單詞而不是一個,降低難度。
我們讀文件有三種,每次讀一個字符,每次讀一串字符,還有就是隨機讀寫。
我在開始寫之前想過好幾種方案,還有的是室友想的,我一股腦先都放出來:
- 每次讀一行,將它存到數(shù)組中,然后判斷數(shù)組中的單詞數(shù),當然也是遍歷數(shù)組,遇到非字母就分隔開,將前面的看做一個單詞。生成節(jié)點,插入鏈表,然后接著讀文件。
- 每次讀一個字符,如果遇到字母,就將其存到數(shù)組中,直到遇到非字母,然后把數(shù)組中的字符串看做一個單詞。生成節(jié)點,插入鏈表,然后接著讀文件。
- 每次讀一個文件,如果是字母則寫入文件tmp.txt中,如果遇到非字母,或者換行,則在tmp.txt中寫入換行。完成所有的讀寫后,在重新開始讀tmp.txt,每次讀一行,把每一行的單詞讀出,生成節(jié)點,插入鏈表,然后接著讀文件。
前兩個我寫的,第三個是室友想的。emmm。。。這個文件一定長。
先不管效率,三種理論上都能讀出單詞。
我是用的第二種方法:
struct myword *find_words(FILE *input)
{
/*輸入文件指針,找到單詞,生成鏈表,輸出鏈表頭head,
從文件中一個字符一個字符的讀取,如果遇到字母則將其存入
字符數(shù)組中,直到遇到非字母,
【在數(shù)組的后面加入'\0',以此來分割數(shù)組中的
單詞,因為前一個單詞可能比當前單詞長】
則數(shù)組中的就是一個單詞,
生成節(jié)點,并插入相應位置。
用字符數(shù)組保存當前單詞*/
printf("start find Words....\n");
struct myword *head, *tmp;
struct myword *instert_word(struct myword *tmp,struct myword *head); //插入節(jié)點
struct myword *new_word(char word[]); //生成一個新的節(jié)點,返回指針
char ch;
char word[25];
int i=0;
head = NULL;
while (! feof(input))
{
ch = fgetc(input);
if ((ch>=65 && ch <=90) || (ch>=97 && ch <=122))
{
word[i]=ch;
i++;
}
else
{
word[i]='\0';
if (strlen(word) !=0)
{
tmp = new_word(word);
head = instert_word(tmp,head);
}
i=0;
}
}
printf("find Words Done!\n");
return head;
}
我用了循環(huán)來讀取文件,這里有個點需要注意,每次循環(huán)結(jié)束,每當我讀到字母時,是將字母存入第i個位置,然后i++;如果是非字母則將當前數(shù)組的第i個位置賦值為"\0",當前面出現(xiàn)了單詞,也就是strlen(word) !=0的情況,生成新的單詞節(jié)點,并插入鏈表,然后不管有沒有單詞都將i=0??梢园l(fā)現(xiàn)我并沒有將數(shù)組的內(nèi)容清空。只是每次將第i個位置復制為"\0",因為strlen(word)在統(tǒng)計字符串長度時,遇到"\0"就會停止,如果第一個位置就是"\0",則認為沒有單詞。
這是很重要的一點。
這里我們找出了單詞,但是我們還沒有鏈表,也沒法統(tǒng)計單詞的個數(shù)。
接下就是一個小的輔助函數(shù),
new_word(word)
這個函數(shù)接受一個字符串數(shù)組,并返回一個指向?qū)慕Y(jié)構(gòu)體的指針。
struct myword * new_word(char word[])
{
/*輸入一個單詞數(shù)組,返回一個myword結(jié)構(gòu)體*/
struct myword *tmp;
char *t_word;
tmp=(struct myword *) malloc(LEN); //給一個新的節(jié)點申請空間
//動態(tài)申請一個空間來保存myword->word,因為這本身是個指針,需要指向一個數(shù)組來保存
t_word = (char *) malloc(strlen(word));
strcpy(t_word,strlwr(word));
tmp->word = t_word;
tmp->num = 1;
tmp->next=NULL;
return tmp;
}
這里需要注意的是,給的結(jié)構(gòu)體并沒有給存放單詞的字符串數(shù)組,而是給了一個字符型指針。所以我們要為每一個節(jié)點分配空間的同時,還要給每一個節(jié)點的單詞分配空間。還有就是,字符串賦值 的時候要用strcpy()函數(shù)。
然后就是重頭戲插入鏈表
instert_word(tmp,head)
這個函數(shù)輸入鏈表的頭結(jié)點和要插入的節(jié)點,返回一個指向鏈表頭節(jié)點的指針。
題目的要求還要統(tǒng)計單詞的個數(shù),而且要排序。一開始我是這么想的:先生成一個沒有排序的鏈表,然后在用排序算法,對鏈表進行排序,后來一想,為啥不在插入的時候就按順序插入呢!
于是有了下面的:
struct myword *instert_word(struct myword *tmp,struct myword *head)
{
/*將新的tmp節(jié)點插入到head對應的位置
->遍歷所有節(jié)點
# head空 放到第一個節(jié)點
# 與當前節(jié)點相同 當前節(jié)點的num+1
# 比當前節(jié)點大 如果比下一個節(jié)點小,則插在當前節(jié)點后
如果比下一個節(jié)點大 ,則遍歷下一個節(jié)點
# 比第一個節(jié)點小 放在第一個節(jié)點前,頭結(jié)點后
【因為鏈表是有序的,且在上一情況中處理,
在后面不存在比當前節(jié)點小的情況】
*/
struct myword *cur;
if (head==NULL)
{
head = (struct myword *) malloc(LEN);
head->next = tmp;
}
else
{
cur = head->next;
while (cur!=NULL)
{
if (strcmp(tmp->word,cur->word)<0)
{
//小于第一個值 ,因為鏈表有序,如果比第一個小,那就是最小的
tmp->next = cur;
head->next = tmp;
break;
}
if (strcmp(tmp->word,cur->word)==0)
{
//等于當前值
cur->num++;
free(tmp); //這個節(jié)點已經(jīng)存在,釋放tmp空間
break;
}
else if (strcmp(tmp->word,cur->word)>0)
{
//大于當前值
if (cur->next!=NULL)// && (tmp->word,cur->next->word)<0
{
//后面不為空
if (strcmp(tmp->word,cur->next->word)<0)
{
//小于下一個值 ,插入當前節(jié)點之后
tmp->next = cur->next;
cur->next = tmp;
break;
}
//大于等于下一個值
cur = cur->next;
}
else
{
//后面為空
cur->next= tmp;
break;
}
}
}
}
return head;
}
值得引起注意的就是,第一個節(jié)點的插入。剩下的就是插入節(jié)點的操作了。在比較字符串大小的時候不能像字符或者數(shù)字一樣直接比較,要用strcmp()。
這個函數(shù)只可意會不可言傳!自己理解吧!
最后
我們先打開了文件,然后初始化鏈表頭head指針,然后調(diào)用find_words()函數(shù),返回統(tǒng)計好的單詞鏈表。這是將返回的結(jié)果賦給head,這時head就是含有所有單詞的鏈表了。然后調(diào)用我們的 print_link_to_file(head)函數(shù),將文件輸出到文件中,最后銷毀鏈表。
最新的程序:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define LEN sizeof(struct myword)
struct myword
{
int num;
char *word;
struct myword *next; /* 指向下一個節(jié)點 */
};
void main()
{
/*讀取infile_name文件,用find_words找到單詞,存儲到head鏈表,最后將鏈表輸出到文件*/
void print_link_to_file(struct myword *head); //輸出鏈表到文件
struct myword *find_words(FILE *input); //找單詞,輸出單詞鏈表
void destory_word(struct myword *head);
char infile_name[]={"test.txt"};
struct myword *head;
FILE *input;
input = fopen(infile_name, "r"); //打開文件
if (input == NULL)
{
printf("打開文件%s失敗\n",infile_name);
exit(0);
}
head = find_words(input); //找單詞
fclose(input); //關(guān)閉文件
print_link_to_file(head); //將鏈表輸出到文件
destory_word(head);
}
void print_link_to_file(struct myword *head)
{
/*輸入鏈表,將鏈表內(nèi)容輸出到input.txt*/
printf("NOW,print LINK to File......\n");
struct myword *p;
FILE *output;
char outfile_name[]={"output.txt"};
output = fopen(outfile_name, "w");
if (output == NULL)
{
printf("打開文件%s失敗\n",outfile_name);
exit(0);
}
p = head->next;
do
{
fprintf(output,"%s,%d\n",p->word,p->num);
p = p->next;
}while(p!=NULL);
fclose(output);
printf("print to file Done!\n");
}
struct myword * new_word(char word[])
{
/*輸入一個單詞數(shù)組,返回一個myword結(jié)構(gòu)體*/
struct myword *tmp;
char *t_word;
tmp=(struct myword *) malloc(LEN); //給一個新的節(jié)點申請空間
//動態(tài)申請一個空間來保存myword->word,因為這本身是個指針,需要指向一個數(shù)組來保存
t_word = (char *) malloc(strlen(word));
strcpy(t_word,strlwr(word));
tmp->word = t_word;
tmp->num = 1;
tmp->next=NULL;
return tmp;
}
struct myword *instert_word(struct myword *tmp,struct myword *head)
{
/*將新的tmp節(jié)點插入到head對應的位置
->遍歷所有節(jié)點
# head空 放到第一個節(jié)點
# 與當前節(jié)點相同 當前節(jié)點的num+1
# 比當前節(jié)點大 如果比下一個節(jié)點小,則插在當前節(jié)點后
如果比下一個節(jié)點大 ,則遍歷下一個節(jié)點
# 比第一個節(jié)點小 放在第一個節(jié)點前,頭結(jié)點后
【因為鏈表是有序的,且在上一情況中處理,
在后面不存在比當前節(jié)點小的情況】
*/
struct myword *cur;
if (head==NULL)
{
head = (struct myword *) malloc(LEN);
head->next = tmp;
}
else
{
cur = head->next;
while (cur!=NULL)
{
if (strcmp(tmp->word,cur->word)<0)
{
//小于第一個值 ,因為鏈表有序,如果比第一個小,那就是最小的
tmp->next = cur;
head->next = tmp;
break;
}
if (strcmp(tmp->word,cur->word)==0)
{
//等于當前值
cur->num++;
free(tmp); //這個節(jié)點已經(jīng)存在,釋放tmp空間
break;
}
else if (strcmp(tmp->word,cur->word)>0)
{
//大于當前值
if (cur->next!=NULL)// && (tmp->word,cur->next->word)<0
{
//后面不為空
if (strcmp(tmp->word,cur->next->word)<0)
{
//小于下一個值 ,插入當前節(jié)點之后
tmp->next = cur->next;
cur->next = tmp;
break;
}
//大于等于下一個值
cur = cur->next;
}
else
{
//后面為空
cur->next= tmp;
break;
}
}
}
}
return head;
}
struct myword *find_words(FILE *input)
{
/*輸入文件指針,找到單詞,生成鏈表,輸出鏈表頭head,
從文件中一個字符一個字符的讀取,如果遇到字母則將其存入
字符數(shù)組中,直到遇到非字母,
【在數(shù)組的后面加入'\0',以此來分割數(shù)組中的
單詞,因為前一個單詞可能比當前單詞長】
則數(shù)組中的就是一個單詞,
生成節(jié)點,并插入相應位置。
用字符數(shù)組保存當前單詞*/
printf("start find Words....\n");
struct myword *head, *tmp;
struct myword *instert_word(struct myword *tmp,struct myword *head); //插入節(jié)點
struct myword *new_word(char word[]); //生成一個新的節(jié)點,返回指針
char ch;
char word[25];
int i=0;
head = NULL;
while (! feof(input))
{
ch = fgetc(input);
if ((ch>=65 && ch <=90) || (ch>=97 && ch <=122))
{
word[i]=ch;
i++;
}
else
{
word[i]='\0';
if (strlen(word) !=0)
{
tmp = new_word(word);
head = instert_word(tmp,head);
}
i=0;
}
}
printf("find Words Done!\n");
return head;
}
void destory_word(struct myword *head)
{
printf("start free!\n");
struct myword *tmp;
while(head!=NULL)
{
tmp = head;
head = head->next;
free(tmp);
}
printf("Free Done!\n");
}
這次的收獲不少:
- 寫代碼前要思考清楚整體的框架
- 命名要規(guī)范,見名知意,這樣看到變量就不用發(fā)愁是那個了
- 寫長的程序,要分成一步一步來,可以單寫一個test.c專門用來編寫當前的功能,測試時,要注意到每一種可能,自己寫一個測試的text,而不是直接用目標文件,太大浪費時間
- 在關(guān)鍵的步驟一定要寫清注釋,不然五分鐘后就不知道為什么這么寫了
- if-else if-else 用的時候要分清到底是只能選擇一種執(zhí)行,還是出現(xiàn)了就執(zhí)行
- 長的程序最好先用一個函數(shù)實現(xiàn)在之后,在考慮分模塊,考慮優(yōu)化,不然可能會做無用功
- 有時候別人不能理解你在說什么,那可能是因為你真的很優(yōu)秀
- 但是為了更優(yōu)秀,你需要讓你說的話誰都能聽懂,這說明你真的懂了
這是一種學習技巧————費曼技巧