目录
函数的定义
参考维基百科中的定义:是一个大型程序中的部分代码,由一个或者多个语句块组成,负责完成某项特定任务,相较于其他代码有相对独立性。
C语言中函数的分类:
库函数
如果只有自定义函数的话那么会出现一种什么情况呢?每个人对于那些常用函数的定义都不一样,而且效率也不高,就拿输入输出函数来说,如果每次写一个新的程序都要重新自定义一次的话是不是就很麻烦呢,于是库函数就出现了,C语言把频繁大量使用的功能封装到库函数里,你想使用接调用就好,就不用那么麻烦。
有一个网站可以很好的学习库函数(不过是英文的):http://www.cplusplus.com/reference/
拿strcpy举个栗子
1.在这个网站上搜strcpy
这里大概就能看出,strcpy有两个参数,前面那个参数是目的地,第二个是源头,就是说将第二个里的字符串克隆到第一个里。strcpy的返回值是目的地的起始地址,要打印的话也是可以直接链式访问的哦(好像剧透了)
#include <string.h>
#include <stdio.h>
int main()
{
char a[100] = { 0 };
char b[] = "hello world";
strcpy(a , b);
printf("%s", a);//这时候打印出来的就是hello world了。
return 0;
}
再记录一个memset函数(内存设置)只要内存里的连续空间都可以初始化
int main()
{
char a[] = "hello world";
memset(a, 'x', 5);//a代表要改变的数组的首地址,'x'则是要改成的东西,5则是改变前五个成x
return 0;
}
自定义函数
自定义函数的组成类型为,返回类型,参数,函数名,函数体。
话不多说,上代码(简单写一个实现找最大值的函数)
int get_max(int x, int y)//int是返回类型,get_max是函数名,int x和 int y是参数
{
int z = 0;
if (x > y)
z = x;
else
z = y;
return z;
}//大括号括起来的是代码的具体实现过程,函数体。
特殊:void,代表这个函数不返回任何值,也不需要返回。
一个函数如果没写返回类型默认返回int
写一个交换值的函数要传址调用,如果不传地址就代表只是把传过来的值复制来用,在自定义函数中改值对传过来的值本身不受影响。
#include <stdio.h>
void swap(int* a, int* b)
{
int z = 0;
z = *a;
*a = *b;
*b = z;
}
int main()
{
int a = 10;
int b = 9;
printf("交换前:a=%d b=%d\n", a, b);
swap(&a, &b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
传值改变不了,但通过指针变量解引用找到原来两个变量的地址就可以改变原来变量的值。
实参和形参
实际参数(实参):真实传递给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传递给形参。
形式参数(形参):
形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(也就是分配内存单元),所以叫形式参数。形式参数当函数调用完之后就自动销毁了。因此形式参数旨在函数中有效。(形参只是实参的一份临时拷贝,改变形参值对于实参是无影响的(当然传址不算改变形参值而是直接通过指针解引用找到实参的地址从而达到改变的效果))
函数的嵌套调用和链式访问
函数可以嵌套调用但是不能嵌套定义,所以需要单独定义哦~
嵌套调用
在一个函数中调用另一个函数叫嵌套调用,举个栗子:
#include <stdio.h>
void test2()
{
printf("lweakm\n");
}
int test1()
{
test2();
return 0;
}
int main()
{
test1();
return 0;
}
这个函数的执行过程是main-->test1-->test2然后再一节一节的返回main函数结束整个程序,最终再屏幕上打印出一个lweakm,这就是函数的嵌套调用。
链式访问
一个函数的返回值做另一个函数的参数叫链式访问,以printf函数举个栗子
非链式
#include <stdio.h>
#include <string.h>
int main()
{
int len = strlen("lweakm");
printf("%d\n", len);
return 0;
}
这里用到一个库函数strlen它所需的头文件是string.h,是用来计算字符串长度的,下面外面直接用它的返回值做printf的参数。链式访问:
#include <stdio.h>
#include <string.h>
int main()
{
printf("%d\n", strlen("lweakm"));
return 0;
}
最终的结果也是6,大家可以自己敲敲试试。
接下来给大家看一个比较有趣的链式访问代码
#include <stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
return 0;
}
大家觉得打印出来会是什么,想一想
没错没错没错434343,就是434343(ε=ε=ε=┏(゜ロ゜;)┛逃bushi,开个玩笑)
其实打印出来是4321,为什么呢?因为printf的返回值是打印出来的字符个数也就是说整个链式访问访问到最里面那个printf开始返回然后它打出来的是43,两个字符,于是中间那个打印2,而它的返回值就是1所以最外面那个函数打印的就是1了。
当然了,库函数可以链式访问,自定义肯定也可以。
自定义函数的链式访问
顾名思义就是自己先写一个函数再链式访问一下,直接上代码:
#include <stdio.h>
int Add(int a, int b)
{
return a + b;
}
int main()
{
int a = 10;
int b = 8;
printf("%d", Add(a, b));
return 0;
}
函数的声明
当然,自定义函数是可以放在主函数后面的,但是你要先声明一下有这个函数,但是声明不代表定义,即使声明了也只是说明有这个函数,但定义还是要自己定义
#include <stdio.h>
int main()
{
int a = 10;
int b = 8;
int Add(int, int);//这个就是函数的声明了
printf("%d", Add(a, b));
return 0;
}
int Add(int a, int b)
{
return a + b;
}
函数声明就是告诉编译器有一个函数叫什么,参数是什么,返回类型是什么
当然这上面介绍的比较少见啦(可以写前面省声明why还要声明,懒)
下面介绍的比较常见,把函数的声明放在头文件里:方便每个人负责不同模块然后集成到一个地方
简单弄一个,减法函数吧
在sub.c里定义一个减法函数再在sub.h中声明
引头文件之后这样你就可以在别的文件里使用这个函数了
我第一次写出来的时候超有成就感,这就是函数声明的妙用了,当然这个只是冰山一角,这种情况更推荐特别复杂的函数用。
函数的递归
递归是函数里比较难理解的地方,建议去多刷刷这方面的题,写的时候可以自己画画那个递归的实现过程,感受代码的运行,可以让你更快的掌握这个比较抽象的知识点。
所以什么是递归?
一个过程或函数在定义或说明中直接或间接调用自身的一种方法,它通常把一个大型复杂的问题转化为一个与原问题相似的规模较小的问题来求解,递归的策略是只需少量的程序就可以描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的主要思考方式在于:大事化小
递归的必要条件(不是充要)
存在限制条件,当满足这个限制条件的时候,递归便不再继续。
每次递归调用后越来越接近这个限制条件(但是递归层次太深可能就会造成死递归的局面)
先写一个最简单的递归(当然是错误示范,虽然这是递归但是是死递归)
#include <stdio.h>
int main()
{
printf("lweakm\n");
main();
return 0;
}
这个结果就是在屏幕上打印lweakm,但是打印到一定程度就会停止,因为递归已经跑死了,栈溢出了。
写一个代码按顺序打印无符号整型1234的每一位1 2 3 4,首先明确思想,最容易拿到的数是4,因为它1234%10就是,然后再1234/10得到123,再%10得3,依次打印下去最终得到1 2 3 4,思想是挺简单的,难的是实现的过程,接下来我将用递归写出这个代码。
#include <stdio.h>
void print(unsigned int x)
{
if (x > 0)
{
print(x / 10);
printf("%d ", x % 10);
}
}
int main()
{
unsigned int num = 0;
scanf("%u", &num);//u是无符号整形
print(num);
return 0;
}
x / 10是递归的跳出条件,递归到最后从1开始返回依次是2 3 4,最终打印出来的结果为1 2 3 4。如果对实现过程不太了解 的话建议去画图试试,相信自己一定能把递归这块硬骨头啃下来的。
语言层次的内存区域
刚刚有说到栈,就顺便简单介绍一下语言层次的内存区域分别放的都是什么吧。
1.栈区:局部变量,函数形参之类的临时变量。然后每一个函数调用都要在栈区上分配空间,所以递归层次太深的话容易栈溢出。
2.堆区:用来动态内存分配的,比如malloc/free,calloc,realloc等等(不知道不要紧,(我也不知道,雾)以后学到动态内存就知道了这里只是粗略地介绍一下。)
3.静态区:全局变量,静态变量。