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

  • 通過調用運算符()調用函數

  • 函數的調用完成兩項工作:

    • 用實參初始化函數對應的形參
    • 將控制權轉移給被調用函數:主調函數的執行被暫時中斷,被調函數開始執行
  • 盡管實參與形參存在對應關系,但是并沒有規定實參的求值順序。編譯器能以任意可行的順序對實參求值

  • 任意兩個形參都不能同名,形式參數名是可選的,但是由于我們無法使用未命名的形式參數,所以形式參數一般都應該有一個名字

  • 函數的返回類型不能是數組類型或者函數類型,但可以是指向數組或者函數的指針

  • 函數體必須是大括號包圍的!

  • 普通局部變量對應的對象是自動對象:當函數的控制路徑經過變量定義語句時創建該對象,當到達定義所在的塊末尾時銷毀它。

  • 局部靜態對象:在程序的執行路徑第一次經過對象定義時對它初始化,并且直到程序終止時才被銷毀,在此期間即使對象所在的函數執行結束也不會對他有影響。如果局部靜態變量沒有顯式的初始值,則執行值初始化(內置類型會初始化為0)

  • 函數只能定義一次,但是可以聲名多次,唯一的區別是函數的聲明不需要函數體,用一個分號替代即可(因此經常省略形式參數的名字,但是寫上名字也有利于理解函數的功能),函數聲明也稱作函數原型

  • 形式參數初始化的機理和變量初始化一樣

  • 拷貝大的類類型對象或者容器對象比較低效,甚至有的類型(包括IO類型在內)根本不支持拷貝操作。因此函數只能通過引用形式參數訪問該類型的對象,如果函數無須改變引用形式參數的值,最好將其聲明為常量引用

  • 熟悉C的程序員常常使用指針類型的形參訪問函數外部的對象,在C++中最好還是使用引用類型的形式參數代替指針

  • 對于有可能是臨時參數的形式參數,我們不應該使用引用(因為無法引用到常量上)

  • 函數重載要求同名函數的形式參數列表應該有明顯的區別,因此如果僅僅是const的不同則不能進行重載(應該要求形式參數類型不同)

  • 形式參數的初始化方式和變量的初始化方式是一樣的:我們可以使用非常量初始化一個底層const對象,但是反過來不行,同時一個普通的引用必須使用同類型的對象初始化(詳細同第二章指針和引用部分)

  • 盡量使用常量引用

    • 給函數調用者傳遞正確的信息
    • 使用非常量引用會極大地限制函數所能接受的實際參數類型:我們不能把const對象、字面值對象或者需要類型轉換的對象傳遞給普通的引用參數
  • 數組形式參數

    • 數組的兩個特點:

      • 不允許拷貝數組
      • 使用數組時通常會將其轉換成指針
    • 以下三種聲明方式是等價的:

      void print(const int*);
      void print(const int[]);
      void print(const int[10]);
      
    • 因為我們不清楚數組的實際大小,因此在使用過程中必須通過一定的方式判斷是否越界

      • 使用標記指定數組長度:例如C風格的字符串,最后一個一定是一個\0,我們可以判斷是否為\0來判斷是否到達末尾

      • 使用標準庫規范:

        void print(const int *beg, const int *end) {while(beg != end) {cout << *beg++ << endl;}
        }
        int arr[] = {0, 1, 2};
        print(begin(arr), end(arr));	//#include<iterator>
        
      • 顯式傳遞一個表示數組大小的形式參數

      • 同常量引用,當函數不需要對數組元素執行讀寫操作的時候,數組形式參數應該是指向const的指針

    • 數組引用參數

      void print(int (&arr)[10]) {for (auto item : arr) {cout << item << endl;}
      }
      

      對于數組的引用詳細可以看第三章關于數組部分的筆記

    • 傳遞多維數組:C++語言中實際上沒有真正的多維數組,所謂的多維數組其實是數組的數組。數組第二維(以及后面所有的維度)的大小都是數組類型的一部分,不能省略

      void print(int (*matrix)[10], int rowSize);
      void print(int matrix[][10], int rowSize);
      

      上面兩種聲明是完全等價的

  • 命令行選項可以通過兩個(可選的)形式參數傳遞給main函數:

    int main(int argc, char *argv[]);
    int main(int argc, char **argv);
    
    • 第二個形式參數是一個數組,它的元素指向C風格字符串的指針,第一個形式參數argc表示數組中字符串的數量
    • argv第一個元素指向程序的名字或者一個空字符串,接下來的元素依次傳遞命令行提供的參數,最后一個指針的元素值保證為0
  • 為了編寫能處理不同數量實際參數的函數,C++11標準提供了幾種方法:

    • 如果函數的實際參數數量未知但是類型相同,我們可以使用initializer_list類型的形式參數(需要#include<initializer_list

      initializer_list<T> lst;	//默認初始化,T類型元素的空列表
      initializer_list<T> lst{a,b,c...};	//lst的元素是對應初始值的副本,列表中的元素是const
      lst2(lst);	 //等價與lst2 = lst ,賦值,不會拷貝列表中的元素,原始列表和副本共享元素
      lst.size()		//列表中的元素數量
      lst.begin()
      lst.end()
      

      initialzer_list對象中的元素永遠是常量值

      void err_msg(initializer_list<string> il) 
      {for (auto beg = il.begin(); beg != il.end(); ++beg)cout << *beg << " ";	//也可以通過范圍for循環訪問65cout << endl;
      }
      err_msg({"A", "B", "C"});
      err_msg({"A", "B"});
      
    • 使用可變參數模板

  • 返回void的函數不要求非得有return語句,因為在這類函數的最后一句會隱式地執行return,一個返回類型是void的函數也能使用return expression,不過此時return語句的expression必須是另一個返回void的函數,強行令void函數返回其他類型將產生編譯錯誤

  • 有返回值函數

    bool str_subrange(const string &str1, const string &str2) 
    {auto size = (str1.size() < str2.size()) ? str1.size() : str2.size();for (decltype(size) i = 0; i < size; ++i) {if (str1[i] != str2[i])return false;}return true;
    }
    
  • 返回一個值的方式和初始化一個變量或形式參數的方式完全一樣:返回的值用于初始化調用點的一個臨時量,這個臨時量就是函數調用的結果

  • 不要返回局部對象的引用或者指針:函數完成后,它所占用的存儲空間也隨之被釋放掉。因此,函數終止意味著局部變量的引用(或指針)將指向不再有效的內存區域

  • 調用一個返回引用的函數得到左值,其他返回類型得到右值

  • C++11新標準規定,函數可以返回花括號包圍的值的列表。類似于其他返回結果,此處的列表也用來對表示函數返回的臨時量進行初始化

  • 我們允許main函數沒有return語句直接結束,如果控制到達了main函數的結尾處而且沒有return語句,編譯器將隱式地插入一條返回0的return語句。在cstdlib頭文件中定義了兩個預處理變量,我們用這兩個變量分別表示成功與失敗

    int main()
    {if (some_failure) {return EXIT_FAILURE;	} else {return EXIT_SUCCESS;}
    }
    
  • 從語法上來說,想要定義一個返回數組的指針或引用的函數比較繁瑣,但是使用類型別名可以簡化這一任務

    typedef int arrT[10];	//using arrT = int[10];
    arrT* func(int i);		//返回一個指向含有10個整數的數組的指針
    int (*func(int i))[10];	//等價于上面的聲明
    

    我們還可以使用尾置返回類型使得上面的聲明變得清晰:

    auto func(int i) -> int(*)[10];
    

    如果我們知道函數返回的指針指向哪個(類別)的數組,我們還可以使用decltype關鍵字聲明返回類型

    int arr[] = {0, 1, 2, 3, 4};
    decltype(arr) *arrPtr(int i) 
    {return &arr;    
    }
    
  • 如果同一作用域內的幾個函數名字相同但是形式參數列表(形式參數數量或形式參數類型)不同,我們稱之為重載函數。main函數不能重載。需要注意的是,函數的重載和返回類型關系不大

  • 頂層const不影響傳入函數的對象,一個擁有頂層const的形式參數無法和另一個沒有頂層const的形式參數區分開來。但是底層const是會影響函數的重載的,當傳入的對象是常量時,會選擇帶有底層cosnt的函數版本,如果傳遞一個非常量對象,編譯器會優先選用非常量版本的函數

  • 最好只重載那些確實非常相似的操作

  • 我們也可以使用const_cast實現const到非const的轉換:

    const string &func(const string &s1, const string &s2)
    {return s1.size() < s2.size() ? s1 : s2;
    }
    string &func(string &s1, string &s2)
    {return const_cast<string&>(func(const_cast<const string&>(s1), const_cast<const string&)(s2));
    }
    
  • 當調用重載函數時的結果:

    • 編譯器找到一個與實際參數最佳匹配的函數
    • 找不到任何一個函數匹配,發出無匹配的錯誤
    • 有多于一個函數可以匹配,但是沒一個都不是明顯的最佳選擇,此時也將發生錯誤,稱為二義性調用
  • 如果我們在內層作用域中聲明名字,它將隱藏外層作用域中聲明的同名實體。在不同的作用域中無法重載函數名

  • 一旦某個形式參數被賦予了默認值,它后面所有形式參數都必須有默認值。當設計含有默認實際參數的函數時,其中一項任務是合理設置形式參數順序,盡量讓不怎么使用默認值的形式參數出現在前面

  • 通常,應該在函數聲明中指定默認實際參數,并將聲明放在合適的頭文件中。局部變量不能作為默認實際參數

  • 用作默認實際參數的名字在函數聲明所在的作用域內解析,而這些名字的求值過程發生在函數調用時。比如函數A某個默認實際參數的值是一個函數B調用的返回值,則該函數調用B會在A被調用的時候調用

    #include <iostream>using namespace std;string A = "global A";
    string B = "global B";const string &func()
    {return const_cast<const string&>(B);
    }int main() 
    {ios::sync_with_stdio(false);void test(const string &a = A, const string &b = func());string A = "local A";   //local varibale cannot be default value::A += " has been changed";B = "local B";test();return 0;
    }void test(const string &a, const string &b)
    {cout << a << endl;cout << b << endl;
    }

    運行結果:

    global A has been changed
    local B
    
  • 將一些簡單但需要多次重復的函數定義為內聯函數的好處:

    • 有利于閱讀理解
    • 可以被重復利用,使得代碼簡潔
    • 需要修改時只用修改一個地方
  • 在函數前面加上inline便可以將函數生命為內聯函數。內聯說明只是向編譯器發出一個請求,編譯器可以選擇忽略這個請求。一般來說,內聯機制用于優化規模較小、流程直接、頻繁調用的函數。很多編譯器都不支持內聯遞歸函數

  • constexpr函數是指能夠用于常量表達式的函數

    • 函數的返回類型以及所有形式參數的類型都必須是字面值類型,而且函數體中必須有且只有一條return語句
    • constexpr函數被隱式地指定為內聯函數
    • 允許constexpr函數的返回值不是一個常量,如果參數非常量表達式導致最后返回值不是常量表達式則在需要常量的地方調用會報錯
  • 內聯(inline)函數和constexpr函數可以在程序中多次定義,但是多個定義必須一致。基于這個原因,內聯函數和constexpr函數通常定義在頭文件中

  • 程序可以包含一些用于調試的代碼,但是這些代碼只在開發程序時使用。當應用程序編寫完成準備發布時,要先屏蔽掉調試代碼

    • assert (expr)預處理宏:首先對expr求值,如果表達式為假(0),assert輸出信息并終止程序的執行,如果為真,則什么也不做
      • 需要頭文件cassert,因為是供預處理器處理,所以無需提供using聲明
      • assert宏常用于檢查“不能發生”的條件,即程序的運行是建立在assert的條件成立的情況下
    • assert的行為依賴于一個名為NDEBUG的預處理變量的狀態,如果定義了NDEBUG,則assert什么也不做。默認情況下沒有定義NDEBUG,此時assert將執行檢查。如果想要關閉assert檢查:
      • 在程序開頭加上#define NDEBUG
      • 或在編譯的時候加上-D NDEBUG參數
    assert(word.size() >=  threshold);
    //等價寫法:
    #ifndef NDEBUG
    if (word.size() < threshold)cerr << "Error: " << __FILE__<< " : in function " << __func__<< " at line " << __LINE__ << endl<< "	Compiled on " << __DATE__<< " at " << __TIME__ << endl<< "	Word read was \"" << word<< "\": Length too short" << endl;
    #endif
    
  • 函數匹配

    • 選定候選函數:
      • 與被調用函數同名
      • 其聲明在調用點可見
    • 選定可行函數:
      • 形式參數和實際參數數量一直
      • 類型符合(相同或可以進行轉換)
    • 尋找最佳匹配
      • 該函數的每個實際參數的匹配不劣于其他可行函數需要的匹配
      • 至少有一個實際參數的匹配優于其他可行函數提供的匹配
    • 如果最終確定了一個函數,則匹配成功,如果最后匹配出多個函數,則匹配失敗,報告二義性錯誤
  • 為了確定最佳匹配,編譯器將實際參數類型到形式參數類型的轉換分成了幾個等級:

    1. 精確匹配
      • 實際參數類型和形式參數類型相同
      • 實際參數從數組類型或函數類型轉換成相應的指針類型
      • 向實際參數添加或者刪除頂層const
    2. 通過const轉換實現的匹配
    3. 通過類型提升實現的匹配(小整數類型會自動變成int,如果放不下再變成unsigned int
    4. 通過算數類型轉換或指針轉換實現的匹配
    5. 通過類類型轉換實現的匹配
  • 想要聲明一個指向函數的指針,只需要用指針替換函數名即可:

    bool func(const string &, const string &);
    bool (*pf)(const string &, const string &);
    

    當我們把函數名作為一個值使用時,該函數自動地轉換成指針

    pf = func;
    //等價于
    pf = &func;	//&是可選的
    

    我們可以直接使用指向函數的指針調用該函數,無需提前解引用指針

    //等價的三種調用方法
    bool b1 = pf("A", "B");
    bool b2 = (*pf)("A", "B");
    bool b3 = func("A", "B");
    
  • 在指向不同函數類型的指針之間不存在轉換規則,但是我們可以為函數指針賦一個nullptr或者0

  • 當我們使用重載函數為指針賦值時,上下文必須清晰地界定到底應該選用哪個函數

  • 我們可以定義函數指針作為形式參數

    void work(bool pf(const string &, const string &));	//看起來是函數類型,實際上會自動轉換成指針
    //等價于
    void work(bool (*pf)(const string &, const string &));
    

    我們同樣可以使用typedefdecltype簡化操作

    typedef bool funcT(const string &, const string &);	//funcT是函數類型
    typedef decltype(func) funcT2;	//同上
    typedef bool (*funcTP)(const string &, const string &);	//funcTP是函數指針
    typedef decltype(func) *funcTP2;	//同上
    void work(funcT);	//同之前定義,函數類型會自動轉換成指針類型
    void work(funcTP);	//同之前定義
    
  • 編譯器不會自動地將函數返回類型當成對應的指針類型進行處理

    using F = int(int *, int);
    using FP = int(*)(int *, int);
    //以下四種方式是等價的
    FP f1(int);
    F *f1(int);
    int (*f1(int))(int *, int);
    auto f1(int) -> int (*)(int *, int);
    

    如果使用decltype指定返回函數指針類型記得decltype(func)如果func是一個函數則得到的是函數類型,還需要加上*

    decltype(func) *getFunc(const string &);
    

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

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

相關文章

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) {…

每日一題:leetcode81.搜索旋轉排序數組Ⅱ

題目描述 題目分析 不含重復元素的題解&#xff08;leetcode33&#xff09; 這道題也是我們算法課的一道編程題&#xff0c;寫完以后發現當時的思路和現在沒有什么變化&#xff0c;果然是自己啊。我的想法是先判斷區間整體是升序的還是旋轉的&#xff0c;如果是升序的就按照正…

C++ JSON庫:JSON for Morden C++

緒論 最近因為項目的需要&#xff0c;需要對JSON進行一定的數據處理&#xff0c;因為想要用C進行編碼&#xff0c;便對C的JSON庫進行的調研&#xff0c;發現這個庫比較好用&#xff1a;JSON for Morder C。 使用指南 想要使用這個json庫&#xff0c;只需要在源文件中包含jso…

Linux信號實現精確到微秒的sleep函數:通過sigsuspend函數解決時序競態問題

原理就是先使用定時器定時&#xff0c;然后再使用pause函數或者sigsuspend函數主動阻塞掛起&#xff0c;最終恢復現場。 如果使用pause函數的話&#xff0c;優點是使用簡單&#xff0c;缺點是有可能產生時序競態&#xff0c;導致進程一直阻塞下去&#xff1a;在定時和掛起之間…