15. 【C语言】C语言的灵魂:指针初体验

前面十四篇文章,我们盖好了地基:变量存数据,函数拆模块,数组管批量。但你有没有觉得还缺一块关键拼图——为什么我们可以在函数里修改数组,却改不了普通变量?为什么 scanf 要用 & 取地址?为什么说 C 语言“贴近硬件”?

答案就在指针

指针是 C 语言最强大、也最让初学者头疼的特性。但不要怕——你已经有变量、内存、作用域的扎实基础,理解指针就只剩一层窗户纸。今天,我们就来捅破它。


一、为什么需要指针?

先看三个你或许已经遇到的困惑:

困惑一:为什么 scanf 要加 &

int age;
scanf("%d", &age);   // 这个 & 是什么?

困惑二:为什么在函数里修改数组,外面也能看到变化?

void fill_zero(int arr[], int n) {
    for (int i = 0; i < n; i++) arr[i] = 0;  // 外面的数组也变了!
}

困惑三:为什么我们写的 swap 函数没用?

void swap(int a, int b) {
    int temp = a; a = b; b = temp;   // 外面纹丝不动
}

这三个困惑都指向同一个核心概念:如何直接访问和操作内存中的数据。普通变量通过名字访问,但有时候我们需要通过地址来访问——这就引出了指针。


二、指针是什么?

简单说:指针就是一个变量,但它里面存的不是普通数值,而是另一个变量的内存地址。

int a = 10;
int *p = &a;   // p 里存的是 a 的地址

这里:

  • a 是一个普通的 int 变量,里面存的是 10
  • &a取地址运算符,得到 a 在内存里的地址(比如 0x7ffd1c)。
  • p 是一个指针变量,它里面存的就是那个地址。
  • *p间接访问运算符(解引用),沿着 p 存的地址找到 a,读写它的值。

关系图:

变量 a:  [10]   <-- 地址 0x100
              ^
              |
指针 p: [0x100]  <-- p 自己的地址是 0x200

所以 *p 就是 a 本人。你写 *p = 20;a 就变成了 20。


三、声明指针变量

声明指针时,在类型后面加一个 *

int *p;      // p 是指向 int 的指针
char *ch;    // ch 是指向 char 的指针
double *dp;  // dp 是指向 double 的指针

声明时可以初始化:

int a = 5;
int *p = &a;    // p 指向 a

也可以分开写:

int *p;
p = &a;         // 让 p 指向 a

注意int *p, q; 中,只有 p 是指针,q 是普通 int。如果想声明两个指针,写 int *p, *q;。这是 C 语法的一个小陷阱。


四、& 取地址与 * 解引用

这两个运算符是指针的基本功,必须熟练掌握。

1. & 取地址

把任意变量的地址取出来,返回指向该类型的指针。

int x = 42;
printf("%p\n", (void*)&x);   // 打印 x 的地址

%p 专门用来打印地址(指针值),需要强制转换为 void*

2. * 解引用(间接访问)

通过指针访问它所指向的变量。

int a = 10;
int *p = &a;

printf("%d\n", *p);   // 输出 10,等价于 printf("%d\n", a);

*p = 20;              // 把 a 改成 20
printf("%d\n", a);    // 输出 20

*p 可以出现在赋值号的左边(左值),用来修改指向的值。


五、指针作为函数参数:实现真正的“交换”

还记得第十二篇那个失败的 swap 吗?现在用指针来拯救它。

#include <stdio.h>

void swap(int *a, int *b) {
    int temp = *a;   // 取 a 指向的值
    *a = *b;         // 把 b 指向的值赋给 a 指向的位置
    *b = temp;
}

int main(void) {
    int x = 5, y = 10;
    printf("交换前: x=%d, y=%d\n", x, y);
    swap(&x, &y);    // 传 x 和 y 的地址
    printf("交换后: x=%d, y=%d\n", x, y);
    return 0;
}

输出:

交换前: x=5, y=10
交换后: x=10, y=5

成功!因为 swap 接收的是 xy 的地址,它通过 *a*b 直接修改了 main 里那两个变量的内存。这就是指针的核心威力:让函数有能力修改外部的变量


六、指针与数组:一对亲兄弟

1. 数组名就是首元素的地址

int arr[5] = {10, 20, 30, 40, 50};
printf("%p\n", (void*)arr);      // 数组名直接当指针用
printf("%p\n", (void*)&arr[0]);  // 和上面一样

数组名 arr 在表达式中会被自动转换成指向首元素的指针。所以:

int *p = arr;   // p 指向 arr[0]

现在你可以用指针来访问数组元素:

printf("%d\n", *p);        // arr[0] = 10
printf("%d\n", *(p+1));    // arr[1] = 20
printf("%d\n", *(p+2));    // arr[2] = 30

p + 1 不是地址值加 1 个字节,而是加 1 * sizeof(int) 个字节——也就是跳过整个元素,指向下一个 int。这称为指针算术

2. 指针算术

指针加整数 n,地址值增加 n * sizeof(指向的类型)

int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;    // 指向 arr[0]

p = p + 1;       // 指向 arr[1]
p++;             // 指向 arr[2]
p += 2;          // 指向 arr[4]

也可以用下标访问指针:

int *p = arr;
printf("%d\n", p[2]);   // 等价于 arr[2],输出 30

为什么数组和指针这么亲?因为 arr[i] 在底层被编译器翻译成 *(arr + i)。这两者完全等价。甚至你可以写 i[arr],会被翻译成 *(i + arr),也是一样的(但千万别真这么写)。

3. 用指针遍历数组

#include <stdio.h>

int main(void) {
    int arr[] = {2, 4, 6, 8, 10};
    int *p;
    for (p = arr; p < arr + 5; p++) {
        printf("%d ", *p);
    }
    printf("\n");
    return 0;
}

p < arr + 5 判断指针是否越过了数组末尾(arr + 5 指向最后一个元素之后的位置,不能解引用,但可以做比较)。


七、数组作为函数参数的本质

现在我们可以解释那个困惑了:为什么函数里修改数组,外面也会变?

void modify(int arr[], int n) {
    arr[0] = 999;
}

实际上,编译器看到 int arr[] 时,会把它当成 int *arr。函数调用时,传进来的是数组首地址的副本,而不是整个数组的副本。通过这个地址,函数可以直接修改原数组。

modify(my_array, 5);   // 传的是 &my_array[0]

所以 arr[0] = 999; 等价于 *(arr + 0) = 999;,直接写到了原数组的内存上。这就是为什么数组“按引用传递”的真相——它传的是地址值。


八、void* 指针初识

有一种特殊的指针类型:void*(无类型指针)。它可以指向任何类型的数据,但不能直接解引用,因为编译器不知道它指向的数据类型大小。

int a = 10;
void *vp = &a;          // 可以指向 int
// printf("%d\n", *vp); // 错误!不能解引用 void*
printf("%d\n", *(int*)vp);  // 先强制转换回 int*,再解引用

void* 常用于通用内存操作函数,比如 mallocmemcpy,后面讲动态内存时会遇到。


九、常见错误与陷阱

1. 使用未初始化的指针

int *p;
*p = 10;   // 危险!p 指向哪里?可能是随机地址,导致崩溃

指针必须指向合法的内存(已声明的变量、数组、动态分配的内存)才能解引用。

2. 返回局部变量的地址

int* bad_func(void) {
    int x = 100;
    return &x;  // 函数返回后 x 已销毁,返回的地址无效
}

这是经典的“悬空指针”错误。要返回指针,可以返回静态局部变量、全局变量或动态分配的内存的地址。

3. 解引用空指针

int *p = NULL;
printf("%d\n", *p);  // 段错误!NULL 是空地址,不允许访问

NULL 是一个宏,表示空指针。在解引用前,一定要确保指针非空。

4. 指针类型不匹配

int a = 10;
double *dp = &a;  // 编译器警告,类型不兼容
printf("%f\n", *dp);  // 未定义行为

不同类型的指针不要随便互指,除非你很明白自己在做什么(并且用强制转换)。

5. 野指针

指针指向的内存已经释放(free 之后),但指针还在,指向的地址无效。后面动态内存部分会细讲。


十、小结

今天你第一次触碰了 C 语言的灵魂——指针。它们不是什么神秘魔法,只是存地址的变量。但它们解锁了:

  • 直接修改外部变量的能力(swap 终于能用了)
  • 高效操作数组的方式(指针算术)
  • 理解了数组作为函数参数的本质

你现在知道了:

  • & 取地址,* 解引用。
  • 指针变量声明用 int *p;
  • 数组名就是首元素地址,arr[i] 就是 *(arr + i)
  • 函数传数组本质是传地址,所以能在函数内修改原数组。

指针的旅途才刚刚开始。下一篇我们会深入指针与数组更复杂的关系——指针数组、数组指针、多级指针,以及字符串与指针的紧密联系。当你能轻松玩弄指针时,你就真正拥有了 C 语言。


课后小练习

  1. 写一个函数 void increment(int *p),让传入的整数加 1。在 main 中测试。
  2. 使用指针遍历一个 double 数组,打印所有元素,观察指针加 1 时地址增加了多少字节。
  3. 写一个函数 int array_sum(int *arr, int n),用指针算术(而不是下标)计算数组元素的和。
  4. 分析以下代码错在哪里:
    int* get_pointer(void) {
        int val = 5;
        return &val;
    }
    int main(void) {
        int *p = get_pointer();
        printf("%d\n", *p);
        return 0;
    }
    
    为什么输出可能不是 5?用静态局部变量怎么修正?

我们下期见!

💡获取本系列示例代码请访问 GitCode 仓库

标题基于SpringBoot的学生读书笔记共享平台设计研究AI更换标题第1章引言介绍学生读书笔记共享平台的研究背景、意义、国内外研究现状、论文方法以及创新点。1.1研究背景与意义阐述学生读书笔记共享平台在当前教育环境下的重要性。1.2国内外研究现状分析国内外学生读书笔记共享平台的研究进展与现状。1.3研究方法及创新点概述本文的研究方法与平台设计的创新点。第2章相关理论总结和评述与SpringBoot及读书笔记共享平台相关的理论。2.1SpringBoot框架介绍阐述SpringBoot框架的特点、优势及其在Web开发中的应用。2.2读书笔记共享平台相关理论介绍读书笔记共享平台的设计原则、功能需求及用户体验理论。2.3数据库设计与优化理论简述数据库设计的基本原则及优化策略。第3章平台设计详细介绍基于SpringBoot的学生读书笔记共享平台的设计方案。3.1平台架构设计平台的整体架构,包括前端、后端及数据库的设计。3.2功能模块设计阐述平台的主要功能模块,如用户管理、笔记上传、笔记分享等。3.3数据库设计介绍数据库的设计方案,包括表结构、索引及关系设计。第4章平台实现详细描述平台的具体实现过程,包括技术选型、开发环境搭建等。4.1技术选型与开发环境介绍开发平台所采用的技术栈及开发环境配置。4.2关键代码实现展示平台实现过程中的关键代码片段,如用户登录、笔记上传等功能的实现。4.3平台测试与优化平台的测试过程及优化策略,确保平台的稳定性和性能。第5章平台应用与分析对平台的应用效果进行分析,包括用户反馈、使用数据等。5.1用户反馈收集与分析收集用户反馈,分析用户对平台的满意度及改进建议。5.2使用数据分析通过数据分析工具,分析平台的使用情况,如用户活跃度、笔记分享量等。5.3对比方法分析对比其他类似平台,分析本平台的优势与不足。第6章结论与展望总结本文的研究成果,并对未来研究方向
内容概要:本文针对多渗透率电动汽车接入对配电网的影响,开展承载能力评估研究,提出了一套融合多类型分布式资源的综合评估体系。研究构建了包含电动汽车、分布式光伏及静止无功补偿器(SVC)的配电网协同运行基础模型,建立了涵盖一次设备安全性、负荷平稳性、电能质量与系统运行效率的多维度评价指标体系,并采用熵权法与模糊综合评价相结合的双层模型实现指标客观赋权与系统承载能力的量化评分。通过Matlab仿真平台,系统分析了不同电动汽车渗透率下各项指标的演变规律与敏感性特征,揭示了高比例电动汽车接入对配电网的潜在压力,从而为电网的规划决策、扩容改造以及电动汽车的有序充电管理提供了科学、量化的技术支撑。; 适合人群:具备电力系统、电气工程或相关领域基础知识,从事新能源并网、智能配电网、电动汽车与电网互动(V2G)等方向研究的研究生、科研人员及电力系统工程技术人员。; 使用场景及目标:①评估大规模电动汽车无序或有序接入对配电网安全稳定运行的综合影响;②为配电网络的升级改造、设备选型及电动汽车充电基础设施布局提供决策依据;③学习并复现基于熵权-模糊综合评价法的多指标体系构建与量化评估方法,掌握其在复杂电力系统分析中的应用。; 阅读建议:建议结合文中提供的Matlab代码进行仿真复现,重点理解算例参数设置、多维指标体系的设计逻辑以及双层评价模型的具体实现步骤,通过调整渗透率等关键参数进行对比实验,以深化对评估方法原理与实际应用效果的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值