C函数
函数
1. 为什么需要函数
1.1 需求
- 输入两个数,再输入一个运算符(+,-,*,/),得到结果
- 使用传统方式来解决
- 代码冗余(即有过多重复的代码)
- 不利于代码的维护
- 引出函数
1.2 使用传统方式解决
- 简单的说就是在需要执行计算时,将这段完成计算任务代码复制即可
2 解决方法-函数
- 为完成某一特定功能的程序指令(语句)的集合,称为函数
- 在C语言中,函数分为:自定义函数,系统函数
- 函数还有其他叫法,比如方法
3 .函数的定义
3.1 基本语法
返回类型 函数名(形参列表){
执行语句...;//函数体
return 返回值;//可选
}
- 形参列表:表示函数的输入
- 函数中的语句:表示为了实现某一功能代码块
- 函数可以有返回值,也可以没有,如果没有返回值,返回类型声明为 void
3.2 快速入门
#include<stdio.h>
//说明
//1.函数名 cal
//2.有返回值double
//3.形参列表(int n1,int n2,char oper)
//4.在函数中,我们使用的变量名需要和列表中的变量名一样
double cal(int n1,int n2,char oper){
//定义一个变量res保存运算的结果
double res = 0.0;
switch(oper){
case '+':
res = n1+n2;
break;
case '-':
res = n1-n2;
break;
case '*':
res = n1 * n2;
break;
case '/':
res = n1 / n2;
break;
fefault:
printf("你的运算符有误~");
}
printf("%d %c %d=%.2f",n1,oper,n2,res);
return res;
}
void main(){
int num1=10;
int num2=20;
double res = 0.0;
char oper = '-';
int num3 = 60;
int num4 = 80;
double res4=0.0;
char oper2='*';
printf("使用函数来解决计算任务~~");
res = cal(num1,num2,oper);//调用函数,使用函数
printf("cal函数返回的结果 res=%.2f",res);
res2=cal(num3,num4,oper2);
printf("函数返回的结果res2=%.2f",res2);
//传统方法
switch(oper2){
case '+':
res2 = num3 + num4;
break;
case '-':
res2 = num3 - num4;
break;
case '*':
res2=num3 * num4;
break;
case '/':
res2=num3/num4;
break;
default:
printf("你的运算符有误");
}
printf("%d %c %d=%.2f",num3,oper2,num4,res2);
getchar();
}
4.头文件
4.1 需求
- 在实际的开发中我们往往需要在不同的文件中,去调用其他文件的定义的函数,比如hello.c中,去使用myfuns.c文件中的函数,如何实现--头文件
4.2 基本概念
- 头文件是扩展名为 .h的文件,包含了C函数声明和宏定义,被多个源文件中引用共享,有两种类型的头文件:程序员编写的头文件和C标准库自带的头文件
- 在程序中要使用头文件,需要使用C预处理指令#include来引用它,前面我们已经看过 stdio.h 头文件,它是C标准库自带的头文件
-
include叫做文件包含命令,用来引入对应的头文件(.h文件)。#include也是C语言预处理命令的一种。#include的处理过程很简单,就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成一个源文件,这与复制粘贴的效果相同,但是我们不会直接在源文件中复制头文件的内容,因为这么做很容易出错,特别在程序是由多个源文件组成的时候
- 建议把所有的常量、宏、系统全局变量和函数原型写在头文件中,在需要的时候随时引用这些头文件
4.3 工作原理图
hello.c 源文件
include "myfun.h" //引入头文件
- 可以使用头文件中声明的函数
- 便于管理和维护
myfun.h和myfun.c 文件名可以不相同,建议从规范上来讲,最好一样
4.4 头文件快速入门
说明:头文件快速入门-C程序相互调用函数,我们将cal声明到文件myFun.h,在myFun.c中定义cal函数。当其他文件需要使用到myFun.h声明的函数时,可以使用#include该头文件,就可以使用了
myfun.h
#include<stdio.h>
int myCal(int n1,int n2,char oper);
void sayHello();
myfun.c
#include<stdio.h>
//实现int myCal(int n1,int n2,char oper)
int myCal(int n1,int n2,char oper){
//定义一个变量res保存运算的结果
double res = 0.0;
switch(oper){
case '+':
res = n1 + n2;
break;
case '-':
res = n1 - n2;
break;
case '*':
res = n1 * n2;
break;
case '/':
res = n1 / n2;
break;
default:
printf("你的运算符有误");
}
printf("%d %c %d=%.2f",n1,oper,n2,res);
return res;
}
void sayHello(){
printf("say Hello");
}
hello.c
#include<stdio.h>
//引入需要的头文件
#include "myfun.h"
void main(){
//使用myCal完成计算任务
int n1 = 10;
int n2 = 50;
char oper ='-';
double res = 0.0;
//调用myfun.c中定义的函数mycal
res = myCal(n1,n2,oper);
printf("res=%.2f",res);
sayHello();
getchar();
}
4.5 头文件注意事项和细节说明
-
引用头文件相当于复制头文件的内容
-
源文件的名字可以不和头文件一样,但是为了好管理,一般头文件和源文件名一样
-
C语言中include<>与include""的区别
- include<>:引用的是编译器的类库路径里面的头文件,用于引用系统头文件
- include"":引用的是你程序目录的相对路径的头文件,如果在程序目录没有找到引用的头文件则到编译器的类库路径下的目录找该头文件,用于引用用户头文件
-
所以:
- 引用系统头文件,两种形式都可以,include<>效率高
- 引用用户头文件,只能使用include""
-
一个#include命令只能包含一个头文件,多个头文件需要多个#include命令
-
同一个头文件如果被多次引入,多次引入的效果和一次引入的效果相同,因为头文件在代码层面有防止重复引入的机制
-
在一个被包含的文件(.c)中又可以包含另一个头文件(.h)
-
不管是标准头文件还是自定义头文件,都只能包含变量和函数的声明,不能包含定义,否则在多次引入时会引起重复应以错误
5.函数调用机制
5.1 如何理解
- 程序员调用方法,给方法必要的输入,方法返回结果
5.2 函数调用过程
//传入一个数+1
void test(int n){
int n2 = n+1;
printf("n2=%d",n2);
}
void main(){
int num = 6;
test(num);
getchar();
}
//计算两个数,并返回
int getSum(int n1,int n2){
return n1+n2;
}
void main(){
int res = getSum(1,9);
printf("res=%d",res);
printf("okook");
getchar();
}
- 函数调用规则(适用于java c++ php)
- 当调用(执行)一个函数时,就会开辟一个独立的空间(栈)
- 每个栈空间是相互独立
- 当函数执行完毕后,会返回到调用函数位置,继续执行
- 如果函数有返回值,则将返回值赋给接收的变量
- 当一个函数返回后,该函数对应的栈空间也就销毁
5.3 举例说明
char *getSum(int num1,int num2){
int res = num1+num2;
return res;
}//错误,类型不匹配
double getSum(int num1,int num2){
int res = num1+num2;
return res;
}//int --->到double自带转换
int getSum(int num1,int num2){
int res = num1+num2;
return res;
}
int getSum(int num1,int num2){
int res = num1+num2;
return 0.0;
}//double--->int有精度损失
int getSum(int num1,int num2){
int res = num1+num2;
return (int)0.0;
}
6.函数递归调用
6.1 基本介绍
- 一个函数在函数体内又调用了本身,称为递归调用
6.2 快速入门
void test(int n){
if(n>2){
test(n-1);
}
printf("n=%d",n);
}
void test(int n){
if(n>2){
test(n-1);
}else{
printf("n=%d",n);
}
}
6.3 函数递归调用原则
- 执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
- 函数的局部变量是独立的,不会相互影响
- 递归必须向退出递归的条件逼近,否则就是无限递归
- 当一个函数执行完,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁
6.4 练习
//斐波那契数
//请使用递归的方式,求出斐波那契数1,1,2,3,5,8,13.....
//给你一个整数n,求出它的斐波那契数是多少
//分析:1.如果n=1,n=2时,返回1
//2.从n=3开始,对应的斐波那契数是前面两个数的和
#include <stdio.h>
int fbn(int n){
if(n==1||n==2){
return 1;
}else{
return fbn(n-1) + fbn(n-2);
}
}
//求函数值
//已知f(1)=3,f(n)=2*f(n-1)+1,请使用递归的编程思想,求出f(n)的值?
int f(int n){
if(n==1){
return 3;
}else{
return 2*f(n-1)+1;
}
}
//猴子吃桃子问题
//有一堆桃子,猴子第一天吃了其中的一半,并再多吃一个,以后每天猴子都吃其 //中的一半,然后再多吃一个,当到第十天时,想再吃时,发现只有一个桃子了,
//问题:最初共多少个桃子?
//分析:
// 1. day = 10有一个桃子
//2.day=9有 (day10+1)*2=(1+1)*2=4
//3.day=8有(day9+1)*2=(4+1)*2=10
int peach(int day){
if(day==10){
return 1;
}else{
return (peach(day+1)+1)*2;
}
}
void main(){
int res = fbn(7);
printf("res=%d",res);
getchar();
int res2=f(30);
printf("res2=%d",res2);
int peachNum = peach(1);
printf("第一天有%d个桃子",peachNum);
}
7.函数注意事项和细节
- 函数的形参列表可以是多个
- C语言传递参数可以是值传递,也可以传递指针,也叫引用传递
- 函数的命名遵循标识符命名规范,首字母不能是数字,可以采用驼峰法或者下划线法,比如getMax(),get_max();
- 函数中的变量是局部的,函数外不生效
- 基本数据类型默认是值传递的,即进行值拷贝,在函数内修改,不会影响到原来的值
void f2(int n){
n++;
printf("f2中的n=%d",n);
}
void main(){
//printf("num=%d",num);
int n=9;
f2(n);
printf("main函数中的n=%d",n);
getchar();
}
- 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作,从效果上看类似引用(即传递指针)
void f3(int *p){
(*p)++;//修改会对函数外的变量有影响
}
void main(){
int n=9;
f3(&n);
printf("main函数中的n=%d",n);
getchar();
}
- C语言不支持函数重载
- C语言支持可变参数函数
#include <stdio.h>
#include <stdarg.h>
//说明
//num表示传递的参数格式
//...表示可以传递多个参数和num一致即可
int fun(int num,...){
//可变参数,即参数的个数可以不确定,使用...表示
int i,totalSum=0;
//totalSum一定要初始化
int val=0;
va_list v1;//v1实际是一个字符指针,从头文件里可以找到
va_start(v1,num);//使v1指向可变列表中第一个值,即num后的第一个参数
printf("*v=%d",*v1);
for(i=0;i<num;i++){
//num减一是为了防止下标超限
val = va_arg(v1,int);
//该函数返回v1指向的值,并使v1向下移动一个int的距离,使其指向下一个int
printf("val=%d",val);
totalSum += val;
}
va_end(v1);
return totalSum;
}
void main(){
int res = fun(8,23,10);
printf("和是=%d",res);
getchar();
}
8. 练习
//请编写一个函数swap(int *n1,int *n2)可以交换n1和n2的值
#include <stdio.h>
//说明
//1.函数名为swap
//2.形参是两个指针类型 int *
void swap(int *n1,int *n2){
int temp = * n1;
//表示将n1这个指针指向的变量的值赋给temp
*n1 = *n2;
//表示将n2这个指针指向的变量的值赋给n1这个指针指向的变量
*n2 = temp;//表示将temp值赋给n2这个指针的变量
}
void main(){
int n1 = 1;
int n2 = 2;
swap(&n1,&n2);
printf("main n1 = %d n2 =%d",n1,n2);
getchar();
}
9.函数参数的传递方式
9.1 基本介绍
- C语言传递参数可以是值传递,也可以是指针传递,也叫传递地址或者引用传递
9.2 两种传递方式
- 值传递
- 引用传递
- 其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝数据大小,数据越大,效率越低;
9.3 值传递和引用传递使用特点
-
值传递:变量直接存储值,内存通常在栈中分配
-
默认是值传递的数据类型有:
- 基本数据类型
- 结构体
- 共用体
- 枚举类型
-
引用传递
- 变量存储的是一个地址,这个地址空间对应的空间才真正存储数据(值)
-
默认是引用传递的数据类型有:指针和数组
-
如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量(*指针),从效果上看类似引用,比如修改结构体的属性
10.变量作用域
10.1 基本说明
- 所谓变量作用域,就是指变量的有效范围
- 函数内部声明/定义的局部变量,作用域仅限于函数内部
void sayHello(){
char name[] = "tom";
printf("hello %s ",name);
}
void main(){
sayHello();
//这里不能使用到sayHello的name变量
printf("name =%s",name);//将提示没有定义name
}
- 函数的参数,形式参数,被当作该函数内的局部变量,如果与全局变量同名他们会优先使用局部变量(编译器使用就近原则)
int n = 20;//函数外部定义的变量,就是全局变量
//函数形参,会被视为f10的局部变量
//说明:当局部变量和全局变量同名使,以局部变量为准(就近原则)
void f10(int n){
printf("n=%d",n);
}
void main(){
f10(10);
}
- 在一个代码块,比如for/if中的局部变量,那么这个变量的作用域就在该代码块
void main(){
int i = 0;
for(i=0;i<10,i++){
int k = 90;//k的作用域在for代码块中
printf("i=%d k=%d",i,k);
}
printf("k=%d",k);
getchar();
}
- 在所有函数外部定义的变量叫全局变量,作用域在整个程序有效
11. 初始化局部变量和全局变量
- 局部变量,系统不会对其默认初始化,必须对局部变量初始化后才可以使用,否则,程序运行后可能会异常退出
- 全局变量,系统会自动对其初始化
| 数据类型 | 初始化默认值 |
|---|---|
| int | 0 |
| char | '\0' |
| float | 0.0 |
| double | 0.0 |
| pointer指针 | NULL |
int a;
float f;
double d1;
void main(){
printf("a=%d f=%f d1=%f",a,f,d1);
getchar();
return;
}
- 正确的初始化变量是一个良好的编程习惯,否则程序可能会产生意想不到的结果,因为未初始化的变量会导致一些在内存位置中已经可用的垃圾值
11.2 作用域的注意事项和细节
-
全局变量保存在内存的全局存储区中,占用静态的存储单元,它的作用域默认是整个程序,也就是所有的代码文件,包括源文件(.c文件)和头文件(.h文件)
-
C程序的内存布局图
- 栈区(局部变量)
- 堆区(malloc 函数动态分配的数据,放在堆)
- 静态存储区/全局区(全局变量,静态数据)
- 代码区(存放代码/指令)
-
局部变量保存在栈中,函数被调用时才动态的为变量分配存储单元,它的作用域仅限于函数内部
-
C语言规定,只能从小的作用域向大的作用域中去寻找变量,而不能反过来,使用更小的作用域中的变量
-
在同一个作用域,变量名不能重复,在不同的作用域,变量名可以重复,使用时编译器采用就近原则
-
由{}包围的代码块也拥有独立的作用域
void f20(){
//函数,定义变量就是局部变量
int num = 90;
if(1){
int num = 900;
}
printf("a=%d",a);
}
#include<stdio.h>
double price = 200.0;//全局变量
void test01(){
printf("%.2f",price);
}
void test02(){
//编译器采用就近原则
double price = 250.0;
printf("%.2f",price);
}
void main(){
printf("main price=%.2f",price);
test01();
test02();
test01();
getchar();
}
#include<stdio.h>
void func1(){
int n=20;
printf("func1 n:%d",n);
}
void fun2(int n){
printf("fun2 n :%d",n);
}
void func3(){
printf("fun3 n:%d",n);
}
int main(){
int n=30;
func1();
func2(n);
func3();
//代码块由{}包围
{
int n = 40;
printf("block n:%d",n);
}
printf("main n:%d",n);
getchar();
return 0;
}

浙公网安备 33010602011771号