云计算百科
云计算领域专业知识百科平台

Linux 学习笔记(十)C语言指针进阶补充:函数指针、指针函数、动态内存与命令行参数

标签: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 *) 反而可能掩盖这个错误。

    总结:

    场景是否加 (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 字符串数组
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Linux 学习笔记(十)C语言指针进阶补充:函数指针、指针函数、动态内存与命令行参数
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!