讲解主要内容:
1. 各种操作符的介绍
2. 表达式求值
以下让我们开始正式重新认识和理解操作符吧!
1. 操作符(也叫运算符)分类
算术操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用、函数调用和结构成员
2. 算术操作符
+(加法) - (减法) *(乘法) /(除法) %(取余或取模)
加减乘操作符同数学一样,但需注意除法和取模!
2.1 '/'操作符
对于/操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。(即想计算机出小数,除号的两端至少有一个操作数是浮点数。)
/操作数计算的结果是商。
代码实例:
// ‘ /’ 操作符
// 备注:对于除法操作符来说,两边的操作数都是整数,执行的是整数除法
// 如果想计算机出小数,除号的两端至少有一个操作数是浮点数
#include<stdio.h>
int main()
{
int ret1 = 10 / 3;
double ret2 = 10.0 / 3;
printf("ret1=%d\n", ret1);
printf("ret2=%lf\n", ret2);
//float与double用printf打印后面默认保留六位小数
return 0;
}
运行结果:
2.2 %操作符
1、%取模(取余)计算的是整除之后的余数。
2、%操作符的两个操作数必须为整数。(操作符为小数报错!)
// '%'取模(取余)
// 1、计算的是整除之后的余数
// 2、%操作数的两个操作数必须为整数。
#include<stdio.h>
int main()
{
int ret = 10 % 3;
printf("ret=%d\n", ret);
return 0;
}
运行结果:
2.3 总结
1. 除了%操作符之外,其他的几个操作符可以作用于整数和浮点数。
2. 对于/操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
3. %操作符的两个操作数必须为整数。返回的是整除之后的余数。
4. 想得到商用/操作符,想得到余数用%操作符。
3. 移(二进制)位操作符
<< 左移操作符(箭头向左)
>> 右移操作符(箭头向右)
注:1. 移位操作符的操作数只能是整数!
2. 移的是二进制数
知识补充:二进制
整形数据(整数)的2进制表示形式,其实有3种:
1. 原码:按照一个数的正负,直接写出它的二进制表示形式得到的就是原码。
2. 反码:反码是原码的符号位不变,其他位按位取反,就是反码。
3. 补码:补码是反码+1。
1. 正数的原码、反码、补码是相同的
2. 负数的原码、反码、补码要通过计算的
3. 区分正负数:最高位为符号位(0代表整数,1代表负数)
4. int占4个字节,就有32个bit位
代码实例:
int a=10;
// 10的2进制:1010
// 原码:0000 0000 0000 0000 0000 0000 0000 1010
int b=-10;
// 原码:1000 0000 0000 0000 0000 0000 0000 1010
// 反码:1111 1111 1111 1111 1111 1111 1111 0101
// 补码:1111 1111 1111 1111 1111 1111 1111 0110
由补码转原码:
// 补码:1111 1111 1111 1111 1111 1111 1111 0110
// 过程:1000 0000 0000 0000 0000 0000 0000 1001
// 原码:1000 0000 0000 0000 0000 0000 0000 1010
回顾进制:
进制只是数值的不同表达形式,它的本质是一样的。
如:数值——12
二进制:1100
八进制:014
十进制:12
十六进制:0xc
只是把数值用不同进制表示出来,但数值本质还是12.
//非十转十:按权展开(注:引导符不算)
//十转非十:整数部分——取余倒排序法
整形数据在内存中的存储形式:
整形数据在内存中存储的其实是:补码的二进制
所以在参与移位的时候,移动的都是补码 。
3.1 左移操作符
移位规则:
左边抛弃,右边补0。
图解:
代码实例:
#include<stdio.h>
int main()
{
int a = 10;
//补码:0000 0000 0000 0000 0000 0000 0000 1010
int b = a << 1;
//补码:0000 0000 0000 0000 0000 0000 0001 0100
printf("a=%d\n", a);
printf("b=%d\n", b);
//a虽然向左移了一位,但实际上a在没被赋值的情况下,自身的值不会变化
return 0;
}
运行结果:
注:
1. a虽然向左移了一位,但实际上a在没被赋值的情况下,自身的值不会变化。(即a<<1的结果是移位之后的效果,但是a是不变的(如b=a+2,a是不变的))。
2. 打印的时候打印的是原码。
3.2 右移操作符
移位规则:(分为两种)
1、算术右移(常见——如VS)
左边补原来值的符号位,右边抛弃。
2、逻辑右移
左边直接补0,右边抛弃。
代码实例:
#include<stdio.h>
int main()
{
int a = -1;
//原码;1000 0000 0000 0000 0000 0000 0000 0001
//反码:1111 1111 1111 1111 1111 1111 1111 1110
//补码:1111 1111 1111 1111 1111 1111 1111 1111
//补码全为1的为(数值)-1
//算术还是逻辑?
int b = a >> 1;
printf("a=%d\n", a);
printf("b=%d\n", b);
return 0;
}
运行结果:
算术右移。
警告:
对于移位运算符,不要移动负数位,这个是标准未定义的。
例: int a=10;
int b=a>>-1;//error
4. 位操作符
位操作符有:
& //按位与
| //按位或
^ //按位异或
注:
1、他们的操作数必须是整数
2、也是针对二进制位进行运算的
运算规则;
&——按(2进制)位与
对应的二进制位:有0则为0,两个同时为1才为1.
|——按(2进制)位或
对应的二进制位:有1则为1,两个同时为0才为0.
^——按(二进制)位异或
对应的二进制位:相同为0,相异为1。
代码实例:
#include<stdio.h>
int main()
{
int a = 3;
//补码:00000000000000000000000000000011
int b = -5;
//原码:10000000000000000000000000000101
//反码:11111111111111111111111111111010
//补码:11111111111111111111111111111011
int c = a & b;
//a的补码:00000000000000000000000000000011
//b的补码:11111111111111111111111111111011
//a&b:00000000000000000000000000000011(正数)
int d = a | b;
//a的补码:00000000000000000000000000000011
//b的补码:11111111111111111111111111111011
//a|b:11111111111111111111111111111011(负数)
int e = a ^ b;
//a的补码:00000000000000000000000000000011
//b的补码:11111111111111111111111111111011
//a^b:11111111111111111111111111111000(负数)
printf("c=%d\n", c);
printf("d=%d\n", d);
printf("e=%d\n", e);
return 0;
}
运行结果:
我们来做一道变态的面试题:
不能创建临时变量(第三个变量),实现两个整数的交换
代码实例:
//代码1——创建临时变量
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
printf("交换前:%d %d\n",a,b);
int t = a;//临时变量
a = b;
b = t;
printf("交换后:%d %d\n",a,b);
return 0;
}
//代码2——计算得到(局限性——当a与b的值太大时int可能溢出)
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
printf("交换前:%d %d\n", a, b);
b = a + b;
a = b - a;
b = b - a;
printf("交换后:%d %d\n", a, b);
return 0;
}
//代码3——使用按位异或操作符
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
printf("交换前:%d %d\n", a, b);
a = a ^ b;
b = a ^ b;//b=a^b^b=a
a = a ^ b;//a=a^b^a=b
printf("交换后:%d %d\n", a, b);
return 0;
}
按位异或操作符加油站:
5. 赋值操作符
5.1 简单赋值操作符
=
1、作用:赋值操作符可以让你自己重新给变量赋值
注:是将右侧的表达式赋值给左侧的变量(左侧一定为变量)
一般格式:变量=表达式
例:
int weight=120;//体重
weight=89;//重新赋值
2、赋值操作符可以连用,但是不推荐。
例:
int a=2;
int b=0;
int c=5;
a=b=c+1;//连续赋值
但是这样的代码你感觉怎么样?
改为同样的语义,你再看看:
b=c+1;
a=b;
这样写法是不是更加清晰爽朗而且易于调试!
3、类型转换:
①转换条件:当赋值操作符两侧数据类型不一致时
②转换原则:转换为被赋值变量的类型
例:
int a=1;
double b=3;
a=3+0.3;//3.3转换为整形为3
5.2 复合赋值操作符
在赋值操作符之前可以加上算术操作符和移位操作符构成复合赋值操作符。C语言规定可以使用10种复合赋值操作符,分别为:
+=
-=
*=
/=
%=
<<=
>>=
&=
|=
^=
一般格式: 变量 双目操作符=表达式;
例:
a*=1-3;//a=a*(1-3),这样写更加简洁!
6 单目操作符
6.1 单目操作符介绍
! 逻辑反操作(相反:真变假,假变真。)
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
注意:单目操作符,只有一个操作数
例:
int b=a+1;//这里‘+’是加法操作符,因为它有两个操作数(1和a),是双目操作符。
int b=+1;//这里‘+’是正值,因为它只有一个操作数(1),是单目操作符。
代码实例:
1、
!——逻辑反操作(相反:真变假,假变真)
//!-逻辑反操作
//相反:真变假,假变真
//C语言中0表示真,非0表示假
#include<stdio.h>
int main()
{
int flag = 5;
if (flag)//if语句,如果flag为真,则执行if控制的语句,为假则不执行
{
printf("hehe\n");
}
if (!flag)
{
printf("haha\n");
}
printf("flag=%d\n", flag);
printf("!flag=%d\n", !flag);
return 0;
}
运行结果:
补充:加油站
布尔类型
1.布尔类型是C99中引入的
2.布尔类型就是专门用来表示真假的类型
3.真:true(本质是1),假:false(本质是0)
代码实例:
#include<stdbool.h>//预处理,对_Bool的声明
#include<stdio.h>
int main()
{
_Bool flag1 = true;//真
if (flag1)
{
printf("hehe\n");
}
_Bool flag2 = false;//假
if (flag2)
{
printf("haha\n");
}
return 0;
}
运行结果:
布尔类型的应用:
写一个函数判断是否为闰年
#include<stdio.h>
#include<stdbool.h>
bool is_leap_year(int y)
{
//判断是否为闰年
if (y % 4 == 0 && y % 100 != 0 || y % 400 == 0)
{
return true;
}
else
{
return false;
}
}
int main()
{
int y = 0;
scanf("%d", &y);
bool ret = is_leap_year(y);//调用函数
if (ret)
{
printf("%d是闰年\n", y);
}
else
{
printf("%d不是闰年\n", y);
}
return 0;
}
2、
- ——负值(就是正数变成负数,负数变成正数)
代码实例:
//-:负值
#include<stdio.h>
int main()
{
int a = 3;
int b = -6;
printf("a=%d\n", a);
printf("-a=%d\n", -a);
printf("b=%d\n", b);
printf("-b=%d\n", -b);
return 0;
}
运行结果:
3、
+ ——正值 (基本上忽略,不用,因为正数的正值还是正数,负数的正值还是负数)
#include<stdio.h>
int main()
{
int a = 3;
int b = -5;
printf("a=%d\n", a);
printf("+a=%d\n", +a);
printf("b=%d\n", b);
printf("+b=%d\n", +b);
return 0;
}
运行结果:
注意:
unsigned和signed
1. unsigned:无符号的,只能存放正数。
无符号——没有符号位,即最高位不是符号位。
2. signed:有符号的(最高位是符号位,正数负数均可),一般可省略不写。
例:signed int a=2;//与int a=2一样,signed省略。
代码实例:
#include<stdio.h>
int main()
{
unsigned int a = 10;
//补码:00000000000000000000000000001010(最高位0不是符号位)
//如果写成负数怎么样
unsigned int b = -10;
//原码:10000000000000000000000000001010
//反码:11111111111111111111111111110101
//补码:11111111111111111111111111110110(最高位1不是符号位)
printf("a=%u\n", a);
printf("b=%u\n", b);
return 0;
}
运行结果:
4、
& 取地址(取出一个对象(可以是变量、字符串)内存的地址)
* 解引用操作符(语法:只要是地址就可以解引用)
相当于快递:&找到地址,*送快递。
4.1 &取地址
//&取地址
#include<stdio.h>
int main()
{
//变量可以是:整形变量、字符变量、数组变量、数组元素变量等等
int a = 10;
int* pa = &a;
char ch = 'w';
char* pc = &ch;
int arr[10] = { 0 };
int* p1 = arr;
int* p2 = &arr[0];
//字符串常量就是地址,产生的是首字符的地址
char* p = "abcd";
printf("%p\n", p);
printf("%c\n", *p);
return 0;
}
运行结果:
4.2 *解引用操作——通过地址,从内存访问
//*解引用操作——通过地址,从内存访问
#include<stdio.h>
int main()
{
int a = 10;
int* pa = &a;
*pa = 20;
printf("a=%d\n", a);
return 0;
}
运行结果:
&取地址操作符与解引用操作符是一对,有因果的先取到一个地址,再解引用。
注意:我们不能自己捏造一个地址就对他解引用,虽然不报错但是它是非法代码!
例:
int main()
{
*(int*)0x0012ff40;
}
这就相当于,你知道了别人家的地址,就可以直接去他家吗?
5.
sizeof 是关键字也是操作符(功能:计算操作数的类型长度)
注:函数调用的时候,要写括号,但是sizeof后边的括号可以省略(注:变量名可以,类型不可以),说明sizeof不是函数
//sizeof是关键字(如变量的命名不能和关键字一样)
//也是操作符(功能:计算操作数的类型长度)
//函数调用的时候,要写括号,但是sizeof后边的括号可以省略(注:变量名可以,类型不可以),说明sizeof不是函数
#include<stdio.h>
int main()
{
int a = 10;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof a);
printf("%d\n", sizeof(int));
int arr[10] = { 0 };
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof arr);
printf("%d\n", sizeof(int[10]));
return 0;
}
运行结果:
数组名是数组首元素的地址。
但是在数组我们说了有两个例外:
1、sizeof(数组名),这里的数组名是表示整个数组,计算的是整个数组的大小,单位是字节。
2、&数组名,这里的数组名是表示整个数组,&数组名取出的是数组的地址。
sizeof的特点:sizeof内部的表达式是不计算的!!!
//sizeof的特点:sizeof内部的表达式是不计算的!!
#include<stdio.h>
int main()
{
int a = 10;
short s = 5;
printf("%d\n", sizeof(s = a + 3));
printf("%d\n", s);
return 0;
}
运行结果:
6.
~ 按位取反操作符 (包括符号位)
注:①操作数为整数;②取的是二进制位
//~按位取反(注:包括符号位)
#include<stdio.h>
int main()
{
//00000000000000000000000000000000
//11111111111111111111111111111111—补码全是1的是-1
int a = 0;
printf("%d\n", ~a);
return 0;
}
运行结果:
综合应用:& | ^ << >> ~
// & | ^ << >> ~的应用
//例:将9的二进制的第五位改成1,其他位不变
#include<stdio.h>
int main()
{
int a = 9;//二进制:1001
//补码:00000000000000000000000000001001
//准备:00000000000000000000000000010000 1<<4
//结果:00000000000000000000000000011001
//将9的二进制的第五位改成1,其他位不变
a |= (1 << 4);
printf("%d\n", a);
//将9的二进制的第五位改回0,其他位不变
//补码:00000000000000000000000000011001
//准备:11111111111111111111111111101111 ~(1<<4)
//结果;00000000000000000000000000001001
a &= ~(1 << 4);
printf("%d\n", a);
return 0;
}
运行结果:
7.
++ 前置++:先++,后使用;后置++:先使用,后++ 。
-- 前置--:先--,后使用;后置--:先使用,后--。
代码实例:
//自增自减操作符
#include<stdio.h>
void test(int b)
{
printf("%d\n", b);
}
int main()
{
//代码1
//int a = 10;
//int b = a++;//后置++:先使用,后++
相当于:①b=a;②a=a+1;
//printf("%d\n", a);//11
//printf("%d\n", b);//10
//int a = 10;
//int b = ++a;//前置++:先++,后使用
相当于:①a=a+1;②b=a;
//printf("%d\n", a);//11
//printf("%d\n", b);//11
//代码2
//int a = 10;
//printf("%d\n", a++);//10
//printf("%d\n", a);//11
//int a = 10;
//printf("%d\n", ++a);//11
//printf("%d\n", a);//11
//代码3
int a = 10;
//test(a++);//10
test(++a);//11
return 0;
}
注:自增自减给自己带来的副作用:
代码实例:
//代码1
int a = 10;
int b = ++a;//a=11,b=11//代码2
int a = 10;
int b = a + 1;//a=10,b=11
8.
(类型)强制类型转换
强制类型转换尽量少用,如果开始能把值设定好就不要故意再使用强制类型转换。
如结构体类型你就不能再强制类型转换。
代码实例:
//(类型)强制类型转换
//强制类型转换尽量少用,如果开始能把值设定好就不要故意再使用强制类型转换
//如结构体类型你就不能再强制类型转换
#include<stdio.h>
int main()
{
//代码1
int a = (int)3.6;//强制类型转换,将浮点型3.6强制转换为整形int型
printf("%d\n", a);
//代码2
//time的返回值类型是time_t,我们强制转换成无符号型
srand((unsigned int)time(NULL));
return 0;
}
6.2 sizeof和数组
代码实例:
//sizeof和数组
#include<stdio.h>
void test1(int arr[])
{
printf("%d\n", sizeof(arr));//计算的是指针的大小
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch));//计算的是指针的大小
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%d\n", sizeof(arr));//计算的是数组arr的大小
printf("%d\n", sizeof(ch));//计算的是数组ch的大小
//数组名传参,形参可以写成数组,也可以写成指针
//形参本质上是指针
test1(arr);
test2(ch);
return 0;
}
运行结果:
当用数组名传参的时候,实际上只是把数组的首元素的地址传递过去了。即使写成数组的形式,本质上也是指针。
arr[i] <------> *(arr+i)
&arr[i] <------> arr+i
7. 关系操作符
关系操作符
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
注意:在编程的过程中==和=不小心写错,导致的错误
== 两个等于在C语言中才是数学中的相等(判断常量和变量相等,好的习惯是把常量写在右边)。
= 一个相等在C语言中是赋值(注:赋值是左边为变量)
代码实例:
//关系操作符
//== 用于测试“相等”
#include<stdio.h>
int main()
{
int a = 0;
if (0 == a)//判断常量与变量是否相等,好的习惯把变量放在右边
{
printf("%d\n", a);
}
if (a = 0)//如写在左边,少些一个等号不报错,一个等号是赋值
{
printf("%d\n", a);
}
return 0;
}
8. 逻辑操作符
逻辑操作符
&& 逻辑与(并且:参与运算的两个逻辑值都为真时,结果为真)
|| 逻辑或(或者:参与运算的两个逻辑值都为假时,结果为假)
注意区分逻辑与(或)和按位与(或):
逻辑与(或)----->只关注真假
按位与(或)----->通过二进制计算得到
代码实例:
//逻辑操作符
//判断闰年的条件
//1、能被4整除,并且不能被100整除
//2、能被400整除
#include<stdio.h>
int main()
{
int y = 2048;
if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
{
printf("%d是闰年\n", y);
}
else
{
printf("%d不是闰年\n", y);
}
return 0;
}
补充:逻辑操作符的短路特性
&&操作符,左边为假,右边无需计算
||操作符,左边为真,右边就无需计算
代码实例:
//逻辑操作符的短路特性
#include<stdio.h>
int main()
{
int a = 0;
int i = 0;
int b = 2;
int c = 3;
int d = 4;
//代码1
//&&操作符,左边为假,右边无需计算
//i = a++ && ++b && d++;
//代码2
// ||操作符,左边为真,右边就无需计算
i= a++ || ++b || d++;
printf(" a=%d\n b=%d\n c=%d\n d=%d\n", a, b, c, d);
return 0;
}
运行结果:
代码1:
代码2:
9. 条件操作符(三目操作符)
代码实例:
//条件操作符
#include<stdio.h>
int main()
{
int a = 10;
int b = 20;
int max = 0;
//代码1:使用选择语句,找两个数的较大值
//if (a > b)
//{
// max = a;
//}
//else
//{
// max = b;
//}
//printf("max=%d\n", max);
//代码2
//转换为条件表达式,是怎么样的?
//改用条件表达式实现找两个数的较大值
max = (a > b ? a : b);
printf("max=%d\n", max);
return 0;
}
运行结果:
10. 逗号表达式
exp1,exp2,exp3,...expN
逗号表达式,就是用逗号隔开的多个表达式。逗号表达式, 从左向右依次执行 。整个表达式的 结果是最后一个表达式 的结果。
//逗号表达式
#include<stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);//逗号表达式
printf("%d\n", c);//13
return 0;
}
11. 下标引用、函数调用和结构成员
1. []下标引用操作符
操作数:一个数组名+一个索引值(下标)
代码实例:
//1、[]—下标引用操作符
//操作数:一个数组名+一个索引值(下标)
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5 };
printf("%d\n", arr[4]);// []—下标引用操作符,操作数是arr,4
return 0;
}
2. ()函数调用操作符
//2、()函数调用操作符
//接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数
#include<stdio.h>
#include<string.h>//预处理,对strlen的声明
int main()
{
int len = strlen("abcdef");//()就是函数调用操作符,操作数:strlen,“abcdef”
printf("%d\n", len);//strlen函数是计算字符串长度的,len=6
return 0;
}
3. 访问一个结构的成员
. 结构体变量.成员名
-> 结构体指针->成员名
代码实例:
//结构体
//类型:内置类型和自定义类型
//内置类型:我们常用的int char short long ,long long float double
//自定义类型(聚合类型):结构体,枚举,联合体
//为什么要有自定义类型?
//生活中有些(复杂)对象要被描述的话,不能简单使用单个内置类型
//如书:书名,作者,定价……(用{}聚合在一起)
#include<stdio.h>
//结构体类型
struct Book
{
char name[20];//书名
char author[20];//作者
double price;//定价
};
void print1(struct Book* p)
{
printf(" %s %s %.2lf\n", (*p).name, (*p).author, (*p).price);
printf(" %s %s %.2lf\n", p->name, p->author, p->price);
//结构体指针->成员名
}
int main()
{
struct Book b1 = { "数学","张三",66 };
struct Book b2 = { "英语","李四",88 };
//那结构体成员是怎么访问的呢?
//结构体变量.成员名
printf(" %s %s %.2lf\n", b1.name, b1.author, b1.price);
printf(" %s %s %.2lf\n", b2.name, b2.author, b2.price);
print1(&b1);//传址调用
return 0;
}
运行结果:
12. 表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
12.1 隐式类型转换
C的整型算术运算总是至少以缺省(缺省就是默认的意思)整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整形提升。
整形提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
代码实例:
代码1:
//隐式类型转换
//C的整形算术运算总是至少以缺省整形类型的精度进行的
//为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整形提升
//整形提升是针对于类型小于整形的
//char short int long……
// 1 2 4
//代码1
#include<stdio.h>
int main()
{
char a = 3;
char b = 127;
char c = a + b;
//b和c的值被提升为普通整型,然后再执行加法运算。
//加法运算完成之后,结果将被截断,然后再存储于a中
printf("c=%d\n", c);
return 0;
}
整形提升是针对于类型小于整形的,如char short类型操作数在使用之前转换为int型
代码2:
如何进行整形提升呢?
整形提升是按照变量的数据类型的符号位来提升的
//代码2
//如何进行整形提升?
//整形提升是按照变量的数据类型的符号位来提升的
//负数的整形提升
char c1 = -1;
//变量c1的二进制位(补码)中只有8个比特位:
//1111111
//因为 char 为有符号的 char
//所以整形提升的时候,高位补充符号位,即为1
//提升之后的结果是:
//11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
//变量c2的二进制位(补码)中只有8个比特位:
//00000001
//因为 char 为有符号的 char
//所以整形提升的时候,高位补充符号位,即为0
//提升之后的结果是:
//00000000000000000000000000000001
//无符号整形提升,高位补0
代码3:
//代码3
#include<stdio.h>
int main()
{
//当前VS编译器char---->signed char
char a = 3;
//整形的二进制:00000000000000000000000000000011
// 截断
//char存储:00000011
char b = 127;
//整形的二进制:00000000000000000000000001111111
//截断
//char存储:01111111
char c = a + b;
//a-00000011
//b-01111111
//整形提升
//a-00000000000000000000000000000011
//b-00000000000000000000000001111111
//a+b:00000000000000000000000010000010
//截断
//c-10000010
printf("c=%d\n", c);
//%d是打印十进制的整数
//c-10000010
//整形提升
//补码:11111111111111111111111110000010
//过程:11111111111111111111111110000001
//原码:10000000000000000000000001111110
return 0;
}
代码运行C是多少呢?
C=-126
为什么呢?那我们了解一下char的取值范围吧。
代码4:
//整形提升的例子
//整形提升是针对于类型小于整形的,如char short类型操作数在使用之前转换为int型
//代码4
#include<stdio.h>
int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if (a == 0xb6)//a-10110110要整形提升,变成负数,假
{
printf("a");
}
if (b == 0xb600)//b-1011011000000000要整形提升,变成负数,假
{
printf("b");
}
if (c == 0xb6000000)//c不整形提升,真
{
printf("c");
}
return 0;
}
代码5:
//代码5
#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;
}
运行结果:
C只要参与表达式运算,就会发生整形提升。
12.2 算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long intunsigned int
int
由低到高转换(从下向上)
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
注意:算术转换讨论的类型都是大于或等于int型的
警告:
但是算术转换要合理,要不然会有一些潜在的问题。(就高不就低,否则可能精度丢失)
例:
float f = 3.14;
int num = f;//隐式转换,会有精度丢失
总结:
低于int型的char,short为整形提升,等于或大于int型的为算术转换。(从低到高)
12.3 操作符的属性
复杂表达式的求值有三个影响的因素。
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
操作符优先级
操作符 | 描述 | 用法示例 | 结合类型 | 结合性 | 是否控制求值顺序 |
---|---|---|---|---|---|
() | 聚组 | (表达式) | 与表达式同 | N/A | 否 |
() | 函数调用 | rexp(rexp,...,rexp) | rexp | L-R | 否 |
[] | 下标引用 | rexp[rexp] | lexp | L-R | 否 |
. | 访问结构成员 | lexp.member_name | lexp | L-R | 否 |
-> | 访问结构指针成员 | rexp->member_name | lexp | L-R | 否 |
++ | 后缀++ | lexp++ | rexp | L-R | 否 |
-- | 后缀-- | lexp-- | rexp | L-R | 否 |
! | 逻辑反 | !rexp | rexp | R-L | 否 |
~ | 按位取反 | ~rexp | rexp | R-L | 否 |
+ | 单目,表示正值 | +rexp | rexp | R-L | 否 |
- | 单目,表示负值 | -rexp | rexp | R-L | 否 |
++ | 前缀自增 | ++lexp | rexp | R-L | 否 |
-- | 前缀自减 | --lexp | rexp | R-L | 否 |
* | 间接访问 | *rexp | lexp | R-L | 否 |
& | 取地址 | &lexp | rexp | R-L | 否 |
sizeof | 取其长度,以字节表示 | sizeof rexp sizeo(类型) | rexp | R-L | 否 |
(类型 ) | 类型转换 | (类型)rexp | rexp | R-L | 否 |
* | 乘法 | rexp*rexp | rexp | L-R | 否 |
/ | 除法 | rexp/rexp | rexp | L-R | 否 |
% | 整数取余 | rexp%rexp | rexp | L-R | 否 |
+ | 加法 | rexp+rexp | rexp | L-R | 否 |
- | 减法 | rexp-rexp | rexp | L-R | 否 |
<< | 左移位 | rexp<<rexp | rexp | L-R | 否 |
>> | 右移位 | rexp>>rexp | rexp | L-R | 否 |
> | 大于 | rexp>rexp | rexp | L-R | 否 |
>= | 大于等于 | rexp>=rexp | rexp | L-R | 否 |
< | 小于 | rexp<rexp | rexp | L-R | 否 |
<= | 小于等于 | rexp<=rexp | rexp | L-R | 否 |
== | 等于 | rexp==rexp | rexp | L-R | 否 |
!= | 不等于 | rexp!=rexp | rexp | L-R | 否 |
& | 位于 | rexp&rexp | rexp | L-R | 否 |
^ | 位异或 | rexp^rexp | rexp | L-R | 否 |
| | 位或 | rexp|rexp | rexp | L-R | 否 |
&& | 逻辑与 | rexp&&rexp | rexp | L-R | 是 |
|| | 逻辑或 | rexp||rexp | rexp | L-R | 是 |
?: | 条件操作符 | rexp?rexp:rexp | rexp | N/A | 是 |
= | 赋值 | lexp=rexp | rexp | R-L | 否 |
+= | 以...加 | lexp+=rexp | rexp | R-L | 否 |
-= | 以...减 | lexp-=rexp | rexp | R-L | 否 |
*= | 以...乘 | lexp*=rexp | rexp | R-L | 否 |
/= | 以...除 | lexp/=rexp | rexp | R-L | 否 |
%= | 以...取模 | lexp%=rexp | rexp | R-L | 否 |
<<= | 以...左移 | lexp<<=rexp | rexp | R-L | 否 |
>>= | 以...右移 | lexp>>=rexp | rexp | R-L | 否 |
&= | 以...与 | lexp&=rexp | rexp | R-L | 否 |
^= | 以...异或 | lexp^=rexp | rexp | R-L | 否 |
|= | 以...或 | lexp|=rexp | rexp | R-L | 否 |
, | 逗号 | rexp,rexp | rexp | L-R | 真 |
说明:N/A是没有的意思,R-L是从右向左
从上到下优先级变低。
小结:
操作符的属性
1.首先确定优先级,相邻操作符按照优先级高低计算(相邻操作符才讨论优先级)
2.优先级相同的情况下,结合性才起作用。3.注意是否控制求值顺序(只有四个:&& || (?:) ,)
那知道了操作符的属性是否就能确定计算的唯一路径呢?
一些问题表达式:
1.
2.
注释:同上,操作符的优先级只能决定自减--的运算在+的运算的前面,但是我们并没有办法得知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。
3.
这个代码是有问题的。
虽然在大多数的编译器上求得结果都是相同的。
但是上述代码 answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知:先算乘法,再算减法。
函数的调用先后顺序无法通过操作符的优先级确定。
4.
看看同样的代码产生了不同的结果,这是为什么?
简单看一下汇编代码.就可以分析清楚.
这段代码中的第一个 + 在执行的时候,第三个++是否执行,这个是不确定的,因为依靠操作符的优先级和结合性是无法决定第一个 + 和第三个前置 ++ 的先后顺序。
总结:
我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。