深入理解指針(詳解)
- 前言
- 一、指針是什么
- 1、指針的定義
- 2、指針的大小
- 二、指針類型
- 1、類型
- 2、不同類型的意義
- 三、野指針
- 1、野指針形成原因
- 2、如何避免野指針
- 四、指針的運算
- 1、 指針±整數
- 2、指針-指針
- 3、指針的關系運算
- 五、const修飾指針
- 1、consr修飾變量
- 2、const修飾指針變量
- 六、指針的使用和傳址調用
- 1、strlen的模擬實現
- 2、指針的傳址調用和傳址調用
- 七、數組名的理解
- 1、數組名本質
- 2、指針訪問數組
- 八、二級指針
- 1、二級指針的概念
- 九、字符指針變量
- 十、指針數組
- 十一、數組指針
- 1、定義
- 2、使用
- 十二、數組參數與指針參數
- 1、一維數組、指針傳參
- 2、二級數組、指針傳參
- 十三、函數指針
- 1、函數指針的定義
- 2、函數指針的使用
- 3、函數指針數組
- 4、函數指針數組的使用 - 模擬計算器
- 5、指向函數指針數組的指針
- 十四、回調函數
- 1、定義
- 2、使用-qsort
前言
指針(Pointer)是C語言中最強大、最靈活,但也最容易令人困惑的概念之一。它直接操作內存地址,賦予程序員底層控制能力,使得C語言在系統編程、嵌入式開發、數據結構等領域占據不可替代的地位。然而,指針的不當使用也常常導致程序崩潰、內存泄漏、數據損壞等嚴重問題。
為什么指針如此重要?如何正確理解指針的本質?指針與數組、函數、動態內存管理之間有何聯系?如何避免指針使用中的常見陷阱?
本文將系統性地剖析指針的核心概念,包括:
- 指針的本質:內存地址與變量訪問
- 指針的基本操作(聲明、初始化、解引用)
- 指針與數組、字符串的關系
- 多級指針(指針的指針)與指針運算
- 函數指針與回調機制
- 動態內存管理與常見錯誤(懸垂指針、內存泄漏)
無論你是C語言初學者,還是希望深入理解底層機制的開發者,本文都將幫助你掌握指針的精髓,并寫出更高效、更安全的代碼。
讓我們從指針的基礎概念開始,逐步深入,揭開它神秘的面紗!
一、指針是什么
1、指針的定義
在計算機科學中,指針(Pointer)是編程語言中的一個對象,利用地址,它的值直接指向(points to)存在電腦存儲器中另一個地方的值。由于通過地址能找到所需的內存單元,可以說地址指向該內存單元。因此,將地址形象化的稱為“指針”。意思是通過它能找到以它為地址的內存單元。
這是官方對指針的定義,其實我們可以理解為:在內存中,內存被細分為一個個大小為一個字節的內存單元,每一個內存單元都有自己對應的地址。
注意:
我們可以將這些內存單元形象地看成一個個的房間,將內存單元(房間)對應的地址形象地看成房間的門牌號。而我們通過門牌號(地址)就可以唯一的找到其對應的房間(內存單元),即地址指向對應內存單元。所以說,可以將地址形象化的稱為“指針”。
指針變量是用于存放地址的變量。(存放在指針中的值都將被當作地址處理)
#include<stdio.h>
int main()
{int a = 10;//在內存中開辟一塊空間int* p = &a;//將a的地址取出,放到指針變量p中return 0;
}
總結:
- 指針變量是用于存放地址的變量。(存放在指針中的值都將被當作地址處理)
2、指針的大小
對于32位的機器,即有32根地址線,因為每根地址線能產生正電(1)或負電(0),所以在32位的機器上能夠產生的地址信號就是32個0/1組成的二進制序列。一共 2 32 個地址。同樣的算法,在64位的機器上一共能產生 264 個不同的地址。
- 232 可以用32個bit位進行存儲,而8個bit位等價于1個字節,所以在32位的平臺下指針的大小為4個字節。
- 264 可以用64個bit位進行存儲,所以在64位的平臺下指針的大小為8個字節。
總結:
在32位平臺下指針的大小為4個字節,在64位平臺下指針的大小為8個字節。
二、指針類型
1、類型
我們知道,變量的類型有int,float,char等。那么指針有沒有類型呢?回答是肯定的。
指針的定義方式是type+ *
**char *** 類型的指針存放的是char類型的變量地址;
**int *** 類型的指針存放的是int類型的變量地址;
**float *** 類型的指針存放的是float類型的變量地址等。
2、不同類型的意義
- 指針±整數
若指針類型為int * 的指針+1,那么它將跳過4個字節的大小指向4個字節以后的內容:
- 指針解引用
指針的類型決定了指針解引用的時候能夠訪問幾個字節的內容。
若指針類型為int *,那么將它進行解引用操作,它將可以訪問從指向位置開始向后4個字節的內容:
若指針類型為char *,那么將它進行解引用操作,它將可以訪問從指向位置開始向后1個字節的內容,以此類推.
總結:
- 指針的類型決定了指針向前或向后走一步有多大距離。
- 指針的類型決定了指針在進行解引用操作時,能向后訪問的空間大小。
三、野指針
概念: 野指針就是指向位置是不可知的(隨機的、不正確的、沒有明確限制的)指針。
1、野指針形成原因
- 野指針的成因
#include<stdio.h>
int main()
{int* p;*p = 10;return 0;
}
局部指針變量p未初始化,默認為隨機值,所以這個時候的p就是野指針。
- 指針越界訪問
#include<stdio.h>
int main()
{int arr[10] = { 0 };int* p = &arr[0];int i = 0;for (i = 0; i < 11; i++){*p++ = i;}return 0;
}
當指針指向的范圍超出arr數組時,p就是野指針。
- 指針指向的空間被釋放
#include<stdio.h>
int* test()
{int a = 10;return &a;
}
int main()
{int* p = test();return 0;
}
指針變量p得到地址后,地址指向的空間已經釋放了,所以這個時候的p就是野指針。(局部變量出了自己的作用域就被釋放了)
2、如何避免野指針
- 指針初始化
當指針明確知道要存放某一變量地址時,在創建指針變量時就存放該變量地址。
當不知道指針將要用于存放哪一變量地址時,在創建指針變量時應置為空指針(NULL)。
#include<stdio.h>
int main()
{int a = 10;int* p1 = &a;//明確知道存放某一地址int* p2 = NULL;//不知道存放哪一地址時置為空指針return 0;
}
- 小心指針越界
- 指針指向的空間被釋放后及時置為NULL
- 使用指針之前檢查有效性
- 在使用指針之前需確保其不是空指針,因為空指針指向的空間是無法訪問的。
四、指針的運算
1、 指針±整數
#include <stdio.h>
int main()
{int n = 10;char *pc = (char*)&n;int *pi = &n;printf("%p\n", &n);printf("%p\n", pc);printf("%p\n", pc+1);printf("%p\n", pi);printf("%p\n", pi+1);return 0;
}
運行結果如下:
我們可以看出, char* 類型的指針變量+1跳過1個字節, int* 類型的指針變量+1跳過了4個字節。
這就是指針變量的類型差異帶來的變化。指針+1,其實跳過1個指針指向的元素。指針可以+1,那也可以-1。
結論:指針的類型決定了指針向前或者向后??步有多?(距離)。
2、指針-指針
指針-指針的絕對值是是兩個指針之間的元素個數。
//指針-指針
#include <stdio.h>
int my_strlen(char *s)
{char *p = s;while(*p != '\0' )p++;return p-s;
}int main()
{printf("%d\n", my_strlen("abc"));return 0;
}
運行結果:
3、指針的關系運算
//指針的關系運算
#include <stdio.h>
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int *p = &arr[0];int sz = sizeof(arr)/sizeof(arr[0]);while(p<arr+sz) //指針的???較 {printf("%d ", *p);p++;}return 0;
}
五、const修飾指針
1、consr修飾變量
變量是可以修改的,如果把變量的地址交給?個指針變量,通過指針變量的也可以修改這個變量。
但是如果我們希望?個變量加上?些限制,不能被修改,怎么做呢?這就是const的作?。
#include <stdio.h>
int main()
{int m = 0;m = 20;//m是可以修改的 const int n = 0;n = 20;//n是不能被修改的 return 0;
}
上述代碼中n是不能被修改的,其實n本質是變量,只不過被const修飾后,在語法上加了限制,只要我們在代碼中對n就?修改,就不符合語法規則,就報錯,致使沒法直接修改n。但是如果我們繞過n,使?n的地址,去修改n就能做到了,雖然這樣做是在打破語法規則。
我們可以看到這??個確實修改了,但是我們還是要思考?下,為什么n要被const修飾呢?就是為了不能被修改,如果p拿到n的地址就能修改n,這樣就打破了const的限制,這是不合理的,所以應該讓p拿到n的地址也不能修改n,那接下來怎么做呢?
2、const修飾指針變量
?般來講const修飾指針變量,可以放在的左邊,也可以放在的右邊,意義是不?樣的。
#include <stdio.h>
//代碼1 - 測試?const修飾的情況
void test1()
{int n = 10;int m = 20;int *p = &n;*p = 20;//ok?p = &m; //ok?
}//代碼2 - 測試const放在*的左邊情況
void test2()
{int n = 10;int m = 20;const int* p = &n;*p = 20;//ok?p = &m; //ok?
}
//代碼3 - 測試const放在*的右邊情況
void test3()
{int n = 10;int m = 20;int * const p = &n;*p = 20; //ok?p = &m; //ok?
}
//代碼4 - 測試*的左右兩邊都有const
void test4()
{int n = 10;int m = 20;int const * const p = &n;*p = 20; //ok?p = &m; //ok?
}
int main()
{//測試?const修飾的情況 test1();//測試const放在*的左邊情況 test2();//測試const放在*的右邊情況 test3();//測試*的左右兩邊都有const test4();return 0;
}
結論:const修飾指針變量的時候
- const如果放在*的左邊,修飾的是指針指向的內容,保證指針指向的內容不能通過指針來改變。但是指針變量本?的內容可變。
- const如果放在*的右邊,修飾的是指針變量本?,保證了指針變量的內容不能修改,但是指針指向的內容,可以通過指針改變。
六、指針的使用和傳址調用
1、strlen的模擬實現
庫函數strlen的功能是求字符串?度,統計的是字符串中 \0 之前的字符的個數。
函數原型如下:
size_t strlen ( const char * str );
參數str接收?個字符串的起始地址,然后開始統計字符串中 \0 之前的字符個數,最終返回?度。
如果要模擬實現只要從起始地址開始向后逐個字符的遍歷,只要不是 \0 字符,計數器就+1,這樣直到 \0 就停?。
int my_strlen(const char * str)
{int count = 0;assert(str);while(*str){count++;str++;}return count;
}
int main()
{int len = my_strlen("abcdef");printf("%d\n", len);return 0;
}
2、指針的傳址調用和傳址調用
傳址調?,可以讓函數和主調函數之間建?真正的聯系,在函數內部可以修改主調函數中的變量;所以未來函數中只是需要主調函數中的變量值來實現計算,就可以采?傳值調?。如果函數內部要修改主調函數中的變量的值,就需要傳址調?。
七、數組名的理解
1、數組名本質
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
這?我們使? &arr[0] 的?式拿到了數組第?個元素的地址,但是其實數組名本來就是地址,?且是數組?元素的地址。
但是數組名是首元素地址,如何理解下面代碼?
其實數組名就是數組?元素(第?個元素)的地址是對的,但是有兩個例外:
- sizeof(數組名),sizeof中單獨放數組名,這?的數組名表?整個數組,計算的是整個數組的??,單位是字節
- &數組名,這?的數組名表?整個數組,取出的是整個數組的地址(整個數組的地址和數組?元素的地址是有區別的)
除此之外,任何地?使?數組名,數組名都表??元素的地址。
2、指針訪問數組
數組名arr是數組?元素的地址,可以賦值給p,其實數組名arr和p在這?是等價的。那我們可以使?arr[i]可以訪問數組的元素,那p[i]是否也可以訪問數組呢?
同理arr[i]應該等價于*(arr+i),數組元素的訪問在編譯器處理的時候,也是轉換成?元素的地址+偏移量求出元素的地址,然后解引?來訪問的。
注意:在數組傳參的時候,傳遞的是數組名,也就是說本質上數組傳參傳遞的是數組?元素的地址。
八、二級指針
1、二級指針的概念
對于?級指針的運算有:
- *ppa 通過對ppa中的地址進?解引?,這樣找到的是 pa , *ppa 其實訪問的就是 pa
- **ppa 先通過 *ppa 找到 pa ,然后對 pa 進?解引?操作: *pa ,那找到的是 a
九、字符指針變量
我們知道,在指針的類型中有一種指針類型叫字符指針char * 。
字符指針的一般使用方法為:
#include<stdio.h>
int main()
{char ch = 'w';char* p = &ch;return 0;
}
代碼中,將字符變量ch的地址存放在了字符指針p中。
其實,字符指針還有另一種使用方式:
#include<stdio.h>
int main()
{char* p = "hello csdn.";printf("%c\n", *p);//打印字符'h'printf("%s\n", p);//打印字符串"hello csdn."return 0;
}
#include<stdio.h>
int main()
{char* p = "hello csdn.";printf("%c\n", *p);//打印字符'h'printf("%s\n", p);//打印字符串"hello csdn."return 0;
}
代碼中,字符指針p中存放的并非字符串"hello csdn.",字符指針p中存放的是字符串"hello csdn.“的首元素地址,即字符’h’的地址。
所以,當對字符指針p進行解引用操作并以字符的形式打印時只能打印字符’h’。我們知道,打印一個字符串只需要提供字符串的首元素地址即可,既然字符指針p中存放的是字符串的首元素地址,那么我們只要提供p(字符串首地址)并以字符串的形式打印,便可以打印字符串"hello csdn.”。
注意:代碼中的字符串"hello csdn."是一個常量字符串。
這里有一道題目,可以幫助我們更好的理解字符指針和常量字符串:
#include <stdio.h>
int main()
{char str1[] = "hello csdn.";char str2[] = "hello csdn.";char *str3 = "hello csdn.";char *str4 = "hello csdn.";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;
}
題目中str1和str2是兩個字符數組,比較str1和str2時,相當于比較數組str1和數組str2的首元素地址,而str1與str2是兩個不同的字符數組,創建數組str1和數組str2是會開辟兩塊不同的空間,它們的首元素地址當然不同。
而str3和str4是兩個字符指針,它們指向的都是常量字符串"hello csdn."的首元素地址,所以str3和str4指向的是同一個地方。
注意:常量字符串與普通字符串最大的區別是,常量字符串是不可被修改的字符串,既然不能被修改,那么在內存中沒有必要存放兩個一模一樣的字符串,所以在內存中相同的常量字符串只有一個。
十、指針數組
指針數組也是數組,是用于存放指針的數組。
int* arr3[5];
char* arr4[10];//數組arr4包含10個元素,每個元素是一個一級字符型指針。
char** arr5[5];//數組arr5包含5個元素,每個元素是一個二級字符型指針。
十一、數組指針
1、定義
我們已經知道了,整型指針是指向整型的指針,字符指針是指向字符的指針,那么數組指針應該就是指向數組的指針了。整型指針和字符指針,在使用時只需取出某整型/字符型的數據的地址,并將地址存入整型/字符型指針即可。
#include<stdio.h>
int main()
{int a = 10;int* pa = &a;//取出a的地址存入整型指針中char ch = 'w';char* pc = &ch;//取出ch的地址存入字符型指針中return 0;
}
數組指針也是一樣,我們只需取出數組的地址,并將其存入數組指針即可。
#include<stdio.h>
int main()
{int arr[10] = { 0 };int(*p)[10] = &arr;//&arr - 數組的地址return 0;
}
解釋:p先和結合,說明p是?個指針變量,然后指針指向的是?個??為10個整型的數組。所以p是?個指針,指向?個數組,叫數組指針。
這?要注意:[]的優先級要?于號的,所以必須加上()來保證p先和*結合。
2、使用
數組指針有一個簡單的使用案例,那就是打印二維數組:
#include<stdio.h>
void print(int(*p)[5], int row, int col)
{int i = 0;for (i = 0; i < row; i++)//行數{int j = 0;for (j = 0; j < col; 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 } };print(arr, 3, 5);//傳入二維數組名,即二維數組首元素地址,即二維數組第一行的地址return 0;
}
在這里我們打印一個三行五列的二維數組。傳參時我們傳入二維數組的數組名,明確打印的起始位置;傳入行數和列數,明確打印的數據范圍。
通過上面對&數組名和數組名的認識,我們知道了這里傳入的數組名代表的是二維數組的首元素地址,而二維數組的首元素第一行的元素,即傳入的是一維數組的地址,所以我們必須用數組指針進行接收。
打印時,通過表達式 * (*(p+i)+j ) 鎖定打印目標:
十二、數組參數與指針參數
1、一維數組、指針傳參
總結:?維數組傳參,形參的部分可以寫成數組的形式,也可以寫成指針的形式
2、二級數組、指針傳參
void test1(int arr[2][3], int row, int column)
{for (int i = 0; i < row; i++){for (int j = 0; j < column; j++){printf("%d ", arr[i][j]);}printf("\n");}
}void test2(int(*p)[3], int row, int column)
{for (int i = 0; i < row; i++){for (int j = 0; j < column; j++){printf("%d ", *(*(p+i) + j));}printf("\n");}
}#include<stdio.h>
int main()
{int arr[2][3] = { {1,2,3},{7,8,9} };test1(arr,2,3);test2(arr, 2, 3);return 0;
}
所以,根據數組名是數組?元素的地址這個規則,?維數組的數組名表?的就是第??的地址,是?維數組的地址。第??的?維數組的類型就是 int [3] ,所以第??的地址的類型就是數組指針類型 int(*)[3] 。那就意味著?維數組傳參本質上也是傳遞了地址,傳遞的是第??這個?維數組的地址,那么形參也是可以寫成指針形式的。
總結:?維數組傳參,形參的部分可以寫成數組,也可以寫成指針形式。
十三、函數指針
1、函數指針的定義
2、函數指針的使用
我們知道,整型指針是指向整型的指針,數組指針是指向數組的指針,其實,函數指針就是指向函數的指針。
和學習數組指針一樣,學習函數指針我們也需要知道三點:
( )的優先級要高于 * 。
一個變量除去了變量名,便是它的變量類型。
一個指針變量除去了變量名和 * ,便是指針指向的內容的類型。
#include<stdio.h>
int Add(int x, int y)
{return x + y;
}
int main()
{int(*p)(int, int) = &Add;//取出函數的地址放在函數指針p中return 0;
}
那么,函數指針p的類型我們是如何創建的呢?
- 首先,p是一個指針,所以必須先與 * 結合,而( )的優先級高于 * ,所以我們要把 * 和p用括號括起來,讓它們先結合。
- 指針p指向的內容,即函數Add的類型是int (int,int),所以函數指針p就變成了int(*p)(int,int)。
- 去掉變量名p后,便是該函數指針的變量類型int( * )(int,int)。
知道了如何創建函數指針,那么函數指針應該如何使用呢?
- 函數指針的賦值
對于數組來說,數組名和&數組名它們代表的意義不同,數組名代表的是數組首元素地址,而&數組名代表的是整個數組的地址。但是對于函數來說,函數名和&函數名它們代表的意義卻是相同的,它們都代表函數的地址(畢竟你也沒有聽說過函數有首元素這個說法吧)。所以,當我們對函數指針賦值時可以賦值為&函數名,也可以賦值為函數名。
int(*p)(int, int) = &Add;int(*p)(int, int) = Add;
- 通過函數指針調用函數
**方法一:**我們知道,函數指針存放的是函數的地址,那么我們將函數指針進行解引用操作,便能找到該函數了,于是就可以通過函數指針調用該函數。
#include<stdio.h>
int Add(int x, int y)
{return x + y;
}
int main()
{int a = 10;int b = 20;int(*p)(int, int) = &Add;int ret = (*p)(a, b);//解引用找到該函數printf("%d\n", ret);return 0;
}
可以理解為, * 和&是兩個相反的操作符,像正號(+)和負號(-)一樣,一個 * 操作符可以抵消一個&操作符。
**方法二:**我們在函數指針賦值中說到,函數名和&函數名都代表函數的地址,我們可以賦值時直接賦值函數名,那么通過函數指針調用函數的時候我們就可以不用解引用操作符就能找到函數了。
#include<stdio.h>
int Add(int x, int y)
{return x + y;
}
int main()
{int a = 10;int b = 20;int(*p)(int, int) = Add;int ret = p(a, b);//不用解引用printf("%d\n", ret);return 0;
}
3、函數指針數組
int(*pArr[10])(int, int);//數組pArr有10個元素,每個元素的類型是int(*)(int,int)
函數指針數組的創建只需在函數指針創建的基礎上加上[ ]即可。
比如,你要創建一個函數指針數組,這個數組中存放的函數指針的類型均為int(*)(int,int),如果你要創建一個函數指針為該類型,那么該函數指針的寫法為int(*p)(int,int),現在你要創建一個存放該指針類型的數組,只需在變量名的后面加上[ ]即可,int(*pArr[10])(int,int)。
4、函數指針數組的使用 - 模擬計算器
函數指針數組一個很好的運用場景,就是計算機的模擬實現:
#include<stdio.h>
//菜單
void menu()
{printf("|----------------------------|\n");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;
}#include<stdio.h>
int main()
{int input = 0;//輸入選項int a = 0;//第一個操作數int b = 0;//第二個操作數int ret = 0;//計算結果int(*Parr[5])(int,int) = {0,Add,Sub,Mul,Div};//加0是因為讓下標剛好對應選項int sz = sizeof(Parr) / sizeof(Parr[0]);do {menu();printf("請輸入:\n");scanf_s("%d", &input);if (input == 0){printf("程序退出!\n");break;}else if (input > 0 && input < sz){printf("請輸入兩個需要計算的數:");scanf_s("%d %d", &a, &b);ret = Parr[input](a, b);printf("ret = %d\n", ret);}else{printf("輸入錯誤!請重新輸入!");}} while (input);return 0;
}
代碼中,函數指針數組存放的是一系列參數和返回類型相同的函數名,即函數指針。將0放在該函數指針數組的第一位是為了讓用戶輸入的數字input與對應的函數指針下標相對應。
該代碼若不使用函數指針數組,而選擇使用一系列的switch分支語句當然也能達到想要的效果,但會使代碼出現許多重復內容,而且當以后需要增加該計算機功能時又需要增加一個case語句,而使用函數指針數組,當你想要增加計算機功能時只需在數組中加入一個函數名即可。
5、指向函數指針數組的指針
既然存在函數指針數組,那么必然存在指向函數指針數組的指針。
int(*p)(int, int);//函數指針int(*pArr[5])(int, int);//函數指針數組int(*(*pa)[5])(int, int) = &pArr;//指向函數指針數組的指針
所以pa就是一個指向函數指針數組的指針,該函數指針數組中每個元素類型是int(*)(int, int)。
十四、回調函數
1、定義
回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數時,我們就說這是回調函數。
#include<stdio.h>
void test1()
{printf("hello\n");
}
void test2(void(*p)())
{p(); //指針p被用來調用其所指向的函數
}
int main()
{test2(test1);//將test1函數的地址傳遞給test2return 0;
}
在該代碼中test1函數不是由該函數的實現方直接調用,而是將其地址傳遞給test2函數,在test2函數中通過函數指針間接調用了test1函數,那么函數test1就被稱為回調函數。
2、使用-qsort
- qsort函數的第一個參數是待排序的內容的起始位置;
- 第二個參數是從起始位置開始,待排序的元素個數;
- 第三個參數是待排序的每個元素的大小,單位是字節;
- 第四個參數是一個函數指針。
qsort函數的返回類型為void。
qsort函數的第四個參數是一個函數指針,該函數指針指向的函數的兩個參數的參數類型均為const void*,返回類型為int。當參數e1小于參數e2時返回小于0的數;當參數e1大于參數e2時返回大于0的數;當參數e1等于參數e2時返回0。
列如,我們要排一個整型數組:
#include<stdio.h>
int compare(const void* e1, const void* e2)
{return *((int*)e1) - *((int*)e2);
}//自定義的比較函數
int main()
{int arr[] = { 2, 5, 1, 8, 6, 10, 9, 3, 5, 4 };int sz = sizeof(arr) / sizeof(arr[0]);//元素個數qsort(arr, sz, 4, compare);//用qsort函數將arr數組排序return 0;
}
最終arr數組將被排為升序。
注意:qsort函數默認將待排序的內容排為升序,如果我們要排為降序可將自定義的比較函數的兩個形參的位置互換一下即可。
在qsort函數中我們傳入了一個函數指針,最終qsort函數會在其內部通過該函數指針調用該函數,那么我們的這個自定義比較函數就被稱為回調函數。