C Primer Plus(6) 中文版 第5章 运算符、表达式和语句 5.3 其他运算符

发布于:2023-01-18 ⋅ 阅读:(173) ⋅ 点赞:(0)

5.3 其他运算符
C语言有大约40个运算符。
5.3.1 sizeof运算符和size_t类型
sizeof运算符以字节为单位返回运算对象的大小(在C语言中,1字节定义为char类型占用的空间大小)。
运算对象可以是具体的数据对象(如,变量名)或类型。如果运算对象是类型,则必须用圆括号将其括起来。
// sizeof.c -- uses sizeof operator
// uses C99 %z modifier -- try %u or %lu if you lack %zd
#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时会发现,在typedef声明中real已成为double的别名,于是把real创建为double类型的变量。
C99做了进一步调整,新增了%zd转换说明用于printf()显示size_t类型的值。
5.3.2 求模运算符:%
求模运算符(modulus operator)用于整数运算。求模运算符给出其左侧整数除以右侧整数的余数(reminder)。
求模运算符只能用于整数,不能用于浮点数。
求模运算符常用于控制程序流。
// min_sec.c -- converts seconds to minutes and seconds
#include <stdio.h>
#define SEC_PER_MIN 60            // seconds in a minute
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);            // read number of seconds
    while (sec > 0)
    {
        min = sec / SEC_PER_MIN;  // truncated number of minutes
        left = sec % SEC_PER_MIN; // number of seconds left over
        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;
}

/* 输出:

*/ 

每次循环都会改变被测试的变量值。
C99规定负数求模“趋零截断”之前,该问题的处理方法很多。但自从有了这条规则之后,如果第1个运算对象是负数,那么求模的结果是负数,那么求模的结果为负数;如果第1个运算对象是正数,那么求模的结果也是正数。
实际上,标准规定:无论何种情况,只要a和b都是整数值,便可通过a-(a/b)*b来计算a%b。
5.3.3 递增运算符:++
递增运算符(increment operator)执行简单的任务,将其运算对象递增1。该运算符以两种方式出现。
第1种方式,++出现在其作用的变量前面,这是前缀模式;
第2种方式,++出现在其作用的变量后面,这是后缀模式;
两种模式的区别在于递增行为发生的时间不同。
/* add_one.c -- incrementing: prefix and postfix */
#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;

/* 输出:

*/ 

super = super + 1; 
创建两个缩写形式的原因之一是:紧凑结构的代码让程序更为简洁诶,可读性更高。让程序看起来更美观。
shoe = 2.0; //note the original shoe is 2.0, not 3.0
while( ++shoe < 18.5 ){
    foot = SCALE * shoe + ADJUST;
    ...

充分利用了递增运算符的优势。
更重要的是,它把控制循环的两个过程几种在一个地方。该循环的主要过程是判断是否继续循环,次要过程是改变待测试的元素。
如果陷入无限循环(infinite loop),只能强行关闭这个程序。把循环测试和更新测试放在一处,就不会忘记更新循环。
但是,把两个操作合在一个表达式中,降低了代码的可读性,让代码难以理解。而且,还容易产生计数错误。
递增运算的另一个优点是,通常它生成的机器语言代码效率更高,因为它和实际的机器语言很相似。
/* post_pre.c -- postfix vs prefix */
#include <stdio.h>
int main(void)
{
    int a = 1, b = 1;
    int a_post, pre_b;
    
    a_post = a++;  // value of a++ during assignment phase
    pre_b = ++b;   // value of ++b during assignment phase
    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递增之后的值。这就是++的前缀形式和后缀形式的区别。
单独使用递增运算符时,使用哪种形式都没关系。但是,但运算符的运算对象是更复杂表达式的一部分时,使用前缀和后缀的效果不同。
如果使用前缀形式或者后缀形式会对代码产生不同的影响,那么最为明智的是不要那样使用它们。例如: 
b = ++i;  //如果使用i++,会得到不同的结果
应该使用下列语句:
++i; //第1行
b=i; //如果第1行使用的是i++,并不会影响b的值。
5.3.4 递增运算符:--
每种形式的递增运算符都有一个递减运算符(decrement operator)与之对应,用--代替++即可。

/* 输出:

*/ 

顺带一提,>运算符表示“大于”,<运算符表示“小于”,它们都会关系运算符(relational operator)。 
5.3.5 优先级
递增和递减运算符都有很高的结合优先级,只有圆括号比它们高。递增和递减运算符只能影响一个变量(或者,更普遍地说,只能影响一个可修改的左值)。
不要混淆这两个运算符的优先级和它们的求值顺序。 
nextnum = (y + n++) * 6;
根据优先级可以判断何时使用n的值对表达式求值,而递增运算符的形式决定了何时递增n的值。 
5.3.6 不要自作聪明 
如果一次使用太多递增运算符,自己都会糊涂。例如:
while( num < 21 ){
    printf( "%10d %10d\n", num, num * num++ );

这种行为是未定义的。
在C语言中,编译器可以自行选择先对函数中的哪个参数求值。这样做提高了编译器的效率,但是如果在函数的参数中使用了递增运算符,就会有一些问题。
该语句的问题是:编译器可能不会按照预想的顺序来执行。编译器可能先对num * num++进行求值,再对num进行求值。
还有一种情况,也不确定:
n = 3;
y = n++ + n++;
结果为n的值为5,但是y的值不确定。对于这种情况更精确地说,结果是未定义的,这意味着C标准并未定义结果应该是什么。
遵循以下规则,很容易避免类似的问题:
*如果一个变量出现在 一个函数的多个参数中,不要对该变量使用递增或递减运算符;
*如果一个变量多次出现在一个表达式中,不要对该变量使用递增或递减运算符。
另一个方面,对于何时执行递增,C还是做了一些保证。这在讨论“副作用”和“序列点”将讲解。