C语言的——操作符详解——(第18篇)

坚持就是胜利

文章目录


一、各种操作符的介绍

1.算数操作符

+ - * / %
%操作符的两个操作数必须是整数,返回的是整除之后的余数

2.移位操作符

<< 左移操作符
>> 右移操作符

(1)左移操作符 移位规则

左边抛弃,右边补0

#include <stdio.h>

int main()
{
	//00000000 00000000 00000000 00001001   9
	int num = 9;
	int b = num << 1;
	//00000000 00000000 00000000 00010010   18
	printf("%d\n", b);
	return 0;
}

(2)右移操作符 移位规则

1.逻辑位移:左边用 0 填充,右边丢弃;
2.算数移位:左边用原该值的符号位填充,右边丢弃。(0:整数 ---------- 1:负数)
通常使用 “算数移位”
因为C语言没有明确规定使用-算数位移-还是-逻辑位移-,但是一般编译器使用-算术位移-。
警告:对于移位运算符,不要移动负数位,这个是标准未定义的。

int num = 10;
num >> -1;  //错误!

正数:原码,反码,补码相同
负数:原码---反码:符号位不变,其余按位取反---补码等于反码+1
整数在内存中存储的是:补码
计算的时候也是使用---补码---计算

//原码:10000000 00000000 00000000 00000111  -7
//反码:11111111 11111111 11111111 11111000  符号位不变,其余按位取反
//补码:11111111 11111111 11111111 11111001  反码 + 1

//右移 1 位  对 补码 进行操作

//补码:11111111 11111111 11111111 11111100  
//反码:11111111 11111111 11111111 11111011  反码 = 补码 - 1
//原码:10000000 00000000 00000000 00000100  原码:符号位不变,其余取反  -4

#include <stdio.h>

int main()
{
	int num = -7;
	printf("移动前:num=%d\n", num);  //-7
	int b = num >> 1;
	printf("移动后:num=%d\n", b);  //-4
	return 0;
}

3.位操作符

& 按位与
| 按位或
^ 按位异或
注:它们的操作数必须是 整数

#include <stdio.h>

int main()
{
	int num1 = 1;
	int num2 = 2;
	int a = num1 & num2;  //0
	int b = num1 | num2;  //3
	int c = num1 ^ num2;  //3
	printf("%d--%d--%d\n", a, b, c); //0--3--3
	return 0;
}
//10000000 00000000 00000000 00000101   -5 原码
//11111111 11111111 11111111 11111010   -5 反码
//11111111 11111111 11111111 11111011   -5 补码 
// 
//00000000 00000000 00000000 00000011    3 原码、反码、补码相同

//00000000 00000000 00000000 00000011    -5 & 3  结果为 3 


#include <stdio.h>

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

题目1:一道变态的面试题:

题目:不能创建临时变量(第三个变量),实现两个数的交换。

方法一

缺点:a = a + b ; 容易超出整型所表示的最大值范围

#include <stdio.h>

int main()
{
	int a = 12;
	int b = 6;
	printf("交换之前:a=%d,b=%d\n", a, b);  //交换之前:a = 12, b = 6
	a = a + b;  //容易超出整型所表示的最大范围。
	b = a - b;
	a = a - b;
	printf("交换之前:a=%d,b=%d\n", a, b);  //交换之前:a = 6, b = 12
	return 0;
}
方法二:异或 支持 交换律

异或 ^ 支持 交换律
只需要满足:相同为 0 ,相异为 1;所以,数值是不会产生溢出的

//异或:相同为 0,相异为 1
//a^ a = 0;  
//a ^ 0 = a;

//a^b^a
//a^a^b

//举例:a: 011
//      b: 101
//    a^b: 110
//      a: 011
//  a^b^a: 101   得到:a^b^a = b;  得知:异或是支持 交换律 的

//    a^a: 000
//      b: 101
//  a^a^b: 101   得到:a^a^b = b; 

#include <stdio.h>

int main()
{
	int a = 3;
	int b = 5;
	printf("交换前:a=%d,b=%d\n", a, b);
	a = a ^ b;  //
	b = a ^ b;  //b=a^b^b;  得到:b=a;  异或 支持 交换律
	a = a ^ b;  //a=a^b^a;  得到:a=b;  异或 支持 交换律
	printf("交换后:a=%d,b=%d\n", a, b);
	return 0;
}

题目2:将 00000000 00000000 00000000 00001101 的倒数第 5 位变成 1

//题目:将 00000000 00000000 00000000 00001101 的倒数第 5 位变成 1 
// 
//a: 00000000 00000000 00000000 000 0 1101
//b: 00000000 00000000 00000000 000 1 0000  按位 或
//c: 00000000 00000000 00000000 000 1 1101  结果:1+4+8+16=29
//
#include <stdio.h>

int main()
{
	int a = 13;
	int b = 1 << 4;
	int c = a | b;
	printf("%d\n", c);
	return 0;
}

题目3:将 00000000 00000000 00000000 000 1 1101的倒数第 5 位 置为 0

//题目:将 00000000 00000000 00000000 000 1 1101的倒数第 5 位 置为 0

//a: 00000000 00000000 00000000 000 1 1101  //1+4+8+16=29
//b: 11111111 11111111 11111111 111 0 1111  
//c: 00000000 00000000 00000000 000 0 1101 按位 与&  //1+4+8=13

//如何得到 b: 11111111 11111111 11111111 111 0 1111 
//将 00000000 00000000 00000000 000 1 0000 按 位 取反
//首先将 00000000 00000000 00000000 00000001 左移 4 位,然后对每一位 取反 : ~(1 << 4)

#include <stdio.h>

int main()
{
	int a = 29;
	int b = ~(1 << 4);
	int c = a & b;
	printf("%d\n", c);
	return 0;
}

题目4:求一个整数存储在内存中的二进制中 1 的个数

缺点:进行了大量的取模以及除法运算,取模和除法的运算效率本来就很低

方法1:最简单的方法
#include <stdio.h>

int main()
{
	int a = 71;
	int count = 0;
	while (a)
	{
		if (a % 2)
		{
			count++;
		}
		a /= 2;
	}
	printf("二进制中 1 的个数是:%d", count);
	return 0;
}

简化代码:

#include <stdio.h>

int count_one_bit(int n)
{
	int count = 0;
	while (n)
	{
		if (n % 2 == 1)
		{
			count++;
		}
		n /= 2;
	}
	return count;
}

int main()
{
	int a = 255;
	count_one_bit(a);
	printf("%d\n", count_one_bit(a));
	return 0;
}
方法2:自己根据老师的提示,写出的代码

思考方法:
1.整数有32个bit位
2.获得32个bit位的每一位 //数字 1 往前按位 移动 31 次
3.判断这一位是否为 1 //按位 与
4.是 1 就是 计数器加 1

第一次尝试:有错误,while( sz - 1 ) 中的判断条件是错误的

在这里插入图片描述
这是错误的代码,有问题!

#include <stdio.h>
#define TYPE int 
#define A 4294967295

int main()
{
	int sz = (sizeof(TYPE)) * 8;
	int count = 0;
	int weishu = 0;
	while (sz - 1)    // 存在错误
	{
		int a = A;
		int b = (1 << weishu);
		int c = a & b;
		if (c == b)
		{
			count++;
		}
		weishu++;
		sz--;
	}
	printf("%d\n", count);
	return 0;
}
改正错误,运行正确
//思考方法:
//1.整数有32个bit位
//2.获得32个bit位的每一位 //数字 1 往前按位 移动 31 次
//3.判断这一位是否为 1  //按位 与
//4.是 1 就是 计数器加 1

//00000000 00000000 00000000 00011111
//00000000 00000000 00000000 00000001  按位 与&
//00000000 00000000 00000000 00000001

//00000000 00000000 00000000 00011111
//00000000 00000000 00000000 00000010  按位 与&
//00000000 00000000 00000000 00000010

#include <stdio.h>
#define TYPE int 
#define A 4294967295

int main()
{
	int sz = (sizeof(TYPE))*8;
	int count = 0;
	int weishu = 0;
	while (sz)  //必须得是 sz ,比如:int,当刚好移动到第32位时,也就是移动了第31次,如果判断条件是:sz - 1,那么就 while( 0 ),就不会进行判断 
	{
		int a = A;
		int b = (1 << weishu);
		int c = a & b;
		if (c == b)
		{
			count++;
		}
		weishu++;
		sz--;
	}
	printf("%d\n", count);
	return 0;
}
方法3:按位向右移动

缺点:无论是什么数据,都要移动 32 次

//-15
//原码:10000000 00000000 00000000 00001111
//反码:11111111 11111111 11111111 11110000
//补码:11111111 11111111 11111111 11110001   //共有 29 个数字 1 

//每次 a 向右移动 1 位,再和 数字1 进行 与操作
//11111111 11111111 11111111 11110001
//00000000 00000000 00000000 00000001

#include <stdio.h>

int count_one_bit(int n)  
{
	int count = 0;
	int i = 0;
	for (i = 0; i < 32; i++)
	{
		if (((n >> i) & 1) == 1)  //向右移 1 位
		{
			count++;
		}
	}
	return count;
}

int main()
{
	int a = -15;
	count_one_bit(a);
	printf("%d\n", count_one_bit(a));  //结果:29
	return 0;
}

方法4:采用“相邻的两个数据”进行 按位 与 运算

此种方式:数据的二进制比特位中有几个 1 ,循环就进行几次,而且中间采用了 位运算,处理起来比较高效。

//相邻的两个数据进行按位 与& 运算
//此种方式:数据的二进制比特位中有几个 1 ,循环就进行几次,而且中间采用了 位运算,处理起来比较高效。

//第一次
//10 0111 0000 1111   9999    
//10 0111 0000 1110   9998
//10 0111 0000 1110   &   9998

//第二次
//10 0111 0000 1110   9998
//10 0111 0000 1101   9997
//10 0111 0000 1100   &   9996

//第三次
//10 0111 0000 1100   9996
//10 0111 0000 1011   9995
//10 0111 0000 1000   &   9992

//第四次
//10 0111 0000 1000   9992
//10 0111 0000 0111   9991
//10 0111 0000 0000   &   9984

//第五次
//10 0111 0000 0000   9984
//10 0110 1111 1111   9983
//10 0110 0000 0000   &   9728

//第六次
//10 0110 0000 0000   9728
//10 0101 1111 1111   9727
//10 0100 0000 0000   &   9216

//第七次
//10 0100 0000 0000   9216
//10 0011 1111 1111   9215
//10 0000 0000 0000   &   8192

//第八次
//10 0000 0000 0000   8192
//01 1111 1111 1111   8191
//00 0000 0000 0000   &   0

#include <stdio.h>

int count_one_bit(int n)
{
	int count = 0;
	while (n)
	{
		n = n & (n - 1);
		count++;
	}
	return count;
}

int main()
{
	int a = 9999;
	count_one_bit(a);
	printf("%d\n", count_one_bit(a));
	return 0;
}

4.赋值操作符

赋值运算符可以 连续赋值,但是不推荐!

#include <stdio.h>

int main()
{
	int a = 9;
	int x = 0;
	int y = 99;
	a = x = y;  //这种赋值方式,很不推荐!!!
	printf("a=%d,x=%d,y=%d\n", a, x, y);
	return 0;
}


#include <stdio.h>

int main()
{
	short s = 5;
	int a = 10;
	printf("%d\n", sizeof(s = a + 2));  //结果:2   //sizeof 括号中的表达式是不参与运算的!
	printf("%d\n", s);   //结果:5
	return 0;
}

//s = a + 2 不会发生“整型提升”
//因为 s = a + 2 并没有运算

//赋值表达式 s = a + 2 是在“编译阶段”处理的

//test.c  ------------------------- test.exe
//源文件  ------------------------> 可执行程序
//编译器  ---------> 链接 --------> 运行

//sizeof(s=a+2) 在编译时,就已经处理完了

//printf("%d\n",s); 在运行阶段才处理
//s = a + 2 在编译阶段就处理完了
//printf("%d\n",sizeof(s=a+2)); 在编译阶段被处理成 printf("%d\n",2); 进入运行阶段的
//所以表达式 s = a + 2 是不会被运算的。

符合赋值符号:*=,/=,+=,-=,%=,>>=,<<=,&=,|=,^=

5.单目操作符

!      逻辑反操作
-       负值
+       正值
&       取地址
sizeof  操作数的类型长度(以 字节 为单位)
~       对一个数的二进制按位取反(对内存中存储的“补码”按位取反)
--      前置、后置--
++      前置、后置++
*       间接访问操作符(解引用操作符)
(类型)  强制类型转换
//原码:10000000 00000000 00000000 00000001
//反码:11111111 11111111 11111111 11111110
//补码:11111111 11111111 11111111 11111111  这是 -1 的补码
//取反:00000000 00000000 00000000 00000000  0

#include <stdio.h>

int main()
{
	int a = -1;
	int b = ~a;
	printf("%d\n", b);  //结果:0
	return 0;
}
#include <stdio.h>

int main()
{
	int a = -10;
	int* p = NULL;
	printf("%d\n", !2);  //逻辑反操作 结果:0
	printf("%d\n", !0);  //结果:1
	a = -a;
	p = &a;
	printf("%d\n", sizeof(a));  //结果:4
	printf("%d\n", sizeof(int));  //结果:4
	printf("%d\n", sizeof a);  //结果:4  这样写,正好说明了,sizeof 是操作符,不是函数
	//printf("%d\n", sizeof int);  //sizeof int 这样写是错误的,无法编译!
	return 0;
}

(1)sizeof 和 数组

printf(“%d\n”, sizeof(arr)); //结果:40

printf(“%d\n”, sizeof(int[10])); //结果:40
原因:int [10] 是 arr数组的类型

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };

	printf("%d\n", sizeof(arr));  //结果:40

	printf("%d\n", sizeof(int[10]));  //结果:40
                                      //原因:int [10] 是 arr数组的类型
	return 0;
}
#include <stdio.h>

void test1(int arr[])
{
	printf("%d\n", sizeof(arr));  //传参 传的是 地址:32 位平台是 4 /64 位平台是 8
}

void test2(char ch[])
{
	printf("%d\n", sizeof(ch));  //传参 传的是 地址:32 位平台是 4 /64 位平台是 8
}

int main()
{
	int arr[10] = { 0 };
	char ch[10] = { 0 };
	printf("%d\n", sizeof(arr));  //40
	printf("%d\n", sizeof(ch));   //10
	test1(arr);
	test2(ch);
	return 0;
}
#include <stdio.h>

int main()
{
	short s = 5;
	int a = 10;
	printf("%d\n", sizeof(s = a + 2));  //结果:2   //sizeof 括号中的表达式是不参与运算的!
	printf("%d\n", s);   //结果:5
	return 0;
}

//s = a + 2 不会发生“整型提升”
//因为 s = a + 2 并没有运算

//赋值表达式 s = a + 2 是在“编译阶段”处理的

//test.c  ------------------------- test.exe
//源文件  ------------------------> 可执行程序
//编译器  ---------> 链接 --------> 运行

//sizeof(s=a+2) 在编译时,就已经处理完了

//printf("%d\n",s); 在运行阶段才处理
//s = a + 2 在编译阶段就处理完了
//printf("%d\n",sizeof(s=a+2)); 在编译阶段被处理成 printf("%d\n",2); 进入运行阶段的
//所以表达式 s = a + 2 是不会被运算的。

(2)++ 和 – 运算符:无意义,没有必要深究!!!

#include <stdio.h>

int main()
{
	int a = 10;
	int x = ++a;  //11
	int y = --a;  //10  原因:从 11 减去 1 变成 0
	printf("%d\n%d\n%d\n", a, x, y);
	return 0;
}
#include <stdio.h>

int main()
{
	int a = 10;
	int x = a++;  //结果:10。首先将10赋值给x,然后a加1变成11
	int y = a--;  //结果:11。首先将11赋值给y,然后a减1变成10
	printf("%d\n%d\n%d\n", a, x, y);
}

这种就是垃圾题目,不做也罢,无意义!!!
垃圾题目,在VS上和GCC上得到的结果不一样!!!

//这种就是垃圾题目,不做也罢,无意义!!!

#include <stdio.h>

int main()
{
	int a = 1;
	int b = (++a) + (++a) + (++a);  //垃圾题目,在VS上和GCC上得到的结果不一样!!!
	printf("%d\n", b);
	return 0;
}

6.关系操作符

> 
>=
< 
<=
!=
==

7.逻辑操作符

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

&&
||

(1)短路运算

#include <stdio.h>

int main()
{
	int a = 0;
	int b = 1;
	int c = a++ && b++;  //短路运算,由于此时a++的结果是 0 ,所以 && 后面的表达式不再进行运算,直接跳过,这就是“短路运算”
	printf("a=%d\nb=%d\nc=%d\n", a, b, c);  // a = 1 ,b = 1 , c = 0
	return 0;
}
360笔试题
#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++ 的结果是 0 ,所以 && 后面的表达式不再进行计算,这就是“短路运算”
	printf("a=%d\nb=%d\nc=%d\nd=%d\n", a, b, c, d);  // a = 1 , b = 2 , c = 3 , d = 4
	return 0;
}

8.条件操作符

exp1 ? exp2 : exp3
int max = (a > 5 ? 1 : -1);  // 不可以写成:a > b ? max = a : max = b ; 这种写法是错误的!

9.逗号操作符

一定要从前往后计算,不能跳过任何一个表达式

exp1,exp2,exp3,...expN

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

代码1:

#include <stdio.h>

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

代码2:

#include <stdio.h>

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

代码3:

a = get_val();
count_val(a);
while (a > 0)
{
	//业务处理
	a = get_val();
	count_val(a);
}

如果使用 逗号表达式,改写:
while (a = get_val(), count_val(a), a > 0)
{
	//业务处理
}

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

1.[ ] 下标引用操作符

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

int arr[100];

2. ( )函数调用操作符

3.访问一个结构的成员

#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 的地址

	stu.age = 20;
	set_age1(stu);
	printf("age1=%d\n", stu.age);  //结果为:20  原因:当set_age1()函数运行结束的瞬间,就没有了!!!

	pStu->age = 20;
	set_age2(pStu);
	printf("age2=%d\n", pStu->age);  //结果为:18

	return 0;
}
第一种:jack.age
#include <stdio.h>

struct Stu {
	int age;
	char name[20];
	char sex[5];
};

int main()
{
	struct Stu jack = { 15,"jack","boy" };
	printf("age=%d\n", jack.age);
	printf("name=%s\n", jack.name);
	printf("sex=%s\n", jack.sex);

	return 0;
}
第二种:(*ja).age
#include <stdio.h>

struct Stu {
	int age;
	char name[20];
	char sex[5];
};

int main()
{
	struct Stu jack = { 15,"jack","boy" };
	
	struct Stu* ja = &jack;  //取结构体变量 jack 的地址
	
	printf("age=%d\n", c);
	printf("name=%s\n", (*ja).name);
	printf("sex=%s\n", (*ja).sex);

	return 0;
}
第三种:ja->age
#include <stdio.h>

struct Stu {
	int age;
	char name[20];
	char sex[5];
};

int main()
{
	struct Stu jack = { 15,"jack","boy" };
	
	struct Stu* ja = &jack;  //取结构体变量 jack 的地址
	
	printf("age=%d\n", c);
	printf("name=%s\n", ja->name);
	printf("sex=%s\n", ja->sex);

	return 0;
}

二、表达式求值

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

1.隐式类型转换:整型提升

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

整型提升的意义:

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是 int 的字节长度,
同时也是CPU的通用寄存器的长度。

因此,即使两个 char 类型的相加,在CPU执行时,实际上也要先转换为CPU内整型操作数的标准长度。

通用CPU是难以直接实现两个 8 比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。
所以,表达式中各种长度可能小于 int 长度的整型值,都必须先转换为 int 或者 unsigned int ,然后才能送入CPU去执行运算。

1.注意:长度 大于 int 的,就不需要“整型提升”!!!

负数的整型提升,高位补充“符号位”,即为 1
正数的整型提升,高位补充“符号位”,即为 0
无符号 整型提升,高位补 0

2.整型提升:是按照变量的数据类型的“符号位”来提升的。

char a, b, c;
a = b + c;

b 和 c 的值被提升为 普通整型,然后再执行加法运算。
加法运算完成后,结果被 截断,然后再存储在 a 中。  //这句话非常重要

如何完成 整型提升 呢?

整型提升 是按照变量的数据类型的 符号位 来提升的。

//负数的整型提升
//-1 的原码:10000001
//-1 的反码:11111110
//-1 的补码:11111111
char c1 = -1;
变量 c1 的二进制位(补码)中只有8个比特位:
11111111
因为 char 为有符号的 char
所以整型提升的时候,高位补充“符号位”,即为 1
提升之后的结果是:
11111111 11111111 11111111 11111111

//正数的整型提升
//+1 的原码、反码、补码相同 00000001
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位;
00000001
因为 char 为有符号的 char
所以整型提升的时候,c
提升之后的结果是:
00000000 00000000 00000000 00000001

//无符号 整型提升,高位补 0

3.是对“补码”进行“整型提升”!!!!


//错误的解答过程

//11111111  -127  a 
//11111111 11111111 11111111 11111111 整型提升 
 
//01111111   127  b
//00000000 00000000 00000000 01111111 整型提升

//   11111111 11111111 11111111 11111111  a
//   00000000 00000000 00000000 01111111  b
// 1 00000000 00000000 00000000 01111110  c=a+b  (溢出)
//   01111110 截断,保存到 c 中
//char c 是有符号的,符号位为 0

//由于是按照 %d 的形式输出
//整型提升   00000000 00000000 00000000 01111110  结果为:
//正数的原码,反码,补码相同

#include <stdio.h>

int main()
{
	char a = -127;
	char b = 127;
	char c = a + b;
	printf("%d\n", c);  //正确的结果为:0
	return 0;
}

//正确的解答过程:  由此知:是对“补码”进行“整型提升”

//11111111  -127  a  (原码)
//10000000 (反码)
//10000001 (补码)
//11111111 11111111 11111111 10000001 整型提升 ,由此可知:是对“补码”进行“整型提升”

//01111111   127  b  
//00000000 00000000 00000000 01111111 整型提升

//  11111111 11111111 11111111 10000001  a
//  00000000 00000000 00000000 01111111  b 
//1 00000000 00000000 00000000 00000000  c=a+b          
//截断,将 00000000 保存到 char c 中,由此知:是 0,是正数

//由于是按照 %d 的形式输出
//整型提升   00000000 00000000 00000000 00000000  
//正数的原码,反码,补码相同

//所以结果是:0

#include <stdio.h>

int main()
{
	char a = 3;
	char b = 127;
	char c = a + b;  //(a+b)的值先“整型提升后”,再被 截断,再存入到 c 中

	//以 %d\%u 打印的时候,再进行“整型提升”!!!

	printf("c=%d\n", c);  //%d:以10进制的形式打印 “有符号” 的整数 c   
	//正确答案:-126  
	//二进制原码(屏幕上显示原码):10000000 00000000 00000000 01111110
	//二进制补码(内存中存储):
	printf("c=%u\n", c);  //%d:以10进制的形式打印“无符号”的整数 c
	//正确答案:4294967170
	//二进制补码:11111111 11111111 11111111 10000010  //因为char是有符号的,以“符号位”来“整型提升”
	//正数的原码、反码、补码相同
	return 0;
}

//char a, b, c;
//a = b + c;



//步骤一:b 和 c 的值被提升为 普通整型,然后再执行加法运算。
//步骤二:加法运算完成后,结果被 截断,然后再存储在 a 中。  //这句话非常重要




//步骤一:

//3             00000011    正数,符号位为 0
//对a整型提升   00000000 00000000 00000000 00000011

//127           01111111    正数,符号位为 0
//对b整型提升   00000000 00000000 00000000 01111111

//00000000 00000000 00000000 00000011  a
//00000000 00000000 00000000 01111111  b
//00000000 00000000 00000000 10000010  c=a+b

//步骤一的注意点:
//我在得出c=a+b的结果为:10000010的时候,直接进行计算为:132,其实这是错误的,答案不是这个!!!

//10000010     c
//%d :%d:以10进制的形式打印 “有符号” 的整数 c 
//char 是有符号位的,在C语言中,char可以是有符号的,也可以是没有符号的,具体由编译器决定,大部分编译器都是有符号的




//步骤二:

//将被截断   则c中存放的是 10000010  ,可以看出,这是一个 负数(char 是“有符号”的)
//由于,char c 是有符号的,且 符号位 为 1,即为 负数

//c=a+b      10000010
//整型提升   11111111 11111111 11111111 10000010

//负数 在内存中以“补码”形式存储,但是 屏幕上显示的是“原码”。

//补码:11111111 11111111 11111111 10000010
//反码:11111111 11111111 11111111 10000001
//原码:10000000 00000000 00000000 01111110  

//屏幕上显示原码: -126

//10000001  a  -1(原码)
//11111110  (反码)
//11111111  (补码)
//整型提升:11111111 11111111 11111111 11111111

//00000001   b   1
//整型提升:00000000 00000000 00000000 00000001

//   11111111 11111111 11111111 11111111  a
//   00000000 00000000 00000000 00000001  b
// 1 00000000 00000000 00000000 00000000  c=a+b

//截断,将 00000000 保存到 c 中,可知 char c 是有符号的正数 0 

//以 %d 十进制的形式输出
//整型提升:00000000 00000000 00000000 00000000  (补码)正数的补码、反码、原码是一样的,所以输出是 0 
//屏幕上看到的是 原码
#include <stdio.h>

int main()
{
	char a = -1;
	char b = 1;
	char c = a + b;
	printf("%d\n", c);  //正确的结果为:0
	return 0;
}

4.“整型提升”后的值是“补码”,屏幕上看到的是“原码”,所以,还要将“补码”转换成“原码”,再求出结果


//10000011  a  -3(原码)
//11111100  (反码)
//11111101  (补码)
//整型提升:11111111 11111111 11111111 11111101

//00000001   b   1
//整型提升:00000000 00000000 00000000 00000001

//   11111111 11111111 11111111 11111101  a
//   00000000 00000000 00000000 00000001  b
//   11111111 11111111 11111111 11111110  c=a+b

//截断,将 11111110 保存到 c 中,可知 char c 是有符号的负数  

//以 %d 十进制的形式输出
//整型提升:11111111 11111111 11111111 11111110 (补码)
//          11111111 11111111 11111111 11111101 (反码)
//          10000000 00000000 00000000 00000010 (原码)  结果为:-2

#include <stdio.h>

int main()
{
	char a = -3;
	char b = 1;
	char c = a + b;
	printf("%d\n", c);  //正确的结果为:-2
	return 0;
}

5.字节长度 大于 int 的,就不需要“整型提升”

#include <stdio.h>

int main()
{
	char a = 0xb6;
	short b = 0xb600;
	int c = 0xb6000000;
	
	if (a == 0xb6)      //因为 a 是 char 类型,1 个字节,所以要进行“整型提升”,所以 if 的条件为 假
	{
		printf("a\n");  //无法输出
	}
	if (b == 0xb600)    //因为 b 是 short 类型,2 个字节,所以要进行“整型提升”,所以 if 的条件为 假
	{
		printf("b\n");  //无法输出
	}
	if (c == 0xb6000000) //因为 c 是 int 类型,4 个字节,所以不要进行“整型提升”,所以 if 的条件为 真
	{
		printf("c\n");  //输出结果:c
	}
	return 0;
}

//只要"参与表达式运算"就会发生"整型提升",表达式 +c,就会发生提升,所以 sizeof(+c) 是4个字节。
//表达式 -c 也会发生“整型提升”,所以,sizeof(-c) 是 4 个字节,
//但是 sizeof(c) ,就是 1 个字节。

#include <stdio.h>

int main()
{
	char c = 1;
	printf("%d\n", sizeof(c));   //结果:1
	printf("%u\n", sizeof(-c));  //结果:4
	printf("%u\n", sizeof(+c));  //结果:4
	return 0;
}

2.算术转换

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

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换成为另外一个操作数的类型后执行运算。

警告:但是算数转换要合理,要不然会有一些潜在的问题。

从下往上转换
long double
double
float
unsigned long int 
long int 
unsigned int
int 
#include <stdio.h>

int main()
{
	printf("%d\n", sizeof(long double));        //8
	printf("%d\n", sizeof(double));             //8
	printf("%d\n", sizeof(float));              //4
	printf("%d\n", sizeof(unsigned long int));  //4
	printf("%d\n", sizeof(long int));           //4
	printf("%d\n", sizeof(unsigned int));       //4
	printf("%d\n", sizeof(int));                //4
	return 0;
}
float f = 3.14;
int num = f;    //隐式转换,会有精度丢失

3.操作符的属性

复杂表达式的求值有 3 个影响的因素。
1.操作符的“优先级”
2.操作符的“结合性”
3.是否”控制求值顺序“

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

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

//表达式 1 :
a*b + c*d + e*f
注释:由于 *+ 的优先级高,只能保证 * 的计算比 + 早,但是优先级并不能决定第三个 * 比第一个 + 早执行。
所以表达式的计算机顺序可能就是:
a*b
c*d
a*b + c*d
e*f
a*b + c*d + e*f
或者:
a*b
c*d
e*f
a*b + c*d
a*b + c*d + e*f

//表达式 2 :
c + --c;
注释:同上,操作符的优先级只能决定 自减-- 的运算在 + 的运算的前面,但是我们并没有办法得知,+ 操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。 

//代码 3 :
int  main()
{
	int i=10;
	i=i-- - --i * (i = -3) * i++ + ++i;
	printf("i=%d\n",i);
	return 0;
}
表达式 3 在不同编译器中测试结果:非法表达式程序的结果

//代码4:
int fun()
{
	static int count =1;
	return ++count;
}
int main()
{
	int answer;
	answer = fun() - fun() * fun();  //2-3*4=-10   或者   4-2*3=-2
	printf("%d\n",answer);  
	return 0}
这个代码有问题!
虽然在大多数的编译器上求得结果都是相同的。
但是上述代码answer = fun() - fun() * fun();中我们只能通过操作符的优先级得知:先算乘法,再算减法。
函数的调用先后顺序无法通过操作符的优先级确定。

//代码5:
#include <stdio.h>

int main()
{
	int i = 1;
	int ret = (++i) + (++i) + (++i);
	printf("%d\n", ret);
	printf("%d\n", i);
	return 0;
}
//linux的结果:10   4    //3+3+4
//VS2013的结果:12   4   //4+4+4

//这段代码中的第一个 + 在执行的时候,第三个 ++ 是否执行,这个是不确定的,
//因为依靠操作符的优先级和结合性是无法决定第一个 + 和第三个前置 ++ 的先后顺序。


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

微软雅黑字体
黑体
3号字
4号字
红色
绿色
蓝色

  • 12
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值