C++Primer學習筆記:第4章 表達式

  • 表達式由一個或多個運算對象組成,對表達式求值將得到一個結果。字面值和變量是最簡單的表達式,其結果就是字面值和變量的值。把一個運算符和一個或多個運算對象組合起來可以生成較復雜的表達式。

  • 重載運算符包括運算對象的類型和返回值的類型,都是由該運算符定義的;但是運算對象的個數、運算符的優先級和結合律都是無法改變的

  • 當一個對象被用作右值的時候,用的是對象的值(內容);當對象被用作左值的時候,用的是對象的身份(在內存中的位置)。需要右值的地方可以用左值來代替,但是不能把右值當成左值使用。

    • 賦值運算符需要一個(非常量)左值作為其左側運算對象,得到的結果也仍然是一個左值
    • 取地址符作用于一個左值運算對象,返回一個指向該運算對象的指針,這個指針是一個右值
    • 內置解引用運算符下標運算符迭代器解引用運算符stringvector的下標運算符的求值結果都是左值
    • 內置類型和迭代器的遞增遞減運算符作用于左值運算對象,其前置版本所得到的結果也是左值
  • 如果表達式的求值結果是左值,decltype作用于該表達式得到一個引用類型

    int a = 1;
    int *p = nullptr;
    decltype(*p) b = a;	//b是int &類型
    decltype(&p) c = nullptr;	//c是int **類型 	
    
  • 優先級規定了運算對象的組合方式,但是沒有說明運算對象按照什么順序求值,在大多數情況下,不會明確指定求值的順序。對于表達式int i = f1() * f2(),我們知道f1()f2()一定在執行乘法之前被調用,但是我們無法直到到底f1f2的執行先后順序。對于那些沒有指定執行順序的運算符來說,如果表達式指向并修改了同一個對象將會引發錯誤并產生未定義行為(UB)。因為<<運算符沒有明確規定何時以及如何對運算對象求值,因此cout << i << " " << ++i << endl是未定義的。

  • 有四種運算符明確規定了運算對象的求值順序:邏輯與&&、邏輯或||、條件(三元)運算符?:、逗號運算符,

  • 運算對象的求值順序與優先級和結合律無關,對每個運算對象的運算結果的計算順序是通過優先級和結合律決定,但是對運算對象的運算順序是不確定的。如果在一個表達式中有多個運算對象涉及對同一個對象的運算,那么很容易產生未定義的行為。例如f() + g() * h() + j()中,對這些函數的返回值的運算順序是確定的,但是對這些函數的運算順序是不確定的。

  • 書寫復合表達式的準則:

    • 拿不準的時候最好使用括號來強制讓表達式的組合關系復合程序邏輯的要求
    • 如果改變了某個對象的值,在表達式的其他地方不要再使用這個運算對象。這個其他地方是不包括當改變運算對象的子表達式本身就是另外一個子表達式的運算對象。例如:*++iter
  • 算數運算符的運算對象和求值結果都是右值。

  • 一元正號運算符、加法運算符和減法運算符都能作用于指針。當一元正號運算符作用于一個指針或者算術值時,返回運算對象的一個(提升后的)副本

    bool b = true;
    bool b2 = -b;	//相當于b2 = -1,所以b2為真	
    
  • 整數相除結果還是整數,參與取余運算的運算對象必須是整數類型

  • C++11新標準規定商一律向0取整(即直接切除小數部分)

  • 根據取余運算的定義,如果mn是整數且n非0,則表達式(m/n)*n+m%n的值和m相同。在C++11新標準中,除了-m導致溢出的特殊情況,其他時候(-m)/nm/(-n)都等于-(m/n)m%(-n)等于m%n(-m)%n等于-(m%n)

  • 邏輯運算符作用于任何能轉換成布爾值的類型。邏輯運算符和關系運算符的返回值都是布爾類型。運算對象和求值結果都是右值

  • 邏輯與與邏輯或都采用短路求值。可以用左側運算對象來保證右側運算對象求值過程的正確性和安全性

  • 使用范圍for循環時盡可能聲明成引用類型,能夠避免對元素的拷貝

  • 進行比較運算時除非比較的對象是布爾類型,否則不要使用布爾字面值truefalse作為運算對象

  • char *cp; if (cp && *cp) {}表示判斷指針cp所指向的數組是否為空,如果C字符串數組為空,則數組中只有一個空字符\0

  • 復制運算的結果是它左側的運算對象,并且是一個左值。如果賦值運算符左右兩個運算對象類型不同,則右側運算對象將轉換成左側運算對象的類型。

  • C++11新標準允許使用花括號括起來的初始值列表作為賦值語句的右側運算對象。如果左側運算對象是內置類型,那么初始值列表最多只能包含一個值,而且該值不能有丟失信息的風險(所占用的空間不應該大于目標類型的空間)。對于類類型來說,賦值運算的細節由類本身決定。vector模板重載了賦值運算符而且可以接收初始值列表,當賦值發生時用右側運算對象的元素替換左側運算對象的元素。無論左側運算對象的類型是什么,初始值列表都可以為空,此時編譯器創建一個值初始化的臨時量并將其賦給左側運算對象(我認為沒有默認構造函數的類可能不行)

    vector<int> a;
    a = {1, 2, 3, 4};			
    a = {1};
    a = {1, 2, 3, 4, 5, 6, 7};
    
  • 賦值運算滿足右結合律。因為賦值運算符的優先級低于關系運算符的優先級,所以在條件語句中,賦值部分通常應該加上括號

    int i;
    while(1 == (i = getValue())) {//
    }
    
  • 使用復合運算符只求值一次,使用普通的運算符則求值兩次(一次計算一次賦值)。因此盡量使用賦值運算符。

  • ++--可以用于迭代器,很多迭代器本身不支持算術運算。前置版本將對象本身作為左值返回,后置版本則將對象原始值的副本作為右值返回。如果不需要保存未修改版本的值,盡量使用前置版本不使用后置版本cout << *iter++ << endl;是一種被廣泛使用的有效的寫法。C++程序追求簡潔、摒棄冗長,因此C++程序員應該習慣于這種寫法。

  • 因為大多數的運算符都沒有規定求值順序,因此同一條表達式中最好在一個地方修改對象的值(且在改變這個對象以后就不要再進行使用),否則很容易產生未定義的行為(&& || , ?:除外)

  • ptr->item等價于(*ptr).item.運算符的優先級更高,因此括號必不可少。箭頭運算符作用于一個指針, 結果是一個左值。點運算符作用于左值則結果是左值,作用于右值則結果是右值

  • 條件運算符cond ? expr1 : expr2,其中cond是判斷條件的表達式,而expr1expr2是兩個類型相同或者可能轉換為某個公共類型的表達式。條件運算符是有求值順序的,而且條件運算符只會對expr1expr2中的一個求值。當條件運算符的兩個表達式都是左值或者能夠轉換成同一種左值類型時,運算的結果是左值,否則運算結果是右值

  • 條件運算符滿足右結合律,意味著運算對象一般按照從右往左的順序結合。條件運算的嵌套最好別超過二到三層。

    sting final_grade	= (grade > 90) ? "high pass": (grade < 60) ? "fail" : "pass";
    

    條件運算符的優先級比較低,因此最好在復合表達式中使用條件運算符的時候加上括號

  • 位運算符作用于整數類型的運算對象,并把運算對象看成是二進制位的集合(bitset可以表示任意大小的二進制位集合,也可以使用位運算符)

    ~ 		位求反		~expr
    <<		左移			expr1 << epxr2
    >>		右移
    &		位與
    ^		位異或
    |		位或
    

    如果運算對象是“小整型”,則它的值會被自動提升成較大的整數類型,如果運算對象是帶符號的,有可能產生未定義的行為(如果有可能為負數),因此建議將位運算用于處理無符號類型

  • 移位運算符將經過移動的(可能進行了提升)左側運算對象的拷貝作為球直接過。其中右側的運算對象一定不能為負,而且值必須嚴格小于結果的位數,否則就會產生未定義的行為。二進制位或者向左移(<<)或者向右移(>>),移出邊界外的位就被舍棄掉了。<<在右側插入值為0的二進制位,>>對無符號數來講是 在左側添加0,但是對帶符號類型依賴環境

  • unsigned char在位運算中會被提升為unsigned intunsigned long在任何機器上都至少擁有32位。1UL << x制造一個第x為1,其他位都為0的數字,對這個數字取反可以得到第x位為0,其他位都為1的數字。然后通過|&進行操作

  • sizeof運算符返回一條表達式或一個類型名字所占的字節數。sizeof運算符滿足又結合律,得到的是一個size_t類型的常量表達式。運算符的運算對象有兩種形式:

    sizeof (type)
    sizeof expr	
    

    在第二種類型中,sizeof返回的是表達式結果類型的大小,與眾不同的一點是sizeof并不實際計算其運算對象的值。因此解引一個無效指針仍然是一種安全的行為,因為指針實際上沒有被真正使用。sizeof不需要真的解引用指針也能知道它所指向對象的類型。

    Sales_data data, *p;
    sizeof *p;		//返回Sales_data的空間大小
    sizeof data.revenue
    sizeof Sales_data::revenue	//同上	
    

    在C++11新標準勻速我們使用作用域運算符來獲取類成員的大小。通常情況下只有通過類的對象才能訪問到類的成員,但是sizeof運算符無需我們提供一個具體的對象

  • 對數組執行sizeof運算得到整個數組所占空間的大小,等價于對數組中所有元素執行一次sizeof運算并將所得的結果求和。注意,sizeof運算符不會把數組轉換成指針來處理。因此可以用數組的大小除以單個元素的大小得到數組中元素的個數

    constexpr size_t sz = sizeof(ia)/sizeof(*ia);
    int arr2[sz];		//arr2的大小和ia一樣	
    
  • string對象或vector對象執行sizeof運算只返回該類型固定部分的大小,不會計算對象中的元素占用了多少空間。實測了一下發現對vectorstring應該也一樣)使用sizeof返回固定的大小,對于vector<int>返回24

  • 逗號運算符有兩個運算對象,按照從左往右的順序依次求值(同&& || ?:一樣規定了運算順序)。先對左側的運算對象求值,然后將求值結果拋棄,真正的運算結果是右側表達式的值,如果右側運算對象是左值,則最終的求值結果也是左值。

  • 隱式類型轉換:

    • 大多數表達式中,比int類型小的整型值首先提升為較大的整數類型
    • 在條件中,非布爾值轉換為布爾值
    • 初始化和賦值語句中,右側運算對象轉換成左側運算對象的類型
    • 如果算術運算或關系運算的運算對象有多種類型,需要轉換為同一種類型
  • 整型提升負責把小整數類型轉換成較大的整數類型,整數類型中比int小的類型如果參與運算,如果可以放到int里就會提升成int,否則提升成unsigned int

  • 隱式類型轉換:

    • 數組轉換成指針:在大多數用到數組的表達式中,數組自動轉換成指向數組首元素的指針。當數組被用作decltype關鍵字的參數,或者作為取地址符&sizeof以及typeid等運算符的運算對象時,以及用一個引用來初始化數組時,上述轉換不會發生
    • 常量整數值0或者字面值nullptr能夠轉換為任意指針類型,指向任意非常量的指針能夠轉化成void*,指向任意對象的指針能夠轉化為const void*
  • 如果有的轉換不會隱式自動轉換就需要強制類型轉換,雖然有時候不得不使用強制類型轉換,但是這種方法本質上是非常危險的。

  • 一個命名的強制類型轉換具有如下形式:

    cast-name<type>(expression);
    

    type是要轉換的類型,expression是要轉換的值,如果type是引用類型,則結果是左值。

    cast-namestatic_castdynamic_castconst_castreinterpret_cast中的一種

    • static_cast:任何具有明確定義的類型轉換,只要不包含底層const,就可以使用static_cast

      • 把較大的算數類型賦給較小的類型

        double slope = static_cast<double>(j)
        
      • 無法自動執行的類型轉換

        void *p = &d;
        double *dp = static_cast<double*>(p);
        
    • const_cast只能改變運算對象的底層const,用于將常量對象轉換為非常量對象

      const char *pc;
      char *p = const_cast<char*>(pc);
      

      只有const_cast可以改變表達式的常量屬性

      const_cast常用于有函數重載的上下文中

    • reinterpret_cast:通常為運算對象的位模式提供較低層次上的重新解釋,不太好用,不建議使用

  • 應該盡量避免使用強制類型轉換,且不推薦使用舊式類型轉換:與命名的強制類型轉換相比,舊式的強制類型轉換從表現形式上來說不那么清晰明了,容易被看漏。

    int x;
    char y = (char)x;
    

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/383568.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383568.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383568.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++Primer學習筆記:第5章 語句

一個表達式末尾加上分號就變成了表達式語句。最簡單的語句是空語句&#xff08;一個單獨的分號&#xff09;&#xff1a;語法上需要一條語句但是邏輯上不需要 復合語句是指用花括號括起來的&#xff08;可能為空&#xff09;語句和聲明的序列&#xff1a;用在語法上需要一條語…

z3 C++學習筆記

因為項目需要使用z3庫來解決問題&#xff0c;所以自己學習了一下&#xff0c;結果發現網上教程比較少&#xff0c;而且大部分都是使用Python&#xff0c;而我本人是C的忠實信徒&#xff0c;在知道C也可以使用z3庫以后我毫不猶豫地著手用C使用z3&#xff0c;但是我很快發現&…

C++Primer學習筆記:第6章 函數

通過調用運算符()調用函數 函數的調用完成兩項工作&#xff1a; 用實參初始化函數對應的形參將控制權轉移給被調用函數&#xff1a;主調函數的執行被暫時中斷&#xff0c;被調函數開始執行 盡管實參與形參存在對應關系&#xff0c;但是并沒有規定實參的求值順序。編譯器能以任…

C++Primer學習筆記:第8章 IO庫

C語言不直接處理輸入輸出&#xff0c;而是通過一族定義在標準庫中的類型來處理IO iostream定義了用于讀寫流的基本類型fstream定義了讀寫命名文件的類型sstream定義了讀寫內存string對象的類型 標準庫使得我們能夠忽略這些不同類型的流之間的差異&#xff0c;是通過繼承機制實…

C++Primer學習筆記:第7章 類

類的基本思想是數據抽象data abstraction和封裝encapsulation。數據抽象是一種依賴于接口interface和實現implementation分離的編程技術 在類中&#xff0c;由類的設計者負責考慮類的實現過程&#xff0c;使用該類的程序員只需要抽象地思考類型做了什么&#xff0c;而無須了解…

每日一題:leetcode191.位1的個數

題目描述 題目分析 很自然地想到了二進制枚舉&#xff0c;直接循環檢查每一個二進制位。 class Solution { public:int hammingWeight(uint32_t n) {int ret 0;uint32_t t 1;for (int i 0; i < 32; i, t << 1) {if (n & t) {ret;}}return ret;} };AC之后看了…

每日一題:leetcode341.扁平化嵌套列表迭代器

題目描述 題目分析 這個題目自己大概花了一個小時&#xff0c;雖然是一遍AC&#xff0c;但是速度有點慢&#xff0c;太長時間不寫代碼導致自己對代碼不太敏感&#xff0c;寫起來慢騰騰的。 看到這個的想法就是&#xff0c;要用棧來保存列表的迭代器&#xff0c;這樣將孩子列表…

每日一題:leetcode82. 刪除排序鏈表中的重復元素 II

題目描述 題目分析 這才是正常的中等題難度嘛&#xff0c;昨天的中等題題解我半天看不懂。。。 首先&#xff0c;需要增加一個啞節點&#xff08;操作鏈表的常規操作&#xff09;&#xff0c;因為有可能刪除首節點&#xff0c;我們不想要為首節點添加單獨的邏輯。其次&#xf…

每日一題:leetcode456.132模式

題目描述 題目分析 我覺得這道題應該是我做過最難的中等題之一了&#xff0c;這是昨天的每日一題&#xff0c;但是昨天用nlogn的做法做出來以后在看題解&#xff0c;發現有些看不懂&#xff08;覺得題解有點故弄玄虛&#xff09;。然后今天中午又花了一點時間才搞懂&#xff0…

leetcode283.移動零

題目描述 題目分析 在寫簡單題放松&#xff0c;看到這道題第一個想法是用STL庫函數&#xff0c;雖然知道大概要用雙指針之類的&#xff0c;但是庫函數爽哇。 class Solution { public:void moveZeroes(vector<int>& nums) {stable_sort(nums.begin(), nums.end(), …

每日一題:leetcode61.旋轉鏈表

題目描述 題目分析 很容易發現&#xff0c;如果k是n的整數倍&#xff0c;相當于沒有移動。這樣直接對k%n使得k在一個可以接受的范圍。 因為是順序移動&#xff0c;各元素之間的相對位置保持不變&#xff0c;所以就想著將鏈表先變成一個環。然后再移動頭指針&#xff0c;最后再…

每日一題:leetcode173.二叉搜索樹迭代器

題目描述 題目分析 更加地覺得編程重要的不在于如何寫代碼&#xff0c;用什么具體的技巧&#xff0c;編碼本身只是一種將思維呈現的方式&#xff0c;但是如果思維是不清晰的&#xff0c;那么就算懂得再多的編碼的奇技淫巧也是沒有什么幫助的。相反&#xff0c;如果有一個清晰的…

Ubuntu20.04 Clion/Pycharm/IDEA 輸入中文+光標跟隨解決方案

ibus輸入法&#xff08;棄用&#xff09; 之前一直用的搜狗輸入法&#xff0c;但是搜狗輸入法無法在Jetbrains全家桶下使用&#xff0c;但是又需要輸入中文&#xff0c;沒有辦法我只能下載了谷歌輸入法&#xff0c;十分難用&#xff0c;但是也沒有其他辦法&#xff0c;經常到網…

leetcode11.盛最多水的容器

題目描述 題目分析 看到題目后第一個想法當然是O(n2)O(n^2)O(n2)的&#xff0c;但是數據范圍是3e4&#xff0c;應該會超時&#xff0c;而且這種數據范圍也不是讓暴力求解的 。 相當于求解∑i<jmax((j?i)?min(a[i],a[j]))\sum_{i<j}{max((j-i)*min(a[i],a[j]))}∑i<…

每日一題:leetcode190.顛倒二進制位

題目描述 題目分析 題目本身很簡單&#xff0c;沒覺得有什么技巧可以再進行優化了&#xff0c;覺得位運算是無法打亂相對順序的&#xff0c;而這里需要進行鏡像顛倒的操作。因此就踏實地寫了一個循環。 在使用位運算得到每一位的時候&#xff0c;我吸取了經驗&#xff0c;用一…

結構屈曲分析

結構屈曲分析主要用于判定結構受載后是否有失穩風險&#xff0c;作為工程應用&#xff0c;一般分為線性屈曲分析和非線性屈曲分析。 線性屈曲分析需要具備較多的前提條件&#xff0c;如載荷無偏心、材料無缺陷等&#xff0c;在實際工程應用中結構制作過程和加載方式很難達到線性…

每日一題:leetcode74.搜索二維矩陣

題目描述 題目分析 感覺這是一個放錯標簽的簡單題。題目非常簡單&#xff0c;思路應該很明確是二分&#xff0c;我很快寫了一個&#xff08;雖然不小心把!打成調試了一會&#xff09;。 class Solution { public:bool searchMatrix(vector<vector<int>>& mat…

每日一題:leetcode90.子集貳

題目描述 題目分析 感覺這道題讓自己對枚舉排列有了一個更好的認識&#xff0c;感覺自己的這種思路不錯。 假設沒有重復元素&#xff08;退化成78.子集&#xff09;&#xff0c;我們應該怎么做&#xff1f;初始的時候冪集中只有一個空集&#xff0c;然后對每個元素&#xff0…

每日一題:leetcode1006.笨階乘

題目描述 題目分析 因為順序一定且沒有括號&#xff0c;所以邏輯很簡單。我們要順序處理的矛盾在于&#xff0c;減號后面會再出現乘法和除法&#xff0c;我們不妨將對乘法和除法用一個臨時值進行計算&#xff0c;計算結束后再合并到值里面&#xff0c;一般來講乘法和除法的處理…

每日一題:leetcode80.刪除有序數組中的重復元素貳

題目描述 題目分析 又是一道貼錯標簽的簡單題&#xff0c;很明顯的雙指針&#xff0c;我的做法是用兩個變量保存是否需要記錄&#xff0c;官方題解的做法是直接判斷&#xff0c;人家的高明一些 class Solution { public:int removeDuplicates(vector<int>& nums) {…