您好,登錄后才能下訂單哦!
這篇“C語言指針、地址和數組函數堆空間的關系是什么”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“C語言指針、地址和數組函數堆空間的關系是什么”文章吧。
指針是C語言中的變量
因為是變量,所以用于保存具體值
特殊之處,指針保存的值是內存中的地址
內存地址是什么?
內存是計算機中的存儲部件,每個存儲單元有固定唯一的編號
內存中存儲單元的編號即內存地址
需要弄清楚的事實
程序中的一切元素都存在于內存中,因此,可通過內存地址訪問程序元素。
內存示例
獲取地址
C語言中通過 & 操作符獲取程序元素的地址
& 可獲取變量,數組,函數的起始地址
內存地址的本質是一個無符號整數(4字節或8字節)
下面看一個簡單的例子:
#include<stdio.h> int main() { int var = 0; printf("var value = %d\n", var); printf("var address = %p\n", &var); return 0; }
下面為輸出結果:
注意事項
只有通過 內存地址+長度 才能確定一個變量中保存的值。
指針定義語法:
type *point;
type - 數據類型,決定訪問內存時的長度范圍
* 標志,意味著定義一個指針變量
pointer 變量名,遵循C語言命名規則
例如:
int main() { char* pChar; short* pShort; int* pInt; float* pFloat; double* pDouble; return 0; }
指針內存訪問:
* pointer
指針訪問操作符(*)作用于指針變量即可訪問內存數據
指針的類型決定通過地址訪問內存時的長度范圍
指針的類型統一占用 4 字節或 8 字節
即:sizeof(type*) == 4或 sizeof(type*) == 8
下面看一段代碼,感受一下:
#include <stdio.h> int main() { int var = 0; int another = 0; int* pVar = NULL; printf("1. var = %d\n", var); printf("1. pVar = %p\n", pVar); pVar = &var; // 使用指針保存變量的地址 *pVar = 100; // *pVar 等價于 var , var = 100; printf("2. var = %d\n", var); printf("2. pVar = %p\n", pVar); pVar = &another; // 改變了 pVar 的指向,使得 pVar 保存 another 的地址 *pVar = 1000; // another = 1000; printf("3. another = %d\n", another); printf("3. pVar = %p\n", pVar); printf("4. add ==> %d\n", var + another + *pVar); // 100 + 1000 + 1000 ==> 2100 return 0; }
下面為輸出結果:
注意 NULL 地址為 00000000
小結
指針是C語言中的變量(本質為容器)
指針專用于保存程序元素的內存地址
可使用 * 操作符通過指針訪問程序元素本身
指針也有類型,指針類型由 數據類型+* 構成
靈魂三問
指針類型和普通類型之間的關系是什么?
何看待“內存地址+長度才能訪問內存中的數據”?
不同類型的指針可以相互賦值嗎?
初學指針的軍規
Type* 類型的指針只保存 Type 類型變量的地址
禁止不同類型的指針相互賦值
禁止將普通數值當作地址賦值給指針
注意:指針保存的地址必須是有效地址
下面看一段代碼:
#include <stdio.h> int main() { int i = 10; float f = 10; int* pi = &f; // WARNING float* pf = &f; // OK printf("pi = %p, pf = %p\n", pi, pf); printf("*pi = %d, *pf = %f\n", *pi, *pf); pi = i; // WARNING *pi = 110; // OOPS printf("pi = %p, *pi = %d\n", pi, *pi); return 0; }
下面為輸出結果:
這個程序犯了兩個錯誤:
1、將不同類型的指針相互賦值,雖然 int 類型的指針變量保存的地址是對的,但是其所保存的值是錯的。
2、 將普通數值當作地址賦值給指針,這會導致嚴重的錯誤,不能正確輸出
編寫函數交換兩個變量的值
想要編寫函數交換變量的值,那么,必須有能力在函數內部修改函數外部的變量!!!
看下面的代碼:
#include <stdio.h> void func(int* p) { *p = 100; // 修改內存中 4 字節的數據,即:修改一個整型變量的值 } void swap(int* pa, int* pb) { int t = 0; t = *pa; *pa = *pb; *pb = t; } int main() { int var = 0; int a = 1, b = 2; printf("1. var = %d\n", var); func( &var ); printf("2. var = %d\n", var); printf("3. a = %d, b = %d\n", a, b); swap(&a, &b); printf("4. a = %d, b = %d\n", a, b); return 0; }
下面為輸出結果:
小結論
可以利用指針從函數中“返回”多個值 (return只能返回一個值)!!
下面看一段代碼:
#include <stdio.h> int calculate(int n, long long* pa, long long* pm) { int ret = 1; if( (1 <= n) && (n <= 20) ) { int i = 0; *pa = 0; *pm = 1; for(i=1; i<=n; i++) { *pa = *pa + i; *pm = *pm * i; } } else { ret = 0; } return ret; } int main() { long long ar = 0; long long mr = 0; if( calculate(5, &ar, &mr) ) printf("ar = %lld, mr = %lld\n", ar, mr); return 0; }
下面為輸出結果:
這段代碼中的子函數通過指針,計算了1加到5以及1乘到5的值,這就間接地通過指針從子函數“返回”多個值
小結
指針是變量,因此賦值時必須保證類型相同
指針變量保存的地址必須是有效地址
通過指針參數
一能夠實現函數交換變量的值
一能夠從函數中“返回”多個值
問題
數組的本質是一片連續的內存,那么,數組的地址是什么?如何獲取?
一些事實
使用取地址操作符&獲取數組的地址
數組名可看作一個指針,代表數組中 0 元素的地址
當指針指向數組元素時,可進行指針運算(指針移動)
深入理解數組地址( int a[]= {1, 2, 3, 4, 5}; )
&a 與 a 在數值上相同,但是意義上不同
&a 代表數組地址,類型為:int(*)[5]
a 代表數組0號元素地址,類型為: int*
指向數組的指針: int (*pName)[5] = &a;
下面看一段代碼:
#include <stdio.h> int main() { int a[] = {1, 2, 3, 4, 0}; int* p = a; // a 的類型為 int*, &a[0] ==> int* int (*pa) [5] = &a; printf("%p, %p\n", p, a); p++; *p = 100; // a[1] = 100; printf("%d, %d\n", *p, a[1]); printf("%p, %p\n", &a, a); p = pa; // WARNING !!!! p = a; while( *p ) { printf("%d\n", *p); p++; } return 0; }
下面為運行結果:
需要注意的是,p 和 pa不是一個指針類型,所以令 p = pa 這種做法是不正確的。
注意
數組名并不是指針,只是代表了0號元素的地址,因此可以當作指針使用。
指針與數組的等價用法
假如:
int a[ ] = {1, 2,3, 4,5}
int* p = a;
則以下等價:
a[i] <--> *(a + i) <--> *(p + i) <--> p[i]
下面看一段代碼,加深理解:
#include <stdio.h> int main() { int a[] = {1, 2, 3, 4, 5}; int* p = a; int i = 0; // a[i] <==> *(a+i) <==> *(p+i) <==> p[i] for(i=0; i<5; i++) { printf("%d, %d\n", a[i], *(a + i)); } printf("\n"); for(i=0; i<5; i++) { printf("%d, %d\n", a[i], p[i]); } printf("\n"); for(i=0; i<5; i++) { printf("%d, %d\n", p[i], *(p + i)); } printf("\n"); printf("a = %p, p = %p\n", a, p); printf("&a = %p, &p = %p\n", &a, &p); return 0; }
下面為輸出結果:
這里可以看到 a和 p的地址不同,因為它們是兩個不同的指針變量。
字符串拾遺
字符串常量是 char* 類型,一種指針類型
指針移動組合拳:
int v = *p++;
解讀:
指針訪問操作符(*)和自增運算操作符(++) 優先級相同
所以,先從p指向的內存中取值,然后p進行移動
等價于:
int v = *p;
p++;
下面看一段代碼,體會一下:
#include <stdio.h> int main() { int a[] = {1, 2, 3}; int* p = a; int v = *p++; char* s = NULL; printf("%p\n", "D.T.Software"); printf("%p\n", "D.T.Software"); printf("v = %d, *p = %d\n", v, *p); printf("First = %c\n", *"D.T.Software"); s = "D.T.Software"; while( *s ) printf("%c", *s++); printf("\n"); return 0; }
下面為輸出結果:
因為D.T.Software 在全局數據區的起始地址一樣,所以兩次打印出來的地址一樣。
小結
數組名可看作一個指針,代表數組中0元素的地址
&a與a在數值上相同,但是意義上不同
C語言中的字符串常量的類型是 char *
當指針指向數組元素時,才能進行指針運算
問題
函數調用時會跳轉到函數體對應的代碼處執行,那么,如何知道函數體代碼的具體位置?
深入函數之旅
函數的本質是一段內存中的代碼(占用一片連續內存)
函數擁有類型,函數類型由返回類型和參數類型列表組成
例:
函數申明 | 類型 |
int sum(int n); | int (int) |
void swap(int* pa, int* pb) | void (int*, int*) |
void g(void); | void (void) |
函數的一些事實
函數名就是函數體代碼的起始地址(函數入口地址)
通過函數名調用函數,本質為指定具體地址的跳轉執行
因此,可定義指針,保存函數入口地址
函數指針( Type func (Type1 a,Type2 b))
函數名即函數入口地址,類型為 Type(*)(Type1,Type2)
對于 func 的函數,&func 與 func 數值相同,意義相同
指向函數的指針:Type (*pFunc) (Type1, Type2) = func;
函數指針參數
函數指針的本質還是指針(變量,保存內存地址)
可定義函數指針參數,使用相同代碼實現不同功能
注意
函數指針只是單純的保存函數的入口地址
因此
只能通過函數指針調用目標函數
不能進行指針移動(指針運算)
下面看一段代碼,理解一下:
#include <stdio.h> int add(int a, int b) { return a + b; } int mul(int a, int b) { return a * b; } int calculate(int a[], int len, int(*cal)(int, int)) { int ret = a[0]; int i = 0; for(i=1; i<len; i++) { ret = cal(ret, a[i]); } return ret; } int main() { int a[] = {1, 2, 3, 4, 5}; int (*pFunc) (int, int) = NULL; pFunc = add; printf("%d\n", pFunc(1, 2)); printf("%d\n", (*pFunc)(3, 4)); pFunc = &mul; printf("%d\n", pFunc(5, 6)); printf("%d\n", (*pFunc)(7, 8)); printf("1 + ... + 5 = %d\n", calculate(a, 5, add)); printf("1 * ... * 5 = %d\n", calculate(a, 5, mul)); return 0; }
下面為輸出結果:
這里注意,只有調用的時候,才能確定 calculate() 子函數中的 cal 是什么函數。
再論數組參數
函數的數組形參退化為指針!因此,不包含數組實參的長度信息。使用數組名調用時,傳遞的是0號元素的地址。
void func(int a[ ]) <--> void func(int* a)
<--> void func (int a[1])
<--> void func (int a[10)
<--> void func(int a[100)
下面看一段代碼:
#include <stdio.h> int demo(int arr[], int len) // int demo(int* arr, int len) { int ret = 0; int i = 0; printf("demo: sizeof(arr) = %d\n", sizeof(arr)); while( i < len ) { ret += *arr++; i++; } return ret; } int main() { int a[] = {1, 2, 3, 4, 5}; // int v = *a++; printf("return value: %d\n", demo(a, 5)); return 0; }
下面為輸出結果:
定義的形參arr[]可以進行 *arr++ 的操作,這就說明函數的數組形參退化為指針,因為數組不可以進行 ++ 的運算。
小結
函數名的本質是函數體的入口地址
函數類型由返回類型和參數類型列表組成
可定義指向函數的指針:Type (*pFunc) (Type1,Type2);
函數指針只是單純的保存函數的入口地址(不能進行指針運算)
再論內存空間
內存區域不同,用途不同
全局數據區:存放全局變量,靜態變量
棧空間:存放函數參數,局部變量
堆空間:用于動態創建變量(數組)
堆空間的本質
備用的“內存倉庫”,以字節為單位預留的可用內存
程序可在需要時從“倉庫”中申請使用內存(動態借)
當不需要再使用申請的內存時,需要及時歸還(動態還)
問題
如何從堆空間申請內存?如何歸還?
預備知識-- void*
void 類型是基礎類型,對應的指針類型為 void*
void* 是指針類型,其指針變量能夠保存地址
通過 void* 指針無法獲取內存中的數據(無長度信息)
void* 總結
不可使用void*指針直接獲取內存數據。
void*指針可與其它數據指針相互賦值。
下面看一段代碼:
#include <stdio.h> int main() { char c = 0; int i = 0; float f = 2.0f; double d = 3.0; void* p = NULL; double* pd = NULL; int* pi = NULL; /* void* 指針可以保存任意類型的地址 */ p = &c; p = &i; p = &f; p = &d; printf("%p\n", p); // void* 類型的指針無法訪問內存中的數據 // printf("%f\n", *p); /* void* 類型的變量可以直接合法的賦值給其他具體數據類型的指針變量 */ pd = p; pi = p; // void* 是例外,其他指針類型的變量不能相互賦值 // pd = pi; return 0; }
下面為輸出結果:
注意幾個問題:
1.void* 指針可以保存任意類型的地址
2.void* 類型的指針無法訪問內存中的數據
3.void* 類型的變量可以直接合法的賦值給其他具體數據類型的指針變量
4.void* 是例外,其他指針類型的變量不能相互賦值
堆空間的使用
工具箱:stdlib.h
申請:void* malloc ( unsigned bytes )
歸還:void free( void* p)
堆空間的使用原則
有借有還,再借不難(杜絕只申請,不歸還)
malloc申請內存后,應該判斷是否申請成功
free只能釋放申請到的內存,且不可多次釋放(free 釋放的是堆空間的地址)
下面看一段代碼感受一下:
#include <stdio.h> #include <stdlib.h> int main() { int* p = malloc(4); // 從堆空間申請 4 個字節當作 int 類型的變量使用 if( p != NULL ) // 如果申請失敗 p 為 0 ,即:空值 { *p = 100; printf("%d\n", *p); free(p); } p = malloc(4 * sizeof(int)); if( p != NULL ) { int i = 0; for(i=0; i<4; i++) { p[i] = i * 10; } for(i=0; i<4; i++) { printf("%d\n", p[i]); } free(p); } return 0; }
下面為輸出結果:
小結
堆空間是程序中預留且可用的內存區域
void*指針只能能夠保存地址,但無法獲取內存數據
void*指針可與其它數據指針相互賦值
malloc申請內存后,應該判斷是否申請成功
free只能釋放申請到的內存,且不可多次釋放
多級指針
可以定義指針的指針保存其它指針變量的地址
如:
Type v;
Type *pv = &v;
Type** ppv = &pv;
type*** pppv = &ppv;
下面看一段代碼:
#include <stdio.h> #include <stdlib.h> int main() { int a = 0; int b = 1; int* p = &a; int** pp = &p; **pp = 2; // a = 2; *pp = &b; // p = &b; *p = 3; // b = 3; printf("a = %d, b = %d\n", a, b); return 0; }
下面為輸出結果:
*pp 就是取 pp 里面的內容,而 pp 里面存的內容是 p 的地址,所以 *pp 就相當于p 的內容,而 p 的內容就是 a 的地址,所以說 **p 就相當于 a,**p = 2 也就是把 2 賦值給 a,*pp = &b 即為 p = &b,所以 *p = 3,就是把 3 賦值給 b。
下面再看一段代碼:
#include <stdio.h> #include <stdlib.h> int getDouble(double** pp, unsigned n) { int ret = 0; double* pd = malloc(sizeof(double) * n); if( pd != NULL ) { printf("pd = %p\n", pd); *pp = pd; ret = 1; } return ret; } int main() { double* p = NULL; if( getDouble(&p, 5) ) { printf("p = %p\n", p); free(p); } return 0; }
下面為輸出結果:
這里特別注意:函數外的一個一級指針指向了這里申請的堆空間
再論二維數組
二維數組的本質是一維數組 ,即:數組中的元素是一維數組!!
因此:
int a[2][2];
a 就是 &a[0]
a[0] 的類型是 int[2]
可知 a 的類型是 int (*)[2]
下面看一段代碼:
#include <stdio.h> #include <stdlib.h> int main() { int b[2][2] = {{1, 2}, {3, 4}}; int (*pnb) [2] = b; // b 的類型是 int(*)[2] *pnb[1] = 30; printf("b[0][0] = %d\n", b[0][0]); printf("b[0][1] = %d\n", b[0][1]); printf("b[1][0] = %d\n", b[1][0]); printf("b[1][1] = %d\n", b[1][1]); return 0; }
下面為輸出結果:
pnb[0]是[1,2],pnb[1]經過賦值后是[30,4],所以*(pnb[1])就是取該數組所代表的第0個元素,也就是30。
下面再看一個代碼:
#include <stdio.h> #include <stdlib.h> int* func() { int var = 100; return &var; } int main() { int* p = func(); // OOPS!!!! // p 指向了不合法的地址,這個地址處沒有變量存在 // p 是一個野指針,保存不合法地址的指針都是野指針 printf("*p = %d\n", *p); *p = 200; // 改變 func 函數中局部變量 var 的值,是不是非常奇怪??? printf("*p = %d\n", *p); return 0; }
這段代碼是有問題的, func() 函數執行后, var 這個變量就會被銷毀,所以 p 指向了一個不合法的地址。
以上就是關于“C語言指針、地址和數組函數堆空間的關系是什么”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。