C語言—深入理解指針(詳)

深入理解指針(詳解)

  • 前言
  • 一、指針是什么
    • 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語言在系統編程、嵌入式開發、數據結構等領域占據不可替代的地位。然而,指針的不當使用也常常導致程序崩潰、內存泄漏、數據損壞等嚴重問題。

為什么指針如此重要?如何正確理解指針的本質?指針與數組、函數、動態內存管理之間有何聯系?如何避免指針使用中的常見陷阱?

本文將系統性地剖析指針的核心概念,包括:

  1. 指針的本質:內存地址與變量訪問
  2. 指針的基本操作(聲明、初始化、解引用)
  3. 指針與數組、字符串的關系
  4. 多級指針(指針的指針)與指針運算
  5. 函數指針與回調機制
  6. 動態內存管理與常見錯誤(懸垂指針、內存泄漏)

無論你是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、不同類型的意義

  1. 指針±整數
    若指針類型為int * 的指針+1,那么它將跳過4個字節的大小指向4個字節以后的內容:
    在這里插入圖片描述
  2. 指針解引用
    指針的類型決定了指針解引用的時候能夠訪問幾個字節的內容。
    若指針類型為int *,那么將它進行解引用操作,它將可以訪問從指向位置開始向后4個字節的內容:
    在這里插入圖片描述

若指針類型為char *,那么將它進行解引用操作,它將可以訪問從指向位置開始向后1個字節的內容,以此類推.

總結:

  • 指針的類型決定了指針向前或向后走一步有多大距離。
  • 指針的類型決定了指針在進行解引用操作時,能向后訪問的空間大小。

三、野指針

概念: 野指針就是指向位置是不可知的(隨機的、不正確的、沒有明確限制的)指針。

1、野指針形成原因

  1. 野指針的成因
#include<stdio.h>
int main()
{int* p;*p = 10;return 0;
}

局部指針變量p未初始化,默認為隨機值,所以這個時候的p就是野指針。

  1. 指針越界訪問
#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就是野指針。

  1. 指針指向的空間被釋放
#include<stdio.h>
int* test()
{int a = 10;return &a;
}
int main()
{int* p = test();return 0;
}

指針變量p得到地址后,地址指向的空間已經釋放了,所以這個時候的p就是野指針。(局部變量出了自己的作用域就被釋放了)

2、如何避免野指針

  1. 指針初始化
    當指針明確知道要存放某一變量地址時,在創建指針變量時就存放該變量地址。
    當不知道指針將要用于存放哪一變量地址時,在創建指針變量時應置為空指針(NULL)。
#include<stdio.h>
int main()
{int a = 10;int* p1 = &a;//明確知道存放某一地址int* p2 = NULL;//不知道存放哪一地址時置為空指針return 0;
}
  1. 小心指針越界
  2. 指針指向的空間被釋放后及時置為NULL
  3. 使用指針之前檢查有效性
  4. 在使用指針之前需確保其不是空指針,因為空指針指向的空間是無法訪問的。

四、指針的運算

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)。
    在這里插入圖片描述
    知道了如何創建函數指針,那么函數指針應該如何使用呢?
  1. 函數指針的賦值
    對于數組來說,數組名和&數組名它們代表的意義不同,數組名代表的是數組首元素地址,而&數組名代表的是整個數組的地址。但是對于函數來說,函數名和&函數名它們代表的意義卻是相同的,它們都代表函數的地址(畢竟你也沒有聽說過函數有首元素這個說法吧)。所以,當我們對函數指針賦值時可以賦值為&函數名,也可以賦值為函數名。
	int(*p)(int, int) = &Add;int(*p)(int, int) = Add;
  1. 通過函數指針調用函數
    **方法一:**我們知道,函數指針存放的是函數的地址,那么我們將函數指針進行解引用操作,便能找到該函數了,于是就可以通過函數指針調用該函數。
#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

在這里插入圖片描述

  1. qsort函數的第一個參數是待排序的內容的起始位置;
  2. 第二個參數是從起始位置開始,待排序的元素個數;
  3. 第三個參數是待排序的每個元素的大小,單位是字節;
  4. 第四個參數是一個函數指針。

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函數會在其內部通過該函數指針調用該函數,那么我們的這個自定義比較函數就被稱為回調函數。

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

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

相關文章

小談相機的學習過程

前言博主本人并非專職相機開發&#xff0c;還涉及系統的其他幾個模塊&#xff0c;雖然都屬于owner&#xff0c;但是都還在學習探索的一個過程&#xff0c;自認為掌握還不夠細致&#xff0c;此篇文章僅梳理&#xff0c;總結&#xff0c;印證自己近五年相機模塊的一個學習過程&am…

CentOS7 內網服務器yum修改

1、首先確定的內網服務器是有yum源代理服務器的2、修改 /etc/yum.conf 配置文件&#xff0c;增加代理ip和端口號proxyhttp://ip.ip.ip.ip:port3、備份源是文件sudo cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak4、修改配置文件 vi CentOS-Base…

基于單片機自行車自動防盜報警系統設計

摘 要 本文闡述了自行車防盜報警系統原理&#xff0c;介紹如何用stc89c52單片機實現防盜報警&#xff0c;分析各個部分的工作原理&#xff0c;并給出了原理圖和源程序。此設計電路由震動傳感器、單片機、無線超再生發射/接收電路、LED顯示器和蜂鳴器組成。由于超再生接收是一種…

【深度學習】神經網絡反向傳播算法-part4

七、反向傳播算法反向傳播Back Propagation 簡稱 BP 。 訓練神經網絡的核心算法之一&#xff0c;通過計算損失函數&#xff0c;相對于每個權重參數的梯度&#xff0c;來優化神經網絡的權重1. 前向傳播前向傳播是把數據經過各層神經元的運算并逐層向前傳輸&#xff0c;知道輸出層…

CTF之服務器端模板注入(SSTI)與賽題

概念定義服務器端模板注入(Server-Side Template Injection)服務端接受攻擊者的輸入&#xff0c;將其作為Web應用內容的一部分&#xff0c;在進行代碼編譯渲染的過程中&#xff0c;進行了語句的拼接&#xff0c;執行了所插入的惡意內容&#xff0c;從而導致信息泄露、代碼執行、…

除了某信,就是這款軟件來替代了!

引言 哈嘍&#xff0c;我是小索奇。有時候會有一個普遍的需求&#xff0c;想在幾個設備之間傳個文件或者發個消息&#xff0c;除了微信&#xff0c;想一想你還能用什么軟件&#xff1f; 今天就是為了解決這個問題&#xff0c;給大家介紹一款軟件 Localsend 來解決。 內容模塊…

Vue2.x封裝預覽PDF組件

一、為什么用PDFObject插件&#xff1f; PDFObject 是一個輕量級的 JavaScript 庫&#xff0c;主要用于在網頁中嵌入和預覽 PDF 文件。它通過簡單的 API 調用&#xff0c;可以在瀏覽器中實現 PDF 文件的顯示&#xff0c;而無需依賴任何插件。以下將詳細介紹 PDFObject 的特點、…

undefined reference to ‘end‘

相關問題&#xff1a; 一、undefined reference to _exit undefined reference to ‘end‘ warning: _close is not implemented and will always fail 一、環境&#xff1a; ubuntu24.04實體機、 arm-none-eabi-gcc gcc version 13.2.1 20231009 (15:13.2.rel1-2) 二…

nginx定制http頭信息

修改http響應頭信息&#xff0c;相關Nginx模塊&#xff1a;ngx_http_headers_moduleexpires語法&#xff1a;expires [modified] time;expires [modified] time;默認值&#xff1a;expires off;作用域&#xff1a;http, server, location, if in location用途&#xff1a;控制緩…

主機安全---開源wazuh安裝

Wazuh 簡介 Wazuh 是一款免費開源的終端安全監控平臺&#xff0c;支持威脅檢測、完整性監控、事件響應和合規性管理&#xff0c;適用于企業級安全運維場景。其核心組件包括&#xff1a; Wazuh Indexer&#xff1a;基于 OpenSearch 的日志存儲與檢索組件。Wazuh Server&#x…

GaussDB 數據庫架構師修煉(四) 備份容量估算

1 影響備份容量關鍵要素業務總數據量備份數據保留周期備份周期備份數據的壓縮比平均每天的新增數據量平均每天新增日志數據量2 備份容量的估算方法公式備份容量C &#xff1d; 自動全量備份容量C1 &#xff0b; 自動差量備份容量C2 &#xff0b; 自動日志歸檔 容量C3 &#xff…

《R for Data Science (2e)》免費中文翻譯 (第0章) --- Introduction

寫在前面 本系列推文為《R for Data Science (2e)》的中文翻譯版本。所有內容都通過開源免費的方式上傳至Github&#xff0c;歡迎大家參與貢獻&#xff0c;詳細信息見&#xff1a; Books-zh-cn 項目介紹&#xff1a; Books-zh-cn&#xff1a;開源免費的中文書籍社區 r4ds-zh-cn…

如何 ASP.NET Core 中使用 WebSocket

如何在 ASP.NET Core 中使用 WebSocket在現代 Web 應用程序中&#xff0c;WebSocket 連接非常流行且使用率極高。它可以幫助企業滿足數字環境需求&#xff0c;并處理來自最終用戶的實時數據。它還能提升生產力、輸出率和用戶體驗。如果您還沒有使用 WebSocket&#xff0c;那么您…

Python之--元組

定義是 Python 中內置的不可變序列。在 Python 中使用&#xff08;&#xff09;定義元組&#xff0c;元素與元素之間使用英文的逗號分隔。元組中只有一個元素的時候&#xff0c;逗號也不能省略。元組的創建方式&#xff08;1&#xff09;使用&#xff08;&#xff09;直接創建元…

工業相機GigE數據接口的優勢及應用

工業相機不同的數據接口適用的應用場景也不同&#xff0c;選擇合適的數據額接口&#xff0c;可大大提高效率。今天我們來看看常見的GigE接口的優勢及應用。基于GigE Vision標準的千兆以太網&#xff08;GigE&#xff09;相機通過提供快速、靈活且成本效益高的成像解決方案&…

【53】MFC入門到精通——MFC串口助手(二)---通信版(發送數據 、發送文件、數據轉換、清空發送區、打開/關閉文件),附源碼

文章目錄1 完整 功能展示2 添加控件變量及聲明2.1 添加控件及變量2.2 SerialPortDlg.h: 頭文件3 函數實現3.1 數據發送3.1.2 寫數據、字符串轉3.2 發送文件3.2.1 打開文件3.2.2 發送文件3.3 清空發送區4 完整MFC項目項下載1 完整 功能展示 串口通信助手 頁面展示&#xff0c;功…

算法學習筆記:27.堆排序(生日限定版)——從原理到實戰,涵蓋 LeetCode 與考研 408 例題

堆排序&#xff08;Heap Sort&#xff09;是一種基于二叉堆數據結構的高效排序算法&#xff0c;由計算機科學家 J. W. J. Williams 于 1964 年提出。它結合了選擇排序的思想和二叉堆的特性&#xff0c;具有時間復雜度穩定&#xff08;O (nlogn)&#xff09;、原地排序&#xff…

I/O 多路復用select,poll

目錄 I/O多路復用的介紹 多進程/多線程模型的弊端 網絡多路復用如何解決問題&#xff1f; 網絡多路復用的常見實現方式 常見的開源網絡庫 select詳細介紹 select函數介紹 套接字可讀事件,可寫事件,異常事件 fd_set類型介紹 select的兩次拷貝&#xff0c;兩次遍歷 se…

最終分配算法【論文材料】

文章目錄一、最終分配算法1.1 平衡的情況1.2 不平衡的情況1.3 TDM 約束一、最終分配算法 上一步合法化后&#xff0c;group 的 TDM 情況大致分為兩類&#xff0c;一類是平衡的&#xff0c;最大的一些 group 的 TDM 比較接近。另外一種情況就是不平衡的&#xff0c;最大的 group…

《大數據技術原理與應用》實驗報告七 熟悉 Spark 初級編程實踐

目 錄 一、實驗目的 二、實驗環境 三、實驗內容與完成情況 3.1 Spark讀取文件系統的數據。 3.2 編寫獨立應用程序實現數據去重。 3.3 編寫獨立應用程序實現求平局值問題。 四、問題和解決方法 五、心得體會 一、實驗目的 1. 掌握使用 Spark 訪問本地文件和 HDFS 文件的…