C初阶-操作符及表达式求值

操作符

分类:

  • 算术操作符

  • 移位操作符

  • 位操作符

  • 赋值操作符

  • 单目操作符

  • 关系操作符

  • 逻辑操作符

  • 条件操作符

  • 逗号表达式

  • 下标引用、函数调用和结构成员

注: 其中移位操作符和位操作符都是对数据在内存中储存的二进制序列进行操作

数据在内存中的存储形式是补码

算术操作符

分类:

  • +

  • -

  • *(乘法)

  • /(除法)

  • %(取余)

注:

1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数

2. 对于 / 操作符如果两个操作数都为整数,执行整数除法(相当于取余返回整数值)

而只要有浮点数执行的就是浮点数除法

3. % 操作符的两个操作数必须为整数返回的是整除之后的余数

移位操作符

分类:

  • >> 右移操作符

  • << 左移操作符

左移操作符移位规则:左边抛弃、右边补0

#include<stdio.h>
int main()
{
    int a = 10;
    /*正数10的原码:00000000 00000000 00000000 00001010
      正数10的反码:00000000 00000000 00000000 00001010
      正数10的补码:00000000 00000000 00000000 00001010*/
    int b = a << 1;
    /*正数10的补码左移一位得到b的补码:00000000 00000000 00000000 00010100
    b的补码取反后得到b的反码:00000000 00000000 00000000 00010100
    b的反码加1得到b的原码:00000000 00000000 00000000 00010100->20
    */
    printf("a=%d\n", a);
    printf("b=%d\n", b);
    return 0;
}

运行结果:

图示:

右移操作符移位规则:

首先右移运算分两种:

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

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

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

int num = 10;
num>>-1;//error

大小的变化

左移:每左移一位,相当于乘2
右移:每右移一位,相当于除2
int main()
{
int a = -10;
/*负数a的原码:10000000 00000000 00000000 00001010
负数a的反码(操作符不变,其它取反):11111111 11111111 11111111 11110101
负数a的补码(加一):11111111 11111111 11111111 11110110
*/
int b = a << 1(a自身不变,类似于b=a+1,a的值不变);
/*b的补码(a移1位):11111111 11111111 11111111 11101100
* b的反码:10000000 00000000 00000000 00010011
* b的原码:10000000 00000000 00000000 00010100*/
printf("%d ", b);
return 0;
}

位操作符

分类:

& :按位与 (两数二进制位都为1 —>1)
| :按位或 (两数二进制位有一个为1 —>1)
^ :按位异或 (两数二进制位不同为1,相同为0)

注:操作数必须是整数

换位与:

int main()
{
    int a = 3;
    //原码即补码:00000000 00000000 00000000 00000011
    int b = -5;
    /*原码:10000000 00000000 00000000 00000101
    * 反码:11111111 11111111 11111111 11111010
    * 补码:11111111 11111111 11111111 11111011
    */
    int c = a & b;
    /*00000000 00000000 00000000 00000011
    * 11111111 11111111 11111111 11111011
    * 00000000 00000000 00000000 00000011*/
    //SO c的补码与a等价,结果为3
    printf("%d ", c);
    return 0;
}

4.换位或:

int main()
{
int a = 3;
//原码即补码:00000000 00000000 00000000 00000011
int b = -5;
//补码:11111111 11111111 11111111 11111011
int c = a | b;
/* 00000000 00000000 00000000 00000011
* 11111111 11111111 11111111 11111011
* 11111111 11111111 11111111 11111011*/
//so c的补码和b的等价 ,结果为-5
printf("%d ",c);
return 0;
}

5.换位异或:相同为0,相异为1

int main()
{
int a = 3;
int b = -5;
int c = a ^ b;
/* 00000000 00000000 00000000 00000011
* 11111111 11111111 11111111 11111011
* 11111111 11111111 11111111 11111000*/
/*c的补码:11111111 11111111 11111111 11111000
*反码:10000000  00000000 00000000 00000111
* 原码:10000000    00000000 00000000 00001000->-8*/
printf("%d",c);
return 0;
}

异或操作符:

①、a ^ a = 0;

②、0^ a = a;

③、异或是支持交换律

for instance:

3^3^5<=>3^5^3

异或(^)的特点

  • 两数相同,异或为0

  • 0与任何数异或,还是这个数

  • (从上面来看可以说)一个数异或另一个数两次还是原数

一道变态的面试题:不创建临时变量(第三个变量),实现两个数的交换:

方法一:

int main()
{
int a = 1;
int b = 2;
a = a + b;
b = a - b;
a = a - b;
}
printf("a=%d b=%d",a,b);
return 0;
}

方法二:

int main()
{
int a = 1;
int b = 2;
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("a=%d b=%d",a,b);
return 0;
}

赋值操作符:

int main()
{
    int a = 7; int y = 8; int x = 0;
    /*application one : 不满意初值就给他赋值替换
    a = 10;*/
    /*application two : 连续赋值
    a = x = y + 1;
    printf("%d %d %d",a,x,y);*/
}

复合赋值符:

  • +=

  • -=

  • *=

  • /=

  • %=

  • >>= (for instance :b>>=1<=>b=b>>1,这种情况下b自身才会发视变化)

  • <<=

  • &=

  • |=

  • ^=

使用举例:

int x = 10;
x = x+10;
x += 10;//复合赋值
//其他运算符一样的道理。这样写更加简洁

单目操作符:

  • ! 逻辑反操作

  • - 负值

  • + 正值

  • & 取地址

  • sizeof 操作数的类型长度(以字节为单位)

  • ~ 对一个数的二进制按位取反

  • -- 前置、后置--

  • ++ 前置、后置++

  • * 间接访问操作符(解引用操作符)

  • (类型) 强制类型转换

注:单目操作符即只有一个操作元素,多少目即操作符需要带上多少个数据

逻辑反操作:

int main()
{
    int flag = 1;//试试flag=0的情况
    printf("%d\n",flag);
    printf("%d\n",! flag);
}

插入:布尔类型(C99中引入的):表示真假的类型

#include<stdbool.h>//布尔类型必须使用这个头文件

for instance ①:取代数字表示的真假

int main()
{
    _Bool flag = true;//试试flag=flase;
    if (flag)
    {
    printf("哈哈\n");
    }
}

for instance ②:用于闰年判断

_Bool is_leap_year(int year)
{
    if (((year % 4 == 0) && (year % 100 != 0)) || year % 100 == 0)
    return true;
    else
    return false;
}

注:_Bool与bool等效


负值:

int main()
{
    int a = 10;//试试a=-10;
    printf("%d\n",a);
    printf("%d\n",- a);
}

正值:没什么用,实际应用中都是被省略掉的

int main()
{
    int a = 10;//试试a=-10;
    printf("%d\n",a);
    printf("%d\n",+ a);
}

unsigned:将符号位视为有效数字位。但在转反码时,还是当作符号位不动它

int main()
{
    //int<=>signed int 
    unsigned int a = -10;
    /*原码:10000000 00000000 00000000 00001010
    反码:11111111 11111111 11111111 11110101
    补码:11111111 11111111 11111111 11110110->4, 294, 967, 286*/
    printf("%u ",a);
}

&:取出对象在内存中的地址

int main()
{
int a = 10;
printf("%p\n",&a);
}

&与*的连用:

for instance ①:地址

int main()
{
    type one:
    int a = 10;
    int* pa = &a;
    printf("%p\n",&a);
    printf("%p\n", pa);
    type two:
    char ch = 'w';
    char* pc = &ch;
    printf("%p\n", &ch);
    printf("%p\n", pc);
    type three :
    char arr[10] = {0};
    char* pb = arr;
    printf("%p\n", arr);
    printf("%p\n", pb);
    type four:
    char arr[10] = { 0 };
    char* pd = &arr[0];
    printf("%p\n", &arr[0]);
    printf("%p\n", pd);
    type five:
    char* pe="abcdef";
    printf("%p\n",pe);
    printf("%c\n",*pe);//打印的是字符串首字母a的地址
}

for instance ②:通过解引用符号修改变量

int main()
{
    int a=10;
    int* p=&a;
    *p=20;//解引用操作
    printf("%d",a);
}

关键字:C语言定义的,可以直接用,不用额外引用其它的头文件

sizeof既是关键字,又是操作符,计算变量、数组、类型大小

int main()
{
    int a = 10;
    printf("%d\n", sizeof(a));
    printf("%d\n", sizeof a);//函数调用的时候要写(),但是sizeof的括号可以不写,说明sizeof不是函数
    printf("%d\n", sizeof(int));
    int arr[10] = { 0 };
    printf("%d\n",sizeof(arr));
    printf("%d\n", sizeof arr);
    printf("%d\n", sizeof(int [10]));
}

sizeof内部的表达式是不计算的!!!

int main()
{
    int a = 10;
    short s = 2;
    printf("%d\n",sizeof(s=a+3));//s就是一地头蛇,结果什么类型等号左边的s说了算
    printf("%d\n",s);
}

运行结果:

对于++/--操作符

//++和--运算符
//前置++和--
 //前置++和--:
 #include <stdio.h>
 int main()
 {
     int a = 10;
     int x = ++a;
     //先对a进行自增,然后对使用a,也就是表达式的值是a自增之后的值。x为11。
     int y = --a;
     //先对a进行自减,然后对使用a,也就是表达式的值是a自减之后的值。y为10;
     return 0;
 }
 
 //后置++和--
 #include <stdio.h>
 int main()
 {
     int a = 10;
     int x = a++;
     //先对a先使用,再增加,这样x的值是10;之后a变成11;
     int y = a--;
     //先对a先使用,再自减,这样y的值是11;之后a变成10;
     return 0;
 }

~按位取反:仅针对整数

#include <stdio.h>
int main()
{
    int a = 0;
    /*a的原码、反码和补码是00000000 00000000 00000000 00000000
    ~a的补码为a的补码取反后为:11111111 11111111 11111111 11111111
    ~a的原码为~a的补码取反加一:10000000 00000000 00000000 00000001—>-1
    */
    printf("%d\n",~a);
} 

运行结果:

通过>>、<<、|、&、^实现改一个数在二进制下任意一个数:

int main()
{
    int a = 9;
    //1:0改1
    //00000000 00000000 00000000 00001001  把00001001改成00011001
    //00000000 00000000 00000000 00010000  把这两个补码用|位操作符运算,但是这个补码怎么来的?——》1<<4
    a|=(1<<4);
    printf("%d\n",a);
    //2:1改0
    把a的二进制第五位改回0:
    //00000000 00000000 00000000 00011001  改成01001
    //11111111 11111111 11111111 11101111  把这两个补码用^位操作符运算,但是这个补码怎么来的?——》1<<4
    a&=(~(1<<4));
    printf("%d\n",a);
}

关系操作符

  • >

  • >=

  • <

  • <=

  • != 用于测试“不相等”

  • == 用于测试“相等”

注意: 在编程的过程中== 和=非常容易混淆写错,而造成错误

逻辑操作符

  • && 逻辑与

  • || 逻辑或

区分逻辑与和按位与 区分逻辑或和按位或

1&2----->0
1&&2---->1
1|2----->3
1||2---->1

条件操作符

表达式:exp1 ? exp2 : exp3

说明:

①这是一个三目操作符

②当exp1的值为真时,执行exp2表达式,反之,执行exp3表达式

示例:

求a,b中最大值
int max = a>b ? a : b;

逗号表达式

表达式:exp1,exp2,exp3,...,expN

说明:

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

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

下标引用、函数调用和结构成员

[ ] 详解:

名字: 下标引用操作符
括号内是: 操作数
构成: 一个数组名 + 一个索引值(经常与数组一起使用)

示例:

 int arr[10];//创建数组
 arr[9] = 10;//实用下标引用操作符
 [ ]的两个操作数是arr和9

( ) 详解:

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

示例:

int main()
 {
     test1();            //实用()作为函数调用操作符
     test2("hello ");   //实用()作为函数调用操作符
     test3(a,3);
     return 0;
 }

.详解:

作用: 访问一个结构的成员

示例:

#include <stdio.h>
struct Stu
{
     char name[10];
     int age;
     char sex[5];
     double score;
};
void set_age1(struct Stu stu)
{
     stu.age = 18;
}
void set_age2(struct Stu* pStu)
{
     pStu->age = 18;//结构成员访问
}
int main()
{
 struct Stu stu;
 struct Stu* pStu = &stu;//结构成员访问
 
 stu.age = 20;//结构成员访问
 set_age1(stu);
 
 pStu->age = 20;//结构成员访问
 set_age2(pStu);
 return 0;
}

表达式求值

表达式求值顺序一部分是由操作符的优先级和结合性决定

有些表达式的操作数在求值的过程中可能需要转换类型

隐式类型转换

概念:C的整型算术运算总是至少以缺省整型类型的精度来进行的,为了获得这个精度

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

整型提升的意义:

表达式的整型运算要在CPU的相应运算器件内执行
CPU内整型运算器操作数字节长度一般就是int字节长度,也是CPU的通用寄存器的长度
两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度
通用CPU是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有相关指令)
所以表达式中长度可能小于int长度的整型值,都必须先转换为int或unsigned int才能执行运算

示例:

//实例1
char a,b,c;
...
a = b + c;

注:b和c的值被提升为普通整型,然后再执行加法运算。加法运算完成之后,结果将被截断,然后再存储于a中

截断:

在C语言中进行变量赋值的时候,赋值了超出范围的数据,即将整数存入比它占字节小的变量类型中时,就会发生截断,保留相应的整数二进制序列的低位,其余部分抛弃。

整型提升

负数(有符号数):高位补1

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

正数(有符号数):高位补0

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

无符号数:高位补0

unsigned char a = 300;
// %u - unsigned int - 按无符号整型数输入或输出数据
//注意:无符号整型数用 %u 打印,否则不会得到正确结果
// %d 表示有符号十进制数的打印
printf("%u", a);  //输出 44

解释:

正数 300 超过了 char 类型的取值范围,其在内存中的补码形式:

00000000 00000000 00000001 00101100 - 正数300

变量 a 是无符号类型,表示一个正数

而 char 类型占用一个字节,所以 300 存入变量 a 截断保留低8位的二进制数,其余部分抛弃,得到变量 a 的二进制序列(补码)如下:

0010 1100 - a

无符号数整型提升时高位补 0 ,结果为:

00000000 00000000 00000000 00101100 - a(补码)

将补码转换为原码就是十进制的 44

示例一:

int main()
{
    char a = 3;
    //整形3的补码:00000000 00000000 00000000 00000011
    //但是我们这里的a只有1byte,也就是8bit
    //所以a:00000011
    
    char b = 127;
    //整形127的补码:00000000 00000000 00000000 01111111
    //同理b:01111111
    
    char c = a + b;
    //a和b都是char类型,自身大小都是1byte,所以这里计算的时候要整形提升
    //因为都是正数,所以整形提升前面补0
    //a:  00000000 00000000 00000000 00000011
    //b:  00000000 00000000 00000000 01111111
    //a+b:00000000 00000000 00000000 10000010
    //计算完后发生截断(char一共8字节,取后面8位)
    //c:  10000010
    printf("%d\n", c);
    //以%d的形式打印,也就是以整形形式打印,这里c又发生了整形提升
    //但是这里因为c是10000010,就认为它的最高位是1,也就是负数了
    //负数整形提升是前面补1
    //c补:11111111 11111111 11111111 10000010(内存里存的是补码)
    //c反:11111111 11111111 11111111 10000001(负数反码=补码-1)
    //c原:10000000 00000000 00000000 01111110(负数原码=反码符号位不变其他全变)
    //我们打印的是原码,也就是-126
    return 0;
}

注:整形提升也就是发生在short和char这两个类型上,因为其他类型都比int型大

示例二:

#include<stdio.h>
int main()
{
    char a = 0xb6;
    //0000 0000 0000 0000 0000 0000 1011 0110 - 0xb6(182)
    //截断:
    //1011 0110 - a(补码,符号位为1)(-54)
    short b = 0xb600;
    //0000 0000 0000 0000 1011 0110 0000 0000 - 0xb600(46592)
    //截断:
    //1011 0110 0000 0000 - b(补码,符号位为1)(-13824)
    int c = 0xb6000000;
    if (a == 0xb6)
        printf("a");
    if (b == 0xb600)
        printf("b");
    if (c == 0xb6000000)
        printf("c");
    return 0;
}

运行结果分析,两种理解的角度:

  • 因为变量 a 和 b 没有达到一个 int 的大小,所以在参与表达式 a == 0xb6 运算时,被整型提升,生成一个int类型的临时变量与 0xb6 比较,所以 if 表达式为假,变量 c 不需要整型提升。

  • 变量 a 是 char 类型, 0xb6 不在该类型取值范围内,存不下,会被截断,所以变量 a 的大小就不是 0xb6 了,if 表达式为假;变量 b 也一样。


补充知识点:如何判断十六进制的正负

把第一个十六进制位转换成 4 个二进制,高位为 1 则为负,为 0 则为正

【首位小于7(即 0~7 )为正,大于或等于8(即 8~F ) 为负】

如 0xb6 :第一个十六进制位 b --> 二进制位 1011(所以 0xb6 为负)


示例三:

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;
}

解释:

实例2中c只要参与表达式运算,就会发生整形提升

直接 sizeof( c ),c没有参加运算也不会有整形提升,所以大小还是1

表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节;

表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节;对于sizeof(c) ,就是1个字节


补充:char取值范围:https://blog.csdn.net/weixin_67916525/article/details/129003359

算术转换

如果操作符的各个操作数是不同的类型,那么需要其中一个操作数的转换为另一个操作数的类 型,才能进行操作

寻常算术转换体系
long double
double
float
unsigned long int
long int
unsigned int
int
低(在上面的列表排名低)类型应首先转为另一个操作数的类型

示例:

int main()
{
    int a = 4;
    float b = 4.1f;
    float c = a + b;//会把int类型的a算术转换成float类型,再与b进行计算
    return 0;
}

注:如果是高转低的话,则会有存在问题(精度缺失)

float f = 3.14;
int num = f;//隐式转换,会有精度丢失

操作符的属性

复杂表达式的求值有三个影响的因素:

1. 操作符的优先级

2. 操作符的结合性

3. 是否控制求值顺序

注:两个相邻的操作符先执行优先级高的;如果优先级相同,取决于结合性

C语言运算符优先级

注:简单来说就是:! > 算术运算符 > 关系运算符 > && > || > 赋值运算符

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值