文章目录
前言
终于把函数这一章上完了,虽然因为我的摸鱼,两周都没有写完这个博客~这一期讲了递归、地址与数组初始化,关于指针的内容我想放在一个专题来讲,所以这节就简单介绍。
递归
递归(recursion)简单来说就是The function calls itself.即程序调用自身的编程技巧称为递归。
例如:
从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?"从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?'从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?…
构成递归需具备的条件:
1. 子问题须与原始问题为同样的事,且更为简单;
2. 不能无限制地调用本身,须有个出口,化简为非递归状况处理。
一般来说可以使用循环的地方通常都可以使用递归。有时用循环解决问题比较好,但有时用递归更好。递归方案更简洁, 但效率却没有循环高。
尾递归
最简单的递归形式是把递归调用置于函数的末尾,即正好在return语句之前。这种形式的递归被称为尾递归(tail recursion),因为递归调用在函数的末尾。尾递归是最简单的递归形式,因为它相当于循环。
我们看一个关于计算阶乘的问题:有这么一个函数
double factorial(unsigned int i)
{
if(i <= 1)
{
return 1;
}
return i * factorial(i - 1);
}
在每次递归后都以return结束当前次数的递归,可以减少对资源的消耗,提高效率。
递归与循环的选择
既然用递归和循环来计算都没问题, 那么到底应该使用哪一个?
一般而言, 选择循环比较好。
首先,每次递归都会创建一组变量, 所以递归使用的内存更多, 而且每次递归调用都会把创建的一组新变量放在栈中。递归调用的数量受限于内存空间。
其次, 由于每次函数调用要花费一定的时间, 所以递归的执行速度较慢。
但是在某些情况下, 不能用简单的循环代替递归。
递归和倒序计算
递归在处理倒序时非常方便(在解决这类问题中, 递归比循环简单)。
当你要倒序输出时,使用递归可以直接输出倒序。因为在递归判断结束时,会跳回上一次递归的下方语句,在这个地方添加输出就可以实现倒序输出了。
递归的缺点
前面也说了递归会耗费很多的内存,特别是双递归以上时,例如我们计算斐波那契数列。
int fibonaci(int i)
{
if(i == 0)
return 0;
if(i == 1)
return 1;
return fibonaci(i-1) + fibonaci(i-2);
}
由数学定义,斐波那契数列满足:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)
假设调用Fibonacci(40)。第1级递归调用, 将创建一个变量n。然后在该函数中要调用Fibonacci()两次,在第2级递归中要分别创建两个变量n。这两次调用中的每次调用又会进行两次调用, 因而在第3级递归中要创建4个名为n的变量。此时总共创建了7个变量。由于每级递归创建的变量都是上一级递归的两倍, 所以变量的数量呈指数增长,这样内存会被大大消耗,很容易导致程序崩溃。
简单来说就是:递归是个好东西,就是效率不行
编译多源代码文件的程序
我们知道,在终端中输入gcc filename.c可以编译一个.c文件,如果我们想一次性编译多个文件呢?
我们可以直接输入:gcc file1.c file2.c
将编译生成file1.o file2.o和a.out文件,如果我们之后想修改一个文件,例如file1,那么我们可以输入gcc file1.c file2.o
把它们拼在一起。
地址
我们知道在输入scanf()语句时,需要在变量前加上“&”,可&这是什么呢?因为每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符(称为取地址符)访问的地址,它表示了在内存中的一个地址。
更改主调函数中的变量
了解了什么是地址后,我们就可以来看以前困扰大家的简单问题,在一个函数中更改其他函数的变量。
例如, 普通的排序任务中交换两个变量的值。假设要交换两个变量x和y的值。
你可能第一步就想这么做:
x = y;
y = x;
但这明显是错的,因为在第一步赋值时x的值就已经改变了,y不可能被赋值为x的原始值。因此我们一般都会写一个其他变量来储存原始值:
temp = x;
x = y;
y = temp;
这时候我们可以怎么简化呢,就要引入指针(pointer)的学习了。
指针
指针是一个变量,其值为另一个变量的地址,即内存位置的直接地址。
就像其他变量或常量一样,必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
type *var-name;
在这里,type 是指针的基类型,它必须是一个有效的 C 数据类型,var-name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,*是用来指定一个变量是指针。
但是我们要知道,所有实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,对应指针的值的类型都是一样的,都是一个代表内存地址的长的十六进制数。
不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
具体关于指针的内容我们在专题中写。
数组初始化
数组初始化我们以前已经介绍过了很多种方法,在C99中 增加了一个新特性: 指定初始化器(designated initializer)。利用该特性可以初始化指定的数组元素。
例如, 只初始化数组中的最后一个元素。对于传统的C 初始化语法, 必须初始化最后一个元素之前的
所有元素, 才能初始化它:
int arr[6]={0,0,0,0,0,212};
而C99 规定, 可以在初始化列表中使用带方括号的下标指明待初始化的元素:
int arr[6]={[5] = 212} ;
对于一般的初始化, 在初始化一个元素后,未初始化的元素都会被设置为0。
声明数组
我们知道声明数组时在C99标准之前, 声明数组时只能在方括号中使用整型常量表达式。所谓整型常量表达式, 是由正整型常量构成的表达式。
在C99标准允许用变量声明, 这创建了一种新型数组, 称为变长数组(variable-length array)或简称VLA。但VLA有一些限制, 例如, 声明VLA时不能进行初始化等。
例如:
int n=5;
Comments NOTHING