2022-11-13

分析程序

程序遵循模块化的编程思想, 使用独立函数(模块) 来验证输入和管理显示。 程序越大, 使用模块化编程就越重要。

main()函数管理程序流, 为其他函数委派任务。 它使用 get_long()获取值、 while 循环处理值、 badlimits()函数检查值是否有效。

输入流和数字

is 28 12.4

对 C程序而言, 这是一个字节流。 第1个字节是字母i的字符编码, 第2个字节是字母s的字符编码, 第3个字节是空格字符的字符编码, 第4个字节是数字2的字符编码。如果get_long()函数处理这一行输入, 第1个字符是非数字, 那么整行输入都会被丢弃, 包括其中的数字。

42

如果在scanf()函数中使用%c转换说明, 它只会读取字符4并将其储存在char类型的变量中。 如果使用%s转换说明, 它会读取字符4和字符2这两个字符, 并将其储存在字符数组中。 如果使用%d转换说明, scanf()同样会读取两个字符, 但是随后会计算出它们对应的整数值: 4×10+2, 即42, 然后将表示该整数的二进制数储存在 int 类型的变量中。

如果使用%f 转换说明,scanf()也会读取两个字符, 计算出它们对应的数值42.0, 用内部的浮点表示法表示该值, 并将结果储存在float类型的变量中。

输入由字符组成, 但是scanf()可以把输入转换成整数值或浮点数值。 使用转换说明(如%d或%f) 限制了可接受输入的字符类型, 而getchar()和使用%c的scanf()接受所有的字符。

菜单管理

用户输入程序所列选项之一, 然后程序根据用户所选项完成任务。 作为一名程序员, 自然希望这一过程能顺利进行。 因此, 第1个目标是: 当用户遵循指令时程序顺利运行; 第2个目标是: 当用户没有遵循指令时, 程序也能顺利运行。 显而易见, 要实现第 2 个目标难度较大, 因为很难预料用户在使用程序时的所有错误情况。

现在的应用程序通常使用图形界面, 可以点击按钮、 查看对话框、 触摸图标, 而不是我们示例中的命令行模式。 但是, 两者的处理过程大致相同:给用户提供选项、 检查并执行用户的响应、 保护程序不受误操作的影响。 除了界面不同, 它们底层的程序结构也几乎相同。 但是, 使用图形界面更容易通过限制选项控制输入。

任务

一个菜单程序需要执行哪些任务。 它要获取用户的响应, 根据响应选择要执行的动作。 另外, 程序应该提供返回菜单的选项。C 的 switch 语句是根据选项决定行为的好工具, 用户的每个选择都可以对应一个特定的case标签。 使用while语句可以实现重复访问菜单的功能。

使执行更顺利

顺利运行指的是, 处理正确输入和错误输入时都能顺利运行。

get_choice()的用法和getchar()相同, 两个函数都是获取一个值, 并与终止值(该例中是'q') 作比较。

混合字符和数值输入

关键概念

C程序把输入作为传入的字节流。 getchar()函数把每个字符解释成一个字符编码。 scanf()函数以同样的方式看待输入, 但是根据转换说明, 它可以把字符输入转换成数值。 许多操作系统都提供重定向, 允许用文件代替键盘输入, 用文件代替显示器输出。

程序通常接受特殊形式的输入。对于一个小型程序, 输入验证可能是代码中最复杂的部分。

本章小结

许多程序使用 getchar()逐字符读取输入。 通常, 系统使用行缓冲输入,即当用户按下 Enter 键后输入才被传送给程序。 按下Enter键也传送了一个换行符, 编程时要注意处理这个换行符。 ANSI C把缓冲输入作为标准。

通过标准I/O包中的一系列函数, 以统一的方式处理不同系统中的不同文件形式, 是C语言的特性之一。 getchar()和 scanf()函数也属于这一系列。当检测到文件结尾时, 这两个函数都返回 EOF(被定义在stdio.h头文件中) 。 在不同系统中模拟文件结尾条件的方式稍有不同。

许多操作系统(包括UNIX和DOS) 都有重定向的特性, 因此可以用文件代替键盘和屏幕进行输入和输出。 读到EOF即停止读取的程序可用于键盘输入和模拟文件结尾信号, 或者用于重定向文件。

混合使用 getchar()和 scanf()时, 如果在调用 getchar()之前, scanf()在输入行留下一个换行符, 会导致一些问题。



函数

函数(function) 是完成特定任务的独立程序代码单元。 语法规则定义了函数的结构和使用方式。 虽然C中的函数和其他语言中的函数、 子程序、 过程作用相同, 但是细节上略有不同。

使用函数可以省去编写重复代码的苦差。 如果程序要多次完成某项任务, 那么只需编写一个合适的函数, 就可以在需要时使用这个函数, 或者在不同的程序中使用该函数, 就像许多程序中使用putchar()一样。函数让程序更加模块化, 从而提高了程序代码的可读性, 更方便后期修改、 完善。

还要编写4个函数readlist()、 sort()、 average()和bargraph()的实现细节。 描述性的函数名能清楚地表达函数的用途和组织结构。

创建并使用简单函数

分析程序

程序在3处使用了starbar标识符: 函数原型(function prototype) 告诉编译器函数starbar()的类型; 函数调用(function call) 表明在此处执行函数;函数定义(function definition)。

函数和变量一样, 有多种类型。 任何程序在使用函数之前都要声明该函数的类型。

void starbar(void);

圆括号表明starbar是一个函数名。 第1个void是函数类型, void类型表明函数没有返回值。 第2个void(在圆括号中) 表明该函数不带参数。 分号表明这是在声明函数, 不是定义函数。 也就是说, 这行声明了程序将使用一个名为starbar()、 没有返回值、 没有参数的函数, 并告诉编译器在别处查找该函数的定义。

void starbar();

一般而言, 函数原型指明了函数的返回值类型和函数接受的参数类型。

这些信息称为该函数的签名(signature) 。 对于starbar()函数而言, 其签名是该函数没有返回值, 没有参数。

starbar();

程序中strarbar()和main()的定义形式相同。 首先函数头包括函数类型、函数名和圆括号, 接着是左花括号、 变量声明、 函数表达式语句, 最后以右花括号结束 。 注意, 函数头中的starbar()后面没有分号, 告诉编译器这是定义starbar(), 而不是调用函数或声明函数原型。

程序把 starbar()和 main()放在一个文件中。把函数都放在一个文件中的单文件形式比较容易编译, 而使用多个文件方便在不同的程序中使用同一个函数。 如果把函数放在一个单独的文件中, 要把#define 和#include 指令也放入该文件。

starbar()函数中的变量count是局部变量(local variable) , 意思是该变量只属于starbar()函数。 可以在程序中的其他地方(包括main()中) 使用count, 这不会引起名称冲突, 它们是同名的不同变量。

如果把starbar()看作是一个黑盒, 那么它的行为是打印一行星号。 不用给该函数提供任何输入, 因为调用它不需要其他信息。 而且, 它没有返回值, 所以也不给 main()提供(或返回) 任何信息。 简而言之, starbar()不需要与主调函数通信。

函数参数

show_n_char()(显示一个字符n次) 。 唯一要改变的是使用内置的值来显示字符和重复的次数, show_n_char()将使用函数参数来传递这些值。

show_n_char()与starbar()很相似, 但是show_n_char()带有参数。

定义带形式参数的函数

void show_n_char(char ch, int num)

该行告知编译器show_n_char()使用两个参数ch和num, ch是char类型,num是int类型。 这两个变量被称为形式参数(formal argument, 但是最近的标准推荐使用formal parameter) , 简称形参。 和定义在函数中变量一样, 形式参数也是局部变量, 属该函数私有。 这意味着在其他函数中使用同名变量不会引起名称冲突。 每次调用函数, 就会给这些变量赋值。

声明带形式参数函数的原型

void show_n_char(char ch, int num);

当函数接受参数时, 函数原型用逗号分隔的列表指明参数的数量和类型。

void show_n_char(char, int);

在原型中使用变量名并没有实际创建变量, char仅代表了一个char类型的变量,

调用带实际参数的函数

在函数调用中, 实际参数(actual argument, 简称实参) 提供了ch和num的值。

实际参数可以是常量、 变量, 或甚至是更复杂的表达式。 无论实际参数是何种形式都要被求值, 然后该值被拷贝给被调函数相应的形式参数。

实际参数是出现在函数调用圆括号中的表达式。 形式参数是函数定义的函数头中声明的变量。 调用函数时, 创建了声明为形式参数的变量并初始化为实际参数的求值结果。

使用return从函数中返回值

关键字return后面的表达式的值就是函数的返回值。该函数返回的值就是变量min的值。 因为min是int类型的变量, 所以imin()函数的类型也是int。

变量min属于imin()函数私有, 但是return语句把min的值传回了主调函数。

函数调用imin(evil1, evil2)只是把两个变量的值拷贝了一份。返回值不仅可以赋给变量, 也可以被用作表达式的一部分。

实际得到的返回值相当于把函数中指定的返回值赋给与函数类型相同的变量所得到的值。

return语句返回确实int类型的值1。 return 语句的另一个作用是, 终止函数并把控制返回给主调函数的下一条语句。

函数类型

声明函数时必须声明函数的类型。 带返回值的函数类型应该与其返回值类型相同, 而没有返回值的函数应声明为void类型。

类型声明是函数定义的一部分。函数类型指的是返回值的类型, 不是函数参数的类型。

ANSI C标准库中, 函数被分成多个系列, 每一系列都有各自的头文件。

ANSIC函数原型

问题所在

第1次调用printf()时省略了imax()的一个参数, 第2次调用printf()时用两个浮点参数而不是整数参数。

主调函数把它的参数储存在被称为栈(stack) 的临时存储区, 被调函数从栈中读取这些参数。主调函数根据函数调用中的实际参数决定传递的类型, 而被调函数根据它的形式参数读取值。

ANSI的解决方案

针对参数不匹配的问题, ANSI C标准要求在函数声明时还要声明变量的类型, 即使用函数原型(function prototype) 来声明函数的返回类型、 参数的数量和每个参数的类型。

无参数和未指定参数

void print_name();

一个支持ANSI C的编译器会假定用户没有用函数原型来声明函数, 它将不会检查参数。

void print_name(void);

支持ANSI C的编译器解释为print_name()不接受任何参数。 然后在调用该函数时, 编译器会检查以确保没有使用参数。

一些函数接受(如, printf()和scanf()) 许多参数。

函数原型的优点

函数原型是C语言的一个强有力的工具, 它让编译器捕获在使用函数时可能出现的许多错误或疏漏。

有一种方法可以省略函数原型却保留函数原型的优点。之所以使用函数原型, 是为了让编译器在第1次执行到该函数之前就知道如何使用它。 因此, 把整个函数定义放在第1次调用该函数之前, 也有相同的效果。 此时, 函数定义也相当于函数原型。

// 下面这行代码既是函数定义, 也是函数原型

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

int main()

{ int

x, z;

...

z = imax(x, 50);

...

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容

  • 变量命名 1.大小写敏感2.只能以字母、下划线、数字组成(数字不能开头)3.见名知意4.蛇形、小驼峰、大驼峰规则5...
    Ache_feee阅读 85评论 0 0
  • 《刻意练习:如何成为一个高手》是美国作家-道格·莱莫夫的著作之一。他是哈佛商学院工商管理硕士,美国畅销书作家、权威...
    钟罗敏阅读 234评论 0 0
  • 用数组(array) 储存字符串(character string) 。 在该程序中, 用户输入的名被储存在数组中...
    4d29e26b2bce阅读 375评论 0 0
  • JAVASE基础总结 Java运行环境 安装JDK文件, 进入计算机环境变量 添加新变量JAVA_HOME,变量值...
    半俗半雅_f42a阅读 257评论 0 0
  • 示例程序 如果输入程序时打错(如, 漏了一个分号) , 编译器会报告语法错误消息。 然而, 即使输入正确无误, 编...
    4d29e26b2bce阅读 686评论 0 0