C語言學(xué)習(xí)之指針的使用詳解
一、指針概念
在學(xué)習(xí)指針之前我們先要了解一下內(nèi)存,內(nèi)存是存儲區(qū)域,我們可以把內(nèi)存劃分成一個一個的內(nèi)存單元,最小的內(nèi)存單元是字節(jié)
計算機將數(shù)據(jù)存儲在內(nèi)存中,而為了能夠快速查找到所需數(shù)據(jù),計算機會將內(nèi)存進行編號,通過查找編號,可以快速查找到數(shù)據(jù)。指針是內(nèi)存中一個最小單元的編號,內(nèi)存單元的編號也被稱為地址,指針就是地址。
平時我們說的指針通常指的是指針變量,是用來存放內(nèi)存地址的變量
1個字節(jié)(byte)=8個bit(bit即比特位,由0和1組成)
在32位的機器上(X86環(huán)境),地址是32個0或者1組成二進制序列,那地址就得用4個字節(jié)的空間來存儲,所以一個指針變量的大小就應(yīng)該是4個字節(jié)。在64位的機器上(X64環(huán)境),地址是64個0或者1組成二進制序列,那地址就得用8個字節(jié)的空間來存儲,所以一個指針變量的大小是8個字節(jié)。
無論何種數(shù)據(jù)類型的指針,指針的大小在32位(X86環(huán)境)平臺下是4個字節(jié),在64位(X64環(huán)境)平臺下是8個字節(jié)。
1.指針變量
我們可以通過&(取地址操作符)取出變量的內(nèi)存起始地址,把地址可以存放到一個變量中,這個變量就是指針變量。
*(解引用操作符又叫間接訪問操作符):對指針變量進行解引用操作,通過指針變量里面的地址找到指向的內(nèi)容。
#include <stdio.h> int main() { int a = 20; char ch; int* pa = &a;//a向內(nèi)存申請了4個字節(jié)的空間,所以&a的時候應(yīng)該拿出第1個字節(jié)的地址 //拿出a的第1個字節(jié)的地址放在整型指針變量pa里 char* pc = &ch;//拿出ch的地址放在字符指針變量pc里 printf("%p\n",&a);//%p打印的是地址 //printf("%p\n",pa);打印出來的地址和&a打印出來的一樣 printf("%p\n",&ch); return 0; }
指針變量是用來存放地址的變量,存放在指針變量中的值都被當(dāng)成地址處理,同時地址是唯一標識一個內(nèi)存單元的
2.指針類型
指針的類型根據(jù)你所需要的數(shù)據(jù)類型進行使用,如
- //
char* pc
;指針類型是char* - //
char** pcc
;指針類型是char** - //
int* pa
;指針類型是int* - //
double* pd
;指針類型是double* - // …
但是要注意區(qū)分指針指向的類型和指針類型之間的區(qū)別
- //
char* pc
;其中*
指pc是指針,指向的類型是char - //
char** pcc
;其中*
指pcc是指針,指向的類型是char* - //
int* pa
;*
指pa是指針,指向的類型是int - //
double* pd
;*
指pd是指針,指針指向的類型是double - // …
指針類型決定了指針進行解引用操作時訪問幾個字節(jié)
char* 類型的指針解引用訪問1個字節(jié);
int* 類型的指針解引用訪問4個字節(jié);
double* 類型的指針解引用訪問8個字節(jié);
例1:
#include <stdio.h> int main() { int a = 0x11223344;//用16進制表示 int* pa = &a; *pa = 0; return 0; }
上述代碼在VS(小端存儲)調(diào)試過程中,&a在內(nèi)存中顯示的是
執(zhí)行完*pa=0;
后,&a在內(nèi)存中變?yōu)?/p>
將例1中的指針類型改為char* 類型,用char* 類型的指針接收a的地址
int* 和 char*類型的指針都能將int類型的a存放,因為在32位平臺下,指針類型的大小都是4個字節(jié)。
#include <stdio.h> int main() { int a = 0x11223344;//用16進制表示 char* pc = &a; *pc = 0; return 0; }
上述代碼在VS(小端存儲)調(diào)試過程中,&a在內(nèi)存中顯示的是
執(zhí)行完*pc=0;
語句后,內(nèi)存變化為
通過上述兩個代碼塊執(zhí)行后內(nèi)存中的變化可以驗證指針類型決定了指針進行解引用操作時訪問幾個字節(jié)
指針類型決定了指針的步長,指針加減整數(shù)的時候向前或者向后走一步走多大距離
(char*)+1------跳過一個char類型的大小,也就是向后走1個字節(jié);
(int*)+1------跳過一個int類型的大小,也就是向后走4個字節(jié);
(double*)+1------跳過一個double類型的大小,也就是向后走8個字節(jié);
#include <stdio.h> int main() { int a = 0x11223344; int* pa = &a; char* pc = &a; printf("%p\n",pa); printf("%p\n",pc); printf("%p\n",pa+1); printf("%p\n",pc+1); return 0; }
上述代碼的運行結(jié)果為
說明指針加減整數(shù)時的步長和指針指向的類型有關(guān)
練習(xí)1:
我們可以通過一個練習(xí)對其有更好的理解:將數(shù)組arr中每個元素賦值為對應(yīng)下標,arr[0]為0;arr[1]為1……
#include <stdio.h> int main() { int arr[10] = { 0 }; int* p = arr;//p指向arr數(shù)組的首元素地址 int i = 0; for(i = 0;i < 10; i++) { *(p++) = i;//將數(shù)組元素賦值; //后置++,對p進行解引用,賦值之后,再向后走int類型的大小,即4個字節(jié),依次循環(huán) } for(i = 0;i < 10; i++) { printf("%d ",arr[i]); } return 0; }
運行結(jié)果如圖
3.二級指針
#include <stdio.h> int main() { int a = 10; int* p = &a; int** pp = &p;//pp就是二級指針 *p = 20;//可以將a修改成20 **pp = 30;//可以將a修改成30 printf("%d\n",a); return 0; }
二、野指針
野指針就是指針指向的位置是不可知的,(隨機的、不正確的、沒有明確限制的)
1.野指針成因
1.指針未初始化
#include <stdio.h> int main() { int* p;//p沒有初始化,p就是野指針 *p = 20;//賦值 return 0; }
2.指針越界訪問
#include <stdio.h> int main() { int arr[10] = { 0 }; int i = 0; int sz = sizeof(arr)/sizeof(arr[0]);//計算arr數(shù)組中元素個數(shù) int* p = arr; for(i = 0;i <= sz; i++)//共循環(huán)11次 { *p++ = i; } return 0; }
當(dāng)p越過arr已有空間去越界訪問的時候就是野指針了,之前不是野指針。
當(dāng)指針指向的范圍超出數(shù)組arr的范圍時,p就是野指針
3.指針指向的空間釋放
#include <stdio.h> int* test() { int num = 100;//num是局部變量,進函數(shù)創(chuàng)建,函數(shù)結(jié)束時銷毀 return #//返回的是??臻g的地址 } int main() { int* p = test();//接收到地址,但是地址并不指向num,不知道指向的是哪里,所以*p = 200并不知道修改的是哪里 //雖然接收到地址,但是num已經(jīng)銷毀 *p = 200; return 0; }
2.規(guī)避野指針
1.指針初始化
#include <stdio.h> int main() { int a = 10; int* pa = &a;//明確指針 int* pa = NULL;//當(dāng)pa不知道指向哪里時,置空 //NULL就是用來初始化指針的 //*pa = 20;//error,在對指針置空之后不能這樣使用,NULL屬于系統(tǒng)(內(nèi)核),不能訪問 return 0; }
2.小心指針越界問題
3.指針指向空間釋放,及時置NULL;
#include <stdio.h> #include <stdlib.h>//malloc的頭文件 int main() { //動態(tài)申請內(nèi)存空間 int* p=(int*)malloc(40); //使用 //釋放 free(p); p=NULL; return 0; }
4.避免返回局部變量的地址,即避免返回??臻g的地址,這樣可以盡量避免指針指向的空間釋放這種情況。
5.指針使用之前檢查有效性
三、指針運算
1.指針±整數(shù)
指針±整數(shù)是根據(jù)指針指向的數(shù)據(jù)類型大小向前或者向后走多少個字節(jié)。
- //char* pc;pc+1就是向后走一個char類型大小的字節(jié);
- //int* pa;pa+1就是向后走一個int類型大小的字節(jié);
- //double* pd;pd+1就是向后走一個double類型大小的字節(jié);
- //……
在練習(xí)1中已經(jīng)對其用法進行簡單演示。
2.指針-指針
兩個指針相減的前提是:指針指向的是同一塊連續(xù)的空間,且是同一種類型的指針。
指針和指針相減的絕對值是指針之間的元素個數(shù)
#include <stdio.h> int main() { int arr[10] = { 0 }; printf("%d\n",&arr[9]-&arr[0]);//9 printf("%d\n",&arr[9]-&arr[0]);//-9 return 0; }
練習(xí)2:
模擬實現(xiàn)strlen,strlen函數(shù)在對字符串中的字符進行統(tǒng)計時,遇見’\0’停止。
#include <stdio.h> int my_strlen(const char* arr) { const char* start = arr;//將arr數(shù)組首元素地址賦給start const char* end = arr; while(*end) { end++; } return end-start; } int main() { char arr[] = "abcdef"; int len = my_strlen(arr); printf("%d",len);//6 return 0; }
3.指針關(guān)系運算
#include <stdio.h> int main() { int i = 0; int arr[10] = { 0 }; int* p = &arr[10]; for(p = &arr[10];p > &arr[0]) { *--p = 0;//先將p向前移動4個字節(jié),再對p賦值; } for (i = 0; i < 10; i++) { printf("%d ", arr[i]);// 全為0 } return 0; }
指針關(guān)系運算的標準規(guī)定:允許指向數(shù)組元素的指針與指向數(shù)組最后一個元素后面的那個內(nèi)存位置的指針比較,但是不允許指向數(shù)組元素的指針與指向數(shù)組第一個元素前面的那個內(nèi)存位置的指針進行比較。
四、指針數(shù)組
1.指針和數(shù)組
指針和數(shù)組不是一個東西,指針是一個變量,是用來存放地址的,4/8個字節(jié);數(shù)組能夠存放一組數(shù),是一個連續(xù)的空間,數(shù)組的大小取決于元素個數(shù)。
聯(lián)系:數(shù)組名就是地址(指針);數(shù)組把首元素的地址交給一個指針變量之后,可以通過指針來訪問數(shù)組。
2.指針數(shù)組的概念
//char arr[n]——字符數(shù)組:存放字符的數(shù)組
//int arr[n]——整型數(shù)組:存放整型的數(shù)組
指針數(shù)組:存放指針的數(shù)組
//char* arr[n]——字符指針數(shù)組:存放字符指針的數(shù)組
//int* arr[n]——整型指針數(shù)組:存放整型指針的數(shù)組
練習(xí)3:
通過指針數(shù)組打印a,b,c,d的值
#include <stdio.h> int main() { int a = 10; int b = 20; int c = 30; int d = 40; int* arr[4] = {&a,&b,&c,&d}; for(i = 0;i < 4;i++) { printf("%d ",*(arr[i])); } return 0; }
運行結(jié)果為
練習(xí)4:
用一維數(shù)組模擬二維數(shù)組,打印出各個數(shù)組的值
#include <stdio.h> int main() { int arr1[4] = {1,2,3,4}; int arr2[4] = {2,3,4,5}; int* arr[2] = {arr1, arr2};//指針數(shù)組中存放的是每個數(shù)組中首元素的地址 int i = 0; for(i = 0;i < 2;i++) { int j = 0; for(j = 0;j < 4;j++) { printf("%d ",arr[i][j]); //arr[i][j]還可以寫為*(arr[i]+j)或者*(*(arr+i)+j);arr是數(shù)組首元素的地址 } printf("\n"); } return 0; }
運行結(jié)果為
練習(xí)5:
用指針數(shù)組打印字符串
#include <stdio.h> int main() { char* arr[4] = {"abc","bcd","cde","def"};//常量字符串 //arr中存放的是每個字符串中首個字符的地址 int i = 0; for(i = 0;i < 4;i++) { printf("%s\n",arr[i]);//%s打印字符串,只要有字符串起始地址就可以打印 } return 0; }
運行結(jié)果為
五、字符指針
在指針的類型中,我們知道有一種指針類型叫字符指針char*。
#include <stdio.h> int main() { char* pc = "abcdef"; printf("%s\n", pc);//abcdef printf("%c", *pc);//a return 0; }
字符指針可以存放字符串的起始地址,把字符串首元素的地址存在pc中,但這種做法不合理;"abcdef"
是常量字符串,不能修改,這時讓"*pc = 'w'"
程序會崩,可以將char* pc = "abcdef"
修改為const char* pc = "abcdef"
,const放在* 的左邊,限制*pc,不能改變字符串內(nèi)容。
練習(xí)6:
判斷下列代碼的輸出內(nèi)容;
#include <stdio.h> int main() { char arr1[] = "abcdef"; char arr2[] = "abcdef"; const char* arr3 = "abcdef"; const char* arr4 = "abcdef"; if(arr1 == arr2)//數(shù)組名表示數(shù)組首元素的地址,比較地址是否相同 printf("arr1 and arr2 are same\n"); else printf("arr1 and arr2 are not same\n"); if(arr3 == arr4) printf("arr3 and arr4 are same\n"); else printf("arr3 and arr4 are not same\n"); return 0; }
輸出結(jié)果為
if(arr1 == arr2)
和if(arr3 == arr4)
比較的是他們的首元素地址是否相同。
數(shù)組名是數(shù)組首元素的地址,arr1和arr2是數(shù)組,他們的首元素地址不同,所以他們不相同。
而arr3和arr4是char*類型的指針,他們指向的是常量字符串,常量字符串在內(nèi)存中只保存一份,地址相同,所以arr3和arr4指向的都是a的地址。
六、數(shù)組指針
//char* pc——字符指針-指向字符的指針,存放字符變量的地址
//int* pa ——整型指針-指向整型的指針,存放整型變量的地址
//int(*p)[n]——數(shù)組指針-指向數(shù)組的指針,存放數(shù)組的地址,p是數(shù)組指針變量
數(shù)組指針在一維數(shù)組里面的應(yīng)用
void print(int(*p)[5]) { int i = 0; for (i = 0; i < 5; i++) { printf("%d ", (*p)[i]); } } int main() { int arr[5] = { 1,2,3,4,5 }; print(&arr);//&arr取的是整個數(shù)組的地址 return 0; }
運行結(jié)果為
注意是數(shù)組指針也是指針,p應(yīng)該先與 * 結(jié)合,再與[ ]結(jié)合, (*p)要用( )括起來
數(shù)組指針很少在一維數(shù)組中應(yīng)用,一般在二維數(shù)組中應(yīng)用
void print(int(*p)[5], int row, int col) { int i = 0; for (i = 0; i < row; i++) { int j = 0; for (j = 0; j < col; j++) { printf("%d ", *( * (p + i) + j)); } printf("\n"); } } int main() { int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} }; print(&arr, 3, 5); return 0; }
運行結(jié)果
數(shù)組名相當(dāng)于數(shù)組首元素的地址,對于二維數(shù)組來說,首元素理解為他的第0行,p里面存的是第0行的地址,p是數(shù)組指針類型,p+1相當(dāng)于向后走了5個int類型的大小,p+1指向第1行,(p+i)指向的就是第i行的地址。* (p+i) 相當(dāng)于第i行的數(shù)組名; * (p+i)=p[i]。
擴展:int(*p[10])[5]——p是數(shù)組,有10個元素,每個元素都是數(shù)組指針,每個數(shù)組指針指向一個具有5個int類型的數(shù)組的地址。我們可以說p是存放數(shù)組指針的數(shù)組。
七、數(shù)組傳參和指針傳參
1.一維數(shù)組傳參
數(shù)組傳參,形參可以是數(shù)組,也可以是指針。
int arr[10] = {0}
傳參,他的參數(shù)用數(shù)組接收可以是int arr[]
或者int arr[10]
;用指針接收是int* arr
。
int* arr[10] = {0}
傳參,他的參數(shù)用數(shù)組接收是int* arr[10]
,用指針接收是int** arr
。
2.二維數(shù)組傳參
int arr[3][5]
傳參,他的參數(shù)用數(shù)組接收是int arr[3][5]
或者int arr[][5]
,用指針接收int (*arr)[5]
。
3.一級指針傳參
int* p
傳參,參數(shù)部分直接寫成指針形式:int* p
。
4.二級指針傳參
int** p
傳參,參數(shù)部分直接寫成指針形式:int** p
。
八、函數(shù)指針
#include <stdio.h> int Add(int x,int y) { return x+y; } int main() { int a = 10; int b = 20; int(*pf)(int,int)=Add; //int(*pf)(int,int)中,int是函數(shù)返回類型,pf是函數(shù)指針變量,(int,int)是函數(shù)的參數(shù)類型 //int ret = Add(a,b); int ret = (*pf)(a,b);//輸出結(jié)果是30 //使用函數(shù)指針時,也可以省略* //int ret = pf(a,b); printf("%d",ret); return 0; }
九、函數(shù)指針數(shù)組
函數(shù)指針數(shù)組:可以存放多個返回類型相同和參數(shù)相同的函數(shù)的地址。
我們通過構(gòu)造一個簡單的計算器來實現(xiàn)對函數(shù)指針數(shù)組的應(yīng)用
void menu() { printf("******************\n"); printf("**1.Add 2.Sub**\n"); printf("**3.Mul 4.Div**\n"); printf("**0.exit **\n"); printf("******************\n"); } 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; } int main() { int input = 0; int x = 0; int y = 0; int ret = 0; int(*pf[5])(int, int) = { 0,Add,Sub,Mul,Div };//利用數(shù)組下標引用函數(shù) do { menu(); printf("請選擇>:"); scanf("%d", &input); if (input == 0)//input為0時,直接退出計算器 { printf("退出計算器\n"); break; } if (input >= 1 && input <= 4)//判斷input是否符合要求 { printf("請輸入兩個操作數(shù):"); scanf("%d%d", &x, &y); ret = (*pf[input])(x, y); printf("結(jié)果為%d\n", ret); } else { printf("選擇錯誤\n");//input不符合要求,給與提示 } } while (input); return 0; }
擴展:指向函數(shù)指針數(shù)組的指針int(*(*pf)[5])(int,int)=&pf
。
十、回調(diào)函數(shù)
回調(diào)函數(shù)就是一個通過函數(shù)指針調(diào)用的函數(shù)。如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個函數(shù),當(dāng)這個指針被用來調(diào)用其所指向的函數(shù)時,我們就說這是回調(diào)函數(shù)。回調(diào)函數(shù)不是由該函數(shù)的實現(xiàn)方直接調(diào)用,而是在特定的事件或發(fā)生時由另外的一方調(diào)用的,用于對該事件或條件進行響應(yīng)。簡言之,我們拿到一個函數(shù),通過函數(shù)的地址調(diào)用一個函數(shù),被調(diào)用的函數(shù)就被稱為回調(diào)函數(shù)。
我們通過對上面那個計算器進行改寫,從而讓我們能夠更好的理解回調(diào)函數(shù)。
void menu() { printf("******************\n"); printf("**1.Add 2.Sub**\n"); printf("**3.Mul 4.Div**\n"); printf("**0.exit **\n"); printf("******************\n"); } 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 calc(int(*p)(int, int)) { int x = 0; int y = 0; printf("請輸入兩個操作數(shù):"); scanf("%d%d", &x, &y); printf("%d\n", (*p)(x,y)); } int main() { int input = 0; int x = 0; int y = 0; int ret = 0; int(*pf[5])(int, int) = { 0,Add,Sub,Mul,Div }; do { menu(); printf("請選擇>:"); scanf("%d", &input); switch (input) { case 1: calc(Add); break; case 2: calc(Sub); break; case 3: calc(Mul); break; case 4: calc(Div); break; case 0: printf("EXIT"); } } while (input); return 0; }
把函數(shù)地址傳給一個函數(shù)calc,在calc函數(shù)內(nèi)部通過函數(shù)指針p調(diào)用Add時,Add就被稱為回調(diào)函數(shù)。
以上就是C語言學(xué)習(xí)之指針的使用詳解的詳細內(nèi)容,更多關(guān)于C語言指針的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
用標準c++實現(xiàn)string與各種類型之間的轉(zhuǎn)換
這個類在頭文件中定義, < sstream>庫定義了三種類:istringstream、ostringstream和stringstream,分別用來進行流的輸入、輸出和輸入輸出操作。另外,每個類都有一個對應(yīng)的寬字符集版本2013-09-09C++中Digraphs、Trigraphs和Tokens的深入講解
這篇文章主要給大家介紹了關(guān)于C++中Digraphs、Trigraphs和Tokens的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用C++具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-09-09OpenMP task construct 實現(xiàn)原理及源碼示例解析
這篇文章主要為大家介紹了OpenMP task construct 實現(xiàn)原理及源碼示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03