張合花,張全法,齊永奇
(1.鄭州大學(xué) 物理工程學(xué)院,鄭州 450001;2.華北水利水電大學(xué) 機(jī)械學(xué)院,鄭州 450045)
?
準(zhǔn)確使用C/C++中等于運(yùn)算符的研究
張合花1,張全法1,齊永奇2
(1.鄭州大學(xué) 物理工程學(xué)院,鄭州 450001;2.華北水利水電大學(xué) 機(jī)械學(xué)院,鄭州 450045)
本文討論C/C++程序設(shè)計(jì)中等于運(yùn)算符的兩種常見使用錯(cuò)誤,即與賦值運(yùn)算符混淆和用來判別兩個(gè)浮點(diǎn)數(shù)是否相等。從程序員的角度給出了減少與賦值運(yùn)算符混淆,以及不同情況下正確判別兩個(gè)浮點(diǎn)數(shù)是否相等的方法。并利用VC 6.0設(shè)計(jì)了一個(gè)Add-in程序,能夠自動發(fā)現(xiàn)有可能出現(xiàn)這兩種錯(cuò)誤的語句,提醒程序員注意,以便從集成開發(fā)環(huán)境的角度減少錯(cuò)誤。
C/C++程序設(shè)計(jì);等于運(yùn)算符;Add-in
《C語言程序設(shè)計(jì)》是大多數(shù)高校開設(shè)的第一門計(jì)算機(jī)語言課程,《C++語言程序設(shè)計(jì)》則是許多高校開設(shè)的計(jì)算機(jī)語言進(jìn)階課程,同時(shí)C/C++也是廣大工程技術(shù)人員最常用的程序設(shè)計(jì)語言之一。然而C/C++程序設(shè)計(jì)的靈活性也帶來了一些負(fù)面影響,即容易出現(xiàn)各種各樣的錯(cuò)誤,其中就有等于運(yùn)算符使用不慎造成的。
等于運(yùn)算符也稱為相等運(yùn)算符,用來判別兩個(gè)操作數(shù)是否相等。它由“==”構(gòu)成,很容易與由“=”構(gòu)成的賦值運(yùn)算符混淆(以下簡稱為混淆錯(cuò)誤),這在很多文獻(xiàn)中有論述[1-4]。另外它還常被用來判別兩個(gè)浮點(diǎn)數(shù)是否相等(以下簡稱為浮點(diǎn)數(shù)錯(cuò)誤),導(dǎo)致運(yùn)行結(jié)果不一定正確,例如文獻(xiàn)[5]和文獻(xiàn)[6]中的例程就出現(xiàn)了這種錯(cuò)誤。本文主要研究在編程中如何盡量避免出現(xiàn)這兩種錯(cuò)誤。
假設(shè)有這樣一個(gè)數(shù)學(xué)問題:在平面直角坐標(biāo)系Oxy中有4個(gè)點(diǎn)A、B、C、D,各點(diǎn)的坐標(biāo)值如圖1所示,問C、D兩點(diǎn)是否位于A、B兩點(diǎn)所決定的直線上?在數(shù)學(xué)上很容易求得通過A、B兩點(diǎn)的直線方程,進(jìn)而知道C點(diǎn)在直線上而D點(diǎn)不在直線上。
為了利用計(jì)算機(jī)求解,用VC 6.0新建控制臺類型的空白項(xiàng)目,項(xiàng)目名為Test。向其中添加名字為Test.cpp的源文件,文件內(nèi)容如下:
圖1 平面直角坐標(biāo)系中的點(diǎn)
#include
void main(){
float xA=0.0f,yA=0.04f;
float xB=0.01f,yB=0.03f;
float xC=0.005f,yC=0.035f;
float xD=0.02f,yD=0.04f;
float y=(xC-xB)*(yA-yB)/(xA-xB)+yB;
if(y==yC)
cout<<"C is on line."< else cout<<"C is not on line."< y=(xD-xB)*(yA-yB)/(xA-xB)+yB; if(y=yD) cout<<"D is on line."< else cout<<"D is not on line."< 以上程序在編譯、鏈接時(shí)均沒有任何錯(cuò)誤或警告。運(yùn)行程序,輸出為: C is on line. D is on line. 從運(yùn)行結(jié)果看,對C點(diǎn)的判別是正確的,對D點(diǎn)的判別是錯(cuò)誤的。針對錯(cuò)誤的運(yùn)行結(jié)果對程序進(jìn)行分析可以發(fā)現(xiàn),在第2個(gè)if語句的判斷表達(dá)式中誤將等于運(yùn)算符寫成了賦值運(yùn)算符。這使得只要其右操作數(shù)不為0,均執(zhí)行if子句而不執(zhí)行else子句。 改正之后再運(yùn)行程序,輸出為: C is on line. D is not on line. 此時(shí)對C、D兩點(diǎn)的判別均是正確的,好像程序沒有問題了。其實(shí)不然,只要將程序中各數(shù)據(jù)的小數(shù)點(diǎn)向后移一位,輸出將變?yōu)椋?/p> C is not on line. D is not on line. 對C點(diǎn)的判別不再正確。 為什么運(yùn)行結(jié)果會變得不再正確呢?原因在于浮點(diǎn)數(shù)在計(jì)算機(jī)中通常只能近似表示,即使兩個(gè)在數(shù)學(xué)上應(yīng)該相等的浮點(diǎn)數(shù),在計(jì)算機(jī)中經(jīng)過計(jì)算也很可能不相等。因此,用等于運(yùn)算符判別兩個(gè)浮點(diǎn)數(shù)是否相等很可能得不到正確結(jié)果。這種錯(cuò)誤比混淆錯(cuò)誤更加隱晦,很難在調(diào)試過程中被發(fā)現(xiàn),所以減少這種錯(cuò)誤的迫切性更強(qiáng)烈。 2.1 減少混淆錯(cuò)誤的方法 作為程序員,首先要認(rèn)識到判別兩個(gè)操作數(shù)是否相等應(yīng)該用兩個(gè)等號而不是一個(gè)等號。其次,在編輯程序時(shí)應(yīng)該時(shí)刻保持警惕,不要因?yàn)檫B續(xù)擊鍵不到位等原因而漏掉兩個(gè)等號中的一個(gè)。為了進(jìn)一步減少這種錯(cuò)誤,在條件允許的情況下應(yīng)該盡量將非左值表達(dá)式(例如常量)放到等于運(yùn)算符的左側(cè)。因?yàn)榇藭r(shí)一旦誤用了賦值運(yùn)算符,編譯器會報(bào)告錯(cuò)誤。 2.2 減少浮點(diǎn)數(shù)錯(cuò)誤的方法 作為程序員,首先要認(rèn)識到用等于運(yùn)算符判別兩個(gè)浮點(diǎn)數(shù)是否相等可能得到錯(cuò)誤的結(jié)果。其次還要知道遇到此類問題該如何處理。由于浮點(diǎn)數(shù)在計(jì)算機(jī)中只能近似表示,無法判別兩個(gè)浮點(diǎn)數(shù)是否嚴(yán)格相等,凡是判別兩個(gè)浮點(diǎn)數(shù)是否相等的問題通常只能轉(zhuǎn)化為判別它們是否近似相等。但是C/C++中沒有近似相等運(yùn)算符。為此,可以根據(jù)具體問題設(shè)置一個(gè)小量,當(dāng)兩個(gè)浮點(diǎn)數(shù)之差的絕對值小于該小量時(shí)認(rèn)為二者近似相等[7]。如果需要對浮點(diǎn)數(shù)進(jìn)行其他關(guān)系運(yùn)算,也需要進(jìn)行類似的處理,否則可能得不到正確結(jié)果。 對于以上經(jīng)過修改的程序,再把“if(y==yC)”和 “if(y==yD)”分別修改為“if(fabs(y-yC)<1.0e-6)”和“if(fabs(y-yD)<1.0e-6)”。由于此時(shí)需要調(diào)用庫函數(shù)fabs(),還應(yīng)該在程序前面包含頭文件math.h。再運(yùn)行程序,輸出為: C is on line. D is not on line. 結(jié)果正確。即使將數(shù)據(jù)恢復(fù)到移動小數(shù)點(diǎn)之前,結(jié)果依然正確。 對于某些問題,如果能夠轉(zhuǎn)化為整數(shù)運(yùn)算,則仍然可以使用等于運(yùn)算符。例如文獻(xiàn)[5]和文獻(xiàn)[6]的例程中,如果將對灰度值出現(xiàn)概率的求和修改為對灰度值出現(xiàn)次數(shù)的求和,再利用等于運(yùn)算符判別求和結(jié)果是否等于灰度值出現(xiàn)的總次數(shù),將可以避免處理某些圖像時(shí)陷入死循環(huán)。 3.1 必要性和可行性 編程知識和經(jīng)驗(yàn)不夠豐富的程序員不具備主動防范上述錯(cuò)誤的能力。即使編程知識和經(jīng)驗(yàn)很豐富的程序員也難以通過主動防范杜絕上述錯(cuò)誤。而僅根據(jù)調(diào)試結(jié)果又不太容易發(fā)現(xiàn)上述錯(cuò)誤,因?yàn)橛袝r(shí)候運(yùn)行結(jié)果沒有錯(cuò)誤,或者因調(diào)試不完備導(dǎo)致存在錯(cuò)誤的語句沒有被執(zhí)行。顯然,如果能夠設(shè)法讓集成開發(fā)環(huán)境自動發(fā)現(xiàn)上述錯(cuò)誤并發(fā)出警告,將非常有利于避免上述錯(cuò)誤的發(fā)生。 目前,在高校和企業(yè)中廣泛使用VC 6.0集成開發(fā)環(huán)境作為C/C++的教學(xué)和研發(fā)平臺。它可以外掛Add-in對正在開發(fā)的程序進(jìn)行分析,查找可能出現(xiàn)的問題。Add-in已經(jīng)被成功地用來減少重復(fù)聲明復(fù)合類型的風(fēng)險(xiǎn)[8]、使用goto語句的錯(cuò)誤[9]以及使用后置自增自減運(yùn)算符的非預(yù)期結(jié)果[10]。因此,設(shè)計(jì)一個(gè)能夠自動查找等于運(yùn)算符使用錯(cuò)誤的Add-in是可行的。 3.2 查找錯(cuò)誤的原理和困難 查找錯(cuò)誤的原理很簡單。對于在程序中查找到的每一個(gè)賦值運(yùn)算符,如果能夠推斷該處應(yīng)該使用等于運(yùn)算符則說明該處出現(xiàn)了混淆錯(cuò)誤,否則該處沒有錯(cuò)誤。對于在程序中查找到的每一個(gè)等于運(yùn)算符,如果能夠推斷它作用在浮點(diǎn)數(shù)上,則說明該處出現(xiàn)了浮點(diǎn)數(shù)錯(cuò)誤,否則該處沒有錯(cuò)誤。按照此原理可以避免對于上述兩種錯(cuò)誤的漏報(bào)。 困難在于如何準(zhǔn)確地進(jìn)行推斷。由于在Add-in中無法知道程序所描述的實(shí)際問題,很難推斷賦值運(yùn)算符是否應(yīng)該為等于運(yùn)算符。推斷等于運(yùn)算符操作數(shù)的類型也很不容易,因?yàn)槠洳僮鲾?shù)可以是各種各樣的表達(dá)式,甚至可以沒有類型,例如宏形參。即使其操作數(shù)為變量也不容易推斷其類型,因?yàn)樽兞款愋偷穆暶骺赡懿辉诒疚募?,例如在包含文件中,包含文件還可以嵌套;內(nèi)層變量的類型可以與外層同名變量的類型不同等等。 3.3 解決問題的辦法 如上所述,在Add-in中很難推斷是否一定產(chǎn)生了錯(cuò)誤,然而程序員對程序所描述的問題是清楚的。因此,將可能出現(xiàn)錯(cuò)誤的位置及可能的錯(cuò)誤類型報(bào)告給程序員去判斷,可以得到準(zhǔn)確的結(jié)果并由其采取正確的措施。但是這又會帶來新的問題。等于運(yùn)算符使用得比較少,即使將所有使用它的地方報(bào)告給程序員,誤報(bào)量也不大。但是如果將所有使用賦值運(yùn)算符的地方報(bào)告給程序員,必將產(chǎn)生大量誤報(bào),因?yàn)橘x值運(yùn)算是一種很常見的運(yùn)算。 可以根據(jù)等于運(yùn)算符的屬性減少誤報(bào)。等于運(yùn)算符屬于關(guān)系運(yùn)算符,最有可能出現(xiàn)的地方首先是if語句的判斷條件表達(dá)式,其次是條件運(yùn)算符的判斷條件表達(dá)式,然后是for、while和do…while語句的循環(huán)條件表達(dá)式。而這些地方使用賦值運(yùn)算符的可能性是比較低的。僅在這些地方尋找賦值運(yùn)算符并報(bào)告給程序員可以減少誤報(bào)。不過這樣做有可能出現(xiàn)漏報(bào),因?yàn)榈扔谶\(yùn)算符允許出現(xiàn)在這些地方之外。 3.4 Add-in的開發(fā) 有關(guān)如何在VC 6.0中創(chuàng)建Add-in類型的項(xiàng)目、在Add-in的哪個(gè)函數(shù)中訪問項(xiàng)目文件、訪問項(xiàng)目文件之前應(yīng)該做哪些準(zhǔn)備工作、如何逐一訪問項(xiàng)目中的所有源文件和頭文件、如何在內(nèi)存中對各個(gè)文件的內(nèi)容進(jìn)行簡化以及如何輸出警告信息等,可以參考文獻(xiàn)[8]~文獻(xiàn)[10]。下面僅簡單介紹一下查找可能存在錯(cuò)誤之語句的位置及類型的方法步驟。 (1)從文件開頭向后搜索“==”,一直到文件末尾。每搜索到一處,記錄正在分析的文件名、“==”所在的行號和問題類型。此時(shí)問題類型為“使用==”。 (2)從文件開頭向后搜索“?”,一直到文件末尾。每搜索到一處,再從該處向前尋找最近的運(yùn)算符。如果它為賦值運(yùn)算符(注意等號未必是賦值運(yùn)算符,下同,不再說明),記錄正在分析的文件名、“?”所在的行號和問題類型。此時(shí)問題類型為“?:中使用=”。 (3)從文件開頭向后搜索“if”(注意防止它是其他標(biāo)識符的一部分,下同,不再說明),一直到文件末尾。每搜索到一處,再從該處向后尋找最近且配對的左、右圓括號,并在其間搜索賦值運(yùn)算符。如果搜索到了,記錄正在分析的文件名、“if”所在的行號和問題類型。此時(shí)問題類型為“if中使用=”。 (4)從文件開頭向后搜索“for”,一直到文件末尾。每搜索到一處,再從該處向后尋找最近且配對的左、右圓括號,接著在其間尋找兩個(gè)“;”,并在這兩個(gè)“;”之間搜索賦值運(yùn)算符。如果搜索到了,記錄正在分析的文件名、“for”所在的行號和問題類型。此時(shí)問題類型為“for中使用=”。 (5)從文件開頭向后搜索“while”,一直到文件末尾。每搜索到一處,再從該處向后尋找最近且配對的左、右圓括號,并在其間搜索賦值運(yùn)算符。如果搜索到了,記錄正在分析的文件名、“while”所在的行號和問題類型。此時(shí)問題類型取決于上述右圓括號后面的第一個(gè)非空白字符。如果它為“;”,則問題類型為“do…while中使用=”,否則為“while中使用=”。 3.5 Add-in的運(yùn)行結(jié)果 Add-in開發(fā)好后,按照文獻(xiàn)[10]中所述方法使其外掛到VC 6.0中。然后重新打開上述Test項(xiàng)目,回退到未進(jìn)行修改之前,再執(zhí)行Build菜單命令,將彈出如圖2所示的對話框,給出警告信息。經(jīng)過測試,證明了它在大多數(shù)情況下能夠發(fā)現(xiàn)等于運(yùn)算符使用中可能存在的問題。 圖2 發(fā)現(xiàn)問題時(shí)彈出的對話框 討論了C/C++中主動減少等于運(yùn)算符使用錯(cuò)誤的方法。開發(fā)的Add-in程序能夠自動尋找在VC 6.0中使用等于運(yùn)算符時(shí)可能出現(xiàn)錯(cuò)誤的地方,有助于程序員及時(shí)發(fā)現(xiàn)問題并采取正確措施,可以為廣大學(xué)生和工程技術(shù)人員學(xué)習(xí)、掌握及運(yùn)用C/C++程序設(shè)計(jì)提供幫助。事實(shí)上,當(dāng)開發(fā)的Add-in發(fā)現(xiàn)可能發(fā)生混淆錯(cuò)誤的地方時(shí),實(shí)際上有可能是其他類型的錯(cuò)誤。例如由于擊鍵不到位,應(yīng)該使用小于等于、大于等于或不等于運(yùn)算符的地方變成了使用賦值運(yùn)算符等。因此也可以減少出現(xiàn)這些錯(cuò)誤的可能性。這可以算是所開發(fā)的Add-in的額外收獲。 [1]譚浩強(qiáng).C程序設(shè)計(jì)[M].4版.北京:清華大學(xué)出版社, 2010:359-360. [2]趙寶琴,袁志民.C/C++上機(jī)實(shí)驗(yàn)中的典型錯(cuò)誤[J].實(shí)驗(yàn)室科學(xué),2005(3):92-93. [3]萬權(quán)性,熊力維,李微.C語言常見錯(cuò)誤原因分析及防范[J].福建電腦,2010(12):167-168. [4]劉偉,支聯(lián)合.C語言教學(xué)中遇到的問題及解決辦法[J].重慶科技學(xué)院學(xué)報(bào):自然科學(xué)版,2009,11(2):136-138. [5]何斌,馬天予,王運(yùn)堅(jiān),等.Visual C++數(shù)字圖像處理[M].2版.北京:人民郵電出版社,2002:583-594. [6]郎銳.數(shù)字圖像處理學(xué)——Visual C++實(shí)現(xiàn)[M].北京:北京希望電子出版社,2002:387-395. [7]周冠方.C語言中浮點(diǎn)數(shù)精度問題分析[J].湖北工業(yè)職業(yè)技術(shù)學(xué)院學(xué)報(bào),2015,28(3):97-99. [8]張全法,齊永奇.用Add-in提高VC 6.0集成開發(fā)環(huán)境的查錯(cuò)能力研究[J].中原工學(xué)院學(xué)報(bào),2009,20(3):47-50. [9]張全法,陳倩.用Add-in減少VC 6.0中g(shù)oto語句使用錯(cuò)誤的研究[J].中原工學(xué)院學(xué)報(bào),2013,24(2):57-60. [10]張合花,齊永奇.減少后置自增自減運(yùn)算符非預(yù)期結(jié)果的研究[J].中原工學(xué)院學(xué)報(bào),2015,26(1):87-90. (責(zé)任編輯 趙冰) Study on Reducing Use Errors of Equality Operator in C/C++ Programming ZHANG He-hua1,ZHANG Quan-fa1,QI Yong-qi2 (1.School of Physics and Engineering, Zhengzhou University, Zhengzhou 450001, China; 2.North China University of Water Resources and Electric Power, Zhengzhou 450045, China) Two kinds of common use errors of equality operator in C/C++ programming are discussed, one is to confuse with assignment operator and the other is to determine whether two floating point numbers are equal.For the programmers, some methods to reduce confusion with assignment operator and determine whether two floating point numbers are equal under different conditions are given.An Add-in is developed with VC 6.0 to find where maybe appear these kinds of errors and to notify the programmer to examine his program, thus to reduce the probability of errors from the perspective of the integrated development environment. C/C++ programming; equality operator; Add-in 2015-12-21 張合花(1970—),女,河南湯陰人,鄭州大學(xué)物理工程學(xué)院技師,主要研究方向?yàn)橛?jì)算機(jī)信息處理。 10.13783/j.cnki.cn41-1275/g4.2016.02.023 TP311.52 A 1008-3715(2016)02-0112-042 主動減少錯(cuò)誤的方法
3 利用Add-in減少錯(cuò)誤
4 結(jié)束語