维系C世界框架的英雄
今天是关于C语言函数的故事。我相信很多人,学习C语言的第一段代码就是:
#include <stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}
而从一开始,最重要的函数main(),就出现在了我们的世界。
函数是什么?
维基百科中对函数的定义:子程序
- 在计算机科学中,子程序(英语:Subroutine, procedure, function, routine, method,subprogram, callable unit),是一个大型程序中的某部分代码, 由一个或多个语句块组 成。它负责完成某项特定任务,而且相较于其他代码,具备相对的独立性。
- 一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。
函数的分类
- 库函数
- 自定义函数
库函数
- 库函数(Library function)是将函数封装入库,供用户使用的一种方式。方法是把一些常用到的函数编完放到一个文件里,供不同的人进行调用。调用的时候把它所在的文件名用#include<>加到里面就可以了。一般是放到lib文件里的。
- 如我们用的输入输出函数(scanf,printf),拷贝字符串的函数(strcpy),求x的y次幂函数(pow)等等,都是我们常用的库函数。
库函数大致分类(举了下列函数类别含有的一些常见函数)
- IO函数:printf(输出)、scanf(输入)
- 字符串操作函数:strcmp(字符串比较)、strlen(计算字符串长度)
- 字符操作函数:toupper(小写转大写)
- 内存操作函数:memcpy(内存拷贝)、memset(内存初始化)
- 时间/日期函数:time(时间)
- 数学函数:sqrt(开平方)、pow(求x的y次幂)
- 其他函数
我们该怎样学习库函数呢?
- 在日常学习C语言的过程中,我们要学会积累库函数
- 要学会并善于使用一些查询工具,如http://zh.cppreference.com或者www.cplusplus.com
自定义函数
自定义函数和库函数一样,有函数名,返回值和函数参数。是程序员根据需求自己定义的一种函数。
自定义函数的组成
return_type fun_name(para)
{
statement;
}
//return_type 返回类型
//fun_name 函数名
//para 函数参数
//statement 语句项
例子1:(写一个函数,求两个数较大值)
#include <stdio.h>
//得到较大值函数的设计
int get_max(int x, int y)
{
return (x>y)?(x):(y);
}
int main()
{
int a = 10;
int b = 20;
//函数的调用
int max = get_max(a, b);
printf("max = %d\n", max);
return 0;
}
//rereturn_type: int
//fun_name: get_max
//para: int x,int y
//statement: return (x>y)?(x):(y);
- 该函数就像是你打球累了,你想去喝水,你就让你(女)朋友去帮你买瓶饮料。(女)朋友买饮料就相当于函数名,而你告诉她,你要喝的饮料名字(还有给的钱),就相当于函数参数。你女朋友去买饮料的过程就相当于函数体的语句项。而她最后把饮料给你就相当于返回类型的值,至于其他函数可能因为返回值的不同,所以函数类型就是不同的,甚至还不需要返回值,这时函数类型就是(void)
例子2:(写一个函数,打印一个直角边长为n的等腰直角三角形)
#include <stdio.h>
void print(int n)
{
int i=0;
for(i=0;i<n;i++)
{
int j=0;
for(j=0;j<=i;j++)
{
printf("* ");
}
printf("\n");
}
}
int main()
{
int n = 10;
print(n);
return 0;
}
//rereturn_type: void
//fun_name: print
//para: int n
//statement: {int i=0...for(...)}
- 该函数就是无返回值的函数,故函数类型就用void。而如果你不写函数类型的话,其实默认返回类型就是int,但不建议这么做。
- 无返回值的函数就相当于你写作业一样,哦豁,你不会,你找小青蛙帮你写,但是你不要她教你方法,而是你要她帮你写,答案她就直接帮你搞定了,而不需要你自己再动笔\ (~ 、~)/
函数的参数
- 函数的参数可以分为实际参数和形式参数
- 实参可以与形参命名一样
- 不同函数形参的命名可以相同
实际参数(实参)
- 真实传给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
形式参数(形参)
- 形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在该函数中有效。
例子3:思考下面函数是否可行(交换两个整形变量a,b的值)
#include <stdio.h>
void Swap(int x,int y)
{
int tmp;
tmp=x;
x=y;
y=tmp;
}
int main()
{
int a=10;
int b=20;
Swap(a,b);
printf("%d %d",a,b);
return 0;
}
- 其中main()函数中Swap(a,b)的a,b就是实际参数
- void Swap(int x, int y)中的x,y就是形式参数
- 按思路,我们将a,b的值分别传给x,y。再通过变量tmp将x,y的值交换,这样子我们就将x,y的值交换了,对吧,然后a,b的值好像完全没做改变!!!感觉跟他没有任何的关系呀!!!所以不能传值
通过比较参数的地址我们追寻原因(通过调试——窗口——监视,找到参数地址)
- 通过上图我们知道,a,b与x,y的值没有任何联系,因为他们每个本身的地址都是不相同的。
- 但却可以将a,b的值传递给x,y,故我们可以认为:形参实例化之后其实相当于实参的一份临时拷贝。
至于我们怎样去将例3的代码修改正确呢,首先我们要们要知道什么是传值调用,什么是传址调用
函数的调用
- 计算机编译或运行时,使用某个函数来完成相关命令。对无参函数调用时则无实际参数表。实际参数表中的参数可以是常数、变量或其它构造类型数据及表达式。各实参之间用逗号分隔。
- 函数调用分为:传址调用和传址调用
- 函数可以实行嵌套调用,但不能嵌套定义
传值调用
- 把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数
- 函数的形参和实参分别占有不同内存块
大家可以参考例1,例2,以及错误的例3来理解
传址调用
- 传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
- 这种传参方式可以让函数和函数外边的变量建立起正真的联系,也就是函数内部可以直接操作函数外部的变量
显而易见,例3是通过传值调用,故形参不能去改变形参的值。接下来,通过传址调用,修改例3
#include <stdio.h>
void Swap(int* x,int* y)
{
int tmp;
tmp=*x;
*x=*y;
*y=tmp;
}
int main()
{
int a=10;
int b=20;
Swap(&a,&b);
printf("%d %d",a,b);
return 0;
}
- 改变1:将Swap中a,b的值,改成其地址,即是将地址传给形参
- 改变2:函数形参改用指针来接收实参传来的地址
- 改变3:函数体的语句通过指针来将*x , *y的值交换
我们继续通过地址追寻这其中的不同 (通过调试——窗口——监视,找到参数地址)
因为指针变量x,y分别存放着a,b的地址,故解引用时,其实是将指针变量x,y地址存放的值互相交换。虽然a,b变量的地址没有改变,但是其地址存放的值发生了交换。故a,b发生交换。
结语
函数的故事还为结束,下一章C世界函数的故事(2)我们将继续深入了解函数,如果你喜欢我讲的故事,可以点赞,收藏,关注我哦!!!