把指針徹底說清楚


“就像英語一樣,經常這麼用的就約定俗成了需要背誦的短語,指針本來也是可以隨便去用(因爲指針本來就是內存上的頁碼,頁碼裏存的是段落),只不過既成的數據類型、常用的習慣以及對程序崩潰的恐懼讓我對指針始終了解不透徹。”


1.指針習慣用法

定義一個指針,一定要指向已有變量、或者malloc/new纔可以使用,不然就是野指針,無法使用!

 

C的malloc(free)。malloc就是memory allocate動態分配內存

# include <stdio.h>
# include <malloc.h>
 
void f(int * q1)
{
	*q1 = 200;//在函數中賦值
	//free(q1);  //把q所指向的內存釋放掉,不然後面在使用*p的時候會報錯,因爲p所指向的內容已經被釋放了
}
int main(void)
{
	int * p = (int *)malloc(sizeof(int)); //sizeof(int)返回值是int所佔的字節數
	*p = 10;
	f(p);  //p是int *類型
	printf("%d\n", *p);
	return 0;
}

數組:

#include <stdlib.h>
int main(void)
{
int *a;//int型指針變量
int n,i;
scanf("%d",&n);
a=malloc(sizeof(int)*n);//sizeof(int)是int型變量所佔字節數【由此可見數組就是一類變量的n次重複】
for (i=0;i<n;i++)
a[i]=i;
free(a);/*釋放malloc()分配的空間*/
return 0;
}

C++的new(delete)。

# include <iostream>
using namespace std;

void f(int * q1)
{
	*q1 = 200;
	//delete(q1);  //把q所指向的內存釋放掉,不然後面在使用*p的時候會【變成亂碼~】,因爲p所指向的內容已經被釋放了
}
int main(void)
{
	int * p = new(int); //從自由存儲分配內存,創建一個int數據類型大小的?對象?,並將已適當分類的非零指針返回到?對象?。
	*p = 10;
	f(p);  //p是int *類型
	printf("%d\n", *p);
	return 0;
}

 

動態創建二維數組de函數模板

//(4)二維數組。C + +提供了多種聲明二維數組的機制。當形式參數是 - 一個二維數組t,必須指定其第二維的大小。
//例如, a[][10]是 - 一個合法的形式參數,而a[][]則不是。爲了打破這種限制,可以使用動態分配的維數組。
//例如,下列代碼創建一一個類型爲Type的動態數組,該數組有rows行cols列。

template < class Type>
void Make2DArray(Type** &x, int rows, int col)
{
	X=new Type*[rows];  //先行
	for (int i = 0; i < rows; i + +)
		x[i] = new Type[cols];	//再列
}

//當不再需要動態分配的二維數組時,可按以下步驟釋放它所佔用的空間。
//首先釋放在for新環中方科行所分配的空間,然後釋放爲行指針分配的空間,具體實現可描述如下:

template <class Type>
void Delete2DArray(Type ** &x, int rows)
{
	for (int i = 0; i < rows; i++)
		delete[]x[i];
	delete x;
	x = 0;
}
//注意,在釋放空間後將x置爲0,
//以防止用戶繼續訪問已被釋放的空間。

 


2.指針實質、用法

整個數組地址和數組首元素地址不是同一個東西。
“內存、地址、數據”三者的關係就好像“書本、頁號、章節”之間的關係一樣
一個章節可能佔據多頁紙,我們要找到這一章,只需要知道它起始於哪一頁;
一個數據可能佔據多個字節的內存空間,我們要找到這個數據,只需要知道起始地址。
編譯報錯只是因爲賦值符號兩遍數據類型不匹配

 

指針基本概念參照前帖:

https://blog.csdn.net/sinat_27382047/article/details/70950898

指針基本用法:

取址操作符 【&】 ——指針用‘ & ’運算符創建

間接引用操作符 【*】—— ‘ * ’操作符用於間接引用指針

定義性聲明

int *p1;
p1 = &a;

引用性聲明

int *p2 = &a;

 

指針與數組:*a與a[]

比如main函數裏的參數:https://blog.csdn.net/eastmount/article/details/20413773

 

操作系統相關:堆內存、棧內存(創建數組、malloc字符)

實例化類(創建對象)

A a; 
A * a = new a(); 

1.前者在堆棧中分配內存,後者爲動態內存分配,在一般應用中是沒有什麼區別的,但動態內存分配會使對象的可控性增強。

2.不加new在堆棧中分配內存 

3.大程序用new,小程序直接申請 

4.只是把對象分配在堆棧內存中 
5.new必須delete刪除,不用new系統會自動回收內存

 


3.指針原理(彙編層面)

節選參照自:《深入理解計算機系統》

每個指針都對應一個類型(void*另說)

——指針類型不是機器代碼中的一部分,它們是C語言提供的一種抽象,幫助程序員避免尋址錯誤。

int型指針:(如果對象類型爲int,那麼指針類型就爲int*)

int *p;

void*型代表通用指針(malloc函數就是返回一個通用指針,然後通過強制類型轉換或賦值操作的隱式強制類型轉換,將它轉換成一個有類型的指針)

 

將指針從一種類型強制轉換成另一種類型,只改變它的類型

——強制類型轉換的效果是改變指針運算的伸縮。

強制類型轉換(c語言形式):

char* p;

則(強制類型轉換優先級高於加法):

(int *)p+7 計算爲p+28

(int *)(p+7) 計算爲p+7

 

數組與指針緊密聯繫

——數組引用、指針運算都需要用對象大小對偏移量進行伸縮。

int型數組指針:

數組引用:a[3]

指針運算+間接引用:*(a+3)

 

指針可以指向函數

——函數指針的值是該機器代碼表示中的第一條指令的地址。

函數指針:

int fun(int x,int *p);//函數原型

int (*fp)(int,int *);//聲明指針fp

fp=fun;//將指針fp指向函數

int y=1;

int result=fp(3,&y);//用指針調/用函數

注:如果寫成“ int *fp(int,int *) ”則會被解釋成“返回值爲int*型的函數fp(int,int *) ”

 

 

4.錯誤用法

(1)野指針

int * p;

p=(int *)123456;

但是這塊內存不允許訪問

 

(2)符號的錯誤用法

數組指針int *a[];

指針數組(int *)a[];

函數指針:https://blog.csdn.net/sinat_27382047/article/details/71941458


malloc返回的是void*指針,void*指針和int*指針是不兼容的類型,無法完成隱式的類型轉換。
int (*p)  (int *)p在一維中無影響,但是到二維數組、多層結構體之類的就有區別了。
int &p在函數傳形參時是引用,方便在於相比傳入int *p是指針【聲明,調用】f(int*)、f(*a),這個傳入的就是變量f(int)、f(a),但是在用的時候會當做指針處理。
 


 

以下摘自:https://blog.csdn.net/lpp1989/article/details/7766574

1)結構體變量作爲函數參數[實參與形參]時,形參結構體變量成員值的改變不影響對應的實參構體變量成員值的改變。

2)結構體數組或結構體指針變量作爲函數參數[實參與形參]時,形參結構體數組元素[或形參結構體指針變量指向的變量]成員值的改變將影響對應的實參構體數組[或實參結構體指針變量指向的變量]成員值的改變。

3)結構體變量可作爲函數的參數,函數可返回一結構體類數據

      4)p=&b; 使結構體指針變量p指向結構體變量b的空間。

            p->num:表示通過指針變量引用結構體變量b的成員num

5)p=a;或p=&a[0];將結構體指針變量指向結構體數組a。則:

             ①p->num:表示通過指針變量引用結構體數組元素的成員num的值。

             ②p->num++:表示通過指針變量先引用結構體數組元素的成員num的值,再使該元素的成員num的值加1,先引用其值然後其加1。

③++p->num:表示使指向的元素的成員num的值加1,再引用其值。

6)p=a;或p=&a[0];表示將結構體指針變量p指向結構體數組a。

           ①(p++)->num:表示通過指針變量先引用結構體數組元素  的成員num的值,再使指針變量本身加1,指針變量加1表示使指針變量指向結構體數組的下一個元素。

           ②(++p)->num:先使指針變量本身加1,先使使指針變量指向結構體數組的下一個元素,然後引用指針變量所指向的結構體數組元素的成員num的值。

 

閒聊:
a:=5比a=5更能體現“賦值動作”

 

雜例

PS:受到貼吧帖子的啓發 http://tieba.baidu.com/p/5868945331?pid=121842627065

此貼是關於多維數組與指針關係的。

#include <stdio.h>
int main()
{
	int a = 3, b = 2, c = 4;

	int *pp = &a;
	printf("%p \n", *pp);

	int *str[3] = { &a,&b,&c };//這是一一個指針數組。

	int **p; //建立一 個雙指針,爲什麼是雙指針呢,因爲單指針只能指向“值” , 而數組str裏的元素是指針。

	p = str;// str表示首元素的地址,就是說&str[0]  
	printf("%d\n", **p);

	int *(*p2)[3]; // 接下來我建一個“數組指針”的指針!
	p2 = &str;	//爲什麼不能把str賦給p2呢? str即是&str[0],因爲我這個是 "數組指針”的指針,
				//需要指向一整個數組,而不是數組裏的單一元素&a的地址 ,所以不能是str。
	printf("%d\n", ***p2);

	int str3[3] = { 3,2,4 };//這是一個數組。
	int *p3; //因爲數組裏面的是“值” ,所以我可以直接用單指針來表示。
	p3 = str3;//因爲我這個指針不是“數組指針”, 所以我可以直接用單一元素的地址: &str3[0]即str3去表示。
	printf("%d\n", *p3);

	int(*p4)[3]; //我建立了一 個“數組指針”
	p4 = &str3; //這個時候就不能用單一元素的地址去表示了,必須要用“整個數組”的地址去表示。
	printf("%d\n",**p4);
	//總結:數組指針就需要用整個數組的地址來表示(:3」 <)_
	return 0;

}

 

***P,雖然可以*三次,但不要誤以爲p就是三級指針
幾級指針這個概念本身並不嚴謹,只是爲交流方便才產生的這麼一個概念。

數組指針,結構體指針
只要數組或結構體裏還存在數組或指針,就很難說數組指針和結構體指針是幾級指針,這時就不能以幾級指針的概念來討論問題。
比如:
int ** arr[10];一個數組含有10個元素,元素類型爲int**
這時我們要關心的是指針本身各級解引用的內容能否合法合理的再解引用
int ** p;p =arr; 
很容易理解p是一個指向int的二級指針。
*p得到int的地址,**p得到int,p是int內容的地址的地址
int **(*p)[10]; p=&arr;
*p得到一個數組,數組類型爲int **[10]
數組可以*,**p得到數組首元素int**
int **又可以*兩次,****p是合法合理(合邏輯的)。
但一般我們不會以四級指針,來說明p
即使要用,一般也會認爲(int **** q;)以這種方式定義的指針是四級指針。

#include<iostream>
//#include<stdio.h>
using namespace std;

int main()
{
	int a = 1, b = 2, c = 3;
	int* str[3] = { &a,&b,&c };//存int*型(int型地址)的3個值
	//str[0]=&a;
	int*(*p)[3];//轉化爲int*型的 指針*p 數量爲3個
	p = &str;

	int* q = str[0];
	printf("q(&a):\t%p\n", q);//str[0]=&a,是int*型變量
	cout << typeid(q).name() << endl;
	printf("\n");

	printf("p:\t%p\n", p);//p=&{&a}(不存在)=&str[0]
	cout << typeid(p).name() << endl;
	printf("*p:\t%p\n", *p);//*p=“&&a”(不存在)=p
	cout << typeid(*p).name() << endl;

	printf("**p:\t%p\n", **p);
	cout << typeid(**p).name() << endl;
	printf("&a:\t%p\n", &a);//**P=&a
	cout << typeid(&a).name() << endl;

	printf("***p的值:\t%d\n", ***p);
	printf("a的值:\t%d\n", a);

	/*
	***p=a;
	**p=&a;
	*p=p(p是用來存a的地址的,也就是“&&a”);
	p=int*(*)[3]型的變量的地址;
	注意,此處只有p是指針,其他的都是存儲的 地址(是常量)。
	*/

	//*p = str;	printf("%d\n",**p);//表達式必須是可修改的左值,*p本身是個地址,不是指針變量

	return 0;
}

 

指針兩個方面:一個是地址值,還有一個就是地址的尋址方式(步長)
如果指針可以多級解引用,主要關心兩方面:
解引用的地址值是否合法合理(不會越界,賦值是否正確等);
地址解引用後是以什麼類型來表徵的,也就是你*一次以後,是怎麼看待得到的內容。
怎麼看待得到的內容,決定了還能否*下去。
如果*以後得到的是數組,就還可以*,而且*之前,這個內容+1或-1運算的步長,是以這個數組長度來表徵的。
如果*以後得到的是char * ,那麼+1,-1的運算,地址值改變的就是一個字節。如果是int*,地址值改變的就是四字節。
如果*以後得到的不再是用地址來表徵的對象,比如int,那麼就不能再*了,因爲,這個int的內容不是以地址來看待的。如果硬要*,一方面在語法層面會給出警告甚至錯誤。另一方面可能會導致內存讀寫錯誤。


如何看待每一級解引用的內容,這個信息映射在了指針聲明時的指針類型信息裏了。
int *p;只能*一次,而且*到的內容以int來看待。
int (*p)[5];可以*兩次,*一次得到的內容以數組來看待,這個數組的類型是:包含5個int元素的數組。*兩次,得到的內容以int來看待。

 

只要理解指針的本質,數組的本質,地址的本質。
再複雜的指針,都能馬上分析出來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章