一、算术操作符
+ - * / %
Tips:1.%操作符的两个操作数必须为整数,其他几个可以用于整数和浮点数
2./操作符若两个操作数为整数,则执行整数除法,求商;只要有浮点数执行的就是浮点数除法。
二、移位操作符
<< 左移操作符 >> 右移操作符
移位操作符针对的是二进制位。
右移操作符:
1.算术右移:右边丢弃,左边补原符号位(一般情况)
2.逻辑右移:右边丢弃,左边补0
再重复一次,整数以补码的形式储存在内存当中,正数的原码、补码、反码相同,负数的反码是原码除符号位按位取反,补码是反码+1。 而移位操作都是针对其存储二进制位进行操作。
左移操作符:
左边丢弃,右边补0
※对于移位操作符,不要移动负数位,这个标准是未定义的。 (num>>-1 ——错误的)
特殊要求运算:左移n位等价于乘以2^n,右移则等价于除以2^n 。
三、位操作符
位操作符 | 按位与 | 按位或 | 按位异或 |
符号 | & | | | ^ |
效果 | 全1则1,有0则0 | 全0则0,有1则1 | 相同为0,不同为1 |
操作数必须为整数
特殊要求运算(X为0或1任意一个):
按位与
1)指定一个或多个二进制位清零(0 & X=0)。将原数按位与一个数字,该数字清零位为0,其余位为1 。
按位或
1)将指定二进制位变为1(1 | X=1)。将原数按位或一个数字,指定位为1,其余位为0 。
按位异或
1)指定位翻转(1 ^ X = !X , 当X=1时!X=0,当X=0时!X=1)(0 ^ X = X)。将原数按位异或一个数字,指定位为1,其余位为0 。
2)将两个值互换(X ^ X=0)。
a = a ^ b ,b = b ^ a ,a = a ^ b;
除此之外还有加减法(存在溢出风险)互换两个值:
a = a + b ,b = a - b ,a = a - b;
小练习—— 统计num的补码中有几个1
int main()
{
int num = 0;
int count = 0;
scanf("%d",&num);
while(num)
{
if(num % 2 == 1)
count++;
num = num / 2;
}
printf("%d\n",count);
return 0;
}
这种方法很容易想到,但是缺点在于无法统计负数。
int main()
{
int num = 0;
int count = 0;
scanf("%d",&num);
int i = 0;
for(i=0;i<32;i++)
{
if(i == ((num >> i) & 1))
count++;
}
printf("%d\n",count);
return 0;
}
这种方法考虑了负数在内,但是算法不够简单。
int main()
{
int num = 0;
int count = 0;
scanf("%d",&num);
while(num)
{
count++;
num = num & (num - 1);
}
printf("%d\n",count);
return 0;
}
对于二进制计算,减1意味着两种结果:①其他位不变,最右位由1变为0;②其他位不变,最后一位由0变为1,同时向上借位。这样将减前减后两个数字进行一次与运算,必然会清掉二进制位中的一个1。不可谓不精妙!
四、赋值操作符
注意点:1)赋值语句执行顺序是从右向左。
a = x = y+1; <=> x = y+1; a = x;
2)复合赋值符(+= -= 等)。
五、单目操作符
符号 | 名称及作用 |
! | 逻辑反操作 |
- | 负值 |
+ | 正值 |
& | 取地址 |
sizeof | 操作数的类型长度(以字节为单位) |
~ | 对一个数的二进制位按位取反 |
-- | 前置、后置-- |
++ | 前置、后置++ |
* | 间接访问操作符(解引用操作符) |
(类型) | 强制类型转换 |
逻辑非(!)
!a:当a为0时,结果为1;当a为非零时,结果为0。
取地址操作符(&) 解引用操作符(*)
int main()
{
int a = 10;
int* p = &a; //取地址操作符
*p = 20; //解引用操作符
return 0;
}
取长度(大小)操作符( sizeof() )
sizeof 计算变量或类型所占空间的大小,单位是字节。
void test(char ch[])
{
printf("%d\n",sizeof(ch)); //4 因为ch为首元素地址,32位机器地址大小位4个字节
}
int main()
{
int a = 10;
char c='r';
char* p =&c;
int arr[10]={0};
char ch[10]={0};
printf("%d\n",sizeof a);//4
printf("%d\n",sizeof(int));/4
printf("%d\n",sizeof(c));//1
printf("%d\n",sizeof(char));//1
printf("%d\n",sizeof(p));//4
printf("%d\n",sizeof(char*));//4
printf("%d\n",sizeof(arr));//40
printf("%d\n",sizeof(int [10]));//40
test(ch)
return 0;
}
注意点:
int main()
{
short s = 0;
int a = 10;
printf("%d\n",sizeof(s = a + 5));
printf("%d\n",s);
}
以上代码输出结果为2 , 0 。非常关键的一点就是,sizeof中的表达式不参与运算,而只进行判断最后结果类型,所以这里sizeof计算short类型大小并且表达式没有运算,s仍然为0 。
按位取反(~)
对任意一个数字m,有 m + ~m = -1, m | ~m = -1成立。
前置、后置++/--
int main()
{
int a = 10;
int b = 10;
printf("%d\n",++a); //11 前置++,先++,后使用
printf("%d\n",b++); //10 后置++,先使用,后++
return 0;
}
强制类型转换( (type) )
int a = (int)3.14; //a = 3
六、关系操作符
> < >= <= != ==
七、逻辑操作符
&& 逻辑与 || 逻辑或
int main()
{
int i = 0,a=0,b=2,c=3,d=4;
i = a++ && ++b && d++; //a=1,b=2,c=3,d=4
//i = a++ || ++b || d++; //a=1,b=3,c=3,d=4
return 0;
}
当&&运算时,若出现一个假,则其后表达式不再运算;
当||运算时,若出现一个真,则其后表达式不再运算。
八、条件操作符
三目操作符: expr1 ? expr2 : expr3
九、逗号表达式
逗号表达式,就是用逗号隔开的多个表达式。逗号表达式的执行顺序是自左向右,整个表达式的结果是左后一个表达式的结果。
int ()
{
int a = 1;
int b = 2;
int c = (a > b,a=b+10,a,b=a+1); //c=13
return 0;
}
十、下标引用、函数调用和结构成员
1.下标引用操作符——[ ]
操作数:一个数组名+一个索引值
arr[9] = 10; // 两个操作数是arr和9
2.函数调用操作符——( )
操作数:一个或多个操作数,包含函数名和参数。
3.访问一个结构的成员—— . 和 ->
结构体 . 成员名
//创建一个结构体类型 (struct) struct Stu { char name[20]; int age; char id[20]; }; int main() { //使用struct Stu这和类型创建一个对象s1,并初始化 struct Stu s1 = {"张三", 20 , "2022123456"} printf("%s\n",s1.name); printf("%d\n",s1.age); printf("%s\n",s1.name); return 0; }
结构体指针 -> 成员名
//创建一个结构体类型 (struct) struct Stu { char name[20]; int age; char id[20]; }; int main() { //使用struct Stu这和类型创建一个对象s1,并初始化 struct Stu s1 = {"张三", 20 , "2022123456"} struct Stu* ps = &s1; printf("%s\n",ps->name); printf("%d\n",ps->age); printf("%s\n",ps->name); return 0; }
表达式求值
隐式类型转换——整型提升
在C语言中,为了扩大精度,在算术运算和逻辑运算的过程中将表达式中的长度可能小于int型的整型值(short、char等)转换为int或unsigned,再进行运算,运算结束后结果被截断,存于原变量内。
整型提升过程:按原变量的符号为提升,即在左补符号位的数字直到长度达到int。
1: 00000001 =》00000000000000000000000000000001
-2: 111111110 =》11111111111111111111111111111110
运算结束后,截断,即取原有长度而舍弃左边的多余位。
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
a == 0xb6; //假
b == 0xb600; //假
c == 0xb60000; //真
当对char类型时,0xb6是一个十六进制的数字,是个正数,但是赋值给char类型后,其符号位成为了1,导致在是否相等的判断中,a变量整型提升,成为一个负数,此时a变量为0xffffffb6而判断数字为0x000000b6,二者不相等。
对于short类型同理。
对于int类型,因为没有整型提升的变化,所以逻辑等于两边的值二进制位相同(尽管他们所表达的值不同),所以判断为真。
char c = 1;
sizeof(c); /1
sizeof(+c); /4
sizeof(!c); /4
可以通过sizeof看到整型提升确实在算术运算和逻辑运算中发挥了作用。
隐式类型转换——算术转换
在使用操作符时操作数类型不同且需要同一类型,即按照一定优先级改变某一操作数的类型。
long double > double > float > unsigned long int > long int > unsigned int > int
int a = 1;
float = 2.0;
a + b; //这里则会根据级别,将a由int变为float
操作符的属性
1.优先级
有关优先级的问题,可以参考本人另一篇博客博客。
2.结合性
即考虑从左向右结合(运算)或者从右向左结合。
3.是否控制求值顺序
a = b*c+d*e+f*g;
对于该表达式运算路径不唯一,a = (b*c+d*e)+f*g 或a = (b*c)+(d*e)+(f*g)。在一些情况下可能造成歧义,应该避免。
a = (++i)+(++i)+(++i);
对于该表达式,不同编译器运算结果不同,是非常不合理,无意义的表达式。对于诸如此类运算路径不唯一,可移植性极差的语句应该杜绝。
本文为学习C语言心得与笔记记录,部分举例来源于B站C语言教学up主鹏哥