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標準輸出(控制檯)
(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++有哪些技術替代巨集?
- 常量定義 換用const
- 函式定義 換用行內函數
行內函數
概念:以inline修飾的函式。編譯時C++編譯器會在呼叫行內函數的地方展開,沒有函式壓棧的開銷,提升了程式執行的效率
(ps1:行內函數與巨集的替換時機不同,巨集替換是在預處理階段,因此不會對引數型別進行檢測)
(ps2:檢視方式:通過測試發現,在vs編譯器Debug模式下為了便於除錯,並沒有將inline修飾的函式當做行內函數進行展開,1.可以在Release模式下,檢視編譯器生成的彙編程式碼中是否存在call Add,在Release模式下,會對程式碼進行很大的優化,甚至一些沒有實際意義的程式碼都會直接刪除,所以它佔用空間會很小,但有可能會打亂程式的執行次序。2.也可以在Debug模式下對編譯器進行設定,以下給出vs2010設定方式)
特性
- inline是一種以空間換時間的做法,省去呼叫函式開銷。所以程式碼很長或者有迴圈/遞迴的函式不適宜使用作為行內函數。
- 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的使用細則
- 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;
}
- 在同一行定義多個變數
當在同一行宣告多個變數時,這些變數**必須是相同的型別,**否則編譯器將會報錯,因為編譯器實際只對第一個型別進行推導,然後用推匯出來的型別定義其他變數。
auto不能推導的場景
- auto不能作為函式的引數
- auto不能直接用來宣告陣列
- 為了避免與C++98中的auto發生混淆,C++11只保留了auto作為型別指示符的用法
- 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;
注意:
- 在使用nullptr表示指標空值時,不需要包含標頭檔案,因為nullptr是C++11作為新關鍵字引入的。
- 在C++11中,sizeof(nullptr) 與 sizeof((void*)0)所佔的位元組數相同。
- 為了提高程式碼的健壯性,在後續表示指標空值時建議最好使用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;
}