函數重載
函數重載:是函數的一種特殊情況,C++ 允許在同一作用域中聲明幾個功能類似的同名函數,這些同名函數的形參列表(參數個數 或 類型 或 順序)必須不同,常用來處理實現功能類似數據類型不同的問題
舉例:以下函數構成重載
int Add(int left, int right)
{
return left+right;
}
double Add(double left, double right)
{
return left+right;
}
long Add(long left, long right)
{
return left+right;
}
int main()
{
Add(10, 20);
Add(10.0, 20.0);
Add(10L, 20L);
return 0;
}
名字修飾
函數的名字修飾(Decorated Name)是編譯器在編譯期間創建的一個字符串,用來指明函數的定義或原型。LINK程序或其他工具有時需要指定函數的名字修飾來定位函數的正確位置。由於 C語言不支持函數重載,而 C++ 語言支持函數重載,所以 C 和 C++的函數名稱修飾規則是不相同的
以下面這個Add函數爲例,來探討一下 C 和 C++ 語言的函數名稱修飾規則
int Add(int x, int y)
{
return x+y;
}
C語言的函數名稱修飾規則:
C語言將上面的函數名稱處理爲 _Add,即其修飾規則爲 _函數名(在函數名字前面添加了下劃線)
Linux下:
C語言將上面的函數名稱處理爲 Add,即爲函數名,用 objdump 命令可以查看目標文件中的函數名字的修飾
Linux下:
C++語言將上面的函數名稱處理爲 _Z3ADDII
其中 _Z 代表開始,3 代表函數名的字符個數,其後跟函數名(這裏爲Add),再加上參數類型(這裏 II 代表有兩個整型的參數)
通過上面的分析,我們發現不同的編譯器對函數名稱修飾的規則是不同的,但我們依然可以找到規律,C語言對函數名稱修飾的處理只關注到了函數名;而 C++ 語言除了函數名,還關注了函數的參數,通過對函數名稱的修飾不同,編譯器調用函數時所找的符號就不同,因而 C++ 語言支持函數重載,也可得出構成函數重載的條件爲函數名相同,參數不同,返回值類型可同可不同
extern “C” 的作用
C++文件中可以直接利用 extern 使用 c 文件中的代碼嗎?
Add.c
int Add(int x, int y)
{
return x+y;
}
Test.cpp
#include <stdio.h>
#include <stdlib.h>
extern int Add(int x, int y);
int main()
{
int z = Add(1, 2);
printf("%d\n", z);
system("pause");
return 0;
}
上面這樣使用可以嗎?
發現會報錯( 連接錯誤 ):error LNK2019: 無法解析的外部符號 “int __cdecl Add(int,int)” (?Add@@YAHHH@Z),該符號在函數 _main 中被引用
【分析一下】
Add函數在 Add.c 這個文件中,編譯時 Add函數被處理爲_Add,在 Test.cpp 文件中我們告訴編譯器在別的文件中定義了 Add函數,但是 Test.cpp 這個文件將 Add 函數處理爲 ?Add@@YAHHH@Z,所以當 main 函數中調用到Add函數時編譯器就在別的文件中一直找 ?Add@@YAHHH@Z,發現沒有找到。其實在別的文件中Add函數被處理爲了 _Add,所以找不到了,長的不一樣!
那麼,爲了讓編譯器能夠找到 .c 文件中的函數,我們該怎麼辦嘞??
應用 extern “C” , 就是將 Test.c 文件中 extern int Add(int x, int y) ; 變爲 extern “C” int Add(int x, int y);
使用extern “C” 相當於告訴編譯器這部分代碼使用C語言的規則進行編譯和鏈接 ,這樣cpp文件中的Add函數就會被處理爲_Add , 編譯器就能在 Add.c 文件中找到它了。
指針空值nullptr(C++11)
C++98中的指針空值
在良好的C/C++編程習慣中,聲明一個變量時最好給該變量一個合適的初始值,否則可能會出現不可預料的錯誤,比如未初始化的指針。如果一個指針沒有合法的指向,我們基本都是按照如下方式對其進行初始化:
void TestPtr()
{
int* p1 = NULL;
int* p2 = 0;
// ……
}
NULL實際是一個宏,在傳統的C頭文件( stddef.h )中,可以看到如下代碼:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
可以看到,NULL可能被定義爲字面常量0,或者被定義爲無類型指針(void*)的常量。不論採取何種定義,在
使用空值的指針時,都不可避免的會遇到一些麻煩,比如:
void f(int)
{
cout<<"f(int)"<<endl;
}
void f(int*)
{
cout<<"f(int*)"<<endl;
}
int main()
{
f(0);
f(NULL);
f((int*)NULL);
return 0;
}
程序本意是想通過f(NULL)調用指針版本的f(int*)函數,但是由於NULL被定義成0,因此與程序的初衷相悖。
在C++98中,字面常量0既可以是一個整形數字,也可以是無類型的指針(void*)常量,但是編譯器默認情況下
將其看成是一個整形常量,如果要將其按照指針方式來使用,必須對其進行強轉(void *)0。
nullptr 與 nullptr_t
爲了考慮兼容性,C++11並沒有消除常量0的二義性,C++11給出了全新的nullptr表示空值指針。C++11爲什
麼不在NULL的基礎上進行擴展,這是因爲NULL以前就是一個宏,而且不同的編譯器廠商對於NULL的實現可
能不太相同,而且直接擴展NULL,可能會影響以前舊的程序。因此:爲了避免混淆,C++11提供了
nullptr,即:nullptr代表一個指針空值常量。nullptr是有類型的,其類型爲nullptr_t,僅僅可以被隱式轉
化爲指針類型,nullptr_t被定義在頭文件中
typedef decltype(nullptr) nullptr_t;
注意:
- 在使用nullptr表示指針空值時,不需要包含頭文件,因爲nullptr是C++11作爲新關鍵字引入的
- 在C++11中,sizeof(nullptr) 與 sizeof((void*)0)所佔的字節數相同
- 爲了提高代碼的健壯性,在後續表示指針空值時建議最好使用nullptr