操作符详解
1.算术操作符
+ | 加 |
- | 减 |
* | 乘 |
/ | 除 |
% | 加取余 |
编程中,除法只会求得整数
常用的取余方式:个位数中 1—9分别对10取余都等于它本身,那么1-99对100取余就是他本身
2.移位操作符
<< | 左移操作符 |
---|---|
>> | 右移操作符 |
-
左移操作符:左边舍去,右边补0
-
右移操作符:
(1)逻辑右移:右边舍去,左边补0
(2)算术右移: 左边用原该值的符号位填充,右边丢弃
右移操作符具体是哪种,取决于你的编译器(vs2013当前为算术右移)在计算机中,整型是以二进制补码形式存储的,正数的补码反码都一样,而负数的补码是其,正数的补码按位取反,末尾+1求得的,且最高位是符号位 例如5的补码是 0000 0101 按位取反 1111 1010 末尾+1 1111 1011 逻辑右移就是:不管其正负,右边舍去 左边补0 如: 1111 1011 >> 1 = 0111 1101 算术右移是:符号位是什么,右边就补什么 如 :1111 1011 >> 1 = 1111 1101 = -1 但是 不可以移负位,要么左移,要么右移
3.位操作符(必须是整数)
& | 按位与 |
---|---|
I | 按位或 |
^ | 按位异或 |
- 按位与 ———— 全1出1,有0出0
- 按位或 ———— 有1出1,全0出0
- 按位异或 ———— 相同出0,相异为1
例如:不能创建临时变量(第三个变量),实现两个数交换
int main()
{
int a = 10;
int b = 20;
a = a^b; //也可以 a=a+b
b = a^b; // b=a-b
a = a^b; // a=a-b
printf("a = %d b = %d\n", a, b);
return 0;
}
例2 求一个整数存储在内存中的二进制中1的个数。
方法一
int main()
{
int a;
int i = 0;
int count = 0;
scanf("%d",&a);
for(i = 0; i < 32; i++)
{
if( ((a>>i)&1) == 1 )
count++;
}
printf("%d\n",count);
}
此方法的缺点是无论什么数据都要进行32次
方法2
#include <stdio.h>
int main()
{
int num = -1;
int i = 0;
int count = 0; //计数
while(num)
{
count++; //每次都能与走一个一,一共与几次就是与走几个1,就是每次把原式处在最右侧的1与走
num = num&(num-1);
}
printf("二进制中1的个数 = %d\n",count);
return 0;
}
此方法每次通过与运算,消去原数处在最右面的1,效率最高
方法3
unsigned int n
whiel(n)
{
if(n%2==1)
count++
n=n/2
}
//与十进制类似如1000除10等100,二进制则除2消去一位
//所以是先判断,后消去
此方法需要大量的取余和除法运算,取余和除法运算本身效率偏低
例3 求两个数在二进制中不同位的个数
方法1
int main()
{
int i,a,b;
int count = 0;
scanf("%d%d",&a,&b);
for(i = 0;i < 32;i++)
{
if( ((a>>i)&1) != ((b>>i)&1))
count++;
}
printf("%d\n",count);
return 0;
}
此方法和求一个数1的个数大同小异
方法二
int main()
{
int a,b,temp;
int i,count = 0;
scanf("%d%d",&a,&b);
temp = a^b; //先异或 在求个数,可以把上面那个&本身-1的封装成函数,在传递异或
for(i = 0;i < 32;i++)
{
if((temp>>i)&1 == 1)
count++;
}
printf("%d\n",count);
return 0;
}
此方法是先异或,在求1的个数即可
例4 打印一个数的二进制 奇数位和偶数位
int main()
{
int a;
int i;
scanf("%d",&a);
printf("偶数位:"); //先想首位怎么移到,再看末位怎么移到
for(i = 31;i>=0;i-=2) //首位移0位到首尾,末位移31位到首尾
printf("%d",(a>>i) & 1);
printf("\n");
printf("奇数位:");
for(i = 30;i>=0;i-=2) //二进制奇偶是从右往左数
printf("%d",(a>>i) & 1);
return 0;
}
4.赋值运算符
- =
- +=
- -=
- *=
- /=
- %=
- >>=
- <<=
- &=
- |=
- ^=
例如:a+=1 —————— a=a+1
其他复合赋值运算符用法同上
5.单目运算符
! | 逻辑反 |
---|---|
+ | 正值 |
- | 负值 |
& | 取地址 |
sizeof | 操作数的类型长度(字节为单位) |
i++ | 后置++ 先输出i,后+1 |
++i | 前置++ 先+1,后输出 |
i- - | 后置- - 先输出i,后-1 |
- -i | 前置- - 先-1,后输出 |
* | 间接访问操作符(解引用操作符) |
~ | 按位取反 如 1111 按位取反就是 0000 |
(类型名) | 强制类型转化 |
int main()
{
int i = 20;
char arr[20] = {0};
int *p;
printf("%d,",!2); //整数取反是0
printf("%d\n",!0); //0取反是1
putchar('\n');
printf("%d,",sizeof(i)); //i是int型大小是4字节
printf("%d\n",sizeof(int)); //4
putchar('\n');
printf("%d,",sizeof(arr)); //数组名是整个数组的地址,所以是20
printf("%d\n",sizeof(arr[0])); //一个元素的地址,则为1
putchar('\n');
i = -i; //i = -20;
printf("%d,",i); //-20
printf("%d\n",-i); // -(i) --20 =20
putchar('\n');
p = &i; //指针接收i的地址
printf("i = %d,",i); //i的内容
printf("&i = %d,",&i); //i的地址以十进制输出
printf("&i = %p\n",&i); //i的地址以十六进制输出
putchar('\n');
printf("*p = %d,",*p); //指针解引用,就是i的内容
printf("&(*p) = %d,",&*p); //取地址与解引用抵消,即 对p的内容以十进制输出,p内容是i的地址
printf("p = %p\n",&p); //指针自己的地址
printf("可知 &*p = &i,&*互相抵消\n");
putchar('\n');
printf("%d\n",sizeof((char)i)); //强制类型转换
return 0;
}
~ 按位取反的作用:
例如在while()中的跳出条件可以用: ~ scanf(%d,&n) 和 scanf()!=EOF
如果scanf没有成功读取,返回0,取反的结果不为假,继续执行如果没有输入,到达文件末尾则返回-1,取反的结果为0,结束while
6.关系操作符
> | 大于 |
---|---|
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
!= | 不等于 |
== | 等于 |
如:比较 abcd 和 adcb的大小
if(“abcd” == “adcb”),结果为假
注意:虽然语法没错,但比较的是字符串首地址,若比较字符串大小可以用 strcmp
strcmp: 比较对应位置上,字符的大小 ,比如 abcde 和abbdepq ,比较到第三位C比b大 所以第一个字符串大 即只要发现不相等的字符就完成比较
7.逻辑操作符
&& | 与 |
---|---|
II | 或 |
示例:
int main()
{
int i = 1;
int z = 2;
int j = 0;
if( j && z && i ) //只要第一个表达式为假则直接跳出,不会计算后续,因为与是全1出1
{
j++;
z++;
i++;
}
printf("j=%d,i=%d,z=%d\n",j,i,z);
if(i++ || z++ || j++) //只要第一个表达式是真,后面就不执行了,因为有1出1
printf("j=%d,i=%d,z=%d\n",j,i,z);
}
示例2是一道面试题
int main()
{
int i = 0,a=0,b=2,c =3,d=4;
// i = a++ && ++b && d++;
i = a++||++b||d++;
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
return 0;
}
&&的答案是 a=1,b=2,c=3,d=4
因为a++是先输出后+,所以a先等于0判为假之后再+1,后面则不再继续执行
||的答案是 a=1,b=3,c=3,d=4
因为第一个表达式 是先输出后++ ,a为假则判断第二个表达式,++b是先加后输出为真则直接退出
8.条件操作符
- exp1 ? exp2 : exp3
int main()
{
int a = 2;
int b = 3;
int i;
i = (a>b? a++:++b);
printf("%d\n",i);//a>b 为假 执行 ++b,就是先+后输出 所以为4,
//若是b++ 则为3,因为先输出后执行
return 0;
}
9.逗号表达式
- exp1, exp2, exp3, …expN
逗号表达式,就是用逗号隔开的多个表达式。 逗号表达式,从左向右依次执行。整个表达式的结果是最左表达式的结果。
而(里的表达式) 则是最右边的结果
#include <stdio.h>
int main()
{
int a, b, c;
a = 5;
c = ++a;
b = ++c, c++, ++a, a++; //第一个++c算完后就给了b
b += a++ + c; //若是(++c, c++, ++a, a++)则取最后一个表达式的值
printf("a = %d b = %d c = %d\n:", a, b, c);
return 0;
}
b = ++c, c++, ++a, a++;
首先 a=6,c=6,之后c=7,b=7,c=8,a=7,a=8
b = (++c, c++, ++a, a++);
首先 a=6,c=6,之后c=7,c=8,a=7,b=7,a=8 因为a++是先输出后执行
int main()
{
int a = 1;
int b = 2;
int c = 0;
int d = 0;
d = a>b, a=b+10, a, b=a+1; //a>b 为假所以d = 0
c = (a>b, a=b+10, a, b=a+1);
printf("%d,%d",c,d); //c为末尾值13,d为首值为假 0
}
10.下标引用、函数调用和结构成员
- [] 下标引用操作符 通过[] 引用数组成员
- () 函数调用操作符 ()函数调用操作符 接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数
11.访问一个结构的成员
- 结构体 . 成员名
- 结构体指针 -> 成员名
整数提升
在表达式中的字符型和短整型操作数在使用之前会被转换为普通整型(int),这种转换称为整型提升
例如负数的整型提升:
char ch = -1;
int i = 1;
i=i+ch;
-1的补码是 1111 1111 ,所以在操作之前会被转为整型
11111111 11111111 11111111 11111111+0000000 00000000 00000000 00000001
= 1 00000000 00000000 00000000 00000000,操作完后再转回char 00000000
以高位符号位为准补
正数的整型提升则是高位补0
也就是,先提升(根据符号位从高位补0/1),再操作,最后截断
int main()
{
char c = 1;
printf("%u\n", sizeof(c)); //char本身的字节
printf("%u\n", sizeof(+c)); //+c操作之前“整型提升”,变为4个字节
printf("%u\n", sizeof(!c)); //同理,操作之前被“整型提升”
return 0;
}
int a =10;
short s =0;
sizef(s = a+2) //输出结果为4 ,整型提升
sizeof()中的表达式不参与计算,sizeof的返回值类型实际为无符号整形,是2
算术转换
寻常算术转换:
long double
double
float
unsigned long int
long int
unsigned int
int
以上为从小到大的顺序
当操作的表达式大于整型的表达式时,会进行算术转换,先令整型等于大的类型(向上转换)
警告: 但是算术转换要合理,要不然会有一些潜在的问题。例如当浮点型转为整型,会丢失小数点,丢失精度
#include <stdio.h>
int i;
int main()
{
i--;
// 算术转换,向上转换
if (i > sizeof(i))
{
printf(">\n");
}
else
{
printf("<\n");
}
return 0;
}
输出结果是 <
因为sizeof的返回值类型为无符号整型,表达式(整型 > 无符号整型) 因此编译器会自动将左侧i自动转换为无符号整形的数据,-1对应二进制的无符号整形是一个非常大的数字
操作符的属性(操作符一览表)
复杂表达式的求值有三个影响的因素。
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序。