标签:C语言、指针、函数指针、动态内存、malloc、命令行参数
本文是《C语言指针从入门到进阶》的补充篇,重点整理函数指针、指针函数、堆区内存和 main 参数。
一、函数指针
1. 函数也有地址
程序编译后,函数代码也会放在内存中,所以函数也有地址。
c
int add(int a, int b)
{
return a + b;
}
-
add 是函数名;
-
函数名代表函数的入口地址;
-
也就是说,add 本身就是一个地址值。
2. 函数类型
c
int add(int a, int b)
去掉函数名 add,剩下的就是函数类型:
c
int (int, int)
表示:
-
返回值是 int;
-
有两个 int 类型的形参。
3. 定义函数指针
理解角度:
c
int (int, int) *p;
C 语言正确语法:
c
int (*p)(int, int);
表示:
-
p 是一个指针变量;
-
它指向一个函数;
-
该函数返回 int,接受两个 int 参数。
4. 使用函数指针调用函数
c
int add(int a, int b)
{
return a + b;
}
int main()
{
int (*p)(int, int) = add;
printf("%d\\n", p(3, 5)); // 8
printf("%d\\n", (*p)(3, 5)); // 8,通常不写 *
return 0;
}
函数指针一般不做 * 运算,*p 也跟不起作用一样,直接写 p(实参) 即可。
5. 函数指针的应用:回调函数
回调函数:通过函数指针调用的函数。
程序员写排序算法时,可以留一个接口:
c
void selectSort(int *a, int len, int (*cmp)(int, int));
项目经理或使用者传入不同的比较函数:
c
int ascending(int a, int b)
{
return a > b;
}
int descending(int a, int b)
{
return a < b;
}
调用:
c
selectSort(arr, len, ascending);
selectSort(arr, len, descending);
这就是回调的思想。
回调函数更详细的例子
回调函数:通过函数指针调用的函数。
排序算法可以留一个比较函数的接口:
c
void selectSort(int *a, int len, int (*cmp)(int, int));
使用者传入不同的比较函数:
c
int ascending(int a, int b)
{
return a > b;
}
int descending(int a, int b)
{
return a < b;
}
调用:
c
selectSort(arr, len, ascending);
selectSort(arr, len, descending);
这就是回调的思想。
练习:加减乘除回调
c
#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 divide(int a, int b) { return a / b; }
void processData(int a, int b, int (*pfunc)(int, int))
{
printf("result = %d\\n", pfunc(a, b));
}
int main()
{
processData(10, 5, add);
processData(10, 5, sub);
processData(10, 5, mul);
processData(10, 5, divide);
return 0;
}
6. 练习:加减乘除回调
c
#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 divide(int a, int b) { return a / b; }
void processData(int a, int b, int (*pfunc)(int, int))
{
printf("result = %d\\n", pfunc(a, b));
}
int main()
{
processData(10, 5, add);
processData(10, 5, sub);
processData(10, 5, mul);
processData(10, 5, divide);
return 0;
}
二、函数指针与指针函数
这两个名字很像,但完全不同。
| 函数指针 | 是一个指针,指向函数 | int (*p)(int, int); |
| 指针函数 | 是一个函数,返回值是指针 | char *strcpy(char *dest, const char *src); |
指针函数考点
不要返回局部变量的地址。
c
char *func()
{
char s[] = "hello";
return s; // 错误,s 是局部数组,函数结束后销毁
}
局部变量在栈上,函数返回后空间被回收,返回它的地址会变成悬空指针。
三、多级指针
1. 二级指针
c
int a = 10;
int *p = &a;
int **q = &p;
-
p 保存 a 的地址;
-
q 保存 p 的地址;
-
*q 得到 p;
-
**q 得到 a。
c
**q = 20; // 等价于 a = 20
2. 什么时候用二级指针?
如果要保存的是 一级指针变量的地址,就需要二级指针。
c
char *str[] = {"hello", "world", "china"};
char **p = str;
-
str[0] 类型是 char *;
-
&str[0] 类型是 char **。
3. 多级指针
c
int a = 10;
int *p = &a;
int **q = &p;
int ***r = &q;
规律:
所在空间的数据类型,充当指针变量的基类型。
四、指针数组与数组指针
1. 指针数组
c
char *s[5];
这是一个数组,数组里每个元素都是 char *。
c
char *str[] = {"hello", "world", "china"};
可以存多个字符串。
2. 数组指针
c
int (*p)[4];
这是一个指针,指向一个数组,数组有 4 个 int。
二维数组常用:
c
int a[3][4];
int (*p)[4] = a;
访问:
c
a[i][j] <=> *(*(p + i) + j)
五、指针操作二维数组
1. 整型二维数组
c
int a[3][4] = {1,2,3,4,5,6,7,8,9,10};
二维数组本质:一维数组的数组。
c
int[4] a[3];
-
a[0] 类型是 int[4];
-
&a[0] 类型是 int(*)[4]。
正确指针写法:
c
int (*p)[4] = a;
行指针与列指针:
c
p + 1 // 行指针,指向下一行
*(p + 1) // 列指针,指向下一行首元素
访问元素:
c
*(*(p + i) + j)
2. 字符型二维数组
c
char s[5][10] = {"hello", "world", "english", "china", "america"};
正确指针写法:
c
char (*p)[10] = s;
访问字符串:
c
printf("%s\\n", p[i]);
六、动态内存分配
1. 为什么需要堆区?
数据存储位置:
-
栈:自动申请,自动释放;
-
全局区:全局变量、静态变量;
-
堆:手动申请,手动释放,空间很大。
2. malloc
c
#include <stdlib.h>
void *malloc(size_t size);
-
size:申请多少字节;
-
成功返回堆区空间首地址;
-
失败返回 NULL。
示例:
c
int *p = (int *)malloc(10 * sizeof(int));
if (p == NULL) {
return -1;
}
3. calloc
c
void *calloc(size_t nmemb, size_t size);
-
nmemb:元素个数;
-
size:单个元素大小;
-
会自动初始化为 0。
4. realloc
c
void *realloc(void *ptr, size_t size);
功能:重新申请一块空间。
-
如果原空间后面够,就扩展;
-
如果不够,重新找一块连续空间,复制原数据,释放原空间;
-
ptr 为 NULL 时,效果相当于 malloc;
-
size 为 0 时,效果相当于 free。
5. free
c
void free(void *ptr);
释放堆区空间。
注意:
-
不能多次 free;
-
释放后指针变成悬空指针,建议置为 NULL。
c
free(p);
p = NULL;
6. 练习:在堆区定义数组
c
int *p = (int *)malloc(10 * sizeof(int));
for (int i = 0; i < 10; i++) {
p[i] = i + 1;
}
for (int i = 0; i < 10; i++) {
printf("%d ", p[i]);
}
free(p);
p = NULL;
关于 malloc 强制类型转换的说明
在 C 语言中,malloc 返回 void *,可以自动转换成任意类型的指针,所以 (int *) 不是必须的。
c
int *p = malloc(10 * sizeof(int)); // C 语言中合法
但在 C++ 中,void * 不能隐式转换成其他指针类型,必须强制转换:
cpp
int *p = (int *)malloc(10 * sizeof(int)); // C++ 必须加
所以很多教材和代码加上 (int *),原因有三:
兼容 C++;
显式表明意图,一眼看出要当作 int 数组用;
消除某些编译器的警告。
写纯 C 代码时,推荐不加强制转换,但一定要 #include <stdlib.h>。
如果忘记包含头文件,编译器会以为 malloc 返回 int,在 64 位系统上可能导致指针被截断。而加上 (int *) 反而可能掩盖这个错误。
总结:
| 纯 C,包含 <stdlib.h> | 可加可不加 | 不加更安全,能暴露缺少头文件的问题 |
| C++ | 必须加 | void * 不能隐式转换 |
| 想兼容 C/C++ | 加 | 两边都能编译 |
| 老编译器有警告 | 加 | 消除警告 |
| 忘记包含 <stdlib.h> | 加反而危险 | 会掩盖错误,导致指针截断 |
七、main 函数与命令行参数
c
int main(int argc, const char *argv[])
{
return 0;
}
-
argc:命令行参数个数;
-
argv:命令行参数字符串数组。
示例:
text
./a.out 2 2026
则:
c
argc == 3
argv[0] = "./a.out"
argv[1] = "2"
argv[2] = "2026"
argv[3] = NULL
练习:输出所有命令行参数
c
int main(int argc, char *argv[])
{
for (int i = 0; i < argc; i++) {
printf("argv[%d] = %s\\n", i, argv[i]);
}
return 0;
}
八、总结
| 函数指针 | 指向函数的指针,int (*p)(int, int) |
| 指针函数 | 返回指针的函数,如 char *strcpy(…) |
| 回调函数 | 通过函数指针调用函数 |
| 二级指针 | 保存一级指针的地址,int **q |
| 指针数组 | char *s[5],元素是 char * |
| 数组指针 | int (*p)[4],指向 int[4] |
| 二维数组 | a[i][j] <=> *(*(p+i)+j) |
| malloc | 申请堆区空间,手动释放 |
| calloc | 申请并初始化为 0 |
| realloc | 调整堆区空间大小 |
| free | 释放堆区空间,避免悬空指针 |
| 命令行参数 | argc 个数,argv 字符串数组 |
网硕互联帮助中心


评论前必须登录!
注册