一、数组名的理解
1.1 数组名
在C语言指针完全指南:从入门到精通(上)中我们在使用指针访问数组的内容时,有这样一个代码
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
这里我们使用&arr[0],可以拿到数组第一个元素的地址,但其实数组名本身就是一个地址,而且数组名就是数组首元素的地址 ,我们通过下面这个代码来观察一下:
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
printf("&arr[0] = %p\n", &arr[0]);
printf("arr = %p\n", arr);
return 0;
}
这里我们可以看到,&arr[0]和arr的地址是一样的,都是00000038531FF608,那么我们就可以印证上面所说的 数组名就是数组首元素的地址
但是如果说数组名就是数组首元素的地址,那么下面这个示例2该如何理解呢
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", sizeof(arr));
return 0;
}
大家可以看到输出结果是40,但如果是数组首元素的话,是地址那结果不应该是4/8吗
这里就涉及到两个点:
- sizeof(数组名),size中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
- &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)
- 除去以上两点,如何地方的数组名,都表示首元素的地址。
示例3:
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
printf("&arr[0] = %p\n", &arr[0]);
printf("arr = %p\n", arr);
printf("&arr = %p\n", arr);
return 0;
}
可以看到三个打印的结果是一样的,那么arr和&arr有什么差别呢?
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
printf("&arr[0] = %p\n", &arr[0]);
printf("&arr[0]+1 = %p\n", &arr[0]+1);
printf("arr = %p\n", arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr = %p\n", arr);
printf("&arr+1 = %p\n", arr+1);
return 0;
}
这里我们发现&arr[0]和&arr[0]+1相差了4个字节,arr和arr+1相差了4个字节,是因为&arr[0]和arr都是首元素的地址,+1就是跳过一个元素。
但是&arr和&arr+1就相差了40个字节,原因是&arr是整个数组的地址,&arr+1是跳过了一整个数组,所以相差40
1.2 通过sizeof和strlen的对比来深刻理解数组名
理解数组名之前,笔者先讲清楚sizeof和strlen
1.2.1 sizeof和strlen
sizeof
- size是来计算变量所占内存空间大小的,单位是字节。
- 如果操作数是类型的话,计算的是使用类型创建的变量所占内存空间的大小。它在编译时计算,因此不会影响程序的运行时性能。
sizeof
是一个运算符,而不是一个函数,因此不需要使用括号(但在某些情况下使用括号是合法的)。- sizeof只关注占用内存空间的大小,不在乎内存中存放了什么数据
int main() { int a = 10; printf("%d\n",sizeof(a)); printf("%d\n",sizeof a); printf("%d\n",sizeof(int)); return 0; }
打印结果都为4
strlen
- strlen是c语言库函数,用来求字符串长度
size_t strlen( const char. * str );
- 统计的是从strlen函数的参数str中这个地址开始向后,\0之前字符串中字符的个数。
- strlen函数会一直向后找\0字符,直到找到为止,所以可能存在越界查找
int main() { char arr1[3] = { 'a','b','c' }; char arr2[] = "a,b,c"; printf("%d\n", strlen(arr1)); printf("%d\n", strlen(arr2)); printf("%d\n", sizeof(arr1)); printf("%d\n", sizeof(arr2)); return 0; }
1.2.2 示例1
#include <stdio.h>
int main()
{
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(a+0));
printf("%d\n", sizeof(*a));
printf("%d\n", sizeof(a+1));
printf("%d\n", sizeof(a[1]));
printf("%d\n", sizeof(&a));
printf("%d\n", sizeof(&a[0]));
printf("%d\n", sizeof(&a[0]+1));
return 0;
}
以下为具体解释:
#include <stdio.h>
int main()
{
int a[] = { 1, 2, 3, 4 }; // 定义一个整型数组 a,包含 4 个元素
printf("%d\n", sizeof(a)); // 计算数组 a 的大小
// sizeof(a) 返回整个数组的字节数。数组 a 中有 4 个 int 类型的元素,假设 int 占 4 字节,则 sizeof(a) = 4 * 4 = 16
// 输出: 16
printf("%d\n", sizeof(a + 0)); // 计算 a + 0 的大小
// a + 0 是指向数组第一个元素的指针,sizeof(a + 0) 返回指针的大小。假设指针在该平台上占 8 字节(64 位系统),则输出 8/4
// 输出: 8
printf("%d\n", sizeof(*a)); // 计算 *a 的大小
// *a 解引用指针,得到数组的第一个元素,即 a[0],其类型为 int。sizeof(*a) 返回 int 的大小,假设 int 占 4 字节,则输出 4
// 输出: 4
printf("%d\n", sizeof(a + 1)); // 计算 a + 1 的大小
// a + 1 是指向数组第二个元素的指针,sizeof(a + 1) 返回指针的大小,假设指针在该平台上占 8 字节(64 位系统),则输出 8/4
// 输出: 8
printf("%d\n", sizeof(a[1])); // 计算 a[1] 的大小
// a[1] 是数组的第二个元素,其类型为 int。sizeof(a[1]) 返回 int 的大小,假设 int 占 4 字节,则输出 4
// 输出: 4
printf("%d\n", sizeof(&a)); // 计算 &a 的大小
// &a 是数组的地址,类型为 int (*)[4](指向包含 4 个 int 的数组的指针)。sizeof(&a) 返回指针的大小,假设指针在该平台上占 8 字节(64 位系统),则输出 8
// 输出: 8/4
printf("%d\n", sizeof(&a[0])); // 计算 &a[0] 的大小
// &a[0] 是数组第一个元素的地址,类型为 int*(指向 int 的指针)。sizeof(&a[0]) 返回指针的大小,假设指针在该平台上占 8 字节(64 位系统),则输出 8
// 输出: 8/4
printf("%d\n", sizeof(&a[0] + 1)); // 计算 &a[0] + 1 的大小
// &a[0] + 1 是指向数组第二个元素的指针,sizeof(&a[0] + 1) 返回指针的大小,假设指针在该平台上占 8 字节(64 位系统),则输出 8
// 输出: 8/4
return 0;
}
1.2.3 示例2
int main()
{
int a[] = "abcdef";
printf("%d\n", strlen(a));
printf("%d\n", strlen(a+0));
printf("%d\n", strlen(*a));
printf("%d\n", strlen(a+1));
printf("%d\n", strlen(a[1]));
printf("%d\n", strlen(&a));
printf("%d\n", strlen(&a[0]));
printf("%d\n", strlen(&a[0]+1));
return 0;
}
int main()
{
char a[] = "abcdef";
printf("%d\n", strlen(a));//访问整个数组,统计\0之前的元素个数 6
printf("%d\n", strlen(a+0));//首元素地址 同上
printf("%d\n", strlen(*a));首元素地址 *解引用拿到首元素。1
printf("%d\n", strlen(a+1));第二个元素开始 5
printf("%d\n", strlen(a[1]));整个数组 4/8
printf("%d\n", strlen(&a)); 整个数组 6
printf("%d\n", strlen(&a[0]));同第一个
printf("%d\n", strlen(&a[0]+1));第二个元素的地址,\0之前有5个元素
return 0;
}
二、使用指针访问数组
有了前面知识的支持,再结合数组的特点,我们就可以很方便的使用指针来访问数组了。
#include <stdio.h>
int main() {
int arr[10] = {0};
int sz = sizeof(arr) / sizeof(arr[0]);
int *p = arr;
for (int i = 0; i < sz; i++)
{
scanf("%d",p+i);
}
//输出
for (int i = 0; i <sz ;i++) {
printf("%d ",*(p+i)); //*(p+i)可替换为p[i]
}
return 0;
}
- 定义一个长度为10的整型数组
arr
,所有元素初始化为0。- 计算数组长度:
sizeof(arr)
获取数组总字节数,sizeof(arr[0])
获取单个元素字节数,相除得到元素数量10。- 定义指针
p
并指向数组首地址(arr
等价于&arr[0]
)。- 通过指针算术访问数组元素:
p+i
等价于&arr[i]
,scanf
将输入值存入对应地址。循环10次读取用户输入- 解引用指针输出元素:
*(p+i)
等价于arr[i]
,循环10次打印数组内容。关键点说明
- 指针算术:
p+i
根据整型大小自动计算偏移量(如i=1
时,实际地址为p + 4
字节,假设int
占4字节)。- 等价写法:
*(p+i)
可替换为p[i]
,效果相同。- 终止条件:两个循环均以
sz
(值为10)为边界,确保不越界
三、一位数组传参的本质
首先我们从一个问题开始,我们之前都是从函数外部计算数组的元素个数,那么我们可以直接把数组传给一个函数后,在内部求元素个数吗?
那么接下来看下面这个代码
void test(int arr[]) {
int sz2 = sizeof(arr) / sizeof(arr[0]);
printf("%d\n", sz2);
}
int main() {
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int sz1 = sizeof(arr) / sizeof(arr[0]);
printf("sz1 = %d\n", sz1);
test(arr);
return 0;
}
对于这个函数我们输出的理想结果应该是sz1 = 10,sz2的输出10,但是调试后并没有我们的理想结果
这是为什么呢?
这就是我们数组传参的本质了,我们知道数组名是首元素的地址:那么在数组传参的时候,传参的是数组名,也就是说本质上数组传惨的传递的就是数组首元素的地址。
所以函数形参的部分理论上应该使用指针变量来接受元素的地址。那么我们函数内部函数的sizeof(arr)计算的是一个地址的大小(字节),而不是数组的大小(字节)。正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。
void test(int arr[])//参数写成数组形式,本质上还是指针
{
printf("%d\n", sizeof(arr));
}
void test(int *arr) {//参数写成指针形式
printf("%d\n", sizeof(arr));//计算一个指针变量的大小
}
int main() {
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int sz1 = sizeof(arr) / sizeof(arr[0]);
printf("sz1 = %d\n", sz1);
test(arr);
return 0;
}
四、冒泡排序
冒泡排序的核心思想其实就是:两两相邻的元素进行比较
#include <stdio.h>
void bubbleSort(int arr[], int n) {
int i, j;
for (i = 0; i < n-1; i++) {
// 最后i个元素已经是有序的
for (j = 0; j < n-i-1; j++) {
if (arr[j] > arr[j+1]) {
// 交换 arr[j] 和 arr[j+1]
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
void printArray(int arr[], int size) {
int i;
for (i=0; i < size; i++)
printf("%d ", arr[i]);
printf("\n");
}
int main() {
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int n = sizeof(arr)/sizeof(arr[0]);
printf("未排序数组: \n");
printArray(arr, n);
bubbleSort(arr, n);
printf("排序后数组: \n");
printArray(arr, n);
return 0;
}
来个比喻:想象你有一群气球,每个气球上有一个数字。你的任务是按照从小到大的顺序排列这些气球。你可以从第一个气球开始,依次比较相邻的两个气球上的数字。如果前面的气球上的数字比后面的气球上的数字大,你就把这两个气球的位置调换一下。这样,最大的气球就会“浮”到最后面去。然后,你再从头开始检查一遍,这次最大的气球已经被排好位置了,所以你只需要检查到倒数第二个气球。不断地重复这个过程,直到所有的气球都按顺序排列好了。
也就是利用两个for循环,从下标0开始,前后比较,当x > x+1时,互换位置,再循环内依次比较
调试后显示:
五、二级指针
指针变量也是变量,是变量就会有地址,那指针变量的地址该放在哪?
这就是二级指针
对于二级指针,你可以认为就是储存一级指针的指针
二级指针的运算有:
*ppa通过ppa中的地址进行解引用,这样我们找到的是pa,*ppa其实访问的就是pa
int b = 20; *PPa = &b;//等价于pa = &b;
**ppa先通过*ppa找到pa,然后对pa进行解引用操作:*pa,那找到的就是a
**ppa = 30; //等价于*pa = 30; //等价于a = 30;
六、指针数组
指针数组结合了指针和数组两者:它是一个数组,其中每个元素都是指针。
int *ptrArray[5];
//表示一个数组ptrArray,有五个元素,每个元素都是指向整数的指针
用一个简单C语言示例来演示指针数组的工作原理。假设我们有一个指针数组,每个指针指向一个整数。
#include <stdio.h>
int main() {
int a = 10, b = 20, c = 30;
int *ptrArray[3]; // 声明一个指针数组,有3个元素
// 分配指针:每个元素指向一个整数变量
ptrArray[0] = &a;
ptrArray[1] = &b;
ptrArray[2] = &c;
// 访问和修改值
printf("初始值: %d, %d, %d\n", *ptrArray[0], *ptrArray[1], *ptrArray[2]);
*ptrArray[1] = 25; // 修改第二个指针指向的值
printf("修改后: %d, %d, %d\n", a, b, c); // 输出: 10, 25, 30
return 0;
}
在这个例子中:
ptrArray
是一个指针数组,每个元素存储一个地址。- 通过
*ptrArray[i]
可以访问或修改指向的值。- 运行结果会显示指针如何间接操作数据。
常见错误:
- 空指针问题:如果指针未初始化就解引用,会导致崩溃。教学时强调初始化。
- 内存泄漏:如果指针指向动态分配的内存(如
malloc
),忘记释放会造成泄漏。
七、指针数组模拟二维数组(加强记忆)
#include <stdio.h>
int main() {
int arr1[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int arr2[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int arr3[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *parr[3] = {arr1 , arr2 , arr3};
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 10; j++) {
printf("%d ", parr[i][j]);
}
printf("\n");
}
return 0;
}
这里我会十分详细的解释这段代码来帮助大家更加好的消化指针数组以及二维数组的知识
int arr1[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int arr2[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int arr3[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
这里定义了三个整型数组
arr1
,arr2
, 和arr3
,每个数组包含10个整数,数值从1到10。接下来我们定义一个指针数组:
int *parr[3] = {arr1, arr2, arr3};
这里定义了一个指针数组
parr
,它可以存储3个指向整型数据的指针。具体来说:
parr[0]
存储arr1
的首地址。parr[1]
存储arr2
的首地址。parr[2]
存储arr3
的首地址。通过这种方式,
parr
实际上是一个指向数组的指针数组,可以用来访问和操作多个数组。使用嵌套循环打印数组元素
for (int i = 0; i < 3; i++) { for (int j = 0; j < 10; j++) { printf("%d ", parr[i][j]); } printf("\n"); }
外层循环遍历指针数组
parr
中的每一个指针(即arr1
,arr2
,arr3
)。 内层循环遍历每个数组中的每一个元素,并使用printf
函数将其打印出来。具体步骤如下:
- 当
i = 0
时,parr[i]
是arr1
的首地址,内层循环打印arr1
的所有元素。- 当
i = 1
时,parr[i]
是arr2
的首地址,内层循环打印arr2
的所有元素。- 当
i = 2
时,parr[i]
是arr3
的首地址,内层循环打印arr3
的所有元素。每次内层循环结束后,使用
printf("\n");
打印一个换行符,以便将不同数组的输出分开。
总的来说这段代码展示如何使用指针数组来管理和操作多个数组。通过指针数组 parr
,我们可以方便地访问和处理多个数组,而不需要为每个数组单独编写访问逻辑。这对于理解和掌握指针和数组的关系非常有帮助。
八、字符指针变量
在指针类型中我们知道有一种指针变量类型为字符指针:char*
一般使用:
#include <stdio.h>
int main() {
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
#include <stdio.h>
int main() {
const char *pstr = "hello world";//这里是把一个字符串放到pstr指针变量里了吗
printf("%s\n", pstr);
return 0;
}
代码const char * pstr = “hello word” 特别容易让同学以为是把字符串hello world放到字符指针pstr里面,但其实本质上是吧字符串hello world 的首字符的地址放入pstr里
这里要注意一点
字符指针 vs 常量字符指针:
char *ptr;
:指向可变字符的数据。const char *ptr;
或char const *ptr;
:指向常量字符的数据,不能通过该指针修改所指向的数据。这里定义了一个指向常量字符的指针
pstr
,并将其初始化为字符串字面量"hello world"
的地址。需要注意的是,"hello world"
是一个字符串字面量,存储在只读内存区域(通常是静态存储区)。因此,通过pstr
不能修改这个字符串的内容。
这里我和大家共享一下我学习时遇到的面试题:
#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
和str2
是两个独立的字符数组,分别存储了字符串"hello bit."
。- 每个数组都有自己的内存空间,因此
str1
和str2
的地址是不同的。- 因此,这个条件判断应该是
false
,应该输出str1 and str2 are not same
。
str3
和str4
是指向常量字符的指针。- 字符串字面量
"hello bit."
存储在只读内存区域(通常是静态存储区)。- 在某些编译器和优化设置下,相同的字符串字面量可能会被合并到同一个内存位置。因此,
str3
和str4
可能会指向同一个地址。- 因此,
str3
和str4
可能会指向同一个地址,导致这个条件判断为true
,输出str3 and str4 are same
。
九、数组指针变量
9.1 数组指针变量的概念
之前我们学习了指针数组,指针数组是一种数组,数组中存放的是地址(数组)
数组指针变量是指针变量?还是数组?
答案是:指针变量
它存储的是数组的地址,而不是单个元素的地址。数组指针变量的声明形式如下:
int (*ptr)[5];
//这里 ptr 是一个指向包含5个整数的数组的指针。
#include <stdio.h>
int main() {
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5] = {6, 7, 8, 9, 10};
int (*ptr)[5];
ptr = &arr1; // ptr 指向 arr1
for (int i = 0; i < 5; i++) {
printf("%d ", (*ptr)[i]); // 输出 arr1 的元素
}
printf("\n");
ptr = &arr2; // ptr 指向 arr2
for (int i = 0; i < 5; i++) {
printf("%d ", (*ptr)[i]); // 输出 arr2 的元素
}
printf("\n");
return 0;
}
- 这里定义了一个包含2行5列的二维数组
arr
- int (*ptr)[5]; 这里定义了一个指向包含5个整数的数组的指针
ptr
。- ptr = &arr1; 这里指向arr1
- 遍历数组输出访问到arr1的元素
- 下面同理
9.2 实际使用的例子
数组指针变量在处理多维数组时非常有用。例如:
#include <stdio.h>
int main() {
int arr[2][5] = {
{1, 2, 3, 4, 5},
{6, 7, 8, 9, 10}
};
int (*ptr)[5];
ptr = arr; // ptr 指向 arr 的第一行
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 5; j++) {
printf("%d ", ptr[i][j]);
}
printf("\n");
}
return 0;
}
总结:
- 数组指针变量 是一种指向数组的指针。
- 它存储的是整个数组的地址,而不是单个元素的地址。
- 在处理多维数组或需要动态管理数组的情况下,数组指针变量非常有用。
注意:[]的优先级要高于*号,所以必须加上()来保证ptr先和*结合
9.3 数组指针变量怎么初始化
数组指针变量是用来存放数组地址的,那怎么获取数组的地址呢?我们之前在学scanf的时候就讲过的&数组名
int arr[10] = {0};
&arr;//得到的就是数组的地址
如果要存放个数组的地址,就得存放在数组指针变量中,如下:
int(*p)[10] = &arr;
大家可以试试这个代码:
#include <stdio.h>
int main()
{
int arr[10] = {0};
int (*p)[10] = &arr;
return 0;
}
大家通过监视其实可以观察到p和&arr的类型是完全一致的
十、二维数组传参的实质
有了对数组指针的理解,我们就能够讲一下二维数组传参的本质了
之前我们要给二维数组传参的话我们会这么写
void test(int a[3][5],int r ,int c) {
int i = 0;
int j = 0;
for(i = 0; i < r; i++) {
for(j = 0; j < c; j++) {
printf("%d ",a[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}};
test(arr,3,5);
return 0;
}
这里实参是二维数组,形参也写成二维数组的形式,那还有什么其他的写法吗?
首先我们再次理解一下二维数组
二维数组其实可以看做是每个元素是一位数组的数组,也就是二维数组的每个元素是一个一位数组。那么二维数组的首元素就是第一行,是个一维数组
如下图:
所以,根据数组名就是数组首元素的地址这个准则,二维数组的数组名表示的就是第一行的地址 ,是一位数组的地址。
根据上面的例子,第一行的一位数组的类型是int [5],所以第一行的地址类型就是数组指针类型. int(*)[5].那就意味着二维数组传参本质上也是传递了的地址,传递的是第一行这个一位数组的地址,那么形参也是可以写成指针形式的,如下图:
#include <stdio.h>
void test(int (*p)[5] , int r , int c) {
int i = 0;
int j = 0;
for (i = 0; i < r; i++) {
for (j = 0; j < c; 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}};
test(arr,3,5);
return 0;
}
十一、函数指针变量
11.1 函数指针的创建
什么是函数指针变量呢?
其实函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数,接下来听我细细分析
加入我们现在有一个函数:
int add(int a, int b) {
return a + b;
}
我们可以定义一个指向该函数的指针:
int (*funcPtr)(int, int);
//这里的 funcPtr 是一个指向返回类型为 int,并且接受两个 int 参数的函数的指针。
接下来我们初始化一下
funcPtr = add; // funcPtr 现在存储 add 函数的地址
//或者我们直接在初始化的时候声明
int (*funcPtr)(int, int) = add;
11.2 使用函数指针调用函数
你可以通过函数指针调用函数:
int result = funcPtr(3, 4); // 调用 add 函数,result 为 7
示例代码
#include <stdio.h>
// 定义两个简单的函数
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
// 定义一个函数,接受一个函数指针作为参数
void performOperation(int (*operation)(int, int), int a, int b) {
int result = operation(a, b);
printf("Result: %d\n", result);
}
int main() {
int num1 = 10;
int num2 = 5;
// 定义一个函数指针并初始化
int (*funcPtr)(int, int);
// 使用函数指针调用 add 函数
funcPtr = add;
performOperation(funcPtr, num1, num2); // 输出: Result: 15
// 使用函数指针调用 subtract 函数
funcPtr = subtract;
performOperation(funcPtr, num1, num2); // 输出: Result: 5
return 0;
}
一开始我们自定义了两个加法和减法函数
int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; }
随后我们定义一个函数指针以便调用我们刚刚创建的加减法函数
void performOperation(int (*operation)(int, int), int a, int b) { int result = operation(a, b); printf("Result: %d\n", result); } //接受一个函数指针 operation 作为参数以及两个整数 a 和 b。 //operation 指向一个返回类型为 int 并且接受两个 int 参数的函数。
主函数中,我们先定义两个变量num1,num2,
随后开始函数指针的创建:
int (*funcPtr)(int, int);
这时就是函数指针的作用体现了,我们创建的函数指针式用funcPter作为参数,以及两个int元素作为参数(初始化,现在没有值)
这时我们要使用函数来调用Add函数
funcPtr = add;//funcPtr 现在存储 add 函数的地址 performOperation(funcPtr, num1, num2); // 输出: Result: 15
减法同理
// 使用函数指针调用 subtract 函数 funcPtr = subtract; performOperation(funcPtr, num1, num2); // 输出: Result: 5
那么我们现在来看看调用结果:
十二、typedef关键字
typedef
是 C 语言中的一个关键字,用于为现有的数据类型创建一个新的名称(别名,就是改个名字)。这可以使代码更具可读性和可维护性。下面我将详细介绍 typedef
的用法,并通过示例来帮助大家更好地理解。
比如说你觉得unsigner int好长一串啊,手指要按12下,那如果我可以缩写一下,写成uint(这个大家自己定,但最好要有意义,毕竟这个关键字的初衷是提高我们程序员开发时的便捷性、可读性和可维护性) ,那我们可以这么使用:
typedef unsigned int uint;
//将unsigned重命名为uint
但如果是指针类型,我们该如何重命名呢,比如我们将*int改成ptr_t
typedef int* int ptr_t;
函数指针类型的重命名也是一样的,比如,将void(*)(int)类型改为pf_t,就可以这样
typedef void(*pfun_t)(int);//新的类型名必须在*的右边
那么在我们日常开发的时候往往会遇到比较复杂的情景,那么这时候typedef关键字能发挥什么样的作用呢?接下来我们看一个示例:
#include <stdio.h> // 定义一个结构体 struct Point { int x; int y; }; // 使用 typedef 创建结构体的别名 typedef struct Point Point; int main() { Point p1; p1.x = 10; p1.y = 20; printf("Point: (%d, %d)\n", p1.x, p1.y); return 0; }
没错,这是一个结构体,
在这个例子中,
typedef struct Point Point;
创建了结构体struct Point
的别名Point
,这样在声明变量时就不需要每次都写struct Point
。等我们后续学到数据结构,或者电子类的学生学习单片机,对于结构体的使用就多了,那这个时候我们用这个关键字就会大大便利我们的开发。
示例2:
那么我们刚刚学习了函数指针,关键字函数指针又有什么发挥呢?
#include <stdio.h> // 定义一个函数 int add(int a, int b) { return a + b; } // 使用 typedef 创建函数指针的别名 typedef int (*OperationFunc)(int, int); void performOperation(OperationFunc operation, int a, int b) { int result = operation(a, b); printf("Result: %d\n", result); } int main() { OperationFunc funcPtr = add; performOperation(funcPtr, 3, 4); // 输出: Result: 7 return 0; }
在这个例子中,
typedef int (*OperationFunc)(int, int);
创建了一个指向返回类型为int
并且接受两个int
参数的函数的指针的别名OperationFunc
,这样在声明函数指针变量时可以简化代码。
十三、函数指针数组
数组是一个存放相同类型的存储空间,我们已经学习了指针数组。
int * arr[10];
那要把函数的地址存到一个数组中,那这个数组就叫做函数指针数组
假设我们有这几个函数
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multiply(int a, int b) {
return a * b;
}
int divide(int a, int b) {
if (b != 0) {
return a / b;
} else {
printf("Error: Division by zero\n");
return 0;
}
}
我们可以定义一个函数指针数组来存储这些函数的地址:
typedef int (*OperationFunc)(int, int);
OperationFunc operations[] = {add, subtract, multiply, divide};
这里的 OperationFunc
是一个类型别名,表示指向返回类型为 int
并且接受两个 int
参数的函数的指针。
十四、转移表
转移表是一种数据结构,用于在程序中快速选择和调用不同的函数。想象一下,你有一个菜单驱动的程序,用户可以选择不同的操作,比如加法、减法、乘法和除法。
简单的例子
假设我们有一个简单的计算器程序,用户可以选择进行加法或减法运算。我们可以使用转移表来实现这个功能。
既然我们要做一个计算机程序,那我们先把加减乘除的函数定义一下
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; }
那么到这我们的计算器程序功能实现部分其实就已经完成了
接下来我们来完成主函数部分
#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(" 0:exit \n");
printf("********************\n");
printf("Enter your choice: ");
scanf("%d", &input);
switch (input) {
case 1:
printf("Enter a number: ");
scanf("%d %d", &x,&y);
ret = add(x, y);
printf("ret: %d\n", ret);
break;
case 2:
printf("Enter a number: ");
scanf("%d %d", &x,&y);
ret = sub(x, y);
printf("ret: %d\n", ret);
break;
case 3:
printf("Enter a number: ");
scanf("%d %d", &x,&y);
ret = mul(x, y);
printf("ret: %d\n", ret);
break;
case 4:
printf("Enter a number: ");
scanf("%d %d", &x,&y);
ret = div(x, y);
printf("ret: %d\n", ret);
break;
case 0:
printf("Thank you for using the program!\n");
break;
default:
printf("选择错误\n");
break;
}
}while(input);
return 0;
}