摘要:故使用無具體類型,又稱通用類型,即可以接收任意類型的指針,但是無法進行指針運算解引用,整數等。求指針所占字節而不是解引用訪問權限大小。數組就是整個數組的大小,數組元素則是數組元素的大小,指針大小都為。
續前文《C語言進階:指針進階》
回調函數:通過函數指針調用的函數,或者說使用函數指針調用函數這樣的機制被稱為回調函數。回調函數不由實現方直接調用,而是作為特殊條件下的響應。
概念無關緊要,理解并熟練運用這種方法才更為重要。
qsort
qsort
函數邏輯void qsort(void* base, size_t num, size_t width, int (*cmp)(const void* e1, const void* e2));
qsort
無返回值,有四個參數。分別為base
:起始地址,num
:元素個數,width
:元素大小以及compare
:比較函數。可與冒泡排序作對比。
//冒泡排序void Bubble_sort(int arr[], int sz) { for (int i = 0; i < sz - 1; i++) { for (int j = 0; j < sz - 1 - i; j++) { //比較函數 if (arr[j] > arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } }}
與冒泡排序作對比發現,冒泡排序僅需起始地址和元素個數即可,暗含了其他信息。由于過度具體化,冒泡排序只能排序整型數組,且比較函數過于簡單無需多帶帶列出。
因為qsort
排序可適用于多種類型如浮點型,字符型,自定義類型的數據,故無法規定具體類型,所以需要多個參數去描述元素的基本信息。
qsort
之所以能夠適應多種數據,是因為參數void* base
再搭配上num
和width
就描述出任意一種類型。
為什么將參數
base
的類型定義為void*
呢?如下述代碼所示。
char* p1 = &a;//從int*到char*類型不兼容char* p2 = &f;//從float*到char*類型不兼容void* p1 = &a;void* p2 = &f;
確定類型的地址之間直接賦值會提示類型不兼容,強制轉化也可能會導致精度丟失。
故使用無(具體)類型void*
,又稱通用類型,即可以接收任意類型的指針,但是無法進行指針運算(解引用, ± ± ±整數等)。
p1++; *p1; p1 - p2; p1 > p2;//表達式必須是指向完整對象類型的指針
base
:用于存入數據的起始地址。類型定義為void*
,可接受任意類型的指針。
num
:待排序的元素個數。
width
:元素寬度,所占字節大小。
明確了排序的起始位置,元素個數和元素大小,貌似已經夠了。但是并無法排序所有類型,因此必須自定義一個抽象的比較函數指定元素的比較方式。
cmp
:比較函數,用于指定元素的比較方式。
elem1
小于elem2
,返回值小于0elem1
大于elem2
,返回值大于0elem1
等于elem2
,返回值為0elem1
,elem2
:進行比較的兩個元素的地址作參數。
qsort
可以說是一個半庫函數半自定義函數。自定義在于其函數最后一個參數為比較函數,該函數內部實現自由,但返回值必須按照規定返回相應的數值。
需要qsort
函數排序各種類型的數據,
base
起始地址不可為固定的指針類型,只能用void*
。qsort
實現冒泡排序//比較函數:整型#include int int_cmp(const void* e1, const void* e2) { return *(int*)e1 - *(int*)e2;}int main() { int arr[10] = { 9,8,7,6,5,4,3,2,1,0 }; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), int_cmp); return 0;}
比較函數int_com
不需要傳參,作為回調函數由qsort
直接調用。比較函數的傳參過程由qsort
內部實現。
qsort
實現結構體排序#include struct stu { char* name; short age; float score;};//按照成績排序int score_cmp(const void* e1, const void* e2) { //1.升序 return ((struct stu*)e1)->score - ((struct stu*)e2)->score; //2.降序 return ((struct stu*)e2)->score - ((struct stu*)e1)->score;}//按照名字排序int name_cmp(const void* e1,const void* e2) { return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);}int main() { struct stu s[3] = { { "張三", 22, 99.5f },{ "李四", 21, 66.4f },{ "王五", 18, 80.1f } }; int sz = sizeof(s) / sizeof(s[0]); //1. qsort(s, sz, sizeof(s[0]), name_cmp); //2. qsort(s, sz, sizeof(s[0]), score_cmp); return 0;}
由此可得,提取出一個比較函數,具體交換的方式由qsort
內部實現。
qsort
用
qsort
的函數邏輯,實現冒泡排序。
//打印函數void print_arr(int arr[],int sz) { for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); }}//交換函數void Swap(char* buf1, char* buf2, size_t width) { for (size_t i = 0; i < width; i++) {//寬度次 char tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; }}//比較函數int cmp(const void* e1, const void* e2) { return *(int*)e1 - *(int*)e2;}//排序函數void my_bubble_sort(void* base, size_t num, size_t width, int(*cmp)(const void* e1, const void* e2)) { for (size_t i = 0; i < num - 1; i++) { for (size_t j = 0; j < num - 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[10] = { 9,8,7,6,5,4,3,2,1,0 }; int sz = sizeof(arr) / sizeof(arr[0]); my_bubble_sort(arr, sz, sizeof(arr[0]), cmp); print_arr(arr, sz); return 0;}
地址統一強轉為char*
,以最小字節單位一個字節進行比較和交換,使代碼更具有普適性。
如果需要排序結構體則只需要在前文代碼中主函數里替換my_qsort
且把比較函數替換Name_cmp
即可。
//1.my_qsort(s, sz, sizeof(s[0]), name_cmp);//2.my_qsort(s, sz, sizeof(s[0]), score_cmp);
?
注意點。數組名代表整個數組:
sizeof(數組名)
&數組名
除此以外,數組名都是代表首元素地址。
int a[] = { 1,2,3,4 };printf("%d/n", sizeof(a));//16printf("%d/n", sizeof(a + 0));//4/8printf("%d/n", sizeof(*a));//4printf("%d/n", sizeof(a + 1));//4/8printf("%d/n", sizeof(a[1]));//4printf("%d/n", sizeof(&a));//4/8printf("%d/n", sizeof(*&a));//16printf("%d/n", sizeof(&a + 1));//4/8printf("%d/n", sizeof(&a[0]));//4/8printf("%d/n", sizeof(&a[0] + 1));//4/8
只有數組名多帶帶放在sizeof
內部才是整個數組。
a+0
放在sizeof
內部表示首元素地址+0。
只要是地址,不管是什么類型的地址大小都是4/8
基本類型指針,數組指針,函數指針大小都是4/8個字節,故sizeof(&a)=sizeof(int(*)[4])=4
。sizeof()
求指針所占字節而不是解引用訪問權限大小。
*
和&
在一起會抵消。
sizeof(*&a)
,&a為整個數組的地址類型int(*)[4]
,解引用后int[4]
大小為16。
char arr[] = { "a","b","c","d","e","f" };printf("%d/n", sizeof(arr));//6printf("%d/n", sizeof(arr + 0));//4/8printf("%d/n", sizeof(*arr));//1printf("%d/n", sizeof(arr[1]));//1printf("%d/n", sizeof(&arr));//4/8printf("%d/n", sizeof(&arr + 1));//4/8printf("%d/n", sizeof(&arr[0] + 1));//4/8printf("%d/n", strlen(arr));//隨機值xprintf("%d/n", strlen(arr + 0));//隨機值xprintf("%d/n", strlen(*arr));//報錯printf("%d/n", strlen(arr[1]));//報錯printf("%d/n", strlen(&arr));//隨機值xprintf("%d/n", strlen(&arr + 1));//隨機值x-6printf("%d/n", strlen(&arr[0] + 1));//隨機值x-1
sizeof(*arr)
,*arr
對首元素地址解引用,計算首元素所占空間大小。
strlen(*arr)
,*arr
依然是首元素,strlen
把a也就是97當成地址,訪問到非法內存所以報錯。
2.strlen(&arr)
雖然是整個數組的地址,但依然是從首元素開始的,所以strlen
依然從第一個元素開始找。
? strlen(&arr+1)
,先計算&arr+1
然后再傳參過去,也就是跳過了整個數組去找。
sizeof
和strlen
的區別
sizeof
— 操作符 — 以字節為單位,求變量或類型所創建變量的所占空間的大小
sizoef
不是函數,計算類型是必須帶上類型說明符()
。sizoef
內容不參與運算,在編譯期間便轉化完成。
strlen
— 庫函數 — 求字符串長度即字符個數,遇/0
停止。庫函數,計算字符串長度沒有遇到
/0
就會一直持續下去。返回類型size_t
,參數char* str
,接收的內容都會認為是char*
類型的地址。
一個求變量所占空間,一個求字符串大小,二者本身是沒有關系的,但總有人把二者綁在一起“混淆視聽”。
首先明確二者的區別:
//1.字符初始化數組char arr[] = { "a","b","c","d","e","f" };//[a] [b] [c] [d] [e] [f]//2.字符串初始化數組char arr[] = "abcdef";//[a] [b] [c] [d] [e] [f] [/0]
字符初始化數組,存了什么元素數組里就是什么元素。而字符串初始化數組,除了字符串中可見的字符外,還有字符串末尾隱含的
/0
。/0
存在于字符串的末尾,是自帶的,雖不算字符串內容,但是字符串中的字符。
char arr[] = "abcdef";printf("%d/n", sizeof(arr));//7printf("%d/n", sizeof(arr + 0));//4/8printf("%d/n", sizeof(*arr));//1printf("%d/n", sizeof(arr[1]));//1printf("%d/n", sizeof(&arr));//4/8printf("%d/n", sizeof(&arr + 1));//4/8printf("%d/n", sizeof(&arr[0] + 1));//4/8printf("%d/n", strlen(arr));//6printf("%d/n", strlen(arr + 0));//6printf("%d/n", strlen(*arr));//報錯printf("%d/n", strlen(arr[1]));//報錯printf("%d/n", strlen(&arr));//6printf("%d/n", strlen(&arr + 1));//隨機值printf("%d/n", strlen(&arr[0] + 1));//5
sizeof
計算變量的長度,變量可以是數組,數組元素以及指針。數組就是整個數組的大小,數組元素則是數組元素的大小,指針大小都為4/8。strlen
把傳過來的參數都當作地址,是地址就從該地址處向后遍歷找/0
,不是地址當作地址非法訪問就報錯。char* p = "abcdef";
"abcdef"
是常量字符串,用一個字符指針p
指向該字符串,實質是p
存入了首字符a
的地址。由于字符串在內存中連續存放,依此特性便可以遍歷訪問整個字符串。
char* p = "abcdef";
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/119679.html
摘要:本章節在此基礎上,對語言階段指針進行更深層次的研究。數組指針的類型由數組類型決定,先找出數組的類型去掉名就是類型。相當于數組指針所指向數組的數組名。數組指針指向整個數組,將其看作二維數組并解引用得到一行的首元素,從而遍歷訪問。 ...
摘要:函數的返回值為指針就按照字面意思,指針函數的定義顧名思義,指針函數即返回指針的函數。 目錄 前言指針與函數函數的返回值為指針作為函數參數的指針指針函數可以改變變量...
摘要:釋放不完全導致內存泄漏。既然把柔性數組放在動態內存管理一章,可見二者有必然的聯系。包含柔性數組的結構用進行動態內存分配,且分配的內存應大于結構大小,以滿足柔性數組的預期。使用含柔性數組的結構體,需配合以等動態內存分配函數。 ...
摘要:三文讀透指針上篇本文將繼續介紹有關函數指針的相關內容。在大型工程里,函數指針應用還是挺普遍的。首先看閱讀下面兩段有趣的代碼出自語言陷阱與缺陷看看他們是什么意思代碼代碼函數指針數組函數指針數組,即存放函數指針的數組。 ...
閱讀 3087·2021-11-24 09:38
閱讀 1338·2021-09-22 15:27
閱讀 2977·2021-09-10 10:51
閱讀 1512·2021-09-09 09:33
閱讀 925·2021-08-09 13:47
閱讀 2091·2019-08-30 13:05
閱讀 897·2019-08-29 15:15
閱讀 2431·2019-08-29 12:21