---
title: C语言学习-3
published: 2022-11-23
description: ''
image: 'https://t.alcy.cc/fj'
tags: [C语言, 笔记]
category: '笔记'
draft: false 
lang: ''
---

<meta name="referrer" content="no-referrer"/>

## 函数

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