学习笔记——《C Primer Plus》
第5章 运算符、表达式和语句
5.1 循环简介
while 循环
#include <stdio.h>
#define ADJUST 7.31 //character constant
int main(void)
{
const double SCALE = 0.333; //const variable
double shoe, foot;
printf("Shoe size (men's') foot lenght\n");
shoe = 3.0;
while(shoe < 18.5) //while循环开始
{
foot = SCALE * shoe + ADJUST;
printf("%10.1f %15.2f inches\n",shoe, foot);
shoe = shoe + 1.0;
}
printf("If the shoe fits, wear it.\n");
return 0;
}
当程序第 1 次到达 while 循环时,会检查圆括号中的条件是否为真。
该程序中,条件表达式为:
shoe < 18.5
该条件为真,程序进入块中继续执行,把尺码转换成英寸;
然后打印计算的结果;
下一条语句把 shoe 增加 1.0 ,使 shoe 的值为 4.0:
shoe = shoe + 1.0;
此时程序返回 while 入口部分检查条件。
为何要返回 while 的入口部分呢?
因为上面这条语句的下面是右花括号,代码使用的是一对花括号( { } )来标出 while 循环的范围。
花括号之间的内容就是要被重复执行的内容。
花括号以及被花括号括起来的部分被称为块(block)。
当出现条件为假:
shoe < 18.5
控制转到紧跟 while 循环后面的第 1 条语句。
5.2 运算符
5.2.1 基本运算符
赋值运算符:=
加法运算符:+
减法运算符:-
乘法运算符:* (可以使用乘法来计算平方)
例子1:
#include <stdio.h>
int main(void)
{
int num = 1;
while(num < 21)
{
printf("%4d %6d\n",num , num * num);
num = num +1;
}
return 0;
}
例子2:
/*
一位强大的统治者想奖励做出突出贡献的学者。他问这位学者想要什么,
学者指着棋盘说,在第一个方格里放1粒小麦、第2个方格里放2粒小麦、
第3个方格里放4个小麦,第4个方格里放8个小麦,以此例推,棋盘一共有64个方格;
程序计算出每个方格应该多少小麦,并计算总数。
*/
#include <stdio.h>
#define SQUARES 64 //棋盘中的方格数
int main(void)
{
const double CROP = 2E16; //世界小麦年产谷粒数
double current, total;
int count=1; //循环计数器
printf("square grains total ");
printf("fraction of \n");
printf(" added grains ");
printf("world total\n");
total = current = 1.0; //从1颗谷粒开始
printf("%4d %13.2e %12.2e %12.2e\n",count,current,total,total/CROP);
while(count < SQUARES)
{
count += 1;
current = 2.0*current; //下一个方格谷粒翻倍
total = total = current; //更新总数
printf("%4d %13.2e %12.2e %12.2e\n",count,current,total,total/CROP);
}
printf("That's all.\n'");
return 0;
}
除法运算符:/
整数除法的结果是整数,整数除法结果的小数部分被丢弃,不会四合五入,这一过程称为截取(truncation);
浮点数除法的结果是浮点数;
整数和浮点数计算的结果是浮点数。
实际上,计算机不能真正用浮点数除以整数,编译器会把两个运算对象转换成相同的类型;在进行除法运算之前,整数会别转换成浮点数。
5.2.2 求模运算符:%
- 求模运算符(modulus operator)只能用于整数运算,不能用于浮点数。
- 求模运算符给出其左侧整数除以右侧整数的余数(remainder);例如,13 % 5 (读作13求模5)得 3 。
- 如果第 1 个运算对象是负数,那么求模的结果为负数;如果第 1 个运算对象是正数,那么求模的结果也是整数。
- 求模运算符常用于控制程序流。
5.2.3 递增运算符:++
递增运算符(increment operator)执行简单的任务,将其运算对象递增 1 。
该运算符以两种方式出现:前缀模式,++出现在其作用的变量前面;后缀模式:++出现在其作用的变量后面。
#include <stdio.h>
#define ADJUST 7.31 //character constant
int main(void)
{
const double SCALE = 0.333; //const variable
double shoe, foot;
printf("Shoe size (men's') foot lenght\n");
shoe = 2.0;
while(++shoe < 18.5) //把变量的递增过程放入while循环的条件中。
{
foot = SCALE * shoe + ADJUST;
printf("%10.1f %15.2f inches\n",shoe, foot);
}
/* while(shoe < 18.5)
{
foot = SCALE * shoe + ADJUST;
printf("%10.1f %15.2f inches\n",shoe, foot);
shoe = shoe + 1; // 次句等同 : shoe++;
}
*/
printf("If the shoe fits, wear it.\n");
return 0;
}
shoe的值递增 1 ,然后和 18.5 作比较。如果递增后的值小于 18.5 ,则执行花括号内的语句一次。然后, shoe的值再递增 1 ,重复刚才的步骤,直到 shoe 的值不小于 18.5 为止。
注意:我们把 shoe 的初始值从 3.0 改为 2.0 ,因为在对 foot 第 1 次求值之前, shoe 已经递增了 1 。
while 循环执行一次过程:
区分前缀和后缀:
#include <stdio.h>
int main(void)
{
int a = 1, b = 1;
int a_post, pre_b;
a_post = a++; //后缀递增:使用 a 的值之后,递增 a
pre_b = b++; //前缀递增:使用 b 的值之前,递增 b
printf("a a_post b pre_b\n");
printf("%1d %5d %5d %5d\n", a, a_post, b, pre_b);
return 0;
}
执行结果:
a 和 b 都递增了 1 ,但是,a_post 是 a 递增之前的值,而 pre_b 是 b 递增之后的值。
(1)单独使用递增运算符(如,ego++;),使用哪种形式都没关系。但是,当运算符和运算对象是更复杂表达式的一部分时,使用前缀或后缀的效果不同。
(2)如果使用前缀形式和后缀形式会对代码产生不同的影响,那么最为明智的是不要那样使用它们。例如,不要使用下面语句:
b = ++i ; // 如果使用 i++ ,会得到不同的结果
应该使用下列语句:
++i;
b = i; //如果第一行使用 i++,并不会影响 b 的值
5.2.4 递减运算符:- -
递增运算符和递减运算符都有很高的结合优先级,只有圆括号的优先级比它们高。
递增和递减运算符只能影响一个变量(或者,更普遍地说,只能影响一个可修改的左值),比如:
x * y- - 表示的是 (x) * (y- -),而不是 (x * y)++;后者无效。
5.3 表达式和语句
5.3.1 表达式
表达式(expression)由运算符和运算对象组成。
每个表达式都有一个值
C 表达式的一个最重要的特性是,每个表达式都有一个值,要获得这个值,必须根据运算符优先级规定的顺序来执行操作。比如,表达式 q = 5*2 作为一个整体的值是10。又如表达式 q > 3 这种关系表达式的值不是0就是1,如果条件为真,表达式的值为1;如果条件为假,表达式的值为0.
5.3.2 语句
语句(statement)是 C 程序的基本构建块。一条语句相当于一条完整的计算机指令。在 C 中,大部分语句都以分号结尾。
5.3.3 复合语句(块)
复合语句(compound statement)是用花括号括起来的一条或多条语句,复合语句也称为块(block)。
/*程序段 1 */
index = 0;
while(index++ < 10)
sam = 10 * index +2;
printf("sam = %d\n", sam);
/*程序段 2 */
index = 0;
while(index++ < 10)
{
sam = 10 * index + 2;
printf("sam = %d\n", sam);
}
(1)程序段 1 ,while循环中只有一条赋值表达式语句。没有花括号,while 语句从 while 这行运行至下一个分号。循环结束后,printf() 函数只会被调用一次。
(2)程序段 2 ,花括号确保两条语句都是 while 循环的一部分,每执行一次循环就调用一次 printf() 函数。根据 while 语句的结构,整个复合语句被视为一条语句,如下图:
5.4 类型转换
- 升级(promotion):从较小类型转换为较大类型。
- 降级(demotion):把一种类型转换成更低级别的类型。
- 涉及两种类型的运算,两个值会被分别转换成两种类型的更高级别。
- 类型的级别从高到低依次是:long double、double、float、unsigned long long、long long、unsigned long、long、unsigned int、int。而 short 和 char 类型会被升级为 int 或 unsigned int。
- 在赋值表达式语句中,计算的最终结果会被转换成被赋值变量的类型。这个过程可能导致类型升级或降级。
5.4.1 强制转换
通常,应该避免自动类型转换,尤其是类型降级。
然而,有时需要进行精确的类型转换,或者在程序中表明类型转换的意图。这种情况下要用到强制类型转换(cast),即在某个量的前面放置用圆括号括起来的类型名,该类型名即是希望转换成的目标类型。
圆括号和它括起来的类型名构成了强制转换运算符(cast operator),通用形式是:
(type)
考虑下面两行代码,其中 mice 是 int 类型的变量。第 2 行包含两次 int 强制类型转化。
mice = 1.6 + 1.7;
mice = (int)1.6 + (int)1.7;
第 1 行使用自动类型转换。首先,1.6 和 1.7 相加得 3.3。然后,为了匹配 int 类型的变量,3.3 被类型转换截断为整数 3。
第 2 行,1.6 和 1.7 在相加之前就被转换成整数 1,所以 1+1 的和赋值给变量 mice。
本质上,两种类型转换都好不到哪去,要考虑程序的具体情况再做取舍。一般而言,不应该混合使用类型。
5.5 带参数的函数
#include <stdio.h>
void pound(int n); //ANSI 函数原型声明
int main(void)
{
int times = 5;
char ch = '!'; // ASCII 码是33
float f = 6.0f;
pound(times); //int 类型的参数
pound(ch); //和pound((int)ch);相同
pound(f); //和pound((int)f);相同
return 0;
}
void pound(int n) //ANSI风格函数头,表名函数接受一个 int 类型参数
{
while(n-- > 0)
{
printf("#");
}
printf("\n");
}
(1):
- 声明参数就创建被称为形式参数 (formal parameter) 的变量。
- 该例中,形式参数是 int 类型的变量 n 。像 pound(10) 这样的函数调用会把 10 赋值给 n 。在该程序中,调用 pound(times) 就是把 times 的值 5 赋值给 n 。
- 我们称函数调用传递的值为实际参数(actual argumentr),简称实参。
- 所以,函数调用 pound(10) 把实际参数 10 传递给函数,然后该函数把 10 赋给形式参数(变量 n )。也就是说,main() 中的变量 times 的值被拷贝给 pound() 中的变量 n 。
我们可以说形参是变量,实参是函数调用提供的值,实参被赋值给相应的形参。
因此, times 是 pound() 的实参,n 是 pound() 的形参。
注意:
变量名是函数私有的,即在函数中定义的函数名不会和别处的相同名称发生冲突。如果在 pound() 中用 times 代替 n , 那么这个 times 和 main() 中的 times 不同。也就是说,程序中出现了两个同名的变量,但是程序可以区分它们。
(2):
函数原型即是函数的声明,描述了函数的返回值和参数。
pound() 函数的原型说明了两点:
- 该函数没有返回值(函数名前面有 void 关键字);
- 该函数有一个 int 类型的参数。