C Primer Plus 第6版 中文版(03)

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

正文完
 0
评论(没有评论)