C/C++中指針的深入理解

計算機的內存模型

CPU是計算機的核心部件,要想讓一個CPU工作,就必須向它提供指令和數據,指令和數據在存儲器中存放,也就是我們平時說的內存。

內存分為:物理內存和虛擬內存,物理內存對應著計算機中的內存條,虛擬內存是操作系統內存管理系統假想出來的,由於這些不是我們本文的重點,我們就不做區分。

在不考慮cpu緩存的情況下,計算機運行程序本質上是對內存中的數據的操作,存儲器被劃分為多個存儲單元,存儲單元從零開始順序編號,CPU要從內存中讀取數據,首先要指定存儲單元的地址。

CPU從內存中讀取數據的過程如圖所示:

計算機為瞭方便管理內存,將內存的每個單元用一個數字編號

指針與指針常量

指針的本意就是內存地址,我們可以通俗的理解成內存編號,既然計算機通過編號來操作內存單元,這就造成瞭指針的高效率

指針變量:

  • 通俗理解為存儲指針的變量,也就是存儲內存地址(內存編號)的變量
  • 指針變量和int,float,char等類型一樣同屬變量類型,指針變量類型占四個字節(32位機器下),存儲的是32位的內存地址
    星號:
  • 在C\C++中(*)被定義為取內容符號
  • 雖然所有指針變量占的內存大小和存儲的內存地址大小都是一樣的,但是由於存儲的隻是數據的內存首地址,所以指針變量存儲的內存地址所指向的數據類型決定著如何解析這個首地址
  • 比如int型指針變量,我們需要從該指針變量存儲的首地址開始向後一直搜索4個字節的內存空間
  • 所以當我們使用*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++中規定數組名表示這個數組的首地址,而這裡出現瞭&a這樣的符號,本來a就是指針常量,再次取地址難道不是非法操作?
  • 這時我們可以將這個&a看成是指向數組的指針,也稱為行指針,&a的類型是int (*p)[5],一個步長即5個元素的長度,&a + 1代表往後移動一個步長

分析:

  • a表示的是第一個元素的首地址,那麼a+1指向的就是下一個元素的內存首地址,所以*(a+1)=2
  • 而&a則表示整個數據的首地址,那麼&a+1移動的內存數目就是整個數組所占字節數,假如原先數組中第一個元素的首地址是0,那麼&a+1表示的就是20,而這個地址已經不屬於數組瞭,接著通過(int*)(&a+1)將數組指針轉換成整型指針,這樣原先&a+1表示的數據范圍20-39就縮小為20-23,正好是一個int型的大小,而ptr-1就是16瞭,表示的數據內存范圍是16-19,這樣*(ptr-1)正好是最後一個元素5瞭

上面的例子,隻是通過簡單的數據類型來說明內存分佈,但是實際上一些復雜的數據類型,尤其是一些自定義的類或者結構體類型,內存分佈還要充分考慮到字節對齊。

函數指針

函數指針是指向函數的指針變量,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;
}

需要註意的是,定義的函數指針類型時的函數簽名(包括函數返回值和函數參數列表的類型,個數,順序)要將賦值給該類型變量的函數簽名保持一致,不然可能會發生很多無法預料的情況,還有C\C++規定函數名就表示函數入口地址,所以,函數名賦值時函數名前面加不加取地址符&都一樣,但是在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++規定,定義一個引用的時候,必須馬上初始化

傳值還是傳引用

如果變量類型是基元數據類型,比如int,float,bool,char等小數據類型被稱為基元數據類型,那麼賦值時傳的是值,這時候b的值是a的拷貝,那麼更改b不會影響到a,但是,如果變量數據類型是復雜數據類型,比如數組,類對象,那麼賦值時傳的就是引用,這個時候,a和b指向的都是同一個內存區域,那麼無論更改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;
	}
	//返回對象本身,,需要使用引用,因為返回值時會創建一個新的對象,使用引用的方式不會創建新的對象
	Person& getSelf()
	{
		return *this;
	}
	int m_number;
};

總結

到此這篇關於C/C++中指針的文章就介紹到這瞭,更多相關C/C++指針理解內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: