#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
操作符(也称运算符)分类:
算术操作符:
+
-
*
/
(备注:对于除法操作符来说,两边的操作数都是整数,执行的就是整数除法:两边至少有一端是浮点数,那么执行的就是小数除法)
int main()
{
int a = 10 / 3;
double b = 10.0 / 3;
printf("a=%d\n", a);
printf("b=%lf\n", b);
printf("b=%.1lf\n", b);//精确到小数点后一位
printf("b=%.2lf\n", b);//精确到小数点后两位
return 0;
}
%
(备注:计算的是整除之后的余数,两边的操作数都是整形)
移位操作符:
<< 左移操作符 >> 右移操作符 注:移位(移的是二进制位)操作符的操作数只能是整数。
整数的二进制表现形式:原码,反码与补码三种。
也可以说,数据在计算机中都是以二进制的形式存储的,计算机中的整数有三种表示方式,分别是原码,反码和补码。
其中,原码存放的是数据本身,且整型数据在内存中是以补码的形式存储的。
正数的原码,反码与补码是相同的。
负数的反码,补码需要计算:
原码:按照一个数的正负,直接写出它的二进制表示形式得到的就是原码。
反码:符号位不变(第一个数字),其他位取反(0->1 1->0),就是反码。
补码:反码+1。
已知补码求原码:
方法:(1)补码-1,取反(2)补码取反,+1
int main()
{
int a = 10;
//10的二进制位:第一个数字为0——正
//0000 0000 0000 0000 0000 0000 0000 0000 1010——000000000000000000000000000000001010——原码
//0000 0000 0000 0000 0000 0000 0000 0000 1010——000000000000000000000000000000001010——反码
//0000 0000 0000 0000 0000 0000 0000 0000 1010——000000000000000000000000000000001010——补码
//整数是int类型,占4个字节,即4·8 bit.
int b = -10;
//-10的二进制位:第一个数字为1——负
//1000 0000 0000 0000 0000 0000 0000 0000 1010——100000000000000000000000000000001010——原码
//1111 1111 1111 1111 1111 1111 1111 1111 0101——111111111111111111111111111111110101——反码
//1111 1111 1111 1111 1111 1111 1111 1111 0110——111111111111111111111111111111110110——补码
return 0;
}
这里为什么要介绍原码,反码,补码呢?
因为内存中存储的起始位置是补码的二进制,故在移位的都是补码。
<< 左移操作符
计算规则:左边丢弃,右边补0
正数:
int main()
{
int a = 10;//0000 0000 0000 0000 0000 0000 0000 0000 1010
a << 1;
int b = a << 1;
//[0000 0000 0000 0000 0000 0000 0000 0000 1010]
//左移一位:0[000 0000 0000 0000 0000 0000 0000 0000 1010 ]
//原空间中少了一位,用0补上:[000 0000 0000 0000 0000 0000 0000 0000 1010 0]
//即,000000000000000000000000000000010100
printf("%d\n", a);//a<<1的结果是移位之后的效果,但a是不变的。
printf("%d\n", b);//1*2^4+0*2^3+1*2^2+0*2^1+0*2^0=16+4=20。
return 0;
}//打印结果:10 20
负数:
int main()
{
int a = -10;//111111111111111111111111111111110110
int b = a << 1;
//[1111 1111 1111 1111 1111 1111 1111 1111 0110]
//左移一位:1[111 1111 1111 1111 1111 1111 1111 1111 0110 ]
//原空间中少了一位,用0补上:[111 1111 1111 1111 1111 1111 1111 1111 0110 0]
//即,b的补码:111111111111111111111111111111101100
//从而得到b的原码:100000000000000000000000000000010011+1,即100000000000000000000000000000010100
printf("%d\n", a);
printf("%d\n", b);//打印的是原码的值//1*2^4+0*2^3+2*2^2+0*2^1+0*2^0=-(16+4)=-20。
return 0;
}//打印结果:-10 -20
>>右移操作符
右移操作符——右移包括:算术右移与逻辑右移 (平常见到的都是算术右移)
算术右移:右边丢弃,左边补原来的符号位。
逻辑右移:右边丢弃,左边补0。
int main()
{
int a = -1;//-1的补码:11111111111111111111111111111111111
int b = a >> 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}//打印结果:-1 -1
位操作符
位操作符位操作符有:& 按位与; | 按位或; ^ 按位异或
注:他们的操作数必须是整数。同时,也是针对二进制位计算的。
& 按位与
运算规则:对应的二进制位:有0则为0;两个全是1才为1。
int main()
{
int a = 3;//a的补码:0000 0000 0000 0000 0000 0000 0000 0011
int b = -5;//b的原码:1000 0000 0000 0000 0000 0000 0000 0101
//b的反码:1111 1111 1111 1111 1111 1111 1111 1010
//b的补码:1111 1111 1111 1111 1111 1111 1111 1011
int c = a & b;//计算c的补码:0000 0000 0000 0000 0000 0000 0000 0011
//1111 1111 1111 1111 1111 1111 1111 1011
//运算规则:有0则为0;全是1才为1。得:
//c的补码:0000 0000 0000 0000 0000 0000 0000 0011——首数字为0,对应数字为正
//c的原码:0000 0000 0000 0000 0000 0000 0000 0011
printf("%d\n", c);
return 0;
}//打印结果:3
| 按位或
运算规则:对应的二进制位:有1则为1;两个全是0才为0。
int main()
{
int a = 3;//a的补码:0000 0000 0000 0000 0000 0000 0000 0011
int b = -5;//b的原码:1000 0000 0000 0000 0000 0000 0000 0101
//b的反码:1111 1111 1111 1111 1111 1111 1111 1010
//b的补码:1111 1111 1111 1111 1111 1111 1111 1011
int c = a | b;//计算c的补码:0000 0000 0000 0000 0000 0000 0000 0011
//1111 1111 1111 1111 1111 1111 1111 1011
//运算规则:有1则为1;全是0才为0。得:
//c的补码:1111 1111 1111 1111 1111 1111 1111 1011
//c的原码:1000 0000 0000 0000 0000 0000 0000 0101
printf("%d\n", c);
return 0;
}//打印结果:-5
^ 按位异或
运算规则:(一)常规运算法则:对应的二进制位:相同则为0;相异则为1。
(二)特殊异或运算:1.a^a=0;
2.0^a=a; 以0,3为例:000 011——>011,得:3
以0,-3为例,0:0000 0000 0000 0000 0000 0000 0000 0000
-3:1111 1111 1111 1111 1111 1111 1111 1101
补:1111 1111 1111 1111 1111 1111 1111 1101
原:1000 0000 0000 0000 0000 0000 0000 0011 得:-3
3.异或^:支持交换律 a^b^a=a^s^b=b
int main()
{
int a = 3;//a的补码:0000 0000 0000 0000 0000 0000 0000 0011
int b = -5;//b的原码:1000 0000 0000 0000 0000 0000 0000 0101
//b的反码:1111 1111 1111 1111 1111 1111 1111 1010
//b的补码:1111 1111 1111 1111 1111 1111 1111 1011
int c = a ^ b;//计算c的补码:0000 0000 0000 0000 0000 0000 0000 0011
//1111 1111 1111 1111 1111 1111 1111 1011
//运算规则:有1则为1;全是0才为0。得:
//c的补码:1111 1111 1111 1111 1111 1111 1111 1000
//c的原码:1000 0000 0000 0000 0000 0000 0000 1000
printf("%d\n", c);
return 0;
}//打印结果:-8
一道面试题:
不能创建临时变量(第三个变量),实现两个整数的交换
方法一:
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
printf("交换前:a = %d b = %d\n", a, b);
a = a + b;//a1=a0+b0
b = a - b;//b1=a1-b0=a0+b0-b0=a0
a = a - b;//a2=a1-b1=a0+b0-a0=b0
printf("交换后:a = %d b = %d\n", a, b);
return 0;
}//缺陷:当a,b为两个十分大的数字时,容易发生溢出现象。
方法二:
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
printf("交换前:a = %d b = %d\n", a, b);
a = a ^ b;//a1=a0^b0
b = a ^ b;//b1=a1^b0=a0^b0^b0=a0
a = a ^ b;//a2=a1^b1=a0^b0^a0=b0
printf("交换后:a = %d b = %d\n", a, b);
return 0;
}
总结:由上可知,异或操作符^不会产生进位,故也不会出现溢出现象。
异或在交换两个变量时的缺陷:1.可读性差;
2.效率不如使用临时变量的方法快;
3.只能针对操作数是整数的情况下使用。
所以:“异或虽好,可不要贪杯哦!”
赋值操作符
int main()
{
//int a = 10;
//int x = 0;
//int y = 20;
//a = x = y + 1;
//printf("%d\n", a);
int a = 10;
a += 5;
printf("a += %d\n", a);
a = 10;
a <<= 5;
printf("a <<= %d\n", a);
a = 10;
a >>= 5;
printf("a >>= %d\n", a);
a = 10;
a &= 5;
printf("a &= %d\n", a);
a = 10;
a |= 5;
printf("a |= %d\n", a);
a = 10;
a ^= 5;
printf("a ^= %d\n", a);
return 0;
}
单目操作符
以a+1为例,在这个式子中,操作符+有两个操作数:a,1,因此这里的+是双目操作符。
与之不同的,还有单目操作符,在运算式子中,只有一个操作数的操作符即称单目操作符。
! 逻辑反操作
例如:
int main()
{
int flag = 0;
if (!flag)
{
printf("PKU\n");
printf("flag = %d\n", flag);//打印结果:0
printf("!flag = %d\n", !flag);//打印结果:1(把假的结果取为真时,这时候取1)
}
return 0;
}//这里的!就是单目操作符,其被称为逻辑反操作。
补充:
布尔类型——用来表示真假的类型(并不是所有编译器都支持)
_Bool和bool都可以
例一:
#include<stdbool.h>//布尔类型_Bool库函数的头文件
int main()
{
_Bool flag = true;//布尔类型的取值有两种:1.true;2.false。
if (flag)
{
printf("PKU\n");
}
//_Bool flag = false;//注意:不能重定义,对同一个变量作多次初始化。
//if (!flag)
//{
// printf("PKU\n");
//}
_Bool PKU = false;
if (!PKU)
{
printf("flag\n");
}
return 0;
}
例二:打印1000~2000之间的闰年
#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 = 1000;
for (y = 1000; y <= 2000; y++)
{
if (is_leap_year(y))
printf("%d ", y);
}
return 0;
}
- 负值(正数变成负数,负数变成正数)
int main()
{
int a = -10;
printf("a=%d\n", a);
printf("-a=%d", -a);
return 0;
}
+ 正值
int main()
{
int a = -10;
printf("a=%d\n", a);
printf("+a=%d\n", +a);
return 0;
}
& 取地址
int main()
{
//对变量取地址
int a = 10;
printf("%p\n", &a);
int* pa = &a;
printf("%p\n", pa);
printf("%d\n", *pa);
printf("\n");
char ch = 'w';
char* pc = &ch;
printf("%c\n", *pc);
printf("\n");
//对数组元素取地址
char arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
char* p1 = &arr;
char* p2 = &arr[7];
printf("%p\n", *p1);
printf("%d\n",*p2);
printf("\n");
//对字符串常量取地址
char* p3 = "abcdefg";//常量产生的就是地址。
printf("%p\n", p3);//所以这里不用取地址。
printf("%c\n", *p3);
return 0;
}
&取地址与*解引用配合使用
* 间接访问操作符(解引用操作符)
int main()
{
int a = 10;
int* pa = &a;
*pa = 20;//解引用操作
printf("a=%d\n", a);
return 0;
}//打印结果:a=20
sizeof 操作数的类型长度(以字节为单位)
注意:函数调用时,要写()
但是sizeof后边的括号可以省略,说明sizeof不是函数。//sizeof后面是变量名时可以省略,sizeof后面是类型时不可以省略。
int main()
{
//int a = 0;
//printf("%d\n", sizeof(a));
//printf("%d\n", sizeof a);
//int tmp = a;
//printf("%d\n", sizeof tmp);
//int arr[10] = { 0 };
//printf("%d\n", sizeof arr);
//printf("%d\n", sizeof (arr));
//printf("\n");
//printf("%d\n", sizeof(int));//这样写:printf("%d\n", sizeof int);是错误的。
//printf("%d\n", sizeof(int[10]));
int a = 10;
short s = 5;
printf("%d\n", sizeof s);//short所占空间大小为2个字节
printf("%d\n", sizeof(s = a + 3));//sizeof内部的表达式不参与计算。//大的类型放到小的类型里时,会发生截断现象。
printf("%d\n", s);
return 0;
}//打印结果:2 2 5
~ 对一个整数的二进制按位取反(所有位)
int main()
{
//int a = 0;
//printf("%d\n", ~a);//0补码:0000 0000 0000 0000 0000 0000 0000 0000
// //~a补码:1111 1111 1111 1111 1111 1111 1111 1111
// //~a原码:1000 0000 0000 0000 0000 0000 0000 0001
// //~a=-1
int a = 9;//原码:00000000000000000000000000001001
//现在想把原码后五位:01001 改成 11001,请问该如何操作呢?
//能用指针吗?不能.指针最小能操作的是比特位,单位为字节;而现在要改的是一个bit,因此指针无法实现想要的结果。
//有本节前面所学内容可知:要对整数的二进制位进行修改可以用到移位操作符和位操作符来实现。
//又∵ 1对应的补码为:00000000000000000000000000000001
// 将其左移四位得: 00000000000000000000000000010000
//因此:再由位操作符 |或 运算:00000000000000000000000000001001
// 00000000000000000000000000010000
// 可得:00000000000000000000000000011001
//即:
a |= (1 << 4);
printf("%d\n", a);
//同理,要将a的二进制中的第5位改回去,变成0;也可用移位操作符及位操作符实现。即:
//∵ a的补码为: 00000000000000000000000000011001
//为了实现: 00000000000000000000000000001001
//则我们需要的补码为:11111111111111111111111111101001 或
// 11111111111111111111111111101111
//又我们很容易发现: 00000000000000000000000000010000 进行按位取反
//即可得: 11111111111111111111111111101111
//则:
a &= (~(1 << 4));
printf("%d\n", a);
return 0;
}//打印结果:-1
-- 前置、后置 --
(1)
int main()
{
int a = 10;
int b = --a;//前置:先执行--,再使用
//int b = a--;//后置:先使用,再执行--
printf("b=--a=%d\n", b);
printf("b=a--=%d\n", b);
return 0;
}//打印结果:b=--a=9;//b=a--=10;
(2)
int main()
{
int a = 10;
printf("%d\n", a--);//先打印,再--
printf("%d\n", a);
return 0;
}//打印结果:10,11
(3)
int main()
{
int a = 10;
printf("%d\n", --a);
printf("%d\n", a);
return 0;
}//打印结果:9 9
++ 前置、后置 ++
(1)
int main()
{
int a = 10;
int b = ++a;//前置:先执行++,再使用
//int b = a++;//后置:先使用,再执行++
printf("b=++a=%d\n", b);
//printf("b=a++=%d\n", b);
return 0;
}//打印结果:b=++a=11;//b=a++=10;
(2)
int main()
{
int a = 10;
printf("%d\n", a++);//先打印,再++
printf("%d\n", a);
return 0;
}//打印结果:10,11
(3)
int main()
{
int a = 10;
printf("%d\n", ++a);
printf("%d\n", a);
return 0;
}//打印结果:11 11
(类型) 强制类型转换
int main()
{
int a = (int)3.14;
int b = (int)3.94;//只取整数部分
printf("%d\n", a);
printf("%d\n", b);
return 0;
}//打印结果:3 3
sizeof 和 数组
前情回顾:
int main()
{
printf("%zu\n", sizeof(char));//1
printf("%zu\n", sizeof(short));//2
printf("%zu\n", sizeof(int));//4
printf("%zu\n", sizeof(long));//4
printf("%zu\n", sizeof(long long));//8
printf("%zu\n", sizeof(float));//4
printf("%zu\n", sizeof(double));//8
return 0;
}
练习:
#include <stdio.h>
void test1(int arr[])//数组传参:形参可以写成数组,也可以写成指针:因为函数实参部分是数组名——首元素地址,故即使写成数组,本质上也是指针。指针在86(32)位中所占空间大小位4个字节,在64位中所占空间大小位8个字节。
{
printf("%zd\n", sizeof(arr));//(2)
}
void test2(char ch[])//因为前面也是传的数组名ch,所以这里的ch也是指针。
{
printf("%zd\n", sizeof(ch));//(4)
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%zd\n", sizeof(arr));//(1)
//sizeof返回的是无符号的整形:故这里不能用%d,而应该用%zd或%du(无符号整形专用)
printf("%zd\n", sizeof(ch));//(3)
test1(arr);
test2(ch);
return 0;
}
问:
(1)、(2)两个地方分别输出多少?(1)打印的是数组所占空间大小:40;(2)打印的是指针所占空间大小:4或8;
(3)、(4)两个地方分别输出多少?(3)打印的是字符串所占空间大小:10;(4)打印的指针是所占空间大小:4或8;
关系操作符
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
逻辑操作符
逻辑操作符有哪些:
&& 逻辑与
|| 逻辑或
区分逻辑与和按位与
区分逻辑或和按位或
1 & 2----->0
1 && 2---- >1
1 | 2----->3
1 || 2---- >1
逻辑与和或的特点:
&& ||
真&&真——>真 真||真——>真
真&&假——>假 真||假——>真
假&&假——>假 假||假——>假
360笔试题:
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
//i = a++ && ++b && d++;//&&操作符:左边为假,右边无需计算。//结果为:1 2 3 4
i = a++||++b||d++;//||操作符:左边为真,右边无需计算。//结果为:1 3 3 4
printf(" a = %d\n b = %d\n c = %d\n d = %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++;//结果为:2 3 3 5
//i = a++ || ++b || d++;//结果为:2 2 3 4
printf(" a = %d\n b = %d\n c = %d\n d = %d\n", a, b, c, d);
return 0;
}
条件操作符
exp1 ? exp2 : exp3
求一个数的较大值
int main()
{
int a = 10;
int b = 20;
int m = 0;
/*if (a > b)
m = a;
else
m = b;*/
m = (a > b ? a : b);
printf("%d\n", m);
return 0;
}
练习1:
1.
if (a > 5)
b = 3;
else
b = -3;
转换成条件表达式,是什么样?
2.使用条件表达式实现找两个数中较大值
int main()
{
int a = 0;
int b = (a > 5 ? 3 : -3);
printf("%d\n",b);
return 0;
}
练习2:
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int c = (a > b ? b : a);
printf("c=%d\n", c);
return 0;
}
逗号表达式
exp1, exp2, exp3, …expN
逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
int main()
{
//代码1
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);//逗号表达式:从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
//c是多少?
printf("c=%d\n", c);
//代码2
int d = 1;
d--;
if (a = b + 1, c = a / 2, d > 0)
//真正起判断作用的还是最后一个表达式,只有在前面存在影响d的值的时候,才会影响判断条件
printf("hehe\n");
else
printf("haha\n");
//代码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)
{
//业务处理
}//由此可见逗号表达式在进行判断时,其规则为:先判断,再执行
//但与while循环不同的是,do…while它是先执行再判断,因此do…while并不适用逗号表达式。
return 0;
}
下标引用、函数调用和结构成员
1.[] 下标引用操作符
操作数:一个数组名 + 一个索引值
int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
[] 的两个操作数是arr和9。
int main()
{
int arr[] = { 1,2,3,4,5 };
printf("%d\n", arr[4]);//[]——下标引用操作符,其操作数为arr和4,
return 0;
}
2. () 函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
示例1:
#include <stdio.h>
void test1()
{
printf("hehe\n");
}
void test2(const char* str)
{
printf("%s\n", str);
}
int main()
{
test1(); //实用()作为函数调用操作符。
test2("hello bit.");//实用()作为函数调用操作符。
return 0;
}
示例2:
#include<string.h>
int main()
{
int len = strlen("abcdef");//()——函数调用操作符,操作数为strlen和"abcdef"。
//当调用函数没有参数时,调用函数也必须存在。即说明函数调用操作符至少有一个操作数。
printf("%d\n", len);
return 0;
}
3. 访问一个结构的成员
回顾以前我们学过的类型有:char,int,short,long,long long,float,double,这些类型我们统称为:内置类型。
除了内置类型以外,实际上还有自定义类型(也称聚合数据类型):结构体、枚举、联合体。
结构体
生活中有些对象要被描述的话,不能简单的使用单个内置类型。
譬如:水果:具体水果名 产地 定价 编码……而我们把C语言中类似这一类多个简单的类型聚合在一起的数据类型称为结构体。
其关键字为:struct
. 结构体.成员名
(*). (变量*p).成员名
-> 结构体指针->成员名
示例1:
#include <stdio.h>
struct Stu
{
char name[10];
int age;
char sex[5];
double score;
};
void set_age1(struct Stu stu)//形参是实参的一份临时拷贝
{
stu.age = 18;//尝试修改stu.age
}
void set_age2(struct Stu* pStu)
{
pStu->age = 18;//结构成员访问
}
int main()
{
struct Stu stu;
struct Stu* pStu = &stu;//结构成员访问
stu.age = 20;//结构成员访问
set_age1(stu);
printf("%d\n", stu.age);//打印结果:20
pStu->age = 20;//结构成员访问
set_age2(pStu);
printf("%d\n", stu.age);//打印结果:18
return 0;
}
示例2:
struct Fruit
{
char fruit_name[20];
char production_area[30];
char price[20];
};
void print1(struct Fruit* p)
{
printf("荔枝:%s %s %s\n", (*p).fruit_name, (*p).production_area, (*p).price);
//(变量:*p) . 成员名
printf("你让我拿什么荔枝啊:%s %s %s\n", p->fruit_name, p->production_area, p->price);
//结构体指针 -> 成员名
}
int main()
{
struct Fruit f1 = { "荔枝","广西","10¥/kg"};
struct Fruit f2 = { "苹果","甘肃","7¥/kg" };
printf("%s %s %s\n", f1.fruit_name, f1.production_area,f1.price);
printf("%s %s %s\n", f2.fruit_name, f2.production_area,f2.price);
//结构体变量(f1/f2) . 成员名(fruit_name/production_area/price)
print1(&f1);
return 0;
}
表达式求值
隐式类型转换
整型提升
为了获得这个精度,表达式中的字符(char;1个字节)和短整型(short:2个字节)操作数在使用之前被转换为普通整型(int:4个字节),这种转换称为整型提升。
由此可知,整型提升针对的是所占空间比整形小的类型。
如何进行整体提升呢?
整形提升是按照变量的数据类型的符号位来提升的
负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
无符号整形提升,高位补0
(1)
int main()
{
//在VS编译器中:char其实是有符号的char,等价于signed char
char a = 3;
//截断:所占空间大的类型强制放入所占空间小的类型里的过程,成为截断。
//3的二进制序列: 00000000000000000000000000000011
//放入字符a中: 00000011
char b = 127;
//127的二进制序列: 00000000000000000000000001111111
//放入字符b中: 01111111
char c = a + b;
//使用之前,整形提升
//因为现在a的符号位为0,所以高位补符号位得:
// 00000000000000000000000000000011
//因为现在b的符号位为0,所以高位补符号位得:
// 00000000000000000000000001111111
//提升完成。
//a+b: 00000000000000000000000000000011
// 00000000000000000000000001111111
//得: 00000000000000000000000010000010
//截断(只能放8个比特位),得c: 10000010
printf("%d\n", c);
//打印(以%d形式打印十进制的整数):
//因为,c是char类型变量,故此时打印时,也需要整型提升。
//提升(高位补符号位): 10000010
//得: 11111111111111111111111110000010——提升后的值是内存里的值,为补码。
//变换成原码: 10000000000000000000000001111110——打印的是原码。
return 0;
}//打印结果:-126
(2)
int main()
{
char a = 3;
char b = 127;
unsigned char c = a + b;
printf("%u\n", c);
return 0;
}//打印结果:130
(3)
int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if (a == 0xb6)
printf("a");
if (b == 0xb600)
printf("b");
if (c == 0xb6000000)
printf("c");
return 0;
}
(3)中的a, b要进行整形提升, 但是c不需要整形提升
a, b整形提升之后, 变成了负数, 所以表达式 a == 0xb6, b == 0xb600 的结果是假, 但是c不发生整形提升, 则表达式 c == 0xb6000000 的结果是真.
程序所输出的结果是:c
(4)
int main()
{
char c = 1;
//sizeof是在编译期间就进行运算的,只判断表达式运算之后最终的类型大小,不会真正执行这个表达式
printf("%u\n", sizeof(c));//1
printf("%u\n", sizeof(+c));//4
printf("%u\n", sizeof(-c));//4
printf("%u\n", sizeof(!c));//1//这里!c逻辑运算,会整形提升的,但是会做一些优化,这里才是1,是优化的结果
//printf("!c=%u\n", !c);
printf("\n");
printf("%u\n", sizeof(c++));//1 //因为自增是会把整形提升的值变成 char 格式的,进行了整形提 升。但自增自减最后还是原本的类型,其原因是变量给自己自增自减之后,这 个表达式的值还是自己,就是说这个c的类型是不会变的,即char自增之后不会变成int。
printf("%u\n", sizeof(c--));//1
printf("%u\n", sizeof(++c));//1
printf("%u\n", sizeof(--c));//1
printf("\n");
printf("%u\n", sizeof(c+1));//char+int//4
printf("%u\n", sizeof(c-1));//4
return 0;
}
(4)中的,c只要参与表达式运算,就会发生整形提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节.
表达式 - c 也会发生整形提升, 所以 sizeof(-c) 是4个字节, 但是 sizeof(c), 就是1个字节.
算术转换
(所占空间≥int类型)
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
(从下往上转换——"就高不就低")
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
警告:但是算术转换要合理,要不然会有一些潜在的问题。
float f = 3.14;
int num = f;//float转换int,不符合"就高不就低"//隐式转换,会有精度丢失
操作符的属性
(本节内容结合讲义观看)
复杂表达式的求值有三个影响的因素。
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
操作符优先级
1.从上自下——>优先级由高到低
2.相邻操作符才考虑优先级
优先级相同情况下,结合性才起作用。
int main()
{
int a = 1;
int b = 2;
int c = 4;
int d = a * 4 + b / 3 + c;
return 0;
}//问题代码