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