C语言初阶——操作符、表达式求值

一、操作符分类

  • 算术操作符
  • 移位操作符
  • 位操作符
  • 赋值操作符
  • 单目操作符
  • 关系操作符
  • 逻辑操作符
  • 条件操作符
  • 逗号操作符

1. 算数操作符 —— +  -  *  /  %

  • % - 取余/取模:只能作用于整数、不可作用于浮点数,返回的是整除之后的余数;其余算数操作符都可作用于整数和浮点数。
  • / - 除法:若操作符左右两边的两个操作数都为整数,则执行整数除法,返回的是整除之后的商;有任意一边为浮点数,则执行浮点数除法
 //算数操作符
int a = 5 / 2;        //结果:2
float b = 5.0 / 2;    //结果:2.5000000
float c = 5 / 2.0;    //结果:2.5000000

int d = 9 % 4;        //结果:1

2. 移位操作符 —— >>  <<

  • 移动的是二进制位;操作数只能是整数。
  • << - 左移操作符:左边丢弃,右边补0。—— 相当于给操作数乘以2。
  • >> - 右移操作符
  1. 逻辑右移:右边丢弃,左边补0。 —— 相当于给操作数除以2。
  2. 算数右移:右边丢弃,左边补该值的原符号位。—— 二进制序列的最高位为符号位,负数的符号位是1。
  • 整数的二进制位表现形式有3种,分别为:原码、反码和补码。 正整数三码相同。
  1. 原码:直接根据数值写出的二进制序列;
  2. 反码:原码的符号位不变,其他位按位取反;
  3. 补码:反码加1。

    例:a = -1存放到内存中的为二进制的补码

           a 的二进制原码为:10000000000000000000000000000001

           a 的二进制反码为:11111111111111111111111111111110

           a 的二进制补码为:11111111111111111111111111111111

           a << 1 的二进制补码为:11111111111111111111111111111110

           a << 1 的二进制反码为:11111111111111111111111111111101

           a << 1 的二进制原码为:10000000000000000000000000000010         结果为:-2

           a >> 1 (逻辑右移) 的二进制补码为:01111111111111111111111111111111

           a >> 1 (逻辑右移) 的二进制反码为:01111111111111111111111111111110

           a >> 1 (逻辑右移) 的二进制原码为:10000000000000000000000000000001    结果非常大

           a >> 1 (算数右移) 的二进制补码为:11111111111111111111111111111111

           a >> 1 (算数右移) 的二进制反码为:11111111111111111111111111111110

           a >> 1 (算数右移) 的二进制原码为:10000000000000000000000000000001    结果为:-1

  • 在C语言的计算中,通常采用的是算数右移。
 //移位操作符
#include<stdio.h>
int main()
{
	int a = -1;
	int b = a << 1;
	int c = a >> 2;
	printf("b=%d\nc=%d\n", b, c);
	return 0;
}                /*输出结果为:b=-2
                              c=-1*/
  • 对于移位运算符,需要注意的是不要移动负数位。

   例:int b = a << -5;    这种书写方式是错误的。

3. 位操作符 —— &  |  ^

  • 位指的是二进制位,是对二进制位进行操作的。
  • & - 按位与:对应的二进制位与。—— 有一假则假。
  • | - 按位或:对应的二进制位或。—— 有一真则真。
  • ^ - 按位异或:对应的二进制位异或。—— 相同为0,相异为1。
 //位操作符
#include<stdio.h>
int main()
{
	int a = 3;       //00000000000000000000000000000011      3
	int b = 5;       //00000000000000000000000000000101      5
	int c = a & b;   //00000000000000000000000000000001      1
	int d = a | b;   //00000000000000000000000000000111      7
	int e = a ^ b;   //00000000000000000000000000000110      6
	printf("c=%d\nd=%d\ne=%d\n", c, d, e);
	return 0;
}          /*输出结果为:c=1
                        d=7
                        e=6*/
  • a ^ a = 0; - 任意两个相同数字异或皆为0。
  • 0 ^ a = a; - 0与任意数字异或还是该数值本身。
  • 3次异或可实现两个数值的交换。
 //不创建临时变量实现两个数的交换
#include<stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	printf("交换前:a=%d b=%d\n", a, b);
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
	printf("交换后:a=%d b=%d", a, b);
	return 0;
}             /*输出结果为:交换前:a=10 b=20
                           交换后:a=20 b=10*/

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

  • 单个 “=” 是赋值,双个 “==” 是判断是否相等。 

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

  • 只有一个操作数

5.1 介绍

  • ! —— 逻辑反操作,取反
  • - —— 负值
  • + —— 正值
  • & —— 取地址
  • sizeof —— 计算操作数的类型长度(单位:字节)
  • ~ —— 对一个数的二进制位进行按位取反
  • -- —— 前置、后置--
  • ++ —— 前置、后置++
  • * —— 解引用操作符(间接引用操作符)
  • (类型)—— 强制类型转换 

5.2 & 取地址 和 * 解引用操作符(间接引用操作符)

  • 两者通常放在一起使用
 //取地址和解引用操作符
#include <stdio.h>
int main()
{
	int a = -10;
	printf("%p\n", &a);    //%p用来打印地址
	int* pa = &a;          //取出a的地址,此时pa是指针变量,指向的是a的地址
	*pa = 20;              //*pa对应的即是a,通过改变*pa的大小可以改变a的大小
	printf("%d\n", a);
	return 0;
}         /*输出结果为:007BF868
                       20*/

5.3 sizeof —— 计算操作数的类型长度

  • sizeof(),()在计算变量时可以省略,其余情况不可省略。好习惯是sizeof后一直加()。
  • sizeof 括号中的表达式是不参与计算的
#include<stdio.h>
int main()
{
	short s = 5;
	int a = 10;
	printf("%d\n", sizeof(s = a + 2));
	printf("%d\n", s);
	return 0;
}             /*输出结果为:2
                           5*/
5.3.1 sizeof 和数组
 //sizeof 和 数组
#include<stdio.h>

void test1(int arr[])
{
	printf("(3) %d\n", sizeof(arr));   //首元素指针变量的大小,该大小只与操作系统有关,与操作数类型无关
	                                   //32位系统一个指针变量的大小为4,64位系统一个指针变量的大小为8
}

void test2(char ch[])
{
	printf("(4) %d\n", sizeof(ch));    //首元素指针变量的大小
}

int main()
{
	int arr[10] = { 0 };
	char ch[10] = { 0 };
	printf("(1) %d\n", sizeof(arr));    //计算arr数组的长度
	printf("(2) %d\n", sizeof(ch));     //计算ch数组的长度

	test1(arr);     //传递的是首元素的地址
	test2(ch);      //传递的是首元素的地址

	return 0;
}              /*输出结果为:(1) 40
                            (2) 10
                            (3) 4
                            (4) 4*/

5.4 ++ 和 -- 运算符

  • a++ 和 a-- —— 后置++、后置--:先使用,后运算。 
  • ++a 和 --a —— 前置++、前置--:先运算,后使用。
#include<stdio.h>
int main()
{
	int a = 10;
	int x = a++;     //x=10, a=11
	int y = ++a;     //y=12, a=12
	int p = --a;     //p=11, a=11
	int q = a--;     //q=11, a=10
	return 0;
}

6. 关系操作符 —— >   >=   <   <=   !=   ==

  • 比较操作数之间的大小、相等关系。
  • 比较两个字符串是否相等,不能使用 == 。

7. 逻辑操作符 —— &&   ||

  • && - 逻辑与:有一假则假。
  • || - 逻辑或:有一真则真。
  • 区分按位与与逻辑与,按位或和逻辑或
 //区分逻辑与按位
1&2------->0     //按位与
1&&2------>1     //逻辑与

1|2------->3     //按位或
1||2------->1    //逻辑或

   例:360笔试题:

 //360笔试题
#include<stdio.h>
int main()
{
	int i = 0, j = 0, k = 0, a = 0, b = 2, c = 3, d = 4;
	i = a++ && ++b && d++;
	printf("计算i时:a=%d b=%d c=%d d=%d i=%d\n", a, b, c, d, i);  //此时先运算a++,先使用后运算,a=0为假,逻辑与有一假则假,后面无需再算。
	
	j = a++ || ++b || a++;         //此时a=1 b=2 c=3 d=4
	printf("计算j时:a=%d b=%d c=%d d=%d j=%d\n", a, b, c, d, j);  //此时先运算a++,先使用后运算,a=1为真,逻辑或有一真则真,后面无需再算。
	
	k = a++ && ++b && d++;         //此时a=2 b=2 c=3 d=4
	printf("计算k时:a=%d b=%d c=%d d=%d k=%d\n", a, b, c, d, k);  //此时a b c运算结果全部为真,一直算到最后。

	return 0;
}            /*输出结果为:计算i时:a=1 b=2 c=3 d=4 i=0
                          计算j时:a=2 b=2 c=3 d=4 j=1
                          计算k时:a=3 b=3 c=3 d=5 k=1*/

8. 条件操作符 —— exp1  exp2 exp3

  • 若表达式1为真,则执行表达式2;若表达式1为假,则执行表达式3.
 //条件操作符
int a = 8;
int b = 5;
max = (a > b ? a : b);     //求a和b的较大值
     //结果为:8

9. 逗号表达式 —— exp1, exp2, exp3,  ...expN

  • 从左向右依次执行,整个表达式的结果是最后一个表达式的结果。
#include<stdio.h>
int main()
{
	int a = 3;
	int b = 5;
	int c = 0;
	int d = (c = 5, a = c + 3, b = a - 4, c += 5);  //a= 8, b=4, c=10
	printf("d=%d\n", d);
	return 0;
}          //输出结果为:d=10

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

10.1 [] 下标引用操作符

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

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

 10.2 () 函数调用操作符

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

 // ()函数调用操作符
test();
test(a);
test(a, b);
//test为第一个操作数,a,b为传递给函数的参数

10.3 访问一个结构的成员——自己创建一个新的结构体类型  .     ->       

  • .  -  结构体变量名.成员名
  • ->  -  结构体指针名->成员名
 //访问一个结构的成员
#include<stdio.h>
struct Stu
{
	char name[20];
	int age;
	char sex[20];
	double score;
};

int main()
{
	struct Stu b = { "张三", 19,"男",94.5 };
	struct Stu * pb = &b;
	printf("姓名:%s\n", b.name);
	printf("姓名:%s\n", pb->name);
	printf("性别:%s\n", b. sex);
	printf("性别:%s\n", pb->sex);
	return 0;
}              /*输出结果为:姓名:张三
                            姓名:张三
                            性别:男
                            性别:男*/

二、表达式求值

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

1. 隐式类型转换

1.1 整形提升

  • 整形提升:表达式中的字符或短整型在使用之前被转换为普通整形,这种转换称为整形提升。
  • 通常CPU是难以直接实现两个8比特字节直接运算的,所以,表达式中各种长度可能小于 int 长度的整型值,都必须转化为 int 或 unsigned int,然后才能送入CPU去执行运算。
  • 针对自身大小达不到一个整形的类型 —— char   short
  • 整形提升的步骤及演示代码:
  1. 写出整形操作数对应的原码;
  2. 看原本类型大小进行截断;
  3. 根据提升对象进行整形提升补二进制位;—— 补的是变量数据类型的符号位:负数补1,正数补0,无符号补0(%u打印无符号整形)。
  4. 提升后进行运算,再根据运算值所赋给的类型判断是否需要截断;
  5. 截断后若所需打印的类型仍需提升,则同以上规则补充符号位,提升后的二进制序列为补码,根据补码写出原码,即为运算的结果值。
 //整形提升
#include<stdio.h>
int main()
{
	char a = 3;          //00000000000000000000000000000011
	                     //因为是char类型,截断后:00000011 - a

	char b = 127;        //00000000000000000000000001111111
	                     //因为是char类型,截断后:01111111 - b

	char c = a + b;      //此时发现 a 和 b 都是char类型的,没有达到一个int的大小,这里就会发生整形提升
	                     //a提升后 - 00000000000000000000000000000011
	                     //b提升后 - 00000000000000000000000001111111
	                     //  a+b   - 00000000000000000000000010000010
	                     //c为char类型的,截断后:10000010 - C

	printf("%d\n", c);   //%d是打印整形的,此时需对c的值进行整形提升
	                     //c提升后 - 11111111111111111111111110000010    补码
	                     //         11111111111111111111111110000001    反码
	                     //         10000000000000000000000001111110    原码   对应值为-126
	return 0;
}          //输出结果为:-126

   例:判断程序输出的结果:

 //代码1
#include<stdio.h>
int main()
{
	char a = 0xb6;       //0xb6对应十进制为182;对应二进制为 00000000000000000000000010010110
	                     //截断后 - 10010110 - a
	                     //整形提升后 11111111111111111111111110010110   补码
	                     //           11111111111111111111111110010101   反码
	                     //           10000000000000000000000001101010   原码      对应十进制数为 -106
	short b = 0xb600;    //同a
	int c = 0xb6000000;  //无需进行整形提升
	if (a == 0xb6)
		printf("a");
	if (b == 0xb600)
		printf("b");
	if (c==0xb6000000)
		printf("c");
	return 0;
}          //输出结果为:c
//因为a和b为char类型,需进行整形提升,整形提升后变成了负数,与原值不相等



 //代码2
#include<stdio.h>
int main()
{
	char c = 1;
	printf("%u\n", sizeof(c));
	printf("%u\n", sizeof(+c));     //运算需要进行整形提升
	printf("%u\n", sizeof(-c));     //运算需要进行整形提升

	return 0;
}             /*输出结果为:1
			               4
						   4*/

1.2 算数转换

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

   下面的层次体系称为寻常算数转换。  —— 由下向上转换

  • long double
  • double
  • float
  • unsigned long int
  • long int
  • unsigned int
  • int

1.3 操作符的属性

  • 如果我们写出的表达式不能通过操作符的属性确定唯一的计算路径,那么这个表达式就是存在问题的。
  • 复杂表达式的求值有三个影响的因素:
  1. 操作符的优先级               c=a+b*6
  2. 操作符的结合性               c=a+(b+d)
  3. 是否控制求值顺序
  • 操作符的优先级如下:

类别

操作符

名称及作用

用法示例

结合方向

是否控制

求值顺序

()

聚组

(表达式)

N/A

后缀操作符

()

函数调用

(表达式)/函数名(形参)

左到右

[]

下标引用

数组名[常量表达式]

.

访问结构成员

结构名.成员名

->

访问指针结构成员

指针结构名->成员名

++

后缀自增

变量名++

--

后缀自减

变量名--

单目操作符

!

逻辑反

!表达式

右到左

~

按位取反

~表达式

+

表示正值

+表达式

-

表示负值

-表达式

++

前缀自增

++变量名

--

前缀自减

--变量名

*

间接访问

*指针变量

&

取地址

&变量名

sizeof

计算长度,单位字节

sizeof(表达式)

(类型)类型转换(类型)表达式

算数操作符

*

乘法

表达式*表达式

左到右

/

除法

表达式/表达式

%

余数(取模)

整型表达式%整型表达式

+

加法

表达式+表达式

-

减法

表达式-表达式

移位操作符

<< 

左移

变量<<表达式

左到右

>> 

右移

变量>>表达式

关系操作符

大于

表达式>表达式

左到右

>=

大于等于

表达式>=表达式

小于

表达式<表达式

<=

小于等于

表达式<=表达式

==

等于

表达式==表达式

!=

不等于

表达式!= 表达式

位操作符

&

按位与

表达式&表达式

左到右

^

按位异或

表达式^表达式

|

按位或

表达式|表达式


逻辑操作符

&&

逻辑与

表达式&&表达式

左到右

||

逻辑或

表达式||表达式

条件操作符

? :

条件运算符

表达式1?

表达式2: 表达式3

N/A

赋值操作符

=

赋值运算符

变量=表达式

右到左

+=

加后赋值

变量+=表达式

-=

减后赋值

变量-=表达式

*=

乘后赋值

变量*=表达式

/=

除后赋值

变量/=表达式

%=

取模后赋值

变量%=表达式

/=

除后赋值

变量/=表达式

*=

乘后赋值

变量*=表达式

%=

取模后赋值

变量%=表达式

<<=

左移后赋值

变量<<=表达式

>>=

右移后赋值

变量>>=表达式

&=

按位与后赋值

变量&=表达式

^=

按位异或后赋值

变量^=表达式

|=

按位或后赋值

变量|=表达式

逗号操作符

逗号运算符

表达式,表达式,…

左到右

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值