顺顺ttl头像
关注

C 语言指针从入门到实践(2)

C 语言的精髓在于指针,而指针函数函数指针const 修饰的指针是指针体系中既核心又易混淆的知识点。本文将从定义、用法、易错点和实战场景出发,全面解析这三大知识点。

一、指针函数:返回指针的函数

1. 定义与语法

指针函数,本质是函数,只是其返回值类型为指针(地址)。语法结构如下:

返回值类型 *函数名(参数列表);
// 例:返回int类型指针的函数
int *Add(int x, int y);

指针函数的核心特征:* 紧跟在返回值类型后,函数名是核心,优先级高于*(这也是和函数指针的关键区别)。

2. 最致命的错误:返回局部变量的地址

局部变量存储在栈区,函数执行结束后,栈区内存会被系统释放(相当于 “大厦被铲平”),此时返回的地址虽有效,但指向的内容已失效,访问会导致未定义行为(程序崩溃、输出随机值等)。

你看到一个大厦的路标(指针),根据上面的地址(指针的内容)找大厦,你到了地方(指针指向的内容)后,却发现大厦被铲车铲平了(指针指向的内容被释放了)

❌ 错误示例(用户原代码修正版):

#include <stdio.h>
int *Add(int x, int y) {
    int sum = 0;
    sum = x + y;
    return &sum; // 错误:返回栈区局部变量地址
}
int main(void) {
    int Num1 = 100, Num2 = 200;
    int *ret = Add(Num1, Num2); // ret拿到了地址,但内容已释放
    printf("ret = %d\n", *ret); // 输出随机值,行为未定义
    return 0;
}

3. 指针函数的正确用法

想要安全返回指针,需让指针指向不会被释放的内存(数据区、堆区、全局区):

(1)返回字符串常量(数据区)

字符串常量存储在只读数据区,程序运行期间始终存在,可安全返回其地址(但注意:字符串常量不可修改)。

#include <stdio.h>
char* inputarray(void) {
    char *str = "helloworld"; // 字符串常量,存储在数据区
    return str; // 安全返回
}
int main() {
    char *p = NULL;
    p = inputarray();
    // *p = 'H'; // 错误:只读数据区不可修改
    printf("p = %s\n",p); // 正确输出:helloworld
    return 0;
}
(2)返回动态分配的内存(堆区)

通过malloc/calloc分配的堆区内存,需手动free释放,函数结束后不会自动回收:

#include <stdio.h>
#include <stdlib.h>
int *GetSum(int x, int y) {
    int *sum = (int *)malloc(sizeof(int)); // 堆区分配内存
    *sum = x + y;
    return sum; // 安全返回
}
int main() {
    int *p = GetSum(10, 20);
    printf("sum = %d\n", *p); // 正确输出:30
    free(p); // 必须释放堆区内存,避免内存泄漏
    p = NULL;
    return 0;
}
(3)返回全局 / 静态变量地址

全局变量、static局部变量存储在全局 / 静态区,生命周期与程序一致:

int *Add(int x, int y) {
    static int sum = 0; // static修饰,存储在静态区
    sum = x + y;
    return &sum; // 安全返回
}

4. 指针函数的实战场景:链式调用

很多库函数(如strcpy)都是指针函数,返回值可直接作为另一个函数的参数,实现链式调用:

#include <stdio.h>
#include <string.h>
int main(void) {
    char src[32] = {0};
    char dst[32] = {0};
    // strcpy(src, "hello world")返回src的地址,作为第二个strcpy的参数
    strcpy(dst, strcpy(src, "hello world"));
    printf("dst = %s\n", dst); // 输出:hello world
    return 0;
}

二、函数指针:指向函数的指针

1. 定义与语法

函数指针,本质是指针,只是其指向的不是普通变量,而是函数的入口地址。语法结构如下:

返回值类型 (*指针名)(参数列表);
// 例:指向“返回int、接收两个int参数”的函数的指针
int (*p)(int, int) = NULL;

⚠️ 关键:括号不能省略!如果写成int *p(int, int),就变成了 “返回 int 指针的函数”(指针函数)。

函数名的本质:函数名本身就是函数的入口地址,可直接赋值给函数指针。

2. 函数指针的基础使用

以四则运算为例,用函数指针指向不同的运算函数,实现灵活调用:

#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; }

int main() {
    int (*p)(int, int) = NULL; // 定义函数指针
    int ret = 0;

    // 1. 打印函数地址(函数名即地址)
    printf("Add函数地址:%p\n", Add);
    printf("Sub函数地址:%p\n", Sub);

    // 2. 函数指针指向Add函数
    p = Add;
    // 3. 通过函数指针调用函数(两种写法等价)
    ret = p(100, 100); // 推荐写法
    // ret = (*p)(100, 100); // 等价写法,*可省略
    printf("100+100 = %d\n", ret); // 输出:200

    return 0;
}

3. 核心应用:函数指针作为函数参数(回调函数)

函数指针最强大的用法是作为函数参数,实现 “将函数作为参数传递”(回调函数思想),让代码解耦、灵活。

示例 1:可自定义运算规则的计算器
#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 Calculator(int x, int y, int (*p)(int, int)) {
    printf("运算结果:%d\n", p(x, y));
}

int main(void) {
    int Num1 = 0, Num2 = 0;
    char op = '\0';
    printf("请输入运算式(如 10 + 20):");
    scanf("%d %c%d", &Num1, &op, &Num2);

    // 根据运算符,传递不同的运算函数
    switch(op) {
        case '+': Calculator(Num1, Num2, Add); break;
        case '-': Calculator(Num1, Num2, Sub); break;
        case '*': Calculator(Num1, Num2, Mul); break;
        case '/': Calculator(Num1, Num2, Div); break;
        default: printf("无效运算符!\n");
    }
    return 0;
}
示例 2:可自定义排序规则的冒泡排序

通过函数指针传递排序规则,让冒泡排序同时支持升序、降序、绝对值排序:

#include <stdio.h>
#include <stdlib.h>

// 输入数组
void InputArray(int *pArray, int len) {
    for (int i = 0; i < len; i++) {
        scanf("%d", &pArray[i]);
    }
}

// 输出数组
void OutputArray(int *pArray, int len) {
    for (int i = 0; i < len; i++) {
        printf("%d ", pArray[i]);
    }
    printf("\n");
}

// 冒泡排序:排序规则由函数指针决定
void BubbleSortArray(int *pArray, int len, int (*pfun)(int, int)) {
    for (int j = 0; j < len-1; j++) {
        for (int i = 0; i < len-1-j; i++) {
            // 排序规则交给外部函数
            if (pfun(pArray[i], pArray[i+1]) > 0) {
                int tmp = pArray[i];
                pArray[i] = pArray[i+1];
                pArray[i+1] = tmp;
            }
        }
    }
}

// 排序规则函数
int AscSort(int x, int y)    { return x > y ? 1 : 0; } // 升序
int DescSort(int x, int y)   { return x < y ? 1 : 0; } // 降序
int AscAbsSort(int x, int y) { return abs(x) > abs(y) ? 1 : 0; } // 绝对值升序

int main(void) {
    int a[5] = {0};
    printf("请输入5个整数:");
    InputArray(a, 5);
    
    printf("绝对值升序排序结果:");
    BubbleSortArray(a, 5, AscAbsSort);
    OutputArray(a, 5);

    return 0;
}

补充:函数指针数组(进阶)

如果有多个同类型的函数,可将函数指针存入数组,简化调用(比如替代 switch):

#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; }

int main() {
    // 函数指针数组:存储4个运算函数的地址
    int (*opFuncs[])(int, int) = {Add, Sub, Mul, Div};
    char ops[] = "+-*/";
    int num1 = 20, num2 = 5;

    // 遍历调用所有运算
    for (int i = 0; i < 4; i++) {
        printf("%d %c %d = %d\n", num1, ops[i], num2, opFuncs[i](num1, num2));
    }
    return 0;
}

三、const 修饰的指针:

const(只读)用于修饰指针,核心是限制 “指针指向的内容” 或 “指针本身” 的修改权限,能有效防止误操作。

1. 三种 const 指针的核心区别

语法形式const 修饰对象核心特性初始化要求
const int *p / int const *p*p(指针指向的内容)指针指向可改,内容不可改(只读内容)可选
int *const pp(指针本身)指针指向不可改,内容可改(只读指针)必须初始化
const int *const p / int const *const p*p + p指针指向和内容都不可改(双重只读)必须初始化
(1)const 修饰内容:const int *p / int const *p

这两种写法完全等价,重点是const靠近*,限制 “通过指针修改内容”,但指针本身可指向其他地址。

#include <stdio.h>

int main() {
    int a = 10, b = 20;
    const int *p = &a;

    // *p = 100; // 错误:不能通过p修改a的值
    p = &b; // 合法:指针可指向其他地址
    printf("*p = %d\n", *p); // 输出:20
    return 0;
}

💡 实战场景:函数参数中保护源数据(如strcpy的原型char *strcpy(char *dest, const char *src);,确保 src 内容不被修改)。

(2)const 修饰指针:int *const p

const靠近指针变量p,限制 “指针本身的指向”,必须初始化(否则后续无法赋值),但可修改指向的内容。

#include <stdio.h>

int main() {
    int a = 10, b = 20;
    int *const p = &a; // 必须初始化

    *p = 100; // 合法:可修改a的值
    // p = &b; // 错误:指针指向不可改
    printf("a = %d\n", a); // 输出:100
    return 0;
}

💡 类比:数组名本质是int *const类型(指向数组首地址,不可修改,但内容可改)。

(3)双重 const:const int *const p

既限制指针指向,又限制内容修改,必须初始化,是最严格的只读限制:

#include <stdio.h>

int main() {
    int a = 10;
    const int *const p = &a; // 必须初始化

    // *p = 100; // 错误:内容不可改
    // p = &b;   // 错误:指向不可改
    printf("*p = %d\n", *p); // 仅可读取,输出:10
    return 0;
}

2. 记忆技巧

判断const修饰谁,只需看const靠近*还是指针变量:

  • 靠近* → 修饰*p(内容不能改);
  • 靠近变量名 → 修饰p(指针不能改);
  • 两边都有 → 内容和指针都不能改。

四、核心知识点总结

  1. 指针函数 vs 函数指针
    • 指针函数是 “返回指针的函数”,语法int *func(int);*归返回值);
    • 函数指针是 “指向函数的指针”,语法int (*p)(int);(括号不可省,*归指针)。
  2. 指针函数避坑:严禁返回栈区局部变量地址,可返回数据区、堆区、全局 / 静态区地址(堆区需手动释放)。
  3. const 指针核心const int *p(内容只读)、int *const p(指针只读)、const int *const p(全只读),核心是通过const限制修改权限,提升代码健壮性。
  4. 函数指针价值:作为函数参数传递(回调函数),可实现代码解耦,让函数支持自定义规则(如灵活排序、通用计算器)。

亲爱的朋友们,今天也是收获满满的一天呐,如果对你有帮助的话,请给博主点点赞!先谢谢大家啦!!

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2201_75336383/article/details/157186248

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--