第四章摘抄笔记The C Programming Lanuage

第四章 函数与程序结构

函数可以把大的计算任务分解成若干个较小的任务。一个设计得当的函数可以把程序中不需要了解的具体操作细节隐藏起来,从而使整个程序结构更加清晰,并降低修改程序的难度。

4.1 函数的基础知识

函数的定义形式如下:

返回值类型函数明(参数声明表)
{
声明和语句
}

函数定义中的各构成部分都可以省略。最简单的函数如下所示:

dummy() {}

如果函数定义中省略了返回值类型,则默认为int类型。

函数在源文件中出现的次序可以是任意的。只要保证每一个函数不被分离到多个文件中,源程序就可以分成多个文件。

被调用函数通过return语句向调用者返回值,return语句的后面可以跟任何表达式:return 表达式;在必要时,表达式将被转换为函数的返回值类型。表达式两边通常加一对圆括号,此处的括号是可选的。

4.3 外部变量

外部变量定义在函数之外,因此可以在许多函数中使用。由于C语言不允许在一个函数中定义其它函数,因此函数本身是“外部的”。默认情况下,外部变量与函数具有下列性质:通过同一个名字对外部变量的所有引用(即使这种引用来自于单独编译的不同函数)实际上都是引用同一个对象(标准中把这一性质称为外部链接)。

因为外部变量可以在全局范围内访问,这就为函数之间的数据交换提供了一种可以代替函数参数与返回值的方式。任何函数都可以通过名字访问一个外部变量,当然这个名字需要通过某种方式进行声明。

4.4 作用域规则

名字的作用域指的是程序中可以使用该名字的部分。对于在函数开头声明的自动变量来说,其作用域是声明该变量名的函数。不同函数中声明的具有相同名字的各个局部变量之间没有任何关系。函数的参数也是这样的,实际上可以将它看作是局部变量。

外部变量或函数的作用域从声明它的地方开始,到其所在的(待编译的)文件的末尾结束。

先声明后使用:对于一个外部变量或者函数,必须在main函数之前声明它,main函数才可以对其进行调用,否则不能都调用。

如果要在外部变量的定义之前使用该变量,或者外部变量的定义与变量的使用不在同一个源文件中,则必须在相应的变量声明中强制性地使用关键字extern。

将外部变量的声明与定义严格区分开来很重要。变量声明用于说明变量的属性(主要是变量的类型),而变量定义除此以外还将引起存储器的分配。如果将下列语句放在所有函数的外部:

int sp;
double val[MAXVAL];

那么这两条语句将定义外部变量sp与val,并为之分配存储单元,同时这两条语句还可以作为该源文件中其余部分的声明。而下面的两行语句:

extern int sp;
extern double val[];

为源文件的其余部分声明了一个int 类型的外部变量sp 以及一个double 数组类型的外部变量val(该数组的长度在其它地方确定),但这两个声明并没有建立变量或为它们分配存储单元。

4.5 头文件

每个模块的函数和变量放在单独的文件中,还必须考虑定义和声明在这些文件之间的共享问题。我们尽可能把共享的部分集中在一起,这样就只需要一个副本,改进程序时也容易保证程序的正确性。我们把这些公共部分放在一个头文件中,在需要使用该头文件时通过#include 指令将它包含进来。

在这个头文件中对所有的函数进行声明,在每个单独的文件中包括头文件,然后对相应的函数进行具体的定义。

对于某些中等规模的程序,最好只用一个头文件存放程序中各部分共享的对象。较大的程序需要使用更多的头文件,我们需要精心地组织它们。

4.6 静态变量

用static声明限定外部变量与函数,可以将其后声明的对象的作用域限定为被编译源文件的剩余部分,相当于将一个外部变量改为了一个内部变量。通过static 限定外部对象,可以达到隐藏外部对象的目的。这样,变量和函数的名字不会和同一程序中的其它文件中的相同的名字相冲突。

static也可用于声明内部变量。static类型的内部变量同自动变量一样,是某个特定函数的局部变量,只能在该函数中使用,但它与自动变量不同的是,不管其所在函数是否被调用,它一直存在,而不像自动变量那样,随着所在函数的被调用和退出而存在和消失。换句话说,static类型的内部变量是一种只能在某个特定函数中使用但一直占据存储空间的变量。

4.7 寄存器变量

register 声明告诉编译器,它所声明的变量在程序中使用频率较高。其思想是,将register 变量放在机器的寄存器中,这样可以使程序更小、执行速度更快。但编译器可以忽略此选项。

register声明只适用于自动变量以及函数的形式参数

4.8 程序块结构

C语言不允许在函数中定义函数。但是,在函数中可以以程序块结构的形式定义变量。变量的声明(包括初始化)除了可以紧跟在函数开始的花括号之后,还可以紧跟在任何其它标识复合语句开始的左花括号之后。以这种方式声明的变量可以隐藏程序块外与之同名的变量,它们之间没有任何关系,并在与左花括号匹配的右花括号出现之前一直存在。

在一个好的程序设计风格中,应该避免出现变量名隐藏外部作用域中相同名字的情况,否则,很可能引起混乱和错误。

4.9 初始化

在不进行显式初始化的情况下,外部变量和静态变量都将被初始化为0,而自动变量和寄存器变量的初值则没有定义(即初值为无用的信息)。定义变量时,可以在变量名后紧跟一个等号和一个表达式来初始化变量。

对于外部变量与静态变量来说,初始化表达式必须是常量表达式,且只初始化一次(从概念上讲是在程序开始执行前进行初始化)。对于自动变量与寄存器变量,则在每次进入函数或程序块时都将被初始化。

对于自动变量与寄存器变量来说,初始化表达式可以不是常量表达式:表达式中可以包含任意在此表达式之前已经定义的值,包括函数调用。

4.10 递归

C语言中的函数可以递归调用,即函数可以直接或间接调用自身。

递归并不节省存储器的开销,因为递归调用过程中必须在某个地方维护一个存储处理值的栈。递归的执行速度并不快,但递归代码比较紧凑,并且比相应的非递归代码更易于编写与理解。在描述树等递归定义的数据结构时使用递归尤其方便。

4.11 C预处理器

从概念上讲,预处理器是编译过程中单独执行的第一个步骤。两个最常用的预处理器指令是:#include 指令(用于在编译期间把指定文件的内容包含进当前文件中)和#define指令(用任意字符序列替代一个标记)。

11.1文件包含
文件包含指令(即#include指令)使得处理大量的#define指令以及声明更加方便。在源文件中,任何形如:

#include "文件名"#include <文件名>

的行都将被替换为由文件名指定的文件的内容。如果文件名用引号引起来,则在源文件所在位置查找该文件;如果在该位置没有找到文件,或者如果文件名是用尖括号<与>括起来的,则将根据相应的规则查找该文件,这个规则同具体的实现有关。被包含的文件本身也可包含#include指令。

11.2 宏替换
宏定义的形式如下:

#define 名字替换文本

后续所有出现名字记号的地方都将被替换为替换文本。

define 指令定义的名字的作用域从其定义点开始,到被编译的源文件的末尾处结束。宏定义中也可以使用前面出现的宏定义。替换只对记号进行,对括在引号中的字符串不起作用。

11.3 条件包含
可以使用条件语句对预处理本身进行控制,这种条件语句的值是在预处理执行的过程中进行计算。

if语句对其中的常量整型表达式(其中不能包含sizeof、类型转换运算符或enum常量)进行求值,若该表达式的值不等于0,则包含其后的各行,直到遇到#endif、#elif或#else 语句为止(预处理器语句#elif 类似于else if)。在#if 语句中可以使用表达式defined(名字),该表达式的值遵循下列规则:当名字已经定义时,其值为1;否则,其值为0。

C语言专门定义了两个预处理语句#ifdef与#ifndef,它们用来测试某个名字是否已经定义。

你可能感兴趣的:(C语言学习摘抄笔记)