【C语言学习】操作符和表达式【初阶详解篇9】

文章目录

    • 本章重点:操作符和表达式
    • 操作符
      • 操作符分类:
        • 1.算术操作符:+ 、 -、 *、 /、 %*
        • 2.移位操作符:<< >>
        • 3.位操作符:&按位与 |按位或 ^按位异或
        • 4.赋值操作符:= += -= *= /= <<= >>= %= &= |= ^=
        • 5.单目操作符:! + - & sizeof ~ ++ -- *
        • 6.关系操作符:> >= < <= != ==
        • 7.逻辑操作符:&&按位与、 || 按位或
        • 8.条件操作符:exp1 ? exp2 : exp3
        • 9.逗号表达式:(表达式,表达式,表达式)
        • 10.下标引用操作符:[ ]
        • 11.函数调用操作符:()
        • 12.结构成员操作符:. ->
    • 表达式
      • 表达式求值
        • 隐式类型转换
        • 整型提升的意义:
        • 算数转换
        • 操作符的属性
        • 问题表达式

本章重点:操作符和表达式

操作符

操作符分类:

算术操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用、函数调用和结构成员

1.算术操作符:+ 、 -、 、 /、 %

/:取整操作符

#include
int main()
{
 int a = 3/ 5;
 printf("%d\n", a);//0
 
 float b = 3/ 5;
 printf("%f\n", b);//0.000000
 
 double c = 3 / 5;
 printf("%lf\n", c);//0.000000
 
 float d = 6/ 5;
 printf("%f\n", d);//1.000000
 
 double e= 6.0/ 5;
 printf("%lf\n", e);//1.200000
 return 0;
}

%:取模操作符

int main()
{
 int f = 7 % 3;//取模操作符两端必须都是整数
 printf("%d\n", f);//1
 return 0;
}
2.移位操作符:<< >>

<<:左移操作符

int main()
{
 int a = 2;
 int b = a << 1;//把a的二进制向左移动一位
 printf("b=%d\n", b);//b=4
 return 0;
}

代码解析:

  • 因为int 是四个字节,所以a的二进制序列为00000000000000000000000000000010=2
  • a << 1:把a的二进制向左移动一位为:00000000000000000000000000000100=4
  • <<左移操作符:左边丢弃,右边补0.
>>:右移操作符
int main()
{
 int a = 10;
 int b = a >> 1;//把a的二进制向右移动一位
 printf("b=%d\n", b);//b=5
 return 0;
}

代码解析:

  • a的二进制序列为:00000000000000000000000000001010 = 21+23=10
  • a >> 1; 把a的二进制向右移动一位为:00000000000000000000000000000101= 5
  • 右移操作符:分为两种
    1.算术右移:右边丢弃,左边补原符号位
    2.逻辑右移:右边丢弃,左边补0
    eg:
int main()
{
 int a = -1;
 int b = a >> 1;//把a的二进制向右移动一位
 printf("a=%d\n", a);//a=-1
 printf("b=%d\n", b);//b=-1
 return 0;
}

整数的二进制表示形式:其实有3种

  • 对于负数:
    原码:直接根据数值写出的二进制序列就是原码
    反码:原码的符号位不变,其他位按位取反就是反码
    补码:反码+1,就是补码
    a=-1,存放在内存中,存放的是二进制的补码
    -1的原码:10000000000000000000000000000001
    -1的反码:11111111111111111111111111111110
    -1的补码:11111111111111111111111111111111
  • 对于正数:原码、反码、补码都相同
    a>>1把a的二进制向右移动一位,移动的是-1的补码,为:11111111111111111111111111111111
    将补码转换成原码为:10000000000000000000000000000001=-1
    因此,当前的右移操作符,使用的是算术右移

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

int num = 10;
int b=num >> -1;//error
3.位操作符:&按位与 |按位或 ^按位异或

&:按位与

int main()
{
 int a = 3;
 int b = 5;
 //&:按位与,这个位是二进制位
 int c = a&b;
printf("c=%d\n",c);//c=1
}

代码解析:

  • a=3,3的二进制位为11,表示为32个bit为是:00000000000000000000000000000011
  • b=5,5的二进制位为101,表示为32个bit为是:00000000000000000000000000000101
  • a&b: a的二进制位&b的二进制位,只有全为1的时候才为1:00000000000000000000000000000001=1

|:按位或

int main()
{
 int a = 3;
 int b = 5;
 //|:按位或,这个位是二进制位
 int c = a | b;
 printf("c=%d\n", c);c=7
}
  • a=3,3的二进制位为11,表示为32个bit为是:00000000000000000000000000000011
  • b=5,5的二进制位为101,表示为32个bit为是:00000000000000000000000000000101
  • a|b: a的二进制位|b的二进制位,只要有一个值为1就是1:00000000000000000000000000000111=7
    ^:按位异或
int main()
{
 int a = 3;
 int b = 5;
 //^:按位异或,这个位是二进制位
 int c = a ^ b;
 printf("c=%d\n", c);//c=6
}

代码解析:

  • 对应的二进制位进行异或
  • 规则:相同为0,相异为1
  • a=3,3的二进制位为11,表示为32个bit为是:00000000000000000000000000000011
  • b=5,5的二进制位为101,表示为32个bit为是:00000000000000000000000000000101
  • a^ b: a的二进制位^ b的二进制位,相同为0,相异为1:00000000000000000000000000000110=6
    注意:他们三个的操作数必须是整数。

一道面试题:交换两个int变量的值,不能使用第三个变量,即a=3,b=5,交换之后,a=5,b=3;
1.先使用变量写

int main()
{
 int a = 3;
 int b = 5;
 //交换
 int  c = 0;
 printf("a=%d b= %d\n", a, b);
 c = a;
 a = b;
 b = c;
 printf("a=%d b= %d\n", a, b);
 return 0;
}

2.不使用第三个变量写

int main()
{
 int a = 3;
 int b = 5;
 //交换
 int  c = 0;
 /*printf("a=%d b= %d\n", a, b);
 a=a + b;
 b=a - b;
 a=a - b;*/
 //这样写其实有一定的缺点:数值太大就会溢出!
 //应该用异或的方式写
 printf("a=%d b= %d\n", a, b);
 a = a^b;//a和b异或产生的结果放到a里面去011^101=110
 b = a^b;//110^101=011
 a = a^b;//110^011=101
 printf("a=%d b= %d\n", a, b);
 return 0;
}   

3.易得结论:

int main()
{
 int a = 3;
 a^a = 0;
 //011
 //011
 //000
 0 ^ a = a;
 //000
 //011
 //011
 return 0;
}

练习:编写代码实现,求一个整数存储在内存中的二进制中1的个数

非完整代码

int main()
{
 int a = 13;
 //00000000000000000000000000001101(1:00:00)
 //00000000000000000000000000000001
 //00000000000000000000000000000001
 return 0;
}
4.赋值操作符:= += -= *= /= <<= >>= %= &= |= ^=

赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值。

int main()
{
 int a = 10;
 a = 100;
 a = a + 100;//1
 a += 100;//2      1和2相同
}

注意:一个=叫赋值;两个==叫相等

5.单目操作符:! + - & sizeof ~ ++ – *

单目操作符:只有一个操作数
!:逻辑反操作

int main()
{
 int flag = 5;
 //flag为真
 if (flag)
 {
  printf("哈哈\n");
 }
 int a = 0;
 printf("%d\n", !a);//1   前面加!,将真变为假
 return 0;
}

sizeof:计算变量和类型的长度,单位为字节

#include
int main()
{
 int a = 10printf("%d\n", sizeof(a));//4 计算a所占空间的大小,单位是字节
 printf("%d\n", sizeof(int));
 printf("%d\n", sizeof a);
 
 int arr[10] = { 0 };
 printf("%d\n", sizeof(arr));//单位是字节
 printf("%d\n", sizeof(int [10]));//int [10]s是arr数组的类型
 
 short s = 5;
 int a = 10;
 printf("%d\n", sizeof(s = a + 2));//2   此时计算的还是s所占空间的大小
 printf("%d\n", s);//5
 return 0;
}
  • sizeof计算大小的时候,对于变量名计算大小的时候,可以省略括号,对于类型不可以省略。由此也可证明sizeof是一个操作符,而不是一个函数,因为函数的括号是不能省的。
  • sizeof括号内的表达式是不参与运算的

~:按位取反

int main()
{
 int a = -1;
 //-1的二进制序列为:  10000000000000000000000000000001--原码
 //                  11111111111111111111111111111110--反码
 //                  11111111111111111111111111111111--补码
 //~按位取反:包括符号位在内全部取反
 //111111111111111111111111111111111
 //000000000000000000000000000000000
 int b = ~a;
 printf("%d\n", b);//0
 printf("%d\n", a);//-1 
}

++ --操作符

int main()
{
 int a = 10;
 int b = a++;//后置++,先使用,再++
 printf("%d\n", a);//11
 printf("%d\n", b);//10
 
 int b = ++a;//前置++,先++,后使用
 printf("%d\n", a);//11
 printf("%d\n", b);//11
 
 //后置--,前置--,使用和++相同。
int b = 10;
printf("%d\n", b--);//10
printf("%d\n", b);//9
 return 0;
}

&:取地址操作符

int main()
{
 int a = 10;
 printf("%p\n", &a);
 int *pa = &a;//pa用来存放地址的,int * 说明pa就是一个指针变量
 *pa=20;//* :解引用操作符/间接访问操作符
 //*pa=20:就是把上面的a改成了20
 printf("%d\n", a);
 return 0;
}

强制类型转换:括号里面放个类型

int main()
{
 int a = (int)3.14;//把3.14强制类型转换成int类型
 return 0;
}
6.关系操作符:> >= < <= != ==
int main()
{
 int a = 3;
 int b = 5;
 if (a==b)
 if (a<b)
 if (a >= b)
 {
 }
}
7.逻辑操作符:&&按位与、 || 按位或

&&按位与

int main()
{
 int a = 3;//真
 int b = 0;//假
 if (a&&b)//两个同时为真才为真,其他都为假
 {
  printf("hehe\n");//不打印
 }
 return 0;
}

|| 按位或

int main()
{
 int a = 3;//真
 int b = 0;//假
 if (a||b)//两个只要有一个真就为真,两个同时为假才为假
 {
  printf("hehe\n");//不打印
 }
 return 0;
}

逻辑与和或的特点:程序输出的结果是什么?

#include 
int main()
{
 int i = 0, a = 0, b = 2, c = 3, d = 4;
 i = a++ && ++b && d++;//&&左边为假的话,后面就不用算了
 printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);//1 2 3 4
 
 i = a++||++b||d++;//||左边为真,后面就不需要算了
 printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);//2 2 3 4
 
 int i = 0, a = 0, b = 2, c = 3, d = 4;
 i = a++||++b||d++;//||左边为假时,后面需要算;左边为真,后面就不需要算了
 printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);//1 3 3 4
 return 0;
}
8.条件操作符:exp1 ? exp2 : exp3
int main()
{
 int a= 3;
 int b = 0;
 if (a > 5)
  b = 1;
 else
  b = -1;
 //三目操作符
 b = a > 5 ? 1 : -1;//a是否大于5,如果大于输出1,否则输出-1;与if....else 语句逻辑益阳 
 return 0;
}
9.逗号表达式:(表达式,表达式,表达式)

逗号表达式是由逗号隔开的一串表达式, 逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。

int main()
{
 int a = 3;
 int b = 5;
 int c = 0;
 //逗号表达式要从左向右依次计算,整个表达式的结果是最后一个表达式的结果
 int d = (c= 5, a = c + 3, b = a - 4, c += b);
 printf("%d\n", d);//9
 return 0;
}
10.下标引用操作符:[ ]

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

int main()
{
 int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
 printf("%d\n", arr[4]);//[]:下标引用操作符
 //[]的操作数是2个:arr,4
 return 0;
}
11.函数调用操作符:()

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

//函数的定义
int Add(int x, int y)
{
 return x+y;
}
void test()
{ }
int main()
{
 int a = 10;
 int b = 20;
 //函数调用,调用add函数
 int ret=Add(a, b);//()函数调用操作符,有参的话需要传参
 //()操作数:函数名Add,参数a,参数b
 test();//无参的话不需要传参,但是()必须写
 return 0;
}
12.结构成员操作符:. ->

结构成员操作符:访问一个结构的成员

. 结构体.成员名
-> 结构体指针->成员名

//结构体
//创建了一个自定义的类型
struct Book
{
 //结构体的成员变量
 char name[20];
 char id[20];
 int price;
};
int main()
{
 //int num = 10;
 //结构体变量名.成员名
 struct Book b = { "C语言", "C20210709", 55 };
 printf("书名:%s\n", b.name);//.操作符,用.的方式可以找出结构体变量里面的成员
 printf("书号:%s\n", b.id);
 printf("定价:%d\n", b.price);
 return 0;
}

打印结果如下:
【C语言学习】操作符和表达式【初阶详解篇9】_第1张图片

struct Book
{
 //结构体的成员变量
 char name[20];
 char id[20];
 int price;
};
int main()
{
 //int num = 10;
 struct Book b = { "C语言", "C20210709", 55 };
 struct Book * pb = &b;//* 说明pb是一个指针,struck book 说明pb指向的那个b的类型是struct Book
 //如何通过pb来访问b呢?
 //用结构体变量名.成员名:就可以找到他的成员
 printf("书名:%s\n", (*pb).name);
 printf("书号:%s\n", (*pb).id);
 printf("定价:%d\n", (*pb).price);
 //这种方式是先解引用找到结构体对象,然后在通过.的方式找到
 
 有没有更简洁的办法呢?
 也可以这样写
 //用结构体指针->成员名:就可以找到他的成员
 //这种方式的意思是:pb是个结构体指针,它指向的那个对象的name、id、price
 printf("书名:%s\n", pb->name);
 printf("书号:%s\n", pb->id);
 printf("定价:%d\n", pb->price);
 return 0;
}

表达式

表达式求值

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

隐式类型转换

C的整型算术运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。

整型提升的意义:
  • 表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度,一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
  • 通用CPU(general - purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令
    中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
    换为int或unsigned int,然后才能送入CPU去执行运算。

通过代码理解:

int main()
{
 char a = 3;
 char b = 127;
 char c = a + b;
 printf("%d\n", c);//-126
 return 0;
}

这个代码需要进行好好的分析!!!
代码解析:

  • 3的二进制序列:00000000000000000000000000000011,32个比特位,但char只能存放8个比特位,所以发生截断, 只存00000011这8个比特位=a
  • 127的二进制序列:000000000000000000000000011111111发生截断只存01111111这8个比特位=b
  • 我们发现a和b都是char类型的,都没有达到一个int的大小,这里就会发生整形提升

如何进行整体提升呢?

整形提升是按照变量的数据类型的符号位来提升的
变量a的二进制位(补码)中只有8个比特位:
00000011
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000011
变量b的二进制位(补码)中只有8个比特位:
01111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000001111111
因为c=a+b
所以00000000000000000000000000000011
00000000000000000000000001111111
00000000000000000000000010000010
又因为c是char类型的只能放8个比特位所以也需要发生截断,所以使用最低位的8个比特位10000010

疑问:那结果为什么是-126呢?

因为c是一个char类型,但打印的是整型,所以需要将c进行整型提升
变量c的二进制位(补码)中只有8个比特位:
10000010
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111110000010(补码)
因为在内存里面放的是补码的形式,正数的原码、反码、补码都相同但负数的不同,需要通过计算求原码
打印出我们肉眼看到的数字
补码-1得反码:11111111111111111111111110000001(反码)
反码符号位不变其他按位取反得原码:
10000000000000000000000001111110(原码)
通过原码可以得出结果为-126

负数的整形提升

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

正数的整形提升

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

下面我们来点整形提升的例子
实例1

int main()
{
 char a = 0xb6;
 short b = 0xb600;
 int c = 0xb6000000;
 if (a == 0xb6)
  printf("a");
 if (b == 0xb600)
  printf("b");
 if (c == 0xb6000000)
  printf("c");
 return 0;
}

代码解析:

  • 实例1中的a, b要进行整形提升, 但是c不需要整形提升 a, b整形提升之后, 变成了负数, 所以表达式a == 0xb6, b == 0xb600 的结果是假, 但是c不发生整形提升, 则表达式 c == 0xb6000000 的结果是真.
    所程序输出的结果是: c

实例2

int main() 
{ 
 char c = 1;
 printf("%u\n", sizeof(c)); //1
 printf("%u\n", sizeof(+c)); //4
 printf("%u\n", sizeof(-c)); //4
 printf("%u\n", sizeof(!c)); //在vs上测试是1,在linux(gcc平台上)上测试是4,要以gcc为准
 return 0; 
}

代码解析:

  • 实例2中的, c只要参与表达式运算, 就会发生整形提升, 表达式 + c, 就会发生提升, 所以 sizeof(+c) 是4个字节.
  • 表达式 - c 也会发生整形提升, 所以 sizeof(-c) 是4个字节, 但是 sizeof©, 就是1个字节
  • 注意:整形提升只用于比int(4个字节)小的,如:char(1个字节),short(2个字节);
  • 等于或大于int的就不需要进行整形提升如:float(4个字节),double(6个字节)
算数转换

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

long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算,这也属于隐式类型转换的一种.

eg:

int main()
{
 int a = 1;
 float f= 3.14f;
 a + f;//他们两个想要参与运算需要将int a转换成float类型(向精度更高的类型转换)
 return 0;
}
操作符的属性

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

  1. 操作符的优先级
  2. 操作符的结合性
  3. 是否控制求值顺序。

两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
eg:

int main()
{
 int a = 4;
 int b = 5;
 int c = a + b * 7;//优先级决定运算顺序
 int d = a + b + 7;//此时优先级不起作用,结合性决定运算顺序
 return 0;
}

附:操作符优先级和结合性一览表

问题表达式

问题代码1:

a*b + c*d + e*f 

上述在计算的时候,由于*比+的优先级高,只能保证,的计算是比+早,但是优先级并不能决定第三个比第一个+早执行。

所以表达式的计算机顺序就可能是:
ab
c
d
ab + cd
ef
a
b + cd + ef
或者:
ab
c
d
ef
a
b + cd
a
b + cd + ef

问题代码2

int fun()
{
     static int count = 1;
     return ++count; }
int main()
{
     int answer;
     answer = fun() - fun() * fun();
     printf( "%d\n", answer);//输出多少?
     return 0; 

代码2中虽然在大多数的编译器上求得结果都是相同的。
但是上述代码 answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知:先算乘法,再算减法。
函数的调用先后顺序无法通过操作符的优先级确定。

问题代码3:非法表达式


int main()
{
 int i = 10;
 i = i-- - --i * ( i = -3 ) * i++ + ++i;
 printf("i = %d\n", i);
 return 0; }

代码3在不同编译器中测试结果不同

总结:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。

你可能感兴趣的:(C语言篇,编程语言基础,c语言)