深入理解指針(指針篇2)

在指針篇1我們已經了解了整型指針,當然還有很多其他類型的指針,像字符指針、數組指針、函數指針等,他們都有他們的特別之處,讓我們接著學習。

1. 指針類型介紹和應用

1.1 字符指針變量

字符指針變量類型為char*,一般這樣使用:

int main()
{char ch = 'w';char *pc = &ch;*pc = 'w';return 0;
}

還有一種使用方式:

int main()
{const char* pstr = "hello bit.";//這?是把?個字符的地址傳給pstr變量printf("%s\n", pstr);return 0;
}

1.2 數組指針變量(與指針數組區分)

提到數組指針,我們會想數組指針到底是數組還是指針,指針數組又是什么,他們怎么區分?

我們可以這樣理解:

數組指針,就是一個存放內容是數組的指針,那自然是指針了

指針數組,就是一個數組元素是指針的數組,那自然是數組了

我們也可以通過后綴來理解,后綴是本質,前者都是形容詞。

知道了兩者的區別后我們看一段代碼:

int *p1[10];  //指針數組
int (*p2)[10];//數組指針

int *p1[10] 解釋:

[ ]的優先級高于*,p1先與[10]結合,表明這是一個數組,前面的int*表明數組中的元素是int*類型的,所以這是一個指針數組。

int (*p2)[10]解釋:因為有()的存在,p2先與*結合,說明p2是一個指針變量,然后指針指向的是一個大小為10個整型的數組,所以這是一個數組指針。

1.3 函數指針變量

通過前面的學習我們不難猜到函數指針是用來存放函數地址的,未來可以通過函數地址調用函數

函數的地址與數組有相似之處,函數名就是函數的地址,也可以通過&函數名的方式獲得函數的地址。我們要將函數的地址存放起來,就需要創建函數指針變量了,函數指針變量的寫法也和數組指針非常相似,如下代碼:

void test()
{printf("hehe\n");
}void (*pf1)() = &test;
void (*pf2)()= test;int Add(int x, int y)
{return x+y;
}int(*pf3)(int, int) = Add;
int(*pf4)(int x, int y) = &Add;//x和y寫上或者省略都是可以的

上面說到可以通過函數地址調用函數,函數地址存放在函數指針變量中,我們是不是可以通過函數指針調用指針指向的函數呢?我們通過代碼實現一下。

 #include <stdio.h>int Add(int x, int y){return x+y;}int main(){int(*pf3)(int, int) = Add;printf("%d\n", (*pf3)(2, 3));//輸出5printf("%d\n", pf3(3, 5));   //輸出8return 0;}

2. 二維數組的本質

有了數組指針的理解,我們就可以講一講二維數組傳參的本質了

過去二維數組要傳參給一個函數時,代碼如下:

#include <stdio.h>void test(int a[3][5], int r, int c)
{int i = 0;int j = 0;for(i=0; i<r; i++){for(j=0; j<c; j++){printf("%d ", a[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}};test(arr, 3, 5);return 0;
}

這里,形參除了寫成二維數組的形式,還可以怎么寫?

我們想想,二維數組名就是數組首行元素的地址,第一行的一維數組類型就是int [5],第一行的地址類型就行int (*) [5],那么意味著二維數組傳參的本質也是傳遞了地址,傳的是第一行這個一位數組的地址,形參也可以寫成指針形式,如下:

#include<stdio.h>void test(int (*p)[5], int r, int c)
{int i = 0;int j = 0;for(i = 0;i < r; i++){for(j = 0; j < c; 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}};test(arr, 3, 5);return 0;
}

3. 函數指針數組(應用:轉移表)

3.1 函數指針數組定義

數組是一個存放相同數據類型的存儲空間,把幾個函數的地址存放到一個數組中,這個數組就叫函數指針數組。嗯,函數指針數組,聽起來好復雜,它又應該怎么定義呢?

int (*parr[num]) ();

解釋一下,變量名是parr,先與[num]結合表明這是一個數組,去掉數組部分,剩余部分是數組元素類型,剩余部分是int(*) (),很明顯類型是函數指針,所以這是一個元素類型為函數指針的數組。這樣是不是有所理解了呢。

3.2 轉移表

舉例:計算器加減乘除的實現

#include <stdio.h>int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}
int main()
{int x, y;int input = 1;int ret = 0;do{printf("***********************\n");printf("*1.add           2.sub*\n");printf("*        0.exit       *\n");printf("*3.mul           4.div*\n");printf("***********************\n");printf("請選擇:>\n");scanf("%d", &input);switch (input){case 1:printf("輸?操作數:>\n");scanf("%d %d", &x, &y);ret = add(x, y);printf("ret = %d\n", ret);break;case 2:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出程序\n");break;default:printf("選擇錯誤\n");break;}} while (input);return 0;
}

上面代碼沒有處理除數為0的情況,而且代碼非常冗余,下面這一段代碼重復了多次,只有選擇函數部分不一致,如果我們加減乘除函數存放在數組中,通過數組選擇加減乘除函數,是不是大大減少了代碼量呢?下面我們實現一下。

 printf("輸?操作數:");scanf("%d %d", &x, &y);ret = func(x, y);printf("ret = %d\n", ret);break;

?使用函數指針數組實現:

//轉移表實現計算器加減乘除(使用函數指針數組)
#include<stdio.h>void menu()
{printf("***********************\n");printf("*1.add           2.sub*\n");printf("*        0.exit       *\n");printf("*3.mul           4.div*\n");printf("***********************\n");
}int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}int main()
{int x = 0;int y = 0;int input = 1;int (*p[5])(int x, int y) = { NULL, add, sub, mul, div };do{menu();printf("請選擇:>\n");scanf("%d", &input);//判斷有效性if (input >= 1 && input <= 4){printf("請輸入操作數:>\n");scanf("%d %d", &x, &y);//判斷除數為0的情況if (input == 4 && y == 0){printf("除數不能為0\n");continue;}int ret = (*p[input])(x, y);printf("%d\n", ret);}else if (input == 0){printf("退出計算器\n");break;}else{printf("輸入有誤,請重新輸入\n");}} while (input);return 0;
}

4. 回調函數

4.1回調函數定義

回調函數就是通過函數指針調用的函數。

當我們將一個函數的指針(地址)作為參數傳遞給另一個函數時,如果該指針被用于調用其指向的函數,這種被間接調用的函數就稱為回調函數。回調函數的獨特之處在于:它不是由函數定義方直接調用,而是在特定事件或條件發生時由第三方觸發,用于響應相應的事件或條件。

4.2回調函數的應用(加減乘除計算器優化)

在上面第三節中我們實現加減乘除計算器時,可不可以用回調函數實現呢,封裝一個函數,把加減乘除函數的地址作為參數傳給函數,下面編程實現一下。

#include <stdio.h>void menu()
{printf("***********************\n");printf("*1.add           2.sub*\n");printf("*        0.exit       *\n");printf("*3.mul           4.div*\n");printf("***********************\n");
}
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}
int func(int (*p)(int,int))
{int x = 0;int y = 0;int z = 0;printf("輸?操作數:>\n");scanf("%d %d", &x, &y);z = (*p)(x,y);
printf("%d\n", z);
}
int main()
{int input = 1;int ret = 0;do{menu();printf("請選擇:>\n");scanf("%d", &input);switch (input){case 1:func(add);break;case 2:func(sub);break;case 3:func(mul);break;case 4:func(div);break;case 0:printf("退出程序\n");break;default:printf("選擇錯誤\n");break;}} while (input);return 0;
}

5. qsort使用舉例及模擬實現(冒泡排序)

qsort是C語言中用于排序各種同類型數據的庫函數,使用者需要實現一個比較函數,傳入待比較的兩個參數,并返回一個整型數據。(返回值>0,前者>后者;返回值<0,前者<后者;返回值=0,前者=后者)

5.1 qsort使用舉例

5.1.1 使用qsort排序整型數據

//使用qsort排序整形
#include<stdio.h>
;
int int_cmp(const void* p1, const void* p2)
{return *(int*)p1 - *(int*)p2;
}int main()
{int arr[] = { 0,5,4,2,3,1,6,7,8,9 };qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%d ", arr[i]);}return 0;
}

5.1.2 使用qsort排序結構體

//使用qsort排序結構體
#include<stdio.h>
#include<string.h>struct Person
{char name[20];int age;
};void print(struct Person p[])
{for (int i = 0; i < 3; i++){printf("%s %d \n", p[i].name, p[i].age);}
}//按照年齡排序
int stu_cmp_by_age(const void* p1, const void* p2)
{return ((struct Person*)p1)->age - ((struct Person*)p2)->age;
}
//按照年齡排序
void test1()
{struct Person s[] = { {"zhangsan", 21}, {"lisi", 25}, {"wangwu", 29} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), stu_cmp_by_age);print(s);
}//按照名字排序
int stu_cmp_by_name(const void* p1, const void* p2)
{return strcmp(((struct Person*)p1)->name, ((struct Person*)p2)->name);
}//按照名字排序
void test2()
{struct Person s[] = { {"zhangsan", 21}, {"lisi", 25}, {"wangwu", 29} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), stu_cmp_by_name);print(s);
}int main()
{test1();test2();return 0;
}

5.2 qsort的模擬實現(冒泡排序)

在指針篇1中我們學習了冒泡排序,這次我們使用冒泡排序的方法模擬實現一下qsort庫函數。因為qsort不知道使用者要比較的是什么類型數據,所以傳入的參數地址要使用void*類型(void*類型在指針篇2.3.3中提到)

//qsort模擬實現(使用冒泡排序)
#include<stdio.h>int int_cmp(const void* p1, const void* p2)
{return *(int*)p1 - *(int*)p2;
}void Swap(char* one, char* two, size_t width)
{for (int i = 0; i < width; i++){char tmp = *one;*one = *two;*two = tmp;one++;two++;}
}void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void*,const void*))
{for (int i = 0; i < sz - 1; i++){for (int j = 0; j < sz - 1 - i; j++){//數組內部兩兩比較if (cmp((char*)base + j * width, (char*)base+(j + 1) * width) > 0){Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);}}}
}int main()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i = 0;bubble_sort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%d ", arr[i]);}printf("\n");return 0;
}

6. sizeof和strlen的對比

6.1 sizeof

sizeof是C語言中的一個單目操作符,sizeof是計算變量所占內存空間大小的,單位是字節,如果操作數是數據類型的話,計算的是使用此類型創建的變量所占內存空間的大小。代碼示例:

#inculde <stdio.h>int main()
{int a = 10;printf("%d\n", sizeof(a));printf("%d\n", sizeof a);printf("%d\n", sizeof(int));//都可以正常輸出4return 0;
}

6.2 strlen

strlen是C語言庫函數,使用需要包含頭文件string.h,功能是求字符串長度。函數原型:

size_t strlen ( const char * str );

統計的是傳入參數的地址到遇到 \0 之前字符串中字符的個數,strlen會一直向后找直到找到 \0 為止,所以可能存在越界查找。

#include <stdio.h>int main()
{char arr1[3] = {'a', 'b', 'c'};char arr2[] = "abc";printf("%d\n", strlen(arr1));//隨機值printf("%d\n", strlen(arr2));//3printf("%d\n", sizeof(arr1));//3printf("%d\n", sizeof(arr2));//4return 0;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/89564.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/89564.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/89564.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Python+Selenium自動化爬取攜程動態加載游記

1. 引言 在旅游行業數據分析、輿情監測或競品研究中&#xff0c;獲取攜程等平臺的游記數據具有重要價值。然而&#xff0c;攜程的游記頁面通常采用動態加載&#xff08;Ajax、JavaScript渲染&#xff09;&#xff0c;傳統的**<font style"color:rgb(64, 64, 64);backg…

ESP8266服務器建立TCP連接失敗AT+CIPSTART=“TCP“,“192.168.124.1“,8080 ERROR CLOSED

1.檢查服務器端口8081是否開啟監聽2.檢查路由項是否被防火墻攔截方法 1&#xff1a;使用 netsh查看防火墻規則?netsh advfirewall firewall show rule nameall dirout | findstr "8081"如果無輸出&#xff0c;說明防火墻未針對該端口設置規則&#xff08;可能默認攔…

Linux 內存管理(2):了解內存回收機制

目錄一、透明大頁1.1 原理1.2 透明大頁的三大優勢1.3 透明大頁控制接口詳解1.4 使用場景與最佳實踐1.5 問題排查與監控1.6 與傳統大頁的對比二、Linux伙伴系統水位機制詳解2.1 三種核心水位詳解2.2 水位在伙伴系統中的實現2.3 水位觸發機制的實際行為2.4 水位關鍵操作接口2.5 水…

前端學習7:CSS過渡與動畫--補間動畫 (Transition) vs 關鍵幀動畫 (Animation)

一、補間動畫&#xff08;Tween Animation&#xff09;vs 關鍵幀動畫&#xff08;Keyframe Animation&#xff09;概念對比表&#xff1a;補間動畫 (Transition)關鍵幀動畫 (Animation)定義元素從初始狀態到結束狀態的過渡效果通過定義多個關鍵幀控制動畫的中間狀態觸發方式需要…

PyTorch 損失函數詳解:從理論到實踐

目錄 一、損失函數的基本概念 二、常用損失函數及實現 1. 均方誤差損失&#xff08;MSELoss&#xff09; 2. 平均絕對誤差損失&#xff08;L1Loss/MAELoss&#xff09; 3. 交叉熵損失&#xff08;CrossEntropyLoss&#xff09; 4. 二元交叉熵損失&#xff08;BCELoss&…

MinIO深度解析:從核心特性到Spring Boot實戰集成

在當今數據爆炸的時代&#xff0c;海量非結構化數據的存儲與管理成為企業級應用的關鍵挑戰。傳統文件系統在TB級數據面前捉襟見肘&#xff0c;而昂貴的云存儲服務又讓中小企業望而卻步。MinIO作為一款開源高性能對象存儲解決方案&#xff0c;正以其獨特的技術優勢成為開發者的首…

騰訊云服務上下載docker以及使用Rabbitmq的流程

執行以下命令&#xff0c;添加 Docker 軟件源并配置為騰訊云源。sudo yum-config-manager --add-repohttps://mirrors.cloud.tencent.com/docker-ce/linux/centos/docker-ce.repo sudo sed -i "s/download.docker.com/mirrors.tencentyun.com\/docker-ce/g" /etc/yu…

UE5 一些關于過場動畫sequencer,軌道track的一些Python操作

刪除多余的軌道 import unreal def execute():movie_scene_actors []sequence_assets []data 0.0# 獲取編輯器實用工具庫lib unreal.EditorUtilityLibrary()selected_assets lib.get_selected_assets()for asset in selected_assets:if asset.get_class() unreal.LevelS…

前端性能優化“核武器”:新一代圖片格式(AVIF/WebP)與自動化優化流程實戰

前端性能優化“核武器”&#xff1a;新一代圖片格式(AVIF/WebP)與自動化優化流程實戰 當你的頁面加載時間超過3秒時&#xff0c;用戶的跳出率會飆升到40%以上。而在所有的前端性能優化手段中&#xff0c;圖片優化無疑是投入產出比最高的一環。一張未經優化的巨大圖片&#xff0…

單元測試學習+AI輔助單測

標題單元測試衡量指標具體測試1、Resource2、MockBean3、Test4、Test模板5、單測示例H2數據庫JSON1、使用方式AI輔助單測使用方法單元測試 單元測試一般指程序員在寫好代碼后&#xff0c;提交測試前&#xff0c;需要驗證自己的代碼是否可以正常工作&#xff0c;同時將自己的代…

Spring Cloud Gateway與Envoy Sidecar在微服務請求路由中的架構設計分享

Spring Cloud Gateway與Envoy Sidecar在微服務請求路由中的架構設計分享 在現代微服務架構中&#xff0c;請求路由層承擔著流量分發、安全鑒權、流量控制等多重職責。傳統的單一網關方案往往面臨可擴展性和可維護性挑戰。本文將從真實生產環境出發&#xff0c;分享如何結合Spri…

GitHub Pages+Jekyll 靜態網站搭建(二)

GitHub PagesJekyll 靜態網站搭建&#xff08;二&#xff09;GitHub PagesJekyll 靜態網站搭建&#xff08;二內容簡介搭建模板網站部署工作流程GitHub PagesJekyll 靜態網站搭建&#xff08;二 內容簡介 &#x1f6a9; Tech Contents 該文主要涉及Jekyll主題的下載與使用。Gi…

Django 實戰:I18N 國際化與本地化配置、翻譯與切換一步到位

文章目錄一、國際化與本地化介紹定義相關概念二、安裝配置安裝 gettext配置 settings.py三、使用國際化視圖中使用序列化器和模型中使用四、本地化操作創建或更新消息文件消息文件說明編譯消息文件五、項目實戰一、國際化與本地化介紹 定義 國際化和本地化的目標&#xff0c;…

通過國內扣子(Coze)搭建智能體并接入discord機器人

國內的扣子是無法直接授權給discord的&#xff0c;但是用國外的coze的話&#xff0c;大模型調用太貴&#xff0c;如果想要接入國外的平臺&#xff0c;那就需要通過調用API來實現。 1.搭建智能體&#xff08;以工作流模式為例&#xff09; 首先&#xff0c;我們需要在扣子平臺…

【辦公類-107-02】20250719視頻MP4轉gif(削減MB)

背景需求 最近在寫第五屆智慧項目結題(一共3篇)寫的昏天黑地,日以繼夜。 我自己《基于“AI技術”的幼兒園教學資源開發和運用》提到了AI繪畫、AI視頻和AI編程。 為了更好的展示AI編程的狀態,我在WORD里面插入了MP4轉gif的動圖。 【教學類-75-04】20241023世界名畫-《蒙…

一文講清楚React的render優化,包括shouldComponentUpdate、PureComponent和memo

文章目錄一文講清楚React的render優化&#xff0c;包括shouldComponentUpdate、PureComponent和memo1. React的渲染render機制2. shouldComponentUpdate2.1 先上單組件渲染&#xff0c;驗證state變化2.2 上父子組件&#xff0c;驗證props2. PureComponent2.1 單組件驗證state2.…

物聯網iot、mqtt協議與華為云平臺的綜合實踐(萬字0基礎保姆級教程)

本學期的物聯網技術與應用課程&#xff0c;其結課設計內容包含&#xff1a;mqtt、華為云、PyQT5和MySQL等結合使用&#xff0c;完成了從華為云配置產品信息以及轉發規則&#xff0c;到mqtt命令轉發&#xff0c;再到python編寫邏輯代碼實現相關功能&#xff0c;最后用PyQT5實現面…

使用IntelliJ IDEA和Maven搭建SpringBoot集成Fastjson項目

使用IntelliJ IDEA和Maven搭建SpringBoot集成Fastjson項目 下面我將詳細介紹如何在IntelliJ IDEA中使用Maven搭建一個集成Fastjson的SpringBoot項目&#xff0c;包含完整的環境配置和代碼實現。 一、環境準備 軟件要求 IntelliJ IDEA 2021.x或更高版本JDK 1.8或更高版本&#x…

Java從入門到精通!第九天, 重點!(集合(一))

十一、集合1. 為什么要使用集合(1) 數組存在的弊端1) 數組在初始化之后&#xff0c;長度就不能改變&#xff0c;不方便擴展。2) 數組中提供的屬性和方法比較少&#xff0c;不便于進行添加、刪除、修改等操作&#xff0c;并且效率不高&#xff0c;同時無法直接存儲元素的個數。3…

為什么使用時序數據庫

為什么使用時序數據庫&#xff1f; 時序數據庫&#xff08;Time-Series Database, TSDB&#xff09;是專為時間序列數據優化的數據庫&#xff0c;相比傳統關系型數據庫&#xff08;如MySQL&#xff09;或NoSQL數據庫&#xff08;如MongoDB&#xff09;&#xff0c;它在以下方面…