在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ C/ 練習(xí)24:輸入輸出和文件
練習(xí)9:數(shù)組和字符串
練習(xí)6:變量類型
練習(xí)3:格式化輸出
練習(xí)4:Valgrind 介紹
練習(xí)28:Makefile 進(jìn)階
練習(xí)14:編寫并使用函數(shù)
練習(xí)21:高級數(shù)據(jù)類型和控制結(jié)構(gòu)
練習(xí)20:Zed的強(qiáng)大的調(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)識達(dá)夫設(shè)備
練習(xí)12:If,Else If,Else
練習(xí)2:用Make來代替Python
練習(xí)1:啟用編譯器
練習(xí)11:While循環(huán)和布爾表達(dá)式
練習(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í)24:輸入輸出和文件

你已經(jīng)學(xué)會了使用printf來打印變量,這非常不錯,但是還需要學(xué)習(xí)更多。這個練習(xí)中你會用到fscanffgets在結(jié)構(gòu)體重構(gòu)建關(guān)于一個人的信息。在這個關(guān)于讀取輸入的簡介之后,你會得到C語言IO函數(shù)的完整列表。其中一些你已經(jīng)見過并且使用過了,所以這個練習(xí)也是一個記憶練習(xí)。

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

#define MAX_DATA 100

typedef enum EyeColor {
    BLUE_EYES, GREEN_EYES, BROWN_EYES,
    BLACK_EYES, OTHER_EYES
} EyeColor;

const char *EYE_COLOR_NAMES[] = {
    "Blue", "Green", "Brown", "Black", "Other"
};

typedef struct Person {
    int age;
    char first_name[MAX_DATA];
    char last_name[MAX_DATA];
    EyeColor eyes;
    float income;
} Person;

int main(int argc, char *argv[])
{
    Person you = {.age = 0};
    int i = 0;
    char *in = NULL;

    printf("What's your First Name? ");
    in = fgets(you.first_name, MAX_DATA-1, stdin);
    check(in != NULL, "Failed to read first name.");

    printf("What's your Last Name? ");
    in = fgets(you.last_name, MAX_DATA-1, stdin);
    check(in != NULL, "Failed to read last name.");

    printf("How old are you? ");
    int rc = fscanf(stdin, "%d", &you.age);
    check(rc > 0, "You have to enter a number.");

    printf("What color are your eyes:\n");
    for(i = 0; i <= OTHER_EYES; i++) {
        printf("%d) %s\n", i+1, EYE_COLOR_NAMES[i]);
    }
    printf("> ");

    int eyes = -1;
    rc = fscanf(stdin, "%d", &eyes);
    check(rc > 0, "You have to enter a number.");

    you.eyes = eyes - 1;
    check(you.eyes <= OTHER_EYES && you.eyes >= 0, "Do it right, that's not an option.");

    printf("How much do you make an hour? ");
    rc = fscanf(stdin, "%f", &you.income);
    check(rc > 0, "Enter a floating point number.");

    printf("----- RESULTS -----\n");

    printf("First Name: %s", you.first_name);
    printf("Last Name: %s", you.last_name);
    printf("Age: %d\n", you.age);
    printf("Eyes: %s\n", EYE_COLOR_NAMES[you.eyes]);
    printf("Income: %f\n", you.income);

    return 0;
error:

    return -1;
}

這個程序非常簡單,并且引入了叫做fscanf的函數(shù),意思是“文件的格式化輸入”。scanf家族的函數(shù)是printf的反轉(zhuǎn)版本。printf用于以某種格式打印數(shù)據(jù),然而scanf以某種格式讀?。ɑ蛘邟呙瑁┹斎搿?/p>

文件開頭沒有什么新的東西,所以下面只列出main所做的事情:

ex24.c:24-28

創(chuàng)建所需的變量。

ex24.c:30-32

使用fgets函數(shù)獲取名字,它從輸入讀取字符串(這個例子中是stdin),但是確保它不會造成緩沖區(qū)溢出。

ex24.c:34-36

you.last_name執(zhí)行相同操作,同樣使用了fgets。

ex24.c:38-39

使用fscanf來從stdin讀取整數(shù),并且將其放到you.age中。你可以看到,其中使用了和printf相同格式的格式化字符串。你也應(yīng)該看到傳入了you.age的地址,便于fsnaf獲得它的指針來修改它。這是一個很好的例子,解釋了使用指向數(shù)據(jù)的指針作為“輸出參數(shù)”。

ex24.c:41-45

打印出用于眼睛顏色的所有可選項(xiàng),并且?guī)в?code>EyeColor枚舉所匹配的數(shù)值。

ex24.c:47-50

再次使用了fscanf,從you.eyes中獲取數(shù)值,但是保證了輸入是有效的。這非常重要,因?yàn)橛脩艨梢暂斎胍粋€超出EYE_COLOR_NAMES數(shù)組范圍的值,并且會導(dǎo)致段錯誤。

ex24.c:52-53

獲取you.income的值。

ex24.c:55-61

將所有數(shù)據(jù)打印出來,便于你看到它們是否正確。要注意EYE_COLOR_NAMES用于打印EyeColor枚舉值實(shí)際上的名字。

你會看到什么

當(dāng)你運(yùn)行這個程序時,你應(yīng)該看到你的輸入被適當(dāng)?shù)剞D(zhuǎn)換。你應(yīng)該嘗試給它非預(yù)期的輸入,看看程序是怎么預(yù)防它的。

$ make ex24
cc -Wall -g -DNDEBUG    ex24.c   -o ex24
$ ./ex24
What's your First Name? Zed
What's your Last Name? Shaw
How old are you? 37
What color are your eyes:
1) Blue
2) Green
3) Brown
4) Black
5) Other
> 1
How much do you make an hour? 1.2345
----- RESULTS -----
First Name: Zed
Last Name: Shaw
Age: 37
Eyes: Blue
Income: 1.234500

如何使它崩潰

這個程序非常不錯,但是這個練習(xí)中真正重要的部分是,scanf如何發(fā)生錯誤。對于簡單的數(shù)值轉(zhuǎn)換沒有問題,但是對于字符串會出現(xiàn)問題,因?yàn)?code>scanf在你讀取之前并不知道緩沖區(qū)有多大。類似于gets的函數(shù)(并不是fgets,不帶f的版本)也有一個我們已經(jīng)避免的問題。它并不是道輸入緩沖區(qū)有多大,并且可能會使你的程序崩潰。

要演示fscanf和字符串的這一問題,需要修改使用fgets的那一行,使它變成fscanf(stdin, "%50s", you.first_name),并且城市再次運(yùn)行。你會注意到,它讀取了過多的內(nèi)容,并且吃掉了你的回車鍵。這并不是你期望它所做的,你應(yīng)該使用fgets而不是去解決古怪的scanf問題。

接下來,將fgets改為gets,接著使用valgrind來執(zhí)行valgrind ./ex24 < /dev/urandom,往你的程序中輸入一些垃圾字符串。這叫做對你的程序進(jìn)行“模糊測試”,它是一種不錯的方法來發(fā)現(xiàn)輸入錯誤。這個例子中,你需要從/dev/urandom文件來輸入一些垃圾,并且觀察它如何崩潰。在一些平臺上你需要執(zhí)行數(shù)次,或者修改MAX_DATA來使其變小。

gets函數(shù)非常糟糕,以至于一些平臺在程序運(yùn)行時會警告你使用了gets。你應(yīng)該永遠(yuǎn)避免使用這個函數(shù)。

最后,找到you.eyes輸入的地方,并移除對其是否在正確范圍內(nèi)的檢查。然后,為它輸入一個錯誤的數(shù)值,比如-1或者1000。在Valgrind執(zhí)行這些操作,來觀察會發(fā)生什么。

譯者注:根據(jù)最新的C11標(biāo)準(zhǔn),對于輸入函數(shù),你應(yīng)該總是使用_s后綴的安全版本。對于向字符串的輸出函數(shù),應(yīng)該總是使用C99中新增的帶n的版本,例如snprintf。如果你的編譯器支持新版本,就不應(yīng)該使用舊版本的不安全函數(shù)。

IO函數(shù)

這是一個各種IO函數(shù)的簡單列表。你應(yīng)該查詢每個函數(shù)并為其創(chuàng)建速記卡,包含函數(shù)名稱,功能和它的任何變體。

  • fscanf
  • fgets
  • fopen
  • freopen
  • fdopen
  • fclose
  • fcloseall
  • fgetpos
  • fseek
  • ftell
  • rewind
  • fprintf
  • fwrite
  • fread

過一遍這些函數(shù),并且記住它們的不同變體和它們的功能。例如,對于fscanf的卡片,上面應(yīng)該有scanf、sscanf、vscanf,以及其它。并且在背面寫下每個函數(shù)所做的事情。

最后,為了獲得這些卡片所需的信息,使用man來閱讀它的幫助。例如,fscanf幫助頁由man fscanf得到。

附加題

  • 將這個程序重寫為不需要fscanf的版本。你需要使用類似于atoi的函數(shù)來將輸入的字符串轉(zhuǎn)換為數(shù)值。
  • 修改這個程序,使用scanf來代替fscanf,并觀察有什么不同。
  • 修改程序,是輸入的名字不包含任何換行符和空白字符。
  • 使用scanf編寫函數(shù),按照文件名讀取文件內(nèi)容,每次讀取單個字符,但是不要越過(文件和緩沖區(qū)的)末尾。使這個函數(shù)接受字符串大小來更加通用,并且確保無論什么情況下字符串都以'\0'結(jié)尾。