函数的格式
ret_type fun_name(para )
{
statement;//语句项
}
ret_type 返回类型
fun_name 函数名,注意的是 函数名与函数名不可重复
para1 函数参数 可以有一个或多个
{ }里面的执行语句也就是函数体,是函数实现特定功能的过程
函数的格式一般分为三大类
1.无参数,无返回值
如:
void 函数名()
{
要实现的功能的代码;
}
2.有参数,无返回值
如:
void 函数名(形式参数)
{
要实现的功能的代码;
}
3.有参数,有返回值
如:
int 函数名(形式参数)
{
要实现的功能的代码;
return 要返回的值;
}
要注意的是:一个函数只能返回一个数值
返回值类型
函数的返回值是指函数被调用之后,执行函数体中的代码所得到的结果,这个结果通过 return 语句返回。
而 需要注意是 return 语句可以有多个,可以出现在函数体的任意位置,但是每次调用函数只能有一个 return 语句被执行,所以只有一个返回值。
最常见的类型如 :
char——字符型
double——浮点型
int——整型
void——空类型
为了使程序可读性更高,一般不要求返回功能的函数,都应定义成void空类型
函数的参数
函数的参数分为,实际参数 和 形式参数
真实传给函数的参数,叫实际参数。实参可以是:常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。
值得注意的是:
1.每个函数中的形参的命名都只属于该函数,且与其他函数中的形参命名相同时,也不会有冲突;
2.但每个函数中可以有多个形参,在其函数中不可重命名;
3.参数互相传递之间的类型必须相同;
4.形参是存储在栈区的;
5.实参位置需要根据具体怎么用来确定,比如全局变量静态变量与局部变量的存储就不相同,全局变量和静态变量(static)就是存储在静态区,局部变量与形参(临时变量)就存储在栈区。
函数的分类
库函数
库函数是系统建立的具有一定功能的函数的集合,如:“Hello world”使用的printf函数、 求字符串长度的strlen函数等。
库函数增加了代码的可移植性,可以拿来直接使用,这样能够节省开发成本,提高开发效率,并且库代码的执行效率、严谨性、安全性和规范性要明显优于我们自己编写的代码
简单的总结,C语言常用的库函数都有:
IO函数
字符串操作函数
字符操作函数
内存操作函数
时间/日期函数
数学函数
其他库函数
如何学习库函数
由于库函数有很多,平时也不一定常用到,可以通过www.cplusplus.com 这个网站可以帮助我们学习库函数相关的知识。
如图所示在最上方搜索我们要学习的函数,会显示相应的知识,如引用什么头文件,返回值类型,和参数类型,具体的功能等。
所以平时最好通过查阅来自主学习,要善于利用这些库,尽量不要重复造轮子。
注: 但是库函数必须知道的一个秘密就是:使用库函数,必须包含 #include 对应的头文件。
自定义函数
自定义函数和库函数一样,有函数名,返回值类型和函数参数。 但是不一样的是这些都是我们自己来根据具体需求所设计的。通过函数封装可重复使用的代码块,从而节省代码数,但注意不要重复造轮子
如何自定义函数
首先根据功能确定返回值类型 和 函数名,之后确定函数形参,最后在函数体里编写所要实现功能的语句
如:比较两个数的大小
int max(int a,int b) //返回值类型 函数名(函数形参)
{ //{
return (a>=b)?a:b; // 如果表达式1的值为真,执行表达式2,并返回表达式2的结果;
} //}
函数的声明及定义
函数声明:
- 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,无关紧要。
- 函数的声明一般出现在函数的使用之前。要满足先声明后使用。
- 函数的声明一般要放在头文件中的(在使用模块化编程的时候必须放在头文件中)。
- 注意有些声明情况需要加符号 ‘ ;’
函数定义: 函数的定义是指函数的具体实现,交待函数的功能实现;如上述自定义函数中(max函数),所根据功能确定的返回值类型、函数名、函数参数和函数体中功能实现语句就可看作为函数定义
例如:
int get_max(int x, int y); //函数的声明,注意符号不能丢
int main()
{
int a,b,i;
scanf("%d,%d",&a,&b);
i = get_max(a,b); //在使用函数前要先声明
printf("%d\n",i);
return 0;
}
//函数的定义
int get_max(int x, int y)
{
return (x>=y? x:y);
}
函数的调用
我们在调用一个函数时,需要向这个函数传其需要的参数,分为两种情况 :
传值调用
函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
传址调用
传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
这种传参方式可以让函数和函数外边的变量建立起正真的联系,也就是函数内部可以直接操作函数外部的变量
传值调用
如:交换两个变量的内容
void exchange(int x,int y)
{
int temp = 0;
temp = x;
x = y;
y = temp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d",&a,&b);
exchange(a,b);
printf("%d %d\n",a,b);
return 0;
}
这个代码虽然调用函数,但由于形参是临时分配的内存,当调用结束后内存销毁,主函数中的 a和b的内容仍然没被改变。
说明:当函数被调用时,形参只是实参的一份临时拷贝
传址调用
继续实现交换两个变量的内容
void exchange(int *x,int *y)//x存的是a的地址
{ //&x才是x的地址
int temp = 0;
temp = *x; //*x 就是通过a的地址找到a的内容
*x = *y; //因为地址是相同的,所以可以对其内容进行更改
*y = *temp; //
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d",&a,&b);
exchange(&a,&b);
printf("%d %d\n",a,b);
return 0;
}
这个代码调用函数时,传递的是内存地址,函数接收的则是a和b的内存地址,通过直接对地址的操作来更改其内容
嵌套调用
函数和函数之间可以互相组合
例如通过比较找出输入的4个数中最大的数
int git_max(int x, int y)
{
return (x>=y? x:y);
}
int Max_4(int a,int b,int c,int d)
{
int temp1,temp2;
temp1 = git_max(a,b);
temp2 = git_max(c,d);
return git_max(temp1,temp2);
}
int main()
{
int a,b,c,d,max;
scanf("%d %d %d %d",&a,&b,&c,&d);
max = Max_4(a,b,c,d);
printf("%d\n",max);
return 0;
}
上述代码,通过求4个数的最大值而调用Max_4函数,而Max_4函数调用了3次git_max实现了比较4个数的大小功能
但注意的是,函数可以嵌套使用,但不能嵌套定义(不能在函数里定义函数)
链式访问
链式访问:把一个函数的返回值作为另外一个函数的参数
链式访问的前提条件是函数得有返回值
示例:
int main()
{
printf("%d\n",strlen("abcdef"));
//打印求字符串的长度的函数 结果为6
printf("%d\n",printf("%d\n",printf("%d\n",43)));
//printf 返回的是打印的字符的个数,所以最后的值是43 2 1
return 0;
函数递归
什么是递归?
程序调用自身的编程技巧称为递归
就是在运行的过程中不断地调用自己。递归有两个过程,一个是递的过程,一个是归的过程。
递归的两个必要条件
- 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
- 每次递归调用之后越来越接近这个限制条件
示例1: 输入1234 打印 1 2 3 4
void print(int i)
{
if(i>9)
print(i/10); //1234/10=123
printf("%d ",i%10); //1234%10 = 4
}
int main()
{
int i = 0;
scanf("%d",&i);
print(i);
return 0;
}
个人思路:
- 先试想最外层最后执行打印功能还是先执行打印功能
- 确定怎么“递”(也就是不断缩小范围)
- 最后确定什么时候“归”,“归”的条件是什么(限制条件)
示例2: 不创建临时变量实现strlen功能
int My_strlen(char temp[])
{
if(temp[] == '\0')
return 0;
return 1+(temp+1);
}
int main()
{
char ch[] = "abcde";
int i;
i = My_strlen(ch);
printf("%d\n",i);
return 0;
}
个人思路:
- 先确定功能:最外层先计1
- 确定以地址+1给下一次调用当作首地址的递方式
- 确定读到 \0 就返回的归方式(限制条件)
示例3: 求n的阶乘
int f(int temp)
{
if(temp == 0)
return 1;
return n*(n-1);
}
int main()
{
int i,j;
scanf("%d",&i);
j = f(i);
printf("%d\n",j);
return 0;
}
个人思路
- 先确定最外层要执行什么功能 如 求5! 最外层就是5*5-1…
- 确定怎么递 :f()函数就是用来求阶乘的 所以就传递比上一层减一 f(5-1)
- 归的条件:最低到0退出并返回0的阶乘 即1
示例4:计算一个数的每位之和(正整数) 如 123 就是 6
int add(int temp)
{
if(temp < 9)
return temp;
return temp%10 + (temp/10);
}
int main()
{
int i;
scanf("%d",&i);
printf("%d\n",add(i));
return 0;
}
个人思路:
- 最外层把个位数的数提出来加上下一次调用
- 递:舍去个位数
- 归:剩个位数的时候返回本身的参数
示例5: 递归实现n的k次方
int f(int n,int k)
{
if(k == 1)
return n;
return n*f(n,(k-1));
}
int main()
{
int i,j;
scanf("%d%d",&i,&j);
printf("%d\n",f(i,j));
return 0;
}
此题与求阶乘大同小异
**示例6:**字符串逆序(递归实现)
void reverse_string(char * string)
{
int end = strlen(string)-1;
int first = 0;
char temp = 0;
temp = string[first];
string[first] = string[end];
string[end] = '\0';
if(string[first+1] != 0)
reverse_string(string+1);
string[end] = temp;
}
int main()
{
char string[] = "abcde";
printf("%s\n",string);
reverse_string(string);
printf("%s\n",string);
return 0;
}
个人思路:
- 先确定最外层的功能:首尾互相赋值
- 递:地址+1;根据递的过程,补充最外层的功能:把最后一个元素先临时给结束符 \0
- 归:首元素+1或者尾元素-1 = 结束符(先用偶个数和奇个数试运行)
由上几例可知:
- 每次调用都在栈区生成随机内存地址,而每层的形参都属于该层,所以尽管名字相同,但是内存地址不同,传递的数据也不同
- 形参只存于在该函数体内的范围
- 注意内存溢出
总而言之:想要学好递归的必要条件就是 不断接近 和 限制条件可以先把不是递归的试着写出来捋捋思路,不要刻意去捋清全过程,只去思考递跟归两个过程,两层之间的递归交接,以及终结条件
总结
- 定义函数名时不可重复
- 函数参数 可以有一个或多个,但不宜过多
- 在不同的函数中,使用相同名字的变量或参数不受影响
- 局部变量能和全局变量重名,局部会屏蔽全局,要用全局变量,需要使用"::",但不要过多的使用全局变量
- 函数形参存储在栈区,形参被调用时会临时创建,调用完成后销毁
- 当函数被调用时,形参只是实参的一份临时拷贝
- 每个函数中return可以有多个,可以出现在函数体的任意位置,但是每次调用函数只能有一个 return 语句被执行,所以只有一个返回值。
- 库函数可直接拿来使用,www.cplusplus.com 可以帮助我们学习库函数相关的知识
- 自定义函数应高内聚低耦合,尽量要功能独立
- 自定义函数时要避免重复造轮子,但是使用库函数,必须包含 #include 对应的头文件
- 函数要满足先声明后使用
- 函数的声明一般要放在头文件(.h的文件)中的(在使用模块化编程的时候必须放在头文件中)。
- 函数可以嵌套调用,不可以嵌套定义
- 递归的必要条件是 不断接近 和 限制条件
- 递归每次调用都在栈区生成随机内存地址,而每层的形参都属于该层,所以尽管名字相同,但是内存地址不同,传递的数据也不同
- 想要学好递归重点在于,只去思考递跟归两个过程,两层之间的递归交接,以及终结条件
如有理解不当地方,欢迎指导与点评