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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...