笨辦法學(xué)C 練習(xí)25:變參函數(shù)

練習(xí)25:變參函數(shù)

原文:Exercise 25: Variable Argument Functions

譯者:飛龍

在C語言中缭受,你可以通過創(chuàng)建“變參函數(shù)”來創(chuàng)建你自己的printf或者scanf版本蠕啄。這些函數(shù)使用stdarg.h頭,它們可以讓你為你的庫創(chuàng)建更加便利的接口舟奠。它們對于創(chuàng)建特定類型的“構(gòu)建”函數(shù)胆剧、格式化函數(shù)和任何用到可變參數(shù)的函數(shù)都非常實(shí)用骇塘。

理解“變參函數(shù)”對于C語言編程并不必要汛闸,我在編程生涯中也只有大約20次用到它。但是平痰,理解變參函數(shù)如何工作有助于你對它的調(diào)試汞舱,并且讓你更加了解計(jì)算機(jī)。

/** WARNING: This code is fresh and potentially isn't correct yet. */

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include "dbg.h"

#define MAX_DATA 100

int read_string(char **out_string, int max_buffer)
{
    *out_string = calloc(1, max_buffer + 1);
    check_mem(*out_string);

    char *result = fgets(*out_string, max_buffer, stdin);
    check(result != NULL, "Input error.");

    return 0;

error:
    if(*out_string) free(*out_string);
    *out_string = NULL;
    return -1;
}

int read_int(int *out_int)
{
    char *input = NULL;
    int rc = read_string(&input, MAX_DATA);
    check(rc == 0, "Failed to read number.");

    *out_int = atoi(input);

    free(input);
    return 0;

error:
    if(input) free(input);
    return -1;
}

int read_scan(const char *fmt, ...)
{
    int i = 0;
    int rc = 0;
    int *out_int = NULL;
    char *out_char = NULL;
    char **out_string = NULL;
    int max_buffer = 0;

    va_list argp;
    va_start(argp, fmt);

    for(i = 0; fmt[i] != '\0'; i++) {
        if(fmt[i] == '%') {
            i++;
            switch(fmt[i]) {
                case '\0':
                    sentinel("Invalid format, you ended with %%.");
                    break;

                case 'd':
                    out_int = va_arg(argp, int *);
                    rc = read_int(out_int);
                    check(rc == 0, "Failed to read int.");
                    break;

                case 'c':
                    out_char = va_arg(argp, char *);
                    *out_char = fgetc(stdin);
                    break;

                case 's':
                    max_buffer = va_arg(argp, int);
                    out_string = va_arg(argp, char **);
                    rc = read_string(out_string, max_buffer);
                    check(rc == 0, "Failed to read string.");
                    break;

                default:
                    sentinel("Invalid format.");
            }
        } else {
            fgetc(stdin);
        }

        check(!feof(stdin) && !ferror(stdin), "Input error.");
    }

    va_end(argp);
    return 0;

error:
    va_end(argp);
    return -1;
}



int main(int argc, char *argv[])
{
    char *first_name = NULL;
    char initial = ' ';
    char *last_name = NULL;
    int age = 0;

    printf("What's your first name? ");
    int rc = read_scan("%s", MAX_DATA, &first_name);
    check(rc == 0, "Failed first name.");

    printf("What's your initial? ");
    rc = read_scan("%c\n", &initial);
    check(rc == 0, "Failed initial.");

    printf("What's your last name? ");
    rc = read_scan("%s", MAX_DATA, &last_name);
    check(rc == 0, "Failed last name.");

    printf("How old are you? ");
    rc = read_scan("%d", &age);

    printf("---- RESULTS ----\n");
    printf("First Name: %s", first_name);
    printf("Initial: '%c'\n", initial);
    printf("Last Name: %s", last_name);
    printf("Age: %d\n", age);

    free(first_name);
    free(last_name);
    return 0;
error:
    return -1;
}

這個(gè)程序和上一個(gè)練習(xí)很像宗雇,除了我編寫了自己的scanf風(fēng)格函數(shù)昂芜,它以我自己的方式處理字符串。你應(yīng)該對main函數(shù)很清楚了赔蒲,以及read_stringread_int兩個(gè)函數(shù)泌神,因?yàn)樗鼈儾]有做什么新的東西。

這里的變參函數(shù)叫做read_scan舞虱,它使用了va_list數(shù)據(jù)結(jié)構(gòu)執(zhí)行和scanf相同的工作欢际,并支持宏和函數(shù)。下面是它的工作原理:

  • 我將函數(shù)的最后一個(gè)參數(shù)設(shè)置為...矾兜,它向C表示這個(gè)函數(shù)在fmt參數(shù)之后接受任何數(shù)量的參數(shù)损趋。我可以在它前面設(shè)置許多其它的參數(shù),但是在它后面不能放置任何參數(shù)椅寺。
  • 在設(shè)置完一些參數(shù)時(shí)浑槽,我創(chuàng)建了va_list類型的變量墙杯,并且使用va_list來為其初始化。這配置了stdarg.h中的這一可以處理可變參數(shù)的組件括荡。
  • 接著我使用了for循環(huán),遍歷fmt格式化字符串溉旋,并且處理了類似scanf的格式畸冲,但比它略簡單。它里面只帶有整數(shù)观腊、字符和字符串邑闲。
  • 當(dāng)我碰到占位符時(shí),我使用了switch語句來確定需要做什么梧油。
  • 現(xiàn)在苫耸,為了從va_list argp中獲得遍歷,我需要使用va_arg(argp, TYPE)宏儡陨,其中TYPE是我將要向參數(shù)傳遞的準(zhǔn)確類型褪子。這一設(shè)計(jì)的后果是你會非常盲目,所以如果你沒有足夠的變量傳入骗村,程序就會崩潰嫌褪。
  • scanf的有趣的不同點(diǎn)是,當(dāng)它碰到's'占位符時(shí)胚股,我使用read_string來創(chuàng)建字符串笼痛。va_list argp棧需要接受兩個(gè)函數(shù):需要讀取的最大尺寸,以及用于輸出的字符串指針琅拌。read_string使用這些信息來執(zhí)行實(shí)際工作缨伊。
  • 這使read_scanscan更加一致,因?yàn)槟憧偸鞘褂?code>&提供變量的地址进宝,并且合理地設(shè)置它們刻坊。
  • 最后,如果它碰到了不在格式中的字符党晋,它僅僅會讀取并跳過紧唱,而并不關(guān)心字符是什么,因?yàn)樗恍枰^隶校。

你會看到什么

當(dāng)你運(yùn)行程序時(shí)漏益,會得到與下面詳細(xì)的結(jié)果:

$ make ex25
cc -Wall -g -DNDEBUG    ex25.c   -o ex25
$ ./ex25
What's your first name? Zed
What's your initial? A
What's your last name? Shaw
How old are you? 37
---- RESULTS ----
First Name: Zed
Initial: 'A'
Last Name: Shaw
Age: 37

如何使它崩潰

這個(gè)程序?qū)彌_區(qū)溢出更加健壯,但是和scanf一樣深胳,它不能夠處理輸入的格式錯(cuò)誤绰疤。為了使它崩潰,試著修改代碼舞终,把首先傳入用于'%s'格式的尺寸去掉轻庆。同時(shí)試著傳入多于MAX_DATA的數(shù)據(jù)癣猾,之后找到在read_string中不使用calloc的方法,并且修改它的工作方式余爆。最后還有個(gè)問題是fgets會吃掉換行符纷宇,所以試著使用fgetc修復(fù)它,要注意字符串結(jié)尾應(yīng)為'\0'蛾方。

附加題

  • 再三檢查確保你明白了每個(gè)out_變量的作用像捶。最重要的是out_string,并且它是指針的指針桩砰。所以拓春,理清當(dāng)你設(shè)置時(shí)獲取到的是指針還是內(nèi)容尤為重要。
  • 使用變參系統(tǒng)編寫一個(gè)和printf相似的函數(shù)亚隅,重新編寫main來使用它硼莽。
  • 像往常一樣,閱讀這些函數(shù)/宏的手冊頁煮纵,確保知道了它在你的平臺做了什么懂鸵,一些平臺會使用宏而其它平臺會使用函數(shù),還有一些平臺會讓它們不起作用行疏。這完全取決于你所用的編譯器和平臺矾瑰。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市隘擎,隨后出現(xiàn)的幾起案子殴穴,更是在濱河造成了極大的恐慌,老刑警劉巖货葬,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件采幌,死亡現(xiàn)場離奇詭異,居然都是意外死亡震桶,警方通過查閱死者的電腦和手機(jī)休傍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蹲姐,“玉大人磨取,你說我怎么就攤上這事〔穸眨” “怎么了忙厌?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長江咳。 經(jīng)常有香客問我逢净,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任爹土,我火速辦了婚禮甥雕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘胀茵。我一直安慰自己社露,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布琼娘。 她就那樣靜靜地躺著峭弟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪轨奄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天拒炎,我揣著相機(jī)與錄音挪拟,去河邊找鬼。 笑死击你,一個(gè)胖子當(dāng)著我的面吹牛玉组,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播丁侄,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼惯雳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鸿摇?” 一聲冷哼從身側(cè)響起石景,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拙吉,沒想到半個(gè)月后潮孽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡筷黔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年往史,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片佛舱。...
    茶點(diǎn)故事閱讀 39,932評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡椎例,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出请祖,到底是詐尸還是另有隱情订歪,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布肆捕,位于F島的核電站陌粹,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜掏秩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一或舞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蒙幻,春花似錦映凳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至抒和,卻和暖如春矫渔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背摧莽。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工庙洼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人镊辕。 一個(gè)月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓油够,卻偏偏與公主長得像,于是被迫代替她去往敵國和親征懈。 傳聞我的和親對象是個(gè)殘疾皇子石咬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容