首頁 > 軟體

C語言學習之指標知識總結

2022-07-08 14:04:46

一、地址

記憶體中的最小單元是位元組,一個位元組對應一個編號,這裡的編號就是對應位元組的地址。換句話說,地址就是記憶體單元的編號。

二、指標與指標變數

指標與指標變數是兩個不同的概念,指標是某個普通變數的地址,所以可以理解為,指標就是地址,地址就是指標。指標變數是一種變數,它的作用是儲存其它變數的地址。

#include <stdio.h>

int main()
{
	int * p; // int *是指標型別,p是對應的變數,定義的指標變數p只能用來儲存int型別變數的地址
	int i = 3, j;
	
	p = &i; // 指標變數只能用來儲存對應型別變數的地址,所以這裡需要對變數i進行取地址操作,即&i
	/*
		指標變數p儲存了變數i的地址,這樣的話,我們稱:p指向i。通俗地說,通過p可以找到i
		雖然p指向i,但變數p和變數i不是同一個變數
		更準確地說,修改其中任何一個變數p值不會影響變數i,反之同理
	*/
	
	printf("%d %dn", *p, i);
	/*
		如果一個指標變數指向某個普通變數,則在指標變數前加上*後就完全等同於指向的普通變數
		換句話說,可以通過指標變數前加*來找到那個指向的普通變數
		以本程式為例,這裡的*p就是以p的內容為地址的變數
	*/

	j = *p; // *p就是i,所以相當於將i的值賦給了j
	printf("i = %d, j = %dn", i, j); // 輸出結果為i = 3, j = 3

	return 0;
}

三、指標的作用

指標是C語言的靈魂

  • 通過指標可以表示一些複雜的資料結構,例如,連結串列、樹、圖等
  • 通過指標可以提高傳輸效率
  • 利用指標可以在被調函數中修改主調函數中的多個值
  • 利用指標可以直接存取硬體
  • 通過指標可以更方便地處理字串
  • 指標是理解物件導向語言中參照的基礎

四、初學指標時常見的錯誤

錯誤1:指標變數無明確指向

#include <stdio.h>

int main()
{
	int * p;
	int i = 5;
	
	*p = i; // 出錯行
	printf("%dn", *p);

	return 0;
}

錯誤原因:由於p未初始化,所以p的內容是垃圾值。*p是以p的內容為地址的變數,即以一個垃圾值為地址的變數,該變數是未知的,所以當把i的值賦值給p指向的未知變數時,有可能會篡改記憶體中其它變數的值,而這樣的操作是不允許的。

錯誤2:賦值時變數型別不一致

#include <stdio.h>

int main()
{
	int * p;
	int * q;
	int i = 5;
	
	p = &i;
	
	*q = p; // 出錯行
	printf("%dn", *q);

	return 0;
}

錯誤原因:由於p是指標型別變數,而*q是int型別變數,所以不能相互複製。

五、通過呼叫函數修改主調函數中的值

思考1:在下述程式中,f函數中的變數i與main函數中的變數i是不是同一個變數?

f函數中的變數i與main函數中的變數i都屬於區域性變數,只在自己對應的函數中起作用,所以,f函數中的變數i與main函數中的變數i不是同一個變數。

#include <stdio.h>

void f(int i)
{
	i = 99;
}

int main()
{
	int i = 66
	printf("%dn", i);
	
	f(i);
	printf("%dn", i);

	return 0;
}

思考2:在上述程式中,能否通過呼叫f函數修改main函數中變數i的值?

由於f函數中的變數i與main函數中的變數i不是同一個變數,所以把實參i傳遞給形參i只會改變f函數中變數i的值,當f函數執行完畢後,分配給形參i的空間會被釋放,故而無法改變main函數中變數i的值。換句話說,f函數中的變數i與main函數中的變數i本質上沒有任何關係,所以不管怎麼修改f函數中變數i的值都不會影響main函數中變數i的值。

那要如何才能通過其它函數來修改主調函數中的值?

此時,指標就派上用場了,如下述程式:

#include <stdio.h>

void f(int * p) // 通過接收地址來確定要修改的變數
{
	*p = 99; // *p就是以p變數的內容為地址的變數,也就是要通過該函數修改的變數
}

int main()
{
	int i = 66
	printf("%dn", i); // 輸出結果為66
	
	f(&i); // 由於函數f的形參是指標變數,故需將變數i的地址傳送過去
	printf("%dn", i); // 輸出結果為99

	return 0;
}

上述程式可以實現在被調函數中修改主調函數中變數的值是因為通過向被調函數傳遞了需要修改的變數的地址,從而確定並指向了需要修改的變數,但如果不傳入地址,就會導致主調函數中的變數無法與被調函數產生關聯,從而無法實現目的。

活學活用:自定義一個swap函數,用該函數互換main函數中的兩個變數的值

常見錯誤:只傳入數值,不傳入地址

void swap(int a, int b)
{
	int t;
	
	t = a;
	a = b;
	b = t;
}

int main()
{	
	int a = 3, b = 5;
	
	swap(a, b);
	
	printf("a = %d, b = %dn", a, b);

	return 0;
}

出現上述錯誤的原因是,main函數中的變數a和b與swap函數中的形參a和b無關,導致的結果是,主函數將3和5傳送給形參a和b後,swap函數只是對3和5進行了操作,而未能對main函數中的變數a和b進行操作,所以無法互換main函數中的變數a和b的值。

正確實現方法:傳入地址,定位需要互換的變數

void swap(int * p, int * q)
{
	int t;
	
	t = *p;
	*p = *q;
	*q = t;
}

int main()
{	
	int a = 3, b = 5;
	
	swap(&a, &b);
	
	printf("a = %d, b = %dn", a, b);

	return 0;
}

思考:如下方法是否可以實現互換功能?

#include <stdio.h>

void swap(int * p, int * q)
{
	int * t;

	t = p;
	p = q;
	q = t;
}

int main()
{	
	int a = 3, b = 5;
	
	swap(&a, &b);
	
	printf("a = %d, b = %dn", a, b);

	return 0;
}

答案是不行的,上述程式將變數a和b的地址傳送給了指標變數p和q,此時,變數p和q中儲存的是變數a和b的地址,然而,swap函數中的操作是互換變數p和q的內容,也就是說,當swap函數執行完畢後,變數p中儲存的是變數b的地址變數q中儲存的是變數a的地址,言下之意,只是將變數p和q的內容互換了而已,並沒有對main函數中的變數a和b進行操作,所以無法實現互換功能,此外,幾乎所有的程式語言都無法通過互換兩個變數的地址實現互換變數中的值。

六、指標與一維陣列

一維陣列的陣列名

一維陣列的陣列名是一個指標常數,該常數是一維陣列中第一個元素的地址。

#include <stdio.h>

int main()
{
	int a[5];
	int b[5];
	
	a = b; // 錯誤,因為a和b都是常數,所以無法進行賦值操作
	a = &a[2]; // 錯誤,因為a是常數,無法對一個常數進行賦值操作
	
	return 0;
}
#include <stdio.h>

int main()
{
	int a[5];

	printf("%#X", &a[0]);
	printf("%#X", a); // 與上一行輸出結果相同,因為一維陣列名就是數一維組中第一個元素的地址

	return 0;
}

參照一維陣列中的元素

通過下標參照:如a[i]表示第i+1個元素

通過指標參照:如*(a+i)表示第i+1個元素

#include <stdio.h>

int main()
{
	int a[5];
	int i;
	
	for (i = 0; i < 5; i++) // 向一維陣列中讀入元素
		scanf("%d", &a[i]); // 通過下標參照陣列中的元素

	for (i = 0; i < 5; i++) // 輸出一維陣列的內容
		printf("%d ", *(a + i)); // 通過指標參照陣列中的元素

	return 0;
}

指標變數的運算

指標變數不能相加,相乘以及相除,只能進行相減。如果兩個指標變數指向同一塊連續空間的不同儲存單元,則這兩個指標變數才可以進行相減運算。兩個指標變數相減得到的結果是兩個指標變數間相隔的元素個數。

#include <stdio.h>

int main()
{
	int a[5];
	int * p = &a[1];
	int * q = &a[4];

	printf("%dn", q - p); // 輸出結果為3,證明相隔3個元素

	return 0;
}

七、使用函數操作一維陣列

使用函數對一維陣列進行操作,首先要將陣列名傳遞給函數,因為一維陣列名是函數第一個元素的地址,傳遞陣列名就相當於傳遞起始位置,其次,普通陣列不同於字元陣列,它們沒有結束的標誌,所以還需要向函數傳遞陣列長度以確定陣列何時結束。故想要在另外一個函數中對一維陣列進行操作需要向該函數傳入兩個引數,陣列名和陣列長度。

定義函數時的形參有兩種寫法,第一種是(int a[], int length),第二種是(int * a, int length)。可以寫第二種的原因是一位陣列名本身就是指標常數,所以可以直接用指標變數來接收。

定義一個函數,該函數的功能是對一維陣列的內容進行輸出

#include <stdio.h>

// 自定義的print函數,其功能是將一維陣列輸出
void print(int a[], int length)
{
	int i;

	for (i = 0; i < length; i++)
		printf("%d ", a[i]); // 也可以寫成printf("%d ", *(a+i));
	
	printf("n");
}

int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int b[6] = { -1, -2, -3, -4, -5, -6 };
	int c[100] = { 23, 88, 99, 44 };

	print(a, 5);
	print(b, 6);
	print(c, 100);

	return 0;
}

八、指標變數所佔位元組數

預備知識:sizeof運運算元的用法

sizeof(資料型別):其值為對應資料型別所佔的位元組數

例如:sizeof(int)值為4;sizeof(double)值為8;sizeof(char)值為1

sizeof(變數):其值為對應變數所佔的位元組數

#include <stdio.h>

int main()
{
	char c = 'A';
	int i = 99;
	double x = 66.66;

	char * p = &ch;
	int * q = &i;
	double r = &x;

	printf("%d %d %dn", sizeof(c), sizeof(i), sizeof(x)); // 輸出結果為1 4 8
	printf("%d %d %dn", sizeof(p), sizeof(q), sizeof(r)); // 輸出結果均為4

	return 0;
}

上述程式證明,儘管普通型別變數所佔的空間大小不一致,但它們對應的指標變數都佔四個位元組。

九、靜態陣列的缺陷

陣列長度必須事先指定,且只能是常整數,不能是變數

陣列的長度在長度不能在函數執行過程中動態的增減,陣列一旦定義,其長度就無法改變

程式設計師無法手動釋放靜態陣列的記憶體,陣列一旦定義,作業系統為該陣列分配的儲存空間就會一直存在,直到該陣列所在的函數執行完畢後,該陣列的空間才會被作業系統釋放

在某個函數內定義的靜態陣列在該函數執行期間可以被其它函數使用,但當該函數執行完畢後,該函數中定義的陣列就無法在其它函數中使用,這是因為該函數執行完畢後,靜態陣列的記憶體就被會被釋放

十、malloc函數

malloc這個詞是由memory(記憶體)與allocate(分配)這兩個單詞合成的,顧名思義,malloc函數就是用來分配記憶體的函數。

#include <stdio.h>
#include <malloc.h>

int main()
{
	int i; // 靜態分配了4個位元組的儲存空間

	int* p = (int*)malloc(4);
	/*
		1.要使用malloc函數,需新增malloc.h標頭檔案
		2.malloc函數只有一個形參,其型別為整型
		3.實參中的4表示請求作業系統為本程式分配4個位元組的動態儲存空間
		4.malloc的返回值是分配給該系程式的第一個位元組的地址
		5.由於第一個位元組的地址不能確定具體的變數型別,所以需要強制型別轉換
		6.第7行程式碼一共分配了8個位元組的儲存空間,其中變數p佔4個位元組,p指向的空間也佔是4個位元組
		7.變數p所佔的記憶體是靜態分配的,p所指向的記憶體是動態分配的
	*/

	free(p); 
	/*
		free(p)表示釋放p所指向的記憶體
		free函數只能用來釋放動態記憶體,不能用來釋放靜態記憶體,靜態記憶體只能由作業系統自動釋放
		free(p)只是釋放了p對應的記憶體空間,但p的內容依舊存在
	*/

	return 0;
}

十一、動態陣列的構造

#include <stdio.h>
#include <malloc.h>

int main()
{
	int a[10]; // 靜態構造一維陣列
	int length;
	int* pArray;
	int i;
	
	scanf("%d", &length);

	pArray = (int*)malloc(sizeof(int) * length);
	/*
		動態構造一維陣列
		該動態陣列陣列名為pArray,陣列長度為length,陣列中每個元素都是int型別
	*/

	// 對該動態一維陣列手動賦值
	for (i = 0; i < length; i++)
		scanf("%d", &pArray[i]);

	// 輸出該動態一維陣列的內容
	for (i = 0; i < length; i++)
		printf("%d ", *(pArray + i));
	
	printf("n");

	free(pArray); // 釋放該動態陣列

	return 0;
}

十二、靜態記憶體與動態記憶體的對比

靜態記憶體是由作業系統自動分配,自動釋放的。靜態記憶體是在棧中分配的;動態記憶體是由程式設計師手動分配,手動釋放,但如果只分配不釋放就會導致記憶體洩露,也就是記憶體越用越少。動態記憶體是在堆中分配的。

十三、多級指標

#include <stdio.h>

int main()
{
	int i = 10;
	int * p = &i;
	int ** q = &p;
	int *** r = &q;
	
	r = &p; // 錯誤,因為r是int *** 型別,它只能用來儲存int ** 型別變數的地址
	
	printf("i = %dn", ***r);

	return 0;
}

表解上述程式:

變數名變數地址變數內容
i1000H10
p2000H1000H
q3000H2000H
r4000H3000h
變數名對應變數
*rq
**rp
***ri
#include <stdio.h>

void g(int ** q) // 由於p的型別是int *,所以q的型別必須是int **,因為q要用來存放p的地址
{
	
}

void f()
{
	int i;
	int * p;
	
	p = &i;
	
	g(&p); // 要通過g函數修改p的內容,則必須傳送p變數的地址
}

int main()
{
	f();

	return 0;
}

十四、跨函數使用記憶體

由於靜態記憶體是在棧中分配的,而函數執行完畢後,棧中的靜態記憶體就會全部出棧,而動態記憶體是在堆中分配的,當函數執行完畢後,堆中分配的記憶體並不會像棧中分配的記憶體一樣直接被釋放掉,所以

靜態記憶體是不能跨函數使用的,而動態記憶體是可以的。

思考:下述程式是否有語法錯誤?是否有邏輯錯誤?

#include <stdio.h>

void f(int** q)
{
	int i = 5;

	*q = &i; // 由於q儲存了p的地址,所以*q就是p,這行程式碼實質是將i的地址賦給了p
}

int main()
{
	int* p;

	f(&p);

	printf("i = %dn", *p); // 由於p儲存了i的地址,所以*p就是i

	return 0;
}

上述程式沒有語法錯誤,但是有邏輯上的錯誤,這是因為,當f函數執行完畢後,f函數中所有的靜態變數的記憶體都會被釋放掉,所以當執行到printf("i = %dn", *p);時,p所指向的變數空間的存取許可權已經返還給了作業系統,這樣就會導致*p存取了不屬於該程式的空間。這個程式說明了在一個函數內部定義的靜態變數在該函數中執行完畢後就不再可以垮函數使用。

思考:對比上一程式,下述程式是否有語法錯誤?是否有邏輯錯誤?

#include <stdio.h>

void f(int** q)
{
	*q = (int*)malloc(sizeof(int));
	
	**q = 5;
}

int main()
{
	int* p;

	f(&p);

	printf("%dn", *p);

	return 0;
}

上述程式是完全沒有語法錯誤的,因為當f函數執行完畢後,其中分配的動態記憶體不會自動釋放,所以在main函數中依然可以使用這段記憶體。這個程式體現出,在一個函數中分配的動態儲存空間在該函數執行完之後,仍然可以在另外一個函數中使用。

趁熱打鐵:下列四個程式中,哪個程式能夠通過呼叫fun函數使main函數中的指標變數p指向一個合法的整型單元?

#include <stdio.h>

void fun(int * p)
{
	int s;
	p = &s;
}

int main()
{
	int * p;

	fun(p);

	return 0;
}
#include <stdio.h>

void fun(int ** p)
{
	int s;
	*p = &s;
}

int main()
{
	int * p;

	fun(&p);

	return 0;
}
#include <stdio.h>
#include <malloc.h>

void fun(int * p)
{
	p = (int *)malloc(sizeof(int));
}

int main()
{
	int * p;

	fun(p);

	return 0;
}
#include <stdio.h>
#include <malloc.h>

void fun(int ** p)
{
	*p = (int *)malloc(4);
}

int main()
{
	int * p;

	fun(&p);

	return 0;
}

以上就是C語言學習之指標知識總結的詳細內容,更多關於C語言指標的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com