指針詳解3
運行環境以Dev-C++、Visual Studio 2022、MacOS的命令行和Xcode為主
1.字符指針變量
// 代碼1:將字符變量的地址賦值給字符指針變量
#include <stdio.h>
int main(int argc, const char * argv[]) {
char ch = 'w';
char *pc = &ch;
*pc = 'b';
printf("%c\n", *pc);
return 0;
}
// 代碼2:將字符串賦值給字符指針變量,實際上字符串"abcdef"并沒有存放在字符指針pstr中
// 本質上是將字符串"abcdef"首字符'a'的地址存到了指針變量pstr中
#include <stdio.h>
int main(int argc, const char * argv[]) {
const char *pstr = "abcdef";
printf("%s\n", pstr);
return 0;
}
// 代碼3:對比分析字符串常量和字符串數組。str3和str4指向的是同一個常量字符串,C語言會把常量字符串存儲到單獨的一個內存區域
// 當幾個指針指向同一個字符串時,它們會指向同一塊內存。但用相同的字符串常量初始化不同的數組時,會開辟出不同的內存塊
#include <stdio.h>
int main(int argc, const char * argv[]) {
char str1[] = "hello world";
char str2[] = "hello world";
const char* str3 = "hello world";
const char* str4 = "hello world";
if (str1 == str2) {
printf("str1 and str2 are same\n");
}
else {
printf("str1 and str2 are not same\n");
}
if (str3 == str4) {
printf("str3 and str4 are same\n");
}
else {
printf("str3 and str4 are not same\n");
}
return 0;
}

2.數組指針變量
-
2.1 定義和指向原理
-
整型指針變量存放整型變量的地址,指向整型數據,如
int *p_int;;浮點型指針變量存放浮點型變量的地址,指向浮點型數據,如float *pf; -
數組指針變量存放數組的地址,是能夠指向數組的指針變量,如
int (*p_arr)[10];-
p_arr先和*結合,說明p_arr是一個指針,指針指向的是一個大小為 10 個整型的數組 -
p_arr是一個指針,指向一個數組,稱為數組指針 -
[]的優先級高于*,所以必須加上()來保證p_arr先和*結合
-
-
-
2.2 數組指針的初始化
- 前面的知識中詳細分析了數組名、數組首元素的地址、數組的地址間的區別和聯系。
&數組名便獲得了數組的地址。若要存放該地址,要用到數組指針變量
int arr[10] = {0}; int (*p)[10] = &arr; // 調試后查看&arr和p的類型完全一致 int (*p) [10] = &arr; | | | | | | | | p指向數組的元素個數 | p是數組指針變量名 p指向的數組元素的類型![image]()
- 前面的知識中詳細分析了數組名、數組首元素的地址、數組的地址間的區別和聯系。
3.二維數組傳參的本質
-
3.1 數組形式的形參
-
這是在第 6 章中二維數組作為形參的主要形式
-
實參是二維數組,形參也寫成二維數組的形式
#include <stdio.h> void print_arr(int arr[3][5], int r, int c) { // int arr[][5] 也可以 int i = 0; for (i = 0; i < r; i++) { int j = 0; for (j = 0; j < c; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } int main(int argc, const char * argv[]) { int arr[3][5] = {{1, 2, 3, 4, 5}, {2, 3, 4, 5, 6}, {3, 4, 5, 6, 7}}; print_arr(arr, 3, 5); return 0; } -
-
3.2 數組指針形式的形參
-
二維數組是一維數組的集合,是由一維數組組成的數組,它的首元素就是第一行,也是個一維數組
-
根據數組名是數組首元素地址這個規則,二維數組的數組名表示第一行,是一維數組的地址
-
第一行的一維數組的類型為
int [5],所以第一行的地址類型為數組指針類型int (*)[5] -
二維數組傳參本質上也是傳遞了地址,且是第一行這個一維數組的地址
#include <stdio.h> void print_arr(int (*p)[5], int r, int c) { int i = 0, j = 0; for (i = 0; i < r; i++) { for (j = 0; j < c; j++) { printf("%d ", *(*(p + i) + j)); // printf("%d ", p[i][j]); // printf("%d ", (*(p + i))[j]); } } } int main(int argc, const char * argv[]) { int arr[3][5] = {{1, 2, 3, 4, 5}, {2, 3, 4, 5, 6}, {3, 4, 5, 6, 7}}; print_arr(arr, 3, 5); return 0; } -
4.函數指針變量
-
4.1 定義和指向原理
- 數組名表示數組首元素的地址,通過它可以定位到數組元素。類似地,函數名表示函數的地址,也可以通過
&函數名的方式獲得函數的地址
#include <stdio.h> void test(void) { printf("test_function\n"); } int main(int argc, const char * argv[]) { printf("test: %p\n", test); printf("&test: %p\n", &test); return 0; }![image]()
- 既然函數名就是函數的地址,就可以用指針變量存儲該地址,這樣的指針變量稱為函數指針,它的寫法和數組指針類似
void test(void) { printf("test_function\n"); } void (*pf1)(void) = &test; void (*pf2)(void) = test; int Add(int x, int y) { return x + y; } int (*pf3)(int, int) = Add; int (*pf4)(int x, int y) = &Add; // 加不加x和y都可以 int (*pf3) (int x, int y) = &arr; | | | | | | | | pf3指向函數的參數類型和參數數量 | 函數指針變量名 pf3指向函數的返回類型 - 數組名表示數組首元素的地址,通過它可以定位到數組元素。類似地,函數名表示函數的地址,也可以通過
-
4.2 函數指針的使用
-
通過函數指針調用指針指向的函數
-
由于
&函數名和函數名的打印結果一致,所以通過函數指針調用函數時函數指針旁加不加解引用操作符*均可
#include <stdio.h> int Add(int x, int y) { return x + y; } int main(int argc, const char * argv[]) { int (*pf3)(int, int) = Add; printf("%d\n", (*pf3)(2, 3)); // 打印結果:5 printf("%d\n", pf3(4, 5)); // 打印結果:9 return 0; } -
-
4.3
typedef關鍵字typedef用于類型重命名,可以將復雜的類型簡單化
// 1.普通變量類型重命名 typedef unsigned int uint; // 將unsigned int重命名為uint // 2.指針變量類型重命名 typedef int * ptr_t; // 將int * 重命名為 ptr_t // 3.數組指針重命名 typedef int(*parr_t) [5]; // 將int (*)[5] 重命名為parr_t,新類型名必須在 * 的右邊 // 4.函數指針重命名 typedef void (*pf_t)(int); // 將void (*) (int) 重命名為pf_t,新類型名必須在 * 的右邊 // 5.分析以下代碼 (*(void (*)())0)(); // 是一次函數調用,調用0地址處存放的那個函數。0地址處的函數沒有參數,返回類型為void // 6.分析以下代碼并用typedef重命名 void (*signal(int, void(*)(int)))(int); // 是一次函數聲明,函數名為signal,參數有兩個,類型分別是整型int和函數指針void(*)(int) // signal函數的返回值也是一個函數指針,類型為 void (*) (int),它指向的函數參數是,范回類型為void typedef void (*pf_t) (int) pf_t signal(int, pf_t)
5.函數指針數組
-
5.1 定義和指向原理
- 將函數的地址存入一個數組中,該數組稱為函數指針數組
int (*parr1[3])(); // parr1先和[]結合,說明parr1是數組,數組的內容是int (*)()類型的函數指針 -
5.2 轉移表
- 使用一般的方法編寫程序實現一個計算器
#include <stdio.h> 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 menu(void) { printf("**********************\n"); printf("**** 1.Add 2.Sub ****\n"); printf("**** 3.Mul 4.Div ****\n"); printf("******* 0. Exit ******\n"); printf("**********************\n"); } int main(int argc, const char * argv[]) { int x = 0, y = 0; int input = 0; int ret = 0; do { menu(); printf("請輸入你的選擇:"); scanf("%d", &input); switch (input) { case 1: printf("請輸入兩個數:"); 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; }![image]()
- 使用函數指針數組編寫程序實現一個計算器
// 代碼1:通過函數指針數組實現 #include <stdio.h> void menu(void) { printf("**********************\n"); printf("**** 1.Add 2.Sub ****\n"); printf("**** 3.Mul 4.Div ****\n"); printf("******* 0. Exit ******\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 argc, const char * argv[]) { int x = 0, y = 0; int input = 0; int ret = 0; int (*pf[5])(int, int) = {0, Add, Sub, Mul, Div}; do { menu(); printf("請輸入你的選擇:"); scanf("%d", &input); if (input >= 1 && input <= 4) { printf("請輸入兩個整數:"); scanf("%d %d", &x, &y); ret = pf[input](x, y); printf("ret = %d\n", ret); } else if (input == 0) { printf("退出...\n"); } else { printf("輸入錯誤,重新輸入!\n"); } } while (input); return 0; } // 代碼2:通過函數指針實現 #include <stdio.h> void menu(void) { printf("**********************\n"); printf("**** 1.Add 2.Sub ****\n"); printf("**** 3.Mul 4.Div ****\n"); printf("******* 0. Exit ******\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 calc(int (*pf)(int, int)) { int x = 0, y = 0; int ret = 0; printf("輸入兩個整數:"); scanf("%d %d", &x, &y); ret = pf(x, y); printf("ret = %d\n", ret); } int main(int argc, const char * argv[]) { int input = 0; do { menu(); printf("請輸入你的選擇:"); scanf("%d", &input); switch (input) { case 1: calc(Add); break; case 2: calc(Sub); break; case 3: calc(Mul); break; case 4: calc(Div); break; case 0: printf("退出...\n"); break; default: printf("輸入錯誤,重新輸入!\n"); break; } } while (input); return 0; }
6.回調函數
-
6.1 定義和實例對比
-
回調函數就是通過函數指針調用的函數。如果將函數指針(地址)作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數時,被調用的函數就是回調函數
-
回調函數不是由該函數的實現方法直接調用,而是在特定的事件或條件發生時由另外的一方調用,用于對該事件或條件進行響應
-
在 5.2 節實現計算器的案例中,最開始使用的是普通方法,在
switch結構中輸入輸出操作重復出現,代碼有較多冗余。這些代碼只有調用函數的邏輯略有區別,因此將調用函數的地址以實參的形式傳遞給calc()函數,它的參數是一個函數指針,該指針指向什么函數就調用什么函數。這里使用的就是回調函數的功能
![image]()
-
-
6.2
qsort函數- 1.函數原型
// 對base指向數組的num個元素進行排序,每個元素的大小為bytes字節,排序規則由compar函數決定 // 此函數使用的排序算法通過調用指定的compar函數來比較元素對,調用時會將指向這兩個元素的指針作為參數傳遞給compar函數 // 該函數無返回值,但會修改base所指向數組的內容,按照compar函數定義的規則對元素重新排序 // 對于值相等的元素,它們的最終順序是未定義的(即不保證穩定排序) void qsort(void* base, // 指向待排序數組的第1個元素的指針 size_t num, // base指向數組中的元素個數 size_t size, // base指向數組中各個元素的大小,單位是字節 int (*compare)(const void *, const void *) // 函數指針,傳遞函數的地址 );![image]()
- 2.使用
qsort()函數排序整型數據
#include <stdio.h> #include <string.h> //int cmp_int(const void *p1, const void *p2) { // if (*(int *)p1 > *(int *)p2) { // return 1; // } // else if(*(int *)p1 < *(int *)p2) { // return -1; // } // else { // return 0; // } //} int cmp_int(const void *p1, const void *p2) { return *(int *)p1 - *(int *)p2; } void print_arr(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } void test1(void) { int arr[] = {2, 4, 6, 8, 0, 5, 3, 7, 9}; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(int), cmp_int); print_arr(arr, sz); } int main(int argc, const char * argv[]) { // insert code here... test1(); return 0; }![image]()
- 3.使用
qsort()函數排序結構數據
#include <stdio.h> #include <string.h> struct Stu { char name[20]; int age; }; void print_stu(struct Stu arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%s--%d\n", arr[i].name, arr[i].age); } printf("\n"); } // 按照年齡排序 int cmp_stu_by_age(const void *p1, const void *p2) { return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age; } // 測試qsort函數排序結構體數據,按照名字比較兩個結構體數據 // 名字是字符串,字符串比較時使用strcmp函數 int cmp_stu_by_name(const void *p1, const void *p2) { // ->的優先級1高于強制類型轉換2,所以要將轉換后的指針整體使用-> return strcmp(((struct Stu *)p1)->name, ((struct Stu*)p2)->name); } void test2(void) { // 按姓名排序時需要將結構體數組中的姓名寫成英文,寫中文的話可能會因為編碼導致預期跟實際結果不一致 struct Stu arr[] = {{"zhangsan", 34}, {"lisi", 24}, {"wangwu", 40} }; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name); print_stu(arr, sz); } void test3(void) { struct Stu arr[] = {{"張三", 34}, {"李四", 24}, {"王五", 40} }; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age); print_stu(arr, sz); } int main(int argc, const char * argv[]) { // insert code here... printf("sorted by name:\n"); test2(); printf("sorted by age:\n"); test3(); return 0; }![image]()
- 4.使用冒泡排序模擬實現
qsort()函數
#include <stdio.h> #include <string.h> struct Stu { char name[20]; int age; }; void swap(char *buff1, char *buff2, size_t width) { int i = 0; char temp = 0; for (i = 0; i < width; i++) { temp = *buff1; *buff1 = *buff2; *buff2 = temp; buff1++; buff2++; } } void bubble_sort(void *base, size_t sz, size_t width, int (*cmp)(const void *p1, const void *p2)) { int i = 0, j = 0; for (i = 0; i < sz - 1; i++) { for (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 cmp_int(const void *p1, const void *p2) { return *(int *)p1 - *(int *)p2; } int cmp_stu_by_name(const void *p1, const void *p2) { return strcmp(((struct Stu *)p1)->name, ((struct Stu*)p2)->name); } int cmp_stu_by_age(const void *p1, const void *p2) { return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age; } void print_arr(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } void print_stu(struct Stu arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%s--%d\n", arr[i].name, arr[i].age); } printf("\n"); } void test1(void) { int arr[] = {2, 4, 6, 8, 0, 5, 3, 7, 9}; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz, sizeof(int), cmp_int); print_arr(arr, sz); } void test2(void) { struct Stu arr[] = {{"zhangsan", 34}, {"lisi", 24}, {"wangwu", 40} }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name); print_stu(arr, sz); } void test3(void) { struct Stu arr[] = {{"張三", 34}, {"李四", 24}, {"王五", 40} }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age); print_stu(arr, sz); } int main(int argc, const char * argv[]) { // insert code here... // test1(); // test2(); test3(); return 0; }







浙公網安備 33010602011771號