C语言——操作符详解

前言:

操作符又称为运算符:作为运算对象的变量或者常量称为操作数。
操作符左侧的操作数称为左操作数,操作符右边的操作数称为右操作数。
操作符同时对两个操作数进行运算的称为“双目操作符”,操作符只对一个操作数进行运算的称为“单目操作符”。

一、目录

1.各种操作符的介绍
总结了C语言中各个操作符的介绍
2.表达式求值
总结了隐式类型转换中的整型提升和算数转换,还总结了复杂表达式的三种影响因素(操作符的属性)

二、各种操作符的介绍

1.算术操作符( + - * / % )

+(加法) -(减法) *(乘法) /(除法) %(求余数)
1.除了%操作符之外,其他的几个操作符都可以作用于整数和浮点数
2.对于/操作符如果两个操作数都为整数,那么结果为整数,而只要有浮点数执行,那么结果为浮点数
3.%操作符的两个操作数必须为整数,返回的是整数之后的余数
4.%操作符所得结果的符号与运算符左侧的操作数(被除数)的符号相同。如:
-5%2=–1 5%-2=1

2.移位操作符( << >> )

符号位‘1’表示负数,符号位‘0’表示正数
2.1 <<(左移操作符)
移位规则:左边抛弃,右边补0
如:

mun<<1 //实际上mun在没有被赋值的情况下,自身的值不会变
mun<<=1 //mun被赋值了,自身的值变了
2.2 >>(右移操作符)
整数的二进制表示形式:不管是正整数还是负整数都可以写出二进制补码,根据正负直接写出的二进制序列就是原码。
有三种表示形式:原码 、反码 、补码
1)正整数的原码、反码、补码是相同的。
2)负整数的原码、反码、补码是要计算的。
(1)原码—>反码(原码的符号位不变,其它位按位取反得到的就是反码)—>补码(反码+1就是补码)
3)整数在内存中存储的是补码
4)计算的时候也是用补码进行计算
移位规则:右移运算分为两种(C语言没有明确规定是算术右移还是逻辑右移,一般编译器上采用的是算术右移)
2.2.1逻辑移位
左边用0填充,右边丢弃
2.2.2算术移位
左边用原该值的符号位填充,右边丢弃
如:

//算术移位
#include <stdio.h>
int main()
{
    int a = -15;
    int b = a >> 1;
    //a用二进制表示的形式 10000000 00000000 00000000 00001111    原码
    // a                  11111111 11111111 11111111 11110000    反码
    // a                  11111111 11111111 11111111 11110001    补码
    //a进行算术右移1位    11111111 11111111 11111111 11111000    补码
    //b                   11111111 11111111 11111111 11111000    补码
    //b                   11111111 11111111 11111111 11110111    反码
    //b                   10000000 00000000 00000000 00001000    原码//十进制表示形式为-8
    printf("%d", b);//打印-8
    return 0;
}

3.位操作符( & | ^ )

&(按位与)、|(按位或)、^(按位异或)

#include <stdio.h>
int main()
{
    int num1 = 10;
    int num2 = 22;
    int a = num1 & num2;//num1和num2进行按位与运算
    //num1     00000000 00000000 00000000 00001010
    //num2     00000000 00000000 00000000 00010110
    //a        00000000 00000000 00000000 00000010
    //规则:   全1为1
    int b = num1 | num2;//num1和num2进行按位或运算
    // num1    00000000 00000000 00000000 00001010
    // num2    00000000 00000000 00000000 00010110
    // b       00000000 00000000 00000000 00011110
    //规则:   有1则1
    int c = num1 ^ num2;//num1和num2进行按位异或运算
    // num1    00000000 00000000 00000000 00001010
    // num2    00000000 00000000 00000000 00010110
    // c       00000000 00000000 00000000 00011100
    //规则:   有1则1,全1位0
    printf("a=%d,b=%d,c=%d", a, b, c);//a=2,b=30,c=28
    return 0;
}

位操作符的问题:
问题一:不能创建临时变量(第三个变量),实现两个数的交换

#include<stdio.h>
int main()
{
    int a = 10;
    int b = 20;
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
    //原理a^a等于0,a^0等于a,如a=a^b,那么b=a^b^b,故b=a(按位异或支持乘法交换律)
    return 0;
}

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

#include <stdio.h>
int main()
{
    int num;
    scanf("%d", &num);
    int count = 0;
    while (num)
    {
        count++;
        num = num & (num - 1);/*这个表达式会让num的二进制中最右边的
        1消失。通过循环,当num为0时,就能用count统计‘1’的个数*/
    }
    printf("%d", count);
    return 0;
}

4.赋值操作符( += -= *= /= %= >>= <<= &= |= ^= )

赋值操作符是个很棒的操作符,它可以给自己重新赋值

int a = 120;//体重
a = 140;//不满意就赋值

//赋值操作符如下:
int x = 10;
x = x + 10;
x += 10;//复合赋值
其他的运算符一样的道理

5.单目操作符( ! - + & sizeof ~ – ++ * (类型))

 !        逻辑反操作
 -         负值
 +         正值
 &         取地址
 sizeof    操作数的类型长度(以字节为单位)
 ~         对一个数的二进制位取反(按补码二进制位取反,符号位也取反)
 --        前置、后置--
 ++        前置、后置++
 *         间接访问操作符(解引用操作符)
(类型)   强制类型转换
#include <stdio.h>
int main()
{
    int a = -10;
    int* p = NULL;
    int arr[10] = {0};
    printf("%d\n", !2);//0
    printf("%d\n", !0);//1
    a = -a;
    p = &a;
    p = arr;//数组名相当于首元素的地址
    printf("%d\n", sizeof(int));//4
    printf("%d\n", sizeof(a));//4
    printf("%d\n", sizeof a);//4
    printf("%d\n", sizeof (arr));//40
    printf("%d\n", sizeof (int[10]));//40
    return 0;
}
 自增、自减运算
 1.运算规则:
      ++i;--i;    //前缀就先算
      i++; i--;    //后缀就后算
 2.注意:
     只能用变量
 3.使用时谨防出错
     j=++i;//i=i+1,j=i;
     j=i++//j=i;i=i+1;

事例:

#include <stdio.h>
int main()
{
    int a = 10, x, y, z;
    x = a++ + a++; //a+a;++a;++a;
    a = 10;
    y = ++a + (++a);//++a;++a;a+a;
    a = 10;
    z = ++a + a++;//++a,a+a,++a;
    printf("x=%d,y=%d,z=%d", x, y, z);//打印  x=20,y=24,z=22
    return 0;
}

6.关系操作费( > >= < <= != ==)

    >       大于
    >=      大于等于
    <       小于
    <=      小于等于
    !=     不等于(用于测试不相等)
    ==      等于(用于测试相等)
        注意:只能是字符比较

7.逻辑操作符( && || )

    &&    逻辑与
    ||    逻辑或
        注意:
        1.区分逻辑与和按位与
        1&2——>0
        1&&2——>1
        2.区分逻辑或和按位或
        1|2——>3
        1||2——>1
        说明:
        1.逻辑运算的结果只有真、假两种,分别用整数10表示
        2.逻辑运算对象的值,可以是任何数据类型,非0则为真,0表示假
        3.分支或循环中的条件,可用数字(任意类型)表示,且非0数值
        表示真,0表示假.如:
        100&&200     //1
        !(4*5)==0    //1
        2&&8==1      //0
        0||9==3*3    //1
/*逻辑表达式短路特性:计算逻辑表达式时,若计算到
某步已经确定整个表达式的值,则表达式中后面部分将
不再被执行(总是先算表达式1)如:
<表达式1>&&<表达式2>
当<表达式1>为0时,<表达式2>不执行
<表达式1>||<表达式2>
当<表达式1>为1时(非0时),<表达式2>不执行
*/
#include <stdio.h>
int main()
{
    int i = 0, a = 0, b = 2, c = 3, d = 4;
    i = a++ && ++b && d++;
   // i=a++||++b||d++;(发生了短路,结果为a=1,b=3,c=3,d=4)
    printf("a=%d,b=%d,c=%d,d=%d\n", a, b, c, d);//结果为a=1,b=2,c=3,d=4(发生了短路)
    return 0;
}

8.条件操作符( exp1 ? exp2 : exp3 )

#include <stdio.h>
//问题:找到两个数中较大的值
int main()
{
    int m, a=20, b=30;
    if (a > b)
        printf("最大值为:%d\n", a);
    else
        printf("最大值为:%d\n", b);
    //转换为条件表达式
    m = a > b ? a : b;
    printf("最大值为:%d\n", m);
    return 0;
}

9.逗号表达式( exp1, exp2, exp3, exp4, …expN )

逗号表达式:用“,”将几个表达式连接起来而形成的表达式
一般形式为:表达式1,表达式2,表达式3,……表达式N
注意:
逗号表达式,按从左到右次序计算各表达式的值,整个逗号表达式的值是
最后一个表达式的值

例子:写出以下表达式的值
a = 8 * 2, a * 4;            //表达式的值为64(a*4的结果),a的值为16
(a = 8 * 2, a * 4), a * 2;   //表达式的值为32(a*2的结果),a的值为16
a = b = 5, 5 * 2;            //表达式的值为10(5*2的结果),a和b的值都为5
a = (b = 5, 5 * 2);          //表达式为赋值表达式,值为10(5*2的结果),a的值为10,b的值为5

10.下标引用、函数调用和结构成员( [] () . )

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

如:
int arr[10];//创建数组
arr[9] = 10;//给元素赋值
[]的两个操作数是arr和9.

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

#include <stdio.h>
void test1(void)
{
    printf("hehe\n");//打印hehe
}
void test2(const char* str)
{
    printf("%s\n", str);//打印hello hello
}
int main()
{
    test1();//()就是函数调用操作符,最少有一个操作数test1
    test2("hello hello");//()就是函数调用操作符
    return 0;
}

10.3 .(访问一个结构的成员)
. 结构体.成员名
-> 结构体指针->成员名

#include <stdio.h>
struct stu              //    类型名
{
    char name[10];//    |
    int age;//          |     成员名
    char sex[5];//      |
    double score;//     |
};
void set_age1(struct stu a)
{
    a.age = 18;
        printf("%d\n", a.age);//打印18
}
void set_age2(struct stu* a)
{
    a->age = 18;
    printf("%d\n", a->age); //打印18
}
int main()
{
    struct stu st;
    struct stu* pst = &st;
    st.age = 23;           //结果成员访问
    set_age1(st);//值传递(传值调用)
    printf("%d\n", st.age);//打印23
    pst->age=23;           //结构成员访问
    set_age2(pst);//地址传递(传址调用)
    printf("%d\n", pst->age);//打印18
    return 0;
}

三、 表达式求值

表达式的求值顺序一般是由操作符的优先级和结合性决定的;同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。

1.隐式类型转换

1.1整型提升
c的整型算术运算总是至少以缺省整型类型的精度来进行的;为了提升这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。(只有字符和短整型操作数)
整型提升的意义
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。通用CPU是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
如:

char a,b,c;
b=1;
c=2;
a=b+c;
b和c的值先提升为普通整型,然后在执行加法运算。

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

// 负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:1111111
因为char 为有符号的char
所以整形提升的时候,高位补充符号位,即为1
11111111111111111111111111111111
提升之后的结果是:
//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:00000001
因为char为有符号的char
所以整形提升的时候,高位补充符号位,即为0提升之后的结果是 :
00000000000000000000000000000001
//无符号整型提升高位补零

例子1:

#include <stdio.h>
int main()
{
    char c1 = 5;
    char c2 = 127;
    // c1  00000000 00000000 00000000 00000101
    // c1  00000101(截断)
    // c2  00000000 00000000 00000000 01111111
    // c2  01111111
    char c3 = c1 + c2;
    // 根据符号位整型提升
    // c1  00000000 00000000 00000000 00000101
    // c2  00000000 00000000 00000000 01111111
    // c3  00000000 00000000 00000000 10000100
    //截断
    // c3  10000100  补码的形式
    //%d   10进制的形式打印有符号得整数
    //c3   11111111 11111111 11111111 10000100   补码的形式
    //c3   11111111 11111111 11111111 10000011   反码的形式
    //c3   10000000 00000000 00000000 01111100   原码的形式  
    printf("%d\n", c3);
    return 0;
}

例子2:

#include <stdio.h>
int main()
{

    char c = 1;
    printf("%u\n", sizeof(c));//打印结果为1
    printf("%u\n", sizeof(+c));//打印结果为4
        printf(" %u\n", sizeof(-c));//打印结果为4
    return 0;
}

结论:c只要进行了表达式运算,就会发生整型提升,表达式+c,就会发生提升,所以sizeof(+c)是4个字节;表达式-c也发生了整型提升,所以sizeof(-c)也是4个字节;但是sizeof(c)就一个字节

2.算术转换

如果某个操作符的各个操作数属于不同类型,那么除非其中一个操作数转换为另一个操作数的类型,否则操作就无法进行。(运算的一瞬间进行了算术转换,操作数的本质类型还是不变的)下面的层次体系称为寻常算术转换

long double
double
float
unsigned long int
long int
unsigned int
int
向上转换,都是大于等于4个字节
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另一个操作数的类型后执行运算。

注意:
算术转换要合理,要不然会有一些潜在的问题

float f=3.14f;
int mun=f; //发生了隐式转换,会有精度丢失

3.操作符的属性

复杂表达式的求值有三个影响的因素。
1.操作符的优先级
2.操作符的结合性
3.是否控制求值顺序。

操作符优先级
在这里插入图片描述
一些问题表达式:

表达式的求值部分由操作符的优先级决定
表达式1:

ab+cd+e*f
//代码1在计算的时候,由于 *比 + 的优先级高,只能保证, * 的计算是比 + 早, 但是优先级并不能决定第三个 * 比第一个 + 早执行。

所以表达式的计算机顺序就可能是:

ab
c
d
ab+cd
ef
a
b+cd+ef
或者
ab
c
d
ef
a
b+cd
a
b+cd+ef

表达式2
c + --c
//操作符的优先级只能决定自减–的运算在+的运算的前面,但是我们并没有办法得知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。(无法确定唯一的计算路径)

表达式3
int a=2+3+5
//相邻操作符的优先级相同的情况下,结合性起作用;相邻操作符的优先级高的先算,低的后算。

注意:
函数的调用先后顺序无法通过操作符的优先级确定。

  • 28
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 33
    评论
评论 33
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值