C++深度解析教程學習筆記(3)函數的擴展

1.內聯函數

1.1.常量與宏的回顧

(1)C++中的 const 常量能夠替代宏常數定義,如:數據庫

const int A = 3;
//等價於
#define A 3

(2)C++中是否有解決方案,能夠用來替代宏代碼片斷呢?函數

1.2.內聯函數的定義

(1)C++編譯器能夠將一個函數進行內聯編譯,被 C++編譯器內聯編譯的函數叫內聯函數。優化

(2)C++中使用 inline 關鍵字聲明內聯函數。如spa

inline int func(int a, int b)
{
     return a < b ? a : b;
}

(3)內聯函數聲明時 inline 關鍵字必須和函數定義結合在一塊兒,不然編譯器會直接忽略內聯請求。(在 vs2013 下,inline 放在聲明或定義前都可以)指針

1.3.內聯函數的特色

(1)C++編譯器直接將內聯函數的函數體插入到函數調用的地方調試

(2)內聯函數沒有普通函數調用時的額外開銷(壓棧、跳轉、返回)code

(3)C++中推薦使用內聯函數替代宏代碼片斷。blog

(4)C++編譯器也不必定知足函數的內聯請求。遞歸

#include <stdio.h>

#define FUNC(a, b) ((a) < (b) ? (a) : (b))

//MSVC下:要讓inline、__forceinline生效必須得作以下的設置:
//①在「項目」→「配置屬性」→「C / C++」 →「優化」→「內聯函數擴展」中選擇「只適用於__inline(/ Ob1)」
//②在「配置屬性」→「C / C++」 →「全部選項」→「調試信息格式」中選擇「程序數據庫( / Zi)」

//VS2013下,inline可放在聲明前或也可放在定義前。或二者前都加
inline int func(int a, int b) 
{
    return a < b ? a : b;
}

int main()
{
    int a = 1;
    int b = 3;

    //int c = FUNC(++a, b);//至關於(++a)<(b)?:(++a):(b);

    //printf("a = %d\n", a); //3
    //printf("b = %d\n", b); //3
    //printf("c = %d\n", c); //3

    int c = func(++a, b);

    printf("a = %d\n", a);//2
    printf("b = %d\n", b);//3
    printf("c = %d\n", c);//2

    return 0;
}

內聯函數沒嵌入到調用地方(仍爲函數調用)ci

函數體被嵌入到調用的地方

1.4.內聯函數與宏的不一樣

 

內聯函數

處理方式

由預處理器處理,只是進行簡單的文本替換

由編譯器處理,會將函數體嵌入到調用的地方。但內聯請求也可能被編譯器拒絕

類型檢查

不作類型檢查

具備普通函數的特徵,會進行參數和返回類型的檢查。

反作用

1.5.現代C++編譯器對內聯函數的優化

(1)現代 C++編譯器可以進行編譯優化,一些函數即沒有 inline 聲明,也可能被內聯編譯。

(2)一些現代的 C++編譯器提供了擴展語法,可用下列列關鍵字替代 inline 來對函數進行強制內聯,如:

①g++:__atrribute__((always_inline))   ②MSVC:__forceinline

(3)MSVC 下:要讓 inline、__forceinline 生效必須得作以下的設置:

①在「項目」→「配置屬性」→「C/C++」 →「優化」→「內聯函數擴展」中選擇「只適用於__inline(/Ob1)」

②在「配置屬性」→「C/C++」 →「全部選項」→「調試信息格式」中選擇「程序數據庫(/Zi)」

#include <stdio.h>

//MSVC2013下:在函數聲明或定義前加inline或__forceinline均可以
//同時,這兩個的表現行爲幾乎如出一轍。只不過__forceinline是MS
//下的,而inline是標準C++的,可移植性更高。

//__forceinline
//__attribute__((always_inline))
//inline
int add_inline(int n);

int main()
{
    int r = add_inline(10);

    printf("r = %d\n", r);

    return 0;
}

__forceinline int add_inline(int n)
{
    int ret = 0;

    for (int i = 0; i < n; i++)
    {
        ret += i;
    }

    return ret;
}

1.6. C++中 inline 內聯編譯的限制

(1)含有遞歸調用的函數不能設置爲 inline

(2)使用了複雜流程控制語句:循環語句和 switch 語句,沒法設置爲 inline(說明:如上述實例,在 VS2013 下,循環語句是能夠被內聯的)

(3)函數體不能過於龐大

(4)不能對函數進行取址操做

(5)函數內聯聲明必須在調用語句以前.

2.函數參數的擴展

2.1.函數參數默認值

(1)C++中能夠在函數聲明時爲參數提供一個默認值(注意是聲明,不能在定義中提供)

(2)當函數調用時沒有提供參數的值,則使用默認值

默認參數值

#include <stdio.h>

//默認值只能在函數聲明時提供
int mul(int x = 0); //參數x的默認值爲0

int main()
{
    printf("%d\n", mul());   //傳入默認值0
    printf("%d\n", mul(-1)); //傳入-1
    printf("%d\n", mul(2)); //傳入2

    return 0;   
}

int mul(int x)    //定義中,不能提供默認值,編譯器會報錯
{
    return x * x;
}

(3)函數參數默認值的規則

①聲明時,默認值必須從右向左提供

②函數調用時,若是使用了默認值,則後續參數必須使用默認值。

#include <stdio.h>

//默認參數必須從右向左提供,諸如
//int add(int x = 0,int y = 1,int z)是錯誤的
int add(int x, int y = 1, int z = 2);

int main()
{
    //第2參數y使用了默認值,則後續的z也必須使用默認值
    //諸如add(1, ,3);的調用是錯的。
    printf("%d\n", add(0));      //x = 0, y = 1, z = 2

    printf("%d\n", add(2, 3));   //x = 2, y = 3, z = 2
    printf("%d\n", add(3, 2, 1));//x = 3, y = 2, z = 1

    return 0;   
}

int add(int x, int y, int z)
{
    return x + y + z;
}

2.2.函數佔位參數

(1)佔位參數只有參數類型聲明,而沒有參數名聲明,如:int func(int x,int)

(2)通常狀況下,在函數體內部沒法使用佔位參數

(3)佔位參數的意義

①佔位參數與默認參數結合起來使用

②兼容 C 語言程序中可能出現的不規範寫法

C++中支持佔位參數,用於兼容 C 語言中的不規範寫法

佔位參數與默認參數值

#include <stdio.h>

//在C中int func()是能夠接受任意參數的,因此在後來的調用中可能
//出現func(1)、func(2, 3)等不一樣的調用,而這樣的代碼在C++中是
//錯誤的,因此爲了兼容C語言這種不規範的寫法,能夠給func提供兩個
//佔用參數如func(int = 0,int = 0),則前面的兩種調用就合法了,
//這樣花不多的代價,就可讓C的代碼能夠在C++中使用。讓人感受仿
//佛C++也能夠像C語言同樣,接受任意個參數了!

//佔位參數,且默認值爲0
int func(int x = 0, int = 0);

int main()
{
    printf("%d\n", func());     //0
    printf("%d\n", func(1));    //1
    printf("%d\n", func(2, 3)); //2

    return 0;   
}

//第2個參數爲佔位參數(沒函數名),所以在函數內部也就沒法使用
//這個參數,只起到佔位的做用
int func(int x, int)
{
    return x;
}

3.函數重載

3.1.函數重載(overload)的概念

(1)用同一個函數名定義不一樣的函數

(2)當函數名和不一樣的參數搭配時,函數的含義不一樣。

#include <stdio.h>
#include <string.h>

int func(int x)
{
    return x;
}

int func(int a, int b)
{
    return a + b;
}

int func(const char* s)
{
    return strlen(s);
}

int main()
{
    printf("%d\n", func(3));              //int (int)
    printf("%d\n", func(4,5));            //int (int,int)
    printf("%d\n", func("Hello World!")); //int (const char* s)

    return 0;   
}

3.2.函數重載

(1)重載的條件:必須至少知足下面的一個條件

①參數個數不一樣

②參數類型不一樣

③參數順序不一樣

(2)函數重載的注意事項

①重載函數在本質上是相互獨立的不一樣函數。

②重載函數的函數類型不一樣

③函數的返回值不能做爲函數重載的依據

④函數重載是由函數名和參數列表共同決定的。

函數重載的本質

include <stdio.h>

int add(int a, int b)  //函數類型:int(int,int)
{
    return a + b;
}

int add(int a, int b, int c)  //函數類型:int(int, int, int)
{
    return a + b + c;
}

int main()
{
    //printf("%p\n", add);//由於函數的重載,在編譯的結果中找不到這樣的函數名

    //如下兩個printf顯示出來,重載函數的本質是相互獨立的兩個函數,其函數地址
    //是不一樣的。

    printf("%p\n",(int (*)(int, int))add);    //在add前面加上類型,編譯器就會
                                              //就根據重載函數的命名規則找到
                                              //被編譯後的真正的函數名

    printf("%p\n",(int (*)(int, int,int))add);//在add前面加上類型,編譯器就會
                                              //就根據重載函數的命名規則找到
                                              //被編譯後的真正的函數名
    return 0;   
}

3.3.函數重載與函數的默認參數

(1)編譯器調用重載函數的準則

①將全部同名函數做爲候選者

②嘗試尋找可行的候選函數(注意,下面 3 種匹配任一種後,會繼續匹配下一種,因此可能出現多個匹配的結果!)

A.精確匹配實參;B 經過默認參數可以匹配實參;C 經過默認類型轉換匹配實參

③匹配失敗

A.最終尋找到的候選函數不惟一,則出現二義性,編譯失敗。

B.沒法匹配全部候選者,函數未定義,編譯失敗

函數默認參數 VS 函數重載

#include <stdio.h>

int func(int a, int b, int c = 0)
{
    return a * b * c;
}

int func(int a, int b)
{
    return a + b;
}

int main()
{
    //根據匹配原則:經過函數名找到兩個候選函數
    //並嘗試先經過精確匹配會找到func(int,int)
    //但這時並不會中止匹配,而是會嘗試用默認參數去匹配
    //因此會找到另外一個func,即func(int,int,int = 0),所以
    //出現了二義性,編譯器直接報錯。
    int c = func(1, 2);

    return 0;   
}

函數重載用於模擬天然語言中的詞彙搭配,使得 C++具備更豐富的語義表達能力。函數重載的本質爲相互獨立的不一樣函數,C++中經過函數名和函數參數肯定函數調用。

3.4.重載函數與函數指針

(1)將重載函數名賦值給函數指針時

①根據重載規則挑選與函數指針參數列表一致的候選者

②嚴格匹配候選者的函數類型與函數指針的函數類型(所謂嚴格匹配,即函數參數及返回值都匹配)

函數重載 VS 函數指針

#include <stdio.h>
#include <string.h>

int func(int x)
{
    return x;
}

int func(int a, int b)
{
    return a + b;
}

int func(const char* s)
{
    return strlen(s);
}

//聲明函數指針
typedef int (*PFUNC)(int a);

int main()
{
    int c = 0;

    PFUNC p = func;//編譯器將根據函數指針的類型去嚴格匹配對應的函數
                    //因此會找到int func(int);其餘函數則匹配不成功

    c = p(1); //

    printf("c = %d\n", c); //1

    return 0;   
}

(2)注意事項

①函數重載必然發生在同一個做用域中(如,同一個類或同一命名空間中)

②編譯器須要用參數列表或函數類型進行函數選擇

③沒法直接經過函數名獲得重載函數的入口地址(由於編譯結束後,C++會根據重載函數命名的規則重命名各個函數,而原來的函數名其實是找不到的)

3.5.C和C++相互調用

1)實際工做中 C++和 C 代碼相互調用是不可避免的

(2)C++編譯器可以兼容 C 語言的編譯方式

(3)C++編譯器會優先使用 C++編譯的方式

(4)extern 關鍵字能強制 C++編譯器進行 C 方式的編譯

C++調用 C 函數

//add.h
int add(int a, int b);
//add.c
#include "add.h"

//該文件的編譯,獲得目標文件add.o
//gcc -c add.c

int add(int a, int b)
{
    return a + b;
}
//main.cpp
#include <stdio.h>

//該文件的編譯
//g++ main.cpp add.o

#ifdef __cplusplus
extern "C" {
#endif

//C++中以C的方式編譯:將add的函數名就是目標名
#include "add.h"

#ifdef __cplusplus
}
#endif

int main()
{
    int c = add(1, 2);

    printf("c = %d\n", c); //3

    return 0;   
}

3.6.讓C/C++代碼只以C的方式編譯

(1)C++內置的標準宏:__cplusplus,能夠確保 C 代碼以統一的 C 方式編譯

#ifdef __cplusplus
extern "C" {
#endif

......; //C/C++代碼,將以C的方式編譯

#ifdef __cplusplus
}
#endif

C 調用 C++函數(其中的 C++函數己經被按 C 方式編譯)

//add.h
//該文件的編譯,獲得目標文件add.o
//g++ -c add.c

#ifdef __cplusplus
extern "C" {
#endif

//C++中以C的方式編譯:add的函數名就是目標名
int add(int a, int b);

#ifdef __cplusplus
}
#endif
//add.cpp
#include "add.h"

//該文件的編譯,獲得目標文件add.o
//g++ -c add.c

#ifdef __cplusplus
extern "C" {
#endif

//C++中以C的方式編譯:add的函數名就是目標名
int add(int a, int b)
{
    return a + b;
}

#ifdef __cplusplus
}
#endif
//main.c
#include <stdio.h>
#include "add.h"
  //編譯方式:
  //gcc main.c add.o
int main()
{
    int c = add(1, 2);

    printf("c = %d\n", c); //3

    return 0;   
}

C 調用 C++函數(其中的 C++函數是 C++方式編譯)

①假設別人提供了編譯好的 cpp 的頭文件和.o 目標文件,但其中的函數是以 C++方式編譯的,很明顯函數名是用 C++方式命名的。咱們的 C 文件裏不方便使用這個的函數名。

②解決的方案是:作一個 C++的封裝層,對其中的函數進行一個封裝,而後再用extern "c"編譯這些封裝層中的函數,最後就能夠在 C 文件中使用了。

//add.h
int add(int a, int b);
//add.cpp
#include "add.h"

//編譯命令:g++ -c add.cpp

int add(int a, int b)
{
    return a + b;
}

咱們的封裝層

//addEx.h
int addEx(int a, int b);
//addEx.cpp
#include "add.h"

//編譯命令:
//g++ -c addEx.cpp

extern "C" int addEx(int a,int b)
{
    return add(a, b);
}
//main.c
#include <stdio.h>
#include "addEx.h"
//編譯命令:
//gcc main.c addEx.0 add.o

int main()
{
    int c = addEx(1, 2);

    printf("c = %d\n", c); //3

    return 0;   
}

(2)注意事項

①C++編譯器不能以 C 的方式編譯重載函數,即若是在 extern "C"塊裏有兩個同名的函數裏,則會編譯失敗。

②編譯方式決定函數名被編譯後的目標名。C++編譯方式將函數名和參數列表編譯成目標名,而 C 編譯方式只將函數名做爲目標名進行編譯。

相關文章
相關標籤/搜索