所有程序在vs2019中调试,如有不同,洗耳恭听!
文章目录
目录
目录
前言
相信大家在学习c语言的过程中经常遇到一些函数,那么这些函数该怎么用,如何写,返回值是什么意思,没有返回值咋办,一文解惑
提示:以下是本篇文章正文内容,下面案例可供参考
一、函数是什么
百度的解释:子程序
乐言的解释:函数也叫子程序,是大型程序中多个代码,由一个或者多个语句组成,他负责完成某项特定任务,相较于其他代码,具有一定的独立性,灵活性
二、c语言中的函数分类
1.库函数:
c语言的编译器提供了一些库函数
例如:printf,scanf,strlen.......
为啥会有库函数?:
如果每个人都写自己的函数,那么开发效率可能太低,不够标准,容易出bug,所以c语言的标准做了一些工作,所以编译器的厂商总结提炼归纳了库函数,使开发更加报标准,效率变高。
库函数的使用要包含对应的头文件
2.自定义函数
既然我们已经拥有了库函数,那么我i吗还需要自定义函数做啥呢?
但是库函数毕竟是有限的,我们该如何自己实现一个函数呢?自定义函数给了程序员一个很大的开发空间,具有极高的自由度
函数的组成:
ret_type fun_name(para1, * ) { statement;//语句项 } ret_type 返回类型 fun_name 函数名 para1 函数参数
浅浅举个例子:
用一个函数实现找出两个数的最大值
int max(int a,int b) { sc if(a>b) { return a; } else { return b; } }
此处用的是int意味着返回值是int类型,简单运用了一个if,else语句实现此函数。相信大家已经对函数有个初步的了解,那么接下来在看一个例子:
一个函数实现两个数的调换
我们来看以下代码:
void Swap1(int x, int y) { int tmp = 0; tmp = x; x = y; y = tmp; }
貌似我们引用了第三个变量tmp,使得x,y的位置互换了一波,天衣无缝,完美,但实则不然!!!
我们将此代码加上main函数来f5运行一下代码我们会惊奇的发现:
这是一个悲伤的故事,那么我们将如何a,b的值换位呢???
先给大家看下代码
#include<stdio.h>
void Swep2(int*x, int*y)
{
int tmp = 0;
tmp = *x;
*x = *y;
*y = tmp;
}
//
int main()
{
int num1 = 1;
int num2 = 2;
Swep2(&num1, &num2);
printf("Swap2::num1 = %d num2 = %d\n", num1, num2);
return 0;
}
此时我们运行起来调试:
那么究竟是怎么回事呢?
这就要讲到两个概念,形式参数和实际参数了
1)实际参数:
是在调用时传递给函数的参数. 实参可以是常量、变量、表达式、函数等, 无论实际参数是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。 因此应预先用赋值,输入等办法使实参获得确定值。
2)形式参数:
形式参数只有在函数被调用的过程中才会被分配内存单元,当函数调用完成后会自动销毁,所以他只在函数内有效。形式参数是函数被调用时用于接收实参值的变量。根据实际需要可有可无。没有形参时,圆括号也不可省;多个参数之间应用逗号分隔。参数包括参数名和参数类型。
那为什么上述两种办法有如此大差别呢?
- 第一种被称为传值调用,第二种被称为传地址调用,在传值时,形参将在内存中开辟独立的空间,而此时形参实例化之后只是实参的一份临时拷贝,对形参的修改本质上不影响实参。
- 第二种叫传址调用,传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。
3.嵌套调用
函数内在加上其他函数,例如:
#include <stdio.h> void new_line() { printf("hehe\n"); } void three_line() { int i = 0; for(i=0; i<3; i++) { new_line(); } } int main() { three_line(); return 0; }
4.链式访问
将一个函数的返回值作为另一个函数的参数
例如:
#include<stdio.h> #include<string.h> int main() { printf(“%d”,strlen(“abcdef”)); }
这是一个极其简单的链式访问。
下面有一个稍微复杂的链式访问,大家来瞧瞧
#include<stdio.h> int main() { printf("%d",printf("%d",printf("%d",43))) }
我们知道printf汉书返回值是打印在屏幕上字符数的个数因此,我们可以知道
结果是“43 2 1”!!!!
看到这里大家是不是对链式访问和嵌套调用有所理解了呢。
5.函数的声明
当函数在main主函数后面时,因为编译器是从前往后扫描的,会出现函数名未解析,所以我们要将函数提前声明,例如
int swep(int,int)
//或者
int swep(int x,int y)
这里引用的还是上文的例子!!!
6.函数递归!!!!
1)递归的定义:
长话短说,用笔者的话来说,递归的主要思想:把大事化小
用少量的程序,实现多步骤计算
2)递归的两个重要条件:
原则1:存在限制条件,当满足此体哦阿健是,递归将不再继续
原则2:每次递归时将接近那个限制条件
如果不满足上述条件,那么兄弟,你的递归必错无疑(中肯的,客观的)
给大伙来个小例子
史上最简单的递归程序:
#include<stdio.h>
int main()
{
printf("你好坏");
main();
return 0;
}
当然这个程序最终会崩掉,因为他违反了基本原则
最后只能变成这样:
完了,崩溃了吧哈哈哈哈哈哈哈
stack overflow的意思是栈溢出,我们知道每次函数调用都会在栈区中申请空间,而当栈区不够分配时,就会出现栈溢出的情况(类似上述情况)
是这样的
例如:我们需要挨个打印数字
例输入:1234 打印:1 2 3 4
我们此时可以发现一个递归的好办法
可以转化为:printf(1234)
printf(123)4
printf(12)34
printf1234
由此可以看出代码应该如下操作:
void PRINT(int a)
1{
if(a>10)
printf(“%d”,a);
else
{
PRINT(a/10);
printf("%d",a%10);
}
}
简单轻松,用少量代码,实现大量运算是递归的好处(其实就是函数自己套自己)
总结
好了,大差不差该结束了,如果兄弟们有需要可以看一下下篇:函数栈帧的创建和销毁
看懂之后,你的函数功法讲修炼小成!!
什么,你想知道咋才能大成???
刷题去吧(偷笑,大成非一日之功)