【C语言初阶】操作符详解(2万字详细讲解)

本文详细介绍了C语言中的算术、移位、位操作、赋值、单目、关系、逻辑、条件、逗号、下标引用、函数调用和结构成员等操作符的用法及特性。通过实例分析了各种操作符的计算规则,如移位操作符的左移和右移、位操作符的按位与、按位或、按位异或,以及逻辑操作符的逻辑与和逻辑或。此外,还讨论了表达式的求值顺序、隐式类型转换和算数转换,以及操作符的优先级和结合性。
摘要由CSDN通过智能技术生成

算数操作符

算数操作符包括:+(加) -(减) * (乘) /(除) %(取模)

1.除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数
2.对于 / 操作符如果两个操作数都为整数,执行整数除法。而操作数中只要有浮点数,执行的就是浮点数除法。
3.%操作符的两个操作数必须都是整数。返回的是整除之后的余数

#include <stdio.h>
int main()
{
	int a = 9;
	int b = 2;
	double c = 2.0;
	printf("a+b = %d\n", a + b);//相加
	printf("a-b = %d\n", a - b);//相减
	printf("a*b = %d\n", a * b);//相乘
	printf("a/b = %d\n", a / b);//求商
	printf("a/c = %lf\n", a / c);//求商
	printf("a%%b = %d\n", a % b);//取模
	return 0;
}

在这里插入图片描述

移位操作符

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

移位操作符移动的是二进制补码。
所以在了解移位操作符之前,我们需先了解整数的二进制表达形式:

对于整数的二进制有3种表达形式:源码、反码、补码
1、对于正整数:源码、反码、补码相同
2、对于负整数:
源码:直接按照数字的正负写出的二进制序列
反码:源码的符号位不变,其他位按位取反得到的
补码:反码+1

符号位:最高位为0,则为正数;最高位为1,则为负数。

例如:
在这里插入图片描述

注: 无论是正整数还是负整数,只要是整数,则在内存中存储的是二进制的补码

接下来我们来看看移位操作符是如何使用的:

左移操作符:

移位规则:左边抛弃,右边补0。

#include <stdio.h>
int main()
{
	//正整数
	int a = 5;
	//00000000000000000000000000000101 -- 源码
	//00000000000000000000000000000101 -- 反码
	//00000000000000000000000000000101 -- 补码
	int b = a << 1;
	printf("%d\n",b);//打印的是源码的值
	return 0;
}

在这里插入图片描述
在这里插入图片描述

#include <stdio.h>
int main()
{
	//负整数
	int c = -1;
	//10000000000000000000000000000001 - 原码
	//11111111111111111111111111111110 - 反码
	//11111111111111111111111111111111 - 补码
	int d = c << 1;
	printf("%d\n",d);//打印的是源码的值
	return 0;
}

在这里插入图片描述
在这里插入图片描述
右移操作符:
移位规则:

首先右移运算分两种:
1、逻辑右移:
右边丢弃,左边补0
2、算数右移:
右边丢弃,左边补原符号位(原来是负数则补1,是正数则补0)

#include <stdio.h>
int main()
{
	int c = -1;
	//10000000000000000000000000000001 - 原码
	//11111111111111111111111111111110 - 反码
	//11111111111111111111111111111111 - 补码
	int d = c >> 1;
	printf("%d\n", d);
	return 0;
}

在这里插入图片描述
我们查看运行结果:
在这里插入图片描述
所以说明,在vs中采用的是算数右移。(绝大部分的编译器都是采用的算数右移,因为算数右移更合理,如果为逻辑右移,则会将一个负数移位变成正数)

注:对于移位运算符,不要移动负数位,这个是标准未定义的。例如:
int a = 15;
int b = a >> -1;//错误

注:移位操作符只能作用于整数,不能用于浮点数,因为浮点数的存储方式跟整数完全不同

位操作符

位操作符有:
& (按位与): 按二进制位的补码与。
| (按位或): 按二进制位的补码或。
^ (按位异或): 按二进制位的补码异或。

注:他们的操作数必须是整数。

& (按位与):
两个同时为1才为1,只要有一个0则为0:

#include <stdio.h>
int main()
{
	int a = 3;
	//00000000000000000000000000000011 -- a的源码、反码、补码
	int b = -2;
	//10000000000000000000000000000010 -- b的源码
	//11111111111111111111111111111101 -- b的反码
	//11111111111111111111111111111110 -- b的补码
	int c = a & b;
	//两个同时为1才为1,只要有一个0则为0:
	//00000000000000000000000000000011 -- a的补码
	//11111111111111111111111111111110 -- b的补码
	//00000000000000000000000000000010 -- a & b (c的补码)
	printf("%d\n", c);//2
	//%d  --- 说明我们要以有符号的形式打印c的值,打印的是c的源码的值
	//最高位为0,所以为正数,即:
	//00000000000000000000000000000010 -- c的补码、反码、源码  值为2
	return 0;
}

在这里插入图片描述
| (按位或):
只要有一个1则为1,两个同时为0才为0:

#include <stdio.h>
int main()
{
	int a = 3;
	//00000000000000000000000000000011 -- a的源码、反码、补码
	int b = -2;
	//10000000000000000000000000000010 -- b的源码
	//11111111111111111111111111111101 -- b的反码
	//11111111111111111111111111111110 -- b的补码
	int c = a | b;
	//只要有一个1则为1,两个同时为0才为0:
	//00000000000000000000000000000011 -- a的补码
	//11111111111111111111111111111110 -- b的补码
	//11111111111111111111111111111111 -- a | b (c的补码)
	printf("%d\n", c);
	//%d  --- 说明我们要以有符号的形式打印c的值,打印的是c的源码的值
	//最高位为1,所以为负数,即:
	//11111111111111111111111111111111 -- c的补码
	//11111111111111111111111111111110 -- c的反码(补码-1)
	//10000000000000000000000000000001 -- c的源码(符号位不变,其他位按位取反)值为:-1
	return 0;
}

在这里插入图片描述
^ (按位异或):
相同为0,相异为1:

#include <stdio.h>
int main()
{
	int a = 3;
	//00000000000000000000000000000011 -- a的源码、反码、补码
	int b = -2;
	//10000000000000000000000000000010 -- b的源码
	//11111111111111111111111111111101 -- b的反码
	//11111111111111111111111111111110 -- b的补码
	int c = a ^ b;
	//相同为0,相异为1:
	//00000000000000000000000000000011 -- a的补码
	//11111111111111111111111111111110 -- b的补码
	//11111111111111111111111111111101 -- a ^ b (c的补码)
	printf("%d\n", c);
	//%d  --- 说明我们要以有符号的形式打印c的值,打印的是c的源码的值
	//最高位为1,所以为负数,即:
	//11111111111111111111111111111101 -- c的补码
	//11111111111111111111111111111100 -- c的反码(补码-1)
	//10000000000000000000000000000011 -- c的源码(符号位不变,其他位按位取反)值为-3
	return 0;
}

在这里插入图片描述

补充:^ 操作符可以用来进行两个数的交换,不用创建第三个变量

了解上方的位操作符与移位操作符之后,我们来进行一些练习,熟悉它们的使用吧:

练习1

求一个整数存储在内存中的二进制中1的个数

思路:将该整数 &(按位与) 1,然后再将该整数向右移动一位,再次按位与1,这样循环32次(整数是4个字节,32个比特位)就可以判断该整数的二进制位的每一位是否为1

#include <stdio.h>
int main()
{
	int a = 15;
	//00000000000000000000000000001111 -- 15的源码、反码、补码
	int i = 0;
	int count = 0;//统计1的个数
	for (i = 0; i<32; i++)
	{
		if (((a >> i) & 1) == 1)
		{
			count++;
		}
	}
	printf("%d\n", count);//输出4
	return 0;
}

在这里插入图片描述

练习2

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

思路1:两数之和,减去其中一个数,得到另外一个数。(注:思考一下,这种方法是否会出现异常)
思路2:使用^(按位异或)

方法一:

#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\n", a, b);
	return 0;
}

在这里插入图片描述
其实这种方法的缺陷在于:假设我们的a和b的值都接近int类型范围的最大值,这两者相加,就会超出int类型的范围(溢出),这样可能会使得我们想得到的结果发生改变。

方法二:

#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\n", a, b);
	return 0;
}

在这里插入图片描述
我们画图了解一下其过程:
在这里插入图片描述

赋值操作符

1、简单赋值符:=

注:一个等号 “ = ” 表示赋值,两个等号“ == ” 表示等于。
通过赋值符我们可以给初始变量进行初始化赋值,也可以将先前赋的值进行调整充型赋值。
例如:

int main()
{
	int a = 10;
	a = 20;//对a的值重新赋值
	int x = 0;
	int y = 20;
	a = x = y + 1; //连续赋值  //赋值操作符是从右往左计算的
	//等同于:
	x = y + 1;
	a = x;
	return 0;
}

从这我们可以看到赋值操作符是如何使用的了,其中有个连续赋值,一般我们不建议使用连续赋值的方法,虽然这种方法在语法上支持,但它不易调试,当操作数多了之后,就不易阅读和理解。

2、符合赋值符
+=、 -=、 *=、 /=、%= 、 >>=、 <<=、 &=、 |=、 ^=

例如:

int main()
{
	int a = 10;
	a = a >> 1;
	//等价于
	a >>= 1;
	
	a = a + 10;
	//等价于
	a += 10; --
	return 0;
}

单目操作符

只有一个操作数的操作符:

1、逻辑反操作 (非)( ! ) :!0 = 1,!(非0数)=0,非0即真,非真即假
2、负值( - )
3、正值 ( + ) 一般默认省略
4、& 取地址
5、sizeof 操作数的类型长度(以字节为单位)
6、~ 对一个数的二进制按位取反
7、 - - 前置、后置- -
8、+ + 前置、后置++
9、* 间接访问操作符(解引用操作符)
10、(类型) 强制类型转换

接下来我们对上方单目操作符进行举例分析,我们从第四个开始,因为前面三个很简单,只需了解即可。

一般我们的取地址操作符( & )和解引用操作符( * )一起使用的,例如:

int main()
{
	int a = 10;
	int* p = &a;//取出a的地址放入一个p指针变量中去,p的类型是int*

	*p = 20;//解引用操作符
	//因为p中存放的是a的地址,*p就是通过a的地址找到a,然后对a进行操作
	printf("%d\n",a);//a的值被改为20,所以打印20
	return 0;
}

在这里插入图片描述

sizeof 讲解:
sizeof是操作符,不是函数。
sizeof是计算变量或者类型创建变量的内存大小,单位是字节,和内存中存放什么数据没有关系。
例一:

#include<stdio.h>
int main()
{
	int a = 10;
	char b = 'a';
	char arr1[10] = "abc";
	int arr2[20] = { 1, 2, 3, 4, 5 };
	printf("%d\n", sizeof(a));

	printf("%d\n", sizeof(b));

	printf("%d\n", sizeof(arr1));//打印的值是10,而不是4

	printf("%d\n", sizeof(arr2));//打印的值是80,而不是5
	return 0;
}

在这里插入图片描述
例二:

#include <stdio.h>
int main()
{
	int a = 5;
	short s = 10;
	printf("%d\n", sizeof(s = a + 2));//sizeof是计算变量所占内存大小
	//虽然a是int类型,但a+2的值最终还是放到变量s中,而s又是short类型,
	//所以无论a是什么类型,sizeof(s = a + 2)等价于sizeof(s)等价于sizeof(short)
	printf("%d\n", s);	
	//sizeof内部的表达式时不参与运算的
	//所以sizeof(s=a+2)里的表达式并没有真实参与运算而改变s的值,s还是原来的值
	return 0;
}

在这里插入图片描述

注:sizeof括号中的表达式不参与实际运算!

~:按位取反
按二进制补码取反
例:

int main()
{
	int a = 0;
	//00000000000000000000000000000000
	int b = ~a;
	//11111111111111111111111111111111 -- 补码
	printf("%d\n", b);//以有符号形式打印b
	//11111111111111111111111111111110 -- 反码(补码-1)
	//10000000000000000000000000000001 -- 源码(符号位不变其他位按位取反)
	//-1
	return 0;
}

在这里插入图片描述
这就是~操作符的作用,接下来我们通过例题来熟悉取反操作符的使用,看下方代码和运行结果:

int main()
{
	int a = 13;
	//00000000000000000000000000001101   现在我们需要将倒数第二位改成1,其余不变
	//00000000000000000000000000000010   我们可以将倒数第二位按位或上一个1即可
	//这个数,我们可以将 1<<1(1向左移动一位) 就可以得到了
	a |= (1 << 1);
	//00000000000000000000000000001111 -- 所得结果
	printf("%d\n", a);//15

	//现在我们需要将所得结果变回原来的值,我们可以将所得结果按位与上这样一个值
	//11111111111111111111111111111101
	//而这个值我们可以发现,是对00000000000000000000000000000010取反得到
	//而00000000000000000000000000000010是通过 1<<1(1向左移动一位)得到
	a &= (~(1 << 1));
	//00000000000000000000000000001101 -- 所得结果
	printf("%d\n", a);//13
	return 0;
}

在这里插入图片描述

注:~对补码的所有二进制位都可以进行按位取反!包括符号位

前置、后置- -、++ :
++与- -使用规则相同,这里只举例++

#include<stdio.h>
int main()
{
	int a = 10;
	int b = a++;//后置++,先使用,再++
	printf("%d\n", b);
	printf("%d\n", a);

	int c = 3;
	int d = ++c;//前置++,先++,后使用
	printf("%d\n", d);
	printf("%d\n", c);
	return 0;
}

在这里插入图片描述
(类型) 强制类型转换
例:

#include<stdio.h>
int main()
{
	double a = 3.14;//默认写出的浮点数是double类型,若要改为float类型,只需在浮点数后面加个f,如
	//flaot a = 3.14f;

	int b = (int)a;
	printf("%d\n", b);
	return 0;
}

在这里插入图片描述
sizeof和数组:

#include <stdio.h>
void test1(int arr[])//接收的是地址,所以本质是int* arr
{
	printf("%d\n", sizeof(arr));//4   计算的是指针的大小
}
void test2(char ch[])//接收的是地址,所以本质是char* ch
{
	printf("%d\n", sizeof(ch));//4   计算的是指针的大小
}
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;
}

在这里插入图片描述

关系操作符

1.> 大于
2.>= 大于等于
3.< 小于
4.<= 小于等于
5.!= 用于测试“不相等”
6.== 用于测试“相等”
注: 在编程的过程中== 和=不小心写错,会导致的错误。

逻辑操作符

&& 逻辑与
|| 逻辑或

逻辑操作符中的 && 和 || 操作的是数本身 (数为0则为假,数为非0则为真)

判断规则:表达式整体若为真则返回值为1,为假则返回0。
&& : 一假即假(0),两真则真(1)
|| :一真即真(1),两假则假(0)

举例:

#include <stdio.h>

int main()
{
	int a = 2;
	int b = 4;
	int c = a && b;
	printf("c = %d\n", c);

	int d = 0;
	int e = 5;
	int f = d && e;
	printf("f = %d\n", f);

	int i = 0;
	int j = 5;
	int k = i || j;
	printf("k = %d\n", k);

	int n = 0;
	int m = 0;
	int z = n || m;
	printf("z = %d\n", z);
	return 0;
}

在这里插入图片描述

特点:
逻辑与( && ) : 左边有一个判断为假后,右边的表达式不再执行,直接停止,返回值0(假)
逻辑或( | | ) : 左边有一个判断为真后,右边的表达式不再执行,直接停止,返回值1(真)

例如:

#include<stdio.h>
int main()
{
	int i = 0, a = 0, b = 2, c = 3, d = 4;
	i = a++ && ++b && d++;
	//a++ 为后置++, 所以a++的值为0,为假,后面的 ++b && d++均不执行
	printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
	return 0;
}

在这里插入图片描述

#include <stdio.h>
int main()
{
	int i = 0, a = 1, b = 2, c = 3, d = 4;
	//i = a++ && ++b && d++;
	i = a++||++b||d++;
	//a++为后置++,所以a++的值为1,为真, 后面的++b||d++均不执行
	printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
	return 0;
}

在这里插入图片描述

条件操作符

exp1 ? exp2 : exp3
这是我们C语言中的唯一一个三目操作符,也叫条件操作符

规则:首先表达式1进行判断,若结果为真,则 exp1 ? exp2 : exp3 整体的结果为 表达式 exp2 的结果
若结果为假,则exp1 ? exp2 : exp3 整体的结果为 表达式 exp3 的结果

例如:

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

在这里插入图片描述

逗号表达式

exp1, exp2, exp3, …expN
逗号表达式,就是用逗号隔开的多个表达式。 逗号表达式,从左向右依次计算。整个表达式的结果是最后一个表达式的结果。

例如:

#include<stdio.h>
int main()
{
	int a = 3;
	int b = 4;
	int c = (a > b, a = b + 10, a, b = a + 1);
	printf("%d\n", c);
	return 0;
}

在这里插入图片描述

强调:必须从左向右依次计算,因为最后一个表达式的值,可能会受到前面表达式值的影响而发生改变。

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

1.[ ] 下标引用操作符
操作数:一个数组名 + 一个索引值

#include<stdio.h>
int main()
{
	int arr[] = {1,2,3,4,5};//定义一个数组
	//如果我们想要访问第5个数组元素
	//arr[4];//用数组变量名+[]+下标索引数字
	//[ ] 对应的两个操作数一个是变量名arr,另外一个就是下标/索引值 4
	printf("%d\n", arr[4]);
	//arr[4] 等价于*(arr+4)
	return 0;
}

在这里插入图片描述

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

#include<stdio.h>
//这个地方的()不是函数调用操作符,是函数定义的语法规则
int get_max(int x, int y)
{
	return x > y ? x : y;
}
void test()
{
	printf("hehe\n");
}
int main()
{
	int a = 10;
	int b = 20;
	//调用函数的时候使用的() 就是函数调用操作符
	int max = get_max(a, b);//只要是调用函数,()绝对不能缺少
	//这里的函数调用操作符()的操作数为 函数名get_max,函数参数a,函数参数b  总共三个操作数
	//对于函数调用操作符()而言,其操作数至少要有一个(函数名)
	printf("max = %d\n", max);
	test();//只要是调用函数,()绝对不能缺少
	//对于函数调用操作符()而言,其操作数至少要有一个(函数名)
	return 0;
}

在这里插入图片描述
3.访问一个结构的成员

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

在了解结构体成员操作符之前,我们先了解如何创建结构体类型与结构体变量:

#include<stdio.h>
//创建一个结构体类型(自定义类型)
struct Book  //struct Book是一个结构体类型
//类型是用来创建变量的
{
	//结构体成员名:
	char name[20];//书名
	float price;//价格
	char id[20];//书号
};//分号不可少
int main()
{
	int a = 10;
	//使用struct Book这个类型创建了一个对象b,并初始化
	struct Book b = { "C语言程序设计", 55.5f, "C20210403" };
	return 0;
}

当我们创建好结构体类型,并将其对象始化后,我们可以通过两种方式来打印其对象的信息:

1.结构体变量.成员名

#include<stdio.h>
//创建一个结构体类型(自定义类型)
struct Book  //struct Book是一个结构体类型
//类型是用来创建变量的
{
	char name[20];//书名
	float price;//价格
	char id[20];//书号
};//分号不可少
void print1(struct Book b)
{
	printf("书名:%s\n", b.name);
	printf("价格:%f\n", b.price);
	printf("书号:%s\n", b.id);

}
int main()
{
	int a = 10;
	//使用struct Book这个类型创建了一个对象b,并初始化
	struct Book b = { "C语言程序设计", 55.5f, "C20210403" };
	print1(b);//打印书的信息
	return 0;
}

在这里插入图片描述
除了这种方式外,我们还可以通过指针的形式来进行访问对象的成员信息,例如:

void print2(struct Book* pb)
{
	printf("书名: %s\n", (*pb).name);
	printf("价格: %f\n", (*pb).price);
	printf("书号: %s\n", (*pb).id);
}
int main()
{
	int a = 10;
	//使用struct Book这个类型创建了一个对象b,并初始化
	struct Book b = { "C语言程序设计", 55.5f, "C20210403" };
	print2(&b);//将b的地址传过去
	return 0;
}

在这里插入图片描述
这样也可以打印出我们所需要的信息。其实我们可以感觉到,用指针这样写比较麻烦,所以我们可以将这种指针的形式做一个改进,使其更加简介。

结构体指针->成员名 通过指针的方式直接访问到变量的成员。

void print2(struct Book* pb)
{
	/*printf("书名: %s\n", (*pb).name);
	printf("价格: %f\n", (*pb).price);
	printf("书号: %s\n", (*pb).id);*/

	printf("书名: %s\n", pb->name);
	printf("价格: %f\n", pb->price);
	printf("书号: %s\n", pb->id);
}
int main()
{
	int a = 10;
	//使用struct Book这个类型创建了一个对象b,并初始化
	struct Book b = { "C语言程序设计", 55.5f, "C20210403" };
	print2(&b);//将b的地址传过去
	return 0;
}

在这里插入图片描述
方式总合:

#include<stdio.h>
//创建一个结构体类型(自定义类型)
struct Book  //struct Book是一个结构体类型
//类型是用来创建变量的
{
	char name[20];//书名
	float price;//价格
	char id[20];//书号
};//分号不可少
void print1(struct Book b)//打印书的信息
{
	printf("书名:%s\n", b.name);
	printf("价格:%f\n", b.price);
	printf("书号:%s\n", b.id);

}
void print2(struct Book* pb)//打印书的信息
{
	/*printf("书名: %s\n", (*pb).name);
	printf("价格: %f\n", (*pb).price);
	printf("书号: %s\n", (*pb).id);*/

	printf("书名: %s\n", pb->name);
	printf("价格: %f\n", pb->price);
	printf("书号: %s\n", pb->id);
}
int main()
{
	int a = 10;
	//使用struct Book这个类型创建了一个对象b,并初始化
	struct Book b = { "C语言程序设计", 55.5f, "C20210403" };
	print1(b);
	print2(&b);//将b的地址传过去,
	return 0;
}

表达式求值

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

隐式类型转换

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

那究竟是如何进行整型提升的呢?

接下来我们通过举例,来理解这些话的意思:

#include <stdio.h>
int main()
{
	char a = 3;
	//00000000000000000000000000000011  整数3的补码
	//现将整数3要放入a中,因为a是char类型的,只有一个字节,所以只能存8bit,所以发生截断
	//截断规则:只取最低的8个bit位
	//所以a中存储的为:  00000011
	char b = 127;
	//00000000 00000000 00000000 01111111  整数127
	//同理b中存储的为:  01111111

	//a和b都是char类型,自身大小都是1字节,所以这里计算的时候需要进行整型提升
	//整型提升规则:按照变量的数据类型的符号位来提升的
	//00000011 ->00000000 00000000 00000000 00000011  a提升之后
	//01111111 ->00000000 00000000 00000000 01111111  b提升之后
			   //00000000 00000000 00000000 10000010  a+b的结果
	char c = a + b;
	//现将a+b的结果放入c中,又因为c是char类型,所以又会发生截断
	//10000010 ->c
	printf("%d\n", c);
	//我们要以%d有符号的形式打印,需要进行整型提升:按照变量数据类型的符号位来提升的
	//11111111 11111111 11111111 10000010  ---补码
	//11111111 11111111 11111111 10000001  ---反码(补码-1)
	//10000000 00000000 00000000 01111110  ---源码(符号位不变,其他位按位取反)
	return 0;
}

相加时,a和b的值首先被提升为普通整型,然后再执行加法运算。
加法运算完成之后,结果将被截断,然后再存储于c中
在这里插入图片描述
从上方例题我们也可以看到整型提升是如何进行的了,接下来,我们将整型提升具体总结一下:

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

1.有符号整型提升:

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

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

2.无符号整型提升:高位补0

从上面我们可以发现,整形提升针对的是字节长度小于int类型的变量,所以整型提升可以说是只对于char类型和short类型的变量。

总结:什么时候会发生整型提升?

只要字节长度小于int,且参与到实际运算后就会进行整型提升。

算数转换

除了char类型与short类型之外,其它类型之间的转换称为算数转换。

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

int main()
{
	int a = 4;
	float f = 4.5f;
	float r = a + f;
	//会先将a转换为float类型 a = 4.00000,然后再进行相加
	return 0;
}

注:排名高的类型向低位转换的时候会出现精度丢失。
例如:
float f = 3.14;
int num = f;//将float类型转换为int类型,会有精度丢失
所以算术转换要合理,要不然会有一些潜在的问题

操作符的属性

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

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

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

C语言操作符优先级、结合性参考表:
优先级从上往下依次递减

在这里插入图片描述
即使我们知道了每个操作符的优先级与结合性,但不代表所有表达式都可以正确计算得出,也有很多问题表达式,例如:

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

就这样一个代码,再不同的编译器中,我们会得出不同的结果:
Linux平台运行结果:10
vs2013中运行结果:12
这是由于我们运算的顺序不同而导致的,我们可以先将所有括号中的先进行计算然后总体相加。也可以先算前两个括号的表达式,所得结果相加,再进行最后一个括号表达式的结果,最后整体相加。

还有很多这种问题表达式,所以我们再写代码时需要注意写的是否为问题表达式。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clumsy、笨拙

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值