鍍金池/ 教程/ C/ 練習(xí)14:編寫并使用函數(shù)
練習(xí)9:數(shù)組和字符串
練習(xí)6:變量類型
練習(xí)3:格式化輸出
練習(xí)4:Valgrind 介紹
練習(xí)28:Makefile 進階
練習(xí)14:編寫并使用函數(shù)
練習(xí)21:高級數(shù)據(jù)類型和控制結(jié)構(gòu)
練習(xí)20:Zed的強大的調(diào)試宏
練習(xí)18:函數(shù)指針
練習(xí)0:準(zhǔn)備
練習(xí)15:指針,可怕的指針
練習(xí)27:創(chuàng)造性和防御性編程
練習(xí)22:棧、作用域和全局
練習(xí)10:字符串?dāng)?shù)組和循環(huán)
練習(xí)8:大小和數(shù)組
練習(xí)16:結(jié)構(gòu)體和指向它們的指針
練習(xí)7:更多變量和一些算術(shù)
練習(xí)23:認(rèn)識達夫設(shè)備
練習(xí)12:If,Else If,Else
練習(xí)2:用Make來代替Python
練習(xí)1:啟用編譯器
練習(xí)11:While循環(huán)和布爾表達式
練習(xí)5:一個C程序的結(jié)構(gòu)
練習(xí)24:輸入輸出和文件
練習(xí)25:變參函數(shù)
練習(xí)13:Switch語句
練習(xí)19:一個簡單的對象系統(tǒng)
練習(xí)26:編寫第一個真正的程序
導(dǎo)言:C的笛卡爾之夢
練習(xí)17:堆和棧的內(nèi)存分配

練習(xí)14:編寫并使用函數(shù)

到現(xiàn)在為止,你只使用了作為stdio.h頭文件一部分的函數(shù)。在這個練習(xí)中你將要編寫并使用自己的函數(shù)。

#include <stdio.h>
#include <ctype.h>

// forward declarations
int can_print_it(char ch);
void print_letters(char arg[]);

void print_arguments(int argc, char *argv[])
{
    int i = 0;

    for(i = 0; i < argc; i++) {
        print_letters(argv[i]);
    }
}

void print_letters(char arg[])
{
    int i = 0;

    for(i = 0; arg[i] != '\0'; i++) {
        char ch = arg[i];

        if(can_print_it(ch)) {
            printf("'%c' == %d ", ch, ch);
        }
    }

    printf("\n");
}

int can_print_it(char ch)
{
    return isalpha(ch) || isblank(ch);
}

int main(int argc, char *argv[])
{
    print_arguments(argc, argv);
    return 0;
}

在這個例子中你創(chuàng)建了函數(shù)來打印任何屬于“字母”和“空白”的字符。下面是一個分解:

ex14.c:2

包含了新的頭文件,所以你可以訪問isalphaisblank。

ex14.c:5-6

告訴C語言你稍后會在你的程序中使用一些函數(shù),它們實際上并沒有被定義。這叫做“前向聲明”,它解決了要想使用函數(shù)先要定義的雞和蛋的問題。

ex14.c:8-15

定義print_arguments,它知道如何打印通常由main函數(shù)獲得的相同字符串?dāng)?shù)組。

ex14.c:17-30

定義了can_print_it,它只是簡單地將isalpha(ch) || isblank(ch)的真值(0或1)返回給它的調(diào)用者print_letters

ex14.c:38-42

最后main函數(shù)簡單地調(diào)用print_arguments,來啟動整個函數(shù)鏈。

我不應(yīng)該描述每個函數(shù)里都有什么,因為這些都是你之前遇到過的東西。你應(yīng)該看到的是,我只是像你定義main函數(shù)一樣來定義其它函數(shù)。唯一的不同就是如果你打算使用當(dāng)前文件中沒有碰到過的函數(shù),你應(yīng)該事先告訴C。這就是代碼頂部的“前向聲明”的作用。

你會看到什么

向這個程序傳入不同的命令行參數(shù)來玩轉(zhuǎn)它,這樣會遍歷你函數(shù)中的所有路徑。這里演示了我和它的交互:

$ make ex14
cc -Wall -g    ex14.c   -o ex14

$ ./ex14
'e' == 101 'x' == 120 

$ ./ex14 hi this is cool
'e' == 101 'x' == 120 
'h' == 104 'i' == 105 
't' == 116 'h' == 104 'i' == 105 's' == 115 
'i' == 105 's' == 115 
'c' == 99 'o' == 111 'o' == 111 'l' == 108 

$ ./ex14 "I go 3 spaces"
'e' == 101 'x' == 120 
'I' == 73 ' ' == 32 'g' == 103 'o' == 111 ' ' == 32 ' ' == 32 's' == 115 'p' == 112 'a' == 97 'c' == 99 'e' == 101 's' == 115 
$

isalphaisblank做了檢查提供的字符是否是字母或者空白字符的所有工作。當(dāng)我最后一次運行時,它打印出除了'3'之外的任何東西,因為它是一個數(shù)字。

如何使它崩潰

下面是使它崩潰的兩種不同的方法:

  • 通過移除前向聲明來把編譯器搞暈。它會報告can_print_itprint_letters的錯誤。
  • 當(dāng)你在main中調(diào)用print_arguments時,試著使argc加1,于是它會越過argv數(shù)組的最后一個元素。

附加題

  • 重新編寫這些函數(shù),使它們的數(shù)量減少。比如,你真的需要can_print_it嗎?
  • 使用strlen函數(shù),讓print_arguments知道每個字符串參數(shù)都有多長,之后將長度傳入print_letters。然后重寫print_letters,讓它只處理固定的長度,不按照'\0'終止符。你需要#include <string.h>來實現(xiàn)它。
  • 使用man來查詢isalphaisblank的信息。使用其它相似的函數(shù)來只打印出數(shù)字或者其它字符。
  • 上網(wǎng)瀏覽不同的人喜歡什么樣的函數(shù)格式。永遠不要使用“K&R”語法,因為它過時了,而且容易使人混亂,但是當(dāng)你碰到一些人使用這種格式時,要理解代碼做了什么。