乐趣区

关于c:C程序设计-07-函数

若程序性能较多,规模较大,将所有的程序写在一个 main 主函数中,会使得主函数庞杂,浏览和保护艰难大。于是提出 模块化程序设计

在设计一个较大的程序时,往往将它分为若干个程序模块,每个模块包含一个或多个函数(function),每个函数实现一个特定的性能。

  • 一个 C 程序可由一个 main 函数和若干个其余函数形成,程序的执行是从 main 函数开始的。
  • 函数间能够互相调用,但其余函数不能调用 mian 函数,一个函数能够被一个或多个函数调用屡次。
  • 所有函数都是平行的、互相独立的,函数不能嵌套定义。

从用户应用的角度看,函数分为:库函数和用户自定义的函数。

从函数模式上看,函数分为:无参函数和有参函数。

一、定义函数

C 语言要求程序中所有用到的函数,必须先定义后应用。定义函数应包含以下几个内容:

  1. 函数的名字。
  2. 函数的类型,即函数返回值的类型。
  3. 函数的参数名和参数类型(无参数函数不须要)。
  4. 函数的性能。

对于 C 编译系统提供的库函数,是由编译系统当时定义好的,只需应用 #include 指令引入到程序文件中即可应用。

1. 定义无参函数

定义无参函数的个别模式有两种:

// 模式 1
类型名 函数名()
{函数体}

// 模式 2
类型名 函数名(void)
{函数体}

模式 2 中括号内 void 示意“空”,即函数没有参数。

定义函数时,如果函数无类型,即无函数值,能够应用 void 代替类型名。这时,执行函数后,不会把任何值带回 main 函数中。

2. 定义有参函数

定义有参函数的办法为:

类型名 函数名(形式参数表列)
{函数体}

例如,定义一个求两个数中最大值的函数:

int max(int x, int y)
{
    int z;
    z = x > y ? x : y;
    return(z);
}

数组元素的作用与变量相当,因而,数组元素也能够作为函数实参,其用法与变量雷同。此外,数组名也能够作为实参和形参。形参数组能够不指定数组大小(高维数组只能疏忽第一维)。

float average(float array[], int n)
{float aver, sum = array[0];
    int i;
    for(i = 1; i < n; i++)
        sum = sum + array[i];
    aver = sum / n;
    return aver;
}

须要留神,应用数组名作为函数实参时,不是吧数组元素的值传递给形参,传递的是数组的第一个元素的地址,这样两个数组就占用同一段内存单元,当形参数组中各元素值发生变化,实参数组元素值也会发生变化。这与变量作函数参数的状况不同。

3. 定义空函数

所谓空函数,即函数体是空的:

类型名 函数名()
    {}

在程序设计中,往往依据须要定义若干模块,别离由一些函数来实现。而在第一阶段只设计最根本的模块,其余一些性能须要后续补上。因而,在程序开始的最后阶段,能够在未来筹备裁减的中央应用空函数。

二、调用函数

1. 函数调用的模式

函数调用的个别模式为:

函数名(理论参数表列)

如果调用的是无参函数,实参表列能够省略,但括号不能省略。函数能够独自调用,也能够呈现在一个表达式中,这时要求函数返回一个确定的值以加入表达式的运算。同时,函数调用也能够作为另一个函数的实参:

m = max(a, max(b, c);

2. 数据传递

函数定义时函数名前面括号内的变量名称为 形式参数 ,简称形参。函数调用时,函数名前面括号内的参数为 理论参数,简称实参。实参能够时常量、变量或表达式。

在函数调用过程中,零碎会把实参的值传递给形参。定义函数时指定的形参不会占据存储单元,只有函数被调用时,形参才会被调配长期内存单元。因为形参和实参在内存中占据不同的存储单元,调用完结后,形参单元被开释,实参单元保留原值。

3. 函数的返回值

函数的返回值通过 return 语句取得。return 语句前面的括号能够不要,return(x);return x; 等价。return 语句中表达式的类型应该与函数定义时指定的类型统一。如果函数类型与 return 语句中表达式的类型不统一,以函数类型为准,数值型数据可自行转化。即 函数类型决定返回值类型。对于不带返回值的函数,该当定义函数为 void 类型。

4. 申明调用函数

在一个函数中调用另一个函数须要具备以下条件:

  1. 被调用的函数必须是曾经定义的。
  2. 如果应用库函数须要在文件结尾应用 #include 指令引入无关文件。
  3. 如果自定义的函数在调用它的函数 (主调函数) 之后,应该在主调函数中对被调用函数作 申明(declaration)。申明的作用是把函数名、函数参数等学习告诉编译系统,以便在调用函数时能正确辨认并查看调用的合法性。

函数的申明和函数的第一行基本一致,也把函数的首行称为 函数原型(function prototype)。

#include <stdio.h>

int main()
{float add(float x, float y);  // 函数的申明
    float a, b;
    scanf("%f, %f", &a, &b);
    printf("sum is %f", add(a, b));
    return 0;
}

float add(float x, float y)
{return (x + y);
}

实际上,编译系统只关怀参数个数和参数类型,而不查看参数名。因而函数申明中能够省略形参名,只保留参数类型:

float add(float, float);

如果曾经在文件的结尾 (即所有函数之前) 对本文件中所调用的所有函数进行了申明,则在函数中不用对其所调用的函数再做申明。

5. 嵌套调用

C 语言函数定义时互相平行、相互独立的,不能嵌套定义,但能够嵌套调用。

#include <stdio.h>

int main()
{int max4(int, int, int, int);
    int a, b, c, d;
    scanf("%d %d %d %d", &a, &b, &c, &d);
    printf("max is %d", max4(a, b, c, d));
    return 0;
}

int max2(int a, int b)
{return(a>=b ? a:b);
}

int max4(int a, int b, int c, int d)
{int max2(int, int);
    return max2(max2(max2(a, b), c), d);
}

6. 递归调用

在调用一个函数的过程中又呈现间接或间接地调用该函数自身,称为函数的递归调用。

#include <stdio.h>

int main()
{int fac(int);
    int n;
    scanf("%d", &n);
    printf("%d", fac(n));
    return 0;
}

int fac(int n)
{if (n == 1)
        return 1;
    else
        return fac(n - 1) * n;
}

三、局部变量和全局变量

每个变量被定义之后都只能在肯定的范畴内应用才无效,这就是变量的 作用域 问题。定义变量有 3 种状况:

  1. 在函数结尾定义,只能在本函数内应用。
  2. 在函数内的复合语句中定义,只能在本复合语句中应用。
  3. 在函数内部定义,能够从定义之处起为本文件中所有函数调用。

前两种状况定义的是 局部变量 ,最初一种状况定义的是 全局变量 。为了辨别局部变量和全局变量,编写程序时通常将全局变量名的首字母大写(习惯,非规定)。
倡议非必要不应用全局变量,因为:

  • 全局变量在程序执行的全程中占用存储单元。
  • 使函数的通用性升高。因为如果在函数中援用了全局变量,函数的执行状况会受到无关内部变量的影响,也不便于函数移植到其余程序文件。程序设计划分模块时,要求模块的内聚性强、与其余模块的耦合性弱,即模块性能繁多、与其余模块的相互影响小,而应用全局变量不合乎这一准则。
  • 应用全局变量过多,会升高程序的清晰度,难以分明判读程序执行过程中内部变量的值。

四、变量的存储形式和生存期

1. 动静存储与动态存储

从变量作用域 (空间) 的角度,能够把变量分为局部变量和全局变量。而从变量值 生存期 (工夫) 的角度,有的变量在程序运行的这个过程都存在,有点变了只有调用其所在函数时才会被长期调配存储单元。因而,变量的存储形式有两种:

  1. 动态存储:在程序运行期间由零碎调配固定的存储空间。
  2. 动静存储:在程序运行期间依据须要动态分配存储空间。

在内存中,供用户应用的存储空间分为 3 个局部:

  1. 程序区。
  2. 动态存储区:寄存全局变量。
  3. 动静存储区:寄存函数形式参数、函数中定义的没有应用关键字 static 申明的变量(即主动变量)、函数调用时的现场爱护和返回地址等。

2. 局部变量的存储类型

在 C 语言中,每一个变量和函数都有两个属性:数据类型和数据的存储类型。存储类型指的是数据在内存中的存储形式(如动态存储和动静存储)。在定义和申明变量与函数时,个别应同时指定其数据类型和存储类型,也能够采纳默认形式,即用户不指定,零碎主动指定。

C 语言的存储类型有 4 种:主动的(auto)、动态的(static)、寄存器的(register)、内部的(extern)。

主动变量 auto

函数中的局部变量和形参,如果不专门申明 static 存储类别,都是动态分配存储空间。这类局部变量称为 主动变量,应用关键字 auto 作为存储类型阐明:

int f(int a)  // 定义 f 函数,a 为形参
{
    auto int b, c = 1;  // 定义 b 和 c 为主动变量
    ...
}

程序中大部分变量都是主动变量,定义时关键字 auto 能够省略。

动态局部变量 static

有时须要函数种局部变量的值在函数调用完结之后持续保留,即其占用的存储单元不开释。这时应应用关键字 static 指定该局部变量为 动态局部变量

#include <stdio.h>

int main()
{int f(int);
    int n, i, r;
    scanf("%d", &n);
    for (i = 2; i < n + 1; i++)
        r = f(i);
    printf("%d\n", r);
    return 0;
}

int f(int n)
{
    static int x = 1;
    x = x * n;
    return x;
}

动态局部变量在编译时赋初值,只赋值一次,当前每次调用函数都不再从新赋值,而保留上次函数调用完结后的值。如果不赋初值,零碎主动为数值型动态局部变量赋初值为 0,为字符型动态局部变量赋初值为 \0。尽管动态局部变量在函数完结后仍旧保留在内存种,但毕竟时局部变量,只能由定义的函数应用,其余函数不能调用。

因为动态局部变量多占内存,且升高了程序的可读性,因而非必要不应用。

寄存器变量 register

个别状况下变量的值是放在内存中的,当程序运行须要时,由控制器收回指令将该变量的值传到运算器,运算后如果须要持续存储,再传回到内存。如果有一些变量频繁应用,为节俭存取变量的值破费的工夫,能够将局部变量的值放到 CPU 的寄存器中,须要时间接从寄存器中读取。因为寄存器的存取速度远高于内存,这样做能够进步程序执行效率。这种变量称为 寄存器变量,应用关键字 register 定义。

register int a;

因为当初计算机的性能越来越好,优化后的编译系统能辨认出应用频繁的变量,主动将这些变量寄存在寄存器中,不须要设计程序时独自指定。因而实际上用 register 申明变量的必要性不大。

3. 全局变量的存储类别

一般来说,内部变量是在函数内部定义的全局变量,它的作用域是从定义处开始,到本程序完结。在此作用域内,全局变量能够被各个函数应用。但有时会心愿扩大内部变量的作用域。

在一个文件内扩大内部变量的作用域

如果内部变量不在文件的结尾定义,其无效范畴只限定于定义处到文件完结。如果因为某种思考,须要定义点之前的程序能援用该内部变量,应应用关键字 extern 在援用前对该变量作 内部变量申明

#include <stdio.h>

int main()
{int max();
    extern A, B, C;  // 扩大内部变量 A,B,C 的作用域到此处
    scanf("%d %d %d", &A, &B, &C);
    printf("max is %d", max());
    return 0;
}

int A, B, C;  // 定义内部变量 A,B,C

int max()
{
    int m = A > B ? A : B;
    if (C > m)
        m = C;
    return m;
}

应用 extern 申明内部变量时能够省略变量类型。通常提倡将内部变量的定义放在援用它的所有函数之前,这样能够防止在函数中多加一个 extern 申明。

将内部变量的作用域扩大到其余文件

如果程序是由多个源程序文件组成的,想在一个文件中援用了一个文件定义的内部变量,此时不能在两个文件中同时定义该变量,而应在一个文件中定义后,在另一个文件中应用 extern 对变量做内部申明。用法与在文件内扩大内部变量的作用域用法雷同。

实际上,编译系统遇到 extern 时,会当初本文件中寻找内部变量的定义,如果找到,就在本文件中扩大内部变量的作用域,找不到时再从其余文件中寻找。

将内部变量的作用域限度在本文件中

如果心愿某些内部变量只能被本文件援用,能够在定义内部变量时加一个 static 申明:

static int A;

这种只能用于本文件的内部变量称为 动态内部变量

五、外部函数与内部函数

变量由作用域,用部分和全局之分,函数也有相似的问题。函数实质上时全局的,因为定义一个函数的目标就是心愿它能被其余函数调用。如果不加申明,一个函数能够被本文件和其余文件中的函数调用。也能够指定某些函数不能被其余函数调用。依据函数是否能被其余源文件调用,分为 外部函数 内部函数

1. 外部函数

如果一个函数只能被本文件中的其余函数调用,称为外部函数,又称为动态函数。定义时在函数类型后面加 static 关键字:

static int f(int x)

通常把只能本文件应用的动态内部变量和动态函数放在文件结尾,进步程序可读性。

2. 内部函数

定义函数时在函数类型前加 extern 关键字,指定该函数为内部函数,可被其余文件调用。extern 能够省略:

extern int f(intx)

调用内部函数也要先申明,如果该函数来自其余文件,申明时也要加关键字 extern


Reference:

谭浩强《C 程序设计(第五版)》

退出移动版