C语言程序设计-Chap.4


函数

1. 函数概述

函数 是C语言中的基本单位,具体表现为一段代码,可重复使用从而实现某种功能。

在C语言的源文件中,仅能存在一个 main函数 (程序入口)以及多个子函数(名称可自定)

注:C语言不允许函数的嵌套定义,如:

void function1(int a, int b){
    int function2(int c, int d){

    }
}

此类函数定义方式是不被允许的。

2. 函数的定义,调用,声明

这里直接列举定义,调用以及声明的方法:

  1. 定义:
    double function1(int a, double b){
     double num;
     ...
     ...
     return num;
    }
  2. 声明:
    double function1(int, double);
  3. 调用:
    double fact=function1(n, m);

几个要注意的点:

  1. 函数的返回值类型必须与函数类型相同(第一个词写的啥返回啥)

  2. void 关键字可以指函数类型,此时函数无返回结果,也可用于描述参数,此时函数无参数。

  3. 关于实参与形参:
    定义/声明函数时:

    int function1(int a, double b);

    这里的a, b称为形参,函数执行完毕后即清除相应内存。

    与此不同,调用函数时:

    int m=3, result;
    double n=4.2;
    result=function1(m, n);

    这里的m, n被称为实参。

    在调用函数时,会将实参中的值自左向右传递给形参
    (上例中即会先将m的值传给a,将n的值传给b)
    此后再进行函数的下一步语句。

    注意:实参传递给形参相应值是单向传递

  4. 关于函数的结束:两种方法

    1. 执行完最后一条语句
    2. 遇到了return,返回了相应值
  5. 一般而言,可以在主函数前定义相应函数并在主函数中进行调用,但如果想在主函数后定义相应函数,则需要在主函数前进行函数声明。

  6. c语言中的函数允许多级调用(也叫嵌套调用)
    明确一点,只是允许多级调用,但不允许嵌套定义(在前面有提及)

3. 递归

递归 指的是一个函数中存在调用自己本身这个函数的行为,如:

int fact(int n){
   int x;
   if(n==1){
      x=1;
   }else{
      x=n+fact(n-1);
   }
   return x;
}

可以发现,在fact函数中,若n!=1,则会重复调用fact自身。

一个特点:
递归可以转化为循环,但递归写法常常更加简洁,更加易读,相应的,递归的计算复杂度(不确定这个词是否准确)会更高,时间成本也会更高。
举出一个比较合适的例子:斐波那契数列
首先写出递归写法:

int fibo(int n){
   int result;
   if(i==1||i==2){
      result=1;
   }
   else{
      result=fibo(n-1)+fibo(n-2);
   }
   return result;
}

然后再给出循环写法:
int fibo(int n){
   int i;
   long f1, f2, result;
   for(i=2;i<n;i++){
      result=f1+f2;
      f1=f2;
      f2=result;
   }
   return result;
}

明显看出,编辑时,循环写法涉及到的细节更多,包括for循环的数量问题,f1, f2, result等变量的重新赋值等,而递归写法编程难度要简单许多。

但同时,也需要承认,在递归写法中,对许多量都需要进行重复运算,比如每算一次fibo(5)就要算一次fibo(4)和fibo(3),但这时算fibo(4)则再次涉及到了fibo(3)的运算,这种运算量如果多次叠加,时间成本是很恐怖的。

综上,请在递归降低编程难度这一特点能够弥补上它自己本身的效率开销时,再进行递归算法的使用。

进一步的问题解决敬请期待C语言进阶训练篇章。 (我直接挖坑)

4. 预编译

在本系列笔记的第二节中曾提及:预编译这一说法,这里给出详细说明。

预编译命令共三类:

  1. 文件引入

    #include <stdio.h>

    这一类编译命令还有一种写法:

    #include "...(你自己的文件名)"

    第一种写法,是在系统头文件中寻找响应文件并引入,如:

    <stdio.h>
    <math.h>
    <stdlib.h>
    ...

    第二种写法,是现在编程文件所在目录下进行文件查找,若找不到,再到系统指定目录下寻找响应文件

    无论如何,文件引入的根本目的是实现多文件编程,使得在这个编程文件中可以使用其他文件中所定义的函数。
    其具体原理是用指定文件的内容代替相应的命令行

    这里存在一种简单易行的方法进行多文件编程,即将你的函数一并写到一个文件中,命名为<file1.h>,此后在需要使用函数的地方预编译: include <file1.h> 即可。

    但当头文件有很多个时,会遇到另一个问题,参考如下例子:

    //这是文件file1.h
    #include<stdio.h>
    #include<math.h>
    ......
    
    //这是文件file2.h
    #include<stdio.h>
    #include<math.h>
    ......
    
    //这是文件file3.h
    #include<file1.h>
    #include<file2.h>
    ......
    
    //这是文件file4.h
    #include<file1.h>
    #include<file2.h>
    ......
    
    //这是文件file5.h
    #include<file.3>
    #include<file.4>
    ......

    当出现必须要同时引用 file3 与 file4 时,会出现对 file1 与 file2 的重复引用(即引用了两次,会报错)
    因此这里给出方法,在每个文件前都加上一行代码

    #pragma once

    此代码代表本文件仅引用一次。

  2. 宏定义

    #define name maintext

    宏定义的作用 maintext 来替换正文中出现的 name
    如:

    #define UD unsigned double
    ...
    UD x, y;

    这里的UD x, y; 作用等同于unsigned double x, y;

    几个注意事项:

    1. 宏定义后面没有引号,与文件引入相同。
    2. 宏定义是简单替换,会将maintext原封不动的替换到name的位置,容易造成计算方面的错误,因此不推荐频繁使用。
  3. 条件编译

    用于仅对c程序中的一部分进行编译,另一部分不编译。(通过if,else实现)
    具体格式:

    #if expression1
       ......
    #elif expression2
       ......
    #else 
       ......
    #endif
  4. 局部/全局变量

    局部变量 指的是在函数内定义的变量,仅仅能在函数内部生效。(包括主函数)
    注意:在复合语句中定义的变量生效范围仅为该复合语句内部。

    全局变量 指的是在函数外定义的变量,作用范围从定义处到函数源文件结束。

这里单开一块单独说明变量的问题。

5. 动态/静态储存变量

程序在内存的分布区域是有规划的,分为:

  1. 程序区:存放用户代码
  2. 静态存储区:存放全局数据与静态数据(在程序执行完毕后再释放相应内存)
  3. 动态存储区:存放动态数据(在相应函数执行完毕后便释放对应内存区域)
  4. 寄存器( CPU内 ):将某些用的很多的动态变量存入CPU相应的存储区域中,加快程序执行速度

由此,可将变量按照存储方式分类:

  1. 自动变量
    自动变量 存储在动态存储区中,定义方式为:

    auto int x;

    但由于auto关键字往往可省略,因此任何函数中的未加说明的变量定义均为自动变量。

  2. 静态变量
    静态变量 存储在静态存储区中,定义方式为:

    static int x;

    静态变量在函数执行完毕后将不会被销毁,并会在后续对其进行操作时保留对其进行的改变。

  3. 寄存器变量
    寄存器变量 存储在寄存器中,定义方式为:

    register int x;

    几个要注意的要点:

    1. 只有动态变量可以作为寄存器变量
    2. 寄存器变量不能无限定义,因为寄存器数目有限
  4. 全局变量
    全局变量 存储在静态存储区中,定义方式为在函数外部进行相应定义:

    #include <stdio.h>
    int x;
    int main(){
       ...
    }

    extern可以通过在多文件编程中进行使用,但是在另一个文件中使用本文件的全局变量时需要加上extern前缀说明。
    这里举一例:

    //这是文件A的内容
    #include <stdio.h>
    int x=2;
    int main(){
       ...
       ...
    }
    
    //这是文件B的内容
    #include <stdio.h>
    
    int main(){
       extern int x;     //x存在,但是定义在别的地方
       printf("%d", x);
       return 0;
    }

需要注意的是,全局变量、静态变量的使用会降低程序可读性,增加编程复杂度,故请适量使用。

此外,添加一点补充:

  1. 在嵌套作用域中出现同名变量名定义时,内层作用域的同名变量在这其中会遮盖外层变量。
    这里浅举一例:

    int main(){
       int a=3, b=5;
       {
          int b=2;
          printf("%d\n", a+b);
       }
       printf("%d\n", a+b);
       return 0;
    }

    运行结果为:

    5
    8

    很明显,在内部作用域中b被重新定义的b=2覆盖,当执行完毕后b的值重新变回5。

  2. 需要注意,多个源文件中不能有重名的全局变量,否则在某一个文件中对全局变量进行引用时会出现链接问题

  3. C语言中的常量定义:

    1. enum
      enum 代表枚举常量,仅能定义整数常量
      enum {N=30, M=300};
      这样定义出的常量在函数编译过程中值不会改变
    2. const
      const 代表常量,可以定义各种常量
      const int x=3;
      const double m=3.14;
      const long r=4;
      同样,这样定义出的常量在函数编译过程中值不会改变

至此,函数以及各类预编译事项便具体说明完毕。

接下来就是一些比较复杂的数据处理了,如数组,指针等。

这篇博文先到这里~


文章作者: MUG-chen
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 MUG-chen !
  目录
加载中...