5. 基本运算符


一、基本运算符

1.1 赋值运算符:=

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

  • 赋值表达式语句的目的是把值储存到内存位置上,用于储存值的数据存储区域统称为数据对象。

  • 左值:用于标识或定位存储位置(标识特定数据对象的名称或表达式,即实际的数据存储)。

  • 可修改的左值:用于标识可修改的对象,赋值运算符的左侧应该是可修改的左值。

  • 右值:能赋值给可修改左值的量,且本身不是左值(是字面常量,无法被赋值)。

  • 运算对象:被称为“项”,是运算符操作的对象。

    int ex,why,zee; //ex,why,zee是可修改的左值
    const int TWO = 2; //TWO是不可改变的左值
    why = 42;
    zee = why;
    ex = TWO * (why + zee); //42和(why + zee)是右值
    

1.2 加法运算符:+

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

1.3 减法运算符:-

  • 减法运算符用于减法运算,使其左侧的数减去右侧的数。

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

1.4 符号运算符:-和+

  • -作为一元运算符,改变其右侧值的符号。

    例如,对rocky = –12; smokey = –rocky; ,可得smokey = 12 。

  • +作为一元+运算符,它不会改变运算对象的值或符号,只能这样使用:dozen = +12 。

  • 一元运算符只需要一个运算对象。

1.5 乘法运算符:*

  • 符号*表示乘法。

  • C没有平方函数,可以使用乘法式来计算平方。

  • 指数增长可以应用while循环。

    /* 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.6 除法运算符:/

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

  • 整数除法和浮点数除法不同。浮点数除法的结果是浮点数;而整数除法的结果是整数,没有小数部分的数。

  • 在C语言中,整数除法结果的小数部分被丢弃的过程被称为截断。

    整数除法会截断计算结果的小数部分(丢弃整个小数部分),不会四舍五入结果。

  • 负整数的运算也是直接丢弃小数部分,但是是“趋零截断”。如,-3.8会被截断为 -3。

  • 混合整数和浮点数计算的结果是浮点数。

    但计算机不能真正用浮点数除以整数,编译器会把两个运算对象转换成相同的类型。

1.7 运算符优先级和求值顺序

  • 加减乘除的运算的优先级顺序跟数学运算优先级顺序一样。

  • 当运算符共享一个运算对象时,优先级决定了求值顺序。

  • 表<运算符优先级(从高到低)>

    运算符 结合律
    ( ) 从左到右
    + -(一元) 从右到左
    * / 从左到右
    + -(二元) 从左到右
    = 从右到左

二、其他运算符

2.1 sizeof 运算符和size_t 类型

  • sizeof 是C语言的内置运算符,以字节为单位给出指定类型的大小。C99和C11提供 %zd 转换说明匹配 sizeof 的返回类型,一些不支持C99和C11的编译器可用 %u 或 %lu 代替 %zd 转换说明匹配 sizeof 的返回类型。

  • 运算对象是类型时,圆括号不能少,如 sizeof(char);但是对于特定量,可有可无,如 sizeof name。

2.2 求模运算符:%

  • 求模运算符用于整数运算,不能用于浮点数。

  • 求模运算符给出其左侧整数除以右侧整数的余数,如13 % 5 = 3。

2.3 递增运算符:++

  • 递增运算符执行简单的任务,将其运算对象递增 1。

  • 该运算符以两种方式出现(两种模式递增行为发生的时间不同):

    第一种,++count,前缀形式的递增运算符;

    第二种,count++,后缀形式的递增运算符。

    #include<stdio.h>
    int main(void)
    {
    	int a = 1, b = 1;
    	int a_post = a++, b_pre = ++b;
    	
    	printf("%d %d %d %d\n", a, a_post, b, b_pre);
       //a = 2, a_post = 1, b = 2, b_pre = 2
       //前后缀区别:和b都递增了1,但是,a_post是a递增之前的值,而b_pre是b递增之后的值。
    	 
    	return 0;
     } 
    

2.4 递减运算符:--

  • 递减运算符执行简单的任务,将其运算对象递减 1。

  • 该运算符以两种方式出现(两种模式递增行为发生的时间不同):

    第一种,--count,前缀形式的递减运算符;

    第二种,count--,后缀形式的递减运算符 。

  • 类比递增运算符

2.5 递增与递减运算符补充

  • 递增和递减运算符只能影响一个变量(即只能影响一个可修改的左值)。

  • 优点:第一,紧凑结构的代码让程序更为简洁,可读性更高,让程序看起来很美观;

    第二,通常它生成的机器语言代码效率更高,因为它和实际的机器语言指令很相似。

  • 优先级:递增运算符和递减运算符都有很高的结合优先级,只有圆括号的优先级比它们高。

  • 不要对变量使用递增或递减运算符的两种情况:

    如果一个变量出现在一个函数的多个参数中,如果一个变量多次出现在一个表达式中。


三、表达式与语句

3.1 表达式

  • 表达式由运算符和运算对象组成。

  • 最简单的表达式是一个单独的运算对象,以此为基础可以建立复杂的表达式。如 4、、x = ++q % 3、q > 3

  • 一些表达式由子表达式组成。如,c/d是a*(b + c/d)/20的子表达式。

    3.1.1 表达式的值

    • C 表达式的一个最重要的特性是,每个表达式都有一个值。

    • 有赋值运算符(=)的表达式的值与赋值运算符左侧变量的值相同。

    • 关系表达式的值不是0就是1。如果条件为真,表达式的值为1;如果条件为假,表达式的值为0。

表达式
- 4 + 6 2
c = 3 + 8 11
5 > 3 1
5 < 3 0
6 +(c = 3 + 8) 17

3.2 语句

  • 语句是C程序的基本构建块。一条语句相当于一条完整的计算机指令。

  • 在C中,大部分语句都以分号结尾。因此, legs = 4 是一个表达式,而 legs = 4; 是一个语句。

  • 最简单的语句是空语句: ; //空语句

  • C把末尾加上一个分号的表达式都看作是一条语句,如, 8; 、3 + 4; 也没有问题,但是,这些语句在程序中什么也不做,不算是真正有用的语句。

  • 虽然一条有用的语句相当于一条完整的指令, 但并不是所有的指令都是语句。如,x = 6 + (y = 5); 语句中的子表达式 y = 5 是一条完整的指令,但是它只是语句的一部分。

  • 因为一条完整的指令不一定是一条语句,所以分号用于识别在这种情况下的语句(即简单语句)。

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

    3.1.2 声明的特殊性

    • 声明创建了名称和类型,并为其分配内存位置,但声明不是表达式语句。

    • 即,如果删除声明后面的分号,剩下的部分不是一个表达式,也没有值。

      如,int port // 不是表达式,没有值

    3.2.3 赋值表达式语句

    • 赋值表达式语句在程序中很常用,它为变量分配一个值,是表达式语句的一个示例。
    • 赋值表达式语句的结构是,一个变量名,后面是一个赋值运算符,再跟着一个表达式,最后以分号结尾。
    • 注意,在while循环中有一个赋值表达式语句

    3.2.4 迭代语句

    • while语句是一种迭代语句,有时也被称为结构化语句,因为它的结构比简单的赋值表达式语句复杂。

    3.2.5 副作用和序列点

    • 副作用:对数据对象或文件的修改。

    • 序列点:程序执行的点,在该点上,所有的副作用都在进入下一步之前发生。

    • 完整表达式:指这个表达式不是另一个更大表达式的子表达式。如,表达式语句中的表达式和 while循环中的作为测试条件的表达式,都是完整表达式。

    • 举例代码:while (guests++ < 10) printf("%d \n", guests);

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

3.3 复合语句(块)

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

    /* 程序段 1 */                  /* 程序段 2 */ 
    index = 0;                     index = 0; 
    while (index++ < 10)           while (index++ < 10){
        sam = 10 * index + 2;      sam = 10 * index + 2; 
    printf("sam = %d\n", sam);     printf("sam = %d\n", sam); }
    /*程序段1,while循环中只有一条赋值表达式语句。没有花括号,while语句从while这行运行至下一个分号。循环  结束后,printf()函数只会被调用一次。
    程序段2,花括号确保两条语句都是while循环的一部分,每执行一次循环就调用一次printf()函数。根据while语句的结构,整个复合语句被视为一条语句。
    

四、类型转换

4.1 类型转换的基本规则

  • 当类型转换出现在表达式时,无论是unsigned 还是signed 的char 和short 都会被自动转换成int ,如有必要会被转换成unsigned int。

    (如果short与int的大小相同,unsigned short 就比int 大,unsigned short 会被转换成 unsigned int)

  • 类型级别从高至低依次是:long double、double、float、unsignedlong long、long long、unsignedlong、long、unsigned int、int。

    例外的情况是,当 long 和 int 的大小相同时,unsigned int 比 long 的级别高。之所以 short 和 char 类型没有列出,是因为它们已经被升级到 int 或unsigned int 。

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

  • 在赋值表达式语句中,计算的最终结果会被转换成被赋值变量的类型。这过程可能导致类型升级或降级。

  • 升级,是指从较小类型转换为较大类型;降级,是指把一种类型转换成更低级别的类型。

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

  • 函数原型会覆盖自动升级。

4.2 待赋值的值与目标类型不匹配的规则

  • 1.目标类型是无符号整型,且待赋的值是整数时,额外的位将被忽略。 例如,如果目标类型是 8 位unsigned char,待赋的值是原始值求模。
  • 2.如果目标类型是一个有符号整型,且待赋的值是整数,结果因实现而异。
  • 3.如果目标类型是一个整型,且待赋的值是浮点数,该行为是未定义的。
/* convert.c -- 自动类型转换 */ 
#include <stdio.h> 
int main(void) 
{
    char ch; 
    int i; 
    float fl; 
    fl = i = ch = 'C';  
    printf("ch = %c, i = %d, fl = %2.2f\n", ch, i, fl); // 第9行 
    ch = ch + 1;
    i = fl + 2 * ch; // 第11行 
    fl = 2.0 * ch + i;  
    printf("ch = %c, i = %d, fl = %2.2f\n", ch, i, fl); //第13行 
    ch = 1107;   
    printf("Now ch = %c\n", ch); // 第15行 
    ch = 80.89;  
    printf("Now ch = %c\n", ch); // 第17行 
    return 0; 
}
/*运行convert.c后输出如下: 
    ch = C, i = 67, fl = 67.00 
    ch = D, i = 203, fl = 339.00 
    Now ch = S 
    Now ch = P
  程序的分析如下(此系统,char是8位,int是32位): 
  第8-9行:字符'C'被作为1字节的ASCII值储存在ch中。整数变量i接受由'C'转换的整数,即按4字节储存67。
  最后,fl接受由67转换的浮点数67.00。
  第10-13行:字符变量'C'被转换成整数67,然后加1。计算结果是4字节整数68,被截断成1字节储存在ch中。 
  根据%c转换说明打印时,68被解 释成'D'的ASCII码。 
  第11-13行:ch的值被转换成4字节的整数(68),然后2乘以ch。为和fl相加,乘积整数(136)被转换成浮点数。 
  计算结果(203.00f)被转换成int类型,并储存在i中。 
  第12-13行:ch的值('D',或68)被转换成浮点数,然后2乘以ch。为了做加法,i的值(203)被转换为浮点类型。
  计算结果(339.00)被储存在fl中。 
  第14-15行:演示了类型降级的示例。把ch设置为一个超出其类型范围的值,忽略额外的位后,最终ch的值是字符S的
  ASCII码。或者,更确切地说,ch的值是1107 % 265 ,即83。 
  第17-18行:演示了另一个类型降级的示例。把ch设置为一个浮点 数,发生截断后,ch的值是字符P的ASCII码。

4.3 强制类型转换运算符

  • 强制类型转换:在某个量的前面放置用圆括号括起来的类型名,该类型名即是希望转换成的目标类型。

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

  • 举例:mice = 1.6 + 1.7; 使用自动类型转换。首先,1.6和1.7相加得3.3。

    然后,为了匹配 int 类型的变量,3.3被类型转换截断为整数3。

    mice = (int)1.6 + (int)1.7; 1.6和1.7在相加之前都 被转换成整数1,所以把1+1的和赋给变量mice。


五、C的一些运算符总结

  • 赋值运算符: = 将其右侧的值赋给左侧的变量

  • 算术运算符:

    + 将其左侧的值与右侧的值相加 ; - 将其左侧的值减去右侧的值 ;

    - 作为一元运算符,改变其右侧值的符号 ; * 将其左侧的值乘以右侧的值 ;

    / 将其左侧的值除以右侧的值,如果两数都是整数,计算结果将被截断;

    % 当其左侧的值除以右侧的值时,取其余数(只能应用于整数) ;

    ++ 对其右侧的值加1(前缀模式),或对其左侧的值加1(后缀模式);

    -- 对其右侧的值减1(前缀模式 ),或对其左侧的值减1(后缀模式)。

  • 其他运算符:sizeof 获得其右侧运算对象的大小(以字节为单位),运算对象可以是一个被圆括号括起来的类型说明符,如sizeof(float),或者是一个具体的变量名、数组名等,如sizeof foo (类型名) 强制类型转换运算符将其右侧的值转换成圆括号中指定的类型,如(float)9把整数9转换成浮点数9.0。

posted @ 2022-02-03 15:26  fidelity_233  阅读(413)  评论(0)    收藏  举报