C语言函数:编程世界的魔法钥匙(2)-学习笔记

news2024/11/15 12:37:01

引言

注:由于这部分内容比较抽象,而小编我又是一个刚刚进入编程世界的计算机小白,所以我的介绍可能会有点让人啼笑皆非。希望大家多多包涵!万分感谢!待到小编我学有所成,一定会把这块知识点重新介绍一遍,让大家更好地理解和掌握。

在上一篇文章中,我们一同探索了函数的基本概念,为深入理解编程中的函数世界打下了坚实基础。现在,让我们继续前行,走进函数递归与迭代的奇妙领域。


1、函数递归

想象一下,你要计算一个非常大的数的阶乘,有没有一种神奇的方法,可以让一个函数自己调用自己来完成这个复杂的计算呢?这里就需要使用我们接下来所要介绍的知识——函数递归

1.1 什么是函数递归?

函数递归是计算机编程中一种非常强大且富有技巧性的概念。
 
从定义上来说,函数递归指的是在函数的定义中使用函数自身的调用
 
通俗来讲,就是一个函数在执行过程中直接或间接地调用了自身。
 

函数递归通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,只需要少量的程序就可以描述出解题过程所需要的多次重复计算,大大减少了程序的代码量。

函数递归的主要思想:把大事化小

举一个生活中的例子:

想象一下你有一堆俄罗斯套娃。打开最大的那个套娃,里面还有一个小一点的套娃,打开这个小套娃,又有一个更小的。一直这样开下去,(递)直到开到最小的那个套娃,没办法再开了(这就相当于递归的终止条件)。然后再把打开的套娃一个一个地按照原来的顺序放回去。(归)

                      图一                                                                 图二

图二呢就像是我们所编写的代码,在程序未运行起来之前,展现给我们的只是少量代码。

 代码解释:比如说我们有一个递归函数,它的任务是计算某个数的阶乘。

int factorial(int n)
{
    if (n == 0 || n == 1) 
    {
        return 1;
    }
    else 
    {
        return n * factorial(n - 1);
    }
}

首先,factorial(4) 发现 4 不等于 0 也不等于 1,所以它要去计算 4 * factorial(3) 。这就相当于打开了一个稍微小一点的“套娃”,即 factorial(3) 。

然后 factorial(3) 又发现 不符合结束条件,所以要计算 3 * factorial(2) ,这又打开了一个更小的“套娃” factorial(2) 

以此类推,一直到 factorial(1) 或 factorial(0) ,这就相当于我们打开到了最小的那个“套娃”,因为它们会直接返回 1 ,不再继续递归调用。

然后再从最小的“套娃”开始,逐步返回计算结果,一层一层地“合上套娃”,最终得到 factorial(4) 的结果。

就像俄罗斯套娃一样,不断深入打开更小的,最后又从最小的开始一层一层关闭回来,得到最终的结果。

完整代码:

#include <stdio.h>

int factorial(int n)
{
    if (n == 0 || n == 1)//限制条件不可少
    {
        return 1;
    }
    else
    {
        return n * factorial(n - 1);
    }
}
int main()
{
    int num = 4;
    int result = factorial(num);
    printf("阶乘 %d 的结果是 %d\n", num, result);
    return 0;
}

下面用图片来解释:

图片文字较小,建议放大观看!!(如有错误,希望各位大佬指正,万分感谢!!!) 

阶乘的定义是,对于非负整数 n,n 的阶乘(记作 n!)等于 n 乘以 (n - 1) 的阶乘,并且 0 的阶乘和 1 的阶乘都规定为 1。

在函数递归计算阶乘的过程中,我们定义一个函数 factorial 。

当 n 等于 0 或者 1 时,这就是递归的终止条件,因为 0 的阶乘和 1 的阶乘都已经明确规定为 1 了,所以此时函数直接返回 1 。

当 n 大于 1 时,函数就会调用自己来计算 (n - 1) 的阶乘,然后将 n 乘以这个结果,从而得到 n 的阶乘。

 这道题我们要计算 4 的阶乘。函数 factorial(4) 被调用,因为 4 大于 1 ,所以它会返回 4 * factorial(3) 。接着计算 factorial(3) ,又会返回 3 * factorial(2) ,以此类推,一直到 factorial(1) ,因为 1 满足限制条件,所以返回 1 ,然后再逐步回溯计算,最终得到 4 的阶乘的值 24 。 这就是通过函数递归计算阶乘的基本原理它通过不断地自我调用,逐步逼近终止条件,最终得出结果。

到这里大家大致应该对函数递归有一点了解了吧!

上文中我提到了终止条件,这也是函数递归必不可少的条件

1.2 函数递归的两个必要条件

  • 存在 终止条件,当满足这个限制条件的时候,递归就会停止。
  • 每次递归调用之后越来越接近这个限制条件。
 if (n == 0 || n == 1)限制条件

为什么说终止条件是必不可少的呢?

你可以想象一下,一辆火车如果没有刹车,那会是什么情况,是不是停不下来?

终止条件就像是一个“刹车”,如果没有它,函数会不停地调用自身,导致无限循环,最终程序可能会因为栈溢出等错误而崩溃。因此,终止条件可以有效的防止代码的无限循环。

举个例子来说明一下:顺序打印每个数

#include <stdio.h>
void print(unsigned int n)
{
    if (n > 9)//限制条件
    {
       print(n / 10);
    }
      printf("%d", n % 10);   
}
int main()
{
    unsigned int num = 0;
    scanf("%u", &num);
    print(num);//
    return 0;
}

如果将 if (n > 9)这行代码删除会发生什么呢?

当没有限制条件后,这个函数就会自己调自己,一直循环,发生死递归,出现堆栈溢出

1.3  什么叫堆栈溢出呢?

内存划分为栈区、堆区、静态区。

在程序运行时,当一个函数被调用时,会在栈区为该函数分配一块内存空间,用于存储函数的参数、局部变量以及函数执行的上下文信息。

堆栈溢出是由于程序在运行时对栈空间的需求超过了其所能提供的容量,通常是由于不合理的函数调用结构、过大的局部数据或错误的代码逻辑引起的。

我们可以调试看一下

在调试过程中,系统会给这样一个错误,stack overflow叫 栈溢出

      这道题出现栈溢出的原因就是因为该函数没有终止条件,出现死递归导致栈空间被持续占用而无法释放。

这就是为什么我们需要终止条件的原因。

以下是一些避免栈溢出错误的常见方法:

1. 优化函数调用 : 减少函数的嵌套调用层数,避免不必要的深层递归。对于可以使用迭代解决的问题,优先选择迭代而不是递归。

2.控制函数局部变量的大小 :避免在函数内部创建过大的局部数组或其他大型数据结构。如果需要较大的存储空间,可以考虑在堆上动态分配内存。

3. 分解复杂函数 : 将复杂的函数拆分成多个较小的、更简单的函数,以减少单个函数的复杂性和所需的栈空间。

4. 尾递归优化 : 如果使用递归,尽量将其转化为尾递归形式。一些编译器可以对尾递归进行优化,避免栈空间的不断增长。

5. 增加栈空间大小 :在某些编程环境中,可以通过设置来增加栈的默认大小。但这只是一种临时的解决方案,不是根本的解决办法。

6. 数据结构优化 : 选择更合适的数据结构和算法,以减少计算过程中的内存需求和函数调用次数。

7. 检查代码逻辑 ; 确保代码没有进入无限循环或不正确的递归逻辑,导致栈空间不断被消耗。 通过以上方法的综合运用,可以有效地降低出现栈溢出错误的风险,提高程序的稳定性和性能。

相信大家现在应该对终止条件的重要性有一定的了解了吧 ! ! !

1.4 对比求解阶乘

常规循环方法:

#include <stdio.h>

int fac(int n) 
{
    int result = 1;
    for (int i = 1; i <= n; i++)
    {
        result *= i;
    }
    return result;
}

int main()
{
    int num = 0;
    scanf("%d", &num);
    int result = fac(num);
    printf("fac = %d\n", result);
    return 0;
}

在这个常规的循环方法中,通过一个 for 循环从 1 乘到指定的数 n ,逐步累乘得到阶乘的结果。 

函数递归方法:

#include <stdio.h>

int fac(int n)
{
    if (n == 0 || n == 1)//限制条件不可少
    {
        return 1;
    }
    else
    {
        return n * fac(n - 1);
    }
}
int main()
{
    int num = 0;
    scanf("%d", &num);
    int result = fac(num);
    printf("fac = %d\n", result);
    return 0;
}

在递归方法中,如果 n 为 0 或 1 ,直接返回 1 作为终止条件。否则,通过 n 乘以 n - 1 的阶乘来实现递归计算。

对比来看:

代码简洁性:递归方法的代码通常更简洁,更能直接体现阶乘的数学定义。

理解难度:对于初学者,循环方法可能更容易理解,因为它的执行过程更直观。递归方法需要理解函数的自我调用和终止条件,相对较难。

性能:在大多数情况下,循环方法的性能通常比递归方法好,因为递归会带来额外的函数调用开销和栈空间的使用。

适用场景:对于一些具有明显递归结构的问题,递归方法可能更自然和直观;而对于简单的计算,循环方法可能更实用。

1.5 函数递归的优缺点

 优点:

1. 代码简洁性 :递归能够以一种简洁而直观的方式表达某些问题的解决方案,使代码更具可读性和表达力。

2. 符合问题的自然逻辑: 对于一些本身具有递归性质的问题,如树形结构的遍历、某些数学计算等,使用递归更贴合问题的本质逻辑。

3. 易于理解和实现复杂问题:在处理一些复杂问题时,递归可以使解决方案的思路更加清晰,更容易理解和编码。

 缺点:

1. 性能开销 :递归调用会带来额外的函数调用开销,包括参数传递、保存和恢复上下文等,这可能导致性能下降,特别是在递归深度较大时。

2. 栈空间消耗: 每次递归调用都会在栈上分配内存来保存函数的状态和局部变量。如果递归深度过大,可能会导致栈溢出错误。

3. 可读性挑战: 对于一些复杂的递归逻辑,如果没有清晰的注释和良好的设计,可能会使代码难以理解和维护。

4. 调试困难:由于递归调用的复杂性,调试递归函数可能比调试普通函数更具挑战性。 综上所述,在使用函数递归时,需要根据具体问题的特点和需求,权衡其优缺点,以决定是否采用递归方法来解决问题。

1.6 函数递归的实际应用

函数递归在现实生活中有以下一些用处:

1. 组织架构管理 : 公司或机构的组织架构可以看作是一个树形结构。通过递归可以遍历整个架构,例如查找特定部门下的所有子部门和员工。

2. 物流与供应链 :在复杂的物流网络中,确定货物从源头到目的地的所有可能路径时可以使用递归。

3. 任务分解与规划 : 将一个大型项目分解为多个子项目,每个子项目又可以进一步分解,类似于递归的过程,以更好地管理和安排工作。

4. 决策树分析 : 例如在金融领域,分析投资决策的各种可能结果和分支。

5. 人工智能中的搜索算法 :如在棋类游戏的 AI 中,通过递归搜索可能的走法和局面。

6. 语法解析 :在自然语言处理中,对句子的语法结构进行解析时可能用到递归。

7. 目录和文件系统操作 : 遍历计算机中的文件夹和子文件夹,执行特定的操作,如查找特定类型的文件或计算文件大小。

8. 电路设计 : 分析复杂的电路连接和信号传递路径。

9. 数学教育与解题 : 帮助理解和解决一些数学问题,如数列的计算、组合数学中的问题等。

2、函数迭代

函数迭代是通过循环结构来重复执行某段代码,实现问题的解决或计算的过程。

循环是一种迭代,但迭代不仅仅是循环。

2.1 什么是函数迭代

函数迭代指的是将一个初始值代入一个函数,得到一个新的值,然后再将这个新值作为输入再次代入同一个函数,如此反复进行,以获得一系列的值或者逼近某个特定的结果。 简单来说,就是按照一定的规则,不断地用函数作用于某个值,产生新的值,并持续这个过程。

2.2 函数递归与迭代

例题:求第n个斐波那契数(不考虑溢出)

斐波那契数列

1 1 2 3 5 8 13  21 34 55...

大家有看出什么规律吗?

斐波那契数列就是 前两个数相加等于第三个数

了解了斐波那契数列,我们可以用函数表示一下

函数递归代码表示:

int count = 0;//全局变量 

int Fib(int n)
{   //在整个流程中,求了多少次第三个斐波那契数
	if (n == 3)
		count++;
	if (n <= 2)
		return 1;
	else
		return Fib(n - 1) + Fib(n - 2);
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = Fib(n);
	printf("%d\n", ret);
	printf("%d\n", count);
	return 0;
}

在这个代码里,新加入了一个全局变量 count ,该变量在该循环中是的作用在整个流程中,求了多少次 Fib(3),比如说我们要求Fib(40),代码结果展示:

我们在求第40位斐波那契数的过程中,第三位斐波那契数被求了39088169次,三千多万次

效率低下,并且在使用 fib 这个函数的时候如果我们要计算第50个斐波那契数字的时候特别耗费时间。这是为什么呢?

其实在使用递归求结果的时候,递归程序会不断的展开,在展开的过程中,我们很容易就能发现,在递归的过程中会有大量的重复计算,⽽且递归层次越深,冗余计算就会越多。

函数迭代代码表示:

int Fib(int n)
{   
	int a = 1;
	int b = 1;
	int c = 0;
	while (n >= 3)
	{
		c = a + b;
		a = b;
		b = c;
		n--;
	}
	return c;

结果展示:

从该视频我们就可以看到函数迭代的效率比函数递归求斐波那契数快很多倍,但是由于会出现栈溢出问题,因此在求结果可能会不正确。不过呢,你不要管它对不对,快不快就完事了!!

函数迭代是为了解决重复操作的问题。当我们需要重复执行一段代码,但每次执行都需要不同的输入或参数时,使用函数迭代可以简化代码并提高效率。

通过使用函数迭代,我们可以定义一个函数,并通过不同的输入值多次调用该函数。这样可以避免重复编写相同的代码,提高代码的重用性和可维护性。

另外,函数迭代还可以帮助我们处理大规模数据集,特别是在数据处理和分析方面。通过使用迭代函数,我们可以逐个处理数据集中的元素,而不需要一次性加载整个数据集到内存中。

总之,函数迭代是一种有效的编程技术,可以提高代码的可重用性和可维护性,同时还可以处理大规模数据集。

2.3 函数迭代相较于函数递归的优点:

1.性能优势

函数迭代通常比递归具有更好的性能。因为递归涉及函数的多次调用,会带来额外的开销,而迭代通过循环实现,效率通常更高。

2.内存使用更高效

递归可能导致大量的栈空间使用,容易出现栈溢出错误。迭代一般在固定的内存区域操作,对内存的使用更可控。

3.更易理解和调试

对于一些复杂的递归逻辑,理解和跟踪其执行过程可能较为困难。迭代的执行流程通常更直观,便于调试和查找问题。

4.可扩展性

在处理大规模数据或复杂问题时,迭代更容易进行优化和扩展,例如通过并行化或分段处理来提高效率。

5.不受递归深度限制

递归存在深度限制,而迭代没有这个限制,可以处理更大规模的计算。

3、 避免堆栈溢出的有效方法:

1.精简函数和代码逻辑

优化函数内部的实现,去除不必要的复杂计算和临时变量,使函数执行所需的栈空间减少。

2.限制递归深度

如果使用递归,明确设置递归的最大深度,并在达到限制时采取适当的措施,如返回默认值或错误提示。

3.优化数据结构

选择更节省空间的数据结构。例如,能用指针代替数组的情况尽量使用指针,或者使用具有动态扩展能力的数据结构(如std::vector在 C++中)。

4.合理分配内存

对于较大的数据块,使用堆内存分配(如malloc/new)而不是在栈上分配。并且在使用完毕后及时释放(free/delete)。

5.分治法

将大型任务分解为较小的子任务,分别处理,避免单个函数或操作需要过大的栈空间。

6.控制循环次数和范围

确保循环不会无限制地运行,并且循环的范围是合理的,不会导致过多的栈空间消耗。

7.利用缓存和重用

对于重复计算或频繁使用的数据,进行缓存,避免重复计算和占用额外的栈空间。

总之,要综合考虑程序的设计、算法选择、数据结构和资源管理等多方面因素,以有效地避免堆栈溢出问题。


结语:

亲爱的读者们,本文即将告一段落。首先,我想向大家表示诚挚的歉意我原本以为自己能够清楚地解析函数递归与迭代的概念,然而我错了。在写作过程中,我深感函数递归与迭代的复杂性超乎我的预料。尤其是当我试图解释迭代时,我甚至产生了放弃的念头,因为我觉得自己无法再向前推进。然而,考虑到我已经付出了很多努力,我不愿意就此放弃,所以我还是决定坚持把文章写完。

我知道这篇文章可能没有完全达到大家的期望,我在此深感遗憾。请大家相信,我并非故意如此,只是我在这个领域的知识还有待提高。将来,我会投入更多的时间和精力,争取为大家带来更加深入、易于理解的函数递归与迭代解析。请大家拭目以待,也欢迎随时向我提出建议和意见。

最后,再次向大家表示由衷的歉意,希望你们能够理解我的困境。谢谢你们的支持,我会继续努力,为大家呈现更优质的内容。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1939536.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VB利用API调用系统的通用颜色对话框

Option Explicit 在窗体上添加一个Command1按钮控件 Private Type ChooseColor lStructSize As Long hwndOwner As Long hInstance As Long rgbResult As Long lpCustColors As String Flags As Long lCustData As Long lpfnHook As Long lpTemplateName As String End Type 该…

pcie拓扑结构与层次结构

一 拓扑结构 PCIE 总线与总线共享式通讯方式的 PCI 不同&#xff0c;PCIE 由点到点的链路组成&#xff0c;并采用树形拓扑结构PCIE 拓扑结构体系由 CPU、根复合体&#xff08;RootComplex&#xff0c;RC&#xff09;、端点设备&#xff08;Endpoint&#xff0c;EP&#xff09;…

Python入门------pycharm加载虚拟环境

pycharm虚拟环境配置&#xff1a; 在按照前面的办法&#xff0c;配置好虚拟环境后,如果我们需要到虚拟环境开发&#xff0c;就需要给编译器配置虚拟环境 1.打开编译器&#xff0c;点击右下角的interpreter选项 2. 点击ADD Interpreter,添加虚拟环境 3. 因为我们使用的是原始…

【LeetCode】二叉树的最大深度

目录 一、题目二、解法完整代码 一、题目 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3 示例 2&#x…

vue2学习笔记9 - 通过观察vue实例中的data,理解Vue中的数据代理

接着上一节&#xff0c;学一学vue中的数据代理。学vue这几天&#xff0c;最大的感受就是&#xff0c;名词众多&#xff0c;听得发懵。。不过&#xff0c;深入理解之后&#xff0c;其实说得都是一回事。 在Vue中&#xff0c;数据代理是指在实例化Vue对象时&#xff0c;将data对…

【C++高阶】精通AVL树:全面剖析与深度学习

目录 &#x1f680; 前言一&#xff1a; &#x1f525; AVL树的性质二&#xff1a; &#x1f525; AVL树节点的定义三&#xff1a; &#x1f525; AVL树的插入四&#xff1a; &#x1f525; AVL树的平衡调整&#xff08;附动图&#xff09; 五&#xff1a;&#x1f525; AVL树的…

防御保护课-防火墙接口配置实验

一、实验拓扑 &#xff08;我做实验用的图如下&#xff09; 二、实验要求 1.防火墙向下使用子接口分别对应生产区和办公区 2.所有分区设备可以ping通网关 三、实验思路 配IP&#xff1b; 划分vlan并配置vlan&#xff1b; 配置路由和安全策略。 四、实验配置 1、画图并…

C++与lua联合编程

C与lua联合编程 一、环境配置二、lua基本语法1.第一个lua和C程序2.基本数据类型和变量2.1 Nil2.2 Booleans2.3 Numbers2.4 String(最常用) 3. 字符串处理3.1 错误处理3.2 字符串长度:string.len3.3 字符串子串 :string.sub3.4 字符串查找: string.find3.5字符串替换: string.gs…

Evil-WinRM一键测试主机安全情况(KALI工具系列四十四)

目录 1、KALI LINUX 简介 2、Evil-WinRM 3、信息收集 3.1 目标IP 3.2 kali的IP 4、操作步骤 4.1 用户访问 4.2 使用哈希值 4.3 文件处理 5、总结 1、KALI LINUX 简介 Kali Linux 是一个功能强大、多才多艺的 Linux 发行版 &#xff0c;广泛用于网络安全社区。它具有全…

swiftui使用ScrollView实现左右滑动和上下滑动的效果,仿小红书页面

实现的效果如果所示&#xff0c;顶部的关注用户列表可以左右滑动&#xff0c;中间的内容区域是可以上下滚动的效果&#xff0c;点击顶部的toolbar也可以切换关注/发现/附近不同页面&#xff0c;实现翻页效果。 首页布局 这里使用了NavigationStack组件和tabViewStyle样式配置…

在项目服务器部署git 并实现自动提交

以下场景适合在服务器当中使用git 方便提交代码&#xff0c;同时不需要外部的git仓库&#xff08;码云gitee或者github作为管理平台&#xff09;。依靠服务器本身ssh 连接协议做为git提交的地址&#xff0c;同时利用钩子自动同步项目代码 首先下载git sudo apt update sudo a…

Linux最直观的性能分析(热点分析)-编译perf并生成火焰图

本文先介绍了linux下perf工具的使用场景&#xff0c;然后对命令行安装和源码编译安装两种方式做了说明&#xff0c;接下来通过最简单的perf top命令给出perf的直观印象&#xff0c;最后通过perf record生成火焰图的方式说明如何发现进程中的函数热点。 一、perf工具介绍 per…

00 JavaWeb

学习资料&#xff1a;B站视频-黑马程序员JavaWeb基础教程 文章目录 JavaWeb1、JavaWeb简介2、 JavaWeb主要内容3、JavaWeb技术栈4、JavaWeb课程安排5、Web核心课程安排 JavaWeb 1、JavaWeb简介 Web&#xff1a;全球广域网&#xff0c;也称为万维网(www)&#xff0c;能够通过浏…

C++: 链表回文结构/分割链表题解

目录 1.链表的回文结构 分析 代码 2.链表分割 ​编辑分析 代码 1.链表的回文结构 分析 这道题的难点是空间复杂度为O&#xff08;1&#xff09; 结合逆置链表找到链表的中间节点就可以解决了。 先找到链表的中间节点&#xff0c;再对中间节点的下一个节点进行逆置&…

macbook pro大模型推理

安装与配置 参考github ollama 链接安装ollama。安装完成后,安装常用的模型,下载速度超快。 性能测试 在进行实际测试之前,我首先对模型进行了预处理,以确保其在 M3 Max 上能够高效运行。测试过程中,我主要关注了以下几个方面: 模型加载时间 加载大型模型通常需要较…

Python WebUIAPI:打造交互式Web界面的利器

Python WebUIAPI&#xff1a;打造交互式Web界面的利器 引言&#xff1a;交互式Web界面的革新 在当今快速发展的互联网时代&#xff0c;Web界面的交互性已成为衡量用户体验的重要标准。Python作为一门流行的编程语言&#xff0c;其生态中涌现出许多强大的库来帮助开发者构建交互…

组队学习——贝叶斯分类器

前言 本次数据继续沿用上一次主题的【组队学习——支持向量机-CSDN博客】 数据处理部分延续【组队学习——支持向量机】主题的处理办法对应划分训练集和验证集 模型选择 本次贝叶斯分类器模型的较多&#xff0c;常用的为高斯朴素贝叶斯分类器、多项式朴素贝叶斯分类器、伯努…

2024年超好用的4款PDF阅读器推荐

PDF文件已经是我们平时常常会接触到的文件&#xff0c;但是无论是阅读和编辑都需要依赖一些工具&#xff0c;所以今天给大家介绍的是4个很多人都在使用的PDF阅读器。 &#xff11;、福昕PDF阅读软件 这款PDF编辑器是一个大厂其他的产品&#xff0c;功能非常的强大&#xff0c;…

Build a Large Language Model (From Scratch)GPT-4o翻译和代码每行中文注释Ch4

目录 4 Implementing a GPT model from Scratch To Generate TextThis chapter covers4.1 Coding an LLM architectureListing 4.1 A placeholder GPT model architecture class4.2 Normalizing activations with layer normalization4.3 Implementing a feed forward network …

STM32CUBEMX_SPI_驱动WS2811灯带

STM32CUBEMX_SPI_驱动WS2811灯带 前言&#xff1a; 关于这种带芯片的之前我都是使用GPIO模拟时序&#xff0c;但是带来一个很大的弊端&#xff0c;那就是严重占用CPU资源&#xff0c;使得其他代码逻辑没办法正常执行了&#xff0c;想办法搞一个单片机的外设使用DMA功能&#xf…