目录
一.移位操作符
二.位操作符
三.sizeof操作符
四.结构体
五.表达式求值
1) 整型提升
2)算术转换(隐式转换)
3)操作符的属性
一.移位操作符
1.整数的二进制表示有三种形式:
原码,反码,补码
2.正数的原码,反码,补码是相同的
负数的原码,反码,补码是要计算的
3.整数在内存中存储的是补码的二进制
4. a:5的补码00000000000000000000000000000101
将该补码向左移动一位,左边丢去一位,右边补上一位0
打印为补码
5.a:-5的原码10000000000000000000000000000101
-5的反码111111111111111111111111111111111010
补码111111111111111111111111111111111011
<<1后的补码变为111111111111111111111111111111110110
反码111111111111111111111111111111110101
打印出的值为原码 10000000000000000000000000001010
6.原码,反码与补码
7.右移
a:5的补码00000000000000000000000000000101
右移操作符:
(1)算术右移:右边丢弃,左边补原符号位
(2)逻辑右移:右边丢弃,左边补0
a:-5的原码10000000000000000000000000000101
-5的反码111111111111111111111111111111111010
补码111111111111111111111111111111111011
>>1后的补码变为111111111111111111111111111111111101
反码111111111111111111111111111111111100
打印出的值原码 10000000000000000000000000000011
>> 当前编译器,在右移采用算术右移
>>到底算术右移还是逻辑右移取决于编译器
8.警告
对于移位操作符,不要移动负数,这个是标准未定义的
二.位操作符
&
按二进制位与
如果两个相应的二进制位都为1,则该位的结果值为1,否则为0
|
按二进制位或
两个相应的二进制位中只要有一个为1,该位的结果值为1
^
按二进制位异或
若参加运算的两个二进制位值相同则为0,否则为1
a^a=0 0^a=a
~
按二进制取反
~是一元运算符,用来对一个二进制的补码数按位取反,即将0变1,将1
&
按二进制位与
如果两个相应的二进制位都为1,则该位的结果值为1,否则为0
|
按二进制位或
两个相应的二进制位中只要有一个为1,该位的结果值为1
^
按二进制位异或
若参加运算的两个二进制位值相同则为0,否则为1
a^a=0 0^a=a
~
按二进制取反
~是一元运算符,用来对一个二进制的补码数按位取反,即将0变1,将1
1.为什么while (~scanf("%d",&n));可以终止循环?
while (scanf("%d", &n) != EOF)
{
.....
}
scanf()读取失败的时候,返回EOF
EOF---> -1
~(-1)=0
2.交换两个数的值 (仅整数可以使用)
a = a ^ b; b = a ^ b ^ b; a = a ^ b ^ a;
三.sizeof操作符
1.sizeof不是函数
2.sizeof求数组,求的是数组元素的类型*元素个数
int main()
{
int arr[10] = { 0 };
printf("%dn", sizeof(arr));
return 0;
}
3.sizeof()内表达式不参与计算
int main()
{
int a=10;
short s=0;
printf("%dn", sizeof(s=a+2));
printf("%dn", sizeof(s));
return 0;
}
2 0
test.c--->编译(求sizeof的结果,所以编译期间sizeof(s=a+2)变成了2)--->链接(s=a+2不存在了) --->test.exe
4.sizeof和函数
字符的地址仍然是48个字节,地址总是48个字节
void test1(int arr[])
{
printf("%dn", sizeof(arr));
}
void test2(int ch[])
{
printf("%dn", sizeof(ch));
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%dn", sizeof(arr));
printf("%dn", sizeof(ch));
test1(arr);
test2(ch);
return 0;
}
40 10 4 4
四.结构体
struct Stu
{
char name[20];
int age;
float score;
};
void print1(struct Stu ss)
{
printf("%s %d %fn", ss.name, ss.age, ss.score);
}
结构体变量.成员名
void print2(struct Stu* ps)
{
printf("%s %d %fn", (*ps).name, (*ps).age, (*ps).score);
printf("%s %d %fn", ps->name, ps->age, ps->score);
}
结构体指针->成员名
int main()
{
struct Stu s = {"张三", 20, 90.5f};
strcpy(s.name, "张三丰");
scanf("%s", s.name);
print1(s);
print2(&s);
return 0;
}
*(s.name) = "张三丰"// err
s.name首字符的地址,解地址后只有一个字符
五.表达式求值
1) 整型提升
操作方式:C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升
1.正数:高位补1 2.负数:高位补0 3.无符号数:高位补0
意义:表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算
int main()
{
char c1 = 3;
//00000000000000000000000000000011
//00000011 - c1
char c2 = 127;
//00000000000000000000000001111111
//01111111 - c2
char c3 = c1 + c2;
//00000000000000000000000000000011 - c1
//00000000000000000000000001111111 - c2
//00000000000000000000000010000010 - c3
//10000010 - c3
//11111111111111111111111110000010 (-1)
//11111111111111111111111110000001
//10000000000000000000000001111110
//-126
printf("%dn", c3);
return 0;
}
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;
}
打印:c //a和b发生了整型提升
int main()
{
char c = 1;
printf("%un", sizeof(c));//1
printf("%un", sizeof(+c));//4
printf("%un", sizeof(-c));//4
return 0;
}
//在sizeof里发生运算,发生了整型提升后,就变为整型
2)算术转换(隐式转换)
定义:如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。
看数据取值范围,最终是表达式中取值范围最大的那个类型
*(s.name) = "张三丰"// err
s.name首字符的地址,解地址后只有一个字符
1) 整型提升
操作方式:C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升
1.正数:高位补1 2.负数:高位补0 3.无符号数:高位补0
意义:表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算
int main()
{
char c1 = 3;
//00000000000000000000000000000011
//00000011 - c1
char c2 = 127;
//00000000000000000000000001111111
//01111111 - c2
char c3 = c1 + c2;
//00000000000000000000000000000011 - c1
//00000000000000000000000001111111 - c2
//00000000000000000000000010000010 - c3
//10000010 - c3
//11111111111111111111111110000010 (-1)
//11111111111111111111111110000001
//10000000000000000000000001111110
//-126
printf("%dn", c3);
return 0;
}
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;
}
打印:c //a和b发生了整型提升
int main()
{
char c = 1;
printf("%un", sizeof(c));//1
printf("%un", sizeof(+c));//4
printf("%un", sizeof(-c));//4
return 0;
}
//在sizeof里发生运算,发生了整型提升后,就变为整型
2)算术转换(隐式转换)
定义:如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。
看数据取值范围,最终是表达式中取值范围最大的那个类型
操作方式:C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升
1.正数:高位补1 2.负数:高位补0 3.无符号数:高位补0
意义:表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算
定义:如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。
看数据取值范围,最终是表达式中取值范围最大的那个类型
3)操作符的属性
复杂表达式的求值有三个影响的因素。
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序。
复杂表达式的求值有三个影响的因素。
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序。
从上到下优先级降低
结合型N/A: 无结合性 L-R : 从左向右
一些问题表达式
a*b+c*d+e*f (错误代码,无法确定唯一计算路径)
注释:代码1在计算的时候,由于*比+的优先级高,只能保证,*的计算是比+早,但是优先级并不能决定三个*哪一个比第一个+早执行。
c+--c (错误)注释:同上,操作符的优先级只能决定自减--的运算在+的运算的前面,但是我们并没有办法得知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。
总结:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的



