目錄
前言:
命名空間
命名空間的定義
命名空間的使用
c++輸入與輸出
缺省參數
函數重載
引用
引用的特性
常引用
引用的使用場景
引用做參數
引用做返回值
引用與指針的區別
內聯函數
內聯函數的特性
前言:
C 語言是結構化和模塊化的語言,適合處理較小規模的程序;對于復雜的問題,規模較大 程序,需要高度的抽象和建模時, C 語言則不合適;為了解決軟件危機, 20 世紀 80 年代, 計算機界提出了 OOP(object oriented programming:面向對象)思想 ,支持面向對象的程序設計語言 應運而生;1982 年, Bjarne Stroustrup博士 在 C 語言的基礎上引入并擴充了面向對象的概念,發明了一 種新的程序語言。為了表達該語言與 C 語言的淵源關系,命名為 C++ ,因此: C++是基于C語言而產生的,它既可以進行C語言的過程化程序設計,又可以進行以抽象數據類型為特點的基于對象的程序設計,還可以進行面向對象的程序設計;
命名空間
由于變量 函數 類的名稱存在于全局作用域中,會導致命名沖突;
使用命名空間的目的是對標識符的名稱進行本地化,以防止命名沖突或名字污染;
# include <stdio.h>
# include <stdlib.h> //stdlib.h文件中包含庫函數rand()函數
int rand = 20; //全局變量rand與stdlib.h文件中的庫函數rand()發生命名沖突
int main()
{printf("%d\n", rand);return 0;
}
//代碼運行結果 error C2365:"rand":重定義,以前定義是"函數"
命名沖突的場景:
1.? 程序員定義的變量與庫函數名相沖突;
2.? 多人協作同一個大型項目,程序員之間發生函數名命名沖突;
命名空間的定義
程序設計者根據需要指定一些帶有名字的空間域,將一些全局實體分別存放于各個命名空間中,從而與其他全局實體分割出來;
定義命名空間的關鍵字:namespace
語法: namespce 命名空間名 { 命名空間成員 }
//命名空間名為Qspace
namespace Qspace
{//定義變量int a = 10;//定義函數int Add(int x, int y){return x + y;}//定義類型struct ListNode{int val;struct ListNode* next;};
}
?命名空間嵌套定義
namespace Addspace
{int Add(int x, int y){return x + y;}//嵌套定義命名空間Subspacenamespace Subspace{int sub(int x, int y){return x - y;}}
}
?多個文件定義同名的命名空間,編譯器最終會合成同一個命名空間中
//test.h文件
# include <iostream>
namespace MSpace
{int Mul(int x, int y){return x*y;}
}//test.cpp文件
# include "test.h"
namespace MSpace
{int Add(int x, int y){return x + y;}namespace Subspace{int sub(int x, int y){return x - y;}}
}
int main()
{int ret = MSpace::Mul(2, 3);printf("%d\n", ret);return 0;
}
命名空間的使用
方式一:加命名空間名稱及作用域限定符(::) 即命名空間名 :: 命名空間成員名
namespace Qspace
{int a = 10;int Add(int x, int y){return x + y;}struct ListNode{int val;struct ListNode* next;};
}
int main()
{printf("%d\n", Qspace::a); //變量的訪問方式int ret = Qspace::Add(2, 3);//函數的訪問方式printf("%d\n", ret);struct Qspace::ListNode node = { 0, NULL };//結構體類型的訪問方式return 0;
}
方式二:使用using將命名空間中某個成員引入 即using 命名空間名 :: 命名空間成員名
namespace MSpace
{int a = 10;int b = 20;
}
//using 命名空間名::命名空間成員名
using MSpace::a;int main()
{printf("%d ", a);return 0;
}
方式三 :使用using namespace 命名空間名引入即using namespace 命名空間名
using namespace 命名空間名 將整個命名空間展開,使得特定命名空間所有成員名可見,此時使用命名空間下的變量、函數不需要加作用域限定符,使得隔離失效;
namespace Addspace
{int a = 10;int Add(int x, int y){return x + y;}
}
using namespace Addspace;
int main()
{scanf("%d", &a);int ret= Add(2, 3);printf("%d\n", ret);return 0;
}
c++輸入與輸出
- std是C++標準庫的命名空間,cout為iostream所定義的標準輸出對象(控制臺)(終端)cin為iostream所定義的標準輸入對象(鍵盤),因此使用cout , cin必須包含< iostream >頭文件及按命名空間使用方法使用std;
- endl(endline)表示換行輸出,相當于換行符,包含在<iostream>頭文件中;
- << 流插入運算符(與cout配合使用,可以將輸出的變量或者字符串流入到cout中,cout負責輸出到終端); ?
- >> 流提取運算符(與cin配合使用,將用戶輸入的值流入到某變量中);
- cin以遇到空格鍵,tab鍵或者換行符作為分隔符,停止讀取;
# include <iostream>
using namespace std;
int main()
{cout << "hello world!" << endl;return 0;
}
運行結果:
//cin遇到空格鍵 Tab鍵 Enter鍵停止讀取
# include <iostream>
using namespace std;
int main()
{char a[10] = { 0 };cin >> a;cout << a << endl;return 0;
}
運行結果:
注:cout與cin可以自動識別變量類型
缺省參數
缺省參數是聲明或定義函數時為函數的形式參數指定一個默認值(缺省值);
調用該函數時,如果沒有指定實參則采用形參的缺省值,否則使用指定的實參;
# include <iostream>
using namespace std;
void Fun(int a = 10)
{cout << a << endl;
}
int main()
{Fun(); //沒有傳參時,使用形式參數的默認值Fun(20); //傳參時,使用指定的實參return 0;
}
?運行結果:
缺省參數的分類
全缺省參數:函數定義或聲明時,為該函數所有形式參數指定缺省值;
半缺省參數:函數定義或聲明時,為該函數的部分形式參數指定缺省值,但是缺省值只能從右向左給值,必須連續給值;
# include <iostream>
using namespace std;
void Fun(int a = 10,int b=20,int c=30)//全缺省參數
{cout << "a="<< a ;cout << "b="<< b ;cout << "c="<< c << endl;
}
int main()
{Fun();Fun(1);Fun(1, 2);Fun(1, 2, 3);return 0;
}
?運行結果:
# include <iostream>
using namespace std;
void Fun(int a,int b=20,int c=30)//半缺省參數
{cout << "a="<< a ;cout << "b="<< b ;cout << "c="<< c << endl;
}
注:缺省參數不能在函數聲明和定義同時出現,若聲明與定義皆具有缺省參數,恰巧兩個位置提供的值不同,此時編譯器無法確定到底該采用那個缺省值,當聲明與定義分離時,只能在函數聲明處給出缺省參數;
函數重載
函數重載:C++允許在同一作用域中聲明幾個功能類似的同名函數,但是要求同名函數的
形參列表(參數個數 或 參數類型 或 類型順序)不同,返回值無要求;
//參數類型不同
int Add(int a, int b)
{return a+b;
}
double Add(double a, double b)
{return a+b;
}
//參數個數不同
int Fun(int a);
int Fun(int a,int b);
//形參類型的順序不同
int Sub(int a, char b);
int Sub(char b, int a);
為什么C++支持函數重載,而C語言不支持函數重載呢?
程序運行時,需要經歷如下階段:預處理 編譯 匯編 鏈接,而在鏈接階段會生成符號表,建立函數名與地址一一映射的關系,C語言鏈接函數地址時,采用函數名尋找函數定義,而對于同名函數無法區分函數地址,但是C++是通過函數修飾規則(如Linux下g++ 修飾規則:
_Z + 函數名字符個數+函數名+參數首字母)來區分,修飾后名字不同,自然支持了重載;
引用
引用不是新定義一個變量,而是 給已存在變量取了一個別名,編譯器不會為引用變量開辟內存空間,它和它引用的變量 共用同一塊內存空間;語法:?? 類型&? 引用變量名(對象名) = 引用實體;
//原變量與引用變量共用同一塊內存空間
int main()
{int a = 10;int& pa = a;printf("%p\n", &a);printf("%p\n", &pa);return 0;
}
運行結果:
注:引用類型與引用實體必須是相同類型;
引用的特性
引用在定義時必須初始化;
int main()
{//正確示例int a = 10;int& pa = a;//錯誤示例int b = 20;int& pc;//編譯時出錯return 0;
}
一個變量既可以有多個引用又可以給引用變量繼續取引用;
int main()
{int a = 10;int& pa = a;int& pb = a;int& pc = pa;cout << "a=" << a << endl;cout << "pa=" << pa << endl;cout << "pb=" << pb << endl;cout << "pc=" << pc << endl;return 0;
}
運行結果:
引用一旦引用一個實體,不能引用其他實體;
int main()
{int a = 10;int& pa = a;int d = 1;// pa變成d的別名?還是d賦值給pa?pa = d;//pa的引用實體為a,讓引用變量pa引用dcout << a << endl;return 0;
}
運行結果:
將d的值賦值給pa,又因為pa是a的引用,所以a的值間接變成了1;
常引用
int main()
{const int a = 10;int& b = a;//錯誤做法const int& b = a;//正確做法return 0;
}
變量a由于const修飾具有常屬性,不可被修改;而引用變量b與原變量應具有相同屬性,不能被修改,但是引用變量放大了權限,導致編譯錯誤;
int main()
{//權限可以縮小int c = 20;const int& d = c;const int& e = 10;return 0;
}
變量c的屬性為可讀寫,引用變量d的屬性為可讀,權限縮小并不會導致編譯錯誤;
int main()
{int i = 10;double j = i;//整型提升double& rj = i;//錯誤做法const double& rm = i;//正確做法return 0;
}
當發生整型提升時,系統不是直接將其賦值給另外一個變量的,而是會創建一個常量區來存放變量提升后的結果,此時變量具有了常屬性,一旦出現權限的放大,必然導致編譯錯誤;
總結:類型轉換(整型提升 截斷)產生臨時變量,臨時變量具有常屬性;
引用的使用場景
引用做參數
C語言階段函數通過傳址調用,實現通過形參修改實參,由于形參與實參數值上相同,空間上獨立,所以實參是形參的臨時拷貝;而引用變量在語法上與原變量共用同一塊內存空間,若形參采用引用變量的方式,也可達到修改實參;
//指針方式
void Swap1(int* pa, int* pb)
{int tmp = *pa;*pa = *pb;*pb = tmp;
}
//引用方式
void Swap2(int& a, int& b)
{int tmp = a;a = b;b = tmp;
}
引用做返回值
void Func()
{int c = 0;cout << &c << endl;
}
int main()
{Func();//Func()函數第一次調用結束,銷毀第一次為func()函數所開辟的函數棧幀Func();return 0;
}
運行結果:
?由于空間可以重復利用,第一次調用Func()函數并為其開辟函數棧幀,并在函數棧幀中為變量c分配空間,當函數運行結束后,該函數所對應的棧空間由操作系統回收,但數據是否被清理是不確定的,當第二次調用Func()函數時,仍然在該地址處創建了c這個變量;
int& Add(int a, int b)
{int c = a + b;return c;
}
int main()
{int& ret = Add(1, 2);Add(3, 4);cout << "Add(1,2)=" << ret << endl;return 0;
}
運行結果:
?ret其實指向的是c那塊空間的地址,當c發生了變化,ret也就會隨之發生改變;
int& Add(int a, int b)
{static int c = a + b;return c;
}
int main()
{int& ret1 = Add(1, 2);cout << "Add(1,2)=" << ret1 << endl;int& ret2 = Add(3, 4);cout << "Add(3,4)=" << ret2 << endl;return 0;
}
運行結果:
當Add()函數運行結束后,由于static修飾的局部變量存放于靜態區,出函數作用域并不會被銷毀,此時可以引用返回,但是第二次調用Add()函數發生錯誤,原因為靜態成員變量只會被初始化一次;
總結:引用做返回值時,返回的數據必須由static修飾或者是存放于堆區的數據或者是全局變量等不會隨著函數調用的結束而被銷毀的數據;
引用與指針的區別
1. 引用概念上定義一個變量的別名,指針存儲一個變量地址;
2. 引用在定義時必須初始化,指針沒有要求;
3. 引用在初始化時引用一個實體后,就不能再引用其他實體,而指針可以在任何時候指向任何一個同類型實體;
4. 沒有NULL引用,但有NULL指針;
5. 在sizeof中含義不同:引用結果為引用類型的大小,但指針始終是地址空間所占字節個數(32位平臺下占4個字節);
6. 引用自加即引用的實體增加1,指針自加即指針向后偏移一個類型的大小;
7. 有多級指針,但是沒有多級引用;
8. 訪問實體方式不同,指針需要顯式解引用,引用編譯器自己處理;
9. 引用比指針使用起來相對更安全;
內聯函數
宏是一種在程序中定義的簡單代碼替換機制,它們通常用于定義常量或執行重復性操作,與函數不同,宏是在編譯時展開的,而不是在運行時調用;
宏的聲明方式:
#define name( parament-list ) stuff
//其中的 parament-list 是一個由逗號隔開的符號表,它們可能出現在stuff中
//宏函數
# define Add(x,y) ((x)+(y))
int main()
{int a = 10;int b = 20;int ret = Add(10, 20);cout << "ret=" << ret << endl;return 0;
}
宏的缺點:
1、容易出錯,語法細節多;
2、不能調試(預編譯階段進行了替換);
3、沒有類型安全的檢查;
由于宏的缺點從而產生內聯函數替代宏函數;
內聯函數:以關鍵字inline修飾的函數叫做內聯函數,編譯時C++編譯器會在調用內聯函數的地方展開,沒有函數調用建立棧幀的開銷,內聯函數提升程序運行的效率;
inline int Add(int x, int y)
{return x + y;
}
int main()
{int a = 10;int b = 20;int ret = Add(10, 20);cout << "ret=" << ret << endl;return 0;
}
內聯函數的特性
- ?inline是一種以空間換時間的做法,如果編譯器將函數當成內聯函數處理,在編譯階段,會用函數體替換函數調用,缺陷:可能會使目標文件變大,優勢:少了調用開銷,提高程序運行效率;
- inline對于編譯器而言只是一個建議,不同編譯器關于inline實現機制可能不同,一般建議:將函數規模較小(即函數不是很長,具體沒有準確的說法,取決于編譯器內部實現)、不是遞歸、且頻繁調用的函數采用inline修飾,否則編譯器會忽略inline特性;
- inline不建議聲明和定義分離,分離會導致鏈接錯誤。因為inline被展開,就沒有函數地址了,鏈接就會找不到,由于內聯函數所生成的地址不會進入符號表,也就沒有函數名與地址一一映射的關系,所以發生鏈接性錯誤;