分享电子工程相关技术文档:设计技巧,应用笔记,参考设计,设计方案 经周立功教授授权,特对本书内容进行连载。第一章为程序设计基础,本文为1.2.3节数据的输入输出。 小编注:在开始阅读前,先来个小测试 以下语句执行后的输出结果是()
(a)5 (b)7 (c)9 (d)11 先记住你的答案,然后往下看。 周立功 1. printf()函数 printf()函数可以传递一个参数列表,它被看成由“控制字符串和其它参数”两部分组成。 这里的控制字符串是个字符串,其中可能包含转换格式符。转换格式是以一个%字符开头,且以一个转换字符结尾。比如,在转换格式符%d中,d是转换字符,将一个整型表达式的值打印成十进制整数的形式。为了在屏幕上打印字符串,可以使用下面的语句。比如: printf('abc'); 实现这个功能的另一种用法是使用下面这条语句。比如: printf('%s', 'abc'); 转换格式符%s使参数'abc'被打印成“字符串”的形式,还有一种方法也可以实现相同的目的。比如: printf('%c%c%c', 'a','b', 'c'); 其中的单引号表示字符常量,'a'就是与小写字母a对应的字符常量,转换格式符%c将表达式打印成“字符”的形式。 当一个参数被打印时,它的打印位置被称为“字段”,这个字段的字符数量被称为字段宽度,字段宽度可以在格式符%和转换字符之间指定。比如,%8c中的8,表示输出的字符少于8个,则用空格填充,目的是为了对齐。比如: printf('%c%3c%5c\n','a', 'b', 'c'); 其打印输出形式为: a b c 格式转换符%后面是小写字母x,与&iNum1相对应,将输入流中的字符解释为16进制整数,并将结果存储在&iNum1中。然后通过printf()查看地址的实际值,应该说,这不失为理解指针的一种非常简单而有效的方式。比如: printf('&iNum指针的值是%x\n', &iNum); printf('iNum变量的值是%x\n', iNum); 其它的转换格式符还有,%e将表达式打印成用科学计数法表示的浮点数的形式,%f将表达式打印成浮点数的形式,%g将表达式打印成e格式或f格式中更短的那种的形式。 2. scanf()函数当用户使用键盘将一些值输入到程序中时,会键入一串字符,这串字符就构成了输入流,由程序所接收。如果用户键入1234,用户可能将它当成一个十进制整数,但程序在接收时却将它当成一串字符。scanf()函数可以将一串十进制数字组成的字符串解释成一个整数值,并将它存储在内存中的一个适当位置。 scanf()的第一个参数是个控制字符串,其格式对应于输入流的字符的解释方式,这个函数的其它参数都是地址。比如: scanf('%d',&a); 格式符%d与表达式&a相对应,它将输入流中的字符解释为10进制整数,并将结果存储在a的地址中。 表达式&a理解为“a的地址”,因为&是取地址操作符。类似地: scanf('%x', &a); 格式符%x与表达式&a相对应,它将输入流中的字符解释为16进制整数,并将结果存储在a的地址中。除了%c、%s和%f(float浮点数)转换格式符之外,还有lf或LF(lf表示long float,即长浮点型)转换格式符,它将输入流中的字符解释为double类型浮点数。 在实际的应用中,用户不一定会按照程序的指令行事。当用户的输入与程序期望的输入不匹配时,将会导致程序运行失败。因此需要事先预料一些可能的输入错误,这样才能编出能检测并处理这些问题的程序。 假设编写一个处理非负数整数的循环,但用户很可能输入一个负数,则可以用关系表达式排除可能出现的错误。比如:
另一个潜在的问题是,用户可能输入错误类型的值,比如,字符q。排除这种情况的一种方法是检查scanf()的返回值。回忆一下,scanf()返回成功读取项的个数,因此下面的表达式但且仅当用户输入一个整数时才为真。比如: scanf('%1d', &n)== 1 结合上面的while循环,循环条件可以描述为“当输入是一个整数且整数为正时”,可以改进为:
3. 预处理器指令如果要将程序转化为机器可执行的形式,对于C程序来说,通常包含以下三个步骤:
由于预处理器与编译器集成在一起,因此人们可能不会注意它在工作。为了便于查看变量的地址和变量的值的存储关系,在这里引入预定义的宏替换。预处理器的行为是由指令来控制的,它是以#符号开头的源文件行,分别为宏定义(即#define)、文件包含(即#inlcue)与条件编译(即#if、#ifdef、#ifndef、#elif、#else与#endif),而不包含预处理命令的行称为源程序文本行。 在C语言中预定义了一些宏,这些宏的名字都是以两个下划线字符开始和结束的。其中的“_FUNCTION_”表示当前所在的函数名,它实际上是一个代码块作用域变量,而不是一个宏,它提供了外层函数的名称,用于程序调试和异常信息报告。 (1)带参数的宏 带参数的宏定义的格式如下: #define 标识符(x1, x2, …, xn) 替换列表 其中,x1…xn是宏的参数,宏命令总是在第一个换行符处结束,如果要在下一行继续宏命令,则必须在当前行的末尾使用“\”续行符,即将下一行看作本行的继续。 假设将宏定义为下面的形式: #define mult(x, y) (x) *(y) 根据这个定义4 / mult(2, 2)被展开为“4 / (2)*(2)”,由于优先级的关系,它并不等同于表达式“4 / ((2)*(2))”。注意,标识符与左边的括号之间不能有空格,否则与预想的结果千差万别。比如: #define SQ (x) ((x)*(x)) SQ(7)中的SQ为(x) ((x)*(x)),SQ(7)展开后为(x) ((x)*(x)) (7)。 同时也不能用分号来结束#define定义,否则分号就会成为替换字符串的一部分。比如: #define SQ(x) ((x)*(x)); 则“x = SQ(y);”被替换为“x= ((y)*(y));;”。如果有以下定义:
此时,这个额外的分号分离了if和else语句,从而产生语法错误。 带参数宏的展开过程是招聘软件工程师经常考察的经典试题,比如,以下语句执行后的输出结果是(b)
(a)5 (b)7 (c)9 (d)11 解题思路:MOD(b, a+4)即就是MOD(94,13 + 4),展开后相当于“94%13+4”。由于操作符的优先级,它并不等同于(94)%(13 +4)。其本意是“94%17”,因此必须用括号将“x%y”中的x、y单独括起来。即: #define MOD(x, y) (x) % (y) (2)#运算符 #运算符将一个带宏的参数转换为字符串常量,它仅允许出现在带参数的宏的替换列表中。假设在调试过程中使用PRINT_INT宏作为一个便捷的方法输出一个整型变量或表达式的值,#运算符可以使PRINT_INT为每个输出的值添加标签。即: #define PRINT_INT(i) printf(#i '=%d\n', i) 即i之前的#运算符通知预处理器根据PRINT_INT的参数创建一个字符串常量,因此调用“PRINT_INT(m/n);”等价于“printf('m/n' '=%d\n', m/n);”。由于相邻的字符串会被合并,因此上面的语句等价于 printf('m/n= %d\n', m/n); 当执行printf()函数时,则同时显示表达式m/n和它的值。比如,当m = 13、n = 5时,则输出为“m/n = 2”。因此可以用宏替换程序清单 1.3中相关的语句的代码: #definePRINT_INT(i) \ printf('%8s():&%-5s = 0x%-6x,%-5s = 0x%-6x\n', __FUNCTION__, #i, &(i), #i, i); 编译器在编译程序时,会将“编译器预定义的宏'__FUNCTION__'”替换为代码所在的函数名。“%8s():”中“%”后面的“8”表示,如果输出的字符少于8个,则用空格填充,目的是便于对齐,“s”表示输出字符串"main",即printf()函数输出“main():”。 “&%-5s= 0x%-6x,”表示首先输出第1个字符'&',“-”表示输出左对齐,右边填充空格,“5”为输出的宽度,接着输出第2个字符'=',然后输出第3个字符“0x”,从第4个字符开始输出变量i的地址&i,“6”后面的“x”表示输出16进制数,详见程序清单 1.7。
特别声明,本书使用的是windows系统下的GNU开发环境MinGW,而gcc是一种广泛使用的C语言编译器,使用gccfor MinGW编译运行的结果详见图1.6。 |
|