文章目錄
- 字符指針
- 指針數組
- 數組指針
- 數組名
- 數組傳參
- 函數指針
- 函數指針數組
- 指向函數指針數組的指針
- 回調函數
- Qsort 的使用
- 通過冒泡排序模擬實現 qsort
大部分的內容都寫在代碼注釋中
指針有類型,指針的類型決定了指針的±整數的步長,指針解引用操作的時候的權限
字符指針
#include <stdio.h> int main(void)
{ const char *str1 = "Hello, World!"; const char *str2 = "Hello, World!"; // 在這個寫法中,字符串放在表達式中("hello world" 視為一個表達式) // 該表達式的值是一個指向字符串的指針 // *str2 = "Hello, C!"; // const 在 * 前面,不能修改指針指向的內容 char str3[] = "Hello, World!"; char str4[] = "Hello, World!"; // 在這個寫法中,字符串放在數組中,數組是一個變量,可以修改 char *str5 = "Hello, World!"; // 在這個寫法中,指針未被修飾,且其中存的為常量字符串 // *str5 = "Hello, C!"; // 會報錯,因為指針指向的是常量字符串,不能被修改 // 在前面加上 const 修飾符,即可在編譯階段報錯,避免運行時錯誤 // const char *str5 = "Hello, World!"; printf("str1: %s, address: %p\n", str1, str1); printf("str2: %s, address: %p\n", str2, str2); printf("str5: %s, address: %p\n", str5, str5); // str1、str2 和 str5 的地址是相同的,因為它們指向的是常量字符串,在內存中只有一份 printf("str3: %s, address: %p\n", str3, str3); printf("str4: %s, address: %p\n", str4, str4); // str3 和 str4 的地址是不同的,因為它們是數組,每個數組都有自己的內存空間 return 0;
}
C/C++會把常量字符串存儲到單獨的一個內存區域,當幾個常量指針指向同一個字符串時,他們實際會指向同一塊內存(全局區)
但是用相同的常量字符串去初始化不同的數組的時候就會開辟出不同的內存塊,因此 str1 和 str2 不同,str3 和 str4 不同
指針數組
指針數組是一個存放指針的數組
#include <stdio.h> int main()
{ int arr1[] = {1, 2, 3, 4, 5}; // 整形數組 // 指針數組 int *parr1[] = {arr1, arr1 + 1, arr1 + 2, arr1 + 3, arr1 + 4}; // parr1 是一個數組,數組中的每個元素都是一個 int*,指向 arr1 中的元素 for (int i = 0; i < 5; i++) { printf("arr1[%d]: %d, address: %p\n", i, arr1[i], &arr1[i]); printf("parr1[%d]: %d, address: %p\n", i, *parr1[i], parr1[i]); // 這里的 address 實際上就是 arr2[i] 的值,即 arr1 中元素的地址 } // ---------------------------- int arr2[] = {1, 2, 3, 4, 5}; int arr3[] = {2, 3, 4, 5, 6}; int *parr2[] = {arr2, arr3}; // 利用指針數組,將多個獨立數組組合成類二維數組 for(int i = 0; i < 2; i++) { for(int j = 0; j < 5; j++) { printf("parr2[%d][%d]: %d, address: %p\n", i, j, parr2[i][j], &parr2[i][j]); // parr2[i][j] 等價于 *(parr2[i] + j),即 arr2[i] 中的第 j 個元素 } } return 0;
}
數組指針
數組指針指指向數組的指針
int main()
{ int *arr1[5]; // 指針數組 // arr1 先是一個數組(先和方塊結合),數組中的每個元素都是一個 int*,指向 int 類型的變量 int (*arr2)[5]; // 數組指針 // arr2 是一個指針,指向一個 int 類型的數組 return 0;
}
數組名
- 通常,數組名表示的都是首元素地址
- 存在兩個例外
- sizeof (數組名),這里的數組名表示整個數組
- &數組名,表示的依然是整個數組
類型(對 int arr[5]]):
- arr -> int*
- &arr -> int (*)[5]
#include <stdio.h> int main()
{ int arr1[] = {1, 2, 3, 4, 5}; printf("%p\n", arr1); printf("%p\n", &arr1); // 在這里,&arr1 表示整個數組的地址,即 arr1 的地址 printf("----------------\n"); int sz = sizeof(arr1); printf("%d\n", sz); // sizeof(arr1) 是整個數組的大小,單位是字節 // 在這里,數組名表示整個數組 printf("----------------\n"); printf("%p\n", arr1 + 1); printf("%p\n", &arr1 + 1); // arr1 + 1 表示數組中第二個元素的地址 // &arr1 + 1 表示整個數組后面的地址 printf("----------------\n"); int (*parr)[5] = &arr1; // parr 是一個指針,指向一個 int 類型的數組 // 需要寫清楚有幾個元素// 此時,parr 的類型為 int(*)[5]int *p = &arr1; // warning:'int(*)[5]' 類型的表達式被隱式轉換為不兼容的指針類型 return 0;
}
數組傳參
#include <stdio.h> /** * @brief 打印數組內容,參數直接傳數組 ** @param arr 傳入數組 * @param line 數組行數 * @param length 數組列數 */void print1(int arr[3][5], int line, int length)
{ for (int i = 0; i < line; i++) { for (int j = 0; j < length; j++) { printf("%d ", arr[i][j]); } printf("\n"); }
} /** * @brief 打印數組內容,傳參傳數組指針 * * @param p * @param line * @param length */
void print2(int (*p)[5], int line, int length)
{ for(int i = 0; i < line; i++) { for(int j = 0; j < length; j++) { printf("%d ", *(*(p + i) + j)); // printf("%d ", (*(p + i))[j]); // 等價于上面的寫法 // 如果寫成 *(p + i)[j],輸出會報錯,因為 [] 的優先級高于 * printf("%d ", p[i][j]); // 也可以這樣寫 } printf("\n"); }
} int main()
{ int arr[3][5] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; // 二維數組的首元素是第一行,在這里為含有 5 個元素的一維數組 print1(arr, 3, 5); print2(arr, 3, 5); // 將二維數組的首元素傳入函數 return 0;
}
int *parr1[10];
// 指針數組int(*parr)[10];
// 數組指針int(*parr[5])[10];
// 存放數組指針的數組
一維指針數組傳參
指針數組元素為 指針
指針數組的數組名相當于一個 指針
指針指向存放一個指針的位置 -> 二級指針
因此指針數組傳參可以為二級指針
void test(int **parr);void test1(int *parr[20]);
二維數組通過指針傳參
二維數組的首元素是一維數組,不能用一級/二級指針接收
形參應該是一個一維數組的指針
void test(int *arr);
// errvoid test1(int (*arr)[5]);
// 參數為一個一維數組void test2(int **arr);
// err,二級指針指向存放一級指針的地址,不能是指向一個一維數組的指針
函數指針
指向函數的指針
#include <stdio.h> int func1(int x, int y)
{ return x + y;
} int main()
{ printf("%p\n", &func1); // 取地址符得到函數的地址 printf("%p\n", func1); // 對函數來說,&func1 和 func1 是一樣的 int (*p)(int, int) = func1; // 定義一個函數指針 // 返回值類型 (*指針變量名)(參數類型1, 參數類型2, ...); int ret = (*p)(1, 2); // 通過函數指針調用函數 int ret2 = p(1, 2); // *p 和 p 是一樣的,如果要寫 *p,必須加括號(如果不加括號,p 先和后面結合,* 會被當成解引用操作符對 p(1, 2) 解引用) // 等價于 int ret = func1(1, 2); printf("%d\n", ret); printf("%d\n", ret2); return 0;
}
案例
(*(void (*)())0)();
// void (*p)() -> p 是函數指針
// void (*)() -> 函數指針類型
// (void (*)())0 -> 把 0(0x00000000) 強制轉換為函數指針類型(此時把 0 看作一個地址)
// (*(void (*)())0)() -> 調用 0 地址對應的函數
// 即這行代碼為一次函數調用,調用 0 地址的函數
void (*signal(int, void(*)(int)))(int);
// 一次函數聲明
// 聲明的 signal 函數參數 1 類型為 int,參數 2 類型為函數指針,該函數指針指向的函數參數是 int 類型,返回類型為 void
// signal 函數的返回類型為函數指針,該函數指針參數是 int 類型,返回類型為 void// ----------typedef void (*pf_t)(int); // 把 void(*)(int) 類型重命名為 pf_t
int main()
{ void (*signal(int, void (*)(int)))(int); pf_t signal(int, pf_t); // 使用 pf_t 重命名后的類型 return 0;
}
函數指針的用途
#include <stdio.h> void menu()
{ printf("Please select:\n"); printf("1. Add\n"); printf("2. Sub\n"); printf("3. Mul\n"); printf("4. Div\n"); printf("0. Exit\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;
} // 方法 2:將輸入代碼塊放在一個函數中,通過函數指針傳遞給 Calculate 函數(回調函數)
/**
/** * @brief 接收一個函數指針,通過函數指針調用函數 * * @param pf 函數指針 */void Calculate(int (*pf)(int, int))
{ printf("Please input two numbers: "); int num1 = 0; int num2 = 0; scanf("%d %d", &num1, &num2); int ret = pf(num1, num2); printf("Result: %d\n", ret);
} int main()
{ int choice = 0; do { menu(); printf("Please select: "); scanf("%d", &choice); // printf("Please input two numbers: "); // int num1 = 0; // int num2 = 0; // scanf("%d %d", &num1, &num2); // int ret = 0; // 輸入代碼塊放在外面,無論選擇什么都要輸入兩個數,不符合邏輯 switch (choice) { case 1: // printf("Please input two numbers: "); // int num1 = 0; // int num2 = 0; // scanf("%d %d", &num1, &num2); // int ret = 0; // 方法 1:將每一個輸入涉及的代碼塊放在一個 case 語句中,造成代碼冗余 // ret = Add(num1, num2); Calculate(Add); break; case 2: // ret = Sub(num1, num2); Calculate(Sub); break; case 3: // ret = Mul(num1, num2); Calculate(Mul); break; case 4: // ret = Div(num1, num2); Calculate(Div); break; case 0: printf("Exit!\n"); break; default: printf("Error input!\n"); break; } // printf("Result: %d\n", ret); } while (choice); return 0;
}
函數指針數組
把函數指針放在數組中
#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()
{ printf("Please select:\n"); printf("1. Add\n"); printf("2. Sub\n"); printf("3. Mul\n"); printf("4. Div\n"); printf("0. Exit\n");
} int main()
{ int choice = 0; // 實現函數跳轉(轉移表) int(*pfArr[4])(int, int) = {Add, Sub, Mul, Div}; do { menu(); scanf("%d", &choice); if(choice < 0 || choice > 4) { printf("Error input!\n"); continue; } else if (choice == 0) { break; } else { printf("Please input two numbers: "); int num1 = 0; int num2 = 0; scanf("%d %d", &num1, &num2); printf("Result: %d\n", pfArr[choice - 1](num1, num2)); } } while (choice); printf("Exit!\n"); return 0;
}
指向函數指針數組的指針
int (*pfarr[])(int, int) = {add, sub, mul, div}; // 指向函數指針數組的指針
int (*(*pfarr)[5])(int, int)
回調函數
通過函數指針調用的函數(把 A 函數的指針作為參數傳遞給 B 函數,當特定的事件或條件滿足時,由 B 函數調用 A 函數,此時 A 為回調函數)
Qsort 的使用
/** * @brief 快速排序(庫函數),可以對任意類型的數據進行排序 ** @param base 排序的數據起始位置 * @param nmemb 待排序元素個數 * @param size 待排序元素大小(字節) * @param compar 函數指針,指向比較函數,e1、e2分別指向待比較的兩個元素,當e1 < e2時,返回負數;當e1 = e2時,返回0;當e1 > e2時,返回正數*/void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void * e1, const void *e2));
回調函數的使用
#include <stdio.h>
#include <stdlib.h> /** * @brief 整形比較函數 ** @param e1 比較的元素1 * @param e2 比較的元素2 * @return int 當e1 < e2時,返回負數;當e1 = e2時,返回0;當e1 > e2時,返回正數 */int compareInt(const void *e1, const void *e2)
{ return *(int *)e1 - *(int *)e2; // void *可以接收任意類型的數據,不能解引用,也不能進行運算,需要轉換為具體類型 // 也可以寫成降序排序,return *(int *)e2 - *(int *)e1;
} void testFunc()
{ int arr[] = {9, 8, 7, 6, 5, 4, 3, 2, 1}; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), compareInt); for(int i = 0; i < sz; i++) { printf("%d ", arr[i]); }
} struct Stu
{ char name[20]; int age;
}; int compareStu(const void *e1, const void *e2)
{ return ((struct Stu *)e1)->age - ((struct Stu *)e2)->age;
} void testFunc2()
{ struct Stu s[3] = {{"zhangsan", 20}, {"lisi", 30}, {"wangwu", 10}}; int sz = sizeof(s) / sizeof(s[0]); qsort(s, sz, sizeof(s[0]), compareStu); for(int i = 0; i < sz; i++) { printf("%s %d\n", s[i].name, s[i].age); }
} int main()
{ testFunc(); // int 類型排序 printf("\n"); testFunc2(); // 結構體類型排序 return 0;
}
通過冒泡排序模擬實現 qsort
#include <stdio.h> struct Stu
{ char name[20]; int age;
}; int compareStu(const void *e1, const void *e2)
{ return ((struct Stu *)e1)->age - ((struct Stu *)e2)->age;
} int compareInt(const void *e1, const void *e2)
{ return *(int *)e1 - *(int *)e2;
} void swapMem(void *e1, void *e2, size_t width)
{ char tmp; for(int i = 0; i < width; i++) { tmp = *((char *)e1 + i); *((char *)e1 + i) = *((char *)e2 + i); *((char *)e2 + i) = tmp; }
} /** * @brief 模擬實現 qsort 函數 ** @param arr 待排序數組 * @param sz 待排序元素個數 * @param width 待排序元素大小(字節),由于傳入的是 void * 類型,無法得知具體類型,需要傳入元素大小 * @param compare 比較函數 */void bubbleSort(void *arr, size_t sz, size_t width, int (*compare)(const void *e1, const void *e2))
{ sz = (int)sz; width = (int)width; for(int i = 0; i < sz - 1; i++) { for(int j = 0; j < sz - 1 - i; j++) { if(compare((char *)arr + j * width, (char *)arr + (j + 1) * width) > 0) // 通過起始位置 + 偏移量來訪問元素 { swapMem((char *)arr + j * width, (char *)arr + (j + 1) * width, width); // 交換兩個元素 } } }
} int main()
{ int arr[] = {9, 8, 7, 6, 5, 4, 3, 2, 1}; int sz = sizeof(arr) / sizeof(arr[0]); bubbleSort(arr, sz, sizeof(arr[0]), compareInt); for(int i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); struct Stu s[3] = {{"zhangsan", 20}, {"lisi", 30}, {"wangwu", 10}}; sz = sizeof(s) / sizeof(s[0]); bubbleSort(s, sz, sizeof(s[0]), compareStu); for(int i = 0; i < sz; i++) { printf("%s %d\n", s[i].name, s[i].age); } return 0;
}