c语言中的操作符
1.算数操作符
a.+ 加号
b.- 减号
c.* 乘号
d./ 除号 当 / 的两边都为整形时,计算结果默认为 int 。当 / 两边任意一边出现小数,计算结果为float or double
e.% 取模 % 计算的是两数相除后的余数,其两边必须为 int ,整除时结果为 0
2.移位操作符
使用条件:只能对于 int类型 使用,无符号整型这里算作正数。移位操作符移动的位数不能为负数,标准未定义这种写法,所以在不同编译器中有不同结果
a.<< 左移 对整数的补码左移一位 左边丢弃,右边补零
b.>> 右移 对整数的补码右移一位 算数移位:右边丢弃,左边补原符号位
逻辑移位:右边丢弃,左边补零 //算数移位和逻辑移位取决于编译器,大部分是算数移位
整数的二进制表达有三种
(原码,反码,补码)
对于正整数来说,三种表达方式在数值上是相同的,而负整数的三种表达在数值上并不相同
//例:
7
00000000 00000000 00000000 00000111——32位原码
00000000 00000000 00000000 00000111——32位反码
00000000 00000000 00000000 00000111——32位补码
// 由于 int 类型内存大小为 4 个字节,其存储方式为32bit的二进制,第一位为字符位,为 1 表示负数,0 为正数 ;
-7
10000000 00000000 00000000 00000111——32位原码
11111111 11111111 11111111 11111000——32位反码
//反码是将原码中除字符位以外的bit位由 1 变为 0 ,由 0 变为 1 ;
11111111 11111111 11111111 11111001——32位补码
//补码就是在反码的基础上 + 1 ;
那么移位操作符是如何使用的,下面我举一个例子
int main()
{
int a = 7;
int b = 0;
b = a << 1;
//此时输出 b 的结果为 14 —————— 00000000 00000000 00000000 00001110
由于7的32位原码为 ——00000000 00000000 00000000 000000111。
而正整数的原码补码相同,我们遵循左移规则对a的补码左移一位,左边的二进制数丢弃,最右边补零,得到补码 ——00000000 00000000 00000000 00001110。
由于第一位的符号位为零,其是正整数,原码补码相同,所以转换成十进制就是14。
我们再举一个负数的例子
int main()
{
int c = -7;
b = c << 1; //此时输出 b 的结果为 -14 ————— 11111111 11111111 11111111 11110010补码
}
-7的原码反码补码上面已经列出,这里不再写了,我们根据左移规则对c的补码左移,左边丢弃,右边补零,得到补码——11111111 11111111 11111111 11110010
这里我们看到符号位为1,得到的是一个负数,我们先求反码,对补码减去1,得到反码11111111 11111111 11111111 11110001
再求原码,对反码除符号位的每一位进行0和1的互换,得到原码10000000 00000000 00000000 00001110
转换成十进制为-14
同理
对于右移操作符也是一样的方法,算出补码,按照规则右移,在将结果的补码转换成原码,变为十进制就能得到数值,一定要记得,在原码反码的转换中,首位符号位是不变的,但在移位的过程中,符号位是可能改变的,这也是为什么使用移位操作符会改变数值的正负。
3.位操作符
使用条件:计算时两边的操作数要为 int类型
a.& 按位与 //与:对于两个二进制数,&的两边都为1,与的结果为1,两边只要有0,结果为0
b.| 按位或 //或:对于两个二进制数,|的两边都为0,或的结果为0,两边只要有1,结果为1
c.^ 按位异或 //异或:对于两个二进制数,相同结果为0,相异结果为1
按位的含义就是对于两个操作数,按补码的二进制位,每位单独进行比较
我举一个例子
//例1:
int main()
{
int a = 8; //00000000 00000000 00000000 00001000 8的补码
int b = -4; //11111111 11111111 11111111 11111100 -4的补码
int c = 0;
c = a & b //00000000 00000000 00000000 00001000 结果的补码,由于是正数,所以原码也是这个,结果为8
a | b = //11111111 11111111 11111111 11111100 结果的补码
//11111111 11111111 11111111 11111011 反码
//10000000 00000000 00000000 00000100 原码,结果为-4
a ^ b = //11111111 11111111 11111111 11110100 补码
//11111111 11111111 11111111 11110011 反码
//10000000 00000000 00000000 00001100 原码,结果为-12
}
举一个用法
//例2:
int main()
{
int a = 10;
int b = 20;
a = a ^ b;//10^20
b = a ^ b;//10^20^20 //不建立新变量,交换两个数值,只适用于int类型
a = a ^ b;//10^20^10 (20^20=0)
}
4.赋值操作符
a.= 赋值
赋值操作符可以连续使用
//例:
int main()
{
int a = 10;
int b = 20;
int c = 30;
a = b = c + 10;//输出的结果为a=40,b=40
}
b.复合赋值操作符
+=
-=
*=
/=
%=
^=
&=
|=
<<=
>>=
复合赋值操作符本质上就是对被赋值的变量进行一次计算,再将计算结果赋值给被计算变量自身
//例:
int main()
{
int a = 0;
a = a + 1;
a +=1; //这两种写法结果是相同的,其他的复合赋值操作符同理
}
5.单目操作符(只有一个操作数)
a.! 逻辑反操作//把真转为假,把假转为真,C语言中一般把0视为假,非零视为真
b.- 负号
c.+ 正号
e.& 取地址(字面意思,取出一个变量的首地址)
f.sizeof(操作数) 计算操作数的字节长度(所占内存大小,类型,变量,数组等)//sizeof后的括号在某些情况下是可以省略的
g.~ 对操作数按位取反
~按位取反是对补码的取反,和之前的求反码的规则类似,但是首位的符号位是改变的,剩下的位数0和1互换,说白了就是每一位都取反
//例
int main()
{
int a = 10; //00000000 00000000 00000000 00001010 补码
a = ~a; //11111111 11111111 11111111 11110101 结果的补码
//11111111 11111111 11111111 11110100 反码
//10000000 00000000 00000000 00001011 原码,结果为-11
a |= (1<<5);
a &= (~(1<<5))//这两个公式算出来的结果相同,这也是这些符号的一些用法,目的是对与a中存储的32位比特位的任意一位进行修改
可以自行计算一下
}
h.++ 自增 前置++:先++再使用 后置++:先使用后++ //++可以看作a = a + 1
i.-- 自减 前置--:先--再使用 后置--:先使用后-- //--可以看作a = a - 1
//对操作数加一或减一,对于使用操作数的场景,其放在操作数前后产生效果不同
关于自增自减的使用问题,可以理解为当有某个过程调用了被自增自减的变量时,视作被使用了,如果此时为后置的自增自减,那么就得先执行调用函数或者计算过程,前置则先进行计算
j.* 间接访问操作符(解引用操作符)
解引用操作符多用于指针的访问上
//例:
int main()
{
int a =10;
int* p = &a;
*p = 20;//此时a中的值变为二十,*的作用就是通过p中存放的地址找到a变量,所以*p就等于a
}
k.(类型) 强制类型转换
对数据类型进行强制转换
#include<stdlib.h>
#include<time.h>
int main()
{
int a = (int)58.5148951;//a现在等于58,相当于把浮点型强制转换成了整数,后面的小数位丢失了
0r
srand((unsigned int) time(NULL))
这是一个关于伪随机数起始种子的一种可变写法,由于srand()中只能接收无符号整型,而time返回值时long long,所以要强制类型转换
}
6.关系操作符
(不是所有内容都可以用关系操作符比较)
a.> 大于
b.< 小于
c.>= 大于等于
d.<= 小于等于
e.!= 不等于
f.== 等于 //一定要注意不要把==写成=,一个是等于,一个是赋值,这是个很常见的错误
//如果对于变量和常量,我们可以把顺序反着写比如 8==a ,这样如果少写了个等号,就会报错了
7.逻辑操作符
只对真假进行判断,结果也只有0或1
&& 逻辑与(全真为真,一假则假)//当其左边为假,直接出结果,右边整个过程不进行计算
int main()
{
int a = 4;
int b = 3;
int c = 0;
int d = a && b//此时d的值为 1
int e = b && c//此时e的值为 0
}
|| 逻辑或(一真则真,全假为假)//当其左边为真,直接出结果,右边整个过程不进行计算
int main()
{
int a = 4;
int b = 3;
int c = 0;
int d = a || b;// 1
int e = b || c;// 1
d = 0;
e = d || c;// 0
}
逻辑操作符在从左往右进行计算时,一旦得到确切结果,后面一律不再计算
int e = b && c || b || c || f || 666 && 0 && a+=100
如果b&&c等于0,结果为假了,后面就全部不计算,直接输出结果0
8.条件操作符
三目操作符(从左向右依次计算)
表达式1 ? 表达式2 :表达式3
//表达式1如果为真,则计算表达式2,表达式2的结果为整个表达式的结果,表达式3不进行计算
//表达式1如果为假,则计算表达式3,表达式3的结果为整个表达式的结果,表达式2不进行计算
9.逗号表达式
表达式1,表达式2,表达式3,表达式4,.....,表达式n
逗号表达式就是用逗号隔开的多个表达式,从左到右依次执行,表达式的结果就是最后一个表达式的结果
//例子
int main()
{
while(a = get_val(), count_val(a),a > 0 )
{
;
}
//等价于
do
{
a=get_val();
count_val(a);
}while(a>0)
}
10.我不知道怎么分了
a.下标引用操作符
[] 下标引用,数组常见
int main()
{
int arr[10] = { 0 };
arr[9]=10;//使用下标引用,两个操作数分别为数组名和下标数9
9[arr]=10;//两者可以交换arr[9] ---> *(arr + 9) ---> *(9 + arr) --->9[arr]
}
比如
int arr1[4] = {1,2,3,4};
int arr2[4] = {1,2,3,4};
int arr3[4] = {1,2,3,4};
int* parr[3] = {arr1,arr2,arr3};
int i = 0;
int j = 0;
for(i = 0; i < 3; i++)
{
for(j = 0; j < 4; j++)
{
printf("%d ",parr[i][j]);//下标引用本质上就是指针偏移 + 解引用
} //parr[i]->*(parr + i)
printf("\n"); //parr[i][j]->*(parr[i] + j)->*(arr(i + 1) + j)
} //上面的arr(i+1)代指arr1 or arr2 or arr3
b.函数调用操作符
() 函数调用,函数常见
//函数定义
int ADD(int a, int b)
{
return a + b;
}
int main()
{
int a = 10;
int b = 20;
//函数调用
int c = ADD(a,b);//()就是函数调用操作符,操作数为函数名和参数,至少有一个操作数(函数名)
return 0;
}
c.访问结构体成员
-> 结构体指针变量 -> 成员名
. 结构体名 . 成员名
都是对于结构体的成员使用时才会用到
#include<stdio.h>
#include<string.h>
struct a
{
char name[20];
int age;
double score;
}
void set_a(struct a* s)
{
strcpy((*s).name,"zhangsan");
(*s).age = 18;
(*s).score = 100.00;
//or strcpy(s->name,"zhangsan");
// s->age = 18;
// s->score = 100.00
}
void print_a(struct a s)
{
printf("%s %d %lf",s.name,s.age,s.score);
}
int main()
{
struct a s = { 0 };
set_a (&s);
print_a(s);
return 0;
}
总结:操作符的计算顺序呢,是由优先级和结合性来共同决定的,当然他们也不能完全决定,大量的特别复杂的操作符的使用可能会造成在不同编译器的场景下的出的结果不同的情况,所以能用简洁分步的方式尽量简洁分步骤,不然代码就可能是垃圾代码。

浙公网安备 33010602011771号