news 2026/9/17 3:07:55

算法题中的指针类型题目:核心考点、解题套路与常见误区

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
算法题中的指针类型题目:核心考点、解题套路与常见误区

最近在刷算法题的朋友应该有感受,链表、二叉树的题目十道里有七八道都在折腾指针。尤其是C/C++选手,写双指针、快慢指针时经常被一两个星号搞得晕头转向——改了指针本身还是改指针指向的内容?改完下一个节点该接谁?一旦想不清楚,代码跑起来不是段错误就是死循环。这篇就把“算法题里的指针类型题目”单独拎出来好好拆一遍,聊清楚这类题到底在考什么、解题时怎么想、有哪些百试百爽的套路,以及我踩过的那些坑。

先说明一下定位:这篇文章不教C语言指针的入门语法(那个随便一本教材都讲得比我好),而是把指针和算法题结合在一起,总结出一套能在刷题时直接用的方法论。适合正在刷LeetCode、打算笔试面试、或者因为指针总写错而头疼的人。

1. 内容整体设计与思路拆解

1.1 指针类型题目到底在考什么

先说个很多人没想明白的点:算法题里考指针,本质上不是在考你会不会用->或者*,而是在考你有没有“对象与引用的心智模型”。

举个例子,很多人背过链表反转的迭代写法:

ListNode* reverseList(ListNode* head) { ListNode* prev = nullptr; ListNode* curr = head; while (curr) { ListNode* next = curr->next; curr->next = prev; prev = curr; curr = next; } return prev; }

背是背下来了,但面试官一改题——比如让你反转链表的第m到n个节点——就开始乱。为什么?因为没想清楚prevcurrnext这三个指针在不同时刻到底指向哪个节点,每一步执行完“谁指向谁”的状态图没有在脑子里建立起来。

所以说到底,指针类算法题考察的是三件事:

  • 是否理解指针是“保存地址的变量”,而不是对象本身
  • 是否能在操作指针时追踪“当前状态”和“下一步状态”
  • 是否具备把复杂操作拆成“指针指向调整”的能力

这三件事,说得直白一点,就是考察你有没有把链表当链表、把树当树去想的直觉。你把链表画成方框和箭头,把所有指针操作看成“改箭头指向”,这类题的正确率会立刻上一个台阶。

1.2 常见题型分类:从内存视角看解题路径

按我刷题的经验,算法题里的指针类型题目大致分四类,每一类的解题策略都有明显差异:

题型代表题核心考点典型解法
链表节点操作反转链表、删除倒数第N个节点、合并有序链表指针指向调整、虚拟头节点迭代/递归 + 哑节点
双指针/快慢指针环形链表、删除排序数组重复项、三数之和空间复用、指针移动条件快慢指针、对撞指针
指针与数组二维数组指针访问、旋转图像、矩阵遍历内存布局理解、指针步长模拟遍历、原地操作
函数指针/指针数组根据条件调用不同函数、命令调度类问题抽象与解耦、回调机制函数指针表、策略模式

这个分类的价值在于:你看到一道新题,第一步不是急着写代码,而是先判断它的指针类型——它操纵的是链表的“箭头”,还是数组的“格子”,还是函数的“入口地址”?判断对了,基本上解题框架就出来了。

比如“合并两个有序链表”,它属于第一类链表节点操作。你自然知道要用一个哑节点(dummy node)来串链表,用一个cur指针指向新链表的最后一个节点,然后不断比较两个原链表头节点值的大小,把小的那个接到cur后面。这个框架一旦建立,后面的实现细节只是机械操作。

1.3 为什么很多人卡在指针题上:三个常见误区

我观察到一个现象:很多人刷指针题,代码写得少,脑子里的“抽象想象”太多了。一上来就试图在脑子里模拟指针的二进制地址,想“指针变量里面到底存的是什么”,结果越想越乱。

实际上解指针题根本不需要关心地址的具体数值,你只需要关心“指针指向哪个对象”“对象的类型是什么”“通过指针能访问到哪些成员”。这就是算法层面看指针和底层层面看指针的区别。

第二个误区是不画图。我见过太多人链表反转写错了,一边看代码一边挠头。你说“这里next丢了”,他半天反应不过来。但只要你把链表画出来,把prev、curr、next三个指针用不同颜色的笔画在图上,每执行一步就更新箭头,出错概率会小一大半。这个方法听着笨,但真管用。

第三个误区是混用C++的引用和指针。刷题时很多人看题解用ListNode*&就说看不懂,实际上引用就是指针的语法糖,只是在编译层面避免了你手动解引用。你理解指针怎么工作,理解引用就是“指针的别名”完全没问题。真正到刷题层面,我会更推荐统一用指针思维去理解,因为题解里两种写法都很多,你两种都看得懂才算真的掌握。

2. 核心细节解析与实操要点

2.1 指针的本质:地址只是“抽屉编号”

要把指针题做好,先得把指针变量本身弄明白。我在给学弟学妹讲的时候经常打一个比方:内存就像一栋公寓楼,每个房间有门牌号,房间里住着数据。指针变量是一个特殊的便利贴,便利贴上面写着某个房间的门牌号。

注意,指针变量自己也是一个房间,它也有自己的门牌号。你取地址&p得到的就是这个便利贴房间的门牌号,而你用int* p = &x做的事,是在便利贴上写了x的门牌号。当你想知道x的值时,你拿着便利贴去找对应房间,这在语法上就是*p

这个模型对解题有什么用?非常有用。比如你写了一个函数,想要修改调用方的变量:

void change(int val) { val = 10; } void reallyChange(int* p) { *p = 10; }

调用change(a)时,传入的是a房间里的数据副本,改副本对a没影响。调用reallyChange(&a)时,传入的是a房间的门牌号,然后在函数里根据门牌号去找到a房间,修改里面的数据。这样一来,调用方的a确实变成了10。

在算法题中,这个区别常见于“递归修改链表”和“递归构建树”的场景。比如你要在一个函数里把树的左孩子替换成某个新节点,如果参数是TreeNode*,你修改参数本身不会影响调用方的指针;但如果参数是TreeNode*&TreeNode**,你修改的就是调用方持有的那个指针本身。

我建议你把这两句话记牢:

参数是T* p时,函数内修改p本身,不影响外部传入的指针变量;函数内修改*p,会影响外部指针指向的对象。

参数是T*& pT** p时,函数内修改p本身,会让外部传入的指针变量指向别处。

2.2 数组指针、指针数组、指针的指针:名字绕但记忆有巧劲

热搜词里出现了“数组指针”“指针数组”“字符串数组指针”“顶层指针和底层指针可以相互赋值吗”这些名词,我估计不少人是被这类“指针和数组的排列组合”绕晕的。

先记两条最基本的:

  • int* p[3]:p先跟[]结合,所以是“数组”,数组元素是int*,叫指针数组。
  • int (*p)[3]:p先跟*结合,所以是“指针”,指向一个“长度为3的int数组”,叫数组指针。

怎么快速区分?看变量名先跟谁结合。*p[3]里,[]优先级高于*,所以p先跟[]结合成数组;(*p)[3]里,括号强行让p先跟*结合成指针。就这么简单。

那“字符串数组指针”又是什么?它是一个指针,指向一个数组,数组里的每个元素是char*(也就是字符串):

char* strArr[] = {"hello", "world", "algorithm"}; char* (*p)[3] = &strArr; // 字符串数组指针

访问第一个字符串的第一个字符,就是(*p)[0][0]。这里小技巧是:p指向整个数组,(*p)才是数组本身,所以(*p)[i]是数组第i个元素(一个char*),再往后[0]就是取这个字符串的第一个字符。

“顶层指针和底层指针可以相互赋值吗”这个问题稍微进阶一点。顶层指针(top-level const)指指针本身是const,即int* const p——p不能再指向别处;底层指针(low-level const)指指针指向的对象是const,即const int* p——p指向的对象不能通过p修改。

答案是:底层指针和底层指针可以相互赋值;顶层指针(尤其是非const指针)与底层指针相互赋值时,有const限定符的差异需要遵循const的兼容规则,不能随意把const int*赋给int*,否则会丢掉只读约束,编译器不允许。反之,int*可以赋给const int*。简单记:加只读容易,去只读不行。

2.3 函数指针:把函数当数据用

有些指针题范围更大,直接考函数指针。我记得有一次遇到一道“实现一个计算器,根据运算符调用不同函数”的题目,最开始写if-else嵌套写得累死,后来用函数指针表一行搞定:

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 (*ops[])(int, int) = {add, sub, mul}; char op[] = {'+', '-', '*'}; // 调用 int idx = 0; if (c == '+') idx = 0; else if (c == '-') idx = 1; else idx = 2; int result = ops[idx](a, b);

这种做法的好处是:新增一种运算(比如除法)时,不需要改调用逻辑,只改表的内容和维护映射。这就是函数指针在算法题里的价值——解耦和抽象。类似的还有“根据状态调函数”的状态机题目。

写函数指针最需要注意的是类型写法。int (*ops[])(int, int)看起来别扭,但你可以先写一个类型别名:

using OpFunc = int (*)(int, int); OpFunc ops[] = {add, sub, mul};

这样可读性会好很多。刷题时可以用这种写法节省心智负担。

2.4 智能指针:理解现代C++的自动化内存管理

热搜词里有“智能指针”“智能指针实现”,这对刷题党来说分为两个层面:第一,刷题时能不能用?第二,面试时问不问原理?

在纯算法题中,我用智能指针的经验是:能用但没必要。LeetCode的链表题节点类型已经定义成了裸指针,你非要用shared_ptr去包一层反而别扭。但你自己写工程化一点的算法组件时,推荐优先用unique_ptr管理独占所有权,用shared_ptr管理共享所有权。

面试问到“实现一个shared_ptr”时,核心考点是引用计数。思路就是:智能指针对象里保存两个东西,原始指针和引用计数指针;拷贝构造时让计数加一;析构时让计数减一;减到零才真正释放资源。

template <typename T> class SharedPtr { private: T* ptr_; int* count_; public: SharedPtr(T* ptr = nullptr) : ptr_(ptr), count_(new int(1)) {} SharedPtr(const SharedPtr& other) : ptr_(other.ptr_), count_(other.count_) { (*count_)++; } ~SharedPtr() { if (--(*count_) == 0) { delete ptr_; delete count_; } } };

这就是智能指针的骨架实现。它把“什么时候释放内存”这个极易出错的问题交给对象生命周期去解决,避免了普通裸指针常见的悬空、泄漏问题。搞懂这个,你理解unique_ptr的move语义、weak_ptr解决循环引用的原理也会顺很多。

3. 实操过程与核心环节实现

3.1 从删除倒数第N个节点到虚拟头节点的习惯

先讲一个我特别想推荐给所有人的习惯:凡是涉及删除链表节点的题目,优先考虑用虚拟头节点(dummy node)。

以LeetCode 19“删除链表的倒数第N个节点”为例。第一步,在head前面加一个哑节点dummy,dummy->next = head。第二步,让快指针先走n步。第三步,快慢指针同时前进,直到快指针到底。第四步,慢指针的next指向next->next。

核心代码:

ListNode* removeNthFromEnd(ListNode* head, int n) { ListNode dummy(0); dummy.next = head; ListNode* fast = &dummy; ListNode* slow = &dummy; for (int i = 0; i < n; i++) fast = fast->next; while (fast->next) { fast = fast->next; slow = slow->next; } slow->next = slow->next->next; return dummy.next; }

为什么用哑节点?因为如果要删除的正好是头节点,用普通写法需要特判if (head == target),多了不少分支。哑节点的存在让头节点也有了一个“前驱”,所有节点统一用同一种逻辑处理,代码简洁而且不容易漏边界。

我建议你把“涉及链表节点增删操作时,先画个dummy”这个习惯固化下来。它虽然不改变算法复杂度,但能显著降低实现错误的概率。

3.2 快慢指针的边界条件:环形链表两道题一起看

快慢指针最经典的题就是判断链表是否有环。做法很简单:快指针一次走两步,慢指针一次走一步;如果有环,快指针会追上慢指针;如果没环,快指针会先走到null。

bool hasCycle(ListNode* head) { ListNode* slow = head; ListNode* fast = head; while (fast && fast->next) { slow = slow->next; fast = fast->next->next; if (slow == fast) return true; } return false; }

这里注意while条件里fast->next的判空。如果链表没有环,快指针走到最后一个节点时,fast->next为null,循环停住,不会发生空指针解引用。这个条件写漏了,很多题会直接段错误。

升级版是“找到环形入口”。先快慢指针直到相遇,然后让其中一个指针回到head,两个指针每次都走一步,再次相遇的位置就是环入口。这背后有个数学推导:从头节点到入口距离是a,入口到相遇点距离是b,环剩余长度是c。相遇时慢指针走了a+b,快指针走了a+b+k(b+c),而快指针路程是慢指针两倍,所以a+b+k(b+c) = 2(a+b),整理得a = c+(k-1)(b+c)。也就是说,从head走到入口的距离,等于从相遇点绕环若干圈后回到入口的距离。所以让一个指针从head走,一个指针从相遇点走,必然在入口相遇。

这个推导是面试问得比较细的点。就算不考数学推导,你也要清楚这个结论,因为扩展题非常多,比如“寻找两链表的交点”“寻找数组中重复数”都能用类似的快慢思想解。

3.3 双指针在数组里的应用:原地去重的“写指针”思维

说完了链表,再说指针类型题目中另一种高频考法——数组上的双指针,尤其是“原地操作”类。

以“删除排序数组中的重复项”为例。因为它要求原地修改,不能开新数组,所以核心思想是维护一个“末尾指针”或者说“写指针”。读指针遍历整个数组,写指针指向下一个不重复元素应该放的位置。

int removeDuplicates(vector<int>& nums) { if (nums.empty()) return 0; int write = 0; for (int read = 1; read < nums.size(); read++) { if (nums[read] != nums[write]) { write++; nums[write] = nums[read]; } } return write + 1; }

这个写法里,write就是“下一个可写位置的前一个位置”,它始终指向已处理区域中最后一个不重复元素。读指针遇到不同值时,先把write向前挪一位,再把新元素写进来。

我特别喜欢这类题的原因是它特别考验“指针指向什么状态”的清晰度。很多人写这类题犯错,是因为把write既当“末尾位置”又当“待写位置”,逻辑混在一起。你只要想清楚write到底指向哪里,代码是一次过的。

同样的模型可以平移到很多题上:移动零、移除指定元素、合并两个有序数组(从后往前写)等。核心都是:一个指针负责读,一个指针负责写,读指针永远比写指针跑得快。

3.4 字符串与指针:C字符串的坑要特别小心

热搜词里连续出现了“指针数组存放字符串”“字符串数组指针”,说明字符串在指针题里也是个老大难。

刷算法题时,如果是C++的std::string,一切都还好说,因为string自己管理字符数组,你不太需要关心底层指针。但如果是C风格的char*,就容易踩坑。比如:

char* p = "hello"; // 字符串字面量,存在只读区,不能修改 char arr[] = "hello"; // 可修改的字符数组

如果用p[0] = 'H'去尝试修改只读区的字符串,直接崩溃。这在面试里是个高频陷阱题。记住:字符串字面量是const的;想要可修改的副本,用数组或malloc出来新空间再拷贝。

另外,C字符串没有内置长度信息,以\0结尾。所以遍历字符串指针时,循环条件是while (*p != '\0')或者while (*p)。这个和C++的std::string差异很大。刷题时如果题目明确说“C风格字符串”,一定要把所有越界访问的风险都排查一遍。

指针数组存放字符串的典型场景是:

const char* fruits[] = {"apple", "banana", "cherry"};

这里fruits是一个数组,数组元素是const char*。每个元素指向不同的字符串字面量。排序这个数组时,其实只是交换指针,不需要复制字符串内容。这就是指针数组在算法题中的优势——交换的是地址,不是数据,效率高,代码也不容易出错。

3.5 二叉树的指针操作:递归与引用

二叉树的题目看似不叫“指针题”,其实骨子里全是指针。每个节点的左右孩子是指针,递归遍历也是在不断传指针。

写递归框架时,我建议你形成模板化思维。以“翻转二叉树”为例:

TreeNode* invertTree(TreeNode* root) { if (!root) return nullptr; TreeNode* left = invertTree(root->left); TreeNode* right = invertTree(root->right); root->left = right; root->right = left; return root; }

这个递归的返回值是一个指针。它表示“翻转完成后,把新的子树根节点返回给上一层”。如果你把返回值丢掉,或者返回错了节点,整棵树就断了。

另一种写法是用递归的引用参数。有些题目的解法里会写void traverse(TreeNode*& root),尤其是“删除二叉搜索树中的节点”这类需要修改root本身的题目。这时TreeNode*&的作用是让函数内修改root指向,能直接反映到调用方的变量上。如果你用普通的TreeNode* root,对root本身的赋值不会影响外面。这是面试时最容易扣分的地方,因为代码能编译、能跑,但结果不对,你排查半天才发现是这个原因。

3.6 经典变式:从“相同的树”看指针比较和值比较

最后补充一个看着基础但有代表性的题——“判断两棵二叉树是否相同”。它的核心是:指针比较和值比较要分清。

bool isSameTree(TreeNode* p, TreeNode* q) { if (!p && !q) return true; if (!p || !q) return false; if (p->val != q->val) return false; return isSameTree(p->left, q->left) && isSameTree(p->right, q->right); }

这里判断p == q和判断p->val == q->val是不同的。p == q比较的是两个指针是否指向同一个地址;p->val == q->val比较的是两个对象的值。很多人在“判断链表是否相交”的题目里也会犯这个错——不能用值是否相等来判断两个链表是否相交,要看指针地址是否相同。

我还见过有人问“顶层指针和底层指针可以相互赋值吗”之后,就混淆了const指针的比较。说白了,指针的比较规则就是:同类型的指针才能直接比较;不同类型(如int*double*)的比较要么报错,要么需要强转;比较的是地址,不是地址处的数据。这个规则在刷题时看似不起眼,但在判断节点是否同一节点时极其关键。

4. 常见问题与排查技巧实录

4.1 一张表理清指针题的经典报错

写指针类题目最痛苦的就是debug。不过绝大多数错误都可以归到下面这几类,我整理了一个速查表,方便你对照排查:

症状原因解决办法
运行时崩溃,段错误空指针解引用,或访问了已经释放的内存加判空,避免访问悬空指针
死循环,程序跑不完链表有环但没被检测到,或指针移动条件写错检查指针步进逻辑,快慢指针会检测环
结果不对,但没有崩溃修改了指针本身而不是指针指向的内容区分p = x*p = x
编译器提示const相关错误试图把const指针赋值给非const指针理解顶层const和底层const的赋权规则
函数体内改指针无效传入的是指针的副本改用T*&T**参数

这些是我反复遇到的真实问题。写题的时候,看到崩溃,先怀疑空指针;看到死循环,先怀疑步进和终止条件;看到结果差一点,先画图核对每一步的状态。

4.2 悬空指针和野指针:快速定位三招

悬空指针就是指向的内存已经被释放,野指针就是指针变量没有初始化,指向一个随机的地址。这两种在算法题里不太常见(因为刷题输入数据都合法),但在实现数据结构和内存池练习里会遇到。

定位方法我总结了三招:

  • 第一招,加打印。在每个疑似出问题的步骤前输出指针地址和值,观察什么时候变成异常值。
  • 第二招,用工具。C++可以用AddressSanitizer,编译时加-fsanitize=address,它能在你访问非法内存时立刻告诉你具体是哪一行触发的。
  • 第三招,代码审查时重点关注:谁分配了内存?谁释放了内存?释放之后还有没有别的指针指向这块内存?

放到算法题里,这第三招就是:链表节点是不是被删了?释放之后别的指针还能不能访问它?比如“删除链表中的节点”这个问题,如果面试题要求释放被删节点,那么所有仍然指向这个节点的指针都要先修正,否则就是悬空指针。

4.3 指针自增和下标访问的等价性

还有一个新手容易忽略的点:*(p + i)p[i]是等价的。很多人在数组指针题里纠结半天,其实这就是下标访问的本质——p[i]在编译期就是*(p+i)

但要注意,这不代表数组和指针完全等价。sizeof(arr)sizeof(p)的结果不同,前者是整个数组的大小,后者是指针变量的大小。函数参数中的数组会退化为指针,所以void f(int arr[])void f(int* p)在签名层面等价。

刷题时这个知识点怎么用?当你在实现“数组旋转”“二维数组遍历”时,可以放心用下标,因为编译器会在底层自动处理指针算术。但在需要极致性能或底层操作的场景(比如图像处理算法题),可以考虑直接移动指针来避免重复计算偏移。

4.4 递归中指针传参的常见错误

递归题目也经常涉及指针。最常见的问题是:递归参数到底传TreeNode*还是TreeNode*&

我的经验是分场景:

  • 如果递归的返回值是节点指针,通常传TreeNode*即可,因为返回值本身会向上传递。
  • 如果要在递归过程中更新某个外部指针(例如记录深度最大的节点),建议传TreeNode*&
  • 如果递归的过程中需要修改当前节点的子指针(比如删除节点操作),建议传TreeNode*&

给你看一个错误示例。假设你想让递归函数把root修改为null:

void resetTree(TreeNode* root) { root = nullptr; }

这个函数根本不会改变外部传入的root变量。因为root参数是外部指针的副本,你把副本改成null,外部指针不受影响。想真正修改外部指针,必须这样:

void resetTree(TreeNode*& root) { root = nullptr; }

这个细节在“删除二分搜索树节点”“删除链表的某个节点”这类问题中出现频率极高。面试官特别喜欢在这里设坑,因为你代码跑一次可能发现log打出来root还是原来的值,却不知道问题出在参数传递上。

4.5 函数指针使用时的避坑清单

函数指针不是算法题的常客,但一旦出现(比如“根据运算符计算结果”“命令分发统计”),有几个坑值得提前知道:

  • 函数指针的类型必须和函数签名完全一致,包括返回类型和参数类型。int (*)(int, int)只能指向返回int、两个int参数的函数,不能指向返回void的函数。
  • 取函数地址有两种写法:&addadd都可以,编译器都接受。刷题时直接用函数名最省事。
  • 函数指针数组初始化时,函数声明要在前,确保类型匹配。
  • 通过函数指针调用时,ops[idx](a, b)(*ops[idx])(a, b)等价。现代C++允许省略解引用,但理解底层调用关系有助于调试。

函数指针表最大的价值是让代码从“if-else丛林”变成“查表映射”。尤其是做计算器、状态机、命令分发这类题时,用函数指针表比写一堆switch分支清爽得多。

5. 实操总结与经验心得

写到这里,指针类型题目的核心方法其实已经讲完了。最后我再分享几条自己这些年刷题和面试中沉淀下来的个人经验,不一定多高深,但全是实际验证过的。

第一,刷指针题一定要多画图。用箭头表示指针指向,用方框表示节点对象,每执行一步更新箭头方向。画个十来道题,你对指针操作的理解会有质的飞跃。这比反复读十遍理论都有用。

第二,遇到“指针指向问题”时,先问自己:我要修改的是“指针本身”还是“指针指向的对象”?这道题里,函数参数需要T*还是T*&?想清楚这两点,再动手写代码。

第三,调试指针错误时,不要凭空猜。打印地址、画状态图、用sanitizer,找到第一次行为异常的点,然后往前倒推。每一次“改代码碰运气”都是在浪费时间。

第四,经典题的模板背熟没有坏处。链表反转的迭代模板、快慢指针判环模板、双指针原地去重模板、二叉树的递归遍历模板,这些是解大量变种题的基础。模板不是让你死记,而是让你在紧张时有一个可靠的起点,再根据题目要求做修改。

最后再讲一点:很多人觉得C/C++指针既然这么容易出错,是不是直接用Java/Python就完事了?其实不是。指针思维训练的是对“对象引用”的理解。你用Java写链表题时,虽然不需要手动管理内存,但节点的next引用怎么改、怎么赋值,和C++指针的底层逻辑完全一样。学懂了指针,不仅C/C++题会写,其他语言里关于引用、可变性、对象共享的概念也会清晰很多。

希望这篇文章能帮你在“链表的箭头世界”和“数组的格子世界”里少踩几个坑。刷题路上最难的不是题难,是你明明知道解法,代码却因为一个指针写错而反复报错——这种挫败感我太懂了。所以,去画图吧,去打印地址吧,去把每一个指针的“指向”和“状态”都搞清楚,你会发现这些题突然变得温顺起来。

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

Android美颜相机实现:CameraX取帧、美颜算法与GLSL渲染管线

简介&#xff1a;面向安卓开发与毕业设计人群的这份项目资料&#xff0c;围绕实现一款类似美颜相机、美图秀秀的应用展开&#xff0c;覆盖实时美颜、照片编辑、滤镜特效等核心功能需求。内容系统梳理了安卓开发基础、相机与相机第二代相机接口的调用、使用开放计算机视觉库进行…

作者头像 李华
网站建设 2026/9/17 3:07:14

东莞工业大风扇控制方式深度拆解:五档方案选型与避坑指南

1. 2025年东莞工业大风扇厂家控制方式全景拆解&#xff1a;别只看风量和叶片&#xff0c;控制器才是分水岭做工业大风扇这个行业久了&#xff0c;你会发现一个挺有意思的现象&#xff1a;客户来询价&#xff0c;张口第一句基本是“4米直径多少钱一片”、“几台起批”&#xff0…

作者头像 李华
网站建设 2026/9/17 3:07:07

FileZilla Server外网访问配置:防火墙、端口转发与内网穿透实战

不少朋友搭好 FTP 服务器之后&#xff0c;卡在最关键的一步&#xff1a;内网测试一切正常&#xff0c;换到外网就死活连不上。这篇文章我从 FileZilla Server 的完整配置讲起&#xff0c;把 Windows 防火墙放行、路由器端口转发、NAT 内网映射的原理和操作一次说透&#xff0c;…

作者头像 李华
网站建设 2026/9/17 3:06:00

PolarDB Agent Express:数据库内核级AI助理实现数据不出域

1. 为什么“数据不出域”不是一句口号&#xff0c;而是企业AI助理落地的第一道生死线我去年帮三家制造业客户部署AI知识助手&#xff0c;前两家图快&#xff0c;直接上了公有云SaaS版——结果不到三个月&#xff0c;法务部发来一纸《数据合规风险提示函》&#xff1a;销售合同摘…

作者头像 李华
网站建设 2026/9/17 3:03:57

ADC与DMA协同工作:高效电压采样方案的原理与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华