C Primer Plus 第五章 运算符、表达式和语句 阅读笔记

5.1 循环简介

shoes1.c程序:把鞋码转换成英寸

#include <stdio.h>
#define ADJUST 7.31		//字符常量 
int main(void)
{
	const double SCALE = 0.333;  //const 变量 
	double shoe, foot;
	
	shoe = 9.0;
	foot = SCALE * shoe + ADJUST;
	printf("Shoe size (men's)    foot length\n");
	printf("%10.1f %15.2f inches\n", shoe, foot);
	
	return 0;
 } 

运行结果:
在这里插入图片描述
程序说明:
  写出来的程序只使用一次,浪费时间和精力。如果写成交互式程序会更有用,但是仍无法利用计算机的优势。

  应该让计算机做一些重复计算的工作,因为需要重复计算是使用计算机的主要原因。下面简单介绍一种重复计算的方法——while循环。

shoes.2程序:循环改进后的程序

#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; 
 } 

运行结果:
在这里插入图片描述
程序说明:
  while循环的原理:当程序第1次到达while循环时,会检查圆括号中的条件是否为真。如果为真,程序进入块中继续执行,把尺寸转换成英寸。然后打印计算的结果。

5.2 基本运算符

  C用运算符表示算术运算。基本算术运算的运算符: =、 +、 -、 *和/。C 没有指数运算符。不过,C 的标准数学库提供了一个pow()函数用于指数运算。例如,pow(3.5,2.2)返回3.5的2.2次幂。

5.2.1 赋值运算符

  赋值表达式语句:bmw = 2002;
  = 号左侧是一个变量名,右侧是赋给该变量的值。符号 = 被称为赋值运算符。读作“把值2002赋给变量bmw”。赋值行为从右往左进行。
  常用的语句:i = i + 1;
  该语句的意思是:找出变量 i 的值,把该值加 1,然后把新值赋值变量 i 。
在这里插入图片描述
  类似这样的语句没有意义(实际上是无效的):2002 = bmw;
  赋值运算符左侧必须引用一个存储位置。最简单的方法就是使用变量名。概括地说,C 使用可修改的左值标记那些可赋值的实体。
  几个术语:数据对象、左值、右值和运算符

  1. 赋值表达式语句的目的是把值储存到内存位置上。用于储存值的数据存储区域统称为数据对象
  2. 左值是 C 语言的术语,用于标识特定数据对象的名称或表达式。因此,对象指的是实际的数据存储,而左值是用于标识或定位存储位置的标签
  3. 可修改的左值,用于标识可修改的对象。所以,赋值运算符的左侧应该是可修改的左值。当前标准建议,使用术语对象定位值更好。
  4. 右值指的是能赋值给可修改左值的量,且本身不是左值。右值可以是常量、变量或其他可求值的表达式(如,函数调用)。
  5. 在学习名称时,被称为“项”(如,赋值运算符左侧的项)的就是运算对象。运算对象是运算符操作的对象。例如,可以把吃汉堡描述为:“吃”运算符操作“汉堡”运算对象。类似地可以说,=运算符的左侧运算对象应该是可修改的左值。

golf.c 程序:高尔夫锦标赛记分卡

#include <stdio.h>
int main(void)
{
	int jane, tarzan, cheeta;
	
	cheeta = tarzan = jane = 68;
	printf("		   cheeta  tarzan    jane\n");
	printf("First round score %4d %8d %8d\n", cheeta, tarzan, jane);
	
	return 0;
 } 

运行结果:
在这里插入图片描述
程序说明:
  赋值的顺序是从右往左:首先把86赋给jane,然后再赋给tarzan,最后赋给cheeta。

5.2.2 加法运算符

  加法运算符用于加法运算,使其两侧的值相加。
  相加的值(运算对象)可以是变量,也可以是常量。因此,执行语句:income = salary + bribes;
  计算机会查看加法运算符右侧的两个变量,把它们相加,然后把和赋给变量income。在此注意,income、salary和bribes都是可修改的左值。因为每个变量都标识了一个可被赋值的数据对象。但是,表达式salary + brives是一个右值。

5.2.3 减法运算符

  减法运算符用于减法运算,使其左侧的数减去右侧的数。
  + 和 - 运算符都被称为二元运算符,即这些运算符需要两个运算对象才能完成操作。

5.2.4 符号运算符:- 和 +

   - 还可用于标明或改变一个值的代数符号。例如,rocky = –12;
   + 不会改变运算对象的值或符号。例如,dozen = +12;
  以这种方式使用的正号和负号被称为一元运算符,一元运算符只需要一个运算对象。
在这里插入图片描述

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;
 } 

运行结果:
在这里插入图片描述
指数增长:
  一位统治者想奖励做出突出贡献的学者。他问这位学者想要什么,学者指着棋盘说,在第1个方格里放1粒小麦、第2个方格里放2粒小麦、第3个方格里放4粒小麦,第4个方格里放 8 粒小麦,以此类推。这位统治者不熟悉数学,很惊讶学者竟然提出如此谦虚的要求。因为他原本准备奖励给学者一大笔财产。如果wheat.c程序运行的结果正确,这显然是跟统治者开了一个玩笑。程序计算出每个方格应放多少小麦,并计算了总数。可能大多数人对小麦的产量不熟悉,该程序以谷粒数为单位,把计算的小麦总数与粗略估计的世界小麦年产量进行了比较。

wheat.c程序:

#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 = 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; 
 } 

运行结果:
在这里插入图片描述
程序说明:
在这里插入图片描述
55个方格的小麦数,总量已超过了世界年产量。

5.2.6 除法运算符:/

  C使用符号/来表示除法。/ 左侧的值是被除数,右侧的值是除数。
  整数除法和浮点数除法不同。浮点数除法的结果是浮点数,而整数除法的结果是整数。整数是没有小数部分的数。在C语言中,整数除法结果的小数部分被丢弃,这一过程被称为截断

divide.c程序:整数除法和浮点数除法的区别

/* 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;
} 

运行结果:
在这里插入图片描述
程序说明:
  “混合类型”,即浮点值除以整型值。C相对其他一些语言而言,在类型管理上比较宽容。尽管如此,一般情况下还是要避免使用混合类型
  注意,整数除法会截断计算结果的小数部分,不会四舍五入。混合整数和浮点数计算的结果是浮点数。实际上,计算机不能真正用浮点数除以整数,编译器会把两个运算对象转换成相同的类型。本例中,在进行除法运算前,整数会被转换成浮点数。

5.2.7 运算符优先级

  当一个语句中出现多个运算符时,应该先计算哪一个呢?C 语言对此有明确的规定,通过运算符优先级来解决操作顺序的问题。
  表5.1总结了到目前为止学过的运算符优先级。
在这里插入图片描述
  注意正号(加号)和负号(减号)的两种不同用法。结合律栏列出了运算符如何与运算对象结合。

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;
}

运行结果:
在这里插入图片描述
程序说明:
  首先,圆括号的优先级最高。先计算-(2 + 5) * 6中的圆括号部分,还是先计算(4 + 3 * (2 + 3))中的圆括号部分取决于具体的实现。圆括号的最高优先级意味着,在子表达式-(2 + 5) * 6中,先计算(2 + 5)的值,得7。然后,把一元负号应用在7上,得 -7。现在,表达式是:
  top = score = -7 * 6 + (4 + 3 * (2 + 3))
  下一步,计算2 + 3的值。表达式变成:
  top = score = -7 * 6 + (4 + 3 * 5)
  接下来,因为圆括号中的 * 比 + 优先级高,所以表达式变成:
  top = score = -7 * 6 + (4 + 15)
  然后,表达式为:
  top = score = -7 * 6 + 19
  -7乘以6后,得到下面的表达式:
  top = score = -42 + 19
  然后进行加法运算,得到:
  top = score = -23
  现在,-23被赋值给score,最终top的值也是-23。记住,=运算符的结合律是从右往左。

5.3 其他运算符

5.3.1 sizeof 运算符和 size_t 类型

  sizeof 运算符以字节为单位返回运算对象的大小。运算对象可以是具体的数据对象(如,变量名)或类型。如果运算对象是类型(如,float),则必须用圆括号将其括起来。

sizeof.c 程序:

#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 %zd bytes.\n", n, sizeof n, intsize);
	
	return 0;
}

运行结果:
在这里插入图片描述
程序说明:
  C 语言规定,sizeof 返回 size_t 类型的值。这是一个无符号整数类型,但它不是新类型。
  size_t是语言定义的标准类型。C有一个typedef机制,允许程序员为现有类型创建别名。例如,typedef double real;
  这样,real就是double的别名。现在,可以声明一个real类型的变量:real deal; // 使用typedef
  编译器查看real时会发现,在typedef声明中real已成为double的别名,于是把deal创建为double 类型的变量。
  类似地,C 头文件系统可以使用 typedef把 size_t 作为 unsigned int 或unsigned long的别名。这样,在使用size_t类型时,编译器会根据不同的系统替换标准类型。

5.3.2 求模运算符:%

  求模运算符用于整数运算。求模运算符给出其左侧整数除以右侧整数的余数。例如,13 % 5(读作“13求模5”)得3,即13除以5的余数是3。
  求模运算符只能用于整数,不能用于浮点数。
  求模运算符常用于控制程序流。例如,假设你正在设计一个账单预算程序,每 3 个月要加进一笔额外的费用。这种情况可以在程序中对月份求模3(即,month%3),并检查结果是否为0。如果为0,便加进额外的费用。

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 the number of seconds (<=0 to quit):\n");
		scanf("%d", &sec);
	}
	printf("Done!\n");
	
    return 0;
}

运行结果:
在这里插入图片描述
程序说明:
  程序通过scanf()为变量sec获取一个新值。只要该值为正,循环就继续。当用户输入一个0或负值时,循环退出。这种情况设计的要点是,每次循环都会修改被测试的变量值。
  负数求模如何进行?如果第1个运算对象是负数,那么求模的结果为负数;如果第1个运算对象是正数,那么求模的结果也是正数。
  标准规定:无论何种情况,只要a和b都是整数值,便可通过a - (a/b)*b来计算a%b。
  例如,可以这样计算-11%5:-11 - (-11/5) * 5 = -11 -(-2)*5 = -11 -(-10) = -1

5.3.3 递增运算符:++

  递增运算符将其运算对象递增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。用下面两条语句分别代替程序中的两条递增语句,程序的输出相同:
  super = super + 1;
  ultra = ultra + 1;
  这些都是很简单的语句,为何还要创建两个缩写形式?原因之一是,紧凑结构的代码让程序更为简洁,可读性更高。这些运算符让程序看起来很美观。另一个优点是,通常它生成的机器语言代码效率更高,因为它和实际的机器语言指令很相似。

post_pre.c程序:

#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递增之前的值,而b_pre是b递增之后的值。这就是++的前缀形式和后缀形式的区别。
在这里插入图片描述

5.3.4 递减运算符:- -

  --count;   // 前缀形式的递减运算符
  count–;   // 后缀形式的递减运算符

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 优先级

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

5.3.6 不要自作聪明

  如果一次用太多递增运算符,自己都会糊涂。
  遵循以下规则,很容易避免类似的问题:
   1. 如果一个变量出现在一个函数的多个参数中,不要对该变量使用递增或递减运算符。
   2. 如果一个变量多次出现在一个表达式中,不要对该变量使用递增或递减运算符。

5.4 表达式和语句

  C的基本程序步骤由语句组成,而大多数语句都由表达式构成。

5.4.1 表达式

  表达式由运算符和运算对象组成(运算对象是运算符操作的对象,运算对象可以是常量、变量或二者的组合)。

每个表达式都有一个值
  有赋值运算符(=)的表达式的值是什么?这些表达式的值与赋值运算符左侧变量的值相同。因此,表达式q = 5*2作为一个整体的值是10。那么,表达式q > 3的值是多少?这种关系表达式的值不是0就是1,如果条件为真,表达式的值为1;如果条件为假,表达式的值为0。
在这里插入图片描述
  最后一个表达式在C中完全合法,但不建议这样使用。

5.4.2 语句

  语句是C程序的基本构建块。一条语句相当于一条完整的计算机指令。在C中,大部分语句都以分号结尾。
  最简单的语句是空语句: ;  //空语句
  语句在程序中什么也不做,不算是真正有用的语句。更确切地说,语句可以改变值或调用函数:x = 25; ++x; y = sqrt(x);
  虽然一条语句(或者至少是一条有用的语句)相当于一条完整的指令,但并不是所有的指令都是语句。考虑这个语句:x = 6 + (y = 5);
  该语句中的子表达式y = 5是一条完整的指令,但是它只是语句的一部分。因为一条完整的指令不一定是一条语句,所以分号用于识别在这种情况下的语句(即,简单语句)。

addemup.c程序:

#include <stdio.h>
int main(void)						/* 计算前20个整数的和 */
{
	int count, sum;					/* 声明 */
	
	count = 0;						/* 表达式语句 */
	sum = 0;						/* 表达式语句 */
	while(count++ < 20)				/* 迭代语句 */
		sum = sum + count;
	printf("sum = %d\n", sum);		/* 表达式语句 */
	
	return 0;						/* 跳转语句 */
 } 
 
//在C语言中,赋值和函数调用都是表达式。没有所谓的"赋值语句"和"函数调用语句",这些语句实际上都是表达式语句。
//根据 C标准,声明不是语句。  

运行结果:
在这里插入图片描述
程序说明:
  声明创建了名称和类型,并为其分配内存位置。
  注意,声明不是表达式语句。也就是说,如果删除声明后面的分号,剩下的部分不是一个表达式,也没有值。
  赋值表达式语句在程序中很常用:它为变量分配一个值。赋值表达式语句的结构是,一个变量名,后面是一个赋值运算符,再跟着一个表达式,最后以分号结尾。
  while语句是一种迭代语句,有时也被称为结构化语句。

副作用和序列点
  副作用是对数据对象或文件的修改。
  给出表达式states = 50,C会对其求值得50。对该表达式求值的副作用是把变量states的值改为50。跟赋值运算符一样,递增和递减运算符也有副作用,使用它们的主要目的就是使用其副作用。
  序列点是程序执行的点,在该点上,所有的副作用都在进入下一步之前发生。
  在 C语言中,语句中的分号标记了一个序列点。意思是,在一个语句中,赋值运算符、递增运算符和递减运算符对运算对象做的改变必须在程序执行下一条语句之前完成。另外,任何一个完整表达式的结束也是一个序列点。
  完整表达式,就是指这个表达式不是另一个更大表达式的子表达式。例如,表达式语句中的表达式和while循环中的作为测试条件的表达式,都是完整表达式。
  序列点有助于分析后缀递增何时发生。

while (guests++ < 10)
printf("%d \n", guests);

  表达式guests++ < 10是一个完整的表达式,因为它是while循环的测试条件,所以该表达式的结束就是一个序列点。因此,C 保证了在程序转至执行 printf()之前发生副作用(即,递增guests)。同时,使用后缀形式保证了guests在完成与10的比较后才进行递增。

5.4.3 复合语句(块)

  复合语句是用花括号括起来的一条或多条语句,复合语句也称为块。

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不是这样)。由于都是从较小类型转换为较大类型,所以这些转换被称为升级。
  2. 涉及两种类型的运算,两个值会被分别转换成两种类型的更高级别。
  3. 类型的级别从高至低依次是long double、double、float、unsigned long long、long long、unsigned long、long、unsigned int、int。例外的情况是,当long 和 int 的大小相同时,unsigned int比long的级别高。之所以short和char类型没有列出,是因为它们已经被升级到int或 unsigned int。
  4. 在赋值表达式语句中,计算的最终结果会被转换成被赋值变量的类型。这个过程可能导致类型升级或降级。降级,是指把一种类型转换成更低级别的类型。
  5. 当作为函数参数传递时,char和short被转换成int,float被转换成double。

  待赋值的值与目标类型不匹配时,规则如下:

  1. 目标类型是无符号整型,且待赋的值是整数时,额外的位将被忽略。例如,如果目标类型是 8 位unsigned char,待赋的值是原始值求模256。
  2. 如果目标类型是一个有符号整型,且待赋的值是整数,结果因实现而异。
  3. 如果目标类型是一个整型,且待赋的值是浮点数,该行为是未定义的。

  当浮点类型被降级为整数类型时,原来的浮点值会被截断。例如,23.12和23.99都会被截断为23,-23.5会被截断为-23。

convert.c程序:自动类型转换

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

	return 0; 
 } 

运行结果:
在这里插入图片描述
程序说明:
  第15行和第16行:演示了类型降级的示例。把ch设置为一个超出其类型范围的值,忽略额外的位后,最终ch的值是字符S的ASCII码。或者,更确切地说,ch的值是1107 % 265,即83。
  第17行和第18行:演示了另一个类型降级的示例。把ch设置为一个浮点数,发生截断后,ch的值是字符P的ASCII码。

5.5.1 强制类型转换运算符

  强制类型转换,即在某个量的前面放置用圆括号括起来的类型名,该类型名即是希望转换成的目标类型。圆括号和它括起来的类型名构成了强制类型转换运算符,其通用形式是:(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.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 pound(int n)
  该函数接受一个 int 类型的参数,所以圆括号中包含一个int类型变量n的声明。
  声明参数就创建了被称为形式参数(简称形参)的变量。该例中,形式参数是 int 类型的变量 n。调用pound(10)就是把 10赋给n。我们称函数调用传递的值为实际参数,简称实参。所以,函数调用pound(10)把实际参数10传递给函数,然后该函数把10赋给形式参数(变量n)。

  形参是变量,实参是函数调用提供的值,实参被赋给相应的形参。因此,在程序pound.c中,times是pound()的实参,n是pound()的形参。

  变量名是函数私有的,即在函数中定义的函数名不会和别处的相同名称发生冲突。如果在pound()中用times代替n,那么这个times与main()中的times不同。

  第2个函数调用是pound(ch)。这里,ch是char类型,被初始化为!字符,在ASCII中ch的数值是33。但是pound()函数的参数类型是int,与char不匹配。程序开头的函数原型在这里发挥了作用。原型即是函数的声明,描述了函数的返回值和参数。
  pound()函数的原型说明了两点:
    - 该函数没有返回值(函数名前面有void关键字)。
    - 该函数有一个int类型的参数。
  当编译器执行到pound(ch)表达式时,会把参数ch自动转换成int类型。与此类似,最后一次调用是pound(f),使得float类型的变量被转换成合适的类型。

  在ANSI C之前,C使用的是函数声明,而不是函数原型。函数声明只指明了函数名和返回类型,没有指明参数类型。为了向下兼容,C现在仍然允许这样的形式:void pound(); /* ANSI C之前的函数声明 */
  如果用这条函数声明代替pound.c程序中的函数原型会怎样?第 1 次函数调用,pound(times)没问题,因为times是int类型。第2次函数调用,pound(ch)也没问题,因为即使缺少函数原型,C也会把char和short类型自动升级为int类型。第3次函数调用,pound(f)会失败,因为缺少函数原型,float会被自动升级为 double,这没什么用。虽然程序仍然能运行,但是输出的内容不正确。在函数调用中显式使用强制类型转换,可以修复这个问题:
  pound ((int)f); // 把f强制类型转换为正确的类型
  注意,如果f的值太大,超过了int类型表示的范围,这样做也不行。

5.7 示例程序

running.c程序:

#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; 
 } 

运行结果:
在这里插入图片描述
程序说明:
  为什么要进行类型转换?因为程序在秒转换成分钟的部分需要整型参数,但是在公里转换成英里的部分需要浮点运算。我们使用强制类型转换运算符进行了显式转换。

5.8 关键概念

  如果不了解运算符的优先级和结合律,写出的表达式可能不合法或者表达式的值与预期不符。这会影响你成为一名优秀的程序员
  不要养成依赖自动类型转换的习惯,应该显式选择合适的类型或使用强制类型转换。这样,就不用担心出现不必要的自动类型转换

5.9 本章小结

  1. 总结 C的一些运算符
    赋值运算符:
      =   将其右侧的值赋给左侧的变量
    算术运算符:
      +   将其左侧的值与右侧的值相加
      -   将其左侧的值减去右侧的值
      -   作为一元运算符,改变其右侧值的符号
      *   将其左侧的值乘以右侧的值
      /   将其左侧的值除以右侧的值,如果两数都是整数,计算结果将被截断
      %  当其左侧的值除以右侧的值时,取其余数(只能应用于整数)
      ++  对其右侧的值加1(前缀模式),或对其左侧的值加1(后缀模式)
      –   对其右侧的值减1(前缀模式),或对其左侧的值减1(后缀模式)
    其他运算符:
     sizeof   获得其右侧运算对象的大小(以字节为单位),运算对象可以是一个被圆括号括起来的类型说明符,
           如sizeof(float),或者是一个具体的变量名、数组名等,如sizeof foo
    (类型名)   强制类型转换运算符将其右侧的值转换成圆括号中指定的类型,如(float)9把整数9转换成浮点数9.0

  2. 当两个运算符共享一个运算对象时,先进行优先级高的运算。如果运算符的优先级相等,由结合律(从左往右或从右往左)决定求值顺序。

  3. 在C语言中,许多类型转换都是自动进行的。当char 和short类型出现在表达式里或作为函数的参数(函数原型除外)时,都会被升级为int类型;float类型在函数参数中时,会被升级为double类型。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值