天花板編程手把手計劃-第1期-第7天

從今天起唬涧,我們要開始學習用工程化的思想去解決問題狮荔。之前雅采,我們總是把所有的代碼寫在一個源文件中巡雨,這樣看起來比較方便正蛙。不過這些代碼中狂塘,有些在邏輯上關系并不緊密泪漂。對于這種情況,我們往往用獨立的文件去管理瞬捕。就像我們之前總把程序劃分為幾個相對獨立的子功能香嗓,如果把這些不同的子功能分別用獨立的文件管理起來,就能幫助我們更容易理解代碼的邏輯結構。

很多同學覺得這個系列越往后代碼越多俏蛮,越難理解第煮。其實,一個大的問題在被拆成若干個小程序之后,都是非常容易的线欲。如果拿出任何一個子功能出來,大部分人都能順利完成,那為什么放在一起就覺得難了呢,原因就在于缺乏一種工程化的編程思想床佳。

今天,我們就模擬一下團隊合作影兽,看看如何通過多人協(xié)作的方法來解決上一篇中的習題旦万。

1. 題目

編程統(tǒng)計出input.txt文件保存的文章中丹莲,每個單詞出現(xiàn)的次數(shù)洲赵。文章內容如下:

In this chapter we will be looking at files and directories and how to manipulate them. We will learn how to create files, open them, read, write and close them. We'll also learn how programs can manipulate directories, to create, scan and delete them, for example. After the last chapter's diversion into shells, we now start programming in C.
Before proceeding to the way UNIX handles file I/O, we'll review the concepts associated with files, directories and devices. To manipulate files and directories, we need to make system calls (the UNIX parallel of the Windows API), but there also exists a whole range of library functions, the standard I/O library (stdio), to make file handling more efficient.

這段文字來自網絡鸳惯。為了統(tǒng)計更有意義,加入兩個條件:

  • 統(tǒng)計過程中不考慮空格和標點符號
  • 不區(qū)分大小寫(可以把所有字母轉成小寫后參與統(tǒng)計)

2. 分析

首先叠萍,我們思考一下芝发,程序流程大概如下:

  • 依次掃描每個單詞
  • 把單詞做簡單的處理
  • 查找單詞計數(shù)
  • 結果排序整理
  • 打印輸出

用這五個功能就能夠組成最終的程序。假如我們有一個5個人團隊來完成這個工作苛谷,那就可以讓這5個人分別負責一個部分辅鲸。對于每個人而言,他的工作量僅僅是實現(xiàn)一個非常簡單的小程序腹殿。下面我們來看看這幾個小程序独悴。

3. 小程序一 : 文件讀取

題目:
請編程實現(xiàn)把input.txt文件中的每個單詞打印在屏幕上。

看到這個題目赫蛇,我們很容易想到天花板編程手把手計劃-第1期-第6天中從文件中讀取表達式的方法绵患。我們依然希望像讀鍵盤輸入那樣讀取文件內容。這個問題我們交給小A同學做悟耘,他的代碼如下:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

#define MAX_SIZE 50

int main()
{
    char str[MAX_SIZE];

    freopen("input.txt", "r", stdin);
    
    while (1)
    {
        str[0] = 0;
        scanf("%s", str);
        
        if (str[0] == 0)
        {
            break;
        }

        printf("%s\n", str);
    }
}

有了前面的基礎落蝙,這段代碼應該很好理解。通過循環(huán)調用scanf函數(shù)來讀取每一個字符串。需要注意的是在scanf函數(shù)之前筏勒,我們用了str[0] = 0;這句話對str數(shù)組進行了初始化移迫。這樣在文件結尾處,str不會發(fā)生變化管行。這時我們就知道循環(huán)可以結束了厨埋。

這段代碼的執(zhí)行結果如下:

現(xiàn)在,問題來了捐顷。如果小A同學最終給你提交的是這份代碼荡陷,你能方便地使用嗎?換句話說迅涮,你希望怎樣使用他寫好的代碼呢废赞?如果是我,我希望他能夠封裝成API函數(shù)給我叮姑。了解了我的需求唉地,小A同學給我提供了兩個文件:

  • 文件:FileOper.h
//   File : FileOper.h
// Author : 小A 
#ifndef __FILE_OPER_H__
#define __FILE_OPER_H__

#include <stdio.h>

void FileInit();
int FileGetString(char* pBuf);
void FileDispose();

#endif
  • 文件FileOper.c
//   File : FileOper.c
// Author : 小A 
#define _CRT_SECURE_NO_WARNINGS
#include "FileOper.h"

void FileInit()
{
    freopen("input.txt", "r", stdin);
}

int FileGetString(char* pBuf)
{
    pBuf[0] = 0;

    scanf("%s", pBuf);

    if (pBuf[0] == 0)
    {
        return 0;
    }
    else
    {
        return 1;
    }
}

void FileDispose()
{
    fclose(stdin);
}

于是,我在文件main.c中只需要調用者三個函數(shù)即可传透。

#include <stdio.h>

#include "FileOper.h"

#define MAX_SIZE 50

int main()
{
    char str[MAX_SIZE];

    FileInit();
    while (FileGetString(str) == 1)
    {
        printf("%s\n", str);
    }

    FileDispose();
}

現(xiàn)在小A的任務已經完成了耘沼。也許有人會說,這么一來代碼行數(shù)會變多朱盐,那是因為我們的程序實在太簡單了群嗤,如果業(yè)務邏輯稍微復雜一些,你就會發(fā)現(xiàn)這么拆開變得豁然開朗了托享。

4. 小程序二 : 字符處理

我們從文件中得到了一組單詞骚烧,但由于有標點符號和大小寫字母,影響了我們的統(tǒng)計闰围。于是小B同學領導了這個小程序。

題目:
實現(xiàn)一個字符串過濾函數(shù)既峡,濾掉字符串中的無用字符羡榴,并把所有的大寫字母轉換成小寫字母。

仔細分析一下运敢,這個小程序包括兩個功能校仑,一個是大小寫字母的轉換,另外一個是刪除符號传惠。小B給出了下面兩個文件:

  • Filter.h
//   File : Filter.h
// Author : 小B 
#ifndef __FILTER_H__
#define __FILTER_H__

void Filter(char* pBuf);

#endif
  • Filter.c
//   File : Filter.c
// Author : 小B 
#include "Filter.h"

void ToLower(char* pBuf)
{
    int i;
    for (i = 0; pBuf[i] != 0; i++)
    {
        if (pBuf[i] >= 'A' && pBuf[i] <= 'Z')
        {
            pBuf[i] += 32;
        }
    }
}

void Remove(char* pBuf, int index)
{
    int i;
    for (i = index; pBuf[i] != 0; i++)
    {
        pBuf[i] = pBuf[i + 1];
    }
}

void FiltSymbols(char* pBuf)
{
    int i;
    for (i = 0; pBuf[i] != 0; i++)
    {
        switch (pBuf[i])
        {
        case '(':
            // go to next
        case ')':
            // go to next
        case ',':
            // go to next
        case '.':
            Remove(pBuf, i);
            i--;
            break;
        default:
            // Do nothing
            break;
        }
    }
}

void Filter(char* pBuf)
{
    ToLower(pBuf);
    FiltSymbols(pBuf);
}

ToLower函數(shù)負責把傳入字符串的大寫字母轉成小寫字母迄沫。由于在ASCII碼表中,大寫字母和它對應的小寫字母的值差32,所以直接計算即可卦方。Remove函數(shù)的功能是刪除一個字符羊瘩,實現(xiàn)過程是把它后面的每一個字符向前移動。FiltSymbols的功能是刪掉符號,這里用了switch的一種特殊用法尘吗,需要注意的是在不使用break的時候要用注釋說明你是故意不寫的逝她。

有人會說,頭文件只有一個函數(shù)聲明睬捶,是否有必要專門寫一組文件呢黔宛?其實這才是重點,通過這種方法擒贸,小B同學有效地幫我們過濾掉了冗余信息臀晃,只提供給我們必要的Filter函數(shù)。極不容易引起混淆介劫,也最大限度的降低了耦合性积仗。在main函數(shù)中,我們只需要做簡單修改就好蜕猫。

#include <stdio.h>

#include "FileOper.h"
#include "Filter.h"

#define MAX_SIZE 50

int main()
{
    char str[MAX_SIZE];

    FileInit();
    while (FileGetString(str) == 1)
    {
        Filter(str);

        printf("%s\n", str);
    }

    FileDispose();
}

看看效果寂曹,是不是工整多了。

5. 小程序三 :單詞統(tǒng)計

下面到了小C出場了回右,他遇到的題目是隆圆。編程統(tǒng)計出一組字符串中每個字符串出現(xiàn)的次數(shù)。

首先翔烁,為了實現(xiàn)統(tǒng)計功能渺氧,首先要設計一個合適的數(shù)據(jù)結構。

  • 文件Word.h
//   File : Word.h
// Author : 小C
#ifndef __WORD_H__
#define __WORD_H__

#define CHAR_SIZE 50

typedef struct _tagWord
{
    char m_arr[CHAR_SIZE];
    int m_cnt;
}Word;

void WordSet(Word* pWord, char* pStr, int cnt);
void WordCpy(Word* pDest, Word* pSrc);

#endif

這個頭文件定義了一個結構體Word蹬屹,它保存了一個字符串和它的次數(shù)侣背。為了方便使用,還提供了兩個函數(shù)用來給結構體賦值和拷貝慨默。這兩個函數(shù)的實現(xiàn)如下:

  • 文件Word.c
//   File : Word.c
// Author : 小C
#define _CRT_SECURE_NO_WARNINGS
#include "Word.h"
#include <string.h>

void WordSet(Word* pWord, char* pStr, int cnt)
{
    strcpy(pWord->m_arr, pStr);
    pWord->m_cnt = cnt;
}

void WordCpy(Word* pDest, Word* pSrc)
{
    strcpy(pDest->m_arr, pSrc->m_arr);
    pDest->m_cnt = pSrc->m_cnt;
}

接下來贩耐,小C又設計了一組統(tǒng)計方法,聲明如下:

  • 文件Dict.h
//   File : Dict.h
// Author : 小C
#ifndef __DICT_H__
#define __DICT_H__

#include "Word.h"

void DictInit(); // 創(chuàng)建字典
void DictInsert(char* pStr); // 插入字符串
Word* DictSearch(char* pStr); // 查找字符串
void DictSort(); // 字典排序
void DictPrint(); // 字典打印

#endif

這個文件聲明了五個函數(shù)厦取,它們都以Dict開頭潮太,表明它們的存在是為了解決同一個問題,維護了一個字典的使用虾攻。

  • 文件Fille.c
//   File : Dict.c
// Author : 小C/小D/小E
#include "Dict.h"

#include <string.h>

#define SIZE 200

Word g_arrWords[200];
int g_index;

void DictInit()
{
    g_index = 0;
}

void DictInsert(char* pStr)
{
    WordSet(&g_arrWords[g_index], pStr, 1);
    g_index++;
}

Word* DictSearch(char* pStr)
{
    int i;
    for (i = 0; i < g_index; i++)
    {
        if (strcmp(g_arrWords[i].m_arr, pStr) == 0)
        {
            return &g_arrWords[i];
        }
    }

    return NULL;
}

小C只實現(xiàn)了三個函數(shù):

  • DictInit()

字典初始化函數(shù)铡买,它負責初始化數(shù)組g_arrWords

  • DictInsert()

它負責把一個字符串保存進字典霎箍。

  • DictSearch()

這個函數(shù)負責在字典中尋找一個字符串的位置奇钞。找到了返回這個Word的指針,找不到返回NULL漂坏。這里用了最簡單的方法景埃,遍歷所有的元素媒至,比較每一個字符串看看是否匹配。比較的動作使用了字符串的庫函數(shù)strcmp纠亚。

6. 小程序四 :字符串排序

小D負責實現(xiàn)DictSort函數(shù)塘慕,他在文件Dict.c中添加了下面的內容:

void DictSort()
{
    int i, j;
    Word wordT;
    for (i = 0; i < g_index - 1; i++)
    {
        for (j = i + 1; j < g_index; j++)
        {
            if (strcmp(g_arrWords[i].m_arr, g_arrWords[j].m_arr) > 0)
            {
                WordCpy(&wordT, &g_arrWords[i]);
                WordCpy(&g_arrWords[i], &g_arrWords[j]);
                WordCpy(&g_arrWords[j], &wordT);
            }
        }
    }
}

這里用了最常用的冒泡排序算法,通過字符串的比較實現(xiàn)排序蒂胞。里面用到了WordCpy函數(shù)用來復制Word图呢。

7. 小程序五 :打印輸出

最后出場的是小E,他的任務很簡單骗随,把字典中的內容打印在屏幕上蛤织。

void DictPrint()
{
    int i;
    for (i = 0; i < g_index; i++)
    {
        printf("%12s - %d\n", g_arrWords[i].m_arr, g_arrWords[i].m_cnt);
    }
}

8. 功能整合

五個小程序都已經完成了,現(xiàn)在作為項目的最終功能實現(xiàn)者鸿染,你擁有了四組API函數(shù)指蚜,看看如何利用這些代碼來完成這個程序。打開文件main.c涨椒,代碼如下:

#include <stdio.h>

#include "FileOper.h"
#include "Filter.h"
#include "Dict.h"

#define MAX_SIZE 50

int main()
{
    char str[MAX_SIZE];
    Word* pWord;

    FileInit();
    DictInit();
    while (FileGetString(str) == 1)
    {
        Filter(str);

        if ((pWord = DictSearch(str)) == NULL)
        {
            DictInsert(str);
        }
        else
        {
            pWord->m_cnt++;
        }

        //printf("%s\n", str);
    }

    DictSort();
    DictPrint();

    FileDispose();
}

執(zhí)行結果:

今天這個題目是一個比較簡單的練習摊鸡,我在某本C語言教科書的課后習題中看到的。但我們用了一個相對復雜的工程方式來設計代碼蚕冬,希望大家在看懂代碼的同時重點思考這個項目的邏輯結構和設計思想免猾。如果有什么問題,可以在群里討論囤热。

這部分源碼如果不清楚猎提,請在GitHub中下載。

9. 課后練習

今天的練習題我們做一道簡單的開發(fā)性程序旁蔼。請編程實現(xiàn)一個功能锨苏,輸入任意一個日期,計算出那一天是星期幾棺聊。這道題沒有任何限制伞租,你可以設計任何自己喜歡的交互形式,可以使用任何自己能想到的算法躺屁。

我是天花板肯夏,讓我們一起在軟件開發(fā)中自我迭代。
如有任何問題犀暑,歡迎與我聯(lián)系。


上一篇:天花板編程手把手計劃-第1期-第6天

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末烁兰,一起剝皮案震驚了整個濱河市耐亏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌沪斟,老刑警劉巖广辰,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件暇矫,死亡現(xiàn)場離奇詭異,居然都是意外死亡择吊,警方通過查閱死者的電腦和手機李根,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來几睛,“玉大人房轿,你說我怎么就攤上這事∷” “怎么了囱持?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長焕济。 經常有香客問我纷妆,道長,這世上最難降的妖魔是什么晴弃? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任掩幢,我火速辦了婚禮,結果婚禮上上鞠,老公的妹妹穿的比我還像新娘际邻。我一直安慰自己,他們只是感情好旗国,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布枯怖。 她就那樣靜靜地躺著,像睡著了一般能曾。 火紅的嫁衣襯著肌膚如雪度硝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天寿冕,我揣著相機與錄音蕊程,去河邊找鬼。 笑死驼唱,一個胖子當著我的面吹牛藻茂,可吹牛的內容都是我干的。 我是一名探鬼主播玫恳,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼辨赐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了京办?” 一聲冷哼從身側響起掀序,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎惭婿,沒想到半個月后不恭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叶雹,經...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年换吧,在試婚紗的時候發(fā)現(xiàn)自己被綠了折晦。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡沾瓦,死狀恐怖满着,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情暴拄,我是刑警寧澤漓滔,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站乖篷,受9級特大地震影響响驴,放射性物質發(fā)生泄漏。R本人自食惡果不足惜撕蔼,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一豁鲤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鲸沮,春花似錦琳骡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至怒坯,卻和暖如春炫狱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背剔猿。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工视译, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人归敬。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓酷含,卻偏偏與公主長得像,于是被迫代替她去往敵國和親汪茧。 傳聞我的和親對象是個殘疾皇子椅亚,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內容

  • 題目 請編程實現(xiàn)一個功能,輸入任意一個日期舱污,計算出那一天是星期幾什往。這道題沒有任何限制,你可以設計任何自己喜歡的交互...
    Hans941閱讀 216評論 0 2
  • 這一期的內容已經過半慌闭,部分同學開始覺得吃力别威。如果這時候放棄,那前邊的努力就白費了驴剔。今天我們來看看上一篇中的課后題省古。...
    天花板閱讀 1,598評論 0 13
  • 題目 原問題鏈接編程統(tǒng)計出input.txt文件保存的文章中,每個單詞出現(xiàn)的次數(shù)丧失。文章內容如下: In this ...
    Hans941閱讀 389評論 0 2
  • 細雨綿綿豺妓,綢緞一般,迎面落在臉上布讹,又如孩童的手琳拭,潤滑細膩∶柩椋滑落嘴角的雨滴白嘁,用舌尖舔舔,有一絲甜膘流,軟軟的絮缅,像...
    向晚葦草閱讀 480評論 3 1
  • 與情緒相處: 當處于一種情緒中耕魄,很容易沉浸進去,第一步彭谁、首要是讓自己意識到這一點吸奴,這是大前提,承認自己的情緒真實...
    ss終生學習者閱讀 370評論 0 2