C/C++編程筆記:C++入門知識丨函數和函數模板

来源:https://www.cnblogs.com/yxy6/archive/2020/07/24/13374383.html
-Advertisement-
Play Games

本篇要學習的內容和知識結構概覽 函數的參數及其傳遞方式 1. 函數參數傳遞方式 傳值: 傳變數值: 將實參記憶體中的內容拷貝一份給形參, 兩者是不同的兩塊記憶體 傳地址值: 將實參所對應的記憶體空間的地址值給形參, 形參是一個指針, 指向實參所對應的記憶體空間 傳引用: 形參是對實參的引用, 形參和實參是同 ...


本篇要學習的內容和知識結構概覽

函數的參數及其傳遞方式

1. 函數參數傳遞方式

傳值:

    傳變數值: 將實參記憶體中的內容拷貝一份給形參, 兩者是不同的兩塊記憶體

    傳地址值: 將實參所對應的記憶體空間的地址值給形參, 形參是一個指針, 指向實參所對應的記憶體空間

傳引用:

    形參是對實參的引用, 形參和實參是同一塊記憶體空間

2. 對象作為函數參數, 也就是傳變數值

將實參對象的值傳遞給形參對象, 形參是實參的備份, 當在函數中改變形參的值時, 改變的是這個備份中的值, 不影響原來的值

像這樣:

void fakeSwapAB(int x , int y) {
    int temp = x;
    x = y;
    y = temp;
}

int a = 5;
int b = 8;
cout << "交換前: " << a << ", " << b << endl;

// 傳變數值
fakeSwapAB(a, b);

cout << "交換後: " << a << ", " << b << endl;

 

3. 對象指針作為函數參數, 也就是傳地址值

形參是對象指針, 實參是對象的地址值, 雖然參數傳遞方式仍然是傳值方式, 因為形參和實參的地址值一樣, 所以它們都指向同一塊記憶體, 我們通過指針更改所指向的記憶體中的內容, 所以當在函數中通過形參改變記憶體中的值時, 改變的就是原來實參的值

像這樣:

void realSwapAB(int * p, int * q) {
    int temp = *p;
    *p = *q;
    *q = temp;
}

int a = 5;
int b = 8;
cout << "交換前: " << a << ", " << b << endl;

// 傳地址值
realSwapAB(&a, &b);

cout << "交換後: " << a << ", " << b << endl;

 

對於數組, 因數組名就是代表的數組首地址, 所以數組也能用傳數組地址值的方式

void swapArrFirstAndSecond(int a[]) {
    int temp = a[0];
    a[0] = a[1];
    a[1] = temp;
}

int main(int argc, const char * argv[]) {
    
    int a[] = {2, 3};
    cout << "交換前: " << a[0] << ", " << a[1] << endl;
    swapArrFirstAndSecond(a);
    cout << "交換後: " << a[0] << ", " << a[1] << endl;
    return 0;
}

 

4. 引用作為函數參數, 也就是傳地址(註意: 這裡不是地址值)

在函數調用時, 實參對象名傳給形參對象名, 形參對象名就成為實參對象名的別名. 實參對象和形參對象代表同一個對象, 所以改變形參對象的值就是改變實參對象的值

像這樣:

void citeSwapAB(int & x, int & y) {
    int temp = x;
    x = y;
    y = temp;
}

int a = 5;
int b = 8;
cout << "交換前: " << a << ", " << b << endl;

// 傳引用
citeSwapAB(a, b);

cout << "交換後: " << a << ", " << b << endl;

 

優點: 引用對象不是一個獨立的對象,不單獨占記憶體單元, 而對象指針要另外開闢記憶體單元(記憶體中放實參傳過來的地址),所以傳引用比傳指針更好用。

5. 預設參數

不要求程式在調用時必須設定該參數, 而由編譯器在需要時給該參數賦預設值. 

規則1:當程式需要傳遞特定值時需要顯式的指明. 預設參數必須在函數原型中說明.

如果函數在main函數後面定義, 而在聲明中設置預設參數, 在定義中不需要設置預設參數

像這樣:

// 在main函數前聲明函數, 並設置預設參數
void PrintValue(int a, int b = 0, int c = 0);

int main(int argc, const char * argv[]) {
    
    // 調用函數
    PrintValue(5);
    
    return 0;
}

// 在main函數後定義函數, 不需要設置預設參數
void PrintValue(int a, int b, int c) {
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << "c = " << c << endl;
}

 

如果函數在main函數前面定義, 則在定義中設置預設參數

像這樣:

// 在main前定義函數, 需要設置預設參數
void PrintValue(int a, int b = 0, int c = 0) {
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << "c = " << c << endl;
}

int main(int argc, const char * argv[]) {
    
    // 調用函數
    PrintValue(5);
    
    return 0;
} 

 

規則2:預設參數可以多於一個,但必須放在參數序列的後部。

像這樣:

可以有一個預設參數:void PrintValue(int a, int b, int c = 0);

可以是有多個預設參數:void PrintValue(int a, int b = 0, int c = 0);

不可以在中間設置預設參數:void PrintValue(int a, int b = 0, int c);

規則3:如果一個預設參數需要指定一個特定值時,則在此之前的所有參數都必須賦值

// 調用函數 第一種: 三個參數全部有特定值
PrintValue(5, 8, 9);

// 調用函數 第二種: 我們給第二個參數設特定值, 它前面所有參數必須賦值, 所以可以
PrintValue(5, 8);

/*
調用函數 第三種: 當一個預設參數有特定值時, 它前面所有的參數都必須賦值,
我們給第三個預設參數設特定值 也就是說第一, 二個參數也必須賦值 所以不可以
 */
//    PrintValue(5, , 9);

 

6. 使用const保護數據

用const修飾要傳遞的參數, 該函數只能使用參數, 而無權修改參數, 以提高系統的自身安全.

像這樣:

// 拼接字元串的函數
void catStr(const string str) {
    string str2 = str + " Ray!";
    
    // 函數內部不能修改const修飾的形參, 所以不能這麼使用
//    str = "Hi";
    cout << str2 << endl;
}

int main(int argc, const char * argv[]) {
    
    // 實例化一個字元串
    string str = "Hello";
    
    // 調用函數
    catStr(str);
    
    return 0;
}

 

函數返回值

C++函數返回值類型可以是除數組和函數以外的任何類型

當返回值是指針或引用對象時, 需要註意函數返回值所指的對象必須存在, 因此不能將函數內部的局部對象作為函數返回值, 因為函數內, 局部變數或者對象在函數運行完畢後記憶體就釋放啦

1. 返回引用的函數

函數可以返回一個引用, 目的是為了讓該函數位於賦值運算符的左邊

格式: 數據類型 & 函數名(參數列表);

像這樣:

// 全局數組
int arr[] = {2, 4, 6, 8};

// 獲得數組下標元素
int & getValueAtIndex(int i) {
    return arr[i];
}

int main(int argc, const char * argv[]) {
    
    cout << "更改前: " << arr[2] << endl;
    
    // 調用函數, 並且用於計算或者重新賦值
    getValueAtIndex(2) = 10;
    cout << "更改後: " << arr[2] << endl;
    
    return 0;
}

 

2. 返回指針的函數

返回值是存儲某種數據類型數據的記憶體地址, 這種函數稱為指針函數

格式: 數據類型 * 函數名(參數列表);

像這樣:

// 返回指針的函數
int * getData(int n) {
    
    // 根據形參, 申請記憶體空間
    int * p = new int[n];
    
    // 給申請下來的記憶體空間賦值
    for (int i = 0; i < n; i++) {
        p[i] = i + 10;
    }
    
    // 返回這段記憶體空間的首地址
    return p;
}

int main(int argc, const char * argv[]) {
    
    // 調用函數, 並接收返回值, 不要忘記釋放函數中分配的記憶體
    int * p = getData(5);
    
    // 列印指針所指向的記憶體中的內容
    for (int i = 0; i < 5; i++) {
        cout << p[i] << endl;
    }
    
    return 0;
}

 

3. 返回對象的函數

格式: 數據類型 函數名(參數列表);

像這樣:

// 返回對象的函數
string sayHello(string s) {
    // 我們拼接好一個字元串, 給str
    string str = "Hello " + s;
    
    // 並把str這個對象返回
    return str;
}

int main(int argc, const char * argv[]) {
    
    // 調用函數, 接收函數返回的對象
    string str = sayHello("Ray");
    cout << str << endl;
    
    return 0;
}

 

4. 函數返回值作為函數參數

如果函數返回值作為另一個函數的參數, 那麼這個返回值必須與另一個函數的參數類型一致

像這樣:

// 求最大值的函數
int getMax(int x, int y) {
    return x > y ? x : y;
}

int main(int argc, const char * argv[]) {
    
    // 先求8, 9返回最大值; 返回值再跟5比較, 返回最大值
    int maxValue = getMax(5, getMax(8, 9));
    cout << maxValue << endl;
    
    return 0;
}

 

內聯函數

1. 內聯函數的概念

使用關鍵字inline聲明的函數稱為內聯函數, 內聯函數必須在程式中第一次調用此函數的語句出現之前定義, 這樣編譯器才知道內聯函數的函數休, 然後進行替換

像這樣:

// 判斷輸入的字元是否為數字
inline bool isNumber(char c) {
    if (c >= '0' && c <= '9') {
        return true;
    } else {
        return false;
    }
}

int main(int argc, const char * argv[]) {
    
    // 聲明字元c
    char c;
    
    // 從鍵盤輸入字元
    cin >> c;
    
    // 進行判斷, 這裡的isNumber(c), 在程式編程期間就會被isNumber()函數體所替換, 跟巨集一樣一樣的
    // 如果函數體特別大, 替換的地方特別多, 就增加了代碼量
    if (isNumber(c)) {
        cout << "輸入了一個數字" << endl;
    } else {
        cout << "輸入的不是一個數字" << endl;
    }
    
    return 0;
}

 

2. 註意

在C++中, 除具有迴圈語句, switch語句的函數不能說明為內聯函數外, 其它函數都可以說明為內聯函數.

3. 作用

使用內聯函數可以提高程式執行速度, 但如果函數體語句多, 則會增加程式代碼量.

函數重載和預設參數

1. 函數重載

一個函數名具有多種功能, 具有多種形態, 稱這種我為多態性, 一個名字, 多個函數

函數重載要滿足的條件:

參數類型不同或者參數個數不同

像這樣:

// 求和的函數 2兩個整型參數
int sumWithValue(int x, int y) {
    return x + y;
}

// 求和的函數 3兩個整型參數
int sumWithValue(int x, int y, int z) {
    return x + y + z;
}

// 求和的函數 2個浮點型參數
double sumWithValue(double x, double y) {
    return x + y;
}

// 求和的函數 3個浮點型參數
double sumWithValue(double x, double y, double z) {
    return x + y + z;
}

int main(int argc, const char * argv[]) {
    
    // 兩個整型變數求和
    int sumValue1 = sumWithValue(8, 9);
    
    // 三個整型變數求和
    int sumValue2 = sumWithValue(8, 9, 10);
    
    // 兩個浮點型變數求和
    double sumValue3 = sumWithValue(1.2, 2.3);
    
    // 三個浮點型變數求和
    double sumValue4 = sumWithValue(1.2, 2.3, 3.4);
    
    cout << sumValue1 << endl;
    cout << sumValue2 << endl;
    cout << sumValue3 << endl;
    cout << sumValue4 << endl;
    
    return 0;
}

 

2. 函數重載與預設參數

當函數重載與預設參數相結合時, 能夠有效減少函數個數及形態, 縮減代碼規模.

這樣我們每種數據類型只保留一個函數即可完成我們的功能, 直接少了兩個函數.

像這樣:

// 整型參數求和
int sumWithValue(int x = 0, int y = 0, int z = 0) {
    return x + y + z;
}

// 浮點型參數求和
double sumWithValue(double x = 0, double y = 0, double z = 0) {
    return x + y + z;
}

int main(int argc, const char * argv[]) {
    
    // 兩個整型變數求和
    int sumValue1 = sumWithValue(8, 9);
    
    // 三個整型變數求和
    int sumValue2 = sumWithValue(8, 9, 10);
    
    // 兩個浮點型變數求和
    double sumValue3 = sumWithValue(1.2, 2.3);
    
    // 三個浮點型變數求和
    double sumValue4 = sumWithValue(1.2, 2.3, 3.4);
    
    cout << sumValue1 << endl;
    cout << sumValue2 << endl;
    cout << sumValue3 << endl;
    cout << sumValue4 << endl;
    
    return 0;
}

 

如果使用預設參數, 就不能對參數個數少於預設個數的函數形態進行重載, 只能對於多於預設參數個數的函數形態進行重載.

像這樣:

// 求和的參數, 並且使用預設參數, 最多三個整型參數求和
int sumWithValue(int x = 0, int y = 0, int z = 0) {
    return x + y + z;
}

// 像這樣是不行的, 不能對參數個數少於預設個數的函數形態進行重載
//int sumWithValue(int x, int y) {
//    return x + y;
//}

// 像這樣是可以的, 當調用時傳入4個整型參數時就會調用該參數
int sumWithValue(int x, int y, int z, int t) {
    return x + y + z + t;
}

int main(int argc, const char * argv[]) {
    
    // 求和, 只給兩個特定值
    int sumValue1 = sumWithValue(8, 9);
    
    // 求和, 給三個特定值
    int sumValue2 = sumWithValue(8, 9, 10);
    
    // 求和, 有4個整型參數
    int sumValue3 = sumWithValue(8, 9, 10, 11);
    
    cout << sumValue1 << endl;
    cout << sumValue2 << endl;
    cout << sumValue3 << endl;
    
    return 0;
}

 

函數模板

從而上面可以看出, 它們是邏輯功能完全一樣的函數, 所提供的函數體也一樣, 區別僅僅是數據類型不同, 為了統一的處理它們, 引入了函數模板. 

現在我們的函數從4個縮減成一個, 但是我們的功能沒有減少, 反而增加了. 比如我們可以計算char, float類型

1. 什麼是函數模板

在程式設計時沒有使用實際存在的類型, 而是使用虛擬的參數參數, 故其靈活性得到加強.

當用實際的類型來實例化這種函數時, 就好像按照模板來製造新的函數一樣, 所以稱為函數模板

格式: 一般用T來標識類型參數, 也可以用其它的

Template <class T> 

像這樣:

// 定義模板
template <class T>

// 定義函數模板
T sumWithValue(T x, T y) {
    return  x + y;
}

int main(int argc, const char * argv[]) {
    
    // 調用模板函數
    int sumValue1 = sumWithValue(3, 5);
    
    // 調用模板函數
    double sumValue2 = sumWithValue(3.2, 5.1);
    cout << sumValue1 << endl;
    cout << sumValue2 << endl;
    return 0;
}

 

當用用函數模板與具體的數據類型連用時, 就產生了模板函數, 又稱為函數模板實例化

2. 函數模板的參數

函數模板名<模板參數>(參數列表);

我們可以將參數列表的數據強制轉換為指定的數據類型

像這樣:int sumValue2 = sumWithValue<int>(3.2, 5.1);

我們將參數列表裡的數據強制轉換為int類型, 再參與計算

也可以樣:double sumValue2 = sumWithValue(3.2, (double)5);

我們也可以將參數列表裡的單個參數進行強制類型轉換, 再參與計算

不過我們一般不會加上模板參數.

3. 使用關鍵字typename

用途就是代替template參數列表中的關鍵字class

像這樣

template <typename T>

只是將class替換為typename, 其它一樣使用.

強烈建議大家使用typename, 因為它就是為模板服務的, 而class是在typename出現之前使用的, 它還有定義類的作用, 不直觀, 也會在一些其它地方編譯時報錯.

總結:

可能對於初學者來說, 函數有點不是很好理解, 包括我當初也是, 不要想得過於複雜, 其實它就是一段有特定功能的代碼, 只不過我們給這段代碼起了個名字而已, 這樣就會提高代碼的可讀性和易維護性。

自學C/C++編程難度很大,不妨和一些志同道合的小伙伴一起學習成長!

C語言C++編程學習交流圈子,【點擊進入微信公眾號:C語言編程學習基地

有一些源碼和資料分享,歡迎轉行也學習編程的伙伴,和大家一起交流成長會比自己琢磨更快哦!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、常量和C預處理器 1.符號常量(symbolic constant) 編譯程式的時候程式中的符號常量都會被實際字面量所替換,這一過程稱為編譯時替換 格式:末尾不加分號,中間不加等號,字面量可以是數字,字元,字元串等 #define CHANGLIANGNAME 890 2.const限定符 C9 ...
  • 首先,回顧一下基礎的巨集操作: C語言巨集 #與## #的作用是字元串化:在一個巨集中的參數前面使用一個#,預處理器會把這個參數轉換為一個字元數組 #define ERROR_LOG(info) fprintf(stderr,"error:"#info"\n"); 則有: ERROR_LOG("add") ...
  • 全棧的自我修養: 0005 Java 包掃描實現和應用(Jar篇) It's not the altitude, it's the attitude. 決定一切的不是高度而是態度。 Table of Contents 依賴的 Jar 思路 完整代碼 整合後代碼 如果你曾經使用過 Spring, 那你 ...
  • 目錄: 一、什麼是介面? 二、介面測試流程 三、介面測試工具 四、介面測試技術點 五、總結 導讀: 為什麼要做介面測試 介面測試本質上是功能測試的一種,屬於後端伺服器測試。但是它的影響範圍要遠廣於web,app層面。原因很簡單,因為目前很多公司,服務架構都是多端共用一套介面。和用戶直接交互的UI界面 ...
  • Python 是一門常用的編程語言,它不僅上手容易,而且還擁有豐富的支持庫。對經常需要針對自己所 處的特定場景編寫專用工具的黑客、電腦犯罪調查人員、滲透測試師和安全工程師來說,Python 的這些 特點可以幫助他們又快又好地完成這一任務,以極少的代碼量實現所需的功能。Python絕技:運用Pyth ...
  • 點擊此處進入網盤下載地址 提取碼:o39n 全書共有20章,書中的簡介如下: 本書旨在讓你儘快學會 Python ,以便能夠編寫能正確運行的程式 —— 游戲、數據可視化和 Web 應用程式,同時掌握讓你終身受益的基本編程知識。本書適合任何年齡的讀者閱讀,它不要求你有任何 Python 編程經驗,甚至 ...
  • 點擊此處進入網盤下載地址 提取碼:btqx 作者介紹: 馬修·羅塞爾(MatthewA.Russell),DigitalReasoningSystems公司的技術副總裁和Zaffra公司的負責人,是熱愛數據挖掘、開源和Web應用技術的電腦科學家。他也是《Dojo:TheDofinitiveGuid ...
  • Python爬蟲開發與項目實戰從基本的爬蟲原理開始講解,通過介紹Pthyon編程語言與HTML基礎知識引領讀者入門,之後根據當前風起雲涌的雲計算、大數據熱潮,重點講述了雲計算的相關內容及其在爬蟲中的應用,進而介紹如何設計自己的爬蟲應用。主要內容分為基礎篇、中級篇、深入篇,基礎篇包括Python編程基 ...
一周排行
    -Advertisement-
    Play Games
  • C#TMS系統代碼-基礎頁面BaseCity學習 本人純新手,剛進公司跟領導報道,我說我是java全棧,他問我會不會C#,我說大學學過,他說這個TMS系統就給你來管了。外包已經把代碼給我了,這幾天先把增刪改查的代碼背一下,說不定後面就要趕鴨子上架了 Service頁面 //using => impo ...
  • 委托與事件 委托 委托的定義 委托是C#中的一種類型,用於存儲對方法的引用。它允許將方法作為參數傳遞給其他方法,實現回調、事件處理和動態調用等功能。通俗來講,就是委托包含方法的記憶體地址,方法匹配與委托相同的簽名,因此通過使用正確的參數類型來調用方法。 委托的特性 引用方法:委托允許存儲對方法的引用, ...
  • 前言 這幾天閑來沒事看看ABP vNext的文檔和源碼,關於關於依賴註入(屬性註入)這塊兒產生了興趣。 我們都知道。Volo.ABP 依賴註入容器使用了第三方組件Autofac實現的。有三種註入方式,構造函數註入和方法註入和屬性註入。 ABP的屬性註入原則參考如下: 這時候我就開始疑惑了,因為我知道 ...
  • C#TMS系統代碼-業務頁面ShippingNotice學習 學一個業務頁面,ok,領導開完會就被裁掉了,很突然啊,他收拾東西的時候我還以為他要旅游提前請假了,還在尋思為什麼回家連自己買的幾箱飲料都要叫跑腿帶走,怕被偷嗎?還好我在他開會之前拿了兩瓶芬達 感覺感覺前面的BaseCity差不太多,這邊的 ...
  • 概述:在C#中,通過`Expression`類、`AndAlso`和`OrElse`方法可組合兩個`Expression<Func<T, bool>>`,實現多條件動態查詢。通過創建表達式樹,可輕鬆構建複雜的查詢條件。 在C#中,可以使用AndAlso和OrElse方法組合兩個Expression< ...
  • 閑來無聊在我的Biwen.QuickApi中實現一下極簡的事件匯流排,其實代碼還是蠻簡單的,對於初學者可能有些幫助 就貼出來,有什麼不足的地方也歡迎板磚交流~ 首先定義一個事件約定的空介面 public interface IEvent{} 然後定義事件訂閱者介面 public interface I ...
  • 1. 案例 成某三甲醫預約系統, 該項目在2024年初進行上線測試,在正常運行了兩天後,業務系統報錯:The connection pool has been exhausted, either raise MaxPoolSize (currently 800) or Timeout (curren ...
  • 背景 我們有些工具在 Web 版中已經有了很好的實踐,而在 WPF 中重新開發也是一種費時費力的操作,那麼直接集成則是最省事省力的方法了。 思路解釋 為什麼要使用 WPF?莫問為什麼,老 C# 開發的堅持,另外因為 Windows 上已經裝了 Webview2/edge 整體打包比 electron ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...