This repository has been archived on 2026-03-06. You can view files and clone it, but cannot push or open issues or pull requests.
Files
hexo_blog/source/_posts/c_learn_3.md
2025-10-12 14:06:44 +08:00

480 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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则默认为外部函数