C語言:指針詳解續

一、字符指針變量

我們知道有種指針類型為字符指針(char*)。

#include <stdio.h>
int main()
{char ch = 'w';char* pch = &ch;printf("%c\n", *pch);return 0;
}

其實它還有一種使用方式。

#include <stdio.h>
int main()
{char* pstr = "hello world";printf("%s\n", pstr);return 0;
}

在代碼?char* pstr = "hello world";?中,指針?pstr?存放的正是字符串?"hello world"?首字符?'h'?的地址。在 C 語言里,字符串是以字符數組的形式存儲在內存中的,并且以?'\0'(空字符)作為字符串結束的標志。當定義一個指針指向一個字符串常量時,該指針的值就是這個字符串存儲在內存中起始位置(也就是首字符所在位置)的地址。當在printf函數中使用%s格式化符并且參數是一個字符指針(如char *pstr)時,printf函數會把這個指針當作字符串的起始地址。它會從這個地址開始讀取字符,一直讀取到遇到'\0'(字符串結束標志)為止。例如,在代碼char* pstr="hello world"; printf("%s\n", pstr);中,pstr指向字符串"hello world"的首字符'h'printf會從這個地址開始,逐個字符地輸出,直到遇到'\0'才停止。

現在我們來看一看《劍指offer》中的一道題。

#include <stdio.h>
int main()
{char str1[] = "hello";char str2[] = "hello";const char* str3 = "hello";const char* str4 = "hello";if (str1 == str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if (str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}

我們來分析一下結果為什么是這樣的。當我們使用char數組存放字符串時,就會為數組分配獨立的內存空間來存放字符串,所以str1和str2都指的是這兩個數組的首字符的地址,即使字符串相同,所對應的地址也不相同。在 C 語言中,像?"hello"?這樣的常量字符串在程序中通常只存儲一份在只讀的內存區域。當定義?str3?和?str4?這兩個指針并讓它們指向同一個常量字符串"hello"?時,它們實際上都指向了這個字符串的起始地址。所以當進行?str3 == str4?比較時,比較的是兩個指針所存儲的地址值,因為它們都指向同一個?"hello"?字符串所在的內存位置,所以地址是相同的。其實造成差異的本質原因是前兩個都是在定義字符串變量,也就是字符數組,它本身是沒有地址的,而后兩個常量字符串本身就是有地址的,這里只是將地址取出來存放在指針中。

二、數組指針變量

數組指針變量是一種指針變量,存放的應該是數組的地址,能夠指向數組的指針變量。
int(*p)[10];

這就是數組指針的形式。p是這個變量的名字,*說明這個變量是指針,還剩下int [10]就說明它指向的對象是大小為10的整型數組類型的。

注意:[]的優先級要高于*號的,所以必須加上()來保證p先和*結合。否則這個變量就不是數組指針了,而是指針數組。

那么數組指針變量怎么初始化呢?其實和我們之前初始化指針變量一樣。

我們調試也能看到 &arr p 的類型是完全一致的。
三、二 維數組傳參的本質
#include<stdio.h>
void test(int arr[2][3],int a,int b)
{for (int i = 0; i < a; i++){for (int j = 0; j < b; j++){printf("%d ", arr[i][j]);}printf("\n");}
}
int main()
{int arr[2][3] = { {1,2,3},{2,3,4} };test(arr,2,3);return 0;
}

之前我們使用二維數組傳參都是如上圖的代碼這樣去進行操作的。其實二維數組就是一維數組的數組,也就是二維數組的每個元素是一個一維數組。所以二維數組的數組名表示的就是第一行的地址,是一個一維數組的地址。根據上面的例子,第一行的一維數組的類型就是 int [3] ,所以第一行的地址的類型就是數組指針類型 int(*)[3] 。那就意味著二維數組傳參本質上也是傳遞了地址,傳遞的是第一行這個一維數組的地址,那么形參也是可以寫成指針形式的。如下:

#include<stdio.h>
void test(int(*arr)[3], int a, int b)
{for (int i = 0; i < a; i++){for (int j = 0; j < b; j++){printf("%d ", arr[i][j]);}printf("\n");}
}
int main()
{int arr[2][3] = { {1,2,3},{2,3,4} };test(arr,2,3);return 0;
}

四、函數指針變量

函數指針變量就是存放函數地址的指針變量。我們將剛才的代碼稍微改動一下,我們就會發現函數其實有自己的地址。而且函數名就是函數的地址,當然使用&函數名的方式也可以。
那么函數指針變量是什么樣的呢?我們再將代碼改動一下。
#include<stdio.h>
void test(int(*arr)[3], int a, int b)
{for (int i = 0; i < a; i++){for (int j = 0; j < b; j++){printf("%d ", arr[i][j]);}printf("\n");}
}
int main()
{int arr[2][3] = { {1,2,3},{2,3,4} };test(arr,2,3);void (*p)(int(*arr)[3], int a, int b) = test;return 0;
}

p是變量名,*表示這個變量是指針,前面的void是函數的返回值,后面加的是函數的參數。而且在參數中可以省略具體的變量名,只留下參數的數據類型。在這里就是將a,b去掉,只留下(int,int)。那我們怎么去使用這個函數指針變量呢?

#include<stdio.h>
int add(int a, int b)
{return a + b;
}
int main()
{int (*p)(int, int) = add;printf("%d", p(1, 2));return 0;
}

使用函數指針名加參數就能實現,當然將函數指針名解引用后加參數也能實現,但是直接使用函數指針名編譯器就可以理解這是對函數指針所指向的函數的調用,使用起來會更加方便,一般不推薦顯式解引用來使用函數。

接下來,我們來看兩段c陷阱與缺陷中的代碼。當然,這些代碼只是為了讓我們更加理解指針等知識,其實某些寫法并不推薦。

1.(*(void (*)())0)();

來分析一下代碼,我們能看到中間的void (*)(),知道這是一個函數指針,參數為空,返回值為void,后面還跟著一個0,那我們就可以知道,這里是將0強制類型轉換了,將它變成了函數指針類型,然后將整體解引用,后面再加上空參,就是在調用函數。

2.void (*signal(int , void(*)(int)))(int);

看到signal(int , void(*)(int))應該就能知道這是一個函數名加上參數,一個是整型類型的,一個是函數指針類型的。其實,剩下的void(*)(int)是這個函數的返回值,這種寫法確實比較奇怪,我們在這里也不需要多糾結,這個代碼整體就是一個函數的聲明。

五、typedef 關鍵字

typedef 是用來類型重命名的,可以將復雜的類型,簡單化。比如,你覺得 unsigned int 寫起來不方便,如果能寫成 uint 就方便多了,那么我們可以使用:
typedef unsigned int uint;
//將unsigned int重命名為uint

一般類型都可以這樣進行操作,但是對于數組指針和函數指針稍微有點區別,新的類型名必須在*的右邊

#include <stdio.h>
int add(int x, int y)
{return x + y;
}
int main()
{int arr[5] = { 0 };typedef int(*parr_t)[5];//int(*)[5]->parr_ttypedef void(*pf_t)(int);//void(*)(int)->pf_tparr_t p1 = arr;pf_t p2 = add;return 0;
}

六、函數指針數組

要把函數的地址存到?個數組中,那這個數組就叫函數指針數組,那函數指針的數組如何定義呢?
parr1 先和 [] 結合,說明 parr1是數組,數組的內容是什么呢?是 int (*)() 類型的函數指針。
int (*parr1[3])();

那么函數指針數組有什么用呢,接下來我們來看轉移表。舉例:計算器的?般實現:

#include<stdio.h>
void menu()
{printf("***0.exit***\n");printf("***1.add****\n");printf("***2.sub****\n");printf("***3.mul****\n");printf("***4.div****\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 n;int(*p[5])(int, int) = { 0,add,sub,mul,div };while (1){menu();scanf("%d", &n);if (n > 0 && n < 5){int a, b;printf("請輸入兩個操作數...\n");scanf("%d%d", &a, &b);printf("結果是:%d\n", p[n](a, b));}else if (n == 0){printf("退出計算器\n");break;}elseprintf("輸入有誤\n");}return 0;
}

如上圖的代碼,我們就能實現簡易的加減乘除計算器,這就使用了函數指針數組。那么轉移表又是什么呢?其實它指的就是運用函數指針數組以數組方式去調用里面的函數,使代碼變得更加簡潔。

七、回調函數

回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另?個函數,當這個指針被用來調用其所指向的函數時,被調用的函數就是回調函數。回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用于對該事件或條件進行響應。 我們可以把調用的函數的地址以參數的形式傳遞過去,使用函數指針接收,函數指針指向什么函數就調用什么函數。下面我們就使用回調函數來實現上面說的簡易計算器。
#include<stdio.h>
void menu()
{printf("***0.exit***\n");printf("***1.add****\n");printf("***2.sub****\n");printf("***3.mul****\n");printf("***4.div****\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 is(int(*p)(int, int))
{int a, b;printf("請輸入兩個操作數...\n");scanf("%d%d", &a, &b);printf("結果是:%d\n", p(a, b));
}
int main()
{int n;int(*p[5])(int, int) = { 0,add,sub,mul,div };while (1){menu();scanf("%d", &n);if (n > 0 && n < 5){switch (n){case 1:{is(add);break;}case 2:{is(sub);break;}case 3:{is(mul);break;}case 4:{is(div);break;}}}else if (n == 0){printf("退出計算器\n");break;}elseprintf("輸入有誤\n");}return 0;
}

八、qsort 使用舉例

qsort的作用是對數組的元素進行排序,使用?compar?函數確定順序,將base?指向的數組的?num?個元素進行排序,每個元素大小為size。該函數不返回任何值,但通過對?compar?定義的元素進行基數重新排序來修改指向的數組的內容。我們能發現參數中的指針都是void*類型的,這是因為我們進行操作的數據類型不是固定的。

我們先來看一下compar?函數,它可以比較元素對,并將指向它們的指針作為參數。其返回值如下圖,一般可以簡化成小于返回-1,等于返回0,大于返回1。

1.使用qsort函數排序整型數據

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

2.使用qsort排序結構數據

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu
{char name[20];int age;
};
int cmp_stu_by_age(const void* e1, const void* e2)
{return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int cmp_stu_by_name(const void* e1, const void* e2)
{return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
void test1()
{struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
void test2()
{struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{test1();test2();return 0;
}

通過調試我們可以看出結構體中的數據確實被排序了。

3.qsort函數的模擬實現

使用回調函數,模擬實現qsort(采用冒泡的方式)。
#include <stdio.h>
int int_cmp(const void* p1, const void* p2)
{return (*(int*)p1 - *(int*)p2);
}
void _swap(void* p1, void* p2, int size)
{int i = 0;for (i = 0; i < size; i++){char tmp = *((char*)p1 + i);*((char*)p1 + i) = *((char*)p2 + i);*((char*)p2 + i) = tmp;}
}
void bubble(void* base, int count, int size, int(*cmp)(void*, void*))
{int i = 0;int j = 0;for (i = 0; i < count - 1; i++){for (j = 0; j < count - i - 1; j++){if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0){_swap((char*)base + j * size, (char*)base + (j + 1) * size,size);}}}
}
int main()
{int arr[5] = { 2,3,4,1,5 };bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), int_cmp);for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%d ", arr[i]);}return 0;
}

九、sizeof和strlen的對比

1.sizeof

sizeof 計算變量所占內存內存空間大小的,單位是字節,如果操作數是類型的話,計算的是使用類型創建的變量所占內存空間的大小。sizeof 只關注占用內存空間的大小,不在乎內存中存放什么數據。操作數是變量可以不加括號。
#include <stdio.h>
int main()
{int a = 10;printf("%d\n", sizeof(a));printf("%d\n", sizeof a );printf("%d\n", sizeof(int));return 0;
}

2.strlen

strlen 是C語言庫函數,功能是求字符串長度。函數原型如下:
size_t strlen(const char* str);
統計的是從 strlen 函數的參數 str 中這個地址開始向后, \0 之前字符串中字符的個數。
strlen 函數會?直向后找 \0 字符,直到找到為止,所以可能存在越界查找。
3. sizeof 和 strlen的對比
sizeof:
(1)? sizeof是操作符
(2) sizeof計算操作數所占內存的大小,單位是字節
(3) 不關注內存中存放什么數據
strlen:
(1) strlen是庫函數,使用需要包含頭文件 string.h
(2) srtlen是求字符串長度的,統計的是 \0 之前字符的個數
(3) 關注內存中是否有 \0 ,如果沒有 \0 ,就會持續往后找,可能
會越界

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

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

相關文章

HarmonyOS-高級(一)

文章目錄 一次開發、多端部署自由流轉 &#x1f3e1;作者主頁&#xff1a;點擊&#xff01; &#x1f916;HarmonyOS專欄&#xff1a;點擊&#xff01; ??創作時間&#xff1a;2024年12月09日12點19分 一次開發、多端部署 布局能力 自適應布局 拉伸能力均分能力占比能力縮放…

[DEBUG] pytorch 加速安裝兼容cuda12.6版本(Torch CUDA is not available )

如何使用鏡像源快速安裝兼容 CUDA 12.6 的 PyTorch 在使用 PyTorch 時&#xff0c;有時會遇到 CUDA 版本不兼容的問題。對于 CUDA 12.6&#xff0c;PyTorch 目前尚未直接支持&#xff0c;但可以通過安裝 cu118 版本來兼容。由于 PyTorch 官網下載速度較慢&#xff0c;我們可以…

云計算IaaS-PaaS-SaaS三種服務模式轉至元數據結尾

在當今數字化時代&#xff0c;云計算已經成為推動企業創新與發展的核心力量。而云計算的模型主要有三種&#xff1a;IAAS、PAAS 和 SAAS&#xff0c;它們各自在云計算的龐大體系中扮演著獨特且關鍵的角色&#xff0c;恰似一座大廈的不同樓層&#xff0c;共同構建起強大而靈活的…

【Excel學習記錄】02-單元格格式設置

1.單元格格式工具美化表格 單元格格式位置 選中單元格&#xff0c;右鍵→設置單元格格式 合并居中 跨越合并 字體類型、大小、顏色、填充底紋、邊框 斜線 軟回車&#xff1a;alt enter 格式刷 2.單元格數字格式 格式不影響數值&#xff0c;只是展示形式 日期本質也是數…

【嵌入式系統】第4章 嵌入式最小系統,供電電路,時鐘電路,復位電路,程序下載電路

關注作者了解更多 我的其他CSDN專欄 過程控制系統 工程測試技術 虛擬儀器技術 可編程控制器 工業現場總線 數字圖像處理 智能控制 傳感器技術 嵌入式系統 復變函數與積分變換 單片機原理 線性代數 大學物理 熱工與工程流體力學 數字信號處理 光電融合集成電路…

期權懂|交易個股期權需要注意哪些風險?

期權小懂每日分享期權知識&#xff0c;幫助期權新手及時有效地掌握即市趨勢與新資訊&#xff01; 交易個股期權需要注意哪些風險&#xff1f; 一、交易個股期權需要注意合約到期風險&#xff1a; 需關注到期日&#xff0c;及時平倉或行權&#xff0c;避免合約作廢。二、交易個…

MVC配置文件配置及位置

配置文件位置 默認位置 WEB-INF目錄下&#xff1a;-servlet.xml 指定位置 在web.xml中配置 DispatcherServlet中的contextConfigLocation屬性可以指定配置文件位置 確保配置文件存在于類路徑&#xff08;Resources&#xff09;下 web.xml <?xml version"1.0" …

可視化邏輯表達式編輯器

優質博文&#xff1a;IT-BLOG-CN 一、QueryBuilder介紹 QueryBuilder 是一個用于創建查詢和過濾器的 UI 組件。 QueryBuilder的特點 1、支持的輸入屬性豐富&#xff0c;常見的 字符串&#xff0c;整數&#xff0c;浮點數&#xff0c;布爾類型&#xff0c;日期類型&#xff0…

若依將數據庫更改為SQLite

文章目錄 1. 添加依賴項2. 更新配置文件 application-druid.yml2.1. 配置數據源2.2. 配置連接驗證 3. 更新 MybatisPlusConfig4. 解決 mapper 中使用 sysdate() 的問題4.1. 修改 BaseEntity4.2. 修改 Mapper 5. 更新 YML 配置 正文開始&#xff1a; 前提條件&#xff1a;在您的…

OCP開閉原則

什么是OCP&#xff1f; OCP是軟件七大開發原則當中最基本的一個原則&#xff1a;開閉原則 對什么開&#xff1f;對擴展開放。 對什么閉&#xff1f;對修改關閉。 OCP原則是最核心的&#xff0c;最基本的&#xff0c;其他的六個原則都是為這個原則服務的。 OCP開閉原則的核心是…

Linux下mysql環境的搭建

1.mysql的下載 去MySQL官網下載mysql的linux壓縮包 MySQL :: Download MySQL Community Server 如果下載慢請到網盤中自行下載 通過網盤分享的文件&#xff1a;mysql-8.0.40-1.el7.x86_64.rpm-bundle.tar 鏈接: https://pan.baidu.com/s/1vUJ-VuTwer1nLPT-haQCqw?pwd6342 提…

基于Qwen2-VL模型針對LaTeX OCR任務進行微調訓練 - 多圖推理

基于Qwen2-VL模型針對LaTeX OCR任務進行微調訓練 - 多圖推理 flyfish 基于Qwen2-VL模型針對LaTeX_OCR任務進行微調訓練_-_LoRA配置如何寫 基于Qwen2-VL模型針對LaTeX_OCR任務進行微調訓練_-_單圖推理 基于Qwen2-VL模型針對LaTeX_OCR任務進行微調訓練_-_原模型_單圖推理 基于Q…

圖像識別 | Matlab基于卷積神經網絡(CNN)的寶可夢識別源程序,GUI界面。附詳細的運行說明。

圖像識別 | Matlab基于卷積神經網絡(CNN)的寶可夢識別源程序&#xff0c;GUI界面。附詳細的運行說明。 目錄 圖像識別 | Matlab基于卷積神經網絡(CNN)的寶可夢識別源程序&#xff0c;GUI界面。附詳細的運行說明。預測效果基本介紹程序設計參考資料 預測效果 基本介紹 Matlab基…

設置IMX6ULL開發板的網卡IP的兩種方法(臨時生效和永久有效兩種方法)

設置開發板網卡的IP&#xff0c;有兩種方法。 方法一&#xff1a;臨時生效 第一種方式是臨時設置&#xff0c;只有本次有效&#xff0c;重啟后又要重新設&#xff0c;命令為&#xff1a; ifconfig eth0 192.168.5.9設置成功后可以使用ifconfig命令來查看已設置的 IP 地址。 …

22. Three.js案例-創建旋轉的圓環面

22. Three.js案例-創建旋轉的圓環面 實現效果 知識點 WebGLRenderer (WebGL渲染器) THREE.WebGLRenderer 是Three.js中最常用的渲染器&#xff0c;用于將場景渲染到WebGL畫布上。 構造器 new THREE.WebGLRenderer(parameters) 參數類型描述parametersObject可選參數對象&…

【D3.js in Action 3 精譯_044】5.1 餅圖和環形圖的創建(四):數據標簽的添加

當前內容所在位置&#xff1a; 第五章 餅圖布局與堆疊布局 ?? 5.1 餅圖和環形圖的創建 ?? 5.1.1 準備階段&#xff08;一&#xff09;5.1.2 餅圖布局生成器&#xff08;二&#xff09;5.1.3 圓弧的繪制&#xff08;三&#xff09; ??5.1.4 數據標簽的添加&#xff08;四&…

java全棧day13-后端Web實戰2

接上述查詢部門實現&#xff0c;完成后續要求 一、統一響應結果 1.1步驟 資料如下 對一開始的代碼修改如下 結果如下 1.2測試 指定請求方式 結果 小結 二、前后端聯調測試 資料如下&#xff1a; (不行&#xff0c;一定要不帶空格和不帶中文&#xff0c;要不然啟動不了試了半天…

AWS sdk for s3 - S3 client

背景 在產品環境上通過 http 的方式訪問 aws s3 是不安全的&#xff0c;需要使用aws sdk 提供的接口來訪問 技術實現 項目中使用的是java 1. 在gradel 中引用對應的aws 包 implementation ‘software.amazon.awssdk:s3:2.20.80’ // aws sdk implementation ‘software.am…

Android的SurfaceView和TextureView介紹

文章目錄 前言一、什么是SurfaceView &#xff1f;1.1 SurfaceView 使用示例1.2 SurfaceView 源碼概述1.3 SurfaceView 的構造與初始化1.4 SurfaceHolder.Callback 回調接口1.5 SurfaceView 渲染機制 二、什么是TextureView&#xff1f;2.1 TextureView 使用示例2.2 TextureVie…

vscode 排除文件夾搜索

排除的文件夾 node_modules/,dist/