系列文章目录
文章目录
1. 字符指针
字符指针是一个指针类型,专门用于指向字符类型数据的地址,通常用来处理字符串和字符数组。
char *str = "Hello, world!";
代码示例:
char s[] = "Hello";
char *p = s; // p指向s的第一个元素
// 输出字符串
while(*p != '\0') {
printf("%c", *p);
p++;
}
// 修改字符串
p = s; // 重置p指向s的第一个元素
p[1] = 'a'; // 将'e'改为'a'
printf("\nModified string: %s\n", s);
使用方法一:
int main() {
char ch = 'w';
char *pc = &ch; // 指针pc指向字符变量ch
*pc = 'w'; // 通过指针pc修改ch的值
return 0;
}
- 变量定义:定义了一个字符变量
ch
并初始化为'w'
。 - 指针使用:定义了一个指向字符的指针
pc
,并将其指向ch
的地址。接着通过指针pc
修改了ch
的值。虽然这里重新将ch
的值设置为'w'
,实际上并没有改变任何内容。 - 内存操作:这个程序操作的是堆栈上的内存,具体是局部变量
ch
的存储空间。
使用方法二:
int main() {
const char* pstr = "hello bit."; // pstr是一个指向字符串常量的指针
printf("%s\n", pstr);
return 0;
}
- 这里是把字符串 hello bit. 首字符的地址放到了pstr中。
- 字符串字面量:这里
"hello bit."
是一个字符串字面量,存储在程序的只读数据段。 - 指针定义:定义了一个指向
const char
的指针pstr
。这意味着pstr
指向的内容不应被修改,这也符合字符串字面量存放在只读内存区的特性。
两个代码的主要区别:
-
内存位置和安全性:
- 第一个程序中的
ch
存储在栈上,可以安全地通过指针修改它的值。 - 第二个程序中的
pstr
指向的是存储在只读内存段的字符串字面量,通过pstr
修改字符串将导致运行时错误(通常是程序崩溃)。
- 第一个程序中的
-
const关键字的使用:
- 在第二个程序中,
const char*
表明指针指向的字符内容不应该被修改,这保护了指向只读内存的字符串字面量。 - 第一个程序中,没有使用
const
,因为pc
指向的ch
是可以修改的。
- 在第二个程序中,
练习:下面的输出是什么?
#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char *str3 = "hello bit.";
const char *str4 = "hello bit.";
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;
}
输出为:str1 and str2 are not same;str3 and str4 are same
- 当比较
str1
和str2
时,比较的是两个数组的起始地址。由于这两个数组独立分配,它们的地址是不同的,因此结果将显示str1
和str2
不相同。 - 当比较
str3
和str4
时,比较的是两个指针的值。如果编译器对字符串字面量"hello bit."
进行了优化,使得所有的指针都指向相同的内存地址,则这两个指针指向的是相同的地址。因此,结果可能显示str3
和str4
是相同的。
2. 指针数组
指针数组是一个数组,其元素都是指针。这种类型的数组可以用来存储指向不同数据类型的指针,例如整数、字符、其他数组或者其他指针等。
int* arr1[10]; //整形指针的数组
- 含义:
arr1
是一个数组,包含 10 个元素,每个元素都是一个指向int
类型的指针。这意味着每个数组元素可以存储一个指向整数的内存地址。 - 用途:这种类型的数组通常用于存储指向不同整数或整数数组的指针。例如,可能有多个动态分配的整数数组,每个数组的长度可以不同,可以使用
arr1
的每个元素来指向这些数组。
char *arr2[4]; //一级字符指针的数组
- 含义:
arr2
是一个数组,包含 4 个元素,每个元素都是一个指向char
类型的指针。这种指针通常用于指向字符串。 - 用途:每个指针可以指向一个独立的字符串,这使得
arr2
成为一个非常适合存储多个字符串(如多个名字或不同的文本条目)的结构。
char **arr3[5];//二级字符指针的数组
- 含义:
arr3
是一个数组,包含 5 个元素,每个元素都是一个指向指向char
类型的指针的指针(即指针的指针)。 - 用途:这种类型的数组通常用于更复杂的数据结构,如动态数组的动态数组,或者用于存储指向不同字符串数组的指针。例如可以用
arr3
来指向不同的字符串数组,其中每个数组可能包含不同数量的字符串。这也常见于处理复杂的多维字符串数据,或者在函数中动态改变指向字符串的指针。
3. 数组指针
数组指针和刚刚的指针数组不一样,它是一种指针类型,用于指向一个完整的数组,而不仅仅是数组的单个元素。与指针数组不同的是,后者是包含多个指针的数组。数组指针能够指向并通过其指针操作整个数组。
3.1 语法
type (*pointerName)[arraySize];
int (*p)[10];
这里,type 是数组元素的数据类型,pointerName 是数组指针的名称,而arraySize 是数组中元素的数量。
p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
3.2 使用示例
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int (*ptr)[4] = arr;
ptr 是一个指向包含 4 个整数的数组的指针。arr 是一个 3x4 的二维数组,而ptr 可以用来遍历arr。
遍历二维数组
使用数组指针遍历二维数组的代码如下:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("%d ", ptr[i][j]); // 访问数组元素
}
printf("\n");
}
动态数组与数组指针
数组指针也可以用来指向动态分配的数组。例如,动态分配一个二维数组:
int (*ptr)[4];
ptr = (int (*)[4])malloc(3 * sizeof(*ptr));
// 初始化并打印数组
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
ptr[i][j] = i * 4 + j + 1; // 填充数据
printf("%d ", ptr[i][j]);
}
printf("\n");
}
// 释放内存
free(ptr);
3.3 &数组名和数组名
数组名
数组名被视为指向数组首元素的指针。也就是说,它表示数组第一个元素的内存地址。例如,如果有一个数组 int arr[5];
,则表达式 arr
在不带有 &
的情况下,转换为一个指向数组第一个元素(arr[0]
)的指针。
int arr[5] = {1, 2, 3, 4, 5};
int* p = arr; // p 指向 arr[0]
&数组名
当使用 & 与数组名时,如 &arr,得到的是指向整个数组的指针,这是一种不同类型的指针。这种指针的类型不是指向单个元素,而是指向整个数组,它的类型表示为 类型 (*指针名)[数组大小]。在例子中,&arr 的类型是 int (*)[5]。
arr
指向数组的第一个元素,类型为int*
。&arr
指向整个数组,类型为int (*)[5]
。
使用区别
- 使用
arr
时可以像使用普通指针一样,通过arr[i]
或*(arr + i)
来访问数组元素。 - 使用
&arr
时必须考虑到它是一个指向整个数组的指针。要访问数组的元素,需要先解引用这个指针,然后指定元素索引,如(*&arr)[i]
。
指针运算区别:
- 对
arr
进行加法运算时(如arr + 1
),结果是移动到下一个元素的地址(即从arr[0]
到arr[1]
)。 - 对
&arr
进行加法运算时(如&arr + 1
),结果是跳过整个数组到数组后面的地址,因为&arr
的类型意味着它的步长是整个数组的大小。
代码示例:
#include <stdio.h>
int main()
{
int arr[10] = {0};
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}
输出将显示 arr
和 &arr
的值相同,但类型不同。这是因为:
arr
给出了数组第一个元素的地址。&arr
也给出了数组的起始地址,但它指的是整个数组。
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("arr = %p\n", arr);
printf("&arr= %p\n", &arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr+1= %p\n", &arr+1);
return 0;
}
arr + 1
会跳到下一个整数的地址。&arr + 1
会跳过整个数组的内存布局,到达数组后的第一个位置。
总之,arr
作为指向第一个元素的指针,而 &arr
是指向整个数组的指针。
3.4 数组指针的使用示例
// print_arr1使用一个正常的二维数组形式作为参数
void print_arr1(int arr[3][5], int row, int col) {
int i, j;
for (i = 0; i < row; i++) {
for (j = 0; j < col; j++) {
// 打印当前元素后跟一个空格
printf("%d ", arr[i][j]);
}
// 每打印完一行后换行
printf("\n");
}
}
// print_arr2使用一个数组指针作为参数
void print_arr2(int (*arr)[5], int row, int col) {
int i, j;
for (i = 0; i < row; i++) {
for (j = 0; j < col; j++) {
// 打印当前元素后跟一个空格
printf("%d ", arr[i][j]);
}
// 每打印完一行后换行
printf("\n");
}
}
int main() {
// 初始化一个3行5列的二维数组
int arr[3][5] = {{1,2,3,4,5}, {6,7,8,9,10}, {11,12,13,14,15}};
// 调用print_arr1来打印数组
print_arr1(arr, 3, 5);
// 调用print_arr2来打印数组
print_arr2(arr, 3, 5);
return 0;
}
3.5 综合示例
int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];
第一个是基本整数数组,arr 是一个包含5个整数的数组。
第二个是指针数组,parr1 是一个数组,包含10个指向整数的指针。这意味着每个数组元素都是一个指针,可以指向一个整数。
第三个是数组指针,parr2 是一个指针,指向一个包含10个整数的数组。它不是一个数组,而是一个单独的指针。
第四个是指向数组的指针数组,parr3 是一个数组,包含10个指针,其中每个指针指向一个包含5个整数的数组。
4. 数组参数、指针参数
将一维数组作为参数传递给函数时,实际传递的是数组的第一个元素的地址,而不是整个数组的副本。这是因为数组名在大多数表达式中会退化为指向其首元素的指针。因此,传递数组给函数时,通常是传递指向数组首地址的指针。这种传递方式使得函数能够通过这个指针访问和修改原始数组的内容。
4.1 一维数组传参
1. 通过指针传递
最简单且最常见的一种方式是直接将数组作为指针传递。这种方法不需要在函数原型中指定数组的大小。
#include <stdio.h>
// 定义一个函数,接收一个整数指针和一个表示数组大小的整数
void printArray(int *array, int size) {
// 循环遍历数组,从0到size-1
for (int i = 0; i < size; i++) {
// 打印每个数组元素后跟一个空格
printf("%d ", array[i]);
}
// 所有元素打印完毕后换行
printf("\n");
}
int main() {
// 初始化一个整数数组
int arr[] = {1, 2, 3, 4, 5};
// 调用printArray函数,传递数组和数组的大小
printArray(arr, 5);
return 0;
}
在这个示例中,printArray
函数接收一个指向整数的指针和数组的大小。在 main
函数中,数组 arr
的名称在调用 printArray
时退化为指向其首元素的指针。
2. 通过固定大小数组参数传递
在函数参数中明确数组的大小,尽管这样做并不改变传递方式(仍然是传递指针),但它可以提供额外的语义信息,表明函数预期的数组大小。
3. 通过可变长度数组参数传递(C99特性)
#include <stdio.h>
// 定义一个函数,第一个参数是数组的大小,第二个参数是可变长度的整数数组
void printArray(int size, int array[size]) {
// 使用第一个参数size来控制循环遍历数组
for (int i = 0; i < size; i++) {
// 打印每个数组元素后跟一个空格
printf("%d ", array[i]);
}
// 所有元素打印完毕后换行
printf("\n");
}
int main() {
// 初始化一个整数数组
int arr[] = {1, 2, 3, 4, 5};
// 调用printArray函数,传递数组的大小和数组本身
printArray(5, arr);
return 0;
}
在这个示例中,printArray
的第二个参数是一个可变长度数组,其大小由第一个参数指定。这允许函数在编译时知道数组的确切大小,但实际上这种方法仍然通过地址传递数组。
4.2 二维数组传参
在C中,将二维数组作为参数传递给函数涉及到类似一维数组的指针退化行为,因为同时处理两个维度的数据。
前面提到过,二维数组可以视为数组的数组。在内存中,二维数组通常以行主顺序存储,即数组的行连续存放。例如,定义一个二维数组 int arr[3][4]
表示一个有3行4列的数组。
1. 明确列维度的数组参数
最常见且最直接的方法是在函数参数中明确指定数组的列数。行数可以是变量,但列数必须是常量,这样编译器才能正确计算行跳转。
#include <stdio.h>
// 定义一个函数,接受一个二维数组和一个整数表示行数
void printMatrix(int arr[][4], int rows) {
// 外层循环遍历每一行
for (int i = 0; i < rows; i++) {
// 内层循环遍历每一列
for (int j = 0; j < 4; j++) {
// 打印当前元素后跟一个空格
printf("%d ", arr[i][j]);
}
// 完成一行的打印后换行
printf("\n");
}
}
int main() {
// 初始化一个3行4列的二维数组
int matrix[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
// 调用函数printMatrix,传递二维数组和行数
printMatrix(matrix, 3);
return 0;
}
在这个例子中,printMatrix
函数接受一个每行有4列的整数二维数组。行数通过参数传递,而列数在函数定义中固定。
2. 使用指针到数组的指针
你可以将二维数组作为一个指向含有特定列数的一维数组的指针来传递。这种方式在语法上比较复杂,但它提供了更多的灵活性。
#include <stdio.h>
// 定义一个函数,接受一个指向含有4个整数的数组的指针和一个整数表示行数
void printMatrix(int (*arr)[4], int rows) {
// 外层循环遍历每一行
for (int i = 0; i < rows; i++) {
// 内层循环遍历每一列
for (int j = 0; j < 4; j++) {
// 打印当前元素后跟一个空格
printf("%d ", arr[i][j]);
}
// 完成一行的打印后换行
printf("\n");
}
}
int main() {
// 初始化一个3行4列的二维数组
int matrix[3][4] = {{1, 2, 3, 4}, {5
在这种情况下,printMatrix
接收一个指向有4个整数的数组的指针,它实际上是一个指向二维数组第一行的指针。
传递二维数组时,必须确保至少列的维度在函数参数中是已知的。这是因为二维数组在退化为指针时需要保持足够的信息来计算跨行的数据访问。
4.3 一级指针传参
一级指针,通常简称为指针,是一个变量,其存储的是另一个变量的内存地址。当将一个指针作为参数传递给函数时,实际上是在传递指向某个数据的内存地址。
一级指针传参的作用
- 数据修改: 通过指针传递,函数可以直接修改原始数据。
- 内存效率: 传递数据的指针而不是数据本身,可以减少内存使用和提高程序效率。
- 功能扩展: 指针传参使函数能够处理动态内存分配的数据,以及创建和操作复杂的数据结构,如链表、树、图等。
1. 修改基本数据类型的值
#include <stdio.h>
// 定义一个函数,使用指针参数来修改传入的整数的值
void increment(int *ptr) {
(*ptr)++; // 使用解引用操作符(*)来修改指针指向的值
}
int main() {
int a = 10;
increment(&a); // 传递a的地址
printf("a after increment: %d\n", a); // 输出修改后的值
return 0;
}
在这个示例中,increment
函数通过指针接收一个整数的地址,并在函数内部直接修改这个整数的值。这显示了如何通过指针传参来修改函数外部定义的变量的值。
2. 处理数组
#include <stdio.h>
// 使用指针参数处理数组
void printArray(int *arr, int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]); // 通过指针访问数组元素
}
printf("\n");
}
int main() {
int numbers[] = {1, 2, 3, 4, 5};
printArray(numbers, 5); // 数组名作为指针传递
return 0;
}
这个示例展示了如何使用指针来处理数组。在 C 中,数组名在传递给函数时自然退化为指向数组首元素的指针。
3. 代码示例
#include <stdio.h>
// 定义一个函数,接收一个整数指针和一个表示数组大小的整数
void print(int *p, int sz)
{
int i = 0;
// 循环遍历从0到sz-1的索引
for(i = 0; i < sz; i++)
{
// 打印指针p指向的当前元素,指针通过加i进行偏移
printf("%d\n", *(p + i));
}
}
//函数使用一个循环来遍历指针指向的数组。循环变量 i 从 0 开始,直到小于 sz。
//在循环体中,使用 *(p + i) 来访问数组的第 i 个元素并打印。
//p + i 是指针算术的应用,它计算出从 p 开始的第 i 个位置的地址,并通过解引用操作符 * 来获取该位置上的数据。
int main()
{
// 初始化一个含有10个整数的数组,第10个整数默认初始化为0
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; // Note: 最后一个元素arr[9]为0
// 定义一个指针p,指向数组arr的首元素
int *p = arr;
// 计算数组的大小,即数组的总字节大小除以单个元素的大小
int sz = sizeof(arr) / sizeof(arr[0]); // sz计算得到数组的元素数量
// 调用print函数,传递数组指针和数组大小
print(p, sz);
return 0;
}
4.4 二级指针传参
二级指针可以存储另一个指针的地址,因此它是一个指向指针的指针。将二级指针用作函数参数时,这使得函数能够修改一级指针的值,以及一级指针所指向的数据。
1. 修改指针的指向
#include <stdio.h>
#include <stdlib.h>
void allocateMemory(int **p) {
*p = (int *)malloc(sizeof(int)); // 动态分配内存
**p = 100; // 设置分配内存的值
}
int main() {
int *ptr = NULL;
allocateMemory(&ptr);
if (ptr != NULL) {
printf("Value: %d\n", *ptr); // 应输出100
free(ptr); // 释放内存
}
return 0;
}
在这个示例中,allocateMemory 函数接收一个 int ** 类型的参数,即一个指向 int * 的指针。该函数通过二级指针修改一级指针 ptr 的指向,并为它分配内存。
2. 修改链表头节点
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int value;
struct Node *next;
} Node;
void insertAtBeginning(Node **head, int val) {
Node *newNode = (Node *)malloc(sizeof(Node));
newNode->value = val;
newNode->next = *head; // 新节点指向当前头节点
*head = newNode; // 更新头节点为新节点
}
void printList(Node *head) {
Node *temp = head;
while (temp != NULL) {
printf("%d -> ", temp->value);
temp = temp->next;
}
printf("NULL\n");
}
int main() {
Node *head = NULL;
insertAtBeginning(&head, 10);
insertAtBeginning(&head, 20);
insertAtBeginning(&head, 30);
printList(head);
// 释放链表内存...
return 0;
}
在这个示例中,insertAtBeginning 函数接收一个指向链表头节点的指针的地址(即二级指针)。这允许函数直接修改头节点指针,从而在链表的开始插入新节点。
5. 函数指针
函数指针是指向函数的指针,用于存储函数的地址。这使得程序能够动态地调用不同的函数,增加程序的灵活性和功能性。
返回类型 (*指针变量名)(参数列表);
5.1 使用函数指针
1. 基本的函数指针使用
#include <stdio.h>
// 定义一个函数
int add(int x, int y) {
return x + y;
}
int main() {
// 声明一个指向函数的指针
int (*func_ptr)(int, int) = add;
// 使用函数指针调用函数
int result = func_ptr(5, 3);
printf("Result: %d\n", result); // 输出8
return 0;
}
//func_ptr 是一个函数指针,它指向 add 函数。通过这个指针可以调用 add 函数。
5.2 函数指针数组
int (*parr1[10])();
parr1 先和 [] 结合,说明 parr1是数组,数组的内容是 int (*)() 类型的函数指针。 函数指针数组的用途:转移表
#include <stdio.h>
int add(int x, int y) {
return x + y;
}
int subtract(int x, int y) {
return x - y;
}
int main() {
// 创建一个函数指针数组,存储不同的函数
int (*operations[2])(int, int);
operations[0] = add;
operations[1] = subtract;
// 动态调用函数
int sum = operations[0](10, 5); // 调用 add
int diff = operations[1](10, 5); // 调用 subtract
printf("Sum: %d, Difference: %d\n", sum, diff);
return 0;
}
5.3 代码示例
传统方式实现计算器:
#include <stdio.h>
// 定义四个基本运算函数
int add(int a, int b) {
return a + b;
}
int sub(int a, int b) {
return a - b;
}
int mul(int a, int b) {
return a * b;
}
int div(int a, int b) {
return a / b;
}
int main() {
int x, y; // 用户输入的两个操作数
int input = 1; // 用户选择的操作
int ret = 0; // 存储操作结果
// 循环直到用户选择退出
do {
// 显示操作菜单
printf("*************************\n");
printf(" 1:add 2:sub\n");
printf(" 3:mul 4:div\n");
printf("*************************\n");
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;
}
使用函数指针数组的实现:
#include <stdio.h>
// 定义四个基本运算函数
int add(int a, int b) {
return a + b;
}
int sub(int a, int b) {
return a - b;
}
int mul(int a, int b) {
return a * b;
}
int div(int a, int b) {
return a / b;
}
int main() {
int x, y; // 用户输入的两个操作数
int input = 1; // 用户选择的操作
int ret = 0; // 存储操作结果
// 函数指针数组,用于根据用户输入调用相应的函数
int(*p[5])(int x, int y) = {0, add, sub, mul, div};
// 循环直到用户选择退出
while (input) {
// 显示操作菜单
printf("*************************\n");
printf(" 1:add 2:sub\n");
printf(" 3:mul 4:div\n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
if (input <= 4 && input >= 1) {
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y); // 通过函数指针数组调用相应的函数
} else {
printf("输入有误\n");
}
printf("ret = %d\n", ret);
}
return 0;
}
6. 指向函数指针数组的指针
函数指针
首先,理解函数指针很重要:它是一个变量,存储了函数的地址,可以像调用普通函数一样通过函数指针调用函数。
函数指针数组
函数指针数组是一组函数指针,存储在一个数组中。每个元素都是一个指向具有特定签名的函数的指针。例如,void (*array[])(int)
表示一个数组,其中每个元素都是指向接受一个 int
参数并返回 void
的函数的指针。
指向函数指针数组的指针
一个指向函数指针数组的指针可以被用来动态地访问和调用数组中的函数。这个指针本身可以指向包含多个函数指针的数组。
代码示例:
#include <stdio.h>
void func1(int x) {
printf("Function 1 called with %d\n", x);
}
void func2(int x) {
printf("Function 2 called with %d\n", x);
}
void func3(int x) {
printf("Function 3 called with %d\n", x);
}
int main() {
// 创建一个函数指针数组,包含了三个函数的指针。
void (*funcs[])(int) = {func1, func2, func3};
// 创建一个指向函数指针数组的指针
//pfuncs 是一个指向 funcs 数组的指针。
//这意味着 pfuncs 可以被用来访问和调用 funcs 数组中的任何函数。
void (**pfuncs)(int) = funcs;
// 调用第一个函数
(*pfuncs[0])(10); // func1(10)
// 调用第二个函数
pfuncs ; // func2(20)
// 调用第三个函数
pfuncs ; // func3(30)
return 0;
}
7. 回调函数
假设需要一个函数来遍历整数数组,但你想让这个函数足够通用,以便它可以对数组中的每个元素执行不同的操作。
#include <stdio.h>
// 定义回调函数类型,它是指向一个接受 int 并返回 void 的函数的指针。
typedef void (*callback_t)(int);
// 实现一个函数,接受一个整数数组、数组大小和一个回调函数。它遍历数组,对每个元素调用回调函数。
void traverseArray(int *array, int size, callback_t callback) {
for (int i = 0; i < size; i++) {
callback(array[i]); // 调用回调函数
}
}
// 回调函数实现:打印元素
void printElement(int element) {
printf("%d ", element);
}
// 回调函数实现:打印元素的平方
void printSquare(int element) {
printf("%d ", element * element);
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
int n = sizeof(arr) / sizeof(arr[0]);
// 使用printElement回调函数
printf("Array elements: ");
traverseArray(arr, n, printElement);
printf("\n");
// 使用printSquare回调函数
printf("Squares of elements: ");
traverseArray(arr, n, printSquare);
printf("\n");
return 0;
}
printElement 和 printSquare 是两个回调函数,分别打印数组元素和它们的平方。
主函数:在 main 函数中,traverseArray 被两次调用,每次调用时使用不同的回调函数来展示不同的行为。
#include <stdio.h>
#include <stdlib.h> // 必须包含stdlib.h来使用qsort函数
// 自定义比较函数,用于qsort
// 比较函数需要两个const void* 参数,返回int值指示哪个元素应当排在前面
int int_cmp(const void *p1, const void *p2) {
// 将void指针转换为int指针,然后解引用获取值进行比较
return (*(int *)p1 - *(int *)p2);
}
int main() {
// 初始化一个整数数组
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
// 调用qsort函数进行排序
// 参数1: arr是要排序的数组
// 参数2: 数组的元素数量
// 参数3: 每个元素的大小,这里是int的大小
// 参数4: 指向比较函数的指针
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
// 遍历排序后的数组并打印每个元素
for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
#include <stdio.h>
// 自定义比较函数,用于比较两个整数
int int_cmp(const void * p1, const void * p2) {
return (*(int *)p1 - *(int *)p2); // 强制转换为int指针,并进行比较
}
// 通用的交换函数,根据提供的元素大小交换两个元素
void _swap(void *p1, void *p2, int size) {
int i = 0;
for (i = 0; i < size; i++) {
char tmp = *((char *)p1 + i); // 逐字节交换
*((char *)p1 + i) = *((char *)p2 + i);
*((char *)p2 + i) = tmp;
}
}
// 通用的冒泡排序函数
void bubble(void *base, int count, int size, int(*cmp)(void *, void *)) {
int i = 0;
int j = 0;
for (i = 0; i < count - 1; i++) { // 外层循环控制排序的轮次
for (j = 0; j < count - i - 1; j++) { // 内层循环对未排序的部分进行比较和交换
if (cmp((char *)base + j * size, (char *)base + (j + 1) * size) > 0) {
// 调用比较函数,根据比较结果决定是否交换
_swap((char *)base + j * size, (char *)base + (j + 1) * size, size);
}
}
}
}
int main() {
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 }; // 定义一个整数数组
// char *arr[] = {"aaaa", "dddd", "cccc", "bbbb"}; // 这是对字符串数组的排序示例,已被注释
int i = 0;
// 调用bubble函数进行排序
bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
// 遍历并打印排序后的数组
for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}