以个人理解为主体的C语言学习理解梳理记录,注重于更好的理解一些概念,肯定有说的不太对的地方,还望在包涵的时候帮我指出一下错误之处!感激不尽!
目录
初学C语言的时候,我们通常将我们需要运用编程所解决的问题写在main函数中,但是全部屯在main函数里不仅会使得代码变得冗长,还会因为过于集中影响我们对代码逻辑的梳理也不易阅读,所以对一些单一功能的实现我们可以专门定义一个函数来实现。
就好像我们想制批量作可乐,我们有了绝大部分的生产机器也有生产物资,可是缺少了关键的糖浆,那么我们可以找一家加工厂,让他们帮我们专门生产糖浆。函数就好像一家专业对口的加工厂,我们可以让其实现特定功能而不用全部堆在main函数里头。
函数定义的类型
那么该怎么定义一个函数,我们又该怎么去使用它呢?
首先函数的声明要在main之前
可以在头文件里面声明与定义,在源文件里应用
函数的声明有两种形式
1.不返回值
void function()
这样声明的函数是没有返回值的,所以在声明的时候在函数名称前加上void即可。
2.返回类型值
int function()
{
return 0;
}
当声明的一个函数需要返回值的时候,我们需要确定这个返回值的类型是什么,如上,函数返回了一个0,那么我们声明时需要指定函数的返回值类型是整形。
注意!函数返回值的时候只能返回一个,我们可能会想到能不能写下return 2,3来返回两个值,很显然,逗号表达式只生效于最后一个参数没法返回两个值,并且函数本身也不支持其他形式的返回多个值。
函数的使用与传参
了解了函数的声明方式后就是使用的方法了,我们先借由函数实现一个简单的功能,比如简单的两个变量交换值。
于main函数中,这项功能非常容易实现,无非是交换一瓶可乐和芬达的事情,但是直接倒进去是完全不行的,这简单,拿个空瓶就好。
int main()
{
int coke = 1;
int fanda = 2;
int bottle = 0;
bottle = coke;//把可乐倒进空瓶
coke = fanda;//芬达倒进已经空了的可乐瓶
fanda = bottle;//把瓶子里的可乐倒进芬达瓶子
printf("%d %d", coke, fanda);
return 0;
}
芬达就成功的灌倒可乐瓶里了。
那么回到函数上来,这很简单,如法炮制就好了。
我们先创建一个函数,称为exchange()
void exchange()//错误示范
{
bottle = coke;
coke = fanda;
fanda = bottle;
}
这样写是不对的,因为根本没有参数传进去!就好像工厂没有进料口一样,所以我们要在括号内声明我们要丢进这个加工厂的“原料类型”是什么
coke的变量类型是整形,那么好办,我们就把“进料口”也设置为int
void exchange(int coke ,int fanda)//形参的名字不必一样,随意取名也可以。
{
int bottle = 0;
bottle = coke;
coke = fanda;
fanda = bottle;
}
接下来是main函数里调用exchange函数
int main()
{
int coke = 1;
int fanda = 2;
int bottle = 0;
exchange(coke,fanda);
printf("%d %d", coke, fanda);
return 0;
}
运行结果如下:
奇怪的是,我们的加工厂仿佛做了无用功一样,完全没有交换我们的饮料,这是为什么?
形参与实参
原因其实很简单,在exchange()内被交换参数的其实并不是main函数里的实参coke和fanda,而是形式参数coke和fanda。
形式参数:当实参传递给形参的时候,形参是一份临时拷贝,而存放这份拷贝的,就是“进料口”int coke
简单来讲,在exchange里被交换的,只是一份拷贝而已,真正所需要被交换的参数还在main里面,拷贝被交换了,本体自然是没有受到任何影响。
那这样子的函数根本没有用啊!我们该怎么让它去改变我们的实参来达成我们所需要的功能呢?
既然函数拿的是传入实参的一份拷贝,那么我们不如传给函数一个可以直接改变实参的东西:实参的地址。
在程序运行的时候,每次声明一个变量都会在栈区为这个变量开辟一段内存空间,就好像开启了存储仓库一样以便于存放数据,但是开了仓库不知道东西放在哪个仓库里就很尴尬了,所以同样的,编译器会为变量生成对应的地址,以方便访问,可以理解为这个变量的门牌号。
所以当我们把实参的地址传过去时,函数就可以顺着地址找到实参本人,运算的就是地址所指向的实参了!
void exchange(int*pcoke ,int*pfanda)//接收传参的时候,传过来的地址用指针变量接收
{
int bottle = 0;
bottle = *pcoke;//由于指针变量所指向的变量才是真正的实参,此处应继续使用指针
*pcoke = *pfanda;
*pfanda = bottle;
}
int main()
{
int coke = 1;
int fanda = 2;
int bottle = 0;
exchange(&coke,&fanda);//在传参的时候取实参的地址,使用取地址操作符&
printf("%d %d", coke, fanda);
return 0;
}
运行结果如下
总结一下,以上使用了两种函数的调用方式:1.传值调用 2.传址调用
传值调用:函数的形参和实参分别占有不同的内存块,形参的改变不会改变实参
传址调用:将函数与函数外的变量建立真正联系,使得变量可以真正的改变,也就是函数内部可以改变函数外部的变量。
所以有个小问题:此时若是需要声明函数帮助运算,什么时候要用指针什么时候不用呢?
当需要改变要传进函数里的数据时,应该使用指针
当只是需要函数帮忙计算值,不需要改变传进去的函数时就不需要指针
数组传参的时候,只是传过去了首元素的地址,防止空间的浪费。就不需要很大的一份数组拷贝了,我们将会在之后讨论数组的时候来讲述数组的函数传参。
递归概述
递归是什么?
递归,指的是程序直接或间接调用自身的编程技巧,只需要少量的程序就可以完成一些大量多次重复计算。
在正式介绍递归前,先罗列两个使用递归时最基本的原则。
1.递归一定要有限制条件,使得递归停止
2.每次递归都一定要让整个函数向限制条件靠近
那我们就来看看递归是怎么运作的
有如下问题:
接受一个整型值(无符号),按照顺序打印它的每一位。
也就是输入1234,打印1 2 3 4
无符号:unsigned 通常指定整形,在unsigned定义下的整形只能是正数,这是因为用于存储int类型变量是否为正负的bit也被用来存放变量数据了,也正因如此,用unsigned定义下的整型变量也可以容下更大的数值。
基本思路如下:
1234除以10得到123,1234%10得到个位4
那么我们让输入的数字每除以10就模10,以得到每一位数。
程序如下:
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;
}
程序运行结果如下
在print函数中,每一次运行都会再次调用一次print函数自己,每一次传进去的参数都是n/10,并且在n>9也就是只剩一位数的时候不在进入print函数自身 ,在递归结束后,再执行打印程序,每一次打印都是当前n10模10所取的个位数。
乍一看可能难以理解,但是复杂的问题可以通过拆解来简化,我们可以尝试画图来了解
没有选择嵌字,字迹有些潦草还望见谅。
由此,我们可以看到,递归的基本运行逻辑,这玩意需要一些巧劲,刚刚接触可能难以理解,还是需要多尝试实现才可以更好的掌握这种编程方法,其中也有不少更多的递归方法,比如return一个表达式来实现递归。
具体的题目在此不做细述。