<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
指標是C語言中的變數
需要弄清楚的事實
記憶體範例
獲取地址
下面看一個簡單的例子:
#include<stdio.h> int main() { int var = 0; printf("var value = %dn", var); printf("var address = %pn", &var); return 0; }
下面為輸出結果:
注意事項
指標定義語法:
type *point;
例如:
int main() { char* pChar; short* pShort; int* pInt; float* pFloat; double* pDouble; return 0; }
指標記憶體存取:
* pointer
即:sizeof(type*) == 4或 sizeof(type*) == 8
下面看一段程式碼,感受一下:
#include <stdio.h> int main() { int var = 0; int another = 0; int* pVar = NULL; printf("1. var = %dn", var); printf("1. pVar = %pn", pVar); pVar = &var; // 使用指標儲存變數的地址 *pVar = 100; // *pVar 等價於 var , var = 100; printf("2. var = %dn", var); printf("2. pVar = %pn", pVar); pVar = &another; // 改變了 pVar 的指向,使得 pVar 儲存 another 的地址 *pVar = 1000; // another = 1000; printf("3. another = %dn", another); printf("3. pVar = %pn", pVar); printf("4. add ==> %dn", var + another + *pVar); // 100 + 1000 + 1000 ==> 2100 return 0; }
下面為輸出結果:
注意 NULL 地址為 00000000
小結
靈魂三問
初學指標的軍規
注意:指標儲存的地址必須是有效地址
下面看一段程式碼:
#include <stdio.h> int main() { int i = 10; float f = 10; int* pi = &f; // WARNING float* pf = &f; // OK printf("pi = %p, pf = %pn", pi, pf); printf("*pi = %d, *pf = %fn", *pi, *pf); pi = i; // WARNING *pi = 110; // OOPS printf("pi = %p, *pi = %dn", 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 = %dn", var); func( &var ); printf("2. var = %dn", var); printf("3. a = %d, b = %dn", a, b); swap(&a, &b); printf("4. a = %d, b = %dn", 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 = %lldn", ar, mr); return 0; }
下面為輸出結果:
這段程式碼中的子函數通過指標,計算了1加到5以及1乘到5的值,這就間接地通過指標從子函數“返回”多個值
小結
指標是變數,因此賦值時必須保證型別相同
指標變數儲存的地址必須是有效地址
通過指標引數
問題
一些事實
深入理解陣列地址( int a[]= {1, 2, 3, 4, 5}; )
下面看一段程式碼:
#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, %pn", p, a); p++; *p = 100; // a[1] = 100; printf("%d, %dn", *p, a[1]); printf("%p, %pn", &a, a); p = pa; // WARNING !!!! p = a; while( *p ) { printf("%dn", *p); p++; } return 0; }
下面為執行結果:
需要注意的是,p 和 pa不是一個指標型別,所以令 p = pa 這種做法是不正確的。
注意
指標與陣列的等價用法
假如:
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, %dn", a[i], *(a + i)); } printf("n"); for(i=0; i<5; i++) { printf("%d, %dn", a[i], p[i]); } printf("n"); for(i=0; i<5; i++) { printf("%d, %dn", p[i], *(p + i)); } printf("n"); printf("a = %p, p = %pn", a, p); printf("&a = %p, &p = %pn", &a, &p); return 0; }
下面為輸出結果:
這裡可以看到 a和 p的地址不同,因為它們是兩個不同的指標變數。
字串拾遺
指標移動組合拳:
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("%pn", "D.T.Software"); printf("%pn", "D.T.Software"); printf("v = %d, *p = %dn", v, *p); printf("First = %cn", *"D.T.Software"); s = "D.T.Software"; while( *s ) printf("%c", *s++); printf("n"); return 0; }
下面為輸出結果:
因為D.T.Software 在全域性資料區的起始地址一樣,所以兩次列印出來的地址一樣。
小結
問題
深入函數之旅
函數申明 | 型別 |
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))
函數指標引數
注意
函數指標只是單純的儲存函數的入口地址
因此
下面看一段程式碼,理解一下:
#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("%dn", pFunc(1, 2)); printf("%dn", (*pFunc)(3, 4)); pFunc = &mul; printf("%dn", pFunc(5, 6)); printf("%dn", (*pFunc)(7, 8)); printf("1 + ... + 5 = %dn", calculate(a, 5, add)); printf("1 * ... * 5 = %dn", 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) = %dn", 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: %dn", demo(a, 5)); return 0; }
下面為輸出結果:
定義的形參arr[]可以進行 *arr++ 的操作,這就說明函數的陣列形參退化為指標,因為陣列不可以進行 ++ 的運算。
小結
再論記憶體空間
記憶體區域不同,用途不同
堆空間的本質
問題
預備知識-- 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("%pn", p); // void* 型別的指標無法存取記憶體中的資料 // printf("%fn", *p); /* void* 型別的變數可以直接合法的賦值給其他具體資料型別的指標變數 */ pd = p; pi = p; // void* 是例外,其他指標型別的變數不能相互賦值 // pd = pi; return 0; }
下面為輸出結果:
注意幾個問題:
1.void* 指標可以儲存任意型別的地址
2.void* 型別的指標無法存取記憶體中的資料
3.void* 型別的變數可以直接合法的賦值給其他具體資料型別的指標變數
4.void* 是例外,其他指標型別的變數不能相互賦值
堆空間的使用
堆空間的使用原則
下面看一段程式碼感受一下:
#include <stdio.h> #include <stdlib.h> int main() { int* p = malloc(4); // 從堆空間申請 4 個位元組當作 int 型別的變數使用 if( p != NULL ) // 如果申請失敗 p 為 0 ,即:空值 { *p = 100; printf("%dn", *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("%dn", p[i]); } free(p); } return 0; }
下面為輸出結果:
小結
多級指標
如:
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 = %dn", 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 = %pn", pd); *pp = pd; ret = 1; } return ret; } int main() { double* p = NULL; if( getDouble(&p, 5) ) { printf("p = %pn", 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] = %dn", b[0][0]); printf("b[0][1] = %dn", b[0][1]); printf("b[1][0] = %dn", b[1][0]); printf("b[1][1] = %dn", 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 = %dn", *p); *p = 200; // 改變 func 函數中區域性變數 var 的值,是不是非常奇怪??? printf("*p = %dn", *p); return 0; }
這段程式碼是有問題的, func() 函數執行後, var 這個變數就會被銷燬,所以 p 指向了一個不合法的地址。
小結
到此這篇關於C語言全方位講解指標與地址和陣列函數堆空間的關係的文章就介紹到這了,更多相關C語言指標內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45