万字长文搞定C语言指针
目錄:
1.指針是什么?
2.定義和使用指針變量
???? ?? 定義指針變量
???? ?? 指針的初始化、賦值、取值
???? ?? 指針變量的交換
3.指針變量作為函數(shù)參數(shù)
4.通過指針引用數(shù)組
???? ?? 指針引用一維數(shù)組
???? ?? 指向一維數(shù)組的指針+1
???? ?? 指針變量作為函數(shù)的參數(shù)
???? ?? 指針指向多維數(shù)組
???? ?? 指向一維數(shù)組的指針
5.通過指針引用字符串
???? ?? 字符形指針變量
???? ?? 字符串指針變量和字符數(shù)組的比較
6.指向函數(shù)的指針
???? ?? 函數(shù)指針
???? ?? 返回指針值的函數(shù)
7.指針數(shù)組和多重指針
???? ?? 指針數(shù)組
???? ?? 指向指針變量的指針
8.動態(tài)內(nèi)存分配與指向它的指針變量
???? ?? 動態(tài)內(nèi)存分配與C語言內(nèi)存模型
???? ?? 動態(tài)內(nèi)存分配與釋放函數(shù)
前言:指針是C語言最重要的一塊知識,也是我們必須要掌握的內(nèi)容,對于初學(xué),可能很難,但是迎難而上才是我們學(xué)習(xí)必須有的態(tài)度。由于博主水平有限,如果博客中出現(xiàn)錯誤,還忘指正,博主會在第一時間修改
1.指針是什么?
在我們學(xué)習(xí)C語言的過程中難免會定義變量,如:
int n=1;對程序進(jìn)行編譯的時候會根據(jù)n的數(shù)據(jù)類型為n分配內(nèi)存,我們通過前面的學(xué)習(xí)知道,int類型的數(shù)據(jù)在內(nèi)存中占據(jù)4個字節(jié),內(nèi)存區(qū)的每一個字節(jié)都有一個編號,這個編號叫地址(這就像人的名字一樣,一個名字對應(yīng)一個人)。地址指向變量單元。實(shí)際上,計(jì)算機(jī)是通過變量名找到存儲單元的地址,對變量值的存取都是通過地址進(jìn)行的,比如上邊定義的變量n
這里我們區(qū)分幾個概念:
地址就相當(dāng)于一個旅館房間的門牌號,變量單元就相當(dāng)于房間,存放的數(shù)據(jù)就相當(dāng)于房間里邊的人。
接下來我給出指針的概念:
一個變量的地址稱為該變量的指針,如果有一個變量專門存儲另一變量的地址,則稱這個變量為指針變量
講到這里不知道你有沒有一個疑問,既然int型的變量有4個字節(jié),每個字節(jié)有一個地址,那么這個變量的指針是四個字節(jié)中的哪一個字節(jié)的地址?
答案是第一個字節(jié)。口說無憑,我們做一個實(shí)驗(yàn),我們知道一維數(shù)組的內(nèi)存空間是連續(xù)分配的,而且數(shù)組中的每一個下標(biāo)都相當(dāng)于一個變量(如arr[[0],arr[1]…),那么我們打印數(shù)組中的連續(xù)兩個下標(biāo)就可以得出結(jié)論了
2.定義和使用指針變量
2.1定義指針變量
類型名 *指針變量名
int *p;類型名代表指針指向的數(shù)據(jù)的數(shù)據(jù)類型(也稱基類型),比如上面定義的指針變量p 只能用來指向int類型的數(shù)據(jù),不能指向浮點(diǎn)型數(shù)據(jù)。*指的是定義的變量是指針類型。
2.2指針的初始化、賦值、取值
指針可以定義時初始化如:
int a=1; int *p=&a;&a就是把a(bǔ)的地址傳遞給整形指針p
注意:注意不要把變量賦給指針,要把變量的地址賦給指針
同樣的也可以定義后賦值如:
int a=1; int *p; p=&a;注意:再定義后初始化時p已經(jīng)是一個指針類型的變量不要寫成*p=&a;
當(dāng)我們想要取得指針?biāo)赶驅(qū)ο蟮闹禃r,我們需要*運(yùn)算符
int a=1; int *p1=&a; printf("%d",*p); //以上程序打印結(jié)果:1 //如果你使用printf("%d",p);是以整形的形式打印p所代表內(nèi)存中地址的編號2.3指針變量的交換
舉個例子,請想一想下面的程序應(yīng)該輸出什么:
#include<stdio.h> int main() {int a=1;int b=2;int *p1=&a;int *p2=&b;printf("%d %d\n",*p1,*p2);int *p;p=p1;p1=p2;p2=p;printf("%d %d\n",*p1,*p2);printf("%d %d\n",a,b); }在p1和p2沒交換之前在內(nèi)存中的指向:
交換之后p1和p2在內(nèi)存中的指向:
交換的只是指向,并沒有交換變量單元里邊的內(nèi)容,如果把a(bǔ)和b的值交換了,就交換的是變量單元里邊的內(nèi)容,如下:
3.指針變量作為函數(shù)參數(shù)
看一個程序:
#include<stdio.h> void swap(int *a1,int *a2) {int temp=*a1;*a1=*a2;*a2=temp; } int main() {int a=1;int b=2;int *p1=&a;int *p2=&b;printf("*p1=%d *p2=%d\n",*p1,*p2);swap(p1,p2);printf("*p1=%d *p2=%d\n",*p1,*p2); }打印結(jié)果:
emm,沒錯就是這樣的,是變量單元里內(nèi)容的交換,好像自己又行了
再看一個程序:
嗯,是這樣,地址交換,看一眼答案:
怎么會這樣??????
地址交換,值應(yīng)該變了啊,別急,首先我們要知道:
C語言里邊的實(shí)參變量和形參變量的數(shù)據(jù)傳遞是值傳遞
什么意思呢?就是形參其實(shí)是實(shí)參的一個副本,這怎么講呢,對于上面的那一段代碼下面一張圖:
p1把自己的值(表示地址)給了a1,p2把自己的值給了a2,也就是說就相當(dāng)于新定義了兩個指針變量和p1,p2指向一樣。也就是說p1與p2指向始終未變,看下圖,我們輸出指針的地址:
如上所示,那么我們就可以知道,交換的只是a1和a2的指向,與p1和p2無關(guān)。如果你第一個程序不是這么分析,那么可以再試著分析一遍
4.通過指針引用數(shù)組
4.1指針引用一維數(shù)組
我們明確兩個概念:
1.一維數(shù)組的內(nèi)存分配是連續(xù)的
2.一維數(shù)組的數(shù)組名代表首元素的地址
所以我們可以得到如下:
int a[10]; int *p1=&a[0]; //int *p1=a;本行與上一行等價4.2指向一維數(shù)組的指針+1
我們知道,指針是一個字節(jié)的地址編號,那么當(dāng)一個指針指向數(shù)組元素,指針+1是不是內(nèi)存中下一個字節(jié)的地址,來看一個程序:
很明顯,不是簡單的地址+1,而是加上一個數(shù)組元素所占字節(jié),其實(shí)不只是一維數(shù)組,任意類型的指針+1加上的都是指針基類型的字節(jié)數(shù),我們看下面一個程序:
通過指針這個性質(zhì),我們可以可以使用指針找到指針任意位置的元素,所以可以用下面的方式遍歷數(shù)組(注意:請不要隨意訪問你未申請的內(nèi)存):
4.3指針變量作為函數(shù)的參數(shù)
我們知道當(dāng)我們使用數(shù)組數(shù)組作為函數(shù)參數(shù)有:
void fun(int arr[],int len);其實(shí)程序在編譯的時候就把a(bǔ)rr[]當(dāng)成指針變量處理,所以上面一行代碼就等價于
void fun(int *arr,int len);那么以下兩個函數(shù)(fun1與fun2)也是等價的:
這里有一個注意點(diǎn):
實(shí)參數(shù)組名代表一個固定的地址,或者說是指針常量,但是形參數(shù)組名并不是一個固定的地址,而是可以看做一個指針變量
舉個例子(程序編譯運(yùn)行會報錯):
這是因?yàn)槲覀兌x的一位數(shù)組首地址是一個指針常量,我們知道常量的值是無法修改的,所以我們使用arr=arr+3;這行代碼就有問題。但是當(dāng)我們把這個指針常量傳給函數(shù)參數(shù)時,函數(shù)參數(shù)這個時候就是一個指針變量,這個時候就可以進(jìn)行類似于arr=arr+3;的操作
4.4指針指向多維數(shù)組
int a[10][10];這里我們主要弄清幾個概念:
| a | 二維數(shù)組名,指向一維數(shù)組a[0],即0行首地址 | 行首地址 |
| a[0], *(a+0), *a, &a[0][0] | 0行0列元素地址 | 元素地址 |
| a+1, &a[1] | 1行首地址 | 行首地址 |
| a[1], *(a+1) | 1行0列元素的地址,即a[1][0]的地址 | 元素地址 |
| *(a[1]+2), *(*(a+1)+2), a[1][2] | 1行2列元素的值,即a[1][2]的值 |
備注的行首地址與元素地址什么意思呢?我們看一個程序:
我們知道a是代表行首地址,*a代表元素地址,使用整形的形式打印出他們的地址發(fā)現(xiàn)是一樣的,那么你可能會問他們有什么區(qū)別,再看一個程序:
二維數(shù)組可以看做是多個一維數(shù)組為元素組成的數(shù)組,a映射到第一行(也就是第一個一維數(shù)組的首地址),*a則是映射到第一行第一列(也就是第一行第一個元素)的首地址:
第一行的首地址和第一個元素的首地址當(dāng)然是一樣的,因?yàn)榈刂肥窃氐谝粋€字節(jié)的編號,只是兩者映射的范圍不同,映射的范圍不同+1所產(chǎn)生的結(jié)果也不同(第二張圖),比如上面兩張圖我們自己也可以算算,比如a是第一行數(shù)組的首地址6683776,那么a+1代表第二行數(shù)組的首地址,他倆中間隔著是10個整形元素,一個整形元素是4個字節(jié),那么一共隔了4*10=40個字節(jié),所以根據(jù)數(shù)組中的元素在內(nèi)存中是連續(xù)分配的a+1的地址是a的地址+40,結(jié)合上面兩張圖a+1的地址是6683816印證了我們的想法。*a映射到的是元素,所以 * a+1映射到的就是下一個元素,一個整形元素四個字節(jié),所以按照正常邏輯 他倆相差四個字節(jié),上面的兩張圖,*a地址是6683776,*a+1的地址是6683780,這就印證了上面的講解。補(bǔ)充一點(diǎn):a和a[0]都是指向第一行,只是不同的表現(xiàn)形式而已
4.5指向整個一維數(shù)組的指針
int (*p)[4];以上定義p表示為一個指針變量,它指向包含四個整形元素的一維數(shù)組,注意它指向的是整個一維數(shù)組,而不是一個整形元素
int a[4];p和a是不同的,a是映射到數(shù)組第一個元素,p是映射到整個一維數(shù)組,看下面程序應(yīng)該就知道我在說什么了:
用指針數(shù)組的指針作為函數(shù)的參數(shù)時:
#include<stdio.h> void func(int (*p)[8]) {} int main() {int arr[10][8]={0};func(arr); }這個程序注意兩個細(xì)節(jié):
1.由于p映射整個一維數(shù)組,所以傳參的時候傳遞的是二維數(shù)組名
2.由于函數(shù)參數(shù)中p是指向含8個整形元素的一維數(shù)組,所以傳入的二維數(shù)組的每一個一維數(shù)組長度也是8
5.通過指針引用字符串
5.1字符形指針變量
char *str="www.baidu.com";C語言通過字符串指針變量(str)來引用字符串常量,同時字符串常量(www.baidu.com)按照字符數(shù)組處理;str映射到的是第一個字符的地址(str+1就映射到第二個字符的地址)
注意:通過字符數(shù)組或字符指針變量可以輸出一整個字符串,而對于一個數(shù)值型數(shù)組(int a[10]),是不能企圖利用數(shù)組名輸出它全部的數(shù)據(jù)的。
5.2字符串指針變量和字符數(shù)組的比較
char str1[20]="www.bilibili.com"; //字符數(shù)組只能定義的時候賦值,不能定義后使用str1[20]="www.bilibili.com"; char *str2="www.bilibili.com"; //對于指針變量來說可以定義后再賦值如:str2="123123";注意:
1.字符數(shù)組里邊的每一個元素存放字符串的一個字符,字符串指針變量存放的是字符串首個字符的地址
2.str1是指針常量,str2指針變量
3.編譯時為字符數(shù)組分配若干個存儲單元,而對字符串指針變量只分配一個存儲單元
4.字符數(shù)組中的各個元素是可以改變的,字符串指針變量指向的字符串常量是不可被改變的,但是字符串指針變量的指向是可以改變的
6.函數(shù)與指針
6.1函數(shù)指針
如果我們在程序中定義了一個函數(shù),那么編譯系統(tǒng)會為函數(shù)分配一段存儲空間,這段存儲空間的其實(shí)地址稱為函數(shù)的指針
#include<stdio.h> int max(int a,int b) {if(a>b)return a;return b; } int main() {int (*p)(int,int);//p只能指向返回值為int類型,參數(shù)也是兩個int類型的函數(shù)p=max;int a=1;int b=2;printf("%d",p(a,b)); }同一個函數(shù)指針可以先后指向同類型的不同函數(shù)
6.2返回指針值的函數(shù)
顧名思義,返回指針類型其實(shí)就是返回地址類型得函數(shù),一般得定義形式為:
類型名 *函數(shù)名(參數(shù)列表)例子:
#include<stdio.h> int *max(int *a,int *b) {if(*a>*b)return a;return b; } int main() {int a=1;int b=2;int *p=max(&a,&b);printf("%d",*p); }7.指針數(shù)組和多重指針
7.1指針數(shù)組
定義格式:
類型名 * 數(shù)組名[數(shù)組長度]例子:
再看一個程序:
str[0]存放字符串”aaaio"中第一個字符的地址,str[1]存放字符串"bbb"中第一個字符的地址。對于一個指針數(shù)組來說,注意,這里和二維數(shù)組不同的是:str不是指向第一行的地址,str里邊存放的是str[0]的地址,相當(dāng)于一個二級指針,如下(str與str[0]的地址):
str,str[0]與str[0][0]的關(guān)系就相當(dāng)于,str是門牌號1,打開門牌號1對應(yīng)的門,里邊有一個門牌號2,這個門牌號2就是str[0],再打開門牌號2的門就可以找到元素str[0][0]
一定要注意指針數(shù)組存放的是內(nèi)容是地址
這里還有一個知識點(diǎn),你可以發(fā)現(xiàn)指針str,str+1,str+2(也就是二級指針)相差八個字節(jié),那么也就是一個二級的char類型的指針占據(jù)八個字節(jié),如下:
那么是不是不同類型的指針?biāo)嫉膬?nèi)存空間大小不同,做一個實(shí)驗(yàn)(sizeof函數(shù)返回所占字節(jié)數(shù)):
結(jié)論:
在64位計(jì)算機(jī)中,不管什么類型的指針,都占據(jù)8個字節(jié)
注意:這里只是64位計(jì)算機(jī),32位計(jì)算機(jī)指針占據(jù)四個字節(jié)
7.2指向指針變量的指針
當(dāng)一個指針指向一個普通數(shù)據(jù)的時候,把這個指針稱為一級指針,指針變量既然是變量,那么肯定也是占據(jù)內(nèi)存中的,所以我們還可以用指針指向這個一級指針的的存儲空間,這時候指向一級指針的指針就叫做二級指針,同理,指向二級指針的指針稱為三級指針。
舉個例子:
a[0]相當(dāng)于存放數(shù)據(jù)元素1的地址,a[1]相當(dāng)于數(shù)據(jù)元素2的地址,a存放a[0]這個整形指針的地址,a+1相當(dāng)于存放a[1]這個整形指針的地址。a與p就相當(dāng)于兩個二級指針。
8.動態(tài)內(nèi)存分配與指向它的指針變量
8.1動態(tài)內(nèi)存分配與C語言內(nèi)存模型
我們復(fù)習(xí)幾個概念:
1.局部變量是按照動態(tài)存儲方式分配內(nèi)存(分配在動態(tài)存儲區(qū))
2.全局變量是按照靜態(tài)存儲方式分配內(nèi)存(分配在靜態(tài)存儲區(qū))
動態(tài)存儲區(qū)分為堆和棧,動態(tài)內(nèi)存分配就是分配堆中的內(nèi)存空間,因?yàn)槎阎械膬?nèi)存空間是程序員自行分配和釋放的
C語言的內(nèi)存模型分為5個區(qū):棧區(qū)、堆區(qū)、靜態(tài)區(qū)、常量區(qū)、代碼區(qū)。每個區(qū)存儲的內(nèi)容如下:
| 棧區(qū) | 存放函數(shù)的參數(shù)值、局部變量等,由編譯器自動分配和釋放,通常在函數(shù)執(zhí)行完后就釋放了,其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧 |
| 堆區(qū) | 就是通過new、malloc、realloc分配的內(nèi)存塊,編譯器不會負(fù)責(zé)它們的釋放工作,需要用程序區(qū)釋放。分配方式類似于數(shù)據(jù)結(jié)構(gòu)中的鏈表。“內(nèi)存泄漏”通常說的就是堆區(qū)。 |
| 靜態(tài)區(qū) | 全局變量和靜態(tài)變量的存儲是放在一塊的,初始化的全局變量和靜態(tài)變量在一塊區(qū)域,未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域。程序結(jié)束后,由系統(tǒng)釋放。 |
| 常量區(qū) | 常量存儲在這里,不允許修改。 |
| 代碼區(qū) | 顧名思義,存放代碼 |
8.2動態(tài)內(nèi)存分配與釋放函數(shù)
1.void *malloc(unsigned int size):作用是在動態(tài)存儲區(qū)中分配一個長度為size的連續(xù)空間,unsigned代表沒有符號位的整形數(shù)據(jù)(非負(fù)整數(shù)),返回所分配內(nèi)存區(qū)域第一個字節(jié)的地址.分配失敗返回NULL指針
2.void *calloc(unsigned n,unsigned size):作用是在動態(tài)內(nèi)存空間中分配n個長度為size的連續(xù)空間,分配失敗返回NULL指針
3.void free(void *p):釋放指針變量p所指向的動態(tài)空間
4.void *realloc(void *p,unsigned int size):對已經(jīng)通過malloc函數(shù)calloc函數(shù)獲得了動態(tài)空間,想改變其大小,用此函數(shù)重新分配
注意:void*類型的指針表示指向空類型或者不指向確定的類型的數(shù)據(jù)
以上函數(shù)得使用#include<stdlib.h>
使用舉例:
#include<stdio.h> #include<stdlib.h> int main() {int i=0;int *p=(int*)malloc(4);//(函數(shù)前的int*代表把分配的內(nèi)存轉(zhuǎn)換成int*類型)*p=3;printf("%d\n",*p);free(p); }總結(jié)
以上是生活随笔為你收集整理的万字长文搞定C语言指针的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于printf()与自增自减运算符结和
- 下一篇: 为啥地址线是20根则存储单元个数为2的2