C Primer Plus 学习笔记_第五章 运算符、表达式和语句

第五章 运算符、表达式和语句

关键字:while、typedef
运算符:=、-、*、/、%、++、–、(类型名)
C语言的各种运算符,包括用于普通数学运算的运算符
运算符优先级以及语句、表达式的含义
while循环
复合语句、自动类型转换和强制类型转换
————————————————————————————————————————————————————————————

5.1 循环简介

/* shoes2.c -- 计算多个不同鞋码对应的脚长 */
#include <stdio.h>
#define ADJUST 7.31 // 字符常量
int main(void)
{
const double SCALE = 0.333;// const变量
double shoe, foot;
printf("Shoe size (men's) foot length\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;
}

花括号之间的内容就是要被重复执行的内容。花括号以及被花括号括起来的部分被称为块(block)。

当程序第1次到达while循环时,会检查圆括号中的条件是否为真。若条件为真,则进入块中继续执行,块中语句结束后再返回while入口检查条件,依此循环直到条件为假。
————————————————————————————————————————————————————————————

5.2 基本运算符

C用运算符(operator)表示算术运算,例如=,+,-,*,/等。

5.2.1 赋值运算符:=

在C语言中,=并不意味着“相等”,而是一个赋值运算符。

=号左侧是一个变量名,右侧是赋给该变量的值。赋值行为从右往左进行。

在C语言中,类似这样的语句没有意义(实际上是无效的):
2002 = bmw;

C 使用可修改的左值(modifiable lvalue)标记那些可赋值的实体。

几个术语:数据对象、左值、右值和运算符

赋值表达式语句的目的是把值储存到内存位置上。用于储存值的数据存储区域统称为数据对象(data object)。左值(lvalue)是 C 语言的术语,用于标识特定数据对象的名称或表达式。因此,对象指的是实际的数据存储,而左值是用于标识或定位存储位置的标签。

可修改的左值(modifiable lvalue),用于标识可修改的对象。
右值(rvalue)指的是能赋值给可修改左值的量,且本身不是左值。
被称为“"(如,赋值运算符左侧的项)的就是运算对象(operand)。运算对象是运算符操作的对象。

5.2.2 加法运算符:+

加法运算符(addition operator)用于加法运算,使其两侧的值相加。
相加的值(运算对象)可以是变量,也可以是常量。

变量1=变量2+变量3;
在这个语句中,变量1,变量2和变量3都是可修改的左值,但变量2+变量3是右值。

5.2.3 减法运算符:-

减法运算符(subtraction operator)用于减法运算,使其左侧的数减去右侧的数。

+和-运算符都被称为二元运算符(binary operator),即这些运算符需要两个运算对象才能完成操作。

5.2.4 符号运算符:-和+

减号还可用于标明或改变一个值的代数符号。
如number1=-1;number2=-number1;中,number2的值为-1,此时-是一元运算符

加号也可做一元运算符,但是不改变值的代数符号,只起强调作用。

5.2.5 乘法运算符:*

符号*表示乘法。

/* squares.c -- 计算1~20的平方 */
#include <stdio.h>
int main(void)
{
int num = 1;
while (num < 21)
{
printf("%4d %6d\n", num, num * num);
num = num + 1;
}
return 0;
}

5.2.6 除法运算符:/

C使用符号/来表示除法。/左侧的值是被除数,右侧的值是除数。

整数除法和浮点数除法不同。浮点数除法的结果是浮点数,而整数除法的结果是整数。整数是没有小数部分的数。这使得5除以3很让人头痛(若5和3都是整型变量的值,则结果为1),因为实际结果有小数部分。在C语言中,整数除法结果的小数部分被丢弃,这一过程被称为截断(truncation)。

/* divide.c -- 演示除法 */
#include <stdio.h>
int main(void)
{
printf("integer division: 5/4 is %d \n", 5 / 4);
printf("integer division: 6/3 is %d \n", 6 / 3);
printf("integer division: 7/4 is %d \n", 7 / 4);
printf("floating division: 7./4. is %1.2f \n", 7. / 4.);
printf("mixed division: 7./4 is %1.2f \n", 7. / 4);
return 0;
}

5.2.7 运算符优先级

(从低至高)
运算符优先级(从低至高)

5.2.8 优先级和求值顺序

/* rules.c -- 优先级测试 */
#include <stdio.h>
int main(void)
{
int top, score;
top = score = -(2 + 5) * 6 + (4 + 3 * (2 + 3));
printf("top = %d, score = %d\n", top, score);
return 0;
}

————————————————————————————————————————————————————————————

5.3 其他运算符

C语言有大约40个运算符。

5.3.1 sizeof运算符和size_t类型

运算对象可以是具体的数据对象(如,变量名)或类型。如果运算对象是类型(如,float),则必须用圆括号将其括起来。

// sizeof.c -- 使用sizeof运算符
// 使用C99新增的%zd转换说明 -- 如果编译器不支持%zd,请将其改成%u或%lu
#include <stdio.h>
int main(void)
{
int n = 0;
size_t intsize;
intsize = sizeof (int);
printf("n = %d, n has %zd bytes; all ints have %zdbytes.\n",n, sizeof n, intsize);
return 0;
}

5.3.2 求模运算符:%

求模运算符(modulus operator)用于整数运算。求模运算符给出其左侧整数除以右侧整数的余数(remainder)。求模运算符只能用于整数,不能用于浮点数。

// min_sec.c -- 把秒数转换成分和秒
#include <stdio.h>
#define SEC_PER_MIN 60 // 1分钟60秒
int main(void)
{
int sec, min, left;
printf("Convert seconds to minutes and seconds!\n");
printf("Enter the number of seconds (<=0 to quit):\n");
scanf("%d", &sec); // 读取秒数
while (sec > 0)
{
min = sec / SEC_PER_MIN; // 截断分钟数
left = sec % SEC_PER_MIN; // 剩下的秒数
printf("%d seconds is %d minutes, %d seconds.\n", sec,
min, left);
printf("Enter next value (<=0 to quit):\n");
scanf("%d", &sec);
}
printf("Done!\n");
return 0;
}

5.3.3 递增运算符:++

递增运算符(increment operator)执行简单的任务,将其运算对象递增1。该运算符以两种方式出现。第1种方式,++出现在其作用的变量前面,这是前缀模式;第2种方式,++出现在其作用的变量后面,这是后缀模式。两种模式的区别在于递增行为发生的时间不同。

/* add_one.c -- 递增:前缀和后缀 */
#include <stdio.h>
int main(void)
{
int ultra = 0, super = 0;
while (super < 5)
{
super++;
++ultra;
printf("super = %d, ultra = %d \n", super, ultra);
}
return 0;
}

5.3.4 递减运算符:–

每种形式的递增运算符都有一个递减运算符(decrement operator)与之对应,用–代替++即可。

程序清单5.12 bottles.c程序
#include <stdio.h>
#define MAX 100
int main(void)
{
int count = MAX + 1;
while (--count > 0) {
printf("%d bottles of spring water on the wall, "
"%d bottles of spring water!\n", count, count);
printf("Take one down and pass it around,\n");
printf("%d bottles of spring water!\n\n", count - 1);
}
return 0;
}

5.3.5 优先级

递增运算符和递减运算符都有很高的结合优先级,只有圆括号的优先级比它们高。因此,xy++表示的是(x)(y++),而不是(x+y)++。不过后者无效,因为递增和递减运算符只能影响一个变量(或者,更普遍地说,只能影响一个可修改的左值),而组合x*y本身不是可修改的左值。
————————————————————————————————————————————————————————————

5.4 表达式和语句

5.4.1 表达式

表达式(expression)由运算符和运算对象组成(前面介绍过,运算对象是运算符操作的对象)。最简单的表达式是一个单独的运算对象,以此为基础可以建立复杂的表达式。

下面是一些表达式:
4
-6
4+21
a*(b + c/d)/20
q = 5*2
x = ++q % 3
q > 3

运算对象可以是常量、变量或二者的组合。一些表达式由子表达式(subexpression)组成(子表达式即较小的表达式)。

每个表达式都有一个值。

5.4.2 语句

语句(statement)是C程序的基本构建块。一条语句相当于一条完整的计算机指令。在C中,大部分语句都以分号结尾。

legs = 4
只是一个表达式(它可能是一个较大表达式的一部分),而下面的代码则是一条语句:
legs = 4;

最简单的语句是空语句。

C把末尾加上一个分号的表达式都看作是一条语句(即,表达式语句)。

/* addemup.c -- 几种常见的语句 */
#include <stdio.h>
int main(void) /* 计算前20个整数的和 */
{
int count, sum; /* 声明[1] */
count = 0; /* 表达式语句 */
279
sum = 0; /* 表达式语句 */
while (count++ < 20) /* 迭代语句 */
sum = sum + count;
printf("sum = %d\n", sum); /* 表达式语句[2] */
return 0; /* 跳转语句 */
}

多条语句需要用花括号括起来。这种语句是复合语句。

while语句是一种迭代语句,有时也被称为结构化语句。

C语言的术语副作用(side effect)。副作用是对数据对象或文件的修改。

序列点(sequence point)是程序执行的点,在该点上,所有的副作用都在进入下一步之前发生。在 C语言中,语句中的分号标记了一个序列点。在一个语句中,赋值运算符、递增运算符和递减运算符对运算对象做的改变必须在程序执行下一条语句之前完成。任何一个完整表达式的结束也是一个序列点。

完整表达式(full expression),就是指这个表达式不是另一个更大表达式的子表达式。

5.4.3 复合语句(块)

复合语句(compound statement)是用花括号括起来的一条或多条语句,复合语句也称为(block)。例如:

while (years < 100)
{
wisdom = wisdom * 1.05;
printf("%d %d\n", years, wisdom);
years = years + 1;
}

————————————————————————————————————————————————————————————

5.5 类型转换

一些基本的类型转换规则:
1.当类型转换出现在表达式时,无论是unsigned还是signed的char和short都会被自动转换成int,如有必要会被转换成unsigned int(如果short与int的大小相同,unsigned short就比int大。这种情况下,unsigned short会被转换成unsigned int)。在K&R那时的C中,float会被自动转换成double(目前的C不是这样)。由于都是从较小类型转换为较大类型,所以这些转换被称为升级(promotion)。

2.涉及两种类型的运算,两个值会被分别转换成两种类型的更高级别。

3.类型的级别从高至低依次是long double、double、float、unsignedlonglong、long long、unsigned long、long、unsigned int、int。例外的情况是,当long 和 int 的大小相同时,unsigned int比long的级别高。之所以short和char类型没有列出,是因为它们已经被升级到int或unsigned int。

4.在赋值表达式语句中,计算的最终结果会被转换成被赋值变量的类型。这个过程可能导致类型升级或降级(demotion)。所谓降级,是指把一种类型转换成更低级别的类型。

5.当作为函数参数传递时,char和short被转换成int,float被转换成double。

类型升级通常都不会有什么问题,但是类型降级会导致真正的麻烦。原因很简单:较低类型可能放不下整个数字。例如,一个8位的char类型变量储存整数101没问题,但是存不下22334。

待赋值的值与目标类型不匹配时,规则如下:
1.目标类型是无符号整型,且待赋的值是整数时,额外的位将被忽略。例如,如果目标类型是 8 位unsigned char,待赋的值是原始值求模256。

2.如果目标类型是一个有符号整型,且待赋的值是整数,结果因实现而异。

3.如果目标类型是一个整型,且待赋的值是浮点数,该行为是未定义的。

当浮点类型被降级为整数类型时,原来的浮点值会被截断。

/* convert.c -- 自动类型转换 */
#include <stdio.h>
int main(void)
{
char ch;
int i;
float fl;
fl = i = ch = 'C'; /* 第9行 */
printf("ch = %c, i = %d, fl = %2.2f\n", ch, i, fl); /* 第10行 */
ch = ch + 1; /* 第11行 */
i = fl + 2 * ch; /* 第12行 */
fl = 2.0 * ch + i; /* 第13行 */
printf("ch = %c, i = %d, fl = %2.2f\n", ch, i, fl); /* 第14行 */
ch = 1107; /* 第15行 */
printf("Now ch = %c\n", ch); /* 第16行 */
ch = 80.89; /* 第17行 */
printf("Now ch = %c\n", ch); /* 第18行 */
return 0;
}

5.5.1 强制类型转换运算符

有时需要进行精确的类型转换,或者在程序中表明类型转换的意图。这种情况下要用到强制类型转换(cast),即在某个量的前面放置用圆括号括起来的类型名,该类型名即是希望转换成的目标类型。

圆括号和它括起来的类型名构成了强制类型转换运算符(cast operator),其通用形式是:
(type)
用实际需要的类型(如,long)替换type即可。
————————————————————————————————————————————————————————————

5.6 带参数的函数

/* pound.c -- 定义一个带一个参数的函数 */
#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");
}

如果函数不接受任何参数,函数头的圆括号中应该写上关键字 void。由于该函数接受一个 int 类型的参数,所以圆括号中包含一个int类型变量n的声明。参数名应遵循C语言的命名规则。

声明参数就创建了被称为形式参数(formal argument或formal parameter,简称形参)的变量。

形参是变量,实参是函数调用提供的值,实参被赋给相应的形参。

变量名是函数私有的,即在函数中定义的函数名不会和别处的相同名称发生冲突。

原型(prototype)即是函数的声明,描述了函数的返回值和参数。
————————————————————————————————————————————————————————————

5.7 示例程序

// running.c -- A useful program for runners
#include <stdio.h>
const int S_PER_M = 60; // 1分钟的秒数
const int S_PER_H = 3600; // 1小时的分钟数
const double M_PER_K = 0.62137; // 1公里的英里数
int main(void)
{
double distk, distm; // 跑过的距离(分别以公里和英里为单位)
double rate; // 平均速度(以英里/小时为单位)
int min, sec; // 跑步用时(以分钟和秒为单位)
int time; // 跑步用时(以秒为单位)
double mtime; // 跑1英里需要的时间,以秒为单位
int mmin, msec; // 跑1英里需要的时间,以分钟和秒为单位
printf("This program converts your time for a metric race\n");
printf("to a time for running a mile and to your average\n");
printf("speed in miles per hour.\n");
printf("Please enter, in kilometers, the distance run.\n");
scanf("%lf", &distk); // %lf表示读取一个double类型的值
printf("Next enter the time in minutes and seconds.\n");
printf("Begin by entering the minutes.\n");
scanf("%d", &min);
printf("Now enter the seconds.\n");
scanf("%d", &sec);
time = S_PER_M * min + sec; // 把时间转换成秒
distm = M_PER_K * distk; // 把公里转换成英里
rate = distm / time * S_PER_H; // 英里/秒×秒/小时 = 英里/小时
mtime = (double) time / distm; // 时间/距离 = 跑1英里所用的时间
mmin = (int) mtime / S_PER_M; // 求出分钟数
msec = (int) mtime % S_PER_M; // 求出剩余的秒数
printf("You ran %1.2f km (%1.2f miles) in %d min, %d
sec.\n",
distk, distm, min, sec);
printf("That pace corresponds to running a mile in %d
min, ",
mmin);
printf("%d sec.\nYour average speed was %1.2f mph.\n", msec,
rate);
return 0;
}

输出示例
This program converts your time for a metric race
to a time for running a mile and to your average
speed in miles per hour.
Please enter, in kilometers, the distance run.
10.0
Next enter the time in minutes and seconds.
Begin by entering the minutes.
36
Now enter the seconds.
23
You ran 10.00 km (6.21 miles) in 36 min, 23 sec.
That pace corresponds to running a mile in 5 min, 51 sec.
Your average speed was 10.25 mph.
————————————————————————————————————————————————————————————

5.8 关键概念

C 通过运算符提供多种操作。每个运算符的特性包括运算对象的数量、优先级和结合律。当两个运算符共享一个运算对象时,优先级和结合律决定了先进行哪项运算。每个 C表达式都有一个值。如果不了解运算符的优先级和结合律,写出的表达式可能不合法或者表达式的值与预期不符。

虽然C允许编写混合数值类型的表达式,但是算术运算要求运算对象都是相同的类型。因此,C会进行自动类型转换。尽管如此,不要养成依赖自动类型转换的习惯,应该显式选择合适的类型或使用强制类型转换。这样,就不用担心出现不必要的自动类型转换。
————————————————————————————————————————————————————————————

5.9 本章小结

只需要一个运算对象的运算符(如负号和 sizeof)称为一元运算符,需要两个运算对象的运算符(如加法运算符和乘法运算符)称为二元运算符。

表达式由运算符和运算对象组成。在C语言中,每个表达式都有一个值,包括赋值表达式和比较表达式。运算符优先级规则决定了表达式中各项的求值顺序。当两个运算符共享一个运算对象时,先进行优先级高的运算。如果运算符的优先级相等,由结合律(从左往右或从右往左)决定求值顺序。

大部分语句都以分号结尾。最常用的语句是表达式语句。用花括号括起来的一条或多条语句构成了复合语句(或称为块)。

在C语言中,许多类型转换都是自动进行的。当把一种类型的值赋给另一种类型的变量时,值将被转换成与变量的类型相同。当把较大类型转换成较小类型时(如,long转换成short,或 double 转换成 float),可能会丢失数据。在混合类型的运算中,较小类型会被转换成较大类型。

定义带一个参数的函数时,便在函数定义中声明了一个变量,或称为形式参数。然后,在函数调用中传入的值会被赋给这个变量。这样,在函数中就可以使用该值了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值