成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

【建議收藏】兩萬字深度解讀 指針 ,學好指針看這一篇文章就夠了

zhkai / 1697人閱讀

摘要:在位機器上,指針變量的大小為個字節(jié)。指針類型的強制類型轉(zhuǎn)換對指針變量進行強制類型轉(zhuǎn)換的一般形式將保存的類型指針強制轉(zhuǎn)換為類型指針后賦值給,其中還是為,沒有改變。

前言

大家好,我是努力學習的少年,今天這篇文章是專門寫關于指針的知識點,因為指針內(nèi)容比較多,所以我將指針的這篇文章我將它分為兩部分,第一部分是基礎篇,是從零開始學習一些基本概念,第二部分是進階篇,如果你指針基礎學得差不多了,你可以嘗試學習進階篇的指針,這部分的內(nèi)容相對較難一些,學完這部分內(nèi)容,你的指針知識點基本就學的差不多了,最后還有指針的筆試題,這部分的題需要通過我們學到的指針的知識去筆算,這樣有利于鞏固我們的知識,并有一個更深的理解。

大綱如下:

?

目錄

前言

?指針初階

1.地址和指針????????

2.指針的定義

?3.取地址操作符:&

4.取內(nèi)容運算符

5.指針的類型

6.指向指針的指針

7.指針與數(shù)組

8.指針運算?

8.1指針與整數(shù)的加減

8.2相同類型指針的減法運算

8.3指針關系運算

8.4指針類型的強制類型轉(zhuǎn)換

9.void* 指針?

10.空指針

11.野指針

12.指針與const

12.1常量指針

?12.3指向常量的指針:

12.4指向常量的常量指針?

進階篇

1.字符指針和字符串

2.指針數(shù)組和數(shù)組指針

3.指針與多維數(shù)組

4.&數(shù)組名vs數(shù)組名

5.函數(shù)指針

6.函數(shù)指針數(shù)組

7.指向函數(shù)指針數(shù)組的指針

8.回調(diào)函數(shù)

9.qsort的使用以及它的底層原理

指針練習題及解析

訓練一

訓練二

題一

?題二

?題三

題四

?題五

題六

題七

題八


?指針初階

1.地址和指針????????

數(shù)據(jù)在程序運行過程中存儲在計算機內(nèi)存中,而內(nèi)存是以字節(jié)為基本單位的連續(xù)存儲空間,為了能夠標識內(nèi)存中不同的存儲單元,每一個存儲單元都有一個編號,這個編號就是內(nèi)存單元的的”地址“。由于內(nèi)存單元是連續(xù)的,所以內(nèi)存地址也是連續(xù)的。

指針是“指向”另外一種類型的復合類型。指針是用來存儲變量的地址,本身就是一個對象,允許對指針進行賦值和拷貝,而且指針的生命周期內(nèi)它可以先后指向不同的對象。準確的說指針就是一個變量,是用來存放地址的變量。

pa可以根據(jù)地址去找到變量x的存儲單元,這種方式為“間接訪問”。?

在內(nèi)存中,一個字節(jié)的空間大小,對應一個地址。?

2.指針的定義

指針變量的定義:
類型說名符* 變量名1,*變量名2,......;

	int a, b;//定義兩個int類型變量	int* c, * d;//定義兩個int*指針變量	int e, * f;//e為int類型變量,fint*指針變量

? 1.指針本身就是一個變量,它也有自己的地址??

? 2.定義指針變量需要在前面加一個*,但它不是變量名的組成部分,只是說明后面的變量為指針

?3.取地址操作符:&

我們知道指針后,我們還需要知道變量的地址怎么取出來?

“ & ”為取地址運算符

取地址運算符是單目運算符,其作用是返回其后的變量(包括數(shù)組元素)的地址。register存儲類型的變量是不能使用“&”返回地址。

	int i = 10;	int* pi = &i;//取出變量i的地址,為int* ,然后賦值給pa變量

對指針變量進行賦值時,要求右邊的表達式的地址地址類型與指針變量的類型相同,如果不相同編譯器會發(fā)生警告,甚至是發(fā)生錯誤。

4.取內(nèi)容運算符

取內(nèi)容運算符為“ * ”,? 當我們有一個地址后,“ * ”能夠通過該地址去訪問相應的內(nèi)存單元

* 指針表達式

指針表達式要求結果是一個“地址”,例如:

	printf("%d/n", *pi);//輸出10:*pi等價于i	*pi = 100;//通過指針變量間接訪問了i這個變量,并將i變量改為100	printf("%d/n", *pi);//輸出100

5.指針的類型

指針變量和其它內(nèi)置類型一樣,也有int*,char*,double*等類型,那么它們的類型代表的大小為多少
我們看下面的例子:

	int i = 0;	char c = "a";	double d = 1.11;	int* pi = &i;	char* pc = &c;	double* pd = &d;	printf("pi:%d  pc:%d  pd: %d", sizeof(pi),sizeof(pc),sizeof(pd));

sizeof運算符是計算變量的大小,單位為字節(jié)。

輸出結果:pi:4 ?pc:4 ?pd: 4

可見,不同類型的指針變量它們的大小都為4個字節(jié)。其實指針變量的大小與它的類型無關,只與我們的機器平臺有關。

在32位機器上,指針變量的大小為4個字節(jié)。

在64位機器上,指針變量的大小為8個字節(jié)。

那么指針變量的類型到底有什么意義呢?我們再來看這個例子:

	int i = 0x11223344;	int* pi=&i;	char* pc = (char*)(&i);//將i的指針強制轉(zhuǎn)換為(char*)	printf("%x/n", *pi);//輸出11223344	printf("%x/n", *pc);//輸出44

%x是按十六進制進行打印數(shù)據(jù),0x11223344是十六進制的整形常量,有效整數(shù)為11223344.

11223344每兩個數(shù)字為一個字節(jié),則*pi則訪問了4個字節(jié),*pc則訪問一個字節(jié)。

總結:指針的類型決定了指針能夠訪問多大的空間。如int*能夠訪問一個int類型大小的空間(4個字節(jié)),char*能夠訪問一個char類型的空間(為一個字節(jié))

也有同學有有點疑惑,為什么數(shù)據(jù)倒著存放的,這涉及到數(shù)據(jù)大小端存儲的問題。?

?那什么是數(shù)據(jù)存儲的大小端呢?

大端是高字節(jié)存放到內(nèi)存的低地址

小端是高字節(jié)存放到內(nèi)存的高地址

由于我的機器是小端存儲,所以高字節(jié)數(shù)據(jù)放在低地址處,如上述。

????????

我們再來看一個例子:

	int i = 0;	char c = "a";	int* pi = &i;	char* pc = &c;	printf("%p/n", pi );	printf("%p/n", pi + 1);	printf("%p/n", pc);	printf("%p/n", pc+1);

?%p是打印出地址的符號。

輸出:0137FB00
? ? ? ? ? ? 0137FB04
? ? ? ? ? ? 0137FAF7
? ? ? ? ? ? 0137FAF8

可以看到pi指針+1走了4個字節(jié),pc指針+1走了一個字節(jié)。

所以,指針類型決定了指針走一步的距離有多大,例如:int*指針類型+1向后走4個字節(jié)的距離,

double*指針+1向后走8個字節(jié)的距離。

6.指向指針的指針

指針是內(nèi)存中的對象,同樣指針也有地址,因此,允許把指針的地址在存放到另一個指針中。

?通過*的個數(shù)可以區(qū)別指針的級別,例如 **表示指向指針的指針,***表示指向指針的指針的指針。

int a=10;int *pa=&a;int** ppa=&pa;//ppa是指向pa的指針,為二級指針int*** pppa=&ppa;//pppa是指向ppa的指針,為三級指針

7.指針與數(shù)組

每個變量都有地址,數(shù)組中包含若干個元素,每個元素都占用內(nèi)存單元,它們都有自己相應的地址,

數(shù)組元素的指針就是數(shù)組元素的地址。

例如:

	int arr[5] = {0];	int* pa = &arr[3];//指針pa指向arr數(shù)組下標為3的元素	int* pb = arr;//指針pa指向arr數(shù)組下標為0的元素

數(shù)組名存放的是數(shù)組首元素的地址,即arr相當于&arr[0].

數(shù)組元素的訪問有兩種方式:

(1)下標法:arr[3]?或pb[3]都可以訪問到數(shù)組下標為3的元素。

(2)指針法:*(arr+3)或*(pb+3)也可以訪問到數(shù)組下標為3的元素。

arr[3]等價于?*(arr+3),pa是數(shù)組下標為3的元素,pa[1]等價于*(pa+1),

所以pa[1]是訪問到數(shù)組下標為4的元素。

例題:打印數(shù)組中所有的元素

#includeint main(){	int arr[5] = { 1,2,3,4,5 };	int sz = sizeof(arr) / sizeof(arr[0]);//計算出數(shù)組有多少個元素	for (int i = 0; i < sz; i++)	{		printf("%d ", arr[i]);	}	return 0;}

sizeof運算符能夠計算變量有多少個字節(jié)。

sizeof(arr)是計算出整個數(shù)組有多少個字節(jié),sizeof(arr[0])計算出數(shù)組中第一個個元素有多少個字節(jié)(相當于計算數(shù)組中每個元素有多少個字節(jié))

sizeof(arr)/sizeof(arr[0])計算出數(shù)組中有多少個元素。?


?

8.指針運算?

8.1指針與整數(shù)的加減

指針可以加減一個整形數(shù)據(jù)。

那么指針加減一個數(shù)據(jù)有什么意義呢?我們來看一下例子:

	int arr[5] = { 0 };	printf("arr:%p/n", arr);	printf("arr+1:%p/n", arr+1);	char str[5] = "0";	printf("str:%p/n", str);	printf("str+1:%p", str + 1);

? ?

?數(shù)組名為數(shù)組首元素的地址,如arr表示的是arr數(shù)組首元素的地址,為int* 類型,

? str表示的是str數(shù)組首元素的地址,為char*類型。

arr+1跳過1個int類型的字節(jié)數(shù)到下一個地址(跳過4個字節(jié))。

str+1跳過1個char類型的字節(jié)數(shù)到下一個地址(跳過1個字節(jié))

?假設指針有一個指針為p:

p+n=p+p指向的數(shù)據(jù)類型的字節(jié)數(shù)×n

p-n=p-p指向的數(shù)據(jù)類型的字節(jié)數(shù)×n;

其中n為整數(shù)。

8.2相同類型指針的減法運算

假設有兩個指針,一個p和q;

其中p和q為相同類型的指針表達式,相減的結果是兩個地址之間間隔的數(shù)據(jù)。

例如:

	int arr[10] = { 0 };	printf("%d/n", &arr[0] - &arr[9]);//輸出-9	printf("%d", &arr[9] - &arr[0]);//輸出9

arr數(shù)組的各個元素是連續(xù)存放的,元素arr[0]是元素arr[9]前面的第9個元素,因此arr[0]-arr[9]的結果為-9.

8.3指針關系運算

關系運算符= =和!=用于判斷兩個指針是否指向同一個內(nèi)存單元,例如有這兩個指針變量:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?int* p,int*q;

如果p==q結果為1(為真),則表明p和q指針指向同一塊內(nèi)存單元,為0(假)表示指向不同的內(nèi)存單元。

8.4指針類型的強制類型轉(zhuǎn)換

對指針變量進行強制類型轉(zhuǎn)換的一般形式:

int a=0;

int* pa=&a;

char* pc=(char*)pa;?

將pa保存的int*類型指針強制轉(zhuǎn)換為char*類型指針后賦值給pc,其中pa還是為int*,沒有改變。

9.void* 指針?

void*指針是一種特殊類型的指針,它能存放任意類型的的地址,一個void* 指針存放一個地址,這與其它類型的的指針是一樣的。但是我們不知道該指針是存放什么類型的地址,也就是說我們無法知道它指向的對象是什么類型,所以我們就無法對它指向的對象進行操作。

	int i = 0;	char c = "a";	void* pi = &i;	void* pc = &c;

10.空指針

? ? ? ? ?指針變量跟我們的內(nèi)置類型一樣,被定義出來后,如果沒有對它進行初始化,則指針變量的值使隨機的,指針變量存儲的地址時不確定的,這時它存儲的地址由可能是用戶程序內(nèi)存區(qū)的一個地址。如果直接使用?該指針區(qū)間接修改對應內(nèi)存地址中的數(shù)據(jù),會導致不可預料的錯誤,甚至導致系統(tǒng)不能正常進行。

? ? ? ? ?為了避免上訴問題的出現(xiàn),所以我們在定義指針變量時需要對指針進行初始化,使指針指向一個合法單元,

?如果指針定義出來后,如果暫時不知道它要指向哪塊空間,那么我們可以把指針賦值為0,表示該指針不指向任何

一塊空間,值為0的指針稱為”空指針“,為了提高代碼的可讀性,c語言在stdio.h這個頭文件定義了如下常量符號:

? ? ? ? ? ? ? ? #define NULL 0

所以,在c語言中,定義指針變量為空指針由以下兩種方法:

? ? ? ? ? ? ? ? ? int* pa=0;

? ? ? ? ? ? ? ? ? int* pa=NULL;

11.野指針

概念:野指針是指向的空間是不可知,如上面的指針未初始化,這個指針就是就是野指針。

訪問野指針,相當于去訪問一個本不存在的位置上本不存在的變量。所以我們需要避免野指針的產(chǎn)生。

野指針產(chǎn)生有三種方式:

?? ?int* pa;//指針未初始化,pa為野指針
?? ?int* pb = (int*)malloc(sizeof(int));
?? ?free(pb);//釋放空間后,pb沒有置成NULL,pb為野指針

?? ?int arr[5] = { 0 };
?? ?arr[5] = 10;//指針越界訪問,&arr[5]為野指針

pa指針未初始化,那么存儲的地址是隨機的,也就是說pa指向哪塊空間我們是不知道。

所以我們定義指針需要對指針初始化。?

pb是malloc的空間釋放掉,但pb指針還在,pb指針指向的內(nèi)容是已經(jīng)歸還給系統(tǒng),那么系

統(tǒng)再分配這塊空間我們是不知道的,此時的pb指針已經(jīng)沒有意義了。(malloc涉及到動態(tài)內(nèi)存開辟的知識)。

所以我們將空間free掉時,需要對相應的指針置成空指針。

pc指針是訪問數(shù)組的以外的空間,系統(tǒng)只給數(shù)組分配5個int類型大小的內(nèi)存,我們直接去訪問數(shù)組以外的?空間是我們是不知道的,所以&arr[5]是野指針,我們在使用數(shù)組時盡量避免指針越界。?

野指針的產(chǎn)生是一件很可怕的事情,它常常會使我們的程序崩潰,作為一名合格的程序員,我們需要避免野指針的產(chǎn)生。

12.指針與const

const修飾的變量則該變量中的值則不能被修改,為一個常變量,如:
?? ?const int a = 10;
?? ?a = 20;//錯誤:a是一個常變量,不能被修改

指針也是一個變量,它也可以被const修飾,const修飾指針可以分為三種:

第一種是修飾指針本身;稱為常量指針

?第二種是修飾指針指向的對象;稱為指向常量的指針

第三種是既是修飾指針本身由修飾指針指向的對象。稱為指向常量的常量指針。

12.1常量指針

常量指針是是const修飾指針,即指針本身是一個常變量,不能被修改,

它的定義方式:

類型* const 變量名

例如:int* const p;注意const在*的右邊。

const變量在定義的同時必須進行初始化,

?? ?int a = 10,b=20;?? ?int* const pa = &a;?? ?pa = &b;//錯誤:pa是常變量指針,不能被修改    *pa=b;//正確,指針指向的值可以被修改

?12.3指向常量的指針:

指向常量的指針是指const修飾指針指向的變量,即不能通過指針去修改它指向的變量。

定義方式:

const 類型* 變量名 或者 類型 const* 變量名?

注意const在*的左邊

    	const int i = 10;    const int a= 20;	int* pi = &i;//錯誤:pi是一個普通的指針,不能指向一個常變量	const int* pi1 = &i;//正確:pi1是一個指向常量的指針	*pi1 = 20;//錯誤:pi1指向的值不能修改    pi1=&a;//正確,指針本身的值可以被修改

?指向常量的指針可以指向一個非常量變量

	int a = 10;	const int* pa = &a;//正確,但是不能通過pa指針去修改a的值

12.4指向常量的常量指針?

指向常量的常量指針即指針本身不能被修改,而且指向的值即不能被修改。

定義方式:

const 類型* const 變量名 或者 類型 const* const 變量名

例如:const int* const pa;

	int a = 10;	int b = 20;	const int* const pa = &a;	*pa = 20;//錯誤:pa是指向常量的指針,即指向的值不能被修改	pa = &b;//錯誤:pa又是一個常量指針,即指針本身的值不能被修改

進階篇

1.字符指針和字符串

c語言中把字符串存放在字符數(shù)組中,通過數(shù)組名可以訪問字符串或字符符串中的某個元素。使用字符指針訪問字符串是需要把字符串的地址(第一個字符的地址)存放到字符指針變量中。

字符指針變量的初始化方式:

?? ? ? ? ? ? ? ? ? char* pc = "abcdef";
其中abcdef不是存儲到指針變量里,而是將首元素的地址存儲到pc中,此時稱字符指針指向字符串第一個元素。此時的字符串是一個字符串常量,只能讀取字符串常量中的值,不能對字符串進行修改。如果要在程序中修改字符串內(nèi)容,需要把字符串放在一個數(shù)組里面,像這樣:?

? ? ? ? ? ? ? ? ? char str[ ] = "abcdef";

用”abcdef“初始化并定義str數(shù)組中。

有這樣一道經(jīng)典題:

#include int main(){	char str1[] = "hello sjp.";	char str2[] = "hello sjp.";	char* str3 = "hello sjp.";	char* str4 = "hello sjp.";	if (str1 == str2)		printf("str1 and str2 are same/n");	else		printf("str1 and str2 are not same/n");	if (str3 == str4)		printf("str3 and str4 are same/n");	else		printf("str3 and str4 are not same/n");	return 0;}

最后輸出的是:

str1 and str2 are not same
str3 and str4 are same

2.指針數(shù)組和數(shù)組指針

? ? ? ? ? ? 指針數(shù)組和數(shù)組指針看起來沒什么區(qū)別,其實這兩個是完全不同的概念,指針數(shù)組本質(zhì)是一個數(shù)

? ?組,?是用來存放指針的數(shù)組,而數(shù)組指針本質(zhì)是指針,是指向數(shù)組的指針。這看起來還是有一點難以理解,

? ?那么我將帶大家去區(qū)分這兩個概念。????????

指針數(shù)組:一個數(shù)組存儲的元素均為指針類型型的數(shù)據(jù),稱其為指針數(shù)組。?

數(shù)組指針:指向一個數(shù)組的的指針

我們來看下它們的區(qū)別:

	int* arr[5] = { 0 };//arr是指針數(shù)組,能夠存放5個int*的指針	//pa是數(shù)組指針,存放的是一個地址,這個指針指向的是一個能夠存放5個int型的數(shù)組	int(*pa)[5]=&arr;

注意:1.*和變量名跟括號括一起的為數(shù)組指針,?如果*和變量名沒有括號括起來為指針數(shù)組,因為[ ]的優(yōu)先級比*高,所以變量名會與[ ]先結合,確認為數(shù)組,*和變量名括號括一起了,則變量名會先與*結合,確認為指針。這點對于我們區(qū)分是數(shù)組還是指針是十分重要的。

? ? ? ? ? ? 2.定義數(shù)組指針時,數(shù)組指針的類型和長度與數(shù)組的類型長度必須相同。

例子 :

int arr[5];//整形數(shù)組int* parr1[5];//指針數(shù)組,存放5個int*指針變量int(*parr2)[5];//數(shù)組指針,指向的數(shù)組是一個能夠存放5個int型的數(shù)據(jù)int(*parr3[5])[5];//指針數(shù)組,存放5個指針,且這兩個指針指向的數(shù)組能夠存放5個int型的數(shù)據(jù)

對于parr3 ,由于" [ ] " 的優(yōu)先級比” * “高,所以parr3先與“ [ ] "結合,所以parr3為數(shù)組,我們把parr3[5]去掉,則只剩下int (* )[5],所以parr3數(shù)組存儲的數(shù)據(jù)類型為int (* )[5],這個數(shù)據(jù)類型為數(shù)組指針,指針指向的數(shù)組能存儲5個int類型的數(shù)據(jù)。

3.指針與多維數(shù)組

指針變量可以指向一維數(shù)組中的元素,也可以指向多維數(shù)組中的元素。

數(shù)組名代表數(shù)組的首地址,是一個地址常量,在二維數(shù)組中這一規(guī)則同樣有效。

例如:

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int arr[3][4];

我們可以把數(shù)組arr理解成有arr[0],arr[1],arr[2]三個元素組成的一維數(shù)組,而arr[0],arr[1],arr[2]又可以理解成由4個int類型組成的一維數(shù)組。

? ? ? ? 其中arr代表的是二維數(shù)組的首元素的地址,為&arr[0],注意&arr[0]的類型不是int*,而是int* [4]類型的指針數(shù)組

則arr+1則代表的是下一個一維數(shù)組的的地址&arr[1].

? ? ? arr[0]、arr[1]、arr[3]可以認為是二維數(shù)組中每一行中的一維數(shù)組的數(shù)組名。所以它們分別代表3個一維數(shù)組的首地址。

arr[0]的值是&arr[0][0],arr[1]代表的是&arr[1][0],arr[2]的值代表的是&arr[2][0]它們的類型為int*

a[i][j]的地址有下列幾種表示方法:

? ? ? ? ? ? ? ? ? ? ?&arr[i][j];

? ? ? ? ? ? ? ? ? ? ?*(arr+i)+j;

? ? ? ? ? ? ? ? ? ? ?arr[ i ]+j;

數(shù)組名a和數(shù)組名a[0]代表的地址相同,但是它們的含義相同,數(shù)組名a為&a[i],它的類型為int* [4],為數(shù)組指針類型,數(shù)組名a[0],

為&a[0][0],它的類型為int*,為整形指針類型。

4.&數(shù)組名vs數(shù)組名

int arr[10]={0};

那么&arr跟arr有什么區(qū)別呢?

我們知道arr代表的是首元素的地址。

其實&arr是數(shù)組的地址。

它們有什么區(qū)別呢?

	int arr[5] = { 0 };	printf("arr:%p/n", arr);	printf("arr+1:%p/n", arr + 1);	printf("&arr:%p/n", &arr);	printf("&arr+1:%p/n", &arr+1);

?輸出:

我們可以看到:

? ? ? ? arr和&arr的地址相同,但arr+1和&arr+1的地址有很大的區(qū)別,arr+1與arr相差4個字節(jié),&arr+1與arr相差20個字節(jié)。

因為arr代表的首元素的地址,它的類型為int*,所以+1就跳過一個int類型。

&arr是整個數(shù)組的地址,它的類型為int* [5].為數(shù)組指針,它+1就向后走5個int類型大小的距離

arr的解引用是指向整個數(shù)組的所有元素,而int*指針解引用僅指向數(shù)組中的一個元素。

如下圖所示:

? ? 只有兩種情況數(shù)組名表示數(shù)組,其它的數(shù)組名表示首元素的地址:
? ?1.&arr表示整個數(shù)組的地址

? ?2.數(shù)組名多帶帶放在sizeof內(nèi)部,計算數(shù)組總的大小。

5.函數(shù)指針

程序定義函數(shù)后,對程序進行編譯時,編譯系統(tǒng)為函數(shù)分配一端存儲空間存儲二進制代碼,這段內(nèi)存空間的起始地址(也稱入口地址)稱為函數(shù)指針。

函數(shù)指針變量的定義:

類型說明符 (* 指針變量名)(函數(shù)的形參列表);

int Add(int x, int y){?? ?return x + y;}int (*pf)(int x , int y) = Add;//等價于int (*pf)(int, int) = Add

其中,&函數(shù)名與函數(shù)名都表示相同的意義,都表示函數(shù)的地址。

pf為函數(shù)指針變量,指向的是Add這個函數(shù)。int(* )(int,int)函數(shù)指針類型。

?實際中函數(shù)定義指針定義變量時,函數(shù)指針的形參的名字沒有實際意義,習慣上省略不寫。

上面的pf定義可以這樣寫:

int (*pf))(int,int)=Add;

?函數(shù)指針的類型中形參列表函數(shù)的形參列表相同,且返回類型與函數(shù)的返回類型相同。

void Swap(double* x, double* y){	double tmp = *x;	*x = *y;	*y = tmp;}void (*pd)(double*, double*) = Swap;

?定義函數(shù)指針pd時,函數(shù)指針的類型為形參為兩個double*,返回類型為void。

? ? 在《c陷阱和缺陷》中有這兩段代碼,讓我們嘗試去解讀它們:

//代碼1 (*(void (*)())0)();//代碼2void (*signal(int , void(*)(int)))(int);

(*(void (*)())0)();的解讀:
void(*)()表示的是一種函數(shù)指針類型,這個函數(shù)指針類型指向的是無參數(shù),且返回類型為void,(void (*)())0是將0這個整形變量強制轉(zhuǎn)換為上面的函數(shù)指針類型,0是一個地址,所以(*(void (*)())0)();表示的是調(diào)用一個0地址處的函數(shù),且這個函數(shù)沒有參數(shù),返回類型為void。
?

void (*signal(int , void(*)(int)))(int);解讀:

如果我們將signal(int , void(*)(int))提取出來后,我們發(fā)現(xiàn)signal其實一個函數(shù)聲明,且這個函數(shù)有兩個參數(shù),一個參數(shù)為int類型,另一個參數(shù)是void(*)(int)類型,返回類型為一個函數(shù)指針類型,為void(* )(int),這個函數(shù)指針,指向的是函數(shù)只有一個參數(shù)為int,返回類型為void。

我們發(fā)現(xiàn)void (*signal(int , void(*)(int)))(int)這條語句有點難以看懂,那么我們怎樣這條語句給簡化呢?

       typedef void(* pfun)(int);//給void(*)(int)這個類型取一個別名為pfun               pfun signal(int ,pfun);

給void(*)(int)這個指針函數(shù)取pfun別名后,注意這個別名必須在(*)里面,那么void (*signal(int , void(*)(int)))(int)這個代碼就可以改為?pfun signal(int ,pfun),這樣是不是容易看多了。

通過函數(shù)指針去調(diào)用函數(shù):

(*函數(shù)指針變量){實參列表}或函數(shù)指針變量{實參列表};

	int ret=(*pf)(2, 3);//通過函數(shù)指針去調(diào)用函數(shù)    //或者int ret=pf(2,3);    //(*pf)(2, 3)等價于Add(2,3)

上面的語句中調(diào)用函數(shù)指針pf指向的函數(shù),實參為2和3,返回賦值給變量c。

6.函數(shù)指針數(shù)組

函數(shù)指針是一個變量,那么變量就可以放在一個數(shù)組里。相同類型函數(shù)指針放在一個數(shù)組里,則這個數(shù)組稱為函數(shù)指針數(shù)組。

函數(shù)指針數(shù)組里元素必須為相同類型的函數(shù)指針。

定義:函數(shù)指針類型? 數(shù)組名[ ]?

int Add(int x, int y){	return x + y;}int Sub(int x, int y){	return x - y;}int(*parr[2])(int, int) = { Add,Sub };//parr為函數(shù)指針數(shù)組

我們之前說過“ [ ]"的優(yōu)先級比” * “比要高,所以parr先與[ ]結合,所以parr為函數(shù)指針數(shù)組,這個數(shù)組存儲的元素的是類型函數(shù)指針類型,為int(* )(int,int)。

例題:通過函數(shù)指針數(shù)組寫一個簡單的計算器;

int Add(int x, int y){	return x + y;}int Sub(int x, int y){	return x - y;}int Mul(int x, int y){	return x * y;}int Div(int x, int y){	return x / y;}void Menu(){	printf("#######################/n");	printf("##1.Add     2.Sub    ##/n");	printf("##3.Mul     4.Div    ##/n");	printf("##     0.exit        ##/n");	printf("#######################/n");}int main(){	int (* parr[5])(int, int) = { 0,Add,Sub,Mul,Div };//將函數(shù)指針存在parr數(shù)組里	int input = 0;	int x = 0, y = 0;	do	{		Menu();		scanf("%d", &input);		if (input == 0)		{			printf("退出成功");			break;		}		else if (input >= 1 && input <= 4)		{			printf("請輸入兩個值:/n");			scanf("%d %d", &x, &y);			int ret = arr[input](x, y);			printf("%d/n", ret);		}		else		{			printf("輸入錯誤,請重新選擇/n");		}	} while (input);	return 0;}

7.指向函數(shù)指針數(shù)組的指針

既然有函數(shù)指針數(shù)組,那么就有指向函數(shù)指針數(shù)組的指針。

指向函數(shù)指針數(shù)組的指針定義:

? ??int(*parr[2])(int, int) = { Add,Sub };//函數(shù)指針數(shù)組

?? ?int(*(*pparr)[2])(int, int) = parr;//指向函數(shù)指針數(shù)組的指針

" * " 先與pparr結合,確定pparr為指針,指向的是一個存儲函數(shù)指針類型的數(shù)組,且這個數(shù)組有兩個元素。

8.回調(diào)函數(shù)

回調(diào)函數(shù)就是一個 通過函數(shù)指針調(diào)用的函數(shù) 。如果 你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一
個函數(shù) , 當這個指針被用來調(diào)用其所指向的函數(shù)時,我們就說這是回調(diào)函數(shù) ?;卣{(diào)函數(shù)不是由該
函數(shù)的實現(xiàn)方直接調(diào)用,而是在特定的事件或條件發(fā)生時由另外的一方調(diào)用的,用于對該事件或
條件進行響應。
例如:

例題:利用回調(diào)函數(shù)去寫一個簡單的計算器;

int Add(int x, int y){	return x + y;}int Sub(int x, int y){	return x - y;}int Mul(int x, int y){	return x * y;}int Div(int x, int y){	return x / y;}void Menu(){	printf("#######################/n");	printf("##1.Add     2.Sub    ##/n");	printf("##3.Mul     4.Div    ##/n");	printf("##     0.exit        ##/n");	printf("#######################/n");}void Cal(int(* p)(int,int)){	int x = 0, y = 0;	int ret = 0;	printf("請輸入兩個數(shù):");	scanf("%d %d", &x, &y);	ret = p(x, y);	printf("%d/n", ret);}int main(){	int input = 0;	do	{		Menu();		printf("請選擇:");		scanf("%d", &input);		switch (input)		{		case 1:			Cal(Add);//Cal通過Add指針去調(diào)用Add函數(shù)			break;		case 2:			Cal(Sub);//Cal通過Sub指針去調(diào)用Add函數(shù)			break;		case 3:			Cal(Mul);//Cal通過Mul指針去調(diào)用Add函數(shù)			break;		case 4:			Cal(Div);//Cal通過Div指針去調(diào)用Add函數(shù)			break;		case 0:			printf("退出成功/n");			break;		default:			printf("選擇錯誤,請重新選擇/n");			break;		}	} while (input);	return 0;}

9.qsort的使用以及它的底層原理

在c語言中,有這樣一個qsort函數(shù),它可以排序任意類型的數(shù)組,其中它這個函數(shù)就使用了函數(shù)回調(diào)的方法。

它的參數(shù)如下:

void qsort( void *base, 	        size_t num, 			size_t width,		    int ( *compare )(const void *elem1, const void *elem2 ) );

之前說過void*可以接受任意類型的指針,為了排序任意類型的數(shù)組,所以void*指針是很有必要的。

? ? ? 其中的base是要排序的數(shù)組的首元素的指針,num是數(shù)組中有多少個元素,width是數(shù)組中元素的寬度,compare是比較函數(shù)的函數(shù)指針(你想用什么方法比較,你就自己寫一個比較函數(shù),)qosort函數(shù)會通過這個函數(shù)去調(diào)用這個compare這個函數(shù)。

那么我們來看一下qsort這個函數(shù)怎么使用:

struct person{	int age;	char ch;};//stuct person類型的數(shù)組struct person str[3] = { {20,"b"},{30,"c"},{25,"a"} };

假設我們要對str數(shù)組進行排序,那么我們有兩種方式對它排序,一種是按age比較進行排序,一種是按ch比較進行排序,這得根據(jù)我們寫的compare是對數(shù)組以什么樣的方式排序。

?例如,我們想要按age的比較的方式,則我們可以寫這樣一個compare的函數(shù):

int cmp_int(const void* e1, const void* e2){	return ((struct person*)e1)->age - ((struct person*)e2)->age;}

則我們先將e1和e2的類型強制轉(zhuǎn)換為struct person*的類型,然后將解引用找到age,再對它們進行比較,

??如果compar返回值小于0(< 0),那么p1所指向元素會被排在p2所指向元素的前面

??如果compar返回值等于0(= 0),那么p1所指向元素與p2所指向元素的順序不確定

??如果compar返回值大于0(> 0),那么p1所指向元素會被排在p2所指向元素的后面

如果我們想排一個升序(從小到大),則可以這樣寫:

return ((struct person*)e1)->age - ((struct person*)e2)->age;

如果排一個逆序(從大到小:則可以這樣寫:

return ((struct person*)e2)->age - ((struct person*)e1)->age;

那么我們將cmp_int傳給qosrt,讓它對我們進行排序,則:

	struct person str[3] = { {20,"b"},{30,"c"},{25,"a"} };	int sz = sizeof(str) / sizeof(str[0]);//計算出數(shù)字有多少個元素變量	qsort(str, sz, sizeof(str[0]), cmp_int);

?運行結果:

結果是按age從小到大排序。

若我們想要按ch的比較的方式來排序,則可以:

int cmp_char(const void* e1, const void* e2){	return ((struct person*)e1)->ch - ((struct person*)e2)->ch;}

?則運行結果為:

我們可以看到,運行結果則按ch從小到大進行排序。

好了,既然我們知道qsort怎樣使用后,那么我們用冒泡排序的思想去實現(xiàn)一個類似qsort的函數(shù),能夠排任意類型的函數(shù)。

(qsort的底層是快速排序的思想,冒泡排序的思想較容易理解)

那么什么是冒泡排序思想是什么呢?

兩兩比較,然后將最大的數(shù)放在最后一個,其次在找出第二大的數(shù),放在最后第二個........

直到排序完成。

?????????

?我們再來模擬實現(xiàn):

void Swap(char* p1, char* p2,size_t width){	for (int i = 0; i < width; i++)	{		char tmp = *p1;		*p1 = *p2;		*p2 = tmp;		p1++;		p2++;	}}void Bubble_sort(void* base, size_t num, size_t width, int cmp(const void* elem1, const void* elem2)){	for (int i = 0; i < num-1; i++)//第一趟比較	{		for (int j = 0; j < num - i-1; j++)//每一趟比較的次數(shù)		{			if (cmp((char*)base + j * width, (char*)base + (j+1)* width)>0 )			{				Swap((char*)base + j * width, (char*)base + (j+1)* width,width);			}		}	}	}

num-1是數(shù)組需要進行多少趟的比較。

例如:有一個數(shù)組的元素個數(shù)為10,那么它就需要進行9趟的比較。

num-i-1是數(shù)組每一趟比較需要進行多少次的比較。

例如:有一個數(shù)組的元素個數(shù)為10,它的第一趟比較的次數(shù)就是選出最大的數(shù)放在最后面,i是0,所以第一趟的比較次數(shù)是9次。

我們再來看這個cmp:

cmp((char*)base + j * width, (char*)base + j * width+ width)

首先,將base指針轉(zhuǎn)換為(char*)指針,因為base是void*指針,而且char*指針為最小單位指針,指針加減整數(shù)以一個字節(jié)

進行移動,width大小能夠讓指針指向下一個數(shù)據(jù)時需要走多少個字節(jié),如int類型,指向下一個數(shù)據(jù)時需要走4個字節(jié),j代表的

是位于數(shù)組下標第幾個元素,,(char*)base+j*width代表的是指向數(shù)組下標為j的元素的指針,(char*)base + (j+1)* width代表

的指向是數(shù)組下標為j+1的元素的指針。

接下來我們再看Swap:

Swap((char*)base + j * width, (char*)base + (j+1)* width,width)

? ? ?既然我們知道元素的地址,但我們要交換任意類型的數(shù)據(jù),所以我們通過一個字節(jié)一個字節(jié)的交換整個元素,所以我們就需要

元素的寬度。

通過代碼我們可以發(fā)現(xiàn),無論我們傳什么類型的元素的數(shù)組,我們都可以將它們進行排序,不過這就需要要我們寫的比較函數(shù),同時我們發(fā)現(xiàn)

void*指針,和回調(diào)函數(shù)發(fā)揮了它們應有的作用,如果沒有這兩個,則任意類型的排序就可能實現(xiàn)不了。

指針練習題及解析

訓練一

int a[] = {1,2,3,4};printf("%d/n",sizeof(a));printf("%d/n",sizeof(a+0));printf("%d/n",sizeof(*a));printf("%d/n",sizeof(a+1));printf("%d/n",sizeof(a[1]));printf("%d/n",sizeof(&a));printf("%d/n",sizeof(*&a));printf("%d/n",sizeof(&a+1));printf("%d/n",sizeof(&a[0]));printf("%d/n",sizeof(&a[0]+1));

答案:

16,數(shù)組名多帶帶放在sizeof里面是計算整個數(shù)組的大小,所以為16個字節(jié)

4/8,a+0代表的是數(shù)組首元素的地址,在32位平臺的機器下是4個字節(jié),在64位平臺下是8個字節(jié)。

4,*a代表的是數(shù)組第一個元素,為4個字節(jié)。

4/8,a+1代表的是數(shù)組第二個元素的地址。

4,a[4]代表的是數(shù)組第二個元素。

4/8,&a代表的是整個數(shù)組的地址.

16,*&a代表整個元素。

4/8,&a代表的是整個數(shù)組的地址,&a+1則跳過整個數(shù)組,是下一塊16個字節(jié)的地址

?4/8,代表的數(shù)組第一個元素的地址。

?4/8,代表數(shù)組第二個元素的地址。

//字符數(shù)組char arr[] = {"a","b","c","d","e","f"};printf("%d/n", sizeof(arr));printf("%d/n", sizeof(arr+0));printf("%d/n", sizeof(*arr));printf("%d/n", sizeof(arr[1]));printf("%d/n", sizeof(&arr));printf("%d/n", sizeof(&arr+1));printf("%d/n", sizeof(&arr[0]+1));printf("%d/n", strlen(arr));printf("%d/n", strlen(arr+0));printf("%d/n", strlen(*arr));printf("%d/n", strlen(arr[1]));printf("%d/n", strlen(&arr));printf("%d/n", strlen(&arr+1));printf("%d/n", strlen(&arr[0]+1));

答案:

6,數(shù)組名多帶帶放在sizeof里面是計算整個數(shù)組的大小

4/8,arr+0為數(shù)組首元素的地址

1,*arr代表的是數(shù)組第一個元素,char的類型為一個字節(jié)

1,a[1]代表的是數(shù)組第一個元素。

4/8,&arr代表的是數(shù)組的地址。

4/8,&arr+1代表的是跳過整個數(shù)組,指向下一塊6個字節(jié)內(nèi)存空間的地址

4/8,&arr[0]&

文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/119406.html

相關文章

  • 一篇夠了建議收藏)——超詳解sizeof與strlen的用法

    摘要:萬字詳解與的用法數(shù)組名的意義一維數(shù)組用法字符數(shù)組用法的用法字符串數(shù)組用法的用法指針與字符串用法用法二維數(shù)組數(shù)組名的意義在講所有東西之前,需要先明確一個關鍵問題數(shù)組名,這里的數(shù)組名表示整個數(shù)組,計算的是整個數(shù)組的大小,單 ...

    Taonce 評論0 收藏0
  • 糊涂算法之「八大排序」總結——用兩萬,8張動圖,450行代碼跨過排序這道坎(建議收藏

    摘要:今天,一條就帶大家徹底跨過排序算法這道坎,保姆級教程建議收藏。利用遞歸算法,對分治后的子數(shù)組進行排序?;舅枷攵雅判蚴抢枚堰@種數(shù)據(jù)結構而設計的一種排序算法,堆排序是一種選擇排序,它的最壞,最好,平均時間復雜度均為,它也是不穩(wěn)定排序。 ...

    greatwhole 評論0 收藏0
  • 【轉(zhuǎn)】成為Java頂尖程序員 ,看這10本書夠了

    摘要:實戰(zhàn)高并發(fā)程序設計這本書是目前點評推薦比較多的書,其特色是案例小,好實踐代碼有場景,實用。想要學習多線程的朋友,這本書是我大力推薦的,我的個人博客里面二十多篇的多線程博文都是基于此書,并且在這本書的基礎上進行提煉和總結而寫出來的。 學習的最好途徑就是看書,這是我自己學習并且小有了一定的積累之后的第一體會。個人認為看書有兩點好處:showImg(/img/bVr5S5);  1.能出版出...

    DTeam 評論0 收藏0
  • ??學懂C語言文件操作讀這篇夠了(萬總結,附習題)??

    目錄 ??? 一,寫在前面 二,為什么使用文件 1,原因 2,數(shù)據(jù)流 3,緩沖區(qū)(Buffer) 4,C語言中帶緩沖區(qū)的文件處理 5,文件類型 6,文件存取方式 三,什么是文件 1,程序文件 ?2,數(shù)據(jù)文件 3,文件名 四,文件的打開和關閉? 1,文件指針 ?2,文件的打開和關閉 五,文件的順序讀寫 1,功能 2,代碼實現(xiàn) 六,文件的隨機讀寫 1,fseek 2,ftell 3,rewind 七,...

    Genng 評論0 收藏0
  • Lombok 看這夠了

    摘要:注解在類上為類提供一個全參的構造方法,加了這個注解后,類中不提供默認構造方法了。這個注解用在類上,使用類中所有帶有注解的或者帶有修飾的成員變量生成對應的構造方法。 轉(zhuǎn)載請注明原創(chuàng)地址:http://www.54tianzhisheng.cn/2018/01/07/lombok/ showImg(http://ohfk1r827.bkt.clouddn.com/blog/180107/7...

    LeanCloud 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<