2016年9月27日 星期二

給新手的C++教學 (上冊) - 13 - 9. 進階的檔案處理

回到「給新手的C++教學 (上冊)」

回到「13. 額外語法 (Extra syntax)」

上一頁

讀取檔案「in.txt」並同時輸出其內容:

#include<cstdio>
int main()
{
    FILE *file_reader=fopen("in.txt","r");
    for(char c;fscanf(file_reader,"%c",&c)==1;)
    {
        printf("%c",c);
    }
    return 0;
}
輸出結果:可以發現黑色視窗上輸出的內容就是「in.txt」的內容

輸入3個檔名,依據輸入的檔名讀取那3個檔案並同時輸出其內容:

#include<cstdio>
void ReadFile()
{
    char file_name[1000];
    scanf("%s",file_name);
    FILE *file_reader=fopen(file_name,"r");
    for(char c;fscanf(file_reader,"%c",&c)==1;) printf("%c",c);
    printf("Finish reading %s!\n",file_name);
}
int main()
{
    ReadFile();
    ReadFile();
    ReadFile();
    return 0;
}
可以隨時決定要從哪一個檔案讀取資料 (fscanf) 或者直接從黑色視窗取得輸入 (scanf)

將「1+2+3+......+100」的完整計算過程寫入到檔案「out.txt」:

2016年9月19日 星期一

給新手的C++教學 (上冊) - 13 - 8. 簡易的檔案處理

回到「給新手的C++教學 (上冊)」

回到「13. 額外語法 (Extra syntax)」

上一頁

您是否有想過要用C++來讀取和寫入檔案呢?
真的可以嗎?需不需要解析磁碟機NTFS、FAT32之類的格式啊?
當然不需要!

放心,這件事連小莫都不知道怎麼做
所謂「站在巨人的肩膀上」,這種事情不用再由我們自己處理了!
在先人的努力之下,「C++」這個偉大的程式語言,已經讓電腦可以只依據簡單的幾行程式碼,就可以執行許多複雜卻需要經常執行的工作

事實上,C++讀寫檔案的方法比你想的任何方式都簡單!
更具體地來講
我們在撰寫C++程式碼的時候,只需要告訴電腦「讀取哪個檔案」和 (或)「寫入哪個檔案」就好了!

要怎麼告訴電腦「恩,我要從這個檔案名稱為『in.txt』的檔案讀取」呢?
(請先到檔案總管設定顯示附檔名以取得完整的檔案名稱 (點我查看教學))
寫下一行程式碼就夠了:

2016年9月16日 星期五

給新手的C++教學 (上冊) - 13 - 7. 中文字元字串的處理

回到「給新手的C++教學 (上冊)」

回到「13. 額外語法 (Extra syntax)」

上一頁

先前有網友提問,他想寫一個程式:
輸入「是」會輸出「真是識相!\n」
輸入「不是」會輸出「你眼光有問題!\n」

我們先來看看這個程式的英文版本怎麼寫:
輸入「YES」會輸出「真是識相!\n」
輸入「NO」會輸出「你眼光有問題!\n」

#include<cstdio>
int main()
{
    printf("我是天才? YES or NO\n");
    char answer[4];
    scanf("%s",answer);
    if(answer[0]=='Y'&&answer[1]=='E'&&answer[2]=='S'&&answer[3]=='\0')
    {
        printf("真是識相!\n");
    }
    if(answer[0]=='N'&&answer[1]=='O'&&answer[2]=='\0')
    {
        printf("你眼光有問題!\n");
    }
    return 0;
}
輸入「YES」
輸入「NO」

請注意,「answer」的長度只有4,因此一旦輸入的長度超過3 (注意,還有一個表示字串結尾的「'\0'」) 就有可能導致程式出錯

那麼,要怎麼改成中文版本呢?是這樣嗎?

#include<cstdio>
int main()
{
    printf("我是天才? YES or NO\n");
    char answer[3];
    scanf("%s",answer);
    if(answer[0]=='是'&&answer[1]=='\0')
    {
        printf("真是識相!\n");
    }
    if(answer[0]=='不'&&answer[1]=='是'&&answer[2]=='\0')
    {
        printf("你眼光有問題!\n");
    }
    return 0;
}

很遺憾的,事情並沒有這麼簡單

2016年9月15日 星期四

給新手的C++教學 (上冊) - 13 - 6. 更彈性的取得和釋放記憶體

回到「給新手的C++教學 (上冊)」

回到「13. 額外語法 (Extra syntax)」

上一頁

名詞解釋:
「釋放」記憶體:將某塊記憶體標記為「使用完畢」,讓這塊記憶體之後或讓其他程式可以再被利用

在之前的字元字串章節,我們使用了一個很大很大 (大小為100萬) 的陣列來儲存「一個名字」
除非要處理特別多的資料,否則應該是不會需要這麼大的陣列啦XD
但接下來,您會發現,當陣列太大的時候,會發生問題的
為了節省版面,這裡以字元字串章節的最後一份程式碼為例:

#include<cstdio>
int main()
{
    char name[1000001];
    scanf("%s",name);
    printf("Hello, %s!\n",name);
    return 0;
}
輸入名字「Motivation」,會輸出「Hello, Motivation!」

假如哪天你發現某人的名字太長了 (?),大小100萬的陣列不夠用 (!),想要宣告大小1000萬的陣列 (......)
好啊!欣然同意啊XD

#include<cstdio>
int main()
{
    char name[10000001];
    scanf("%s",name);
    printf("Hello, %s!\n",name);
    return 0;
}
欸欸等等我甚麼是都還沒做耶!

咦?怎麼程式掛了?!
根本都還沒開始輸入名字呀!

2016年9月12日 星期一

給新手的C++教學 (上冊) - 13 - 5. 陣列就是指標,指標就是陣列

回到「給新手的C++教學 (上冊)」

回到「13. 額外語法 (Extra syntax)」

上一頁

當您讀到「陣列」這個單元時,大可將「宣告一個大小為n的陣列」理解成「讓電腦幫你一次宣告n個變數」
但是,事實真的是如此嗎?

當您宣告一個變數的時候,電腦除了要挪出一塊記憶體給你用,還要額外提供一個資訊:到底是哪一塊記憶體要給你用?
或許您沒有感覺,但是在您做出「int a;」這件事之後:「a」對您這位程式設計師來講,是一個變數的名稱;但是,對電腦來講,「a」代表對一塊記憶體的存取或修改,換句話說,電腦必須知道「a的指標」,也就是哪一塊記憶體是給「a」用的
電腦會默默地把「a的指標」記好,這會需要額外的記憶體,當然,您不會意識到這件事

那這樣的話,把「宣告一個大小為n的陣列」理解成「讓電腦幫你一次宣告n個變數」會有甚麼問題嗎?
注意到,這時電腦不用把所有n個變數的指標都記起來,相對的,在配置記憶體給這n個變數的時候,電腦可以偷懶直接取一大塊記憶體,分成n等分「依序」給這n個變數使用
也就是說,電腦只要記住第1個變數的指標,就可以直接算出其他n-1個變數的指標了!

怎麼證明電腦真的在做這件事?
信不信,陣列的名稱可以直接當指標來用XD (?!)
當然,小莫還要示範給你看!

2016年9月2日 星期五

給新手的C++教學 (上冊) - 13 - 4. 指定輸出格式

回到「給新手的C++教學 (上冊)」

回到「13. 額外語法 (Extra syntax)」

上一頁

您是否有想過要固定輸出數字的寬度呢?
當您想要讓輸出的格式類似表格,行行之間的數字要對齊,就會想要這樣做
這時候該怎麼辦呢?
用代表「Tab」的「\t」來排版吧!
可是數字太長會讓「\t」跳到下一個位置,反而加大行行數字間的差距甚至造成混淆......
先想辦法算出數字有幾個位數,再決定每個數字前面各要補幾個空格?
功能完美!可是似乎有點麻煩......

哈哈,相信您已經猜到了--本章將教您如何簡單的設定輸出格式!
本方法僅限「scanf」和「printf」的使用者哦~
如果您是使用「cin」和「cout」,小莫真的不知道怎麼辦呢......XD

類似表格的東西喔......就拿九九乘法表來當例子吧!
首先,我們先把輸出九九乘法表9*9個數字的程式碼寫出來~

#include<cstdio>
int main()
{
    for(int row=1;row<=9;row=row+1)
    {
        for(int column=1;column<=9;column=column+1)
        {
            if(column>1)printf(" ");//除了第一個數字之外,其餘的數字前面都要有一個空格
            printf("%d",row*column); 
        }
        printf("\n");
    }
    return 0;
}
九九乘法表~寫錯了也沒關係,可以直接貼程式碼哦
我們來把每個數字的寬度都固定成2吧~

#include<cstdio>
int main()
{
    for(int row=1;row<=9;row=row+1)
    {
        for(int column=1;column<=9;column=column+1)
        {
            if(column>1)printf(" ");//除了第一個數字之外,其餘數字的前面都要有一個空格
            printf("%2d",row*column); 
        }
        printf("\n");
    }
    return 0;
}
耶~所有數字的寬度都固定成2了
耶~所有數字的寬度都固定成2了 \(^o^)/
等等,為甚麼?程式碼看起來沒差呀
其實真的沒差多少,我們只是把輸出數字的「%d」改成「%2d」而已 (在「printf("%d",row*column);」那裡)
就這麼簡單!
當然,如果想要把數字的寬度固定成3,把「%2d」改成「%3d」即可

您可能會想問,可以用一個「變數」來指定數字的寬度嗎?
沒問題!

給新手的C++教學 (上冊) - 13 - 3. 進階型別的介紹

回到「給新手的C++教學 (上冊)」

回到「13. 額外語法 (Extra syntax)」

上一頁

請注意,這裡只介紹小莫用過的型別,但應該已經非常非常夠用了!
為了方便介紹,小莫將其分為兩大類:「浮點數」和「整數」(註13)
  1. 浮點數
    • 特色:支援小數、有微小誤差、數字範圍大
    • 溢位會怎樣:超過最大值會變成inf,超過最小值會變成-inf
    • 除以0會怎樣:正數除以0會變成inf、負數除以0會變成-inf,0除以0會變成nan (「not a number」的縮寫)
    • 型別有哪些:
      名稱floatdoublelong double__float128
      占用記憶體32位元64位元96位元128位元
      誤差和數字範圍占用記憶體越大,誤差越小、數字範圍越大
      輸入(scanf)%f%lf不支援不支援
      輸出(printf)%f%f不支援不支援
      斜體標示 (針對__float128):如果要使用此型別,請先參考前一頁並設定好編譯參數,以使用C++11的功能
  2. 整數
    • 特色:不支援小數、沒有誤差、數字範圍小
    • 溢位會怎樣:超過最大值會變成最小值,超過最小值會變成最大值
    • 除以0會怎樣:會造成不可預知的錯誤,通常是程式出錯並停止運作
    • 型別有哪些:
      名稱boolcharshortintlong long__int128
      占用記憶體8位元8位元16位元32位元64位元128位元
      數字範圍$0\sim 1$$(-2^{占用記憶體-1})\sim (2^{占用記憶體-1}-1)$
      輸入(scanf)不支援%c不支援%d%lld不支援
      輸入(scanf)不支援%c不支援%d%lld不支援
      粗體標示 (針對char):輸入輸出皆以字元的方式進行 (而不是數字)
      斜體標示 (針對__int128):如果要使用此型別,請先參考前一頁並設定好編譯參數,以使用C++11的功能
對於不支援輸入輸出的型別的其中一種處理方式 (以相加兩個「long double」為例):