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

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

函數的參數及其傳遞方式

1. 函數參數傳遞方式

傳值:數組

    傳變量值: 將實參內存中的內容拷貝一份給形參, 二者是不一樣的兩塊內存安全

    傳地址值: 將實參所對應的內存空間的地址值給形參, 形參是一個指針, 指向實參所對應的內存空間微信

傳引用:函數

    形參是對實參的引用, 形參和實參是同一塊內存空間學習

2. 對象做爲函數參數, 也就是傳變量值

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

像這樣:設計

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. 對象指針做爲函數參數, 也就是傳地址值

形參是對象指針, 實參是對象的地址值, 雖然參數傳遞方式仍然是傳值方式, 由於形參和實參的地址值同樣, 因此它們都指向同一塊內存, 咱們經過指針更改所指向的內存中的內容, 因此當在函數中經過形參改變內存中的值時, 改變的就是原來實參的值指針

像這樣:code

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語言編程學習基地

有一些源碼和資料分享,歡迎轉行也學習編程的夥伴,和你們一塊兒交流成長會比本身琢磨更快哦!

相關文章
相關標籤/搜索