复习函数

  • 什么是函数:函数是完成特定任务的独立程序代码单元。
  • 为什么要使用函数:

    1. 使用函数可以省去编写重复代码的苦差。
    2. 即使程序只完成某项任务一次,也值得使用函数。因为函数让程序更加模块化,从而提高了程序代码的可读性,更方便后期修改、完善。
  • 使用函数
    要使用一个函数需要使用3次该函数标识符:函数原型告诉编译器函数的类型;函数调用表明执行函数;函数定义明确地指定了函数要做什么。
  • 函数参数

    1. 定义带形式参数的函数
      函数定义中的形式参数(形参)是局部变量,属该函数私有。
    2. 声明带形式参数函数的原型
      函数声明中形参写法与函数定义基本一致,但声明可以省略形参变量名只写变量类型。
    3. 调用带实际参数的函数
      在函数调用中,实际参数(实参)提供了形参的值。

简而言之,形参是被调函数中的变量,实参是主调函数赋给被调函数的具体值。

  • 黑盒视角
    黑盒视角即在函数里发生了什么对主调函数是不可见的。
  • 使用return从函数中返回值

    1. 参数的作用是把信息从主调函数传递给被调函数,反过来,函数的返回值可以把信息从被调函数传回主调函数。
    2. 关键字return后面的表达式的值就是函数的返回值。return还有一个作用是终止函数并把控制返回主调函数的下一条语句。
  • 函数类型

    1. 声明函数时必须声明函数的类型。带返回值的函数类型应该与其返回值类型相同,而没有返回值的函数应声明为void类型。
    2. 类型声明是函数的一部分。要记住,函数类型指的是返回值的类型,不是函数参数的类型。

ANSI C函数原型

  1. 问题:
    ANSI C标准之前的声明函数方案有缺陷,因为只需要声明函数的类型,不用声明任何参数。会导致参数不匹配,而编译器根本不会察觉出来,最终得出错误的程序结果。
  2. 原因:

    • 主调函数把它的参数存储在被称为的临时存储区,被调函数从栈中读取这些参数。
    • 主调函数根据函数调用中的实参决定传递的类型,而被调函数根据它的形参读取值。
  3. ANSI C的解决方案:
    针对参数不匹配问题,ANSI C标准要求在函数声明时还要声明变量的类型,即使用函数原型来声明函数的返回类型、参数的数量和每个参数的类型。有了这些信息,编译器可以检查函数调用是否与函数原型匹配。
  4. 无参数和未指定参数
    一个支持ANSI C的编译器如果在函数中参数列表留空,编译器会假定用户没有用函数原型来声明函数,他将不会检查参数。为了表明函数确实没有参数,应该在参数列表中使用void关键字。
  5. 函数原型的优点
    它让编译器捕获在使用函数时可能出现的许多错误或疏漏。如果编译器没有发现这些问题,就很难察觉出来。
    但也不一定必须使用函数原型,首先要明白,之所以使用函数原型,是为了让编译器在第一次执行到该函数之前就知道如何使用它。因此,把整个函数定义放在第一次调用函数前也有相同的效果。

递归

C允许函数调用它自己,这种调用过程称为递归

  1. 递归的基本原理

    1. 每级函数调用都有自己的变量。也就是说各级变量互不相干,都属于各自的局部变量。
    2. 每次函数调用都会返回一次。当函数执行完毕后,控制权将被传回上一级递归。程序必须按顺序逐级返回递归。
    3. 递归函数中位于递归调用之前的语句,均按被调函数的顺序执行。
    4. 递归函数中位于递归调用之后的语句,均按被调函数相反的顺序执行。
    5. 虽然每级递归都有自己的变量,但是并没有拷贝函数的代码。程序按顺序执行函数中的代码,而递归调用就相当于又从头开始执行函数的代码。
    6. 递归函数必须包含能让递归调用停止的语句。
  2. 尾递归
    最简单的递归形式是把递归调用置于函数的末尾,即正好在return语句前。这种形式的递归被称为尾递归,因为递归调用在函数的末尾。尾递归是最简单的递归形式,因为它相当于循环。
  3. 递归和倒序计算
    递归在处理倒序时非常方便,在解决这类问题中,递归比循环简单。
  4. 递归的优缺点
    优点:递归为某些编程问题提供了简单的解决方案。
    缺点:一些递归算法会快速消耗计算机的内存资源。

编译多源代码文件的程序

  1. 使用头文件

    • 如果把main()放在第一个文件中,把函数定义放在第二个文件中,那么第一个文件仍然要使用函数原型。把函数原型放在头文件中,就不用在每次使用函数文件时都写出函数的原型。C标准库就是这样做的。
    • 另外,程序中常用C预处理器定义的符号常量,通常也放入头文件中。

查找地址:&运算符

  • 指针是C语言最重要的(有时也是最复杂的)概念之一,用于存储变量的地址。
  • 一元&运算符给出变量的存储地址。用%p转换说明打印指针。
  • 常见的函数传参是传的值,这样做可以保护主调函数中的变量不会被意外修改(或防止原始变量被被调函数中的副作用意外修改)。

更改主调函数中的变量

但也有需要被调函数改变主调函数中的值的场合,鉴于return语句只能把被调函数中的一个值传回主调函数,所以这种情况只能使用指针实现。

指针简介

指针是一个值为内存地址的变量。
要创建指针变量,先要声明指针变量的类型。

  1. 间接运算符*
    使用间接运算符*找出存储在变量中的值,该运算符有时也称为解引用运算符(即与&相反)。

    //指针的一般使用流程
    ptr = &bah;
    val = *ptr;
  2. 声明指针

    • 声明指针的一般格式如下:
    int * pi;
    char * pc;
    float * pf, * pg;

    [data_type] * [var_name]格式,一般把* [var_name]看作一个整体,就跟声明普通变量一样,唯一区别就是指针(变量)名前加上间接运算符**和指针名之间的空格可有可无,通常在声明时使用空格,在解引用变量时省略空格。

    • 在大部分系统中,指针是一个无符号整数,但是不要把指针认为是整数类型,指针实际上是一个新类型,不是整数类型。
  3. 使用指针在函数间通信

    • 一般而言,在函数中可以把变量的两类信息传递给函数,即值和地址。
    • 给函数传递指针变量的一般流程如下:
    //交换两变量的值
    void swap(int * a, int * b);
    int a = 10, b = 20;
    
    int main(void)
    {
        swap(&a, &b);  //引用传值,传递地址
        return 0;
    }
    
    void swap(int * a, int * b)
    {
        int temp;
        temp = *a;  //解引用取出该地址的值
        *a = *b;  //解引用改变该地址的值
        *b = temp;
    }

    普通变量把值作为基本量,把地址作为通过&运算符获得的派生量;而指针变量把地址作为基本量,把值作为通过*运算符获得的派生量。

标签: C/C++, C-Primer-Plus

添加新评论