1. 程式人生 > >C++學習筆記——名稱空間&預設引數&函式過載&引用

C++學習筆記——名稱空間&預設引數&函式過載&引用

C++學習筆記——名稱空間&預設引數&函式過載&引用

戳這裡:我的印象筆記原連結

C++:

1.解決C語言中設計不好或者使用不是很方便的語法—>優化
2.增加新的語法特性
注:extern “C”:在C++工程中,將程式碼按照C語言的風格來編譯

C++關鍵字 (C++98)----63

此圖源@位元科技
在這裡插入圖片描述

名稱空間:

用於解決名字衝突,相當於一個作用域

namespace N1
{
1.變數
2.函式
3.名稱空間(巢狀)
}

名稱空間的定義方式
1.普通名稱空間(只包含變數和函式)
2.名稱空間可以巢狀
3.可以建立多個相同名字的名稱空間–>合併

名稱空間的訪問方式
1.在成員前+ N:: (N為名稱空間的名字,::為作用域限定符—預設訪問全域性的)
2.使用using來指定訪問變數:using N2::N3::a;
3.使用using來指定訪問名稱空間:using namespace N2;

標準輸入/輸出

使用標準輸入cin和標準輸出cout時,必須包含**標頭檔案以及std標準名稱空間**
//為了和c區分,c++98之後標頭檔案不需要包含.h
// using namespace std;標準名稱空間

cin標準輸入(鍵盤):int a = 0;
double b = 12.34;
cin>>a>>b;
cout標準輸出(控制檯)

:cout<<10<<" "<<12.34<<endl; //好處是不需要加資料格式控制(%d,%c之類)
dec:十進位制
oct:八進位制
hex:十六進位制
二進位制可以使用bitset<> 把要輸出的數變成二進位制儲存輸出)

“備胎”–>預設引數

概念:宣告或定義函式時為函式的引數指定一個預設值(呼叫時如果沒有指定實參就會使用預設值)

全預設引數:所有引數都有預設值
對於全預設引數,如果呼叫函式時只傳遞了一部分實參,則實參從左往右傳遞,其餘採用預設值
半預設引數:部分引數帶有預設值,必須從右向左依次給出
對於半預設引數,要注意對沒有給出預設值的引數傳遞實參,實參同樣從左往右傳遞

注意:
1.半預設引數必須從右往左依次給出,不能間隔著給
2.預設引數不能在函式宣告和定義中同時出現(為了避免出現宣告和定義不一致情況),最好在宣告的位置
3.預設值必須是常量或者全域性變數
4.C語言不支援(編譯器不支援)

“一詞多義”–>函式過載:

概念:是函式的一種特視情況,C++允許在同一作用域中宣告幾個功能類似的同名函式,但這些同名函式的形參列表(引數個數、型別、順序)必須不同,常用來處理功能類似資料型別不同的問題 //與返回值型別無關,如果只是返回值型別不同,則不能構成過載

二義性無參函式同名的全預設函式不能同時存在

C語言中不支援函式過載是因為:
C語言中編譯器對函式名字的修飾規則:只是簡單地在函式名字前新增下劃線
C++中支援函式過載是因為:
//在vs中通過只給宣告不給定義的方式呼叫函式,編譯成功,連結時報錯就可以看到編譯器對函式名字的修飾規則↓↓↓
C++中編譯器對函式名字的修飾規則(_cdecl:C語言預設呼叫約定):
int ADD(int left,int right); —> [email protected]@[email protected] ?函式名@@YA引數表@Z
引數表(返回值和形參型別)符號表示:
void - X
int - H
unsigned int - I
float - M
double - N
bool - N
char - D
short - F
long - J
unsigned long - K

“外號”–>引用

概念給已存在的變數取一個別名,編譯器不會為引用變數開闢記憶體空間,它和它引用的變數共用同一塊記憶體空間

型別& 引用變數名(物件名)= 引用實體

引用特性:
1.引用在定義時必須初始化
2.一個變數可以有多個引用
3.引用一旦引用了一個實體,就不能再引用其他實體

注意
1.引用型別必須與引用實體同類型
2.引用常量實體時要加const修飾
3.一般情況下,因為引用與實體共用同一塊記憶體空間,所以改變引用的值也就是改變了實體的值
4.引用型別與引用實體不同時加const可以通過編譯,此時編譯器會為引用建立一個臨時變數,這個臨時變數具有常屬性

使用場景
1.作形參
如果不需要通過形參修改實參的值,最好的方法是在形參引用前加上const修飾
2.作返回值
如果用引用作為函式的返回值型別不能返回函式棧上的空間
在這裡插入圖片描述
如果一定要用引用作為返回值,返回的變數生命週期一定要比函式的生命週期長
比如可以這樣稍作修改:
在這裡插入圖片描述

傳值、傳地址、傳引用效率比較

#include <Windows.h>
struct A
{
    int array[10000];
};
void TestFunc(A& a)
{}
void TestRefPtr()
{
    A a;
    size_t start = GetTickCount();
    for (size_t i = 0; i < 1000000; i++)
        TestFunc(a);
    size_t end = GetTickCount();
    cout << end - start << endl;
}
int main()
{
    TestRefPtr();
    system("pause");
    return 0;
}

通過上面程式碼的比較,我們發現引用和指標在傳參上的效率幾乎相同

引用與指標的區別
在這裡插入圖片描述
不同點
1.引用在定義時必須初始化,指標沒有要求(但最好有一個合法的指向)
2.引用在初始化時引用一個實體後,就不能再引用其他實體,而指標可以在任何時候指向任意一個同類型實體
3.沒有NULL引用,但有NULL指標
4.在sizeof中含義不同:引用的結果為引用型別的大小,但指標始終是地址空間所佔的位元組個數(32位平臺為4個位元組)
5.引用自加是引用的實體加1指標自加是指標向後偏移一個型別的大小
6.有多級指標,但沒有多級引用(拓展:C++11中將const int&& rra = 10;這種形式稱為右值引用,將普通引用稱為左值引用
7.訪問實體的方式不同,指標需要顯示解引用,而引用由編譯器自己處理
總結來說也可以得出;
1.引用更安全。因為指標在使用之前必須要判空,而引用不需要(因為規定了引用在定義時必須初始化)
2.引用更簡潔。引用在使用時程式碼比較清晰,也不需要解引用操作,寫起來簡單,看起來舒服,還可以達到指標的效果

巨集的優缺點?

優點:
1.增強程式碼的複用性。
2.提高效能。
缺點:
1.不方便除錯巨集。(因為預編譯階段進行了替換)
2.導致程式碼可讀性差,可維護性差,容易誤用。
3.沒有型別安全的檢查 。
C++有哪些技術替代巨集?

  1. 常量定義 換用const
  2. 函式定義 換用行內函數

行內函數

概念:以inline修飾的函式。編譯時C++編譯器會在呼叫行內函數的地方展開,沒有函式壓棧的開銷,提升了程式執行的效率
(ps1:行內函數與巨集的替換時機不同,巨集替換是在預處理階段,因此不會對引數型別進行檢測)
(ps2:檢視方式:通過測試發現,在vs編譯器Debug模式下為了便於除錯,並沒有將inline修飾的函式當做行內函數進行展開,1.可以在Release模式下,檢視編譯器生成的彙編程式碼中是否存在call Add,在Release模式下,會對程式碼進行很大的優化,甚至一些沒有實際意義的程式碼都會直接刪除,所以它佔用空間會很小,但有可能會打亂程式的執行次序。2.也可以在Debug模式下對編譯器進行設定,以下給出vs2010設定方式)
在這裡插入圖片描述

特性

  1. inline是一種以空間換時間的做法,省去呼叫函式開銷。所以程式碼很長或者有迴圈/遞迴的函式不適宜使用作為行內函數。
  2. inline對於編譯器而言只是一個建議,編譯器會自動優化,如果定義為inline的函式體內有迴圈/遞迴等等,編譯器優化時會忽略掉內聯

auto關鍵字(C++11

C++11中,標準委員會賦予了auto全新的含義即:auto不再是一個儲存型別指示符,而是作為一個新的型別指示符來指示編譯器(“佔位符”),auto宣告的變數必須由編譯器在編譯時期推導而得。

int TestAuto()
{
return 10;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = TestAuto();
cout << typeid(b).name() << endl; // int
cout << typeid(c).name() << endl; // char
cout << typeid(d).name() << endl; // int
//auto e; 無法通過編譯,使用auto定義變數時必須對其進行初始化
return 0;
}

auto的使用細則

  1. auto與指標和引用結合起來使用
    用auto宣告指標型別時,用auto和auto*沒有任何區別,但用auto宣告引用型別時則必須加&
int main()
{
int x = 10;
auto a = &x;
auto* b = &x;
auto& c = x;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
*a = 20;
*b = 30;
c = 40;
return 0;
}
  1. 在同一行定義多個變數
    當在同一行宣告多個變數時,這些變數**必須是相同的型別,**否則編譯器將會報錯,因為編譯器實際只對第一個型別進行推導,然後用推匯出來的型別定義其他變數。

auto不能推導的場景

  1. auto不能作為函式的引數
  2. auto不能直接用來宣告陣列
  3. 為了避免與C++98中的auto發生混淆,C++11只保留了auto作為型別指示符的用法
  4. auto在實際中最常見的優勢用法就是跟C++11提供的新式for迴圈,還有lambda表示式等進行配合使用。

//5. auto不能定義類的非靜態成員變數
//6. 例項化模板時不能使用auto作為模板引數

auto的優勢
1.在擁有初始化表示式複雜型別變數宣告時的簡化
2.可以免除程式設計師在一些型別宣告時的麻煩,或者避免一些在型別宣告時的錯誤(程式設計師不用自己去抉擇,編譯器根據運算的結果推導)

基於範圍的for迴圈

範圍for的語法
對於一個有範圍的集合而言,由程式設計師來說明迴圈的範圍是多餘的,有時候還會容易犯錯誤。因此C++11中引入了基於範圍的for迴圈:for迴圈後的括號由冒號“ :”分為兩部分:第一部分是範圍內用於迭代的變數,第二部分則表示被迭代的範圍。

void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for(auto& e : array)
e *= 2;
for(auto e : array)
cout << e << " ";
return 0;
}

(ps:與普通迴圈類似,可以用continue來結束本次迴圈,也可以用break來跳出整個迴圈。)

範圍for的使用條件
1. for迴圈迭代的範圍必須是確定的
對於陣列而言,就是陣列中第一個元素和最後一個元素的範圍;對於而言,應該提供begin和end的方法,begin和end就是for迴圈迭代的範圍。
2. 迭代的物件要實現++和==的操作。

指標空值nullptr(C++11)

1.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)的常量*。不論採取何種定義,在使用空值的指標時,都不可避免的會遇到一些麻煩
在C++98中,字面常量0既可以是一個整形數字,也可以是無型別的指標(void*)常量,但是編譯器預設情況下將其看成是一個整形常量,如果要將其按照指標方式來使用,必須對其進行*強轉(void )0

2.nullptr 與 nullptr_t
為了考慮相容性,C++11並沒有消除常量0的二義性,為了避免混淆,C++11給出了全新的nullptr表示空值指標。即:nullptr代表一個指標空值常量。nullptr是有型別的,其型別為nullptr_t,僅僅可以被隱式轉化為指標型別,nullptr_t被定義在標頭檔案中:

typedef decltype(nullptr) nullptr_t;

注意:

  1. 在使用nullptr表示指標空值時,不需要包含標頭檔案,因為nullptr是C++11作為新關鍵字引入的。
  2. 在C++11中,sizeof(nullptr) 與 sizeof((void*)0)所佔的位元組數相同。
  3. 為了提高程式碼的健壯性,在後續表示指標空值時建議最好使用nullptr。

【總結】
此圖源@位元科技
在這裡插入圖片描述

最後附上我的學習程式碼,僅供參考

#include <iostream>
using namespace std;

#if 0
//名稱空間

#include <stdio.h>
#include <stdlib.h>



//普通名稱空間
namespace N1
{
	int a = 10;
	int b = 20;

	int Add(int left,int right)
	{
		return left + right;
	}
}

//名稱空間可以巢狀
namespace N2
{
	int c = 30;
	int d = 40;

	int Sub(int left, int right)
	{
		return left - right;
	}

	namespace N3
	{
		int a = 50;
		int b = 60;

		int Mul(int left, int right)
		{
			return left * right;
		}
	}
}

//可以建立多個相同名字的名稱空間
namespace N1
{
	int Div(int left, int right)
	{
		return left / right;
	}
}

//using N2::N3::a;
using namespace N2;
int main()
{
	/*printf("%d\n", ::a);
	printf("%d\n", N1::a);
	printf("%d\n", N2::N3::a);*/
	//printf("%d\n", a);
	printf("%d\n", Sub(d, c));
	system("pause");
	return 0;
}
#endif

#if 0
//標準輸入/輸出

int main()
{
	int a = 0;
	double b = 12.34;
	cin >> a >> b;
	cout << a <<"	"<< b << endl;
	//cout << hex << a<<endl;
	cout << 10 << "    " << 12.34 << endl;
	cout << "hello world!" << endl;
	system("pause");
	return 0;
}
#endif

#if 0
//預設引數

int g_a = 9;
void TestFunc(int a = g_a)
{
	cout << a << endl;
}

//全預設引數:所有引數都有預設值
void TestFunc1(int a = 0, int b = 1,int c=2)
{
	cout << a << " " << b << " " << c << endl;
}

//半預設引數:部分引數帶有預設值,必須從右向左依次給出
void TestFunc2(int a, int b = 1, int c = 0)
{
	cout << a << " " << b << " " << c << endl;
}

int main()
{
	/*TestFunc();
	TestFunc(10);*/

	/*TestFunc1();
	TestFunc1(10, 20, 30);
	TestFunc1(10);
	TestFunc1(10, 20);*/

	TestFunc2(10);
	TestFunc2(10,20);
	TestFunc2(10,20,30);
	system("pause");
	return 0;
}
#endif

#if 0
//函式過載

int ADD(int left, int right)
{
	return left + right;
}

double ADD(double left, double right)
{
	return left + right;
}

char ADD(char left, char right)
{
	return left + right;
}

//形參列表不同(個數、型別、順序)
void Test()
{}

void Test(int a)
{}

void Test(double a)
{}

void Test(int a, double b)
{}

void Test(double a, int b)
{}

int main()
{
	//cout << ADD(1, 2) << endl;
	//cout << ADD(1.1, 2.2) << endl;
	//cout << ADD('1', '2') << endl;//ASCLL碼相加


	system("pause");
	return 0;
}
#endif

#if 0
//引用

void Swap(int& left,int& right)
{
	int tmp = left;
	left = right;
	right = tmp;
}

//如果不需要通過形參修改實參的值,最好的方法是在形參引用前加上const修飾
int TestFunc(const int& a)
{
	return a;
}
//如果用引用作為函式的返回值型別,不能返回函式棧上的空間
//如果一定要用引用作為返回值,返回的變數生命週期一定要比函式的生命週期長
int& Test()
{
	int x = 1;
	return x;
}

int main()
{
	int a = 10;
	int b = 20;
	const int c = 30;
	int& ra = a;//引用在定義時必須初始化
	int& rra = a;//一個變數可以有多個引用
	//int& ra = b; //引用一旦引用了一個實體,就不能再引用其他實體

	cout << &a << endl;//共用同一塊記憶體空間,所以地址都相同
	cout << &ra << endl;
	cout << &rra << endl;

	const int& rc = c;//引用常量實體必須加const修飾
	const int& rd = 10;

	double e = 12.34;
	const int& re = e;//引用型別與引用實體不同時加const可以通過編譯,此時編譯器會為引用建立一個臨時變數,這個臨時變數具有常屬性
	e = 100;

	ra = 20;//一般情況下,因為引用與實體共用同一塊記憶體空間,所以改變引用的值也就是改變了實體的值
	rra = 30;

	a = 10;
	b = 20;
	Swap(a, b);

	cout << TestFunc(a) << endl;

	int& rx = Test();
	cout << rx << endl;//10
	cout << rx << endl;//隨機值 因為第一次輸出時Test函式中x所指向的棧上空間已經被cout壓棧覆蓋了
	system("pause");
	return 0;
}
#endif

//傳值、傳地址、傳引用效率比較:

#include <Windows.h>
struct A
{
	int array[10000];
};

void TestFunc(A& a)
{}

void TestRefPtr()
{
	A a;
	size_t start = GetTickCount();
	for (size_t i = 0; i < 1000000; i++)
		TestFunc(a);

	size_t end = GetTickCount();
	cout << end - start << endl;
}

int main()
{
	int a = 10;
	int* pa = &a;
	*pa = 20;

	int &ra = a;
	ra = 20;

	TestRefPtr();

	ra++;
	pa++;

	char c = 'a';
	char& rc = c;

	char* pc = &c;
	cout << sizeof(rc) << endl;
	cout << sizeof(pc) << endl;

	system("pause");
	return 0;
}