news 2026/10/14 7:10:53

C语言指针实战指南:从内存地址到函数指针与二级指针

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言指针实战指南:从内存地址到函数指针与二级指针

1. 指针到底存了什么:先把根本不搞错

很多人学 C 语言基础学到指针这一节,突然就开始怀疑自己是不是不适合编程。变量、数组、函数都还好好的,一遇到int *p就头晕,写着写着不是段错误就是花屏。我可以负责任地告诉你:这不是你笨,而是指针这个知识点在教材里往往被讲得太抽象了。这篇是 C 语言基础系列的第九篇,我们专门把指针在实际开发里最常见的用法掰开揉碎,讲清楚指针和数组的关系、函数指针、二级指针、复杂声明,以及那些经常让程序崩溃的误区。不管你是在校学基础、准备面试,还是已经开始写小项目,这篇都值得静下心读一遍。

1.1 指针就是一个装着内存地址的变量

内存对程序而言就是一大排字节,每个字节都有自己的编号。你声明一个int a = 42,编译器在内存里找一个空闲区域把 42 放进去,这个区域的起始编号就是a的地址。&a返回这个编号,*则是“你拿着编号去找那个位置上的东西”。

#include <stdio.h> int main(void) { int a = 42; int *p = &a; // p 里存的是 a 的地址,不是 42 printf("a = %d\n", a); // 42 printf("&a = %p\n", (void *)&a); printf("p = %p\n", (void *)p); printf("*p = %d\n", *p); // 通过地址访问 a 的值 *p = 100; // 沿着地址找到 a,把它改成 100 printf("a = %d\n", a); // 100 return 0; }

把指针理解成“快递柜的取件码”就顺了:取件码本身不是你的快递,但它告诉你货在哪一格,拿着取件码去开柜子,才能拿到里面的东西。p是取件码,*p是取出来的快递。这个类比能解释最开始的大部分困惑——你不需要把取件码当成快递本身,它只是一张写着位置的小纸条。

printf打印指针用%p,而且要习惯把指针转成(void *),这是规范做法。我看到很多初学者直接printf("%p", p),大多数编译器能过,但严格来说是不严谨的。养成打印地址的习惯对调试非常有帮助,后面我们会反复用这一招。

1.2 指针大小和类型:为什么int *和char *占一样的内存

在 64 位环境下,所有指针变量都是 8 个字节;在 32 位环境下,都是 4 个字节。因为地址编号的长度是固定的,跟它指向的对象有多大没关系。char *和int *占用一样的内存,那为什么还要区分类型?因为类型决定了你要“取多少字节”和“怎么解释这段字节”。

*p的时候,编译器要知道从地址开始往后读多少字节。int *p读 4 个字节(在常见平台上),char *p读 1 个字节,double *p读 8 个字节。如果类型搞错,读出来的就是完全错误的一堆字节。这就是指针类型存在的意义,不是摆设,它直接决定了内存解释方式。

还有一点很多人会忽略:指针变量声明时初始化为NULL是个好习惯。空指针解引用是明确的错误,会立刻崩溃,反而容易定位;野指针指的是指向了不知道哪里的内存,它可能不崩溃、也可能随机崩溃,这种问题最阴间。宁可让程序“正确地崩”,也不要让它“莫名其妙地活”。

1.3 为什么要有指针:函数传参背后的故事

C 语言的函数参数是按值传递的,传进去的是变量的一个拷贝,你在函数里怎么改它,外面的变量都不受影响。

void swap_bad(int x, int y) { int t = x; x = y; y = t; } void swap_good(int *x, int *y) { int t = *x; *x = *y; *y = t; }

swap_bad(a, b)交换的是形参拷贝,什么都没发生,调用完 a、b 原封不动。swap_good(&a, &b)是把两个变量的地址传进去,函数拿着地址去改原始位置,这才真正换过来。这是指针最基础也最核心的价值:想修改函数外部的数据,光传值不够,必须传地址。

当然,传指针也不是万能的。指针本身还是按值传递,你在函数里修改“指针变量本身”的值(比如让p重新指向别的地址),外面照样不知道。这就引出了后面的二级指针话题,别急,第 4 节会专门讲清楚。

2. 数组名与指针的关系:a[i]的本相是*(a + i)

数组是 C 语言基础里最适合和指针放在一起讲的内容,因为它们在底层是同一件事。很多你看得懂、但写不对的代码,问题都出在这里。把这一节读透,后面学字符串处理、动态内存分配会轻松很多。

2.1 数组名会退化,但不是随时都退化

数组名代表整个数组,但绝大多数表达式中它会“退化”成指向首元素的指针。所以int *p = arr;是合法的,p指向arr[0]。但sizeof是个例外:sizeof(arr)返回整个数组占用的字节数,比如int arr[5]是 20 字节;而sizeof(p)返回指针的大小,8 字节。这两者经常把初学者坑了,尤其在函数参数里计算数组长度时。

同样,对一维数组来说arr和&arr打印出来的地址数值一样,但类型完全不同:arr是int *,&arr是int (*)[5]。把&arr加 1,会跳过整个 5 个 int 数组,而不是只跳过一个 int。这种差异平时不明显,一旦你做指针运算就会露出真相。我在实际排查代码时见过有人拿着&arr去当普通指针做遍历,最后跑到内存别的地方去了还一脸茫然。

2.2 下标访问的本质:编译器帮你算的地址偏移

arr[i]在编译器眼里就是*(arr + i),即“首地址往后数 i 个元素的位置”。因为数组元素是连续存放的,加上下标就能算出确切地址。所以你能看到有些人写i[arr]这种邪门代码,它在 C 里语法上合法,编译后等价于*(i + arr)。自己写项目的时候别用这种写法,除了炫技没有别的好处,但理解这一点能帮你彻底看穿“数组就是指针”的假象——数组本身不是指针,只是下标访问和指针运算在多数情况下等价。

用p = arr遍历时,p + i仍然是第 i 个元素的地址,*(p + i)是它的值。在绝大多数场景下,arr[i]、*(arr + i)、p[i]、*(p + i)可以当成同一个东西用。我自己调试时,经常会混用这几种写法,目的就是让自己随时记住背后的地址计算逻辑。

2.3 二维数组:它其实是一维数组的数组

int m[2][3]可以理解为“有 2 个元素,每个元素是一个长度为 3 的 int 数组”。m[i]是第 i 行的首地址,类型是int *;m指向第一行,类型是int (*)[3],这就是行指针。访问m[i][j]展开成*(*(m + i) + j):先跳到第 i 行的地址,再从那个地址往后偏移 j 个元素。

int m[2][3] = {{1, 2, 3}, {4, 5, 6}}; int (*row)[3] = m; // 指向“长度为 3 的 int 数组”的指针 for (int i = 0; i < 2; i++) { for (int j = 0; j < 3; j++) { printf("%d ", *(*(row + i) + j)); } putchar('\n'); }

行指针在日常开发里不如一维指针那么高频,但在封装矩阵、处理图像缓冲区、解析某些协议字段时一定会碰到。理解了m[0]和&m[0]的类型差异,很多二维数组的编译警告一眼就能看出来。一个常见的坑是:二维数组传参时,形参写int arr[][3]其实会退化成int (*)[3],第二维大小必须写清楚,否则编译器没法算行偏移。

2.4 指针数组 vs 数组指针:名字像双胞胎,作用差很远

这两兄弟是最高频的混淆点。判断方法看优先级:[]的优先级比*高,所以int *p[4]首先是数组,p 是包含 4 个int *的数组;而int (*p)[4]加了括号,p 先跟*结合,它是一个指针,指向含 4 个 int 的数组。

写法读法含义
int *p[4]p 是数组,元素是int *指针数组,常见于字符串数组、命令表
int (*p)[4]p 是指针,指向含 4 个 int 的数组数组指针/行指针,用于指向二维数组的行

函数参数里,char *argv[]几乎等价于char **argv,它是指针数组退化成二级指针的表现。很多人在命令行参数、字符串列表上栽跟头,都是没想清楚这层关系。如果把“指针数组”和“数组指针”当成同一回事,后面你在读别人代码时,至少有三成的类型错误会理解不了。

3. 函数指针:把函数当作数据来传

变量有地址,函数也有地址。函数名表示的是函数机器码存放的起始地址,所以你可以用一个指针保存它,再通过这个指针去调用它。这个设计让 C 语言可以写出非常灵活的代码,比如回调机制、命令分发表、插件式架构,底层都依赖函数指针。

3.1 基本用法:声明、赋值、调用

声明函数指针要顺着“生长方向”读:int (*fp)(int, int);意思是fp是指针,指向一个接收两个 int、返回 int 的函数。注意括号不能省,写成int *fp(int, int)就变成了“一个返回int *的函数”,完全不是一回事。

#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 main(void) { int (*fp)(int, int) = add; printf("%d\n", fp(3, 4)); // 7 fp = mul; printf("%d\n", fp(3, 4)); // 12 return 0; }

调用时fp(3, 4)和(*fp)(3, 4)都合法,后者是从老代码里流传下来的写法,新代码里通常直接写fp(3, 4)就够了。函数指针可以重新赋值,指向不同的函数,所以同一个变量、同一段调用代码,却能产生完全不同的行为。这种“动态切换”的能力,是硬编码 if-else 很难比的。

3.2 回调机制:把“怎么比较”交给调用者

标准库的qsort是函数指针最典型的教学案例。qsort不知道你的数组元素是什么类型,也不知道怎么比较,所以它要求你传一个比较函数进来,由你来定义元素之间的大小规则。

#include <stdio.h> #include <stdlib.h> int cmp_int(const void *a, const void *b) { int x = *(const int *)a; int y = *(const int *)b; return x - y; } int main(void) { int nums[] = {5, 2, 9, 1, 7}; size_t n = sizeof(nums) / sizeof(nums[0]); qsort(nums, n, sizeof(int), cmp_int); for (size_t i = 0; i < n; i++) { printf("%d ", nums[i]); } putchar('\n'); return 0; }

cmp_int接收const void *,在函数内部转成int *再取值,返回值小于 0 表示 a 应该在 b 前面,大于 0 则相反。你不需要知道qsort内部怎么排序,只需要定义比较规则,这就是“接口与实现分离”的思想。换成结构体数组时,唯一的改动就是比较函数内部换成结构体字段比较,排序逻辑完全复用。

类似的还能自己写一个map风格的函数,让调用者决定每个元素怎么变换:

void apply(int *arr, size_t n, int (*f)(int)) { for (size_t i = 0; i < n; i++) { arr[i] = f(arr[i]); } }

在事件系统、状态机、插件化框架里,回调函数是让核心代码不知道业务实现也能正常工作的关键设计。我们经常在项目里看到这样的模式:框架提供注册接口,业务方注册自己的处理函数,框架在合适时机回调。函数指针在这里就是连接双方的那座桥。

3.3 用 typedef 让函数指针读起来舒服

函数指针的声明很容易让人头皮发麻,实际项目里一个通用的做法是先用 typedef 把类型起个名字:

typedef int (*Operator)(int, int);

之后声明变量就成了Operator op = add;,签名变化时也只改一处。函数指针数组也很常见,比如一个命令分发表:

typedef void (*Handler)(const char *); void cmd_help(const char *arg) { /* 处理逻辑 */ } void cmd_run(const char *arg) { /* 处理逻辑 */ } Handler handlers[] = { cmd_help, cmd_run };

这比在核心代码里写一大串 if-else 要清爽得多。注意函数指针不能做加减运算,它只能赋值、比较、然后调用。想给函数指针“加偏移量”去调用相邻函数,这种投机取巧是未定义行为,千万别碰。我在面试时见过有人答“函数指针可以fp++跳到下一个函数”,这是一个经典的错误理解,函数不是连续存放、按顺序可寻址的字节流。

4. 二级指针:什么时候必须用到指针的指针

普通指针存的是变量的地址,二级指针存的是“指针变量的地址”。听起来绕,但实际场景非常明确:当你要在函数里修改调用者手里的那个指针变量时,就必须传入指针变量的地址,也就是二级指针。这个需求比想象中频繁得多,链表操作就是一个典型代表。

4.1 为什么int *p传进函数仍然改不了外面的 p

这里是初学者最大的一个认知盲区。看这段代码:

#include <stdio.h> #include <stdlib.h> void bad_init(int *p) { p = malloc(sizeof(int)); // 只改了这个形参拷贝,外面的 p 还是 NULL } int main(void) { int *p = NULL; bad_init(p); printf("p = %p\n", (void *)p); // 很可能还是 0 return 0; }

函数参数是按值传递的,你传进来的p只是把“地址值拷贝了一份”,p = malloc(...)改的是这个拷贝,外部没有发生任何变化。想修改外部的指针变量,只能把它的地址传进去:

void good_init(int **pp) { *pp = malloc(sizeof(int)); // *pp 才是外部那个 p } int main(void) { int *p = NULL; good_init(&p); *p = 8; free(p); return 0; }

good_init的参数是int **pp,调用时传&p,pp里存的是p这个变量本身所在的内存地址,*pp就是对那块内存进行写入,所以malloc的结果才能真正落到外部的p上。可以把二级指针理解成“你需要修改纸条上的内容,就要把纸条的抽屉位置告诉对方,而不是把纸条上的字抄一遍给人家”。

4.2 单链表头插:不传二级指针就断链

链表操作是二级指针最常见的实战场景。头插法要在链表头前面插入新节点,就得修改头指针本身:

typedef struct Node { int value; struct Node *next; } Node; void insert_head(Node **head, int value) { Node *new_node = (Node *)malloc(sizeof(Node)); new_node->value = value; new_node->next = *head; // 新节点接到旧头部上 *head = new_node; // 头指针指向新节点 }

这里如果只传Node *head,*head = new_node是做不到的,因为外面那个头节点指针不会被更新,链表头就丢了。很多初学链表的人第一次报错或内存泄漏,原因就在这里。这个问题也经常出现在“删除链表头节点”的场景里,删完后需要把链表的头指针更新成下一个节点,不传二级指针就没法写。

当然,二级指针不是唯一解法。可以不用二级指针,改用结构体把链表头包起来:

typedef struct List { Node *head; } List; void insert_head(List *list, int value) { Node *new_node = (Node *)malloc(sizeof(Node)); new_node->value = value; new_node->next = list->head; list->head = new_node; }

这个方案更容易读,也避免了二级指针对心智的挑战。所以我的建议是:除非确实需要修改指针本身(初始化指针、链表的头插/删除头节点、替换缓冲区的内部指针),否则优先用返回值或者结构体包装,让代码意图更直白。好的代码不是最能炫技的,而是三个月后你自己还能一眼看懂。

4.3 二级指针和字符串数组的纠缠

char *argv[]本质是“数组的元素是char *指针”。当它作为函数参数传递时,会退化成char **。所以你在处理命令行参数时看到的main(int argc, char **argv)和main(int argc, char *argv[])是同一个东西的两种写法。理解这一点,再去看字符串数组、动态分配二维字符串表,就不会晕了:

char *names[] = {"alice", "bob", "carol"}; char **p = names; printf("%s\n", p[1]); // bob

p先取到names[1]这个char *,然后按字符串方式打印。指针的层级和组织方式在这里看得特别清楚。类似地,如果需要动态分配一堆字符串,常见做法是先分配一个char **,再让每个元素指向各自的字符串缓冲区,这时二级指针其实是容器指针。

5. 复杂声明:用“右左法则”一招搞定

C 语言的声明语法和表达式优先级绑定在一起,导致一些人看到char *(*(*p)(int))[5]直接摆烂。其实有章法可循:我从第一份工作开始就靠“右左法则”读这种声明,用了十几年没失手。这篇既然叫基础第九篇,我建议你把它当工具存着,以后看头文件、读回调接口时都用得上。

5.1 右左法则的完整步骤

先说规则,就五步:

  1. 找到要读的标识符(变量名),从它开始。
  2. 先向右读,遇到]就是数组,遇到(后跟参数列表就是函数。
  3. 右边读完后,再看左边,遇到*就是指针。
  4. 遇到左括号(挡住去路时,跳到括号外面,继续先右后左。
  5. 重复直到读完整条声明。

关键是记住一个顺序:先看右边,再看左边,括号挡路就跳出去继续。这个法则不需要背,用三次就熟了。它本质上是在模拟 C 语言解析类型的规则,只是用人话翻译了一遍。

5.2 三个例子跑一遍

第一个:int *p[4]

p开始,右边是[4],说明 p 是数组;右边读完了,看左边是int *,说明元素类型是int *。所以:p 是一个包含 4 个 int 指针的数组,也就是“指针数组”。

第二个:int (*p)[4]

p开始,右边是),括号挡住了。跳到括号外面,右边是[4],说明 p 是指针;再回来看括号里面,p 左边是*,确认 p 是指针;再往左是int,说明 p 指向的对象是“包含 4 个 int 的数组”。所以:p 是一个指向 int[4] 数组的指针,也就是“数组指针”。

第三个:char *(*p)(char *, int)

p开始,右边是),被括号挡住;跳到括号外,右边是(char *, int),这是函数参数列表,所以 p 是函数指针;再绕回括号里看左边,*确认指针;再往左边看,char *是返回类型。所以:p 是一个函数指针,指向的函数接收一个char *和一个int,返回char *。

这种链式阅读在工作里很有用,尤其读第三方库的头文件时,能一眼分辨“这到底是个数组、指针、还是函数指针”。很多人卡在类型上,其实不是智商问题,只是没有掌握读法。

5.3 项目里宁可分步 typedef,也不要炫技

虽然右左法则能读懂复杂的声明,但实际写项目时,我的建议是尽量别写出超过两层的声明。多个*和括号叠在一起,过两个月连自己都要重新读一遍。项目里更推荐分步 typedef:

typedef int FourInts[4]; typedef FourInts *FourIntsPtr;

或者直接给函数指针取名:

typedef void (*ErrorHandler)(int code, const char *msg);

这样一方面让声明变成普通类型,另外也方便统一修改签名。面试题喜欢考右左法则,但真的写工程代码,优先考虑可读性。能让人一眼看明白的代码,才是好代码,而不是那种“只有作者能看懂、三个月后作者也看不懂”的花活。

6. 指针高频翻车现场:从症状反推原因

这一节说说我在实际开发和帮别人看代码过程中遇到最多的指针问题。每一条都有血有泪,也是我过去几年在论坛和代码评审里反复看到的典型坑。

6.1 六种最常见的指针坑

第一,野指针。声明了int *p;就急着*p = 10;,p 指向哪里完全不知道。先初始化成NULL再使用,或者直接给一个有效地址,别留裸声明。程序崩溃反而不可怕,可怕的是它恰好撞上内存里一个合法可写区域,把数据写坏,然后过几个小时才莫名其妙地崩。

第二,悬空指针。free(p)之后没有置为NULL,后续又free(p)一次或者继续通过 p 访问内存。释放后应该立刻p = NULL;,让那个地址不再被持有。重复释放是未定义行为,轻则运行时崩溃,重则破坏堆管理器的内部结构,导致后续所有 malloc 行为诡异。

第三,越界访问。数组长度为 5,却访问arr[5]。C 语言不做边界检查,编译器不报错,运行时能不能活全看运气。这类问题用调试器很难直接定位,最好借助工具来抓。缓冲区溢出还会带来安全隐患,这是所有 C 程序员必须养成的警觉。

第四,返回局部变量的地址。函数里定义了局部变量int a,然后return &a;。函数结束后这张栈帧就销毁了,返回地址指向的是一个已经失效的区域,下一回函数调用可能就会覆盖这块内存。这种 bug 非常难查,因为第一次调用时数据可能还是好的,多调几次就乱了。

第五,想通过传指针给函数改变外面的变量,却只改了形参。比如把int *p传进函数,然后p = malloc(...),外面丝毫没变,必须用二级指针或者返回值。这是第 4 节讲过的内容,但实操中仍然反复出现。

第六,修改字符串字面量。char *s = "abc"; s[0] = 'x';是未定义行为,很多平台直接段错误。字符串字面量通常放在只读区,要修改就声明成char s[] = "abc";,编译器会把内容拷贝到可写的栈上。

6.2 排查三板斧:printf、ASan、Valgrind

遇到指针问题,我先建议“用打印把地址摊开看”:

printf("before: p = %p, *p = %d\n", (void *)p, *p);

地址打印出来是0x...,如果出现0x0或明显的随机值,说明指针是空的或者野的。这一步信息量很大,常常一眼定位。我在调试时甚至会写一个临时函数把关键地址全部打印出来,找到“哪个指针的地址不符合预期”,问题就缩小了一半。

无脑用 AddressSanitizer。编译时加-fsanitize=address,越界、释放后使用、栈溢出这类错误会直接给出具体行号:

gcc -g -fsanitize=address -o demo demo.c ./demo

再有内存泄漏、疑似访问未初始化内存的问题,用 Valgrind:

valgrind --leak-check=full ./demo

它会列出泄漏的 malloc 调用栈。注意 Valgrind 运行会比较慢,适合小规模测试,大数据量程序建议用 ASan 那一路。这两件工具搭配使用,能覆盖我工作中九成以上的指针类疑难杂症。

下面这张表是我经验里最常见的症状对应:

现象主要嫌疑建议排查动作
程序随机段错误野指针、悬空指针打印所有关键指针,开启 ASan
输出乱码或随机数未初始化内存、越界读检查是否用了未初始化的*p
内存只增不减malloc/free 不配对Valgrind 看泄漏栈
函数外变量没变化按值传递了指针本身改成传二级指针或用返回值
访问固定地址崩掉空指针解引用在使用前判断p == NULL

6.3 写代码前的一分钟自查

我的习惯是写任何一段涉及指针的代码之前,先在注释或者脑子里问三个问题:这个指针确定有有效地址吗?释放以后还有别的地方引用它吗?我到底是想改“指的东西”还是想改“指针本身”?这三问往往能提前拦住一大半问题。尤其是第三个问题,它是二级指针的天然分界线:想改指的东西,传一级指针加解引用;想改指针本身,就得上二级指针。

指针这个东西,本质不难,难的是它在不同场景里的伪装太多了。但只要抓住“指针是一个变量,这个变量保存的是内存地址”这条主线,再多写法都只是这条线上的花样。数组名退化成指针也好,函数指针也好,二级指针也好,归根到底都是在说同一件事:你手里拿着一张写着地址的纸条,真正重要的是纸条指向的那个地方。

最后说点实际的。指针不是玄学,它就是一个装着地址的变量,你跟它相处久了自然就不怕了。我自己带人入门的时候常说一句话:遇到说不清的问题,先把它打出来。地址不会骗人,打印出来什么就是什么。下一篇我们会接着讲动态内存分配和链表,如果这一节的内容还没完全消化,可以先回去多写几个小例子,把地址和值的关系亲手验证一遍。写代码这件事,手熟了,心就不慌了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 7:10:37

量化软件推荐 股票与ETF研究应怎样选择三款工具

量化软件推荐&#xff0c;准备同时研究股票与ETF时&#xff0c;可以比较米筐研究工具、牛股王股票和掘金量化。需要明确查询证券类型并自行组织数据&#xff0c;可先核对米筐RQData&#xff1b;希望用现有规则与模型开展普通个人研究&#xff0c;可检查牛股王股票&#xff1b;准…

作者头像 李华
网站建设 2026/10/14 7:10:36

数组与字符串:架构师的底层系统建模能力校验

1. 这不是刷题清单&#xff0c;是架构师能力的底层校验标准“数组和字符串”听起来像入门内容&#xff1f;我带过十几支后端与基础平台团队&#xff0c;亲手筛过上千份简历&#xff0c;也作为技术终面官参与过近百场架构师级晋升答辩。最常被低估的&#xff0c;恰恰是这两个最基…

作者头像 李华
网站建设 2026/10/14 7:10:24

Python动画核心语句全解析:Matplotlib、Pygame等库的选型与实战

市面上讲Python动画的教程&#xff0c;我见过太多版本&#xff1a;有拿一个正弦波糊弄完事的&#xff0c;有上来就甩一套Pygame主循环的&#xff0c;还有动辄几十个参数折腾数学视频的。看多了你会发现问题根本不是没资料&#xff0c;而是资料东一块西一块&#xff0c;学完A忘了…

作者头像 李华
网站建设 2026/10/14 7:10:13

基于SpringBoot的在线诊疗管理系统设计与实现全流程指南

做毕业设计选到“在线诊疗管理系统”的同学&#xff0c;第一反应大概都是“这题目是不是有点大”——SpringBoot、在线诊疗、远程就医这些词堆在一起&#xff0c;听起来像要做一个完整产品。但实际动手拆开看&#xff0c;这个方向反而是近几年Java类毕设里性价比很高的一类&…

作者头像 李华
网站建设 2026/10/14 7:10:12

三母线高斯赛德尔潮流计算:从原理到MATLAB实现

写这个三母线高斯赛德尔潮流计算&#xff0c;其实是很多电力系统方向同学绕不开的一道坎。刚接触电力系统分析那会儿&#xff0c;课本上雅可比矩阵、节点导纳矩阵看得头皮发麻&#xff0c;用MATLAB把N-R法跑通之前&#xff0c;我先是把高斯赛德尔法仔细玩了一遍。这东西听起来老…

作者头像 李华
网站建设 2026/10/14 7:09:36

2026年旧猫与爱回收、转转荣耀手机回收:32项检测3分钟对照

2026年旧猫与爱回收、转转荣耀手机回收&#xff1a;32项检测3分钟对照 旧猫回收做荣耀手机回收横向梳理&#xff1a;很多人问「荣耀手机回收平台推荐」&#xff0c;单看报价最容易上当。这篇以旧猫回收为主线&#xff0c;横向对比爱回收、转转、闲鱼等渠道&#xff08;具体以 A…

作者头像 李华