目录
一.操作符的分类:
1.算术操作符
2.移位操作符
3.位操作符
4.赋值操作符
5.单目操作符
6.关系操作符
7.逻辑操作符
8.条件操作符
9.逗号表达式
10.下标引用、函数调用和结构成员
二.表达式求值
1.隐式类型转换
2.算术转换
3.操作符的属性
4.一些问题表达式
一.操作符的分类:
1.算术操作符
2.移位操作符
3.位操作符
4.赋值操作符
5.单目操作符
6.关系操作符
7.逻辑操作符
8.条件操作符
9.逗号表达式
10.下标引用、函数调用和结构成员
1.算术操作符
+ - * / %
%(取余) 结果是余数。
%操作符的两个操作数必须是整数,则最后返回的数是余数。
其余的操作符可以是整数也可以是浮点数。
对应 / 操作符如果两个都是整数,则执行整数除法。
2.移位操作符
<<
左移操作符
>>
右移操作符
这个移位是指在二进制的基础上进行的。
而整数的内存的存储是补码的二进制数
如:
int a=5;
这个5是十进制数,转化二进制就是101,又因为a是整数,占4个字节,32个bit位,则:
原码:00000000 00000000 00000000 00000101
反码:00000000 00000000 00000000 00000101
补码:00000000 00000000 00000000 00000101
(正整数的原码,反码,补码都是一致的)
若:
int a=-5;
原码:10000000 00000000 00000000 00000101(把5的原码首位改成1)
反码:111111111 111111111 111111111 11111010(-5原码首位不变,其余的取反)
补码:111111111 111111111 111111111 11111011 (在反码的基础上+1)
(负数的原码,反码,补码各不相同)
(原码,补码,反码的首位称为符号位 ,符号位为0 -代表正数,1-代表负数)
int a = 5;
int b <<1 ;
b的值是a的补码向左移动一格,即:左边去掉一个数,右边补一个0;
b的补码:00000000 00000000 00000000 00001010
转化二进制数就是10
同理:
int a = 5 ;
int c >> 1 ;
c的值是a的补码向右移动一格,即:右边去掉一个数,左边补一个0;
c的补码:00000000 00000000 00000000 00000010
转化二级制就是2
从算术上说:左/右边丢弃,右/左边补原符号位
从逻辑上说:左/右边丢弃,右/左边补0
移位不会改变操作数的本身数值
注:移位操作符的操作数只能是整数。
对应于移位操作符,不要移动负正数。
+ - * / %
%(取余) 结果是余数。
%操作符的两个操作数必须是整数,则最后返回的数是余数。
其余的操作符可以是整数也可以是浮点数。
对应 / 操作符如果两个都是整数,则执行整数除法。
2.移位操作符
<<
左移操作符
>>
右移操作符
这个移位是指在二进制的基础上进行的。
而整数的内存的存储是补码的二进制数
如:
int a=5;
这个5是十进制数,转化二进制就是101,又因为a是整数,占4个字节,32个bit位,则:
原码:00000000 00000000 00000000 00000101
反码:00000000 00000000 00000000 00000101
补码:00000000 00000000 00000000 00000101
(正整数的原码,反码,补码都是一致的)
若:
int a=-5;
原码:10000000 00000000 00000000 00000101(把5的原码首位改成1)
反码:111111111 111111111 111111111 11111010(-5原码首位不变,其余的取反)
补码:111111111 111111111 111111111 11111011 (在反码的基础上+1)
(负数的原码,反码,补码各不相同)
(原码,补码,反码的首位称为符号位 ,符号位为0 -代表正数,1-代表负数)
int a = 5;
int b <<1 ;
b的值是a的补码向左移动一格,即:左边去掉一个数,右边补一个0;
b的补码:00000000 00000000 00000000 00001010
转化二进制数就是10
同理:
int a = 5 ;
int c >> 1 ;
c的值是a的补码向右移动一格,即:右边去掉一个数,左边补一个0;
c的补码:00000000 00000000 00000000 00000010
转化二级制就是2
从算术上说:左/右边丢弃,右/左边补原符号位
从逻辑上说:左/右边丢弃,右/左边补0
移位不会改变操作数的本身数值
注:移位操作符的操作数只能是整数。
对应于移位操作符,不要移动负正数。
如:
int a=5;
这个5是十进制数,转化二进制就是101,又因为a是整数,占4个字节,32个bit位,则:
原码:00000000 00000000 00000000 00000101
反码:00000000 00000000 00000000 00000101
补码:00000000 00000000 00000000 00000101
(正整数的原码,反码,补码都是一致的)
若:
int a=-5;
原码:10000000 00000000 00000000 00000101(把5的原码首位改成1)
反码:111111111 111111111 111111111 11111010(-5原码首位不变,其余的取反)
补码:111111111 111111111 111111111 11111011 (在反码的基础上+1)
(负数的原码,反码,补码各不相同)
(原码,补码,反码的首位称为符号位 ,符号位为0 -代表正数,1-代表负数)
int a = 5;
int b <<1 ;
b的值是a的补码向左移动一格,即:左边去掉一个数,右边补一个0;
b的补码:00000000 00000000 00000000 00001010
转化二进制数就是10
同理:
int a = 5 ;
int c >> 1 ;
c的值是a的补码向右移动一格,即:右边去掉一个数,左边补一个0;
c的补码:00000000 00000000 00000000 00000010
转化二级制就是2
从算术上说:左/右边丢弃,右/左边补原符号位
从逻辑上说:左/右边丢弃,右/左边补0
移位不会改变操作数的本身数值
#includeint main() { int a = 5; int b = a << 1; int c = a >> 1; printf("%dn", a); // 5 printf("%dn", b); //10 printf("%dn", c); //2 return 0; }
3.位操作符
& //按(二进制)位与
| //按(二进制)位或
^ //按(二进制)位异或
#include
int main()
{
int a = 3;
int b =-5;
int c = a & b;
printf("%dn", c);
return 0;
}
先分别求出a和b的补码,&是与,二级制数一一对应,两个同为1则为1
先分别求出a和b的补码,|是或,二级制数一一对应,两者其中为1则为1
先分别求出a和b的补码,^是异或,二级制数一一对应,两者相同则为0,相异为1
不能创建临时变量(第三个变量),实现两个数的交换。
#include
int main()
{
int a = 3;
int b = 5;
printf("%d %dn", a, b);
//这是有第三个变量的
//int tmp = a;
//a = b;
//b = tmp;
//printf("%d %dn", a, b);
//这是没有第三方变量
//a = a + b; //把a和b的和赋值给a
//b = a - b; //此时a=8;b=a-b;b=3
//a = a - b; //此时a=8,b=3;a=a-b;a=5
//printf("%d %dn", a, b);
//但这种有缺陷,当a,b的数值过大,导致溢出,就会出现问题
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("%d %dn", a, b);
//a=3; 二进制是011
//b=5; 二级制是101
//进行异或后就成立了
return 0;
}
注意:异或操作符只能运用整数里。
4.赋值操作符
= 赋值操作符可以连续使用,从右向左赋值,但不能在定义变量时对变量进行连续赋值。
复合赋值:
-
+=
-
-=
-
*=
-
/=
-
%=
-
>>=
-
<<=
-
&=
-
|=
-
^=
5.单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
//*&a==>a
sizeof对于变量名可以省略后面的括号,但是对于数据类型不能省。
sizeof括号中放的表达式不参与运算。
EOF-->-1,则~EOF是0
前置++是先++再使用
后置++是先使用再++
先分别求出a和b的补码,&是与,二级制数一一对应,两个同为1则为1
先分别求出a和b的补码,|是或,二级制数一一对应,两者其中为1则为1
先分别求出a和b的补码,^是异或,二级制数一一对应,两者相同则为0,相异为1
不能创建临时变量(第三个变量),实现两个数的交换。
#includeint main() { int a = 3; int b = 5; printf("%d %dn", a, b); //这是有第三个变量的 //int tmp = a; //a = b; //b = tmp; //printf("%d %dn", a, b); //这是没有第三方变量 //a = a + b; //把a和b的和赋值给a //b = a - b; //此时a=8;b=a-b;b=3 //a = a - b; //此时a=8,b=3;a=a-b;a=5 //printf("%d %dn", a, b); //但这种有缺陷,当a,b的数值过大,导致溢出,就会出现问题 a = a ^ b; b = a ^ b; a = a ^ b; printf("%d %dn", a, b); //a=3; 二进制是011 //b=5; 二级制是101 //进行异或后就成立了 return 0; }
注意:异或操作符只能运用整数里。
= 赋值操作符可以连续使用,从右向左赋值,但不能在定义变量时对变量进行连续赋值。
复合赋值:
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
5.单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
//*&a==>a
sizeof对于变量名可以省略后面的括号,但是对于数据类型不能省。
sizeof括号中放的表达式不参与运算。
EOF-->-1,则~EOF是0
前置++是先++再使用
后置++是先使用再++
sizeof对于变量名可以省略后面的括号,但是对于数据类型不能省。
sizeof括号中放的表达式不参与运算。
EOF-->-1,则~EOF是0
前置++是先++再使用
后置++是先使用再++
6.关系操作符
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等
7.逻辑操作符
&& 逻辑与
|| 逻辑或
逻辑与,逻辑或只用来关心真假,按位与,按位或是用来二进制的计算。
逻辑与是两者都为真才行,逻辑或是有一个成立就可以。
#include
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
printf("a = %dn b = %dn c = %dnd = %dn", a, b, c, d);
return 0;
}
逻辑与从左往右看,a=0;a++是后置,先使用,a++是0,所以为假,后面的不用看,最后打印出a=1,b=2,c=3,d=4。
8.条件操作符
exp1 ? exp2 : exp3
exp1如果为真,exp2执行
exp1如果为假,exp3执行
9.逗号表达式
exp1
,
exp2
,
exp3
,
…expN
逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
10.下标引用、函数调用和结构成员
1. [ ]
下标引用操作符
操作数:一个数组名
+
一个索引值
int arr[10];
arr[9] = 10;
2. ( )
函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
int add(int x,int y)
{
return x+y;
}
int main()
{
int c=add(2,3);
printf("%d",c);
return 0;
}
3.
访问一个结构的成员
结构体
.
成员名
结构体指针
->
成员名
#include
struct stu
{
char name[20];
char id[20];
int age;
};
int main()
{
struct stu b = {"xiaohong","C202115311103",55};
printf("%s %s %d",b.name,b.id,b.age);
struct stu * pb=&b;
printf("%s %s %d",(*pb).name,(*pb).id,(*pb).age);
//printf("%s %s %d", pb->name, pb->id, pb->age);
}
二.表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
1.隐式类型转换
C
的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要在
CPU
的相应运算器件内执行,
CPU
内整型运算器
(ALU)
的操作数的字节长度
一般就是int的字节长度
,同时也是
CPU
的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用
CPU
(
general-purpose CPU
)是难以直接实现两个
8
比特字节直接相加运算(虽然机器指令 中可能有这种字节相加指令)。所以,
表达式中各种长度可能小于int长度的整型值,都必须先转 换为int或unsigned int,
然后才能送入
CPU
去执行运算。
&& 逻辑与 || 逻辑或
逻辑与,逻辑或只用来关心真假,按位与,按位或是用来二进制的计算。
逻辑与是两者都为真才行,逻辑或是有一个成立就可以。
#includeint main() { int i = 0, a = 0, b = 2, c = 3, d = 4; i = a++ && ++b && d++; printf("a = %dn b = %dn c = %dnd = %dn", a, b, c, d); return 0; } 逻辑与从左往右看,a=0;a++是后置,先使用,a++是0,所以为假,后面的不用看,最后打印出a=1,b=2,c=3,d=4。
8.条件操作符
exp1 ? exp2 : exp3
exp1如果为真,exp2执行
exp1如果为假,exp3执行
9.逗号表达式
exp1
,
exp2
,
exp3
,
…expN
逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
10.下标引用、函数调用和结构成员
1. [ ]
下标引用操作符
操作数:一个数组名
+
一个索引值
int arr[10];
arr[9] = 10;
2. ( )
函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
int add(int x,int y)
{
return x+y;
}
int main()
{
int c=add(2,3);
printf("%d",c);
return 0;
}
3.
访问一个结构的成员
结构体
.
成员名
结构体指针
->
成员名
#include
struct stu
{
char name[20];
char id[20];
int age;
};
int main()
{
struct stu b = {"xiaohong","C202115311103",55};
printf("%s %s %d",b.name,b.id,b.age);
struct stu * pb=&b;
printf("%s %s %d",(*pb).name,(*pb).id,(*pb).age);
//printf("%s %s %d", pb->name, pb->id, pb->age);
}
二.表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
1.隐式类型转换
C
的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要在
CPU
的相应运算器件内执行,
CPU
内整型运算器
(ALU)
的操作数的字节长度
一般就是int的字节长度
,同时也是
CPU
的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用
CPU
(
general-purpose CPU
)是难以直接实现两个
8
比特字节直接相加运算(虽然机器指令 中可能有这种字节相加指令)。所以,
表达式中各种长度可能小于int长度的整型值,都必须先转 换为int或unsigned int,
然后才能送入
CPU
去执行运算。
exp1 ? exp2 : exp3
exp1如果为真,exp2执行
exp1如果为假,exp3执行
exp1 , exp2 , exp3 , …expN
逗号表达式,就是用逗号隔开的多个表达式。 逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
10.下标引用、函数调用和结构成员
1. [ ]
下标引用操作符
操作数:一个数组名
+
一个索引值
int arr[10];
arr[9] = 10;
2. ( )
函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
int add(int x,int y)
{
return x+y;
}
int main()
{
int c=add(2,3);
printf("%d",c);
return 0;
}
3.
访问一个结构的成员
结构体
.
成员名
结构体指针
->
成员名
#include
struct stu
{
char name[20];
char id[20];
int age;
};
int main()
{
struct stu b = {"xiaohong","C202115311103",55};
printf("%s %s %d",b.name,b.id,b.age);
struct stu * pb=&b;
printf("%s %s %d",(*pb).name,(*pb).id,(*pb).age);
//printf("%s %s %d", pb->name, pb->id, pb->age);
}
二.表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
1.隐式类型转换
C
的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要在
CPU
的相应运算器件内执行,
CPU
内整型运算器
(ALU)
的操作数的字节长度
一般就是int的字节长度
,同时也是
CPU
的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用
CPU
(
general-purpose CPU
)是难以直接实现两个
8
比特字节直接相加运算(虽然机器指令 中可能有这种字节相加指令)。所以,
表达式中各种长度可能小于int长度的整型值,都必须先转 换为int或unsigned int,
然后才能送入
CPU
去执行运算。
表达式求值的顺序一部分是由操作符的优先级和结合性决定。 同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
1.隐式类型转换
C
的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要在
CPU
的相应运算器件内执行,
CPU
内整型运算器
(ALU)
的操作数的字节长度
一般就是int的字节长度
,同时也是
CPU
的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用
CPU
(
general-purpose CPU
)是难以直接实现两个
8
比特字节直接相加运算(虽然机器指令 中可能有这种字节相加指令)。所以,
表达式中各种长度可能小于int长度的整型值,都必须先转 换为int或unsigned int,
然后才能送入
CPU
去执行运算。
怎么整型提升呢?
// 负数的整形提升 char c1 = - 1 ; 变量 c1 的二进制位 ( 补码 ) 中只有 8 个比特位: 1111111 因为 char 为有符号的 char 所以整形提升的时候,高位补充符号位,即为 1 提升之后的结果是: 11111111111111111111111111111111 // 正数的整形提升 char c2 = 1 ; 变量 c2 的二进制位 ( 补码 ) 中只有 8 个比特位: 00000001 因为 char 为有符号的 char 所以整形提升的时候,高位补充符号位,即为 0 提升之后的结果是: 00000000000000000000000000000001 // 无符号整形提升,高位补 0
#includeint main() { char a = 3; //3的二进制补码是00000000000000000000000000000011 //提升后00000011 char b = 127; //127的二进制补码00000000000000000000000001111111 //提升后01111111 char c = a + b; //00000000000000000000000000000011 //00000000000000000000000001111111 //00000000000000000000000010000010 //c提升后10000010 //因为 char 为有符号的 char所以整形提升的时候,高位补充符号位,即为1提升之后的结果是: //11111111111111111111111110000010 //11111111111111111111111110000001 //10000000000000000000000001111110 //即-126 printf("%dn", c); return 0; }
2.算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类
型,否则操作就无法进行。下面的层次体系称为
寻常算术转换
。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
注:但是算术转换要合理,要不然会有一些潜在的问题。
3.操作符的属性
1.
操作符的优先级
2.
操作符的结合性
3.
是否控制求值顺序。
*两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
1. 操作符的优先级 2. 操作符的结合性 3. 是否控制求值顺序。 *两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
优先级: (优先级从上到下依次变小)
| 操作 符 | 名称 | 用法示例 | 结合 性 | 是否控制求值 顺序 |
|---|---|---|---|---|
| () | 聚组 | (表达式) | 无 | 否 |
| () | 函数调用 | rexp ( rexp , ...,rexp ) | 从左到右 | |
| [ ] | 下标引用 | rexp[rexp] | ||
| . | 访问结构成员 | lexp.member_name | ||
| -> | 访问结构指针成员 | rexp->member_name | ||
| ++ | 后缀自增 | lexp ++ | ||
| -- | 后缀自减 | lexp -- | ||
| ! | 逻辑反 | ! rexp | 从右向左 | |
| ~ | 按位取反 | ~ rexp | ||
| + | 单目,表示正值 | + rexp | ||
| - | 单目,表示负值 | - rexp | ||
| ++ | 前缀自增 | ++ lexp | ||
| -- | 前缀自减 | -- lexp | ||
| * | 间接访问 | * rexp | ||
| & | 取地址 | &lexp | ||
| sizeof | 取其长度,以字节表示 | sizeof rexp sizeof() | ||
| (类 型) | ( 类型) | (类型 ) rexp | ||
| * | 乘法 | xp * rexp | 从左向右 | |
| / | 除法 | rexp / rexp | ||
| % | 取余 | rexp % rexp | ||
| + | 加法 | rexp + rexp | ||
| - | 减法 | rexp - rexp | ||
| << | 左移位 | rexp << rexp | ||
| >> | 右移位 | rexp >> rexp | ||
| > | 大于 | rexp > rexp | ||
| >= | 大于等于 | rexp >= rexp | ||
| < | 小于 | rexp < rexp | ||
| <= | 小于等于 | rexp <= rexp | ||
| == | 等于 | rexp == rexp | ||
| != | 不等于 | rexp != rexp | ||
| & | 位与 | rexp & rexp | ||
| ^ | 位异或 | rexp ^ rexp | ||
| | | 位或 | rexp | rexp | ||
| && | 逻辑与 | rexp && rexp | 是 | |
| || | 逻辑或 | rexp || rexp | ||
| ? : | 条件操作符 | rexp ? rexp : rexp | 无 | |
| = | 赋值 | lexp = rexp | 从右向左 | 否 |
| += | 以 ... 加 | lexp += rexp | ||
| -= | 以 ... 减 | lexp -= rexp | ||
| *= | 以 ... 乘 | lexp *= rexp | ||
| /= | 以 ... 除 | lexp /= rexp | ||
| %= | 以 ... 取模 | lexp %= rexp | ||
| <<= | 以 ... 左移 | lexp <<= rexp | ||
| >>= | 以 ... 右移 | lexp >>= rexp | ||
| &= | 以 ... 与 | lexp &= rexp | ||
| ^= | 以 ... 异或 | lexp ^= rexp | ||
| |= | 以 ... 或 | lexp |= rexp | ||
| , | 逗号 | lexp,rexp | 从左向右 | 是 |
4.一些问题表达式
示例1:
a*b + c*d + e*f
代码 1 在计算的时候,由于 * 比 + 的优先级高,只能保证, * 的计算是比 + 早,但是优先级并不 能决定第三个 * 比第一个 + 早执行。
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;
同上,操作符的优先级只能决定自减 -- 的运算在 + 的运算的前面,但是我们并没有办法得 知, + 操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。 如:如果已知c的值为2 先--c得1 2+1=3 如果c的值还没准备好,先--c 得出值 (--c的值)+ --c 比如1+1=2所以:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。



