C++ 基礎 2:C++ 對 C 語言的拓展

引用

定義及編程實踐

引用,是某個已存在變量的另外一個名字。ios

一旦把引用初始化爲某個變量,就可使用該引用名稱或變量名稱來指向變量。編程

注意:函數

  • 引用沒有定義,是一種關係型聲明。聲明它和原有某一變量(實體)的關
    系。所以引用類型必須與原類型保持一致,且不分配內存。與被引用的變量有相同的地
    址。this

  • & 符號前有數據類型時,是引用。其它皆爲取地址。spa

  • 可對引用再次引用。屢次引用的結果,是某一變量具備多個別名。指針

建立引用例子以下:code

// reference.cpp

#include <iostream>
 
using namespace std;
 
int main ()
{
	// 聲明簡單的變量
	int i;
	double d;

	// 聲明引用變量
	int& r = i;
	double& s = d;

	i = 1;
	cout << "Value of i : " << i << endl;
	cout << "Value of i reference : " << r << endl;

	d = 6.1;
	cout << "Value of d : " << d << endl;
	cout << "Value of d reference : " << s << endl;

	getchar();

	return 0;
}

運行結果:對象

引用編程實踐以下:blog

// reference2.cpp

#include <iostream>
 
using namespace std;
 
int main()
{
	int a = 1;

	int& b = a;	// b = a = 1

	a = 2;

	int *p = &a; 

	*p = 3;		// a = 3

	cout << "a = "  << a << endl;

	b = 4;	    // b = a -> a = 4

	cout << "a = " << a << ", b = " << b << endl;

	getchar();

	return 0;
}

運行結果:內存

引用和指針的區別

引用很容易和指針混淆,它們之間有如下不一樣:

  • 不存在空引用。引用必須鏈接到一塊合法的內存。指針能夠爲空指針。

  • 引用必須在建立時被初始化(引用做爲函數參數的時候不須要初始化,由於形參必定會被賦值的)。指針能夠在任什麼時候間被初始化。

  • 一旦引用被初始化爲一個對象,就不能被指向到另外一個對象。指針能夠在任什麼時候候指向到另外一個對象。

引用的意義

  1. 引用做爲其餘變量的別名而存在,所以在一些場合能夠代替指針

  2. 引用相對於指針來講具備更好的可讀性和實用性

// 沒法實現兩數據的交換
void swap(int a,int b);
//開闢了兩個指針空間用於交換
void swap(int *a,int *b);
// referenceSwap.cpp,不開闢空間使用引用進行數值交換
#include <iostream>

using namespace std;

void swap(int& a, int& b)
{
	int temp;

	temp = a;
	a = b;
	b = temp;
}

int main()
{
	int a = 1,b = 2;

	cout << "a = " << a << " , b = " << b << endl;

	swap(a,b);

	cout << "a = " << a << " , b = " << b << endl;

	getchar();

	return 0;
}

運行結果:

堆變量,棧變量

全局變量、靜態局部變量、靜態全局變量,new 產生的變量都在堆中,動態分配的變量在堆中分配。

局部變量在棧裏面分配。

程序爲棧變量分配動態內存,在程序結束爲棧變量清除內存,可是堆變量不會被清除。

引用做爲函數的返回值

當函數返回值爲引用時:

  1. 若返回棧變量引用時,不能成爲其餘引用的初始值。

以下代碼所示:

// funcReturnRef.cpp,引用做爲函數的返回值,何時能夠爲其餘引用初始化的值
#include <iostream>

using namespace std;

// 返回棧變量
int getA1()
{
	int a;

	a = 1;

	return a;
}

// 返回棧變量引用
int& getA2() 
{
	int a;

	a = 1;

	return a;
}

int main()
{
	int a1 = 0;
	int a2 = 0;

	// 值拷貝
	a1 = getA1();

	// 將 棧變量引用 賦值給 變量,編譯器相似作了以下隱藏操做:a2 = *(getA2())
	a2 = getA2();

	// 將 棧變量引用 賦值給 另外一個引用做爲初始值。此時將會有警告:返回局部變量或臨時變量的地址
	int& a3 = getA2();

	cout << "a1 = " << a1<< endl;
	cout << "a2 = " << a2<< endl;
	cout << "a3 = " << a3<< endl;

	getchar();

	return 0;
}

警告信息:

第一次運行結果:

第二次運行結果:

  1. 若返回堆變量引用時,能夠成爲其餘引用的初始值

以下代碼所示:

// funcReturnRef2.cpp,引用做爲函數的返回值,何時能夠爲其餘引用初始化的值
#include <iostream>

using namespace std;

// 返回堆變量
int getA1()
{
	static int a;

	a = 1;

	return a;
}

// 返回棧變量引用
int& getA2() 
{
	static int a;

	a = 1;

	return a;
}

int main()
{
	int a1 = 0;
	int a2 = 0;

	// 值拷貝
	a1 = getA1();

	// 將 棧變量引用 賦值給 變量,編譯器相似作了以下隱藏操做:a2 = *(getA2())
	a2 = getA2();

	// 將 堆變量引用 賦值給 另外一個引用做爲初始值。因爲是靜態區域,地址不變,內存合法。
	int& a3 = getA2();

	cout << "a1 = " << a1<< endl;
	cout << "a2 = " << a2<< endl;
	cout << "a3 = " << a3<< endl;

	getchar();

	return 0;
}

運行結果:

指針引用做函數參數

C++ 中指針引用做函數參數,與 C 語言中二級指針做函數參數的區別。

以下代碼所示:

// pointReference.cpp 
// C++ 中指針引用做函數參數,與 C 語言中二級指針做函數參數的區別

#include <iostream>

using namespace std;

#define AGE 18

// C 語言中的二級指針
int getAge1(int **p)
{
	int age = AGE;

	int *ptemp = &age;

	// p 是實參的地址, *實參的地址,去間接的修改實參
	*p = ptemp;

	return 0;
}

// C++ 中指針引用
int getAge2(int* &p)
{
	int age = AGE;

	if(p == NULL)
	{
		p = (int *)malloc(sizeof(int));

		if(p == NULL)
			return -1;
	}

	// 給 p 賦值,至關於給 main 函數中的 pAge 賦值
	*p = age;

	return 0;
}


int main(void)
{
	int *pAge = NULL;

	// 1 C 語言中二級指針
	getAge1(&pAge);
	cout << "age: " << *pAge << endl;
	pAge = NULL;

	// C++ 中指針引用
	getAge2(pAge);
	cout << "age: " << *pAge << endl;
	pAge = NULL;

	getchar();

	return 0;
}

運行結果:

const 引用

  1. const 對象的引用必須是 const 的。

  2. const 引用可使用相關類型的對象(常量,非同類型的變量或表達式)初始化。這個是 const 引用與普通引用最大的區別。

例:

const int &a = 1;
double x = 1.1;
const int &b = x;
  1. const 引用限制對象爲只讀,不能經過修改 const 引用來修改 const 對象。

inline 內聯函數

C 語言中有宏函數的概念。宏函數的特色是內嵌到調用代碼中去,避免了函數調用的開銷。可是因爲宏函數的處理髮生在預處理階段,確實了語法檢測和有可能帶來的語義差錯,所以 C++ 引入了 inline 內聯函數。

內聯函數基本概念

  1. 內聯函數聲明時 inline 關鍵詞必須和函數定義結合在一塊兒,不然編譯器會忽略內聯請求。

  2. C++ 編譯器直接將函數體插入在函數調用的地方。

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

  4. 內聯函數是一種特殊的函數,具備普通函數的特徵(參數檢查,返回類型等)

  5. 內聯函數由編譯器處理,直接將編譯後的函數體插入在調用的地方,宏函數由預處理器處理,進行簡單的文本替換,沒有任何的編譯過程。

  6. C++ 對內聯函數的限制:

    • 不能存在任何形式的循環語句
    • 不能存在過多的條件判斷語句
    • 函數體不能過於龐大
    • 不能對函數進行取址操做
    • 函數內聯聲明必須在調用語句以前
  7. 編譯器對於內聯函數的限制不是絕對的,內聯函數相對於普通函數的優點知識節省了函數調用時壓棧,跳轉,和返回的開銷。所以,當函數體的執行開銷遠大於壓棧,跳轉,和返回的開銷時,那麼內聯函數將沒有意義。

實例代碼以下所示:

// inlineFunction.cpp
// 內聯函數示例

#include <iostream>

using namespace std;

inline void func()
{
	cout << "this is inlineFunction example!" << endl;
}

int main()
{
	func();

	getchar();

	return 0;
}

運行結果:

函數重載

函數重載:用同一個函數名定義不一樣的函數,當函數名和不一樣的參數搭配時函數的含義不一樣。

重載規則

  1. 函數名相同

  2. 參數個數不一樣,參數的類型不一樣,參數順序不一樣,都可構成重載。

  3. 返回值類型不一樣則不能夠構成重載。

以下所示:

void func(int a);         // ok
void func(char a);        // ok
void func(char a,int b);  // ok
void func(int a,char b);  // ok
char func(int a);         // 與第一個函數衝突,報錯

調用準則

  1. 嚴格匹配,找到即調用。

  2. 經過隱式轉換尋求一個匹配,找到即調用。

重載底層實現

C++ 利用 name mangling(傾軋)技術,來改變函數名,以區分參數不一樣的同名函數。

實現原理:用 v c i f l d 表示 void char int float long double 及其引用。

以下所示:

void func(char a);  // func_c(char a);
void func(char a,int b,double c); // func_cid(char a,int b,double c);

函數指針基本語法

// 方法一:
// 聲明一個函數類型
typedef void (myfunctype)(int a,int b);
// 定義一個函數指針
myfunctype* fp1= NULL; 

// 方法二:
// 聲明一個函數指針類型
typedef void (*myfunctype_pointer)(int a,int b)
// 定義一個函數指針
myfunctype_pointer fp2 = NULL;  

// 方法三:
// 直接定義一個函數指針
void (*fp3 )(int a,int b);

函數重載和函數指針結合

當使用重載函數名對函數指針進行賦值時,根據重載規則挑選與函數指針參數列表一致的候選者,嚴格匹配候選者的函數類型與函數指針的函數類型。

示例代碼以下所示:

#include <iostream>

using namespace std;

void func(int a, int b)
{
	cout << a << b << endl;
}

void func(int a, int b, int c)
{
	cout << a << b << c << endl;
}


void func(int a, int b, int c, int d)
{
	cout << a << b << c << d << endl;
}

// 1 定義一個函數類型
typedef void(myfunctype)(int, int); //定義了一個函數類型, 返回值void 參數列表是 int,int   ,, void()(int,int)

// 2 定義一個函數指針類型 
typedef void(*myfunctype_pointer)(int, int); //定義了一個函數指針類型, 返回值void 參數列表是 int,int   ,, void(*)(int,int)

int main(void)
{
	//1  定義一個函數指針
	myfunctype * fp1 = NULL;

	fp1 = func;

	fp1(10, 20);

	// 2 定義一個函數指針
	myfunctype_pointer fp2 = NULL;

	fp2 = func;

	fp2(10, 20);

	// 3 直接定義一個函數指針
	void(*fp3)(int, int) = NULL;

	fp3 = func;

	fp3(10, 20);

	cout << " -----------------" << endl;

	// 此時的fp3 是 void(*)(int,int)
	// fp3(10, 30, 30); // fp3 恆定指向一個 函數入口,void func(int, int) 的函數入口
	// fp3(10, 30, 40, 50); // 想要經過函數指針,發生函數重載 是不可能。
	fp3(10, 20); 

	void(*fp4)(int, int, int) = func; // 在堆函數指針賦值的時候,函數指針會根據本身的類型 找到一個重載函數

	fp4(10, 10, 10);
	// fp4(10, 10, 10, 10);
	// 函數指針,調用的時候是不可以發生函數重載的。

	void(*fp5)(int, int, int, int) = func; // void func(int ,int ,int ,int )
	fp5(10, 10, 10, 10);
	
	return 0;
}

運行結果:

函數重載總結

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

  • 函數重載是由函數名和參數列表決定的。

相關文章
相關標籤/搜索