C++_系列自學課程_第_8_課_指針和引用_《C++ Primer 第四版》
C語言最富有迷幻色彩的部分當屬指針部分,無論是指針的定義還是指針的意義都可算是C語言中最復雜的內容。指針不但提供給了程序員直接操作硬件部分的操作接口,還提供給了程序員更多靈活的用法。C++繼承這一高效的機制,同時引入了另一個與指針相似但不相同的機制: 引用。
一、引用
簡單的來說,引用就是變量的別名(alias), 通過別名我們可以操作引用代表的變量。 定義一個引用的語法如下所示:
變量類型 &引用標識符 = 變量名。
Exp:
復制代碼
int iVar=10;
int &iRef = iVar;
iRef = 20 ;
cout<<iVar<<endl;
復制代碼
這段程序執行的結果就是輸出: 20 ;
程序通過引用 iRef 改變了變量iVar的值。
要點:
1、在定義引用的同事必須初始化,指出引用代表的是哪一個變量,而且這種“指向關系”不能改變。
2、引用只是對象的另一個名字,可以通過對象的原標識符訪問對象,也可以通過對象的引用訪問對象。
3、在一個語句定義多個引用的時候,每個引用標識符(引用名)的前面必須都加上&符號,否則就是錯誤。
1、const引用
const引用是指向const對象的引用, 不能通過const引用改變原對象的值。如下所示:
復制代碼
1 #include <iostream>
2 #include <string>
3 #include <vector>
4 #include <bitset>
5
6 using std::cin;
7 using std::cout;
8 using std::endl;
9 using std::string;
10 using std::vector;
11 using std::bitset;
12
13 int main()
14 {
15 const int iVar=10;
16 const int &iRef = iVar;
17 iRef = 20;
18 cout<<iVar<<endl;
19
20 return 0;
21 }
復制代碼
上面的程序編譯的結果如下所示:
[root@localhost cpp_src]# g++ test.cpp
test.cpp: In function ‘int main()’:
test.cpp:17: 錯誤:assignment of read-only reference ‘iRef’
可以發現在第17行,試圖對一個指向const對象的const引用賦值,結果編譯報錯。
復制代碼
1 #include <iostream>
2 #include <string>
3 #include <vector>
4 #include <bitset>
5
6 using std::cin;
7 using std::cout;
8 using std::endl;
9 using std::string;
10 using std::vector;
11 using std::bitset;
12
13 int main()
14 {
15 const int iVar=10;
16 const int &iRef = iVar;
17 iRef = 20;
18
19 int &iRef1 = iVar;
20 cout<<iVar<<endl;
21
22 return 0;
23 }
復制代碼
程序編譯結果如下:
[root@localhost cpp_src]# g++ test.cpp
test.cpp: In function ‘int main()’:
test.cpp:17: 錯誤:assignment of read-only reference ‘iRef’
test.cpp:19: 錯誤:將類型為 ‘int&’ 的引用初始化為類型為 ‘const int’ 的表達式無效
我們發現在程序編譯的時候第19行也報錯啦,報錯的類型是: 將 類型int &的引用初始化類型const int的表達式無效。
2、字面值引用
可以定義const引用代表字面值。實例如下:
復制代碼
int main()
{
int const &iRef = 100;
const string &strRef = "volcanol";
cout << iRef <<endl;
cout << strRef <<endl;
return 0;
}
復制代碼
程序的執行結果如下:
[root@localhost cpp_src]# g++ test.cpp
[root@localhost cpp_src]# ./a.out
100
volcanol
上面的實例注意一點: 要對字面值定義別名引用,則必須將別名引用定義為const型的,否則將出現編譯錯誤。
二、指針
指針是什麼,有的地方說是指針是一個地址。這裡我們不對指針的復雜用法進行討論,如果想了解指針的復雜用法可以產考我在園子裡的另外一篇隨筆,鏈接地址
為:http://www.cnblogs.com/volcanol/archive/2011/06/05/2073042.html
1、指針的定義
在C++中定義指針,很簡單,在定義的變量的時候,在變量的前面加上一個 * 就表示要定義一個指針變量。語法如下:
指針要指向的數據類型 * 指針變量名;
Exp:
int *pInt; 定義了一個指向整型變量的指針變量pInt;
string *pStr; 定義了一個指向string類型的對象的指針pStr;
vector<int> *pVectorInt; 定義一個指向vector<int> 容器的指針。
bitset<5> *pBitset5; 定義一個指向bitset<5>類型的對象的指針。
2、指針變量賦值和初始化
指針變量在使用前必須有一個確定的指向,否則就會造成一個游離的指針,操作的游離指針會得到一個意想不到的的結果。通過取得一個變量的地址然後賦值給
指針變量或者初始化指針變量使指針變量有一個確定的指向。 通過操作符 & 取得一個變量/對象的地址或者(指針)。
指針變量初始化:
int iVar = 10;
int *pInt = &iVar;
指針變量賦值:
int iVar = 10;
int *pInt1;
int *pInt2;
pInt1 = &iVar;
pInt2 = pInt1;
3、指針的引用
通過解引用操作符 * 可以引用指針指向的變量。
復制代碼
int iVar = 20;
int *pInt = NULL;
pInt = &iVar;
cout<< * pInt<<endl;
復制代碼
Exp:
復制代碼
int main()
{
int iVar = 100;
int *pInt = &iVar;
cout<<(*pInt)<<endl;
string strVar = "volcanol";
string *pStr = &strVar;
cout<<(*pStr)<<endl;
vector<int> vInt(1);
vector<int> *pVecInt=&vInt;
cout<<(*pVecInt)[0]<<endl;
bitset<5> bitVar(5);
bitset<5> *pBitset5 = &bitVar;
cout<< (*pBitset5) <<endl;
return 0;
}
復制代碼
程序的執行結果如下所示:
[root@localhost cpp_src]# g++ test.cpp
[root@localhost cpp_src]# ./a.out
100
volcanol
0
00101
要點:
在定義指針變量的時候,必須在每個指針變量的前面都加上 * ,否則定義的就是一個非指針變量。
int *pInt1,pInt2; //pInt1 為指針變量, pInt2為整型變量。
在定義指針變量的時候,有兩種風格的格式: int *pInt 和 int* pInt; 這兩種格式沒有對錯之分,兩種格式C++都是接受的,只是在理解的時候可能會引起
誤解。為了避免誤解,在一個程序裡面,最好選取一種格式一直保持下去。
4、指針的指針
指針變量也是一種對象,同樣可以給指針變量定義一個指向它的指針,就是指針的指針。定義語法如下:
指針的指針變量指向的對象類型 **指針的指針變量標識符;
Exp:
int iVar = 10 ;
int *pInt = &iVar;
int **ppInt = &pInt;
如上就定義了一個指向整型指針變量的指針變量ppInt; ppInt指向的對象的類型為 int* 類型的對象。
復制代碼
int main()
{
int iVar = 100;
int *pInt = &iVar;
int **ppInt = &pInt;
cout <<"iVar ="<< iVar<<endl;
cout <<"int *pInt = &iVar,then *pInt ="<<*pInt<<endl;
cout <<"int **ppInt = &pInt,then *ppInt="<<*ppInt;
cout <<";and then **ppInt="<<**ppInt<<endl;
return 0;
}
復制代碼
程序的執行結果如下所示:
[root@localhost cpp_src]# g++ test.cpp
[root@localhost cpp_src]# ./a.out
iVar =100
int *pInt = &iVar,then *pInt =100
int **ppInt = &pInt,then *ppInt=0xbfb949f8;and then **ppInt=100
5、通過指針訪問數組元素
這裡需要說明一個細節: 某一個數組的數組名是一個常量,而且數組名表示的是數組的第一個元素的首地址,同時數組元素在內存中是連續存放的。
正是因為數組具有上述的特點,才能方便的通過指針來訪問數組的元素。
通過指針訪問數組元素的例子如下:
復制代碼
int main()
{
int iArray[5] = {1,2,3,4,5};
int *pInt = iArray;
cout << *pInt << endl; // 1
cout << pInt[0]<<endl; // 1
cout << *++pInt<<endl; // 2
cout << *pInt++<<endl; // 2
cout << *pInt<<endl ; // 3
return
}
復制代碼
程序的執行結果如下所示:
[root@localhost cpp_src]# ./a.out
1
1
2
2
3
不但可以通過++運算符來改變指針的指向,指針還支持加整數和減整數運算,同時支持兩個指針的減法運算。
復制代碼
int main()
{
int iArray[5] = {1,2,3,4,5};
int *pInt1 = iArray;
int *pInt2= &iArray[4];
cout <<*(pInt1 + 2)<<endl; // 3
cout <<*(pInt2 - 1)<<endl; // 4
cout << pInt2 - pInt1 <<endl;
return 0;
}
復制代碼
程序的執行結果如下:
[root@localhost cpp_src]# g++ test.cpp
[root@localhost cpp_src]# ./a.out
3
4
4
要點:
可以發現這個地方 pInt2 - pInt1 的結果是4, 這個結果與C語言的輸出是存在差別的。這一點要非常注意,在指針與數組結合使用的過程中,兩個指針相減
是經常見到的操作,因此這個地方需要注意。
通過上面的實例,我們可知利用指針可以很方便的訪問數組的元素,因此我們可以通過指針遍歷整個數組。
復制代碼
int main()
{
int iArray[5] = {1,2,3,4,5};
for(int *pBegin=iArray,*pEnd=iArray+5; pBegin != pEnd; ++pBegin)
cout<<*pBegin<<endl;
return 0;
}
復制代碼
程序的執行結果如下所示:
復制代碼
[root@localhost cpp_src]# g++ test.cpp
[root@localhost cpp_src]# ./a.out
1
2
3
4
5
復制代碼
指針和數組之間的定義還包括* 和 [] 符號同時在定義中出現的情況,
Exp:
復制代碼
1 #include <iostream>
2 #include <string>
3 #include <vector>
4 #include <bitset>
5
6 using std::cin;
7 using std::cout;
8 using std::endl;
9 using std::string;
10 using std::vector;
11 using std::bitset;
12
13 int main()
14 {
15 int iArray_1[5] = {1,2,3,4,5};
16 int iArray_2[3] = {1};
17 int *pInt1[5] ={iArray_1, iArray_2};
18 int (*pInt2)[5] = iArray_1; //error
19 pInt2 = iArray_2; //error
20
21
22 return 0;
23 }
復制代碼
上面的代碼中, 我標出了兩處錯誤,錯誤的原因是, pInt2 是一個二維的指針,而iArray_1 和 iArray_2 都是int * 類型的指針, 如果將程序修改一下就可以
得到如下的結果。
復制代碼
1 #include <iostream>
2 #include <string>
3 #include <vector>
4 #include <bitset>
5
6 using std::cin;
7 using std::cout;
8 using std::endl;
9 using std::string;
10 using std::vector;
11 using std::bitset;
12
13 int main()
14 {
15 //int iArray_1[5] = {1,2,3,4,5};
16 //int iArray_2[3]= {1};
17 //int *pInt1[5] ={iArray_1, iArray_2};
18 //int (*pInt2)[5] = iArray_1; //error
19 //pInt2 = iArray_2; //error
20
21 int iArray_1[5]={1,2,3,4,5};
22 int iArray_2[3][5]={{1}};
23 int iArray_3[5][3]={{2}};
24 int (*pInt)[5] = iArray_2;
25 pInt=iArray_3; //error
26
27
28 return 0;
29 }
復制代碼
上面的代碼中,我們可以知道 25行的語法是錯誤的,錯誤的原因是二維數組的第二維的指針長度不一致。通過上面的例子我們可以知道,* 和 [] 在一起定義指針變量
的時候,需要注意 * 和 [] 符號的優先級,同事需要知道加上括號後,定義的時候[] 的維度的擴展。這個地方是C語言當中經常會使用的,而且是屬於較復雜的用法,因
此需要因此特別的重視。
6、 指針 和 const限定符/修飾符
指針和const的結合使用沒有太多的說頭,主要是注意const修飾的 *p 還是 p, 只要分清楚修飾對象的不同就很好理解。
復制代碼
int main()
{
int iVar1 = 10;
int iVar2 = 20;
const int *pInt1 = &iVar1;
int const *pInt2 = &iVar1;
int * const pInt3 = &iVar1;
const int * const pInt4 = &iVar1;
int const * const pInt5 = &iVar2;
return 0;
}
復制代碼
關於const限定符需要知道的就是上面的各個定義的意義,只要知道 const是修飾 *pInt 還是修飾pInt就可以准確的分辨各個定義的意義,具體可以關注我前面
給出的關於C語言趣事相關的鏈接文章。
這裡還有一個需要注意的地方,就是對於const對象如何定義指向其的指針,下面是一個例子:
復制代碼
int main()
{
const int iVar = 10;
//int *pInt1 = &iVar; //error
int const *pInt1 = &iVar;
const int *pInt2 = &iVar;
return 0;
}
復制代碼
這裡要注意加了注釋部分錯誤的原因。這裡就不解釋了,這個與const對象與引用的關系是一樣的。
7、指針和typedef的使用
在C語言中進程會做這樣的預處理指令。
#define PINT int*
這樣定義宏以後,就可以通過這個宏來定義指針變量,如下所示:
#define PINT int*
int iVar = 0;
PINT pInt = &iVar;
這樣是可以通過的,但是這樣會存在一個漏洞,如果同時定義兩個指針變量的話,就會出現錯誤。
#define PINT int*
int iVar1 = 0;
int iVar2 = 0;
PINT pInt1 = &iVar1, pInt2 = &iVar2;
很顯然上面的代碼存在漏洞, 第二個變量 pInt2 不是指針變量,而是一個整型的變量, 好在這樣的錯誤編譯器在編譯的時候會檢查出來,這裡需要引起注意。
我們可以利用typedef機制來規避上述的風險, typedef 的作用就是為數據類型取一個別名,尤其在數據類型比較長時是一個非常有效的機制, typedef的語法
如下:
typedef 數據類型 數據類型別名;
例如:
typedef int* PINT;
這就為 int* 這種類型定義了一個新的別名 PINT,在使用的時候PINT就表示 int*。
Exp:
typedef int* PINT;
int iVar1 = 0;
int iVar2 = 0;
PINT pInt1 = &iVar1, pInt2 = &iVar2;
上面的代碼定義了兩個整型變量 iVar1、iVar2, 同時定義了兩個指針變量pInt1 和 pInt2;
要點:
通過上面兩個例子,就可以清楚 typedef和#define 之間的差別。
注意typedef是語句,因此後面必須有個分號結尾。 這個點是經常容易忘記的,好在編譯器一般可以檢測出這樣的錯誤。
typedef和指針的結合還有一個值得注意的地方,就是 typedef 、const和指針同時出現。
typedef int* PINT
const PINT pInt; //error
這裡定義的指針對象pInt是const指針對象, 這個指針對象在定義的時候必須初始化。因此要注意上面的這個錯誤。
復制代碼
1 #include <iostream>
2 #include <string>
3 #include <vector>
4 #include <bitset>
5
6 using std::cin;
7 using std::cout;
8 using std::endl;
9 using std::string;
10 using std::vector;
11 using std::bitset;
12
13 int main()
14 {
15 typedef int* PINT;
16 const PINT pInt;
17
18 return 0;
19 }
復制代碼
程序編譯的結果如下所示:
[root@localhost cpp_src]# g++ test.cpp
test.cpp: In function ‘int main()’:
test.cpp:16: 錯誤:未初始化的常量 ‘pInt’
將程序改成下面的形式則正確:
復制代碼
#include <iostream>
#include <string>
#include <vector>
#include <bitset>
using std::cin;
using std::cout;
using std::endl;
using std::string;
using std::vector;
using std::bitset;
int main()
{
typedef int* PINT;
//const PINT pInt;
int iVar = 0;
const PINT pInt = &iVar; //初始化const指針
return 0;
}
復制代碼
當然還可以定義更加復雜的數據類型,這裡就不再進行描述,後面如果碰到會進行相關的描述。
指針的操作基本上就是這些,在C++語言中,大部分的人傾向於不使用指針, 但是指針確實是一種非常高效的機制,但是如果能把指針用好,則會對
程序的性能的提升具有很好的提高作用。