天天看點

【C/C++】一個有點炫技的不必要的“複雜”的for循環示例

作者:海洋餅幹叔叔

C/C++裡for循環的初始化語句、測試及更新表達式可以是任意合法的語句/表達式,甚至可以空缺。當初始化語句及測試表達式空缺時,需要用一個分号占位。靈活運作上述特性,可以寫出很“複雜”的for循環。從軟體工程的角度看,不必要的使用“技巧”會使得程式難以了解和維護,實踐中,我們應該避免這種過分依賴于技巧的程式寫法。

知識産權協定

允許以教育/教育訓練為目的向學生或閱聽人進行免費引用,展示或者講述,無須取得作者同意。

不允許以電子/紙質出版為目的進行摘抄或改編。

示例程式如下:

//Project - ASCII
#include <cstdio>
#include <conio.h>

int main() {
    char c = 0;

    for (; c = getch(), c!=13 && c!=10;) { //初始化語句為空,更新表達式空缺
        putch(c);
        printf(" %d ",c);
    }

    printf("\nProgram exited!\n");
    return 0;
}           

上述程式的執行結果為(在英文輸入法下依次輸入字元a, b, A, B, 1, 2以及回車):

a 97 b 98 A 65 B 66 1 49 2 50
Program exited!           

conio.h頭檔案是C語言中控制台輸入輸出(console input & output)頭檔案,在本程式中,它引入了getch()及putch()函數。其中,getch()負責從控制台讀入一個輸入字元,傳回表示該字元對應ASCII碼的int整數;putch()函數預期接收一個int整數,然後向控制台輸出該整數按ASCII碼表對應的字元。

第8行:for循環中,初始化語句為空(分号占位),更新表達式空缺,僅提供了循環測試表達式。該for循環執行過程中, 初始化和更新兩步将被省略。測試表達式如下:

c = getch(), c!=13 && c!=10;           

這個測試表達式以逗号為界,分成了兩個表達式。這個逗号(comma)其實是一個操作符,它保證了如下兩點:

  • 逗号左邊的表達式c = getch()比逗号右邊的表達式先執行;這樣,當逗号右邊的表達式c!=13 && c!=10執行時,c已經取得了從getch()讀取的新字元。
  • 将逗号右方表達式的值作為整個測試表達式的結果。本例中,左表達式c = getch()在指派後傳回c作為表達式的值,右表達式也會傳回布爾運算的結果,但逗号表達式確定将右表達式,即c!=13 && c!=10的結果作為整個循環測試表達式的結果。

本程式中,作者期望把按下Enter鍵作為程式停止執行的條件,但在不同的作業系統及執行環境下,按下Enter鍵後,getch()得到的字元卻不盡相同,有的是’\r’,即“傳回行首”符,對應ASCII碼13,有的是’\n’,即“換行符”,對應ASCII碼10,甚至有的系統13和10會順序傳回。為了相容上述不同情況,循環的測試表達式同時檢查了13和10兩個值。

第8 ~ 11行:上述for循環借助于測試表達式,不斷從控制台讀取字元,如果是普通字元,執行循環體:輸出該字元(第9行),輸出該字元的ASCII碼值(第10行);如果讀入的字元等于13或10,即是由Enter鍵導緻的“傳回行首”符或“換行”符,測試表達式為假,結束循環,列印“Program exited!”資訊(第13行)。

上述程式的輸出結果證明:a的ASCII碼為97, b為98,A為65,B為66 … 它們是連續的。

讀者容易想到,上述程式其實不必寫得這麼晦澀,一個更容易的了解的寫法大緻如下:

//Project - ASCII2
#include <cstdio>
#include <conio.h>

int main() {
    while (true) { 
        char c = getch();
        if (c==13 || c==10)
            break;
        putch(c);
        printf(" %d ",c);
    }

    printf("\nProgram exited!\n");
    return 0;
}           

從軟體工程的角度看,過分的“炫技”隻會導緻難以了解和維護的代碼!實踐中,我們應該避免這種過分依賴于技巧的程式寫法。

關于逗号操作符(comma operator),有必要進行更進一步的讨論。請讀者考慮下述程式的執行結果:

//Project - Comma
#include <cstdio>

int main() {
   int a = 0, b = 0;

   a = b++, 3;

   printf("a = %d, b = %d",a,b);
   return 0;
}           

按照稍早提及的逗号操作符的文法含義,讀者可能會對第7行代碼進行如下解讀:

  • 逗号左表達式b++先執行,b值由0變1;
  • 逗号右表式的值3作為整個表達式的結果傳回,然後指派給a,a值變為3;
  • 執行結果為: a = 3, b = 1。

但真實的執行結果是:

a = 0, b = 1           

第7行:編譯器的真正解讀如下。

  • 逗号操作符的優先級低于指派操作符,是以a = b++被視為逗号的左表達式。逗号左表達式a = b++先執行,b++先取值,後遞增,故a被指派為b之前的初始值0;
  • 逗号右表達式3後執行,其傳回值3作為整個逗号表達式的值傳回。由于傳回後沒有“人”需要它,是以直接被舍棄。

本例中,如果期望a被指派為3,第7行應修改為:

a = (b++, 3);           

本案例節選自作者編寫的教材及配套實驗指導書。

《C++程式設計基礎及應用》(高等教育出版社,出版過程中)

《Python程式設計基礎及應用》,高等教育出版社

《Python程式設計基礎及應用實驗教程》,高等教育出版社

【C/C++】一個有點炫技的不必要的“複雜”的for循環示例

高校教師同行如果期望索取樣書,教學支援資料,加群,請私信作者,聯系時請提供學校及個人姓名為盼,各高校在讀學生勿擾為謝。

青少年讀者們如果期望系統性地學習Python及C/C++程式設計語言,歡迎嘗試下述今日頭條(西瓜)免費視訊課程。

C/C++從入門到放棄(重慶大學現場版)

Python程式設計基礎及應用(重慶大學現場版)

【C/C++】一個有點炫技的不必要的“複雜”的for循環示例