C语言第五课,操作符详解

基本的操作符:算术操作符、赋值操作符、移位操作符、位操作符、单目操作符、关系操作符、逻辑操作符、条件操作符、逗号表达式、下标引用。
注意:移位操作符、位操作符在嵌入式开发中的妙用。
注意:单目操作符sizeof()
下标引用操作符[ ]和->操作符的密切关系
学会函数调用和结构成员。
整型提升。
注意:操作符的优先级确定,并不能求出唯一的答案。以后不要写出很挫的代码。

算术操作符

+-*/%
  1. % 操作符两端只能是整数。其他的操作符两端可以是整数也可以是浮点数。
  2. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。

移位操作符

移位操作符操作
<<左移,左边抛弃、右边补0

| 右移,有逻辑移位和算数移位

左移操作符:
把a在内存中存储的二进制位向左移动两位,后面补上0。
比如这个a的存储:00000000 00000000 00000000 00000101
向左移动两位之后:00000000 00000000 00000000 00010100,
读出来就是20。

int main()
{
	int a = 5;
	int b = a << 2;//相当于扩大了四倍
	printf("%d\n", b);
	printf("%d\n", a);//并没有让a的值发生变化

	return 0;
}

右移操作符:
移位规则:
首先右移运算分两种:

  1. 逻辑移位
    左边用0填充,右边丢弃。
int main()
{
	int a = 5;//00000000 00000000 00000000 00000101
	int b = a >> 1;//00000000 00000000 00000000 00000010 前面补上0
	printf("%d\n", b);//2

	return 0;
}
  1. 算术移位
    左边用原该值的符号位填充,右边丢弃。
int main()
{
	//10000000 00000000 00000000 00000101原
	//11111111 11111111 11111111 11111010反
	//11111111 11111111 11111111 11111011补
	int a = -5;//11111111 11111111 11111111 11111011
	int b = a >> 1;//111111111 11111111 11111111 1111101补码
	printf("%d\n", b);//-3
	//111111111 11111111 11111111 1111101补码
	//111111111 11111111 11111111 1111100反码
	//100000000 00000000 00000000 0000011原码,表示-3
	return 0;
}

警告,不要写那种标准未定义的形式比如向右移动-2位!
警告

位操作符

" &" 按位与,同时为1才为1
" | " 按位或,只要有一个为1,就为1
" ^ " 按位异或,相同为0,相异为1
注意:他们的操作数必须是整数。

都是根据在内存中的二进制补码进行运算的。

int main()
{
	int a = 3;     // 00000000 00000000 00000000 00000011
	int b = -5;     //11111111 11111111 11111111 11111011
	
	int c = a & b;  //00000000 00000000 00000000 00000011
	printf("%d\n", c);//3

	c = a | b;      //11111111 11111111 11111111 11111011
	printf("%d\n", c);//-5

	c = a ^ b;      //11111111 11111111 11111111 11111000
	printf("%d\n", c);//-8

	return 0;
}

通过两次异或操作,实现两个数的交换。(面试题中可能会考到)
优点在于:不创建临时变量
缺点在于:使用异或操作,可读性差,而且只适用于整型,不会写出这样的代码。

int main()
{
	int a = 3;
	int b = 5;
	printf("交换前:%d %d\n", a, b);

	//a = a + b;
	//b = a - b;
	//a = a - b;//要是超过了他的最大值,就会出错!
	
	//注意特点:3^3=0,0^5=5,
	a = a ^ b;
	b = a ^ b;
	**加粗样式**a = a ^ b;

	printf("交换后:%d %d\n", a, b);

}

再比方说,在嵌入式开发工作中,开发板的引脚都是使用二进制控制的,如果把第三位的0改为1,就可以使用按位或的方法,这些类似的方法是需要牢固地掌握的。
代码如下:

int main()
{
	int a = 10;
	//0000 0000 0000 0000 0000 0000 0000 1010
	a |= (1<<2);
	printf("%d\n", a);
	return 0;
}

赋值操作符

=

左值,可以放在等号左边的,一般是一块空间。
右值,可以放在等号右边的,一般是一个值,或者一块空间的内容。

+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=

比方说x+=10操作,可以看成x=x+10,这样可读性更好。

int x = 10;
x = x+10;
x += 10;//复合赋值

单目操作符

! 逻辑反操作 。非零即真。

int main()
{
	int flag = 0;
	if (!flag)//!把真变为假,假的变为真。
	{
		printf("hehe\n");
	}
	return 0;
}

- 取负值
+ 正值

int main()
{
	int i = 0;
	int flag = 1;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", i*flag);//0 -1 2 -3 4 -5 6 -7 8 -9
		flag = -flag;
	}
	return 0;
}

对整数取绝对值:abs
对小数取绝对值:fabs

& 取地址

int main()
{
	int a = 10;
	int* p = &a;
	printf("%p", p);
	return 0;
}

sizeof 操作数的类型长度(以字节为单位)
这里再次提醒一下:
sizeof是操作符,
strlen是函数

int main()
{
	int a = 0;
	int arr[10] = { 1,2,3,4,5,6 };
	printf("%d\n", sizeof(arr));//40字节
	printf("%d\n", sizeof(int[10]));//40,这里给到的是arr的类型

	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof (int));//这里给的是a的类型
	return 0;
}

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

int main()
{
	int a = 0;
	//0000 0000 0000 0000 0000 0000 0000 0000
	printf("%d\n", ~a);//-1
	return 0;
}

前置、后置–
++ 前置、后置++
前置++,先自加,再使用。
后置++,先使用,再自加。

int main()
{
    int a = 10;
    int x = a++;
    //先对a先使用,再增加,这样x的值是10;之后a变成11;
    printf("%d %d\n", x,a);

    int y = --a;
    //先对a进行自减操作,然后把a的值赋给y,结果是y和a都为10
    printf("%d %d", y,a);
    return 0;
}

星号 * 间接访问操作符(解引用操作符)(和指针一起使用的)
大致过程是这样的:首先定义一个变量a,这个变量有一个地址(假设是0x0012ff40),还有一个初始值10。&a就相当于获取了地址0x0012ff40,赋值给指针变量p。使用*对p进行解引用操作,可以修改指针指向的变量。

打个比方:变量a是宝藏的位置,变量的值是宝藏的内容,p就是这个宝藏的藏宝图,有了这个图,你可以解引用操作,找到宝藏然后把它拿走(更改值)!

int main()
{
	int a = 10;
	int* p = &a;
	*p=20;//解引用操作
	printf("%d", a);//通过地址解引用,来操作a
	return 0;
}

即使是const类型修饰过的变量,也可以使用解引用的方式修改变量的值。这不禁让我想到了现实生活中,自行车上锁确实可以防止被偷,但是任然有办法偷,“防君子,防不了小人”。

(类型) 强制类型转换
比如,我要给int类型的变量赋值3.14(double型),这显然是不行的,就需要强制类型转换:

int main()
{
	int a = (int)3.14;
	printf("%d\n", a);//3
	return 0;
}

关系操作符

>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
这些关系运算符比较简单,没什么可讲的,但是我们要注意一些运算符使用时候的陷阱。

警告:在编程的过程中== 和=不小心写错,导致的错误。

逻辑操作符

&& 逻辑与
|| 逻辑或

区分逻辑与和按位与
区分逻辑或和按位或
逻辑与和按位与的区别
来一道笔试题:

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);
    return 0;
}

结果为1 2 3 4 。
a++,先使用再++,a为0,已经为假了,后面就不用算了,也就是说++b、d++根本没有计算。

那再来一道:

int main()
{
    int i = 0, a = 1, b = 2, c = 3, d = 4;
    i = a++ && ++b && d++;
    //i = a++||++b||d++;
    printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
    return 0;
}

结果为:2 3 3 5

如果是逻辑或呢?左操作数为真,右边不计算

int main()
{
    int i = 0, a = 1, b = 2, c = 3, d = 4;
    i = a++||++b||d++;
    //a++的时候就为真了,后面的++b、d++没有算
    printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);//结果:2 2 3 4
    return 0;
}

条件操作符

exp1 ? exp2 : exp3
表达式1为真,执行表达式2的内容;为假,执行表达式3的内容。

int main()
{
	int a = 2,b=0;
	if (a > 5)
		b = 3;
	else
		b = -3;
	printf("%d\n", b);
	return 0;
}

这样的代码,如果使用条件操作符就像这样表示:

int main()
{
	int a = 2, b = 0;
	b=a>5 ? 3:-3;
	printf("%d\n", b);
	return 0;
}

逗号表达式

exp1, exp2, exp3, …expN

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

比如这个,a > b关系运算,a = b + 10得到a=12,b = a + 1得到b=13,然后赋值给c。一定要计算每一步,不能直接计算最后一步!

int main()
{
	int a = 1;
	int b = 2;
	int c = (a > b, a = b + 10, a, b = a + 1);
	printf("%d\n", c);
	return 0;
}

练习题:
求结果,答案已给出。

int main()
{
	int a, b, c;
	a = 5;
	c = ++a;// ++a:加给a+1,结果为6,用加完之后的结果给c赋值,因此:a = 6  c = 6
	b = ++c, c++, ++a, a++;
	// 逗号表达式的优先级,最低,这里先算b=++c, b得到的是++c后的结果,b是7
	// b=++c 和后边的构成逗号表达式,依次从左向右计算的。
	// 表达式结束时,c++和,++a,a++会给a+2,给c加1,此时c:8,a:8,b:7
	b += a++ + c; // a先和c加,结果为16,在加上b的值7,比的结果为23,最后给a加1,a的值为9
	printf("a = %d b = %d c = %d\n:", a, b, c); // a:9, b:23, c:8
	return 0;
}

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

[ ] 下标引用操作符

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

int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10 };
	printf("%d\n", arr[8]);//打印了第八个元素 ,arr和8是操作数
	return 0;
}

要知道,数组传参的时候,其实传的是首元素的地址。同样这里的arr其实可以看做首元素的地址,索引值(8)就相当于指针从起始地点往后走了8个单位。那么arr[8]可以看成*(arr+8),也就是arr+8再解引用。

然而离谱的是,加法满足交换律*(arr+8)——>*(8+arr)——>8[arr],即使是这样子写,程序也可以通过,但是理解起来比较 离谱!!!

int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10 };
	//arr[8]——>*(arr+8)——>*(8+arr)——>8[arr]
	printf("%d\n", 8[arr]);
	return 0;
}

函数调用操作符 ()

void test()
{
	printf("hehe\n");
}
int main()
{
	test();//()这个就是函数调用操作符
	return 0;
}

结构的成员访问操作符

. 结构体.成员名

//当前的结构体对象
struct Stu
{
    char name[10];
    int age;
    double score;
};
int main()
{
    struct Stu s = { "zhangsan",20,85.8 };
    //用 . 访问
    printf("%s %d %.1lf\n", s.name, s.age, s.score);//结构体变量.结构体成员
    return 0;
}

-> 结构体指针->成员名
用&s取结构体变量s的地址,存放在指针变量p中,这个p是什么类型的指针呢?它指向的是什么变量,就应该是什么类型的指针!!!————结构体指针
所以用struct Stu* 定义结构体类型的指针p。

struct Stu
{
    char name[10];
    int age;
    double score;
};
int main()
{
    struct Stu s = { "zhangsan",20,85.8 };
    //用->访问
    struct Stu* p = &s;
    printf("%s %d %.1lf\n", p->name, p->age, p->score);//结构体指针->结构体成员
    return 0;
}

关于表达式求值等问题

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

隐形类型转换

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

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

整形提升是按照变量的数据类型的符号位来提升的
正数和无符号数高位补0,负数高位补0。
以下是计算机处理的过程:

int main()
{
	char a = 5;//0000 0101 存储的低八位
	char b = 126;//0111 1110 存储的低八位

	char c = a + b;
	//当a和b相加的时候,a b都是char类型,需要整型提升,因为都是正数,所以高位补0
	//a 0000 0000 0000 0000 0000 0000 0000 0101
	//b 0000 0000 0000 0000 0000 0000 0111 1110
	//c 0000 0000 0000 0000 0000 0000 1000 0011(补码)
	//c 1000 0011 截断操作后
	printf("%d\n", c);//-125
	//打印的是%d类型,先整型提升
	//c 1111 1111 1111 1111 1111 1111 1000 0011(补码)
	//c 1111 1111 1111 1111 1111 1111 1000 0010(反码)
	//c 1000 0000 0000 0000 0000 0000 0111 1101(原码)——>-125
	return 0;
}

整型提升的例子一:
a,b要进行整形提升,但是c不需要整形提升
a,b整形提升之后,变成了负数,所以表达式 a = = 0xb6 , b = =0xb600 的结果是假,但是c不发生整形提升,则表达式 c==0xb6000000 的结果是真。最后的结果为:c

int main()
{
    char a = 0xb6;//1011 0110
    //提升为1111111 11111111 11111111 1011 0110
    short b = 0xb600;//1011 0110 0000 0000
    //提升为1111111 11111111 1011 0110 00000000
    int c = 0xb6000000;
    if (a == 0xb6)
        printf("a");
    if (b == 0xb600)
        printf("b");
    if (c == 0xb6000000)
        printf("c");
    return 0;
}

整型提升的例子二:
c只要参与表达式运算,就会发生整形提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节.
表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof( c ) ,就是1个字节.

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

算术转化

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类
型,否则操作就无法进行。下面的层次体系称为寻常算术转换
算术转化类型的排名
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。

操作符的属性

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

  1. 操作符的优先级
  2. 操作符的结合性
  3. 是否控制求值顺序。
    两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。

即使是有了结核性和优先级,也不能保证代码有唯一解。
case1:ab + cd + e*f ,可能计算出不同的值,除非使用大括号让代码有唯一的计算路径。

case2:a + --a;虽然有优先级顺序,但是左右操作数是有关系的,不知道这个操作数的取值时机是什么,所以也会有两种不同等答案。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值