亚洲免费av电影一区二区三区,日韩爱爱视频,51精品视频一区二区三区,91视频爱爱,日韩欧美在线播放视频,中文字幕少妇AV,亚洲电影中文字幕,久久久久亚洲av成人网址,久久综合视频网站,国产在线不卡免费播放

        ?

        淺析C語言運算符及表達(dá)式的教學(xué)誤區(qū)

        2019-04-08 00:46:50熊志斌
        現(xiàn)代計算機(jī) 2019年6期
        關(guān)鍵詞:標(biāo)準(zhǔn)

        熊志斌

        (海南熱帶海洋學(xué)院藝術(shù)與創(chuàng)意學(xué)院,三亞572022)

        0 引言

        C語言是一門優(yōu)秀的程序設(shè)計語言,以功能強(qiáng)大、語法靈活、易移植等特點而著稱。自上世紀(jì)70年代以來,C語言一直是最受歡迎的編程語言之一,廣泛用于系統(tǒng)應(yīng)用開發(fā)。我國高校計算機(jī)專業(yè)把C語言作為編程入門課程,許多工科專業(yè)也把C語言作為必修課或選修課,因此提高C語言教學(xué)質(zhì)量,具有重要的意義。筆者發(fā)現(xiàn)在一些教材、教改論文中把一些錯誤表達(dá)式當(dāng)成正確的代碼來分析、講解,如下面類似的錯誤表達(dá)式,在許多教材、教改論文分析、討論,也出現(xiàn)在等級考試、企業(yè)招聘試題中:

        其實,這些表達(dá)式都是錯誤的,會導(dǎo)致未定義行為(Undefined Behavior),C標(biāo)準(zhǔn)對未定義行為的解釋是,使用不可移植的或錯誤的程序結(jié)構(gòu),以及使用錯誤數(shù)據(jù)的行為,而標(biāo)準(zhǔn)對這種行為沒有強(qiáng)制性的要求[1-2]。C標(biāo)準(zhǔn)定義了很多未定義行為,表達(dá)式導(dǎo)致的未定義行為只是其中之一。C標(biāo)準(zhǔn)不要求編譯器負(fù)責(zé)診斷這種未定義行為,因此,一些未定義行為在編譯時能通過,程序也可以正確執(zhí)行。正是在某些平臺能編譯執(zhí)行這些錯誤代碼,才使得它們被當(dāng)成正確的代碼被分析、被討論、被考試。但并不能保證這些代碼在不同編譯器中都能通過,也不能保證在不同平臺都可以正確執(zhí)行。

        把這些錯誤的表達(dá)式當(dāng)成正確的代碼分析、講授,即不能使學(xué)生掌握正確的C語法知識,也不利于培養(yǎng)學(xué)生的優(yōu)良編程風(fēng)格。本文試圖從C標(biāo)準(zhǔn)出發(fā),正本溯源,去蕪存菁,通過介紹序列點和副作用,澄清“表達(dá)式求值按運算符優(yōu)先級順序從左到右計算,同級運算符按結(jié)合性方向計算”的模糊認(rèn)識,剖析導(dǎo)致未定義行為的表達(dá)式的錯誤根源,以供C語言教學(xué)時參考。

        1 相關(guān)的重要概念

        與運算符及表達(dá)式相關(guān)的重要概念,除教材上介紹的運算符優(yōu)先級和結(jié)合性外,C標(biāo)準(zhǔn)定義的副作用(Side Effects)和序列點(Sequence Point)也是不可或缺的兩個概念。教材出于內(nèi)容簡單明了,易于學(xué)習(xí)的考慮,盡量回避復(fù)雜的概念,因此教材在介紹運算符及表達(dá)式相關(guān)知識時,基本回避了副作用和序列點,國內(nèi)外的經(jīng)典教材都是如此[3-4]。筆者在教學(xué)中發(fā)現(xiàn),在講授運算符及表達(dá)式時,引入上述兩個概念是大有裨益的,能幫助學(xué)生正確地理解運算符及表達(dá)式的知識。

        1.1 副作用

        C標(biāo)準(zhǔn)對副作用的定義是,訪問易變(Volatile)型變量、修改變量、修改文件、以及調(diào)用執(zhí)行前述操作的函數(shù)都是副作用[2]。副作用可以簡單理解成,作為表達(dá)式求值過程中的副產(chǎn)品,某些變量的值發(fā)生了修改[5]。例如表達(dá)式:

        表達(dá)式(1)是一個算術(shù)表達(dá)式,在計算表達(dá)式a+b的值時,變量b的值也會發(fā)生修改,修改變量的值就是副作用。表達(dá)式(2)是一個賦值表達(dá)式,表達(dá)式的值為5,變量a的值被修改成5,也是發(fā)生副作用。副作用并非是不受歡迎的副產(chǎn)品,而是修改變量值的一個手段。

        1.2 序列點

        序列點是程序執(zhí)行中的一個點,在這個點之前,前面的表達(dá)式的求值和副作用已經(jīng)完成,而后面表達(dá)式的求值和副作用還沒有發(fā)生[2]。C標(biāo)準(zhǔn)定義以下序列點[2]:

        (1)運算符&&;運算符||;逗號運算符,;條件運算符?:的第一個子表達(dá)式求值結(jié)束后;

        (2)函數(shù)調(diào)用運算符()中對所有實參數(shù)完成求值之后;

        (3)每個完整表達(dá)式結(jié)束時。完整表達(dá)式包括變量初始化表達(dá)式,表達(dá)式語句的表達(dá)式,return語句的表達(dá)式,if或switch語句中的控制表達(dá)式,while或do語句的控制表達(dá)式,for語句的所有三個表達(dá)式;

        (4)標(biāo)準(zhǔn)庫函數(shù)返回之前,標(biāo)準(zhǔn)輸入輸出函數(shù)格式化轉(zhuǎn)換說明符關(guān)聯(lián)動作之后,標(biāo)準(zhǔn)查找函數(shù)和排序函數(shù)在調(diào)用比較函數(shù)之前和之后及參數(shù)傳遞之后.

        由序列點的定義可知,與運算符&&;或運算符||;逗號運算符,;條件運算符?:等4個運算符的左操作數(shù)屬于前一個序列點,右操作數(shù)屬于后一個序列點,因此,這4個運算符的左操作數(shù)的求值要先于右操作數(shù)完成,本文第2節(jié)詳細(xì)分析這4個運算符。每個表達(dá)式語句后存在一個序列點,體現(xiàn)在程序里就是語句后的分號“;”是一個序列點。

        2 深入理解操作數(shù)的求值順序

        2.1 求值順序的不確定性

        C標(biāo)準(zhǔn)規(guī)定,在兩個序列點之間,運算符的子表達(dá)式(操作數(shù))的求值順序和副作用的發(fā)生順序,屬于未規(guī)定行為[2]。

        C標(biāo)準(zhǔn)對未規(guī)定行為的定義是,標(biāo)準(zhǔn)提供了兩種及以上的可能性,但在具體實現(xiàn)中并未強(qiáng)制選擇哪種可能性[2]。C標(biāo)準(zhǔn)定義了很多未規(guī)定行為,對未規(guī)定行為采取何種可能的方式實現(xiàn),取決于編譯器。未規(guī)定行為和未定義行為有本質(zhì)區(qū)別的,未定義行為是一種嚴(yán)重的程序錯誤,而未規(guī)定行為是C標(biāo)準(zhǔn)有意為之,目的是給編譯器提供優(yōu)化空間,以便提高C程序的效率。根據(jù)序列點的定義可知,除與運算符&&;或運算符||;逗號運算符,;條件運算符?:之外,其他雙目運算符,左右操作數(shù)的求值順序是不確定的,發(fā)生副作用的順序也是不確定的;在函數(shù)調(diào)用運算符()構(gòu)成的表達(dá)式里,函數(shù)名和各實參的求值順序和副作用發(fā)生的順序也是不確定的。

        2.2 存在序列點的表達(dá)式

        與運算符&&;或運算符||;逗號運算符,;條件運算符?:;函數(shù)調(diào)用運算符()等5個運算符和操作數(shù)構(gòu)成的表達(dá)式中存在序列點,者決定了它們的運算性質(zhì)與其他運算符不同。

        (1)邏輯與運算符及表達(dá)式

        邏輯與運算符&&和操作數(shù)構(gòu)成邏輯與表達(dá)式,一般形式為:

        表達(dá)式1&&表達(dá)式2

        邏輯與表達(dá)式的求值規(guī)則是:表達(dá)式1在&&左側(cè),屬于前一個序列點,因此首先計算表達(dá)式1的值,如果表達(dá)式1的值為0,則提前終止表達(dá)式2的計算(邏輯與的短路計算性質(zhì)),只有當(dāng)表達(dá)式1的值為1時,才開始計算表達(dá)式2的值。例如:

        (a+1)&&(--b>1)

        當(dāng)a的值為-1,b的值為2時,雖然表達(dá)式中自減運算符的優(yōu)先級最高,但(a+1)在&&左側(cè),屬于前一個序列點,因此首先計算(a+1)的值為0,可以判斷整個邏輯表達(dá)式的值為0,表達(dá)式--b>1的值來不及計算就被終止了,變量b的值保持不變。

        (2)邏輯或運算符及表達(dá)式

        邏輯或運算符||和操作數(shù)構(gòu)成邏輯或表達(dá)式,一般形式為:

        表達(dá)式1||表達(dá)式2

        邏輯或表達(dá)式的求值規(guī)則是:表達(dá)式1在||左側(cè),屬于前一個序列點,因此首先計算表達(dá)式1的值,如果表達(dá)式1的值為1,則提前終止表達(dá)式2的計算(邏輯或的短路計算性質(zhì)),只有當(dāng)表達(dá)式1的值為0時,才開始計算表達(dá)式2的值。例如:

        (a>b)||(++b>1)

        當(dāng)變量a為2,變量b為1時,首先求a>b的值為1,則整個邏輯表達(dá)式的值為1,表達(dá)式++b>1來不及計算就被終止,變量b的值保持不變。

        (3)條件運算符及表達(dá)式

        條件運算符?:需要3個操作數(shù)構(gòu)成條件表達(dá)式,一般構(gòu)成形式:

        表達(dá)式1?表達(dá)式2:表達(dá)式3

        條件表達(dá)式的求值規(guī)則是:表達(dá)式1位于?號前,屬于前一個序列點,首先計算表達(dá)式1的值,若表達(dá)式1的值為真,則條件表達(dá)式的值取表達(dá)式2的值,表達(dá)式3不被執(zhí)行;否則,條件表達(dá)式的值取表達(dá)式3的值,而表達(dá)式2不被執(zhí)行。表達(dá)式2和表達(dá)式3只能執(zhí)行其中的一個。例如:

        i>j?2*k:++m

        當(dāng)i為2,j為1,k為3,m為4時,雖然乘法運算符和自增運算符的優(yōu)先級都高于大于運算符,但表達(dá)式i>j屬于前一個序列點,因此首先求得表達(dá)式i>j的值為1,再執(zhí)行表達(dá)式2*k,不執(zhí)行表達(dá)式++m。所以條件表達(dá)式的值為6,m的值保持不變。

        (4)逗號運算符及表達(dá)式

        逗號作運算符需要兩個操作數(shù)構(gòu)成逗號表達(dá)式,一般形式為:

        表達(dá)式1,表達(dá)式2

        逗號表達(dá)式求值規(guī)則是:表達(dá)式1位于逗號運算符之前,屬于前一個序列點,先求解表達(dá)式1,再求解表達(dá)式2,表達(dá)式2的值是整個逗號表達(dá)式的值。例如:

        i=25+5,i*6

        雖然表達(dá)式中乘法運算符*的優(yōu)先級最高,但i=25+5位于逗號運算符之前,屬于前一個序列點,因此先計算賦值表達(dá)式i=25+5,得到i的值為30,然后計算i*6,得180,整個逗號表達(dá)式的值為180。

        (5)函數(shù)調(diào)用運算符()

        函數(shù)調(diào)用運算符的一般形式為:f(參數(shù)1,參數(shù)2,參數(shù)3)。對于函數(shù)調(diào)用運算符有兩點需要注意:一是參數(shù)之間的逗號,不是逗號運算符,是分隔符;二是函數(shù)名、各參數(shù)的求值順序是未規(guī)定行為,取決于編譯器的實現(xiàn)。有些參考書籍上說函數(shù)的各參數(shù)的求值順序是從左到右求值,這是不正確的,C標(biāo)準(zhǔn)本身沒有規(guī)定實參的求值順序。

        2.3 序列點的好處

        認(rèn)真分析表達(dá)式的構(gòu)成,充分利用表達(dá)式中序列點前表達(dá)式先計算的特性,可以寫出簡潔高效,風(fēng)格優(yōu)雅的C程序代碼。如程序需要從鍵盤讀取數(shù)字,直到用戶輸入0時為止,則可以寫成如下風(fēng)格的代碼:

        寫邏輯與表達(dá)式時,把最基本的條件放在第一個表達(dá)式,首先被執(zhí)行,如果值為假,后面就不用計算。如防止除0運算:

        a!=0&&b/a>5

        這樣就可以避免當(dāng)a值為0時,導(dǎo)致除0的異常。再如防止數(shù)組越界

        i0

        寫邏輯或表達(dá)式時,可以讓某些運算首先執(zhí)行,增加程序的效率。如閏年的滿足條件是:能被4整除而不能被100整除,或者能被400整除??捎眠壿嫳磉_(dá)式來表示:

        表達(dá)式(3)的執(zhí)行效率比表達(dá)式(4)執(zhí)行效率高。

        3 表達(dá)式的求值過程

        在計算表達(dá)式的值時,先根據(jù)運算符的優(yōu)先級和結(jié)合性地解析表達(dá)式,無論多復(fù)雜的一個表達(dá)式,經(jīng)過優(yōu)先級和結(jié)合性對表達(dá)式進(jìn)行解析后,最終都可以解析成某個基本運算符表達(dá)式結(jié)構(gòu)(如屬于賦值表達(dá)式、條件表達(dá)式)。判斷依據(jù)就是表達(dá)式中最外層執(zhí)行的運算符。解析過程中,從左到右利用運算符的優(yōu)先級構(gòu)成子表達(dá)式,同級運算符用結(jié)合性構(gòu)成子表達(dá)式。

        表達(dá)式(5)等價于(x>y)?++y:((++y>2)?y:100),所以表達(dá)式(5)是一個條件表達(dá)式。

        解析成基本運算符表達(dá)式結(jié)構(gòu)后,考察基本運算符是否構(gòu)成序列點,按序列點的前后順序計算子表達(dá)式。C語言中只有與運算符&&;或運算符||;逗號運算符,;條件運算符?:;函數(shù)調(diào)用運算符()等5個基本運算符構(gòu)成的表達(dá)式中存在序列點,因此這些表達(dá)式求值,總是先執(zhí)行序列點之前的子表達(dá)式,然后執(zhí)行序列點之后的子表達(dá)式。其他基本運算符構(gòu)成的表達(dá)式不存在序列點,子表達(dá)式求值順序和副作用發(fā)生順序則是不確定的。

        表達(dá)式(5)中,條件運算符雖然是右結(jié)合性,但并不是先計算子表達(dá)式((++y>2)?y:100),而是先計算屬于前一個序列點的子表達(dá)式(x>y)。當(dāng)x值為1,y值為1時,條件表達(dá)式的值為100;當(dāng)x值為2,y值為1時,表達(dá)式的值為2。

        4 導(dǎo)致未定義行為的表達(dá)式

        C標(biāo)準(zhǔn)定義了很多未定義行為,對于表達(dá)式而言,標(biāo)準(zhǔn)規(guī)定,兩個序列點之間,一個變量被多次修改,或被修改一次同時發(fā)生了不是為了存儲而讀取變量的操作,會導(dǎo)致未定義行為[2]。

        (1)變量被修改多次

        上面兩個表達(dá)式的變量都發(fā)生多次修改,因此是未定義行為。變量修改的時間點是不確定的,這種不確定導(dǎo)致表達(dá)式結(jié)果不確定。

        (2)變量被修改一次同時發(fā)生非存儲性的讀取操作

        如表達(dá)式:

        x++*y+x/2

        表達(dá)式中變量x被修改了一次,同時變量x被讀取參與子表達(dá)式x/2的求值,因此是未定義行為。x++自增運算符的副作用在何時發(fā)生,是不確定的,這種不確定影響子表達(dá)式x/2的求值不確定,導(dǎo)致表達(dá)式結(jié)果不確定。

        又如表達(dá)式:

        printf("%d %d",a++,a+5)

        在函數(shù)實參求值完成時是一個序列點,但在此序列點前,實參的求值的順序是不確定的,第二個參數(shù)a++和第三個參數(shù)a+5求值順序不一樣,傳入的實參是不一樣的,顯示的結(jié)果不確定,表達(dá)式導(dǎo)致未定義行為。

        又如表達(dá)式:

        a[i]=i++

        變量i發(fā)生一次修改,同時i被讀取參與a[i]求值,因此是未定義行為。對于賦值運算符兩端的操作數(shù),是先計算a[i]還是先計算i++,是不確定的,當(dāng)先計算i++時,如果i++的副作用在計算a[i]之前生效,則變成給a[i+1]賦值。

        本質(zhì)上說,表達(dá)式導(dǎo)致未定義行為是由于副作用使得子表達(dá)式之間存在計算順序上的依賴關(guān)系,而副作用的發(fā)生順序和子表達(dá)式的計算順序是不確定的,導(dǎo)致表達(dá)式的結(jié)果也不確定。因此,對導(dǎo)致未定義行為的表達(dá)式,只需增加臨時變量和語句來破解這種計算順序上的依賴關(guān)系就行了。如:

        a[i]=i++;

        可以寫成兩條語句

        a[i]=i;i++;

        5 結(jié)語

        通過介紹C標(biāo)準(zhǔn)中的序列點和副作用兩個概念,以及兩個序列點之間子表達(dá)式求值順序和副作用發(fā)生順序的不確定性的規(guī)定后,學(xué)生就不會產(chǎn)生“表達(dá)式求值是按運算符優(yōu)先級從左到右計算,同優(yōu)先級運算符按結(jié)合性方向計算”的似是而非的認(rèn)識,也很容易判斷出表達(dá)式是否會導(dǎo)致未定義行為,避免模仿別人寫出錯誤的表達(dá)式。其實,在C89標(biāo)準(zhǔn)中就有副作用和序列點的概念介紹,但國內(nèi)外教材基本在回避這兩個概念,國內(nèi)教材甚至以訛傳訛,把錯誤的表達(dá)式當(dāng)正確的代碼分析講授。這說明在C語言教學(xué)活動中,教師應(yīng)該勤查權(quán)威的C標(biāo)準(zhǔn),深入理解C之精髓,提高甄別錯誤、駕馭教材的能力,去蕪存菁,向?qū)W生傳授正確的知識。

        猜你喜歡
        標(biāo)準(zhǔn)
        2022 年3 月實施的工程建設(shè)標(biāo)準(zhǔn)
        忠誠的標(biāo)準(zhǔn)
        標(biāo)準(zhǔn)匯編
        上海建材(2019年1期)2019-04-25 06:30:48
        美還是丑?
        你可能還在被不靠譜的對比度標(biāo)準(zhǔn)忽悠
        一家之言:新標(biāo)準(zhǔn)將解決快遞業(yè)“成長中的煩惱”
        專用汽車(2016年4期)2016-03-01 04:13:43
        2015年9月新到標(biāo)準(zhǔn)清單
        標(biāo)準(zhǔn)觀察
        標(biāo)準(zhǔn)觀察
        標(biāo)準(zhǔn)觀察
        高潮毛片无遮挡高清视频播放| 亚洲中文久久久久无码| 亚洲av综合日韩精品久久久| 午夜免费观看一区二区三区| 综合五月激情二区视频| 欲色天天网综合久久| 秋霞日韩一区二区三区在线观看| 91精品啪在线观看国产色| 青青草精品视频在线播放| 久久久国产精品免费a片3d| 波多野结衣中文字幕在线视频| 亚洲精品一区二区三区播放| 亚洲女同av在线观看| 比较有韵味的熟妇无码| av无码久久久久久不卡网站| 日韩精人妻无码一区二区三区 | 少妇性l交大片免费快色| 男女av一区二区三区| 国产成人涩涩涩视频在线观看| 久久天天爽夜夜摸| 丰满人妻被猛烈进入中文字幕护士| 亚洲中文字幕精品乱码2021| 亚洲老妈激情一区二区三区 | 人妻 日韩 欧美 综合 制服| 亚洲一区二区三区偷拍女厕| 亚洲情精品中文字幕有码在线 | 国产日产在线视频一区| 久久久www免费人成精品| 热久久这里只有| 五十路一区二区中文字幕| 亚洲av无码乱码精品国产| 国产色a在线观看| 中文字幕无码免费久久9| 夜夜高潮夜夜爽免费观看| 最新国产福利在线观看精品| 不卡视频一区二区三区| 亚洲精品色播一区二区| 久久精品中文字幕| 国农村精品国产自线拍| 久久迷青品着产亚洲av网站| 亚洲免费国产中文字幕久久久|