文章目录
助记提要
- 转换说明的格式;
- scanf处理输入的过程;
- 除法操作的注意事项;
- 运算符的结合性;
- 不在子表达式中输入操作数的原因;
- 表达式允许用作语句的问题;
3章 格式化输入/输出
3.1 printf函数
printf函数用来显示格式串的内容,并在该串中指定位置插入要显示的值。
printf(格式串, 表达式1, 表达式2, ...);
表达式表示用来插到指定位置的值,也可以是常量、变量。
格式串包含普通字符和转换说明。普通字符会按照它们在字符串中的样子显示出来。
转换说明以字符%
开头,表示待填充的值的占位符。跟随在%
后的信息指定了把数值从二进制形式转换为打印格式的方法。
转换说明的数量应该和表达式的数量匹配。表达式不够时,多余的转换说明会显示无意义的数值;表达式过多时,多出的项的值不会显示。
转换说明
转换说明的格式为:%m.pX
。
m
表示显示数值的区域的最小宽度。可以省去。表示小数时,m
指定的宽度包括小数点在内。
X
是转换指定符,表示显示时需要对数值做什么转换。显示数值,常用的转换指定符有:
d
,转换为十进制整数;e
,转换为指数形式的浮点数;f
,转换为定点十进制形式的浮点数;g
,根据浮点数的大小,转换为指数形式或定点十进制形式的浮点数;
p
表示精度,转换指定符不同,含义也不同。对于d
是表示待显示数值的最少个数,默认为1;对于e
、f
,表示小数点后应该出现的数字个数,默认为6;对于g
,表示可以显示的有效数字的最大数量。
p
也可以不写,不写的时候需要把小数点去掉。
printf("|%d|\n", 5);
// 宽度10(默认右对齐)
printf("|%10d|\n", 5);
// 宽度10,左对齐
printf("|%-10d|\n", 5);
// 宽度10,至少显示三位数字,不足的用0补齐
printf("|%10.3d|\n", 5);
// 宽度10,小数部分3位数字
printf("|%10.3f|\n", 12.3f);
printf("|%10.3e|\n", 12.3f);
// 不同的p对g转换显示的影响
printf("|%10.2g|\n", 12.3f);
printf("|%10.3g|\n", 12.3f);
printf("|%10.4g|\n", 12.3f);
|5|
| 5|
|5 |
| 005|
| 12.300|
|1.230e+001|
| 12|
| 12.3|
| 12.3|
转义序列
转义序列可以使字符串中包含一些特殊字符,包括非打印字符和对编译器有特殊意义的字符。
这些转义序列出现在格式串中时,表示在显示时执行的操作。
\a
,警报符,产生鸣响;
\b
,回退符;
\n
,换行符,光标跳到下一行位置;
\t
,制表符,光标移动到下一个制表符位置;
使用两个连续的字符%
,可以在输出结果中显示%
符。
3.2 scanf函数
scanf函数根据特定的格式读取输入,转换后赋值给对应的变量。
scanf(格式串, &变量1, &变量2, ...);
scanf的格式串也可以包括普通字符和转换说明两部分。有时候格式串可以只包含转换说明符。
符号&
需要常常放在被赋值的每个变量前面。忽略&
符号会出现不可预知的结果,导致程序崩溃。
处理转换说明符
scanf从左往右开始处理格式串中的信息。
对于每一个转换说明,scanf从输入数据中定位适当类型的项,并在必要时跳过空白字符(空格、换行符、制表符)。
读取数据项时,遇到不可能属于此项的字符时才会停止。这个不属于数据项的字符会被放回原处,扫描下一次输入项或下一次调用scanf函数时再次读入。
如果数据项读入成功,就继续处理格式串的剩余部分;如果某一项读取失败,就立即返回不继续读取。
处理普通字符
scanf处理普通字符时,需要看这个普通字符是不是空白字符。
格式串中有一个或多个连续的空白字符,scanf会从输入中读取空白字符,直到遇到第一个非空字符为止。格式串中的一个空白字符可以与输入中任意数量(包括0个)的空白字符匹配。
格式串中的非空字符,如果与scanf收到的下一个字符匹配,就继续往后处理格式串;如果不匹配,scanf就把不匹配的字符放回,然后退出,不再继续读取。下一个scnaf函数会这个从不匹配的字符开始读取。
注意 scanf的格式串末尾如果写了空白字符(尤其是\n
),可能会导致交互式程序一直挂起,直到用户输入一个非空白字符为止。因为scanf无法以换行和空格确认格式串处理结束。
4章 表达式
表达式是表示如何计算值的公式。
4.1 算术运算符
一元运算符:正号+
,负号-
。
二元运算符:加法+
,减法-
,乘法*
,除法/
,取余%
。
一元运算符只需要一个操作数,二元运算符需要两个操作数。
注意
%
要求两个操作数都是整数。- 其他二元运算符的操作数既可以是整数也可以是浮点数。混合使用时,结果是
float
类型。 /
和%
的右操作数不能是0。/
的两个操作数都是整数时,它会截取整数部分作为结果。/
或%
用于负操作数时,结果难以确定。
如果有一个操作数是负数,C89标准中,/
的结果可以向上取整也可以向下取整;C99中除法的结果总是趋0截断的。
C89和C99都要确保(a / b) \* b + a % b
的结果总是等于a。%
的结果的负号与左操作数相同。
由实现定义的行为 C标准故意对C语言的某些部分未加指定,并认为其细节可以由具体实现来定。即由程序在特定平台上编译、链接、执行时用到的软件来定。
写程序的时候,要避免依赖由实现定义的行为。
运算符的优先级和结合性
算数运算符的优先级(1最高):
- 正号
+
,负号-
*
、/
、%
- 加号
+
、减号-
不同运算符在计算时有不同的优先级。
多个运算符出现在同一个表达式时,按运算符的优先级从高到低计算。
相同优先级的运算符,按运算符的结合性确定计算顺序。
从左向右结合的运算符,称为左结合的运算符(二元运算符加、减、乘、除、取余);从右向左结合的运算符,称为右结合的运算符(一元运算符正、负)。
C语言运算符很多,记不住优先级和结合性规则时,可以使用圆括号对表达式进行分组。
4.2 赋值运算符
赋值操作语法:v = e
表示求出e的值,然后复制给v。
如果v和e的类型不同,赋值运算时会把e的值转为v的类型。
整个赋值表达式的值是赋值运算后左操作数的值。
赋值是右结合的运算符。它的左操作数必须是对象,而不是常量或计算的结果。
- 运算符的副作用
通常运算符不应该修改他们的操作数,表达式也只是计算他们的结果,不会改变操作数的值。
有些运算符不仅计算出值,还会有其他副作用。赋值运算符的副作用就是会改变左操作数的值。
复合赋值
利用变量的原有值计算新值然后重新赋值给这个变量,这种操作非常普遍。C语言允许用复合赋值运算符缩短类似的语句。
// 原值计算新值,然后重新赋值
v = v + e
// 复合赋值
v += e
前面提到的二元运算符的计算都有对应的复合赋值运算符:+=
、-=
、*=
、/=
、%=
。
复合赋值运算符也是右结合的。
注意 v += e
有时候不等同于v = v + e
。
一是优先级导致的问题:表达式i *= j + k
和表达式i = i * j + k
不一样。
二是如果v是表达式,且其中发生了对变量的更改时,v += e
中更改只会发生一次,但是v = v + e
中会更改两次。
自增、自减运算符
变量自增1、自减1的运算也很常见。C语言用++
和--
两种运算符简化这些语句。
自增++
、自减--
运算符既可以做为前缀运算符,也可以作为后缀运算符。
做为前缀运算符时,其表达式的值为操作数自增后的值。
做为后缀运算符时,其表达式的值是操作数自增前的值,计算完表达式的值后,操作数才自增。
前缀++
、前缀--
是右结合的,优先级和一元的正号、负号一样。
后缀++
、后缀--
是左结合的,优先级比一元的正号、负号高。
4.3 不要在子表达式中修改操作数
长的表达式可以利用运算符的优先级和结合性规则划分为子表达式。
但是C语言没有定义子表达式的求值顺序(除非含有逻辑运算符、条件运算符或逗号运算符)。表达式(a+b) * (c - d)
中,无法确定(a + b)
是否在(c - d)
之前求值。
大多数表达式,不管子表达式的求值顺序如何,都是相同的值。但是当子表达式修改了某个操作数的值时,就不一定了:
a = 5;
c = (b = a + 2) - (a = 1);
如果先计算(a = 1)
,c值就为2;先计算(b = a + 2)
,c值就为6。
i = 2;
j = i * i++;
很多人会自然地认为j被赋值为4。但是j也可能被赋值为6:
- 先取出乘法的右操作数i,为2。之后i自增;
- 取左操作数也是i,取到新值3;
- i的新值和旧值相乘,结果为6。
取出变量,表示从内存中获取它的值,存在寄存器中。变量的后续变化不会影响已取出的值。
上述在表达式中修改变量的值的语句会导致未定义的行为,不同的编译器给出的编译结果是不同的。应该避免出现未定义的行为。
4.4 表达式可以做为语句
C语言任何表达式都可以用作语句,只要在后面添加分号即可。
// 赋值1,然后取出i的新值,不使用
i = 1;
// 取出i的值,不使用,然后i自减
i--;
// 计算表达式的值,然后丢弃
i * j - 1;
由于+
和=
两个字符在键盘的同一个键上,发生误输入的概率很高,从而出现了什么也不做的表达式语句。
更多相关内容参考: 《C语言程序设计:现代方法》笔记