操作符详解

1.操作符的分类

~算数操作符:+、-、*、/、%

~移位操作符:<<、>>

~位操作符:&、|、^

~赋值操作符:=、+=、-=、*=、/=、%=、<<=、>>=、&=、|=、^=

~单目操作符:!、++、==、&、*、+、-、~、sizeof、(类型)

~关系操作符:>、>=、<、<=、==、!=

~逻辑操作符:&&、||

~条件操作符:?:

~逗号表达式:,

~下标引用:[]

~函数调用:()

~结构成员访问:.、->

操作符中有一些操作符和二进制有关系,需要用到二进制有关的内容

2.二进制和进制转换

二进制、八进制、十六进制是数值的不同表现形式

十进制满10进1,二进制满2进1

2.1二进制转十进制

二进制的权重为2^0,2^1,2^2

按照权重对二进制的数进行运算

2.1.1十进制转二进制数

将十进制数除以2,由上往下依次所得的余数就是十进制转换出的二进制数

2.2二进制转八进制和十六进制

2.2.1二进制转八进制

从二进制序列中右边低位开始向左每3个二进制位会换算1个八进制位,剩余不够3个二进制位的直接换算

0开头的数字,会被当做八进制

2.2.2二进制转十六进制

从二进制序列中右边低位开始向左每4个二进制位会换算个十六进制位,剩余不够4个二进制位的直接换算

十六进制表示数字的时候需要在前面加上0x

3.原码、反码、补码

整数的二进制表示方法有3种,即原码、反码和补码

这三种表示方法中均有符号位和数值位,二进制序列中,最高位是被当作符号位,剩余都是数值位

符号位用‘0’表示正,用‘1’表示负

正数的原、反、补码都相同

负数的三种表示方法各有不同

原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码

反码:将原码的符号位不变,其他位依次按位取反就可以得到反码

补码:反码+1就得到补码

补码得到原码的方式也是可以使用:取反,+1

对于整型来说:数据在内存中存放的实际是补码

4.移位操作符

<<左移操作符

>>右移操作符

操作符的操作数只能是整数

4.1左移操作符

移位规则:左边抛弃、右边补零

4.2右移操作符

移位规则:

首先右移运算符分两种

1.逻辑右移:左边用0填充,右边丢弃

2.算数右移:左边用原该值的符号位填充,右边丢弃

注:对于移位操作符,不要移动负数位,这个是标准未定义的

5.位操作符

位操作符有:&(按位与)、|(按位或)、^(按位异或)、~(按位取反)

注:他们的操作数必须是整数

&(按位与):对应两串二进制序列都为1时,结果位才是1,否则为0

|(按位或):对应两串二进制序列都为0时,结果位才是0,否则为1

^(按位异或):对应两串二进制序列相同时结果为0,不同时结果为1

~(按位取反):将二进制序列中的1变为0,0变为1

一道面试题:不能创建临时变量,实现两个整数的交换

int main()
{
    int a = 10;
    int b = 20;
    int c = a;
    a = b;
    b = c;//这种方法需要创建临时变量
    a = a + b;
    b = a - b;
    b = a - b;//这种方法在数据过大时无法进行计算
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;//推荐使用这种方法
    printf("%d %d", a, b);
    return 0;
}

练习1:求一个整数储存在内存中的二进制中1的个数

方法一:

int main()//此方法在n为负数时无法实现
{
    int n = 13;
    int count = 0;
    while (n)
    {
        if (n % 2 == 0)
            count++;
        n = n / 2;
    }
    printf("%d", count);
    return 0;
}

方法二:

int main()
{
    int n = -1;
    int count = 0;
    int i = 0;
    for (i = 0; i < 32; i++)
    {
        if (((n >> 1) & 1) == 1)
            count++;
    }
    return 0;
}

方法三:

int main()//更为简便的方法
{
    int n = 0;
    scanf("%d", &n);
    int count = 0;
    while (n)
    {
        n = n & (n - 1);
        count++;
    }
    printf("%d", count);
    return 0;
}

练习二:写一个代码,判断n是否为2的次方数

int main()
{
    int n = 0;
    scanf("%d", &n);
    if (n & (n - 1) == 0)
        printf("yes");
    else
        printf("no");
    return 0;
}

练习三:编写代码将13的二进制序列的第5位改为1,然后再改回0

int main()
{
    int n = 13;
    n |= (1 << (5 - 1));
    printf("%d", n);
    n &= (~(1 << (5 - 1)));
    printf("%d", n);
    return 0;
}

6.单目操作符

!、++、--、&、*、+、-、~、sizeof、(类型)这些都是单目操作符

7.逗号表达式

是用逗号隔开的多个表达式

从左向右依次执行,整个表达式的结果是最后一个表达式的结果

int main()
{
    int a = 1;
    int b = 2;
    int d = 0;
    int c = (a > b, a = b + 10, a, b = a + 1);
    if(a=b+1,c=a/2,d>0)
    printf("%d", c);
    return 0;
}

8.下标访问[]、函数调用()

8.1[]下标引用操作符

操作数:一个数组名+一个索引值

8.2函数调用操作符

接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数

9.结构成员访问操作符

9.1结构体

结构体这种自定义的数据类型,可以让程序员创造出合适的类型来

9.1.1结构的声明

struct tag

{

        member-list;

}variable-list;

9.1.2结构体变量的定义和初始化

struct B//变量的定义
{
    char c;
    int m;
};p1//声明类型的同时定义变量p1
struct Stu
{
    char name[20];
    int age;
    struct B bb;//结构体嵌套定义
    float score;
}s5, s6, s7;
int main()
{
    struct Stu s1 = { "张三",20,{'q',100}//结构体嵌套初始化,95.5f };//初始化
    struct Stu s2 = { .age = 10,.score = 98.5f,.name = "aj",.bb = {'q',20} };指定顺序初始化
    struct Stu s3;
    printf("%d %d", s1.age,s1.bb.m);
    return 0;
}

9.2结构成员访问操作符

9.2.1结构成员的直接访问

通过操作符(.)进行访问

使用方式:结构体变量.成员名

9.2.2结构体成员的间接访问(后续讲)

10.操作符的属性:优先级、结合性

这两个属性决定了表达式求值的计算顺序

10.1优先级

优先级是指一个表达式如果包含多个表达式,哪个运算符应该优先执行

10.2结合性

如果两个运算符优先级相同,这时候就要看结合性

大部分运算符是左结合(从左到右执行),少数是右结合,比如赋值运算符

大概记住一下操作符的优先级

• 圆括号( () )

• ⾃增运算符( ++ ),⾃减运算符(- )

• 单⽬运算符( + 和 )

• 乘法( * ),除法( / )

• 加法( + ),减法( )

• 关系运算符( < 、 > 等)

• 赋值运算符(=)

由于圆括号的优先级最高,故可以使用它改变其他运算符的优先级

参考网站:https://zh.cppreference.com/w/c/language/operator_precedence

11.表达式求值

11.1整型提升

C语言中整型算术运算符总是至少以缺省(默认)整型类型的精度来进行的

为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升

整型提升的方法:

1.有符号的整数提升是按照变量的数据类型的符号位来提升的

2.无符号整数提升,高位补0

11.2算数转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的类型转换为另一个操作数的类型,否则操作就无法进行,层次如下

1 long double

2 double

3 float

4 unsigned long int

5 long int

6 unsigned int

7 int

如果某个操作数的类型在上面的列表中靠后,那么首先需要转换为另外一个操作数类型后执行运算

11.3问题表达式解析

11.3.1表达式1

a*b+c*d+e*f

优先级不能决定第三个*比第一个+早执行

11.3.2表达式2

c+--c

无法得知c是--前还是--后的值

11.3.3表达式3

int main()

{

        int i = 10;

        i = i-- - --i * ( i = -3 ) * i++ + ++i;

        printf("i = %d\n", i);

        return 0;

在不同的编译器中测出来的值不一样

11.3.4表达式4

int fun()

{

        static int count = 1;

        return ++count;

}

int main()

{

        int answer;

        answer = fun() - fun() * fun();

        printf( "%d\n", answer);// 输出多少?

        return 0;

}

无法得知函数调用的先后顺序

11.3.5表达式5

int main()

{

        int i = 1;

        int ret = (++i) + (++i) + (++i);

        printf("%d\n", ret);

        printf("%d\n", i);

        return 0;

这个代码中第一个+在执行的时候,第三个++是否执行这个是不确定的