1. 程式人生 > >[C]第六章--指標

[C]第六章--指標

指標

指標是什麼?

指標變數是一個數字,這個數字是記憶體的房間號。

  • 在電腦科學中,指標(pointer)是程式語言中的一個物件,利用地址,它的值直接指向存在電腦儲存器中另一個地方的值。
  • 由於通過地址能到找所需的變數單元,可以說,地址指向該變數單元
    。因此,將地址形象化的稱為“指標”,意思就是通過它能夠找到以它為地址的記憶體單元

指標是個變數,存放記憶體單元的地址,對應到程式碼:

#include <stdio.h>
int main({
	int a = 10;//開闢一部分記憶體空間
	int* p = &a;//針對變數a取出它的地址,存放在p變數中
	//p就成為了一個指標變數
	return 0}

對於32位機器,假設有32根地址線,那麼假設每根地址線在定址時產生一個電訊號正電/負電(也就是1或者0),那麼32根地址線產生的地址就是:

00000000 00000000 00000000 00000000 ... 11111111 11111111

也就是有 232

個地址。
每個地址標識一個位元組,那麼我們就可以給 232 的空閒進行編址。

2^32^  bit 
= 2^32^ / 8 byte 
= 2^32^ / 1024 KB 
= 2^32^ / 1024 / 1024 MB 
= 2^32^ / 1024 /1024 /1024 GB 
= 4GB

也就是說,32位機器可以編址4GB的空間
同理可得,64位機器也可以用這種方式計算空間。

結論:

  • 在32位機器上,地址是32個0或者1組成的二進位制序列,那地址就得用4個位元組的空間來儲存,所以一個指標變數的大小就應該是4個位元組
  • 如果在64位機器上,如果有64根地址線,那一個指標變數的大小是8個位元組
    ,才得以存放一個地址。

指標型別

前面討論過,變數的型別有很多:整型,浮點型,布林型等等,那指標有什麼型別呢?

char* pa = NULL;
int* pb = NULL;
short* pc = NULL;
long* pd = NULL;
float* pe = NULL;
double* pf = NULL;

這裡我們可以看到,指標的定義方式是型別 + *
其實,char*型別的指標是為了存放char型別變數的地址,int*型別的指標是為了存放int型別變數的地址,其他型別指標同理。

指標的解引用

#include <stdio.h>
int main(){
	int n = 0x11223344;
	char* pa = &n;
	char* pi = &n;
	*pc = 0x55;
	*pi = 0;
	return 0;
}

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

陣列和陣列名

我們之前的學習過程中知道了陣列在記憶體中是連續存放的。
那麼陣列名呢?如下程式碼:

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

程式執行結果為:
在這裡插入圖片描述
通過對結果的比對,可見二者的地址是相同的.

  • 結論:
    陣列名錶示的是首元素的地址.

所以如下圖程式中編制程式碼也是可行的:

int arr[10] = {0,1,2,3,4,5,6,7,8,9};
int* p = arr;
//這裡p其實就是存放的首元素的地址

指標運算

1. 指標 + - 整數

#include <stdio.h>
int main(){
	int n = 10;
	char* pc = &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;
  1. %p是專門列印地址的格式,它表示以十六進位制格式輸出.
  2. 指標 + 1 / - 1 ,不代表地址 + 1,而是跳過一個當前指向的元素,與元素大小有關。

總結:

  • 指標的型別決定了指標向前/向後的步距,也就是走一步的大小。

2. 指標 - 指標

先明晰一點,為什麼指標不能相加呢?其實指標可以相加,不過沒有具體的邏輯可以解釋這個操作,也就是指標相加,沒有意義.
但是相減就有具體的含義了.

int my_strlen(char* s){
	char *p = s;
	while(*p != '\0'){
		p++;
	}
	return p - s;
}

3. 指標的關係運算

for(num = &arr[i];num > &arr[0];){
	*--num = 0;
}

實際上在絕大部分編譯器上可以順利完成任務,但還是應該避免這麼寫,因為標準並不保證它可行。
標準規定:

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

指標和陣列

既然可以把陣列名當成地址存放在一個指標中,我們就可以通過指標來訪問陣列:

#include <stdio.h>
int main(){
	int arr[] = {1,2,3,4,5,6,7,8,9,0};
	int i;
	int* p = arr;
	int size = sizeof(arr)/sizeof(arr[0]);
	for(i = 0;i < size; i++){
		printf("&arr[%d] = %p <-> p + %d = %p\n",i,&arr[i],i,p+i);
	}
	return 0;
}

執行結果為:
在這裡插入圖片描述

所以:p + i其實計算的是陣列arr下標為i的地址。

二級指標

指標變數也是變數,是變數就會有地址,指標變數的地址存放之處,就是 二級指標
對於二級指標的運算有:

  • *ppa通過對ppa中的地址進行解引用,這樣找到的是pa*ppa其實訪問的就是a
int b = 20;
*ppa = &b;
//等價於 pa = &b;
  • **ppa先通過*ppa找到pa,然後對pa進行解引用操作*pa,找到的就是a.
**pa = 30;
//等價於 *pa = 30;
//等價於 a = 30;

在這裡插入圖片描述

指標陣列

指標陣列本質上是陣列.他存放的是指標
我們在之前學過整型陣列:

int arr1[5];
int arr2[6];

那指標陣列是怎樣的?

int* arr3[5];

arr3是一個數組,有5個元素,每一個元素是一個整形指標.
在這裡插入圖片描述

void*

void*:只關注”房間號“,而不關注“房間大小”。
它的特徵有:

  1. 不能解引用
  2. 不可與指標和整數相加減
  3. void*型別的兩個指標也不可相加減

我不禁要問了,void*這麼多注意事項,難道真的是有百害而無一利嗎?其實不然,它還是有它存在的意義的:
由於void*型別指標不關注“房間”大小,所以可以儲存不同型別的指標.

int a = 10;
void* p = &a;
char b = 'a';
p = &b;
//程式不會報錯,執行通過

const 修飾的指標定義

  1. const int* p
    表示的是: p指向的內容不可修改
  2. int const * p
    表示的是: p指向的內容不可修改
    (不建議這麼寫,因為int*是一個完整的型別)
  3. int* const p
    表示的是: p的指向不可修改