480 lines
13 KiB
Markdown
480 lines
13 KiB
Markdown
---
|
||
title: C语言-3
|
||
date: 2022-11-23 20:56:10
|
||
tags: [C语言, 笔记]
|
||
categories: 笔记
|
||
---
|
||
|
||
|
||
|
||
## 函数
|
||
|
||
### 1、为什么需要:
|
||
|
||
- 程序需要多次实现某一功能
|
||
- 程序需要实现多种功能
|
||
|
||
组装思想—>模块化程序设计
|
||
|
||
### 2、什么是函数:
|
||
|
||
函数(function):函数就是功能,每一个函数用来实现某一个特定的功能
|
||
|
||
### 3、函数从哪来:
|
||
|
||
- 库函数
|
||
- 编译函数
|
||
- 自己编写函数
|
||
|
||
### 4、函数的分类
|
||
|
||
- 无参函数
|
||
- 有参函数
|
||
|
||
### 5、其他
|
||
|
||
- 一个C程序由一个或多个源文件组成
|
||
- 一个源文件由一个或多个函数组成
|
||
|
||
## 怎么定义函数
|
||
|
||
### 1、为什么要定义
|
||
|
||
- 程序中用到的所有函数必须“先定义,后使用”
|
||
|
||
- 同变量定义的道理类似,需要事先告知系统该函数功能、参数等信息,具体包括:
|
||
|
||
> 函数的名字,一遍按名调用
|
||
> 函数的类型,即函数返回值的类型
|
||
> 函数的参数名字即类型,以便调用函数时像他们传递数据
|
||
> 函数完成什么操作,即功能
|
||
|
||
### 2、定义函数的方法
|
||
|
||
note blue no-icon
|
||
定义无参函数
|
||
endnote
|
||
|
||
```c
|
||
类型名 函数名() //()内可以加void,可不加
|
||
{
|
||
函数体
|
||
}
|
||
|
||
|
||
例:
|
||
void pr()
|
||
{
|
||
ptintf("hello world!");
|
||
}
|
||
```
|
||
|
||
note blue no-icon
|
||
定义有参函数
|
||
endnote
|
||
|
||
```c
|
||
类型名 函数名(形参列表)
|
||
{
|
||
函数体
|
||
}
|
||
|
||
|
||
例:
|
||
int max(int a,int b)
|
||
{
|
||
return(a>b?a:b);
|
||
}
|
||
```
|
||
|
||
note blue no-icon
|
||
定义空函数
|
||
endnote
|
||
|
||
```
|
||
类型名 函数名()
|
||
{}
|
||
|
||
|
||
例:
|
||
void fun()
|
||
{}
|
||
```
|
||
|
||
## 调用函数
|
||
|
||
一般形式:函数名(实参表列)
|
||
|
||
### 1、调用函数的形式
|
||
|
||
```
|
||
函数调用语句
|
||
例:
|
||
pr();
|
||
|
||
|
||
函数表达式 //函数调用语句出现在另一个表达式中
|
||
例:
|
||
c=2*max(a,b); //调用函数带回一个确定值并参
|
||
|
||
|
||
|
||
加表达式的运算
|
||
函数参数 //函数调用作为另一个函数调用时的参数
|
||
例:
|
||
m=max(a,max(b,c)); //将调用max函数的结果再重新作为下一次调用max函数的参数
|
||
```
|
||
|
||
### 2、函数调用时的数据传递
|
||
|
||
在调用有参函数时,系统会把实参的值传传给被调用函数的形参,该值在函数调用期间有效。
|
||
|
||
**1.形参:定义函数时,函数名括号后面的变量称为形式参数(或虚拟参数)**
|
||
|
||
- 形参在函数调用时被分配内存单元,调用结束后立即释放
|
||
- 形参为变量时,实参与形参的数据传递是“值传递”,即“单向传递”。
|
||
|
||
**2.实参:在主调函数中调用一个函数时,函数名后面括号中的参数称为实际参数**
|
||
|
||
- 实参可以是常量、变量或表达式,但要求有确定值。
|
||
- 应保证形参与实参个数、类型、顺序的一致(字符型与整型可通用)。
|
||
- 形参与实参的类型应相同或i赋值兼容。
|
||
|
||
note red modern
|
||
注:实参向形参的数据传递是“值传递”,单向传递,只能由实参传给形参,而不能由形参传给实参。
|
||
实参和形参在内存中占有不同的存储单元,实参无法得到形参的值。
|
||
endnote
|
||
|
||
### 3、函数的返回值
|
||
|
||
调函数调用函数希望得到一个确定值,这就是函数的返回值
|
||
|
||
- 函数的返回值是通过return语句获得的
|
||
|
||
- 可以有多个return语句,但只能有一个起作用。即函数只能返回一个值
|
||
|
||
- 函数返回值的类型取决于定义函数时指定的函数值的类型
|
||
|
||
```
|
||
int max(int x,int y) //函数值为整型
|
||
double min(int x,int..y) //函数值为double类型
|
||
```
|
||
|
||
- 在定义函数时指定的函数类型一般应该和return语句中的表达式一致,若不一致,则以函数类型为
|
||
准,即函数类型决定返回值类型。
|
||
|
||
- 对于不带返回值的函数,应当定义为void类型。
|
||
|
||
**小结**
|
||
|
||
- 函数——即想要实现某一功能所编写的程序,可将其理解为一个黑匣子,给它相应的输入(由调用
|
||
者决定),它经过加工操作,给出你一个输出(结果)。
|
||
|
||
- 函数的定义可理解为该制作该黑匣子,需要包括以下信息:
|
||
|
||
> 需要明确告知该函数的名字(一般以黑匣子的功能简称作为名字,做到见名知意)
|
||
> 使用该函数需要提供的输入(包括明确规定需要提供几个输入以及每个输入的类型)
|
||
> 该函数能够实现什么功能(黑匣子的详细功能介绍)
|
||
> 函数类型,也称函数返回值类型(即执行完毕后会输出什么)
|
||
>
|
||
> > 该输出值由return语句带回。
|
||
> > 若不用带回值,则不用写return语句,同时函数定义为void类型
|
||
> > 返回值类型应与函数类型一致,若不一致,则以函数类型为准。
|
||
|
||
- 函数调用:使用该黑匣子的过程,若需要提供输入,则涉及到形参和实参之间的数据传递
|
||
|
||
## 对被调用函数的声明和函数原型
|
||
|
||
**1、调用函数时应该具备的条件**
|
||
|
||
- 被调用函数必须是已经定义的函数
|
||
- 若该被调用函数为库函数,则需要在文件开头,用#include指令调用
|
||
- 若该被调用函数为用户自己定义的函数,而该函数的位置在调用它的函数的后面,则需要在主调函数中对被调函数做声明。
|
||
- 声明是为了提前将该函数的相关信息告知编译系统,以允许编译系统检查调用是否合法。
|
||
|
||
**2、函数声明的形式(2种)**
|
||
|
||
- 1.函数类型函数名(参数类型1参数名1,参数类型2c参数名2,……参数类型n参数名n);
|
||
- 2.函数类型函数名(参数类型1,参数类型2,……参数类型n)
|
||
|
||
## 函数的嵌套调用
|
||
|
||
**在调用一个函数的过程中,又调用另一个函数**
|
||
|
||
```c
|
||
#include<stdio.h>
|
||
int fun2(int m)
|
||
{
|
||
return m*m;
|
||
}
|
||
|
||
int fun1(int x,int y)
|
||
{
|
||
return fun2(x) + fun2(y);
|
||
}
|
||
|
||
int main()
|
||
{
|
||
int a,b;
|
||
scanf("%d%d",&a,&b);
|
||
printf("%d",fun1(a,b));
|
||
return0;
|
||
}
|
||
```
|
||
|
||
## 函数的递归调用
|
||
|
||
**在调用一个函数的过程中又直接或间接地调用该函数本身**
|
||
|
||
```c
|
||
#include<stdio.h>
|
||
unsigned fac(int n)
|
||
{
|
||
unsigned f;
|
||
if (n==0)
|
||
f = 1;
|
||
else
|
||
f = fac(n-1)*n;
|
||
return f;
|
||
}
|
||
|
||
int main()
|
||
{
|
||
unsigned n,y;
|
||
scanf("%d",&n);
|
||
y = fac(n);
|
||
printf("%d!=%d\n",n,y);
|
||
return0;
|
||
}
|
||
```
|
||
|
||
## 数组作为函数参数(数组元素和数组名)
|
||
|
||
- 调用有参函数时,需要提供实参。
|
||
- 实参可以是常量、变量、表达式。
|
||
- 数组元素和数组名也可以作为函数的实参。
|
||
|
||
### 1、数组元素作为函数参数
|
||
|
||
- 与变量作用相当,凡是变量可以出现的地方,都可以用数组元素代替。
|
||
|
||
- 数组元素**只可以作为函数的实参,不可以用作形参。**
|
||
|
||
- 因为形参是在函数被调用时临时分配存储单元的,不可能为一个数组元素单独分配存储单元。
|
||
|
||
- 而实参的传递是单向值传递。
|
||
|
||
```c
|
||
//已知10个三角形的三边长,求它们的面积。
|
||
#include<math.h>
|
||
#include<stdio.h>
|
||
float area(float a,float b,float c)
|
||
{
|
||
float p,s;
|
||
p = (a+b+c)/2;
|
||
s = sqrt(p*(p-a)*(p-b)*(p-c));
|
||
return(s);
|
||
}
|
||
int main()
|
||
{
|
||
floata[10],b[10],c[10],s[10];
|
||
int i;
|
||
for(i=0;i<10;i++)
|
||
{
|
||
scanf("%f%f%f",&a[i],&b[i],&c[i]);
|
||
s[i]=area(a[i],b[i],c[i]);
|
||
printf("s[%d]=%f\n",i+1,s[i]);
|
||
}
|
||
return0;
|
||
}
|
||
```
|
||
|
||
### 2、数组名作为函数参数
|
||
|
||
#### 1、一维数组名作为函数参数
|
||
|
||
- 数组名既可以作形参,也可以作实参。
|
||
|
||
- 数组名表示的是数组第一个元素的地址
|
||
|
||
- 形参数组可以不指定大小,但在定义数组时,需要在数组名后加上一个空的方括号
|
||
|
||
```c
|
||
float average(float array[]) //定义average函数,形参数组不指定大小
|
||
```
|
||
|
||
- 由于用数组名作函数实参时,不是把数组元素的值传给形参,而是把实参数组的首元素地址传递给形参数组,因此这两个数组共用一段内存单元。即形参数组中各元素的值如果发生了变化,会使实参数组元素的值同时发生变化。
|
||
|
||
```c
|
||
//形参和实参共享一段内存单元
|
||
#include <stdio.h>
|
||
void fun(int b[])
|
||
{
|
||
int i;
|
||
for (i=0;i<=4;i++)
|
||
{
|
||
b[i] = 100;
|
||
}
|
||
}
|
||
|
||
int main()
|
||
{
|
||
int a[5] = {0};
|
||
int i;
|
||
fun(a);
|
||
for (i=0;i<=4;i++)
|
||
{
|
||
printf("%d",a[i]);
|
||
}
|
||
return0;
|
||
}
|
||
```
|
||
|
||
#### 2、多维数组名作函数参数
|
||
|
||
- 多维数组元素可以作为函数参数,在被调用函数中,对形参数组定义时,可以指定每一维大小,也可省略第一维大小。
|
||
|
||
```c
|
||
//一下两种均合法
|
||
|
||
int array[3][10];
|
||
int array[][10];
|
||
```
|
||
|
||
- 但不能将2为或更高维的大小省略
|
||
|
||
```
|
||
//错误示范
|
||
|
||
int array[][]
|
||
int array[3][]
|
||
```
|
||
|
||
- 第二维大小相同的前提下,形参数组第一维可以与实参数组不同
|
||
|
||
```c
|
||
实参数组定义:int score[5][10];
|
||
形参数组定义:int array[][10]; 或 int array[8][10];
|
||
```
|
||
|
||
## 局部变量和全局变量
|
||
|
||
- 量必须先定义,后使用
|
||
- 在一个函数中定义的变量,在其他函数中能否被引用?====》**作用域**
|
||
- 在**函数内**定义的变量是**局部变量**,在**函数外**定义的变量是**外部变量**,**外部变量**是**全局变量**
|
||
|
||
### 1、定义变量的三种情况
|
||
|
||
- 在函数开头定义(作用范围:从定义处开始至本函数结束)局部变量
|
||
- 在函数内的复合语句内定义(作用范围:本复合语句范围内)局部变量
|
||
|
||
```c
|
||
#include <stdio.h>
|
||
int main()
|
||
{
|
||
int a=1,b=2;
|
||
{
|
||
int c;
|
||
c = a+b;
|
||
printf("%d",c); //可以输出
|
||
} //c的作用范围仅限于该复合语句块内
|
||
printf("%d",c); //会报错,显示c未被定义
|
||
return0;
|
||
}
|
||
```
|
||
|
||
- 在函数的外部定义(作用范围:从定义变量的位置开始到本源文件结束)外部变量
|
||
|
||
### 2、其他注意事项
|
||
|
||
- 在一个函数中既可以使用本函数中的局部变量,也可以使用有效的全局变量
|
||
- 设置全局变量可以增加函数间数据联系的渠道,但也因此如果在一个函数中改变了全局变量的值,
|
||
- 就会影响到其他函数全局变量的值。
|
||
- 因函数调用只能带回一个函数返回值,因此有时可以利用全局变量得到一个以上的值。
|
||
- 不成文的规定:将全局变量首字母大写
|
||
- 非必要不使用全局变量
|
||
- 长时间占用存储空间
|
||
- 函数通用性降低
|
||
- 增加了耦合性(各函数之间关联变多)
|
||
- 移植性差
|
||
- 降低了清晰性
|
||
- 若在同一个源文件中,全局变量和局部变量同名,在局部变量的作用范围内,全局变量会被屏蔽。
|
||
|
||
## 变量的存储方式和生存期
|
||
|
||
- 从变量值的存在时间(生存期)来看,变量的存储可以分为**静态存储方式**和**动态存储方式**。
|
||
- 静态存储方式:程序运行期间由系统分配固定的存储空间(全局变量全部存放在静态存储区中)
|
||
- 动态存储方式:程序运行期间,根据需要动态的分配存储空间。(函数形参,自动变量,函数调用时的现场保护和返回地址)
|
||
|
||
### 1、局部变量的存储类别
|
||
|
||
#### 1、自动变量(auto)
|
||
|
||
- 特点:在调用该函数时,系统会给这些变量分配存储空间,在函数调用结束时就自动释放这些存储空间
|
||
|
||
- 函数中的形参和在函数中定义的局部变量都属于自动变量
|
||
|
||
- 不写auto则隐含指定为自动存储类别
|
||
|
||
```
|
||
int fac(int a)
|
||
{
|
||
auto int b,c=3; //与intb,c=3;完全等价
|
||
.....
|
||
}
|
||
```
|
||
|
||
#### 2、静态局部变量(static)
|
||
|
||
- 特点:函数中局部变量的值在函数调用结束后不消失而继续保留原值,在下一次调用该函数时,该变量已有值。
|
||
|
||
#### 3、寄存器变量(register)
|
||
|
||
- 对于一些频繁使用的变量,可将其存储在具有高速存取速率的寄存器中,这种变量叫寄存器变量
|
||
|
||
```
|
||
register int f;
|
||
```
|
||
|
||
- 目前已不需要,遇到能看懂即可。
|
||
|
||
### 全局变量的存储类别
|
||
|
||
**1、在一个文件内扩展外部变量的作用域(extern关键字)**
|
||
**2、将外部变量的作用域扩展到其他文件(extern关键字)**
|
||
**3、将外部变量的作用域限制在本文件中(定义变量时加上static声明)**
|
||
|
||
- 对局部变量用static声明,把它分配在静态存储区,该变量在整个程序执行期间不释放,其所分配的空间始终存在。
|
||
*对全局变量用static声明,则该变量的唑酮与只限于本文件模块(即被声明的文件中)。
|
||
|
||
## 关于变量的声明和定义
|
||
|
||
### 1、对于函数而言
|
||
|
||
- 函数的声明时函数的原型,函数的定义是对函数功能的定义。
|
||
|
||
### 2、对变量而言
|
||
|
||
*建立存储空间的声明称为定义,不建立存储空间的声明称为声明。
|
||
|
||
## 内部函数和外部函数
|
||
|
||
- 根据函数能否被其他源文件调用,将函数区分为内部函数和外部函数
|
||
|
||
### 1、内部函数(静态函数)
|
||
|
||
如果一个函数只能被本文件中其他函数所调用,则称为内部函数。定义内部函数时,在函数名和函数类型的前面加static,即
|
||
|
||
```
|
||
static 类型名 函数名(形参名)
|
||
```
|
||
|
||
### 2、外部函数
|
||
|
||
如果在定义函数时,在函数首部的左端加关键字extern,则此函数时外部函数,可供其他文件调用
|
||
|
||
```
|
||
extern int fun(int a,int b)
|
||
```
|
||
|
||
若在定义时省略extern,则默认为外部函数 |