1. 程式人生 > 其它 >9.【C語言詳解】指標

9.【C語言詳解】指標

指標是什麼

指標是什麼?

指標理解的2個要點:

  1. 指標是記憶體中一個最小單元的編號,也就是地址;
  2. 平時口語中說的指標,通常指的是指標變數,是用來存放記憶體地址的變數;

指標就是地址,指向某一塊記憶體空間。

我們有一個變數在棧中被建立,如果我們想找到它有幾種方式呢?

  1. 通過變數名去訪問
  2. 通過地址訪問

這一個個編號就是虛擬記憶體地址,相當於將記憶體分為以一個位元組為基本單位的很多單元體,通過對這些單元體的編號,我們就可以在任何位置找到一個對應的編號。

像不像我們現實中的地址門牌號?

指標變數

我們可以通過&(取地址操作符)取出變數的記憶體其實地址,把地址可以存放到一個變數中,這個變數就是指標變數。

int main()
{
	int a = 10;//在記憶體棧中開闢一塊空間
	int *p = &a;//這裡我們對變數a,取出它的地址,可以使用&操作符。
  	//a變數佔用4個位元組的空間,這裡是將a的4個位元組的第一個位元組的地址存放在p變數
	//中,p就是一個之指標變數。
	return 0;
}

指標變數,用來存放地址的變數。(存放在指標中的值都被當成地址處理)。

那這裡的問題是:

  • 一個小的單元到底是多大?(1個位元組)
  • 如何編址?

經過仔細的計算和權衡我們發現一個位元組給一個對應的地址是比較合適的。

對於32位的機器,假設有32根地址線,那麼假設每根地址線在定址的時候產生高電平(高電壓)和低電平(低電壓)就是(1或者0);

這樣每個地址就可以用32個二進位制位表示。每個二進位制位有兩種情況。

那麼32根地址線產生的地址就會是:232

每個地址標識一個位元組,那我們就可以給 (2^32Byte = 2^32/1024KB = 232 / 1024/1024MB = 2^32/1024/1024/1024GB = 4GB) 4G的空閒進行編址。

這裡我們就明白:

  • 在32位的機器上,地址是32個0或者1組成二進位制序列,那地址就得用4個位元組的空間來儲存,所以

  • 一個指標變數的大小就應該是4個位元組。

  • 那如果在64位機器上,如果有64個地址線,那一個指標變數的大小是8個位元組,才能存放一個地址。

    總結:

  • 指標是用來存放地址的,地址是唯一標示一塊地址空間的。

  • 指標的大小在32位平臺是4個位元組,在64位平臺是8個位元組。

指標和指標型別

先看看基本型別:

整形
int
char
short
long
longlong

浮點型
double
float

那麼指標指向不同的型別,是不是就代表著指標型別的不同呢?

可以確定的說:當然。

如下程式碼:

int a = 0;
int* p = &a;

p就是一個指標變數,指向一個int型別的資料。

根據a的型別的不同,p的型別也要做出相應的調正。

我們知道p就是一個指標變數,那它的型別是怎樣的呢?

我們給指標變數相應的型別。

char  *pc = NULL;
int  *pi = NULL;
short *ps = NULL;
long  *pl = NULL;
float *pf = NULL;
double *pd = NULL;

可以看到任何任何型別的指標的變數前都有一個 * , 大致模樣是

type + * + 變數名

char* 型別的指標是為了存放 char 型別變數的地址。

short* 型別的指標是為了存放 short 型別變數的地址。

int* 型別的指標是為了存放 int 型別變數的地址。

指標可以根據指向不同的型別來分類,那麼它們作用上有什麼不同?

通常來說,想通過地址訪問變數,不同變數的儲存方式,大小都不同,那麼指標去訪問的時候的許可權以及解引用訪問方式是不是應該也不同。

int i;
double d;
//假如它們的首地址相同
int* p1 = &i;
double *p2 = &d;
p1 == p2;

int 和 double的型別長度不同,那麼通過解引用方式取得 i 和 d的值的時候,所訪問的位元組長度也不同。

//演示例項
int main()
{
	int n = 10;
	char *pc = (char*)&n;
	int *pi = &n;
	printf("%p\n", &n);
	printf("%p\n", pc);
	printf("%p\n", pc+1);
	printf("%p\n", pi);
	printf("%p\n", pi+1);
	return 0;
}

輸出:

可以看到指標型別決定了指標加一或者減一時跳過幾個位元組。

指標的解引用

int main()
{
	int n = 0x11223344;
	char* pc = (char*)&n;
	int* pi = &n;
	*pc = 0; (1)
	*pi = 0; (2)
    return 0;
}

上圖為(1)被執行

下圖為(2)被執行

指標的型別決定了,對指標解引用的時候有多大的許可權(能操作幾個位元組)。
比如: char* 的指標解引用就只能訪問一個位元組,而 int* 的指標的解引用就能訪問四個位元組。

野指標

成因

  1. 指標未初始化
int main()
{
	int* p;
	int a = *p;
	return 0;
}

會報錯:使用了未初始化的區域性變數。

  1. 指標越界訪問
int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	for (i = 0; i <= 11; i++)
	{		
		*(p++) = i;//當指標指向的範圍超出陣列arr的範圍時,p就是野指標
	}
	return 0;
}

如何規避野指標的使用

  1. 指標初始化;
  2. 注意指標的越界;
  3. 指標使用後及時置NULL;
  4. 函式體中不應返回區域性變數的地址;
  5. 檢查指標的有效性;
#include <stdio.h>
int main()
{
  int *p = NULL;
  //....
  int a = 10;
  p = &a;
  if(p != NULL)
  {
    *p = 20;
  }
  return 0;
}

指標運算

  • 指標 + 整數
  • 指標 - 整數
  • 指標 - 指標

指標 +- 整數

指標的值增加(減少) = 指向的型別的位元組數 * 整數。

指標 - 指標

結果等於:兩個指標相差的位元組數 / 指向型別的位元組數。

指標的關係運算

就是普通的地址值的比較。

值得注意的是,在對陣列的操作中:

標準規定:

允許指向陣列元素的指標與指向陣列最後一個元素後面的那個記憶體位置的指標比較,但是不允許與指向第一個元素之前的那個記憶體位置的指標進行比較。

指標和陣列

看程式碼:

#include <stdio.h>
int main()
{
  int arr[10] = {1,2,3,4,5,6,7,8,9,0};
  printf("%p\n", arr);
  printf("%p\n", &arr[0]);
  return 0;
}

陣列名和陣列首元素的地址的值是一樣的。

但是注意型別是不同的,只是在數值上相等。

我們既然可以知道陣列元素的指標,那麼自然可以實現對其元素地址的逐一訪問。

int main()
{
  int arr[] = {1,2,3,4,5,6,7,8,9,0};
  int *p = arr; //指標存放陣列首元素的地址
  int sz = sizeof(arr)/sizeof(arr[0]);
  for(i=0; i<sz; i++)
 {
    printf("&arr[%d] = %p  <====> p+%d = %p\n", i, &arr[i], i, p+i);
 }
  return 0;
}

輸出:

可以看到&arr[i] == (arr + i )。

既然連地址都可以逐一訪問了,那麼地址上所儲存的資料自然可以輕鬆獲得:

int main()
{
	int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	int *p = arr; //指標存放陣列首元素的地址
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i = 0; i<sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

二級指標

指標是儲存變數地址的變數,那麼自然指標變數也是需要一塊記憶體空間來儲存它的值。

如果我們想得到指標變數的地址,只能對指標變數&操作。

如果我們也想找一個變數用來儲存指標變數的值,那麼這個變數的型別是什麼?

這就是二級指標。

int main()
{
	int num = 10;
	int* pa = &num;
	int** ppa = &pa;
	return 0;
}

那麼可以通過二級指標獲取int變數的值嗎?

是的,我們既然二級指標記錄了一級指標的值,就可以取得一級指標值,而一級指標的值又是int變數的地址,層層遞進解引用,便可以獲取int變數的值。

*ppa = pa;
**ppa = *pa = num

指標陣列

元素型別為整形集合的陣列叫什麼?

答案是整形陣列,那麼理所應當,元素型別為指標的陣列就叫指標陣列了。

我們建立一個整型陣列或者字元陣列如下:

int arr1[10];
char arr2[10];

陣列名可以看作一個變數名,[10]代表這是個陣列中有十個元素,那麼剩下的就是元素型別。

我們建立一個有是個元素,每個元素為int*指標的陣列:

我們建立一個int*變數是如何建立的?

  1. 確定變數名
  2. 確定變數的型別
  3. 在變數名前加上型別名

那麼陣列也是差不多的。

首先寫出想要的陣列名
arr;

arr是個陣列,並且有十個元素,我們加上[10];
arr[10];

元素型別是什麼? int*
int* arr[10];

整形陣列在記憶體中的儲存

指標陣列呢?

可以看到,每個元素都是一個指標,指向一塊特定的記憶體空間。

思考:指標陣列的元素可以是陣列名嗎?

答案是的,除了對陣列名進行sizeof和&操作的兩種情況外,其他時候的陣列名,一律當作指標來使用。