C语言——操作符详解

简介: 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.逻辑运算的结果只有真、假两种,分别用整数1、0表示
        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

cd

ab+cd

ef

ab+cd+ef

或者

ab

cd

ef

ab+cd

ab+cd+ef


表达式2

c + --c

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

表达式3

int a=2+3+5

//相邻操作符的优先级相同的情况下,结合性起作用;相邻操作符的优先级高的先算,低的后算。

意:

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

相关文章
|
1月前
|
存储 C语言 索引
【C语言篇】操作符详解(下篇)
如果某个操作数的类型在上⾯这个列表中排名靠后,那么⾸先要转换为另外⼀个操作数的类型后执⾏运算。
|
1月前
|
程序员 编译器 C语言
【C语言篇】操作符详解(上篇)
这是合法表达式,不会报错,但是通常达不到想要的结果, 即不是保证变量 j 的值在 i 和 k 之间。因为关系运算符是从左到右计算,所以实际执⾏的是下⾯的表达式。
|
1月前
|
C语言
C语言操作符(补充+面试)
C语言操作符(补充+面试)
34 6
|
1月前
|
存储 编译器 C语言
十一:《初学C语言》— 操作符详解(上)
【8月更文挑战第12天】本篇文章讲解了二进制与非二进制的转换;原码反码和补码;移位操作符及位操作符,并附上多个教学代码及代码练习示例
44 0
十一:《初学C语言》—  操作符详解(上)
|
2月前
|
C语言
五:《初学C语言》— 操作符
本篇文章主要讲解了关系操作符和逻辑操作符并附上了多个代码示例
34 1
五:《初学C语言》—  操作符
|
3月前
|
C语言
C语言逻辑操作符的短路问题
C语言逻辑操作符的短路问题
|
3月前
|
编译器 C语言
【C语言】:中移位操作符,位操作符详运算规则详解
【C语言】:中移位操作符,位操作符详运算规则详解
28 1
|
3月前
|
存储 编译器 C语言
|
3月前
|
存储 C语言 索引
【C语言基础】:操作符详解(二)
【C语言基础】:操作符详解(二)
|
3月前
|
编译器 C语言
C语言学习记录——操作符详解知识点选记(算术操作符、单目操作符、移位操作符、关系操作符、逻辑操作符、条件操作符......)二
C语言学习记录——操作符详解知识点选记(算术操作符、单目操作符、移位操作符、关系操作符、逻辑操作符、条件操作符......)二
38 3