Lazy loaded image
知识小记
🍇函数与递归
字数 1562阅读时长≈ 4 分钟
2026-3-23
2026-3-23
编程不是看会的,也不是听会的,而是练会的,所以应尽量在计算机旁阅读本 书,以便把书中的程序输入到计算机中进行调试,顺便再做做上机练习。

Plans

  1. 掌握多参数、单返回值的数学函数的定义和使用方法
  1. 学会用typedef定义结构体
  1. 函数调用时用实参给形参赋值的过程
  1. 学会定义局部变量和全局变量
  1. 理解调用栈和栈帧,学会用gdb查看调用栈并选择栈帧
  1. 理解地址和指针
  1. 理解递归定义和递归函数
  1. 理解可执行文件中的正文段、数据段和BSS段
  1. 熟悉堆栈段,了解栈溢出的常见原因

Notes

自定义结构体

上述代码中定义了一个名为point的结构体,包括两个域:double型的x和y。
💡
在C语言中,定义结构体的方法为“struct 结构体名称{ 域定义 };”,注意花括 号的后面还有一个分号。
所有用到Point的地方都得写一个struct。有一个方法可以避 开这些struct,让结构体用起来和int、double这样的“原生”类型更接近:

函数调用与参数传递

形参与实参

函数的形参和在函数内声明的变量都是该函数的局部变量。无法访问其他函数的局部变量。局部变量的存储空间是临时分配的,函数执行完毕时,局部变量的空间将被释放,其中的值无法保留到下次使用。在函数外声明的变量是全局变量,可以被任何函数使用。

调用栈

调用栈描述的是函数之间的调用关系。它由多个栈帧(Stack Frame)组成,每个栈帧对应着一个未运行完的函数。
栈帧中保存了该函数的返回地址和局部变量,因而不仅能在执行 完毕后找到正确的返回地址,还很自然地保证了不同函数间的局部变量互不相干——因为不 同函数对应着不同的栈帧。
💡
C语言用调用栈(Call Stack)来描述函数之间的调用关系。调用栈由栈帧 (Stack Frame)组成,每个栈帧对应着一个未运行完的函数。

指针

为什么要用指针?
C 语言函数参数默认是值传递。如果写成 swap(int a, int b),只会交换副本,函数外原变量不变。用指针后,传入的是变量地址,函数里修改的是“原变量本体”。
用 int* a 声明的变量a是指向int型变量的指针。赋值 a = &b 的含义是把变量b的地址存放在指针a中,表达式 *a 代表a指向的变量,既可以放在赋值符号的左边(左值),也可以放在右边(右值)。
💡
*a是指“a指向的变量”,而不仅是“a指向的变量所拥有的值。例如,*a = *a + 1就是让a指向的变量自增1。

数组作为参数和返回值

💡
是错误的,因为sizeof(a)无法得到数组的大小。把数组作为参数传递给函数时,实际上只有数组的首地址作为指针传递给了函数。
换句话说,在函数定义中的int a[]等价于int *a。在只有地址信息的情况下,是无法知道数组里有多少个元素的。
以数组为参数调用函数时,实际上只有数组首地址传递给了函数,需要另加一个参数表示元素个数。除了把数组首地址本身作为实参外,还可以利用指针加减法把其他元素的首地址传递给函数。

把函数作为函数的参数

💡
指向常数的“万能”的指针:const void *,它可以通过强制类型转化变成任意类型的指针。对于本题来说,排序的对象是整型数组,因此要这样写:

递归

💡
递归就是“自己用到自己”的意思。
“间接地用到自己”也算递归。
可以递归定义“常量表达式”(以下简称表达式): (1)整数和浮点数都是表达式。 (2)如果A是表达式,则(A)是表达式。 (3)如果A和B都是表达式,则A+B、A-B、A*B、A/B都是表达式。 (4)只有通过(1)、(2)、(3)定义出来的才是表达式。

递归函数

数学函数也可以递归定义。例如,阶乘函数f(n)=n!可以定义为:
notion image
💡
C语言支持递归,即函数可以直接或间接地调用自己。但要注意为递归函数 编写终止条件,否则将产生无限递归。
递归调用时新建了一个栈帧,并且跳转到了函数开头处执行,就好比皇帝找大臣、大臣找知府这样的过程。尽管同一时刻可以有多个栈帧(皇帝、大臣、知府同时处于“等待下级回话”的状态),但“当前代码行”只有一个。

头文件

这是一个头文件。什么是头文件呢?实践者的理解方式就是——不加这一行时会出现什 么错误,反过来就说明了这一行的作用。不加这一行的编译警告是:
头文件的作用就是:包含了一些函数,供主程序使用。下表中列出了一些常用函数和对应的头文件
notion image
 
上一篇
SQL
下一篇
Vim 语法速记