最近在用c++語言程式設計時,遇到如下問題:判斷下列程式片段的輸出內容。
下面是其標準輸出:
很明顯,以上問題的根本在於運算子(解引用和遞增運算子)作用的優先順序,只有弄清楚了相關運算子的優先順序順序,這個問題的解決自然水到渠成。
遞增(++)和遞減運算子(–)為物件的加1和減1提供了一種簡潔的書寫形式。這兩個運算子還可以用於迭代器,因為很多迭代器本身不支援算術運算,所以此時遞增和遞減運算子除了書寫簡潔外還是必須的。
遞增和遞減運算子有兩種形式:前置版本和後置版本(或字首或字尾)。前置版本的運算子首先將運算物件加1(或減1),然後將改變後的物件作為求值結果。後置版本也會將運算子物件加1(或減1),但求值結果是運算物件改變之前那個值得副本。
以上引用我們需要注意兩點:第一,遞增和遞減運算子在某些迭代器中不僅簡潔而且是必須的;第二,後置版本的遞增或遞減運算子返回未改變之前的值作為運算結果。知道這兩點後我們就可以看看本文開頭的問題。
這裡用了『混用』一詞,我想只是為了說明問題所在,而不是表示乙個貶義或不提倡,後文對這一點會做出合理的建議!
引入的問題是,對於指向陣列arr的指標ptr,*ptr++的輸出到底是什麼?這裡應該知道後置遞增運算子的優先順序是高於解引用運算子的。(不熟悉可以參考博文:所以,此式子會首先執行ptr++運算,這導致的問題有兩個:其一,ptr這個指標確實指向了陣列的下乙個元素;其二,ptr++運算後的返回值是ptr未改變前的副本(由文章第二部分易知)。然後執行*ptr運算,此時的ptr是未經遞增運算的副本即arr[0],所以第乙個輸出是元素1。之後再對ptr解引用輸出,自然是已經遞增過的指標,則應該輸出arr[1]即2。
知道了上面表示式的運算過程,我們可以看看下面幾條語句的輸出:
讀者可以自己梳理,然後上機除錯出了,輕鬆應對這些繁瑣的變化!
我們該講點正式的東西了。首先我們還是看一段實現乙個簡單功能的程式片段:
以上**,主體是用迭代器對向量容器的一次遍歷,但是我們要研究的問題不會停留在這裡。下面我們要把他改動成我們本次所研究的問題——混用解引用與遞增運算子的版本,**段如下:
兩個程式段的列印結果如下:
沒錯,這兩段**的列印結果都是上圖中的這乙個!我可沒說他們真的是一樣的 !為了方便前後**的比較,我在改進後的版本中注釋掉了原版的部分語句。改進後的版本加入了我們本文中的「運算子混用」策略。為什麼說他們不是真的一樣?下面我們把程式做乙個小小的改動,再來看看執行結果:
我們的改動是,插入的元素不再是原來的奇數,而是原來奇數的100倍,這樣做只是為了區分那個才是我們真正插入的元素!從列印結果中可以看到,他們真的不一樣,新插入的元素乙個是在原來的奇數前,另乙個是在原來的奇數後面。先前我們感覺『一樣』是因為我們插入了與原來一樣的資料,導致我們產生了誤判。那麼到底為何?
其本因還是在於混用導致我們提前改變了迭代器iter,在原來的版本中,我們插入元素後,直接讓迭代器前進兩個元素,跳過了我們新插入的元素,這沒什麼,如我們所料。但是在改進的版本中,插入函式的第二個引數解引用之後,iter就已經改變了,而恰好我們在插入函式的第乙個引數中使用了他,儘管我們會用insert的返回值重新更新iter,但是為時已晚,我們剛好插入到了已知奇數的後面,弄拙成巧!所以看到了列印出完全不同的結構。
從這裡我們應該尤其注意一點,在混用解引用與遞增(或遞減)運算子的時候,無論指標還是迭代器都是立刻改變的,這就會出現乙個「隱患」,我們可能會立刻用這個指標或者運算子,但是如果我們依舊認為他還是原來的指向就會出現嚴重的程式設計邏輯錯誤,並且這種錯誤很難被發現!
程式設計中,上述的邏輯錯誤是很難被發現的,也許會浪費我們大量的時間和精力,甚至導致專案擱淺!
避免這些的有效途徑除了掌握紮實的語法外,還應該在平時程式設計的時候養成良好的習慣。下面是摘自一些大師的忠告:
(1)除非必須,否則不用遞增遞減運算子的後置版本
前置版本的運算子避免了不必要的工作,他把值加1後直接返回改變了的運算物件。與之相比,後置版本需要將原始值儲存下來以便於返回這個未修改的內容。如果我們不需要修改前的值,那麼後置版本的操作就是一種浪費。
(2)簡潔可以成為一種美德
cout<
(3)大多數運算子都沒有規定運算物件的求值順序,這在一般情況下不會有什麼影響。然而如果一條子表示式改變了某個運算物件的值,另一條子表示式又要使用該值得話,運算物件的求值順序就很關鍵了。因為遞增運算子和遞減運算子會改變運算物件的值,所以要提防在復合表示式中錯用這兩個運算子。
書籍:《c++primer(第五版)》
博文:
linq語句中多個記錄合成一條記錄
小菜先說一下環境應用,在福分系統中每天都有加分的記錄,並且每次加分的分值是不同的。現在有乙個這樣的需求就是按照一定的時間段,求出某個人的所加的分值。並且按照分值排名。該怎麼著手呢?先求出這段時間的所有的記錄,這個比較簡單。難的地方在,這怎麼通過人名 id 得到特定人的幾條記錄,然後得到總分,並且是不...
一條SQL語句在MySQL中執行過程全解析
本篇文章會分析乙個 sql 語句在 mysql 中的執行流程,包括 sql 的查詢在 mysql 內部會怎麼流轉,sql 語句的更新是怎麼完成的。在分析之前我會先帶著你看看 mysql 的基礎架構,知道了 mysql 由那些元件組成以及這些元件的作用是什麼,可以幫助我們理解和解決這些問題。下圖是 m...
SQL語句中一條常見的行合併問題
sql行合併問題一般寫個自定義函式,由orig word 統計similar word,就可以實現 問題 原來問題 現有兩個表 表一originalword id word 101 about 102 abound 103 beard 104 boast 105 beast 表二similarwor...