C/C++中指針的深入理解
計算機的內存模型
CPU是計算機的核心部件,要想讓一個CPU工作,就必須向它提供指令和數據,指令和數據在存儲器中存放,也就是我們平時說的內存。
內存分為:物理內存和虛擬內存,物理內存對應著計算機中的內存條,虛擬內存是操作系統(tǒng)內存管理系統(tǒng)假想出來的,由于這些不是我們本文的重點,我們就不做區(qū)分。
在不考慮cpu緩存的情況下,計算機運行程序本質上是對內存中的數據的操作,存儲器被劃分為多個存儲單元,存儲單元從零開始順序編號,CPU要從內存中讀取數據,首先要指定存儲單元的地址。
CPU從內存中讀取數據的過程如圖所示:
計算機為了方便管理內存,將內存的每個單元用一個數字編號
指針與指針常量
指針的本意就是內存地址,我們可以通俗的理解成內存編號,既然計算機通過編號來操作內存單元,這就造成了指針的高效率
指針變量:
- 通俗理解為存儲指針的變量,也就是存儲內存地址(內存編號)的變量
- 指針變量和int,float,char等類型一樣同屬變量類型,指針變量類型占四個字節(jié)(32位機器下),存儲的是32位的內存地址
星號: - 在C\C++中(*)被定義為取內容符號
- 雖然所有指針變量占的內存大小和存儲的內存地址大小都是一樣的,但是由于存儲的只是數據的內存首地址,所以指針變量存儲的內存地址所指向的數據類型決定著如何解析這個首地址
- 比如int型指針變量,我們需要從該指針變量存儲的首地址開始向后一直搜索4個字節(jié)的內存空間
- 所以當我們使用*p,必須知道p是一個什么類型的指針
指針變量和指針常量
指針變量首先是一個變量,由于指針變量存儲了某個變量的內存首地址,我們通常認為"指針變量指向了該變量",同時指針變量時一個變量,它的值是可以變動的。
相反,指針常量可通俗地理解為存儲固定的內存單元地址編號的量,它一旦存儲了某個內存地址以后,不可再改存儲其他的內存地址了
舉個例子:
void f(const int *x,int *y) { *x=2;//錯誤,由于x前面有個const修飾,所以不可以修改x所指向的內存單元的內容 //正確寫法 *y=3; }
指針變量和數組
先看一個例子:
int a[5]={1,2,3,4,5}; int *ptr=(int*)(&a+1); cout<<*(a+1)<<endl; cout<<*(ptr-1)<<endl;
輸出結果為2和5,首先我們看一下&a+1的含義:
- 我們知道C\C++中規(guī)定數組名表示這個數組的首地址,而這里出現了&a這樣的符號,本來a就是指針常量,再次取地址難道不是非法操作?
- 這時我們可以將這個&a看成是指向數組的指針,也稱為行指針,&a的類型是int (*p)[5],一個步長即5個元素的長度,&a + 1代表往后移動一個步長
分析:
- a表示的是第一個元素的首地址,那么a+1指向的就是下一個元素的內存首地址,所以*(a+1)=2
- 而&a則表示整個數據的首地址,那么&a+1移動的內存數目就是整個數組所占字節(jié)數,假如原先數組中第一個元素的首地址是0,那么&a+1表示的就是20,而這個地址已經不屬于數組了,接著通過(int*)(&a+1)將數組指針轉換成整型指針,這樣原先&a+1表示的數據范圍20-39就縮小為20-23,正好是一個int型的大小,而ptr-1就是16了,表示的數據內存范圍是16-19,這樣*(ptr-1)正好是最后一個元素5了
上面的例子,只是通過簡單的數據類型來說明內存分布,但是實際上一些復雜的數據類型,尤其是一些自定義的類或者結構體類型,內存分布還要充分考慮到字節(jié)對齊。
函數指針
函數指針是指向函數的指針變量,C\C++程序在編譯時,每個函數都有一個入口地址,該入口地址就是函數指針所指向的地址,有了指向函數的指針變量后,可用該指針變量調用函數,同時也可以做函數的參數
我們先看函數指針調用函數,如下:
int f(int x, int y) { return x + y; } //申明一個函數指針 typedef int (*pf)(int, int); int main() { int a = 1; int b = 2; //將函數f地址賦給函數指針pf pf = f; //利用函數指針調用函數 int c = (*pf)(a, b); cout << c << endl; }
需要注意的是,定義的函數指針類型時的函數簽名(包括函數返回值和函數參數列表的類型,個數,順序)要將賦值給該類型變量的函數簽名保持一致,不然可能會發(fā)生很多無法預料的情況,還有C\C++規(guī)定函數名就表示函數入口地址,所以,函數名賦值時函數名前面加不加取地址符&都一樣,但是在C++中取類的方法函數的地址時,這個&符號不能省略。
函數指針還有另外一個用處,就是作為一個函數的參數,在Windows編程中作為回調函數很常見:
typedef int (*PF)(int, int); int f1(int x, int y) { return x + y; } int f2(PF pf, int t) { return (*pf)(3, t); } int main() { //將函數f1作為參數傳遞給函數f2 int c = f2(f1, 4); cout << c << endl; return 0; }
C++中的引用
所謂引用,使用另外一個變量名來代表某一塊內存,這就相當于同一個人有不同的名字,但是不管哪個名字,指的都是同一個人。
int a=1; //通過&符號,將b定義為a的引用 int &b=a; //b和a完全一樣,等價于int c=a int c=b;
注意,C++規(guī)定,定義一個引用的時候,必須馬上初始化
傳值還是傳引用
如果變量類型是基元數據類型,比如int,float,bool,char等小數據類型被稱為基元數據類型,那么賦值時傳的是值,這時候b的值是a的拷貝,那么更改b不會影響到a,但是,如果變量數據類型是復雜數據類型,比如數組,類對象,那么賦值時傳的就是引用,這個時候,a和b指向的都是同一個內存區(qū)域,那么無論更改a或者b都會相互影響。
最后,在利用C++中拷貝構造函數復制對象時需要注意,基元數據類型可以直接復制,但是對于引用類型數據,我們需要實現引用類型的真正復制
C++中的new關鍵詞
在c++中通過new關鍵詞定義一個對象,不能直接得到對象的實例,我們需要用一個指針去接收這個new出來的對象,我們引用這個對象必須使用指針引用運算符->
#include <iostream> using namespace std; class Person { public: Person() { } Person(int a, int b) { this->m_a = a; this->m_b = b; } int m_a; int m_b; }; int main() { //在C++中可以用以下形式來實例化一個對象,per1和per2為實例化的person類對象 Person per1; int i = 1; int j = 2; Person per2(i, j); }
在C++中,this關鍵詞是一個指針,而不像在java中是一個類實例,在C++中*this才等價于java
class Person { public: Person(int number) { //C++中需要使用指針引用符號 this->m_number = number; } //返回對象本身,,需要使用引用,因為返回值時會創(chuàng)建一個新的對象,使用引用的方式不會創(chuàng)建新的對象 Person& getSelf() { return *this; } int m_number; };
總結
到此這篇關于C/C++中指針的文章就介紹到這了,更多相關C/C++指針理解內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Mac OS上搭建Apache+PHP+MySQL開發(fā)環(huán)境的詳細教程
這篇文章主要介紹了Mac OS上搭建Apache+PHP+MySQL開發(fā)環(huán)境的詳細教程,包括常見的PHP連接MySQL失敗問題的解決辦法,需要的朋友可以參考下2016-01-01