第5章 运算符、表达式和语句
赋值行为从右往左进行。赋值的目的是把值存到指定的内存位置上。
- 用于存储值的数据存储区统称为数据对象,因此数据对象是指实际的数据存储区域
- 左值用于标识特定数据对象的名称或表达式,因此左值是用于标识或定位存储位置的标签
指数增长的例子
用浮点数替代整数存储非常非常大的数字
棋盘64个格子,从第一个格子开始每个格子的麦粒数量是上一个格子的2倍,计算总数、与全世界小麦年产量的比较。
#include <stdio.h>
int main(void) {
const double CROP = 2e16;
int current=1; //当前方格编号
double num_current=1,num_total=1,percent=1/CROP; // 当前方格的麦粒数,总数,总数占全世界产量的比例
// 表头
printf("方格序号 该方格麦粒数量 麦粒总量 占世界麦粒产量占比\n");
while(current<=64) {
printf("%7d %12.2e %12.2e %14.2e\n",current,num_current,num_total,percent);
current+=1;
num_current=num_current*2;
num_total+=num_current;
percent=num_total/CROP;
}
return 0;
}
/*
方格序号 该方格麦粒数量 麦粒总量 占世界麦粒产量占比
1 1.00e+00 1.00e+00 5.00e-17
2 2.00e+00 3.00e+00 1.50e-16
3 4.00e+00 7.00e+00 3.50e-16
4 8.00e+00 1.50e+01 7.50e-16
5 1.60e+01 3.10e+01 1.55e-15
6 3.20e+01 6.30e+01 3.15e-15
7 6.40e+01 1.27e+02 6.35e-15
8 1.28e+02 2.55e+02 1.27e-14
9 2.56e+02 5.11e+02 2.55e-14
10 5.12e+02 1.02e+03 5.12e-14
...
55 1.80e+16 3.60e+16 1.80e+00
56 3.60e+16 7.21e+16 3.60e+00
57 7.21e+16 1.44e+17 7.21e+00
58 1.44e+17 2.88e+17 1.44e+01
59 2.88e+17 5.76e+17 2.88e+01
60 5.76e+17 1.15e+18 5.76e+01
61 1.15e+18 2.31e+18 1.15e+02
62 2.31e+18 4.61e+18 2.31e+02
63 4.61e+18 9.22e+18 4.61e+02
64 9.22e+18 1.84e+19 9.22e+02
*/
用整数,溢出不会报错:
#include <stdio.h>
int main(void) {
const double CROP = 2e16;
int current=1; //当前方格编号
unsigned long long num_current=1,num_total=1; // 当前方格的麦粒数,总数,总数占全世界产量的比例
double percent=1/CROP;
// 表头
printf("方格序号 该方格麦粒数量 麦粒总量 占世界麦粒产量占比\n");
while(current<=66) {
printf("%7d %20llu %20llu %14.2e\n",current,num_current,num_total,percent);
current+=1;
num_current=num_current*2;
num_total+=num_current;
percent=num_total/CROP;
}
return 0;
}
/*
方格序号 该方格麦粒数量 麦粒总量 占世界麦粒产量占比
1 1 1 5.00e-17
2 2 3 1.50e-16
3 4 7 3.50e-16
4 8 15 7.50e-16
5 16 31 1.55e-15
6 32 63 3.15e-15
7 64 127 6.35e-15
8 128 255 1.27e-14
9 256 511 2.55e-14
10 512 1023 5.12e-14
11 1024 2047 1.02e-13
12 2048 4095 2.05e-13
13 4096 8191 4.10e-13
14 8192 16383 8.19e-13
15 16384 32767 1.64e-12
16 32768 65535 3.28e-12
17 65536 131071 6.55e-12
18 131072 262143 1.31e-11
19 262144 524287 2.62e-11
20 524288 1048575 5.24e-11
21 1048576 2097151 1.05e-10
22 2097152 4194303 2.10e-10
23 4194304 8388607 4.19e-10
24 8388608 16777215 8.39e-10
25 16777216 33554431 1.68e-09
26 33554432 67108863 3.36e-09
27 67108864 134217727 6.71e-09
28 134217728 268435455 1.34e-08
29 268435456 536870911 2.68e-08
...
60 576460752303423488 1152921504606846975 5.76e+01
61 1152921504606846976 2305843009213693951 1.15e+02
62 2305843009213693952 4611686018427387903 2.31e+02
63 4611686018427387904 9223372036854775807 4.61e+02
64 9223372036854775808 18446744073709551615 9.22e+02
65 0 18446744073709551615 9.22e+02
66 0 18446744073709551615 9.22e+02
*/
浮点数可以表示非常大的数:
#include <stdio.h>
int main(void) {
const double CROP = 2e16;
int current = 1; //当前方格编号
double num_current = 1, num_total = 1, percent = 1 / CROP; // 当前方格的麦粒数,总数,总数占全世界产量的比例
// 表头
printf("方格序号 该方格麦粒数量 麦粒总量 占世界麦粒产量占比\n");
while (current <= 100) {
printf("%7d %12.2e %12.2e %14.2e\n", current, num_current, num_total, percent);
current += 1;
num_current = num_current * 2;
num_total += num_current;
percent = num_total / CROP;
}
return 0;
return 0;
}
/*
方格序号 该方格麦粒数量 麦粒总量 占世界麦粒产量占比
1 1.00e+00 1.00e+00 5.00e-17
2 2.00e+00 3.00e+00 1.50e-16
3 4.00e+00 7.00e+00 3.50e-16
...
64 9.22e+18 1.84e+19 9.22e+02
65 1.84e+19 3.69e+19 1.84e+03
66 3.69e+19 7.38e+19 3.69e+03
67 7.38e+19 1.48e+20 7.38e+03
68 1.48e+20 2.95e+20 1.48e+04
...
97 7.92e+28 1.58e+29 7.92e+12
98 1.58e+29 3.17e+29 1.58e+13
99 3.17e+29 6.34e+29 3.17e+13
100 6.34e+29 1.27e+30 6.34e+13
*/
整数除法
整数除法结果无小数部分,结果使用趋零截断: 直观看,就是直接把小数部分干掉
#include <stdio.h>
int main(void) {
printf("35/6=%8d\n",35/6);
printf("35/6=%8.2f\n",35.0/6);
printf("-35/6=%7d\n",35/6);
printf("-35/6=%7.2f\n",-35.0/6);
return 0;
return 0;
}
/*
35/6= 5
35/6= 5.83
-35/6= 5
-35/6= -5.83
*/
sizeof 运算符和 size_t数据类型
sizeof 运算符以字节为单位返回运算对象的大小(在C中,1字节定义为char 类型占用的空间大小。过去,1字节通常是8位,但是一些字符集可能使用更大的字节)。运算对象可以是具体的数据对象(如,变量名)或类型。如果运算对象是类型(如,float),则必须用圆括号将其括起来。
#include <stdio.h>
int main(void) {
char c = 'a';
int i = 0;
printf("char %c has %zd bytes, int %d has %zd bytes", c, sizeof c, i, sizeof(int));
return 0;
}
/*
char a has 1 bytes, int 0 has 4 bytes
*/
- C语言规定,sizeof返回size_t类型的值。这是一个无符号整数类型,但它不是新类型。
- size_t是语言定义的标准类型。C有一个typedef机制,允许程序员为现有类型创建别名。例如,
typedef double real;
, 这样,real就是double的别名。现在,可以声明一个real类型的变量:real deal;//使用typedef
, 编译器査看 rea1 时会发现,在 typedef声明中 real已成为 double 的别名,于是把 deal创建为double 类型的变量。 - 类似地,C头文件系统可以使用typedef把size_t作为 unsigned int 或unsigned long的别名。这样,在使用size_t类型时,编译器会根据不同的系统替换标准类型.
- C99做了进一步调整,新增了%zd转换说明用于printf()显示sizet类型的值。如果系统不支持号zd,可使用%u或%lu代替%zd。
site_t 具体可能是 unsigned int 或unsigned long, 用%zd 就是用对应的格式打印
求模运算(对整数求余)%
C99标准规定: a%b = a-(a/b)*b
#include <stdio.h>
int main(void) {
printf("11%5 = %d \n",11%5);
printf("11%-5 = %d \n",11%-5);
printf("-11%5 = %d \n",-11%5);
printf("-11%-5 = %d \n",-11%-5);
return 0;
}
/*
11%5 = 1
11%-5 = 1
-11%5 = -1
-11%-5 = -1
*/
递增运算符 ++
有前缀形式和后缀形式的区别
#include <stdio.h>
int main(void) {
int a = 0, b = 0;
printf("a=%d ,b= %d\n", a, b);
int c = ++a, d = b++;
printf("a=%d ,b= %d,c= %d,d=%d\n", a, b, c, d);
return 0;
}
/*
a=0 ,b= 0
a=1 ,b= 1,c= 1,d=0
*/
类型转换
基本规则:
- 当类型转换出现在表达式时,无论是unsigned还是signed的char和short 都会被自动转换成int,如有必要会被转换成 unsigned int(如果short与int 的大小相同,unsigned short 就比int 大。这种情况下,unsianed short 会被转换成unsianed int)。
- 涉及两种类型的运算,两个值会被分别转换成两种类型的更高级别。类型的级别从高至低依次是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.
- 在赋值表达式语句中,计算的最终结果会被转换成被赋值变量的类型。这个过程可能导致类型升级或降级(demotion)。所谓降级,是指把一种类型转换成更低级别的类型。
- 当作为函数参数传递时,char 和 short 被转换成 int,float 被转换成 double。第9章将介绍,函数原型会覆盖自动升级。
如果待转换的值与目标类型不匹配,转换取决于涉及的类型。待赋值的值与目标类型不匹配时,规则如下。
- 目标类型是无符号整型,且待赋的值是整数时,额外的位将被忽略。例如,如果目标类型是8位unsigned char,待赋的值是原始值求模256。
- 如果目标类型是一个有符号整型,且待赋的值是整数,结果因实现而异。
- 如果目标类型是一个整型,且待赋的值是浮点数,该行为是未定义的。
- 如果把一个浮点值转换成整数类型:当浮点类型被降级为整数类型时,原来的浮点值会被截断。例如,23.12和23.99都会被截断为23,-23.5会被截断为-23。
#include <stdio.h>
int main(void) {
char ch;
int i;
float f;
f = i = ch = 'C';
printf("ch=%c i=%d f=%2.2f \n", ch, i, f); //ch=C i=67 f=67.00
ch += 1;
i = f + 2 * ch;
f = 2.0 * ch + i;
printf("ch=%c i=%d f=%2.2f \n", ch, i, f); //ch=D i=203 f=339.00
ch = 1107;
printf("ch=%c \n", ch); // ch=S ,1107%256=83
ch = 83.89;
printf("ch=%c \n", ch); // ch=S 83.89 截断得83
return 0;
}
强制类型转换(type)
-
通常应该避免自动类型转换,尤其是类型降级。一般而言,不应该混合使用类型(因此有些语言直接不允许这样做),但是偶尔这样做也是有用的。
-
C语言的原则是避免给程序员设置障碍,但是程序员必须承担使用的风险和责任。
-
有时需要进行精确的类型转换,或者在程序中表明类型转换的意图。这种情况下要用到强制类型转换(cast),即在某个量的前面放置用圆括号括起来的类型名,该类型名即是希望转换成的目标类型。圆括号和它括起来的类型名构成了强制类型转换运算符(cast operalor),其通用形式是:
(type)
,用实际需要的类型(如,long)替换type即可。
#include <stdio.h>
int main(void) {
int i = 1.6 + 1.7;
int j = (int) 1.6 + (int) 1.7;
printf("i=%d,j=%d\n", i, j); // i=3,j=2
return 0;
}