C 运算符


运算符是一个符号,用于操作一个或多个变量或常量。


运算符类型

C语言有下面几种运算符:

  • 算术运算符(Arithmetic Operator),包含+、-、*、/、%、++、--,分别为加、减、乘、除、取余数运算符、自增运算符(Increment Operator)、自减运算符(Decrement Operator)。
  • 关系运算符(Relational Operator),包含==、!=、>、>=、<、<=。关系运算符用于比较两个运算数的大小是否相等。
  • 逻辑运算符(Logical Operator),包含&&、||、!。&&是与(and)运算符,当两个运算数都为true时,结果为true,否则为false。||是或(or)运算符,当两个运算数中至少有一个为true时,结果为true,否则为false。一元取反运算符(negation operator )!将非0运算数转换成0,将0运算符转换成1。
  • 位运算符(Bitwise Operator),包含~、&、|、^。~是二进制取反操作符(One's Complement Operator),对整型的每个bit,将0变成1,1变成0。&是按位与(and)运算符,对两个运算符的每个对应的bit,如果两个bit都为1,结果为1,否则为0。|是按位或(or)运算符,对两个运算符的每个对应的bit,如果两个bit至少有一个为1时,结果为1,否则为0。^是按位异或(xor)运算符,对两个运算符的每个对应的bit,如果两个bit有且只有一个为1,结果为1,否则为0。
  • 移位运算符(Bit Shift Operator),包含<<、>>。<<是左移运算符,向左移动指定个数的bit后,右边空出来的bit用0填充。>>右移运算符,如果右移一个无符号数时,向右指定个数的bit后,用0填充左边空出来的bit,向右移出的bit被丢弃。如果右移一个有符号数时,在一些机器使用0填充左边空出来的bit(logical shift),在另一些机器上使用符号位填充左边空出来的bit(arithmetic shift),向右移出的bit被丢弃。
  • 赋值运算符(Assignment Operator),包含=、+=、-=、*=、/=、%=、&=、|=、^=、>>=、<<=。=,用于给变量赋值。其他+=、-=等都是+、-等运算符和赋值运算符两个运算符一起使用的简写形式。如x += y,等同于x = x + y。
  • **三元运算符(Ternary Operator),?:。?:运算符是if-else的简化版本,共有3个运算数,格式为result = condition ? value1 : value2; 先计算condition,如果condition的值不是0(true),result为value1的值,否则result为value2的值。
  • 指针相关运算符,包含&和*。*是间接引用(indirection或dereference)运算符,放在指针变量前,用于得到指针变量保存的地址处的值。&是取址运算符,放在变量前,得到变量的地址。
  • []运算符(Subscript Operator),下标运算符,用于访问数组的成员。
  • ->和.运算符,用于访问struct类型的成员。
  • ,运算符(Comma Operator),用于for循环中分隔两个表达式,从左往右计算两个表达式。
  • ()运算符,用于函数调用。
  • sizeof运算符,用于返回一个数据类型的长度。

下面是运算符的例子:

#include <stdio.h>
int main(int argc, const char * argv[]) {
    int i = 1;
    int j = 1;
    int n = i++;
    int m = ++j;

    printf("i : %d, j : %d \n", i, j);  // i : 2, j : 2
    printf("n : %d, m : %d \n", n, m);  // n : 1, m : 2

    int k = 0xfe;
    int k2 = 0xaabbccfe;

    // 设置k和k2的所有bit(除去最后3个bit)的值为0
    int x = k & 7;
    int x2 = k2 & 7;
    printf("x : %d, x2 : %d \n", x, x2);  // x : 6, x2 : 6

    int d = 0x1e;
    int d2 = 0xaa1e;
    // 设置d和d2的最后3个bit的值为0
    int y = d & ~7;
    int y2 = d2 & ~7;
    printf("y : %x, y2 : %x \n", y, y2);  // y : 18, y2 : aa18

    return 0;
}

自增运算符++放在变量前面,例如++i,称为前缀运算符(prefix operator),++放在变量后面,例如i++,称为后缀运算符(postfix operator),两种情况下i都会增加1。表达式++i中的i值先增加1,再使用i的值。表达式i++则是先使用i的值,再将i的值增加1。

自增++和自减--运算符只能应用在变量上,不能应用在表达式上。例如:语句(i*j)++;会提示编译错误。

运算符优先级和运算符结合性

下面是运算符优先级(Operator Precedence)运算符结合性(Operator Associativity)的列表,同一行的操作符有相同的优先级,从上往下优先级递减,后面是运算符结合性:包含从左到右从右到左

  1. () [] -> .     从左到右。
  2. ! ~ ++ -- + - * & (类型名) sizeof 从右到左。都是一元(Unary)运算符。+ -加在数字前表示正、负。* &是间接引用运算符和取址运算符。(类型名)表示类型转换。
  3. * / %             从左到右。*是乘法运算符。
  4. + -                从左到右。加、减运算符。
  5. << >>             从左到右。
  6. < <= > >=      从左到右。
  7. == !=             从左到右。
  8. &                    从左到右。位and运算符。
  9. ^                    从左到右。
  10. |                    从左到右。
  11. &&                  从左到右。
  12. ||                  从左到右。
  13. ?:                  从右到左
  14. = += -= *= /= %= &= ^= |= <<= >>=   从右到左
  15. ,                    从左到右。

操作符结合性用于在缺少括号的情况下,如何对相同级别的多个运算符进行分组。如=操作符是从右到左结合的(可简称右结合),例如语句a=b=c;,等同于a=(b=c);。表达式b=c返回值为c的值。语句a=b=c;则是将变量a和b设置为c的值。

下面是运算符优先级的例子:

#include <stdio.h>
int main(int argc, const char * argv[]) {
    int i = 2;
    int j = ++i * 3;

    printf("i : %d, j : %d \n", i, j);  // i : 3, j : 9

    int k = 2 == 2;
    printf("k == %d \n", k);  // k == 1

    // & 优先级低于 ==
    if (1 & 2 == 2)
        printf("1 & 2 == 2 \n");  // 1 & 2 == 2

    return 0;
}


下面是运算符结合性的例子:

#include <stdio.h>
int main(int argc, const char * argv[]) {
    int i = 1;
    int j, k;
    j = k = i;

    printf("i : %d, j : %d, k : %d \n", i, j, k); // i : 1, j : 1, k : 1

    // ?: 右结合
    int d = k ? 1 : k ? 0 : 0;
    int d2 = (k ? 1 : k) ? 0 : 0;
    printf("d : %d, d2 : %d \n", d, d2);  // d : 1, d2 : 0

    return 0;
}

上面例子中?:运算符是右结合的,语句int d = k ? 1 : k ? 0 : 0;等同于int d = k ? 1 : (k ? 0 : 0);