【C语言学习笔记】《C程序设计语言》 第4章(函数与程序结构)——第1节(函数、外部变量、作用域规则)

Warning:
为了避免非零基础人群感到身体不适、头晕恶心、易怒及粗口,请不要查看以下内容。

这一章我们开始学习函数与程序结构,会遇到很多新的概念,难度会有所增加,但只要掌握好基础,学习起来还是能够游刃有余的。学习完本章,我们就已经学习完C语言的大部分内容了。

文章目录

  • 第4章 函数与程序结构
    • 4.1 函数的基本知识
    • 4.2 返回非整数型值的函数
    • 4.3 外部变量
    • 4.4 作用域规则
  • 总结

第4章 函数与程序结构

函数可以把大的计算任务分解成若干个较小的任务,程序设计人员可以基于函数进一步构造程序,而不需要重新编写一些代码。一个设计得当的函数可以把程序中不需要了解的具体操作细节隐藏起来,从而使整个程序结构更加清晰,并降低修改程序的难度。
C语言在设计中考虑了函数的高效性与易用性这两个因素。C语言程序一般由许多小的函数组成,而不是由少量较大的函数组成。一个程序可以保存在一个或者多个源文件中。各个文件可以单独编译,并可以与库中已编译过的函数一起加载。我们在这里不打算详细讨论这一过程,因为编译与加载的具体实现细节在各个编译系统中并不相同。
ANSI标准对C语言所做的最明显的修改是函数声明与函数定义这两方面。第1章中我们曾经讲过,目前C语言已经允许在声明函数时声明参数的类型。为了使函数的声明与定义相适应,ANSI标准对函数定义的语法也做了修改。基于该原因,编译器就有可能检测出比以前的C语言版本更多的错误。并且,如果参数声明得当,程序可以自动地进行适当的强制类型转换。
ANSI标准进一步明确了名字的作用域规则,特别要求每个外部对象只能有一个定义。初始化的适用范围也更加广泛了,自动数组与结构都可以进行初始化。
C语言预处理器的功能也得到了增强。新的预处理器包含一组更完整的条件编译指令(一种通过宏参数创建带引号的字符串的方法),对宏拓展过程的控制更严格。

4.1 函数的基本知识

首先,我们设计一个程序,它将输入中包含特定字符串的各行打印出来。
例如,在下面的文本行中查找包括“ould”的行:

Ah Love! could you and I with Fate conspire
To grasp this sorry Scheme of Things entire,
Would not we shatter it to bits – and then
Re-mould it nearer to the Heart’s Desire!

程序执行后输出:

Ah Love! could you and I with Fate conspire
Would not we shatter it to bits – and then
Re-mould it nearer to the Heart’s Desire!

按照我们前面所学的知识,该任务可以明确划分为下列3个部分:

while(还有未处理的行)
if(该行包含指定的字符串)
                 打印该行

我们以往的做法是把全部代码放入main函数,但更好的做法是利用其结构把每一部分都设计成一个独立的函数。分别处理3个小的部分比处理1个大的部分更容易,因为这样可以把不相关的细节隐藏在函数中,从而减少了不必要的相互影响机会,而且,这些函数也可以在其它程序中使用。

我们可以使用getline实现“还有未处理的行”,该函数第一章已经写过,用print实现“打印该行”,这个函数是现成的。我们只需要再编写一个判断"该行包含指定的字符串"的函数。

我们编写函数strindex(s, t)实现该目标。该函数返回字符串t在字符串s中出现的起始位置或索引。当s不包含t时,函数返回-1。使用函数的好处是:如果以后需要进行更复杂的字符串匹配,只需替换strindex函数即可,其余程序部分保持不变,大大降低了程序的维护难度。

代码如下:

#include 
#define MAXLINE 1000

int getline(char line[], int max);
int strindex(char source[], char searchfor[]);

char pattern[] = "ould";

/* 找出所有匹配的行 */
int main()
{
	char line[MAXLINE];
	int found = 0;

	while(getline(line, MAXLINE) > 0)
		if (strindex(line, pattern) >= 0) {
			printf("%s", line);
			found++;
		}
	return found;
}

/* getline函数:将行保存到s中,并返回该行的长度 */
int getline(char s[], int lim)
{
	int c, i;
	i = 0;
	
	while (--lim > 0 && (c = getchar()) != EOF && c != '\n')
		s[i++] = c;
	if (c == '\n')
		s[i++] = c;
	s[i] = '\0';
	return i;
}

/* strindex函数:返回t在s中的位置,若未找到则返回-1 */
int strindex(char s[], char t[])
{
	int i, j, k;

	for (i = 0; s[i] != '\0'; i++) {
		for (j = i, k = 0; t[k] != '\0' && s[j] == t[k]; j++, k++)
			;
		if (k > 0 && t[k] == '\0')
			return i;
	}
	return -1;
}

函数的定义形式如下:

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

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

dummy() {}

该函数不执行任何操作也不返回任何值。这种不执行任何操作的函数有时很有用,它可以在程序开发期间用以保留位置。如果函数定义中省略了返回值的类型,则默认为int类型。

程序可以看作是变量定义和函数定义的集合。函数之间的通信可以通过参数、函数返回值以及外部变量进行。函数在源文件中出现的次序可以是任意的。只要保证每一个函数不被分离到多个文件中,源程序就可以分成多个文件。

被调用函数通过return语句向调用者返回值,return语句的后面可以跟任何表达式:

return 表达式;

在必要时,表达式将被转换为函数的返回值类型。表达式两边通常加一对圆括号,此处的括号是可选的。

4.2 返回非整数型值的函数

到目前为止,我们所讨论的函数都是不返回任何值(void)或只返回int类型值的函数。假如某个函数必须返回其它类型的值,该怎么办?许多数值函数返回的是double类型的值,某些专用函数则返回其它类型的值。

这里的函数和我们所学的返回int类型的值的函数基本一致,但有一点值得注意,我们在调用返回非整数型值的函数前一定要声明它的返回值类型,否则,编译器将默认它返回int型返回值。

4.3 外部变量

C语言程序可以看成由一系列的外部对象构成,这些外部对象可能是变量或函数。形容词external与internal是相对的,internal用于描述定义在函数内部的函数参数及变量。外部变量定义在函数外,因此可以在许多函数中使用。由于C语言不允许在一个函数中定义其它函数,因此函数本身是“外部的”。

默认情况下,外部变量与函数具有下列性质:
通过同一个名字对外部变量的所有引用(即使这种引用来自于单独编译的不同函数)实际上都是引用同一个对象(标准中把这一性质称为外部链接)。我们将在后面介绍如何定义只能在某一个源文件中使用的外部变量与函数。

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

如果函数之间需要共享大量是变量,使用外部变量要比使用一个很长的参数表更方便、有效。但是,我们在第一章中就指出,这样做必须非常谨慎,因为这种方式可能对程序结构产生不良的影响,而且可能会导致程序中各个函数之间具有太多的数据联系。

外部变量的用途还表现在它们与内部变量相比有更大的作用域和更长的生存期。自动变量只能在函数内部使用,从其所在函数被调用时变量开始存在,在函数退出时变量也将消失。而外部变量是永久存在的,它们的值在一次函数调用到下一次函数调用之间保持不变。因此,如果两个函数必须共享某些数据,而这两个函数互不调用对方,这种情况下最方便的方式便是把这些共享数据定义为外部变量,而不是作为函数参数传递。

4.4 作用域规则

构成C语言程序的函数与外部变量可以分开进行编译。一个程序可以存放在几个文件中,原先已经编译过的函数可以从库中进行加载

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

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

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

将外部变量的声明定义严格区分开来很重要。变量声明用于说明变量的属性(主要是变量的类型),而变量定义除此以外还引起存储器的分配。

在一个源程序的所有源文件中,一个外部变量只能在某个文件中定义一次,而其它文件可以通过extern声明来访问它(定义外部变量的源文件中也可以包含对该外部变量的extern声明)。外部变量的定义中必须指定数组的长度,但extern声明则不一定要指定数组的长度。

总结

以上就是本节学习的主要内容,我们学习了 函数的定义与声明等相关基本知识、外部变量与作用域的相关知识,下节课我们将会继续学习本章的剩余内容。到目前,我们开始接触了许多对于初学者比较陌生的新概念,可能会被它的表面所迷惑,但是经过长时间的深化练习,一定会熟练掌握。

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