目录
操作符
1.算数操作符
2.移位操作符
左移操作符<<:
右移操作符>>:
3.位操作符
按位与&:
按位或 | :
按位异或 ^ :
4.赋值操作符
5.单目操作符
6.关系操作符
7.逻辑操作符
8.条件操作符
9.逗号操作符
10.下标引用,函数调用和结构成员
下标引用操作符:
函数调用操作符:
结构成员操作符:
表达式求值
隐式类型转换
算术转换
操作符的属性
操作符
1.算数操作符
C语言提供了常用的操作符:+ - * / %
除了%操作符,其余几个操作符都是既适用于浮点类型又适用于整数类型
当/操作符的两个操作数都是整数时,它执行整除运算,在其他情况下则执行浮点数除法
%为取模操作符,它接受两个整型操作符,把左操作数除以右操作数,但它返回的值是余数而不是商
int main()
{
//对于/操作符如果两个操作数都为整数,执行整数除法,而只要有浮点数执行的就是浮点数除法
int a = 3 / 2;
printf("%d",a);//1
int a = 3.0 / 2;
printf("%d",a);//1
float a = 3.0 / 2;
printf("%f",a);//1.500000
float a = 3 / 2;
printf("%f\n",a);//1.000000
//%操作符的两个操作数必须为整数。返回的是整除之后的余数
int a = 9 % 2;
printf("%d\n",a);//1
return 0;
}
2.移位操作符
移位操作符(二进制补码运算,运算结果转原码)
正整数的原码,反码,补码都相同
负整数的原码,反码,补码是要计算的;反码:原码的符号位不变,其他位取反;补码:反码+1
整数在内存中,存储的是二进制的补码,移位操作符,移动的是存储在内存当中的补码
左移操作符<<:
移位规则:左边抛弃,右边补0
int main()
{
//左移<<:左边抛弃,右边补0
int a = 5;//5的补码:00000000 00000000 00000000 00000101
int b = a << 1;//00000000 00000000 00000000 00000101,左移一位:00000000 00000000 00000000 00001010(最左面的0舍弃,最右面补0),正数的原返补码相同,得b=10
printf("%d\n", a);//5
printf("%d\n", b);//10
//负数的补码转原码:
//如果补码的最高位是1,说明这个补码所代表的数是负数,将补码除符号位外的所有位取反(0变成1,1变成0)得到反码,在反码的基础上加1,得到原码
int a = -5;//-5的原码:10000000 0000000 0000000 00000101,-5的反码:11111111 11111111 11111111 11111010,-5的补码:11111111 11111111 11111111 11111011
int b = a << 1;//-5的补码:11111111 11111111 11111111 11111011,左移一位:11111111 11111111 11111111 11110110,求反码:10000000 00000000 00000000 00001001,求原码:10000000 00000000 00000000 00001010,得b=-10
printf("%d\n", a);//-5
printf("%d\n", b);//-10
return 0;
}
右移操作符>>:
移位规则:
逻辑右移:左边用0填充,右边丢弃
算术右移:左边用原符号位填充,右边丢弃
当前编译器在右移的时候采用算术右移
int main()
{
//右移>>
//逻辑右移:左边用0填充,右边丢弃
//算术右移:左边用原符号位填充,右边丢弃
//当前编译器在右移的时候采用算术右移
//int a = 5;//5的补码:00000000 00000000 00000000 00000101
//int b = a >> 1;//00000000 00000000 00000000 00000101,右移一位:00000000 00000000 00000000 00000010,正数的原返补码相同,得2
//正数的算术右移和逻辑右移没有区别
//printf("%d\n", a);//5
//printf("%d\n", b);//2,算数右移
int a = -5;//-5的原码:10000000 0000000 0000000 00000101,-5的反码:11111111 11111111 11111111 11111010,-5的补码:11111111 11111111 11111111 11111011
int b = a >> 1;//-5的补码:11111111 11111111 11111111 11111011,右移一位:11111111 11111111 11111111 11111101,求反码:10000000 00000000 00000000 00000010,求原码:10000000 00000000 00000000 00000011,得b=-3
printf("%d\n", a);//-5
printf("%d\n", b);//-3,算数右移
return 0;
}
标准说明:无符号值执行的所有移位操作都是逻辑移位,但对于有符号值,到底是采用逻辑移位还是算数移位取决于编译器
对于移位运算符,不要移动负数位,这个是标准未定义的,如:int num=10; num>>-1;
3.位操作符
位操作符有:& | ^,它们分别执行AND,OR和XOR操作
它们要求操作数为整数类型,它们对操作数对应的位进行指定的操作,每次对左右操作数的各一位进行操作
按位与&:
按位与&:a和b中只要有0就为0,两个都为1时才为1
int main()
{
int a = 3;//3的补码:00000000 00000000 00000000 00000011
int b = -5;//-5的原码:10000000 00000000 00000000 00000101,-5的反码:11111111 11111111 11111111 11111010,-5的补码:11111111 11111111 11111111 11111011
//按位与&:a和b中只要有0就为0,两个都为1时才为1
int c = a & b;
//a:00000000 00000000 00000000 00000011
//b:11111111 11111111 11111111 11111011
//&:00000000 00000000 00000000 00000011->得3
printf("%d\n", c);//3
return 0;
}
按位或 | :
按位或 |:a和b中只要有1就为1,两个都为0时才为0
int main()
{
int a = 3;//3的补码:00000000 00000000 00000000 00000011
int b = -5;//-5的原码:10000000 00000000 00000000 00000101,-5的反码:11111111 11111111 11111111 11111010,-5的补码:11111111 11111111 11111111 11111011
//按位或|:a和b中只要有1就为1,两个都为0时才为0
int d = a | b;
//a:00000000 00000000 00000000 00000011
//b:11111111 11111111 11111111 11111011
//|:11111111 11111111 11111111 11111011,求反码:10000000 00000000 00000000 00000100,求原码:10000000 00000000 00000000 00000101,得-5
printf("%d\n", d);//-5
}
按位异或 ^ :
按位异或 ^ :相同为0,相异为1
int main()
{
int a = 3;//3的补码:00000000 00000000 00000000 00000011
int b = -5;//-5的原码:10000000 00000000 00000000 00000101,-5的反码:11111111 11111111 11111111 11111010,-5的补码:11111111 11111111 11111111 11111011
//按位异或:相同为0,相异为1
int d = a ^ b;
//a:00000000 00000000 00000000 00000011
//b:11111111 11111111 11111111 11111011
//^:11111111 11111111 11111111 11111000,求反码:10000000 00000000 00000000 00000111,求原码:10000000 00000000 00000000 00001000,得-8
printf("%d\n", d);//-8
}
异或运算三个性质:
- 任何数和 0做异或运算,结果仍然是原来的数
- 任何数和其自身做异或运算,结果是 0
- 异或运算满足交换律和结合律
案例:不能创建临时变量(第三个变量),实现两个数的交换
int main()
{
//a^a=0
//0^a=a
int a = 10;
int b = 20;
a = a ^ b;
b = a ^ b;//b=a^b^b=a^0=a
a = a ^ b;//a=a^b^a=a^a^b=0^b=b
printf("a=%d b=%d", a, b);//20 10
return 0;
}
4.赋值操作符
赋值操作符可以连续使用
赋值操作符的结合性(求值的顺序)是从右到左
int main()
{
int a = 10;
int x = 0;
int y = 20;
a = x = y + 1;//连续赋值
//等价于
x = y + 1;
a = x;
return 0;
}
复合赋值符:
+= -= *= /= %= <<= >>= &= ^= |=
int main()
{
a = a >> 1;
a >>= 1;
a = a & 4;
a &= 4;
return 0;
}
5.单目操作符
!:!操作符对它的操作数执行逻辑反操作;如果操作数为真,则结果为假,如果操作数为假,则结果为真。和关系操作符一样,这个操作符实际上产生一个整型结果,0或1
C语言中0表示假,非0表示真
C语言中C99之前没有表示真假的类型,C99中引用了bool类型:<stdbool.h>
int main()
{
_Bool flag1 = false;
_Bool flag2 = true;
if (flag2)
{
printf("hehe\n");
}
return 0;
}
~:~操作符对整型类型的操作数进行求补操作,操作数中所有原先为1的位变为0,所有原先为0的位变为1
int main()
{
int a = 0;
printf("%d\n", ~a);//-1
//00000000 00000000 00000000 00000000:0的补码
//11111111 11111111 11111111 11111111:按位取反
//10000000 00000000 00000000 00000000:反码
//10000000 00000000 00000000 00000001:原码->-1
return 0;
}
为什么while(^scanf("%d",&n))可以终止循环?
在while(scanf("%d",&n)!=EOF){...}中,scanf()读取失败的时候,返回EOF,而EOF对应的值等于-1
而~(-1)的值为0;因为-1的补码:11111111 11111111 11111111 11111111按位取反后为00000000 00000000 00000000 00000000对应的原码为0
-:-操作符产生操作数的负值
+:+操作符产生操作数的值
&:&操作符产生它的操作数的地址
struct S
{
char name[20];
int age;
};
int main()
{
//&:取地址
//变量取地址
int a = 10;
int* pa = &a;
//数组取地址
//&数组名:取出的是数组的地址,数组名表示整个数组
int arr[10] = { 0 };
&arr;//取出数组的地址,数组的地址应该存放到数组指针中去
//结构体取地址
struct S s = { 0 };
struct S* ps = &s;
}
*:*操作符是间接访问操作符(解引用操作符),它与指针一起使用,用于访问指针所指向的值
int main()
{
int a = 10;
int* pa = &a;
//*解引用操作符(间接访问操作符)
*pa = 20;//把a改成20
printf("%d\n",a);//20
}
sizeof:sizeof操作符判断它的操作数的类型长度,以字节为单位。操作数既可以是个表达式(常量是单个变量),也可以是两边加括号的类型
sizeof是一个操作符,不是函数
int main()
{
int a = 10;
//类型不可省略,变量可省略
printf("%d\n",sizeof(a));//4
printf("%d\n", sizeof a );//4
printf("%d\n", sizeof(int));//4
//printf("%d\n", sizeof int );//不可省略
int arr[10] = {0};
printf("%d\n",sizeof(arr));//40
int a = 10;
short s = 0;
printf("%d\n", sizeof(s = a + 2));//2,sizeof()中的表达式不参与计算,看向s
printf("%d\n", sizeof(a = s + 2));//4,sizeof()中的表达式不参与计算,看向a
printf("%d\n", s);//0
//sizeof和数组
//数组作为函数参数传递时,传入的是数组首地址,并非是数组本身
//当数组作为函数参数传递时,不能在函数中用sizeof求数组长度
return 0;
}
(类型):(类型)操作符被称为强制类型转换,它用于显示地把表达式的值转换为另外的类型,如果要对整个表达式的结果进行强制类型转换,必须把整个表达式用括号括起来
int main()
{
int a = (int)3.14;
printf("%d\n",a);//3
return 0;
}
++:前置++,先++,后使用,后置++,先使用,后++
--:前置--,先--,后使用,后置--,先使用,后--
int main()
{
int a = 3;
//++a相当于a=a+1
int b = ++a;//前置++,先++,后使用
printf("%d\n",b);//4
printf("%d\n",++a);//5
int b = a++;//后置++,先使用,后++
printf("%d\n", b);
int b = --a;//前置--,先--,后使用
int b = a--;//后置--,先使用,后--
return 0;
}
6.关系操作符
关系操作符有:> < >= <= != ==
这些操作符产生的结果都是一个整型值,而不是布尔值。如果两端的操作数符合操作符指定的关系,表达式的结果为1,否则,表达式的结果为0。关系操作符的结果是整型值,所以它可以赋值给整型变量,但通常它们用于if或while语句中,作为测值表达式。
在C语言中,在测试相等性的地方出现赋值是合法的,它并非是一个语法错误
==:用于判断相等,不能用于两个字符串的判断相等
=:用于赋值
int main()
{
if ("abcdef" == "abbq")//不能用该方式,应该用strcmp:专门用于比较字符串的大小,比较对应位置上字符的大小,不是比较长度
{
}
return 0;
}
7.逻辑操作符
逻辑操作符:&& ||
它们用于对表达式求值,测试它们的值是真是假
&&操作符的左操作数总是首先进行求值,如果为真,就紧接着对右操作数进行求值;如果左操作数的值为假,那么右操作数便不再进行求值,因为整个表达式的值肯定是假的,右操作数的值已经无关紧要。
||操作符首先对左操作数进行求值,如果它的值是真,右操作数便不再求值,因为整个表达式的值此时已经确定。这个行为常常被称为“短路求值”
int main()
{
int a = 3;
int b = 5;
if (a && b)//为真
{
printf("hehe\n");
}
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;//&&:只要碰到一个为假,后面的就不再计算;因为a为后置++,则先使用,后++,所以a=0为假,后面则不再计算
printf("a=%d b=%d c=%d d=%d\n", a, b, c, d);//1 2 3 4
int i = 0, a = 0, b = 2, c = 3, d = 4;
i=a++||++b||d++;//||:直到碰到一个为真,才结束后面的运算
printf("a=%d\nb=%d\nc=%d\nd=%d\n",a,b,c,d);//1 3 3 4
return 0;
}
位操作符与逻辑操作符的区别:
- 逻辑操作符具有短路性质,如果表达式的值根据左操作数便可决定,它就不再对右操作数进行求值;与之相反,|和&操作符两边的操作数都需要进行求值
- 逻辑操作符用于测试零值和非零值,而位操作符用于比较它们的操作数中对应的位
8.条件操作符
exp1 ? exp2 : exp3
首先计算的是exp1,如果它的值为真(非零值),那么整个表达式的值就是exp2的值,exp3不会进行求值。但是,如果exp1的值为假(零值),那么整个表达式的值就是exp3的值,exp2不会进行求值
int main()
{
int a = 3;
int b = 0;
b = (a > 5 ? 3 : -3);
printf("%d\n",b);//-3
return 0;
}
9.逗号操作符
exp1,exp2,...,expN
逗号操作符将两个或多个表达式分隔开来。这些表达式自左向右逐个进行求值,整个逗号表达式的值就是最后那个表达式的值
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);
printf("a=%d b=%d\n",a,b);//12 13
printf("%d\n",c);//13
return 0;
}
10.下标引用,函数调用和结构成员
下标引用操作符:
[ ]:下标引用操作符,接受两个操作数:一个数组名和一个索引值
C语言的下标值总是从零开始,并且不会对下标值进行有效性检查。除了优先级不同之外,下标引用操作和间接访问表达式是等价的,像arr[4]==*(arr+4)==*(4+arr)
int main()
{
int arr[10] = { 0 };
arr[4] = 5;//就是下标引用操作符
return 0;
}
函数调用操作符:
( ):函数调用操作符函数调用操作符接受一个或多个操作数。它的第一个操作数是你希望调用的函数名,剩余的操作数就是传递给函数的参数
int Add(int x, int y)
{
return x + y;
}
int main()
{
int ret = Add(2, 3);//()函数调用操作符,操作数就是:Add,2,3
printf("%d\n",ret);
return 0;
}
结构成员操作符:
.:结构体变量.成员名
->:结构体指针->成员名
如果s是个结构体变量,那么s.a就访问s中名叫a的成员。当你拥有一个指向结构的指针而不是结构本身,且欲访问它的成员时,就需要使用->操作符而不是.操作符
struct Stu
{
char name[20];
int age;
float score;
};
void print1(struct Stu ss)
{
printf("%s %d %f\n",ss.name,ss.age,ss.score);
}
void print2(struct Stu* ps)
{
//printf("%s %d %f\n",(*ps).name,(*ps).age,(*ps).score);
printf("%s %d %f\n",ps->name,ps->age,ps->score);
}
int main()
{
struct Stu s = {"张三",20,90};
//s.name="张三丰"//name是数组名,代表的是数组首元素的地址
strcpy(s.name,"张三丰");
//print1(s);
print2(&s);
return 0;
}
表达式求值
表达式的求值顺序一部分是由它所包含的操作符的优先级和结合性决定
隐式类型转换
C语言的整型算术运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符型和短整型操作数在使用之前被转换为普通类型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长
度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算。
在下面表达式的求值中,
char a,b,c;
a = b + c;
b和c的值被提升为普通整型,然后再执行加法运算。加法运算的结果将被截短,然后再存储于a中
如何进行整型提升?
整型提升是按照变量的数据类型的符号位来提升的
负数的整型提升:
char c1=-1;变量c1的二进制位(补码)中只有8个比特位:11111111。因为char为有符号的char,所以整型提升的时候,高位补充符号位,即为1。提升之后的结果是:11111111 11111111 11111111 11111111
正数的整型提升:
char c2=1;变量c2的二进制(补码)中只有8个比特位:00000001。因为char为有符号的char,所以整型提升的时候,高位补充符号位,即为0。提升之后的结果是:00000000 00000000 00000000 00000001
无符号整型提升:高位补0
案例一:
int main()
{
//整型提升:是按照变量的数据类型的符号位来提升的
char c1 = 3;//00000011->整型提升00000000 00000000 00000000 00000011
char c2 = 127;//01111111->整型提升00000000 00000000 00000000 01111111
char c3 = c1 + c2;//相加得:00000000 00000000 00000000 10000010,c3=10000010->整型提升11111111 11111111 11111111 10000010(补码)->原码10000000 0000000 0000000 01111110=-126
printf("%d\n",c3);//-126
return 0;
}
案例二:
int main()
{
char a = 0xb6;//10110110,整型提升转换为负数
short b = 0xb600;//10110110 00000000,整型提升转化为负数
int c = 0xb6000000;
if (a == 0xb6)
printf("a");
if (b == 0xb600)
printf("b");
if (c == 0xb6000000)
printf("c");//c
return 0;
}
案例二中的a,b要进行整型提升,但是c不需要整型提升。a,b整型提升之后就变成了负数,所以表达式a == 0xb6,b == 0xb600的结果是假。但是c不发生整型提升,则表达式c == 0xb6000000的结果是真。
案例三:
int main()
{
char c = 1;
printf("%u\n",sizeof(c));//1
printf("%u\n", sizeof(+c));//4,是表达式需要整型提升
printf("%u\n", sizeof(-c));//4,是表达式需要整型提升
return 0;
}
案例三中c只要参与表达式运算,就会发生整型提升,表达式+c就会发生提升,所以sizeof(+c)是4个字节;表达式-c也会发生整型提升,所以sizeof(-c)是4个字节,但是sizeof(c)就是1个字节.
算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数转换为另外一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换
long double |
double |
float |
unsigned long int |
long int |
unsigned int |
int |
如果某个操作数的类型在上面这个列标中排名较低,那么它首先将转换为另外一个操作数的类型然后执行操作。
操作符的属性
复杂表达式的求值顺序是由3个因素决定的:操作符的优先级,操作符的结合性以及操作符是否控制执行的顺序。
两个相邻的操作符哪个先执行取决于它们的优先级,如果两者的优先级相同,那么它们的执行顺序由它们的结合性决定。简单地说,结合性就是一串操作符是从左向右依次执行还是从右向左依次执行。