摘要:在位機(jī)器上,指針變量的大小為個(gè)字節(jié)。指針類型的強(qiáng)制類型轉(zhuǎn)換對(duì)指針變量進(jìn)行強(qiáng)制類型轉(zhuǎn)換的一般形式將保存的類型指針強(qiáng)制轉(zhuǎn)換為類型指針后賦值給,其中還是為,沒有改變。
大家好,我是努力學(xué)習(xí)的少年,今天這篇文章是專門寫關(guān)于指針的知識(shí)點(diǎn),因?yàn)橹羔槂?nèi)容比較多,所以我將指針的這篇文章我將它分為兩部分,第一部分是基礎(chǔ)篇,是從零開始學(xué)習(xí)一些基本概念,第二部分是進(jìn)階篇,如果你指針基礎(chǔ)學(xué)得差不多了,你可以嘗試學(xué)習(xí)進(jìn)階篇的指針,這部分的內(nèi)容相對(duì)較難一些,學(xué)完這部分內(nèi)容,你的指針知識(shí)點(diǎn)基本就學(xué)的差不多了,最后還有指針的筆試題,這部分的題需要通過我們學(xué)到的指針的知識(shí)去筆算,這樣有利于鞏固我們的知識(shí),并有一個(gè)更深的理解。
大綱如下:
?
目錄
數(shù)據(jù)在程序運(yùn)行過程中存儲(chǔ)在計(jì)算機(jī)內(nèi)存中,而內(nèi)存是以字節(jié)為基本單位的連續(xù)存儲(chǔ)空間,為了能夠標(biāo)識(shí)內(nèi)存中不同的存儲(chǔ)單元,每一個(gè)存儲(chǔ)單元都有一個(gè)編號(hào),這個(gè)編號(hào)就是內(nèi)存單元的的”地址“。由于內(nèi)存單元是連續(xù)的,所以內(nèi)存地址也是連續(xù)的。
指針是“指向”另外一種類型的復(fù)合類型。指針是用來存儲(chǔ)變量的地址,本身就是一個(gè)對(duì)象,允許對(duì)指針進(jìn)行賦值和拷貝,而且指針的生命周期內(nèi)它可以先后指向不同的對(duì)象。準(zhǔn)確的說指針就是一個(gè)變量,是用來存放地址的變量。
pa可以根據(jù)地址去找到變量x的存儲(chǔ)單元,這種方式為“間接訪問”。?
在內(nèi)存中,一個(gè)字節(jié)的空間大小,對(duì)應(yīng)一個(gè)地址。?
指針變量的定義:
類型說名符* 變量名1,*變量名2,......;
int a, b;//定義兩個(gè)int類型變量 int* c, * d;//定義兩個(gè)int*指針變量 int e, * f;//e為int類型變量,fint*指針變量
? 1.指針本身就是一個(gè)變量,它也有自己的地址??
? 2.定義指針變量需要在前面加一個(gè)*,但它不是變量名的組成部分,只是說明后面的變量為指針。
我們知道指針后,我們還需要知道變量的地址怎么取出來?
“ & ”為取地址運(yùn)算符
取地址運(yùn)算符是單目運(yùn)算符,其作用是返回其后的變量(包括數(shù)組元素)的地址。register存儲(chǔ)類型的變量是不能使用“&”返回地址。
int i = 10; int* pi = &i;//取出變量i的地址,為int* ,然后賦值給pa變量
對(duì)指針變量進(jìn)行賦值時(shí),要求右邊的表達(dá)式的地址地址類型與指針變量的類型相同,如果不相同編譯器會(huì)發(fā)生警告,甚至是發(fā)生錯(cuò)誤。
取內(nèi)容運(yùn)算符為“ * ”,? 當(dāng)我們有一個(gè)地址后,“ * ”能夠通過該地址去訪問相應(yīng)的內(nèi)存單元
* 指針表達(dá)式
指針表達(dá)式要求結(jié)果是一個(gè)“地址”,例如:
printf("%d/n", *pi);//輸出10:*pi等價(jià)于i *pi = 100;//通過指針變量間接訪問了i這個(gè)變量,并將i變量改為100 printf("%d/n", *pi);//輸出100
指針變量和其它內(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運(yùn)算符是計(jì)算變量的大小,單位為字節(jié)。
輸出結(jié)果:pi:4 ?pc:4 ?pd: 4
可見,不同類型的指針變量它們的大小都為4個(gè)字節(jié)。其實(shí)指針變量的大小與它的類型無關(guān),只與我們的機(jī)器平臺(tái)有關(guān)。
在32位機(jī)器上,指針變量的大小為4個(gè)字節(jié)。
在64位機(jī)器上,指針變量的大小為8個(gè)字節(jié)。
那么指針變量的類型到底有什么意義呢?我們?cè)賮砜催@個(gè)例子:
int i = 0x11223344; int* pi=&i; char* pc = (char*)(&i);//將i的指針強(qiáng)制轉(zhuǎn)換為(char*) printf("%x/n", *pi);//輸出11223344 printf("%x/n", *pc);//輸出44
%x是按十六進(jìn)制進(jìn)行打印數(shù)據(jù),0x11223344是十六進(jìn)制的整形常量,有效整數(shù)為11223344.
11223344每?jī)蓚€(gè)數(shù)字為一個(gè)字節(jié),則*pi則訪問了4個(gè)字節(jié),*pc則訪問一個(gè)字節(jié)。
總結(jié):指針的類型決定了指針能夠訪問多大的空間。如int*能夠訪問一個(gè)int類型大小的空間(4個(gè)字節(jié)),char*能夠訪問一個(gè)char類型的空間(為一個(gè)字節(jié))
也有同學(xué)有有點(diǎn)疑惑,為什么數(shù)據(jù)倒著存放的,這涉及到數(shù)據(jù)大小端存儲(chǔ)的問題。?
?那什么是數(shù)據(jù)存儲(chǔ)的大小端呢?
大端是高字節(jié)存放到內(nèi)存的低地址
小端是高字節(jié)存放到內(nèi)存的高地址
由于我的機(jī)器是小端存儲(chǔ),所以高字節(jié)數(shù)據(jù)放在低地址處,如上述。
????????
我們?cè)賮砜匆粋€(gè)例子:
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是打印出地址的符號(hào)。
輸出:0137FB00
? ? ? ? ? ? 0137FB04
? ? ? ? ? ? 0137FAF7
? ? ? ? ? ? 0137FAF8
可以看到pi指針+1走了4個(gè)字節(jié),pc指針+1走了一個(gè)字節(jié)。
所以,指針類型決定了指針走一步的距離有多大,例如:int*指針類型+1向后走4個(gè)字節(jié)的距離,
double*指針+1向后走8個(gè)字節(jié)的距離。
指針是內(nèi)存中的對(duì)象,同樣指針也有地址,因此,允許把指針的地址在存放到另一個(gè)指針中。
?通過*的個(gè)數(shù)可以區(qū)別指針的級(jí)別,例如 **表示指向指針的指針,***表示指向指針的指針的指針。
int a=10;int *pa=&a;int** ppa=&pa;//ppa是指向pa的指針,為二級(jí)指針int*** pppa=&ppa;//pppa是指向ppa的指針,為三級(jí)指針
每個(gè)變量都有地址,數(shù)組中包含若干個(gè)元素,每個(gè)元素都占用內(nèi)存單元,它們都有自己相應(yīng)的地址,
數(shù)組元素的指針就是數(shù)組元素的地址。
例如:
int arr[5] = {0]; int* pa = &arr[3];//指針pa指向arr數(shù)組下標(biāo)為3的元素 int* pb = arr;//指針pa指向arr數(shù)組下標(biāo)為0的元素
數(shù)組名存放的是數(shù)組首元素的地址,即arr相當(dāng)于&arr[0].
數(shù)組元素的訪問有兩種方式:
(1)下標(biāo)法:arr[3]?或pb[3]都可以訪問到數(shù)組下標(biāo)為3的元素。
(2)指針法:*(arr+3)或*(pb+3)也可以訪問到數(shù)組下標(biāo)為3的元素。
arr[3]等價(jià)于?*(arr+3),pa是數(shù)組下標(biāo)為3的元素,pa[1]等價(jià)于*(pa+1),
所以pa[1]是訪問到數(shù)組下標(biāo)為4的元素。
例題:打印數(shù)組中所有的元素
#includeint main(){ int arr[5] = { 1,2,3,4,5 }; int sz = sizeof(arr) / sizeof(arr[0]);//計(jì)算出數(shù)組有多少個(gè)元素 for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); } return 0;}
sizeof運(yùn)算符能夠計(jì)算變量有多少個(gè)字節(jié)。
sizeof(arr)是計(jì)算出整個(gè)數(shù)組有多少個(gè)字節(jié),sizeof(arr[0])計(jì)算出數(shù)組中第一個(gè)個(gè)元素有多少個(gè)字節(jié)(相當(dāng)于計(jì)算數(shù)組中每個(gè)元素有多少個(gè)字節(jié))
sizeof(arr)/sizeof(arr[0])計(jì)算出數(shù)組中有多少個(gè)元素。?
指針可以加減一個(gè)整形數(shù)據(jù)。
那么指針加減一個(gè)數(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個(gè)int類型的字節(jié)數(shù)到下一個(gè)地址(跳過4個(gè)字節(jié))。
str+1跳過1個(gè)char類型的字節(jié)數(shù)到下一個(gè)地址(跳過1個(gè)字節(jié))
?假設(shè)指針有一個(gè)指針為p:
則
p+n=p+p指向的數(shù)據(jù)類型的字節(jié)數(shù)×n
p-n=p-p指向的數(shù)據(jù)類型的字節(jié)數(shù)×n;
其中n為整數(shù)。
假設(shè)有兩個(gè)指針,一個(gè)p和q;
其中p和q為相同類型的指針表達(dá)式,相減的結(jié)果是兩個(gè)地址之間間隔的數(shù)據(jù)。
例如:
int arr[10] = { 0 }; printf("%d/n", &arr[0] - &arr[9]);//輸出-9 printf("%d", &arr[9] - &arr[0]);//輸出9
arr數(shù)組的各個(gè)元素是連續(xù)存放的,元素arr[0]是元素arr[9]前面的第9個(gè)元素,因此arr[0]-arr[9]的結(jié)果為-9.
關(guān)系運(yùn)算符= =和!=用于判斷兩個(gè)指針是否指向同一個(gè)內(nèi)存單元,例如有這兩個(gè)指針變量:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?int* p,int*q;
如果p==q結(jié)果為1(為真),則表明p和q指針指向同一塊內(nèi)存單元,為0(假)表示指向不同的內(nèi)存單元。
對(duì)指針變量進(jìn)行強(qiáng)制類型轉(zhuǎn)換的一般形式:
int a=0;
int* pa=&a;
char* pc=(char*)pa;?
將pa保存的int*類型指針強(qiáng)制轉(zhuǎn)換為char*類型指針后賦值給pc,其中pa還是為int*,沒有改變。
void*指針是一種特殊類型的指針,它能存放任意類型的的地址,一個(gè)void* 指針存放一個(gè)地址,這與其它類型的的指針是一樣的。但是我們不知道該指針是存放什么類型的地址,也就是說我們無法知道它指向的對(duì)象是什么類型,所以我們就無法對(duì)它指向的對(duì)象進(jìn)行操作。
int i = 0; char c = "a"; void* pi = &i; void* pc = &c;
? ? ? ? ?指針變量跟我們的內(nèi)置類型一樣,被定義出來后,如果沒有對(duì)它進(jìn)行初始化,則指針變量的值使隨機(jī)的,指針變量存儲(chǔ)的地址時(shí)不確定的,這時(shí)它存儲(chǔ)的地址由可能是用戶程序內(nèi)存區(qū)的一個(gè)地址。如果直接使用?該指針區(qū)間接修改對(duì)應(yīng)內(nèi)存地址中的數(shù)據(jù),會(huì)導(dǎo)致不可預(yù)料的錯(cuò)誤,甚至導(dǎo)致系統(tǒng)不能正常進(jìn)行。
? ? ? ? ?為了避免上訴問題的出現(xiàn),所以我們?cè)诙x指針變量時(shí)需要對(duì)指針進(jìn)行初始化,使指針指向一個(gè)合法單元,
?如果指針定義出來后,如果暫時(shí)不知道它要指向哪塊空間,那么我們可以把指針賦值為0,表示該指針不指向任何
一塊空間,值為0的指針稱為”空指針“,為了提高代碼的可讀性,c語言在stdio.h這個(gè)頭文件定義了如下常量符號(hào):
? ? ? ? ? ? ? ? #define NULL 0;
所以,在c語言中,定義指針變量為空指針由以下兩種方法:
? ? ? ? ? ? ? ? ? int* pa=0;
? ? ? ? ? ? ? ? ? int* pa=NULL;
概念:野指針是指向的空間是不可知,如上面的指針未初始化,這個(gè)指針就是就是野指針。
訪問野指針,相當(dāng)于去訪問一個(gè)本不存在的位置上本不存在的變量。所以我們需要避免野指針的產(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指針未初始化,那么存儲(chǔ)的地址是隨機(jī)的,也就是說pa指向哪塊空間我們是不知道。
所以我們定義指針需要對(duì)指針初始化。?
pb是malloc的空間釋放掉,但pb指針還在,pb指針指向的內(nèi)容是已經(jīng)歸還給系統(tǒng),那么系
統(tǒng)再分配這塊空間我們是不知道的,此時(shí)的pb指針已經(jīng)沒有意義了。(malloc涉及到動(dòng)態(tài)內(nèi)存開辟的知識(shí))。
所以我們將空間free掉時(shí),需要對(duì)相應(yīng)的指針置成空指針。
pc指針是訪問數(shù)組的以外的空間,系統(tǒng)只給數(shù)組分配5個(gè)int類型大小的內(nèi)存,我們直接去訪問數(shù)組以外的?空間是我們是不知道的,所以&arr[5]是野指針,我們?cè)谑褂脭?shù)組時(shí)盡量避免指針越界。?
野指針的產(chǎn)生是一件很可怕的事情,它常常會(huì)使我們的程序崩潰,作為一名合格的程序員,我們需要避免野指針的產(chǎn)生。
const修飾的變量則該變量中的值則不能被修改,為一個(gè)常變量,如:
?? ?const int a = 10;
?? ?a = 20;//錯(cuò)誤:a是一個(gè)常變量,不能被修改
指針也是一個(gè)變量,它也可以被const修飾,const修飾指針可以分為三種:
第一種是修飾指針本身;稱為常量指針
?第二種是修飾指針指向的對(duì)象;稱為指向常量的指針
第三種是既是修飾指針本身由修飾指針指向的對(duì)象。稱為指向常量的常量指針。
常量指針是是const修飾指針,即指針本身是一個(gè)常變量,不能被修改,
它的定義方式:
類型* const 變量名
例如:int* const p;注意const在*的右邊。
const變量在定義的同時(shí)必須進(jìn)行初始化,
?? ?int a = 10,b=20;?? ?int* const pa = &a;?? ?pa = &b;//錯(cuò)誤:pa是常變量指針,不能被修改 *pa=b;//正確,指針指向的值可以被修改
指向常量的指針是指const修飾指針指向的變量,即不能通過指針去修改它指向的變量。
定義方式:
const 類型* 變量名 或者 類型 const* 變量名?
注意const在*的左邊
const int i = 10; const int a= 20; int* pi = &i;//錯(cuò)誤:pi是一個(gè)普通的指針,不能指向一個(gè)常變量 const int* pi1 = &i;//正確:pi1是一個(gè)指向常量的指針 *pi1 = 20;//錯(cuò)誤:pi1指向的值不能修改 pi1=&a;//正確,指針本身的值可以被修改
?指向常量的指針可以指向一個(gè)非常量變量:
int a = 10; const int* pa = &a;//正確,但是不能通過pa指針去修改a的值
指向常量的常量指針即指針本身不能被修改,而且指向的值即不能被修改。
定義方式:
const 類型* const 變量名 或者 類型 const* const 變量名
例如:const int* const pa;
int a = 10; int b = 20; const int* const pa = &a; *pa = 20;//錯(cuò)誤:pa是指向常量的指針,即指向的值不能被修改 pa = &b;//錯(cuò)誤:pa又是一個(gè)常量指針,即指針本身的值不能被修改
c語言中把字符串存放在字符數(shù)組中,通過數(shù)組名可以訪問字符串或字符符串中的某個(gè)元素。使用字符指針訪問字符串是需要把字符串的地址(第一個(gè)字符的地址)存放到字符指針變量中。
字符指針變量的初始化方式:
?? ? ? ? ? ? ? ? ? char* pc = "abcdef";
其中abcdef不是存儲(chǔ)到指針變量里,而是將首元素的地址存儲(chǔ)到pc中,此時(shí)稱字符指針指向字符串第一個(gè)元素。此時(shí)的字符串是一個(gè)字符串常量,只能讀取字符串常量中的值,不能對(duì)字符串進(jìn)行修改。如果要在程序中修改字符串內(nèi)容,需要把字符串放在一個(gè)數(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;}
最后輸出的是:
? ? ? ? ? ? 指針數(shù)組和數(shù)組指針看起來沒什么區(qū)別,其實(shí)這兩個(gè)是完全不同的概念,指針數(shù)組本質(zhì)是一個(gè)數(shù)
? ?組,?是用來存放指針的數(shù)組,而數(shù)組指針本質(zhì)是指針,是指向數(shù)組的指針。這看起來還是有一點(diǎn)難以理解,
? ?那么我將帶大家去區(qū)分這兩個(gè)概念。????????
指針數(shù)組:一個(gè)數(shù)組存儲(chǔ)的元素均為指針類型型的數(shù)據(jù),稱其為指針數(shù)組。?
數(shù)組指針:指向一個(gè)數(shù)組的的指針。
我們來看下它們的區(qū)別:
int* arr[5] = { 0 };//arr是指針數(shù)組,能夠存放5個(gè)int*的指針 //pa是數(shù)組指針,存放的是一個(gè)地址,這個(gè)指針指向的是一個(gè)能夠存放5個(gè)int型的數(shù)組 int(*pa)[5]=&arr;
注意:1.*和變量名跟括號(hào)括一起的為數(shù)組指針,?如果*和變量名沒有括號(hào)括起來為指針數(shù)組,因?yàn)閇 ]的優(yōu)先級(jí)比*高,所以變量名會(huì)與[ ]先結(jié)合,確認(rèn)為數(shù)組,*和變量名括號(hào)括一起了,則變量名會(huì)先與*結(jié)合,確認(rèn)為指針。這點(diǎn)對(duì)于我們區(qū)分是數(shù)組還是指針是十分重要的。
? ? ? ? ? ? 2.定義數(shù)組指針時(shí),數(shù)組指針的類型和長(zhǎng)度與數(shù)組的類型長(zhǎng)度必須相同。
例子 :
int arr[5];//整形數(shù)組int* parr1[5];//指針數(shù)組,存放5個(gè)int*指針變量int(*parr2)[5];//數(shù)組指針,指向的數(shù)組是一個(gè)能夠存放5個(gè)int型的數(shù)據(jù)int(*parr3[5])[5];//指針數(shù)組,存放5個(gè)指針,且這兩個(gè)指針指向的數(shù)組能夠存放5個(gè)int型的數(shù)據(jù)
對(duì)于parr3 ,由于" [ ] " 的優(yōu)先級(jí)比” * “高,所以parr3先與“ [ ] "結(jié)合,所以parr3為數(shù)組,我們把parr3[5]去掉,則只剩下int (* )[5],所以parr3數(shù)組存儲(chǔ)的數(shù)據(jù)類型為int (* )[5],這個(gè)數(shù)據(jù)類型為數(shù)組指針,指針指向的數(shù)組能存儲(chǔ)5個(gè)int類型的數(shù)據(jù)。
指針變量可以指向一維數(shù)組中的元素,也可以指向多維數(shù)組中的元素。
數(shù)組名代表數(shù)組的首地址,是一個(gè)地址常量,在二維數(shù)組中這一規(guī)則同樣有效。
例如:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int arr[3][4];
我們可以把數(shù)組arr理解成有arr[0],arr[1],arr[2]三個(gè)元素組成的一維數(shù)組,而arr[0],arr[1],arr[2]又可以理解成由4個(gè)int類型組成的一維數(shù)組。
? ? ? ? 其中arr代表的是二維數(shù)組的首元素的地址,為&arr[0],注意&arr[0]的類型不是int*,而是int* [4]類型的指針數(shù)組。
則arr+1則代表的是下一個(gè)一維數(shù)組的的地址&arr[1].
? ? ? arr[0]、arr[1]、arr[3]可以認(rèn)為是二維數(shù)組中每一行中的一維數(shù)組的數(shù)組名。所以它們分別代表3個(gè)一維數(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*,為整形指針類型。
int arr[10]={0};
那么&arr跟arr有什么區(qū)別呢?
我們知道arr代表的是首元素的地址。
其實(shí)&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個(gè)字節(jié),&arr+1與arr相差20個(gè)字節(jié)。
因?yàn)閍rr代表的首元素的地址,它的類型為int*,所以+1就跳過一個(gè)int類型。
而&arr是整個(gè)數(shù)組的地址,它的類型為int* [5].為數(shù)組指針,它+1就向后走5個(gè)int類型大小的距離。
arr的解引用是指向整個(gè)數(shù)組的所有元素,而int*指針解引用僅指向數(shù)組中的一個(gè)元素。
如下圖所示:
? ? 只有兩種情況數(shù)組名表示數(shù)組,其它的數(shù)組名表示首元素的地址:
? ?1.&arr表示整個(gè)數(shù)組的地址
? ?2.數(shù)組名多帶帶放在sizeof內(nèi)部,計(jì)算數(shù)組總的大小。
程序定義函數(shù)后,對(duì)程序進(jìn)行編譯時(shí),編譯系統(tǒng)為函數(shù)分配一端存儲(chǔ)空間存儲(chǔ)二進(jìn)制代碼,這段內(nèi)存空間的起始地址(也稱入口地址)稱為函數(shù)指針。
函數(shù)指針變量的定義:
類型說明符 (* 指針變量名)(函數(shù)的形參列表);
int Add(int x, int y){?? ?return x + y;}int (*pf)(int x , int y) = Add;//等價(jià)于int (*pf)(int, int) = Add
其中,&函數(shù)名與函數(shù)名都表示相同的意義,都表示函數(shù)的地址。
pf為函數(shù)指針變量,指向的是Add這個(gè)函數(shù)。int(* )(int,int)函數(shù)指針類型。
?實(shí)際中函數(shù)定義指針定義變量時(shí),函數(shù)指針的形參的名字沒有實(shí)際意義,習(xí)慣上省略不寫。
上面的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í),函數(shù)指針的類型為形參為兩個(gè)double*,返回類型為void。
? ? 在《c陷阱和缺陷》中有這兩段代碼,讓我們嘗試去解讀它們:
//代碼1 (*(void (*)())0)();//代碼2void (*signal(int , void(*)(int)))(int);
(*(void (*)())0)();的解讀:
void(*)()表示的是一種函數(shù)指針類型,這個(gè)函數(shù)指針類型指向的是無參數(shù),且返回類型為void,(void (*)())0是將0這個(gè)整形變量強(qiáng)制轉(zhuǎn)換為上面的函數(shù)指針類型,0是一個(gè)地址,所以(*(void (*)())0)();表示的是調(diào)用一個(gè)0地址處的函數(shù),且這個(gè)函數(shù)沒有參數(shù),返回類型為void。
?
void (*signal(int , void(*)(int)))(int);解讀:
如果我們將signal(int , void(*)(int))提取出來后,我們發(fā)現(xiàn)signal其實(shí)一個(gè)函數(shù)聲明,且這個(gè)函數(shù)有兩個(gè)參數(shù),一個(gè)參數(shù)為int類型,另一個(gè)參數(shù)是void(*)(int)類型,返回類型為一個(gè)函數(shù)指針類型,為void(* )(int),這個(gè)函數(shù)指針,指向的是函數(shù)只有一個(gè)參數(shù)為int,返回類型為void。
我們發(fā)現(xiàn)void (*signal(int , void(*)(int)))(int)這條語句有點(diǎn)難以看懂,那么我們?cè)鯓舆@條語句給簡(jiǎn)化呢?
typedef void(* pfun)(int);//給void(*)(int)這個(gè)類型取一個(gè)別名為pfun pfun signal(int ,pfun);
給void(*)(int)這個(gè)指針函數(shù)取pfun別名后,注意這個(gè)別名必須在(*)里面,那么void (*signal(int , void(*)(int)))(int)這個(gè)代碼就可以改為?pfun signal(int ,pfun),這樣是不是容易看多了。
通過函數(shù)指針去調(diào)用函數(shù):
(*函數(shù)指針變量){實(shí)參列表}或函數(shù)指針變量{實(shí)參列表};
int ret=(*pf)(2, 3);//通過函數(shù)指針去調(diào)用函數(shù) //或者int ret=pf(2,3); //(*pf)(2, 3)等價(jià)于Add(2,3)
上面的語句中調(diào)用函數(shù)指針pf指向的函數(shù),實(shí)參為2和3,返回賦值給變量c。
函數(shù)指針是一個(gè)變量,那么變量就可以放在一個(gè)數(shù)組里。相同類型函數(shù)指針放在一個(gè)數(shù)組里,則這個(gè)數(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)先級(jí)比” * “比要高,所以parr先與[ ]結(jié)合,所以parr為函數(shù)指針數(shù)組,這個(gè)數(shù)組存儲(chǔ)的元素的是類型函數(shù)指針類型,為int(* )(int,int)。
例題:通過函數(shù)指針數(shù)組寫一個(gè)簡(jiǎn)單的計(jì)算器;
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("請(qǐng)輸入兩個(gè)值:/n"); scanf("%d %d", &x, &y); int ret = arr[input](x, y); printf("%d/n", ret); } else { printf("輸入錯(cuò)誤,請(qǐng)重新選擇/n"); } } while (input); return 0;}
既然有函數(shù)指針數(shù)組,那么就有指向函數(shù)指針數(shù)組的指針。
指向函數(shù)指針數(shù)組的指針定義:
? ??int(*parr[2])(int, int) = { Add,Sub };//函數(shù)指針數(shù)組
?? ?int(*(*pparr)[2])(int, int) = parr;//指向函數(shù)指針數(shù)組的指針
" * " 先與pparr結(jié)合,確定pparr為指針,指向的是一個(gè)存儲(chǔ)函數(shù)指針類型的數(shù)組,且這個(gè)數(shù)組有兩個(gè)元素。
例題:利用回調(diào)函數(shù)去寫一個(gè)簡(jiǎn)單的計(jì)算器;
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("請(qǐng)輸入兩個(gè)數(shù):"); scanf("%d %d", &x, &y); ret = p(x, y); printf("%d/n", ret);}int main(){ int input = 0; do { Menu(); printf("請(qǐng)選擇:"); 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("選擇錯(cuò)誤,請(qǐng)重新選擇/n"); break; } } while (input); return 0;}
在c語言中,有這樣一個(gè)qsort函數(shù),它可以排序任意類型的數(shù)組,其中它這個(gè)函數(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ù)組中有多少個(gè)元素,width是數(shù)組中元素的寬度,compare是比較函數(shù)的函數(shù)指針(你想用什么方法比較,你就自己寫一個(gè)比較函數(shù),)qosort函數(shù)會(huì)通過這個(gè)函數(shù)去調(diào)用這個(gè)compare這個(gè)函數(shù)。
那么我們來看一下qsort這個(gè)函數(shù)怎么使用:
struct person{ int age; char ch;};//stuct person類型的數(shù)組struct person str[3] = { {20,"b"},{30,"c"},{25,"a"} };
假設(shè)我們要對(duì)str數(shù)組進(jìn)行排序,那么我們有兩種方式對(duì)它排序,一種是按age比較進(jìn)行排序,一種是按ch比較進(jìn)行排序,這得根據(jù)我們寫的compare是對(duì)數(shù)組以什么樣的方式排序。
?例如,我們想要按age的比較的方式,則我們可以寫這樣一個(gè)compare的函數(shù):
int cmp_int(const void* e1, const void* e2){ return ((struct person*)e1)->age - ((struct person*)e2)->age;}
則我們先將e1和e2的類型強(qiáng)制轉(zhuǎn)換為struct person*的類型,然后將解引用找到age,再對(duì)它們進(jìn)行比較,
??如果compar返回值小于0(< 0),那么p1所指向元素會(huì)被排在p2所指向元素的前面
??如果compar返回值等于0(= 0),那么p1所指向元素與p2所指向元素的順序不確定
??如果compar返回值大于0(> 0),那么p1所指向元素會(huì)被排在p2所指向元素的后面
如果我們想排一個(gè)升序(從小到大),則可以這樣寫:
return ((struct person*)e1)->age - ((struct person*)e2)->age;
如果排一個(gè)逆序(從大到小:則可以這樣寫:
return ((struct person*)e2)->age - ((struct person*)e1)->age;
那么我們將cmp_int傳給qosrt,讓它對(duì)我們進(jìn)行排序,則:
struct person str[3] = { {20,"b"},{30,"c"},{25,"a"} }; int sz = sizeof(str) / sizeof(str[0]);//計(jì)算出數(shù)字有多少個(gè)元素變量 qsort(str, sz, sizeof(str[0]), cmp_int);
?運(yùn)行結(jié)果:
結(jié)果是按age從小到大排序。
若我們想要按ch的比較的方式來排序,則可以:
int cmp_char(const void* e1, const void* e2){ return ((struct person*)e1)->ch - ((struct person*)e2)->ch;}
?則運(yùn)行結(jié)果為:
我們可以看到,運(yùn)行結(jié)果則按ch從小到大進(jìn)行排序。
好了,既然我們知道qsort怎樣使用后,那么我們用冒泡排序的思想去實(shí)現(xiàn)一個(gè)類似qsort的函數(shù),能夠排任意類型的函數(shù)。
(qsort的底層是快速排序的思想,冒泡排序的思想較容易理解)
那么什么是冒泡排序思想是什么呢?
則兩兩比較,然后將最大的數(shù)放在最后一個(gè),其次在找出第二大的數(shù),放在最后第二個(gè)........
直到排序完成。
?????????
?我們?cè)賮砟M實(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ù)組需要進(jìn)行多少趟的比較。
例如:有一個(gè)數(shù)組的元素個(gè)數(shù)為10,那么它就需要進(jìn)行9趟的比較。
num-i-1是數(shù)組每一趟比較需要進(jìn)行多少次的比較。
例如:有一個(gè)數(shù)組的元素個(gè)數(shù)為10,它的第一趟比較的次數(shù)就是選出最大的數(shù)放在最后面,i是0,所以第一趟的比較次數(shù)是9次。
我們?cè)賮砜催@個(gè)cmp:
cmp((char*)base + j * width, (char*)base + j * width+ width)
首先,將base指針轉(zhuǎn)換為(char*)指針,因?yàn)閎ase是void*指針,而且char*指針為最小單位指針,指針加減整數(shù)以一個(gè)字節(jié)
進(jìn)行移動(dòng),width大小能夠讓指針指向下一個(gè)數(shù)據(jù)時(shí)需要走多少個(gè)字節(jié),如int類型,指向下一個(gè)數(shù)據(jù)時(shí)需要走4個(gè)字節(jié),j代表的
是位于數(shù)組下標(biāo)第幾個(gè)元素,,(char*)base+j*width代表的是指向數(shù)組下標(biāo)為j的元素的指針,(char*)base + (j+1)* width代表
的指向是數(shù)組下標(biāo)為j+1的元素的指針。
接下來我們?cè)倏碨wap:
Swap((char*)base + j * width, (char*)base + (j+1)* width,width)
? ? ?既然我們知道元素的地址,但我們要交換任意類型的數(shù)據(jù),所以我們通過一個(gè)字節(jié)一個(gè)字節(jié)的交換整個(gè)元素,所以我們就需要
元素的寬度。
通過代碼我們可以發(fā)現(xiàn),無論我們傳什么類型的元素的數(shù)組,我們都可以將它們進(jìn)行排序,不過這就需要要我們寫的比較函數(shù),同時(shí)我們發(fā)現(xiàn)
void*指針,和回調(diào)函數(shù)發(fā)揮了它們應(yīng)有的作用,如果沒有這兩個(gè),則任意類型的排序就可能實(shí)現(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里面是計(jì)算整個(gè)數(shù)組的大小,所以為16個(gè)字節(jié)
4/8,a+0代表的是數(shù)組首元素的地址,在32位平臺(tái)的機(jī)器下是4個(gè)字節(jié),在64位平臺(tái)下是8個(gè)字節(jié)。
4,*a代表的是數(shù)組第一個(gè)元素,為4個(gè)字節(jié)。
4/8,a+1代表的是數(shù)組第二個(gè)元素的地址。
4,a[4]代表的是數(shù)組第二個(gè)元素。
4/8,&a代表的是整個(gè)數(shù)組的地址.
16,*&a代表整個(gè)元素。
4/8,&a代表的是整個(gè)數(shù)組的地址,&a+1則跳過整個(gè)數(shù)組,是下一塊16個(gè)字節(jié)的地址
?4/8,代表的數(shù)組第一個(gè)元素的地址。
?4/8,代表數(shù)組第二個(gè)元素的地址。
//字符數(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里面是計(jì)算整個(gè)數(shù)組的大小
4/8,arr+0為數(shù)組首元素的地址
1,*arr代表的是數(shù)組第一個(gè)元素,char的類型為一個(gè)字節(jié)
1,a[1]代表的是數(shù)組第一個(gè)元素。
4/8,&arr代表的是數(shù)組的地址。
4/8,&arr+1代表的是跳過整個(gè)數(shù)組,指向下一塊6個(gè)字節(jié)內(nèi)存空間的地址
4/8,&arr[0]&
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/119406.html
摘要:萬字詳解與的用法數(shù)組名的意義一維數(shù)組用法字符數(shù)組用法的用法字符串?dāng)?shù)組用法的用法指針與字符串用法用法二維數(shù)組數(shù)組名的意義在講所有東西之前,需要先明確一個(gè)關(guān)鍵問題數(shù)組名,這里的數(shù)組名表示整個(gè)數(shù)組,計(jì)算的是整個(gè)數(shù)組的大小,單 ...
摘要:今天,一條就帶大家徹底跨過排序算法這道坎,保姆級(jí)教程建議收藏。利用遞歸算法,對(duì)分治后的子數(shù)組進(jìn)行排序。基本思想堆排序是利用堆這種數(shù)據(jù)結(jié)構(gòu)而設(shè)計(jì)的一種排序算法,堆排序是一種選擇排序,它的最壞,最好,平均時(shí)間復(fù)雜度均為,它也是不穩(wěn)定排序。 ...
摘要:實(shí)戰(zhàn)高并發(fā)程序設(shè)計(jì)這本書是目前點(diǎn)評(píng)推薦比較多的書,其特色是案例小,好實(shí)踐代碼有場(chǎng)景,實(shí)用。想要學(xué)習(xí)多線程的朋友,這本書是我大力推薦的,我的個(gè)人博客里面二十多篇的多線程博文都是基于此書,并且在這本書的基礎(chǔ)上進(jìn)行提煉和總結(jié)而寫出來的。 學(xué)習(xí)的最好途徑就是看書,這是我自己學(xué)習(xí)并且小有了一定的積累之后的第一體會(huì)。個(gè)人認(rèn)為看書有兩點(diǎn)好處:showImg(/img/bVr5S5); 1.能出版出...
目錄 ??? 一,寫在前面 二,為什么使用文件 1,原因 2,數(shù)據(jù)流 3,緩沖區(qū)(Buffer) 4,C語言中帶緩沖區(qū)的文件處理 5,文件類型 6,文件存取方式 三,什么是文件 1,程序文件 ?2,數(shù)據(jù)文件 3,文件名 四,文件的打開和關(guān)閉? 1,文件指針 ?2,文件的打開和關(guān)閉 五,文件的順序讀寫 1,功能 2,代碼實(shí)現(xiàn) 六,文件的隨機(jī)讀寫 1,fseek 2,ftell 3,rewind 七,...
摘要:注解在類上為類提供一個(gè)全參的構(gòu)造方法,加了這個(gè)注解后,類中不提供默認(rèn)構(gòu)造方法了。這個(gè)注解用在類上,使用類中所有帶有注解的或者帶有修飾的成員變量生成對(duì)應(yīng)的構(gòu)造方法。 轉(zhuǎn)載請(qǐng)注明原創(chuàng)地址:http://www.54tianzhisheng.cn/2018/01/07/lombok/ showImg(http://ohfk1r827.bkt.clouddn.com/blog/180107/7...
閱讀 3556·2023-04-25 20:41
閱讀 2671·2023-04-25 16:40
閱讀 1441·2021-09-23 11:44
閱讀 1260·2021-09-10 10:51
閱讀 1689·2021-09-07 09:59
閱讀 1661·2019-12-27 12:08
閱讀 561·2019-08-30 15:44
閱讀 3342·2019-08-30 11:08