目录
操作符
分类
算术操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用
函数调用
结构成员
算术操作符
+ - * / %
加 减 乘 除 取模
A%B A除以B取余数,取模时A与B都要为整数
A/B A除以B,当两边全为整数时,结果也为整数,当A与B任意一方为浮点数时(2.0或2.1···),结果可算出小数
移位操作符
<<左移位操作符
>>右移位操作符
移动的是二进制位
右移位操作符
算术右移
需要知道符号位(正0负1)
右边丢弃,左边补原符号位
#include <stdio.h> int main() { int a = -1; int b = a >> 1; //整数二进制表示有:原码、反码、补码 //存储到内存中的是补码 //正数:原码=反码=补码 //10000000000000000000000000000001——-1原码 //11111111111111111111111111111110——-1反码(符号位不变,其他位按位取反 //11111111111111111111111111111111——-1补码(反码+1 //11111111111111111111111111111111——右移位后 printf("%d\n", b); return 0; }
#include <stdio.h> int main() { int a = 16; int b = a >> 1; // 16:00000000000000000000000000010000 //右移后:00000000000000000000000000001000 printf("%d\n", b); return 0; }
逻辑右移
右边丢弃,左边补0
左移位操作符
左边舍弃,右边补0
#include <stdio.h> int main() { int a = 5; //00000000000000000000000000000101——5 int b = a << 1; //00000000000000000000000000001010——移位后 printf("%d\n", b); return 0; }
PS:对于移位操作符,不要移动负数位,这个标准是未定义的
int a = 1;
int b = a >> -1;
PS:移位及位操作符只能作用于整数,不能作用于浮点数
位操作符
& 按位与
| 按位或
^ 按位异或
&——按位与
是按二进制位与
相同位只要有一个为0则为0,两个为1才为1(补码进行运算
#include <stdio.h>
int main()
{
int a = 3;
//00000000000000000000000000000011
int b = 5;
//00000000000000000000000000000101
int c = a & b;
//00000000000000000000000000000001
printf("%d\n", c);
return 0;
}
|——按位或
相同位只要有一个为1则为1,两个为0才为0(补码进行运算
#include <stdio.h>
int main()
{
int a = 3;
//00000000000000000000000000000011
int b = 5;
//00000000000000000000000000000101
int c = a | b;
//00000000000000000000000000000111
printf("%d\n", c);
return 0;
}
^——按位异或
相同位相同为0,相异为1(补码进行运算
#include <stdio.h>
int main()
{
int a = 3;
//00000000000000000000000000000011
int b = 5;
//00000000000000000000000000000101
int c = a ^ b;
//00000000000000000000000000000110
printf("%d\n", c);
return 0;
}
应用:
一
在不创建第三个变量的情况下交换两个值,a=3,b=5,交换后:a=5,b=3
加减法:
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
a = a + b;
b = a - b;
a = a - b;
printf("a=%d,b=%d\n", a, b);
return 0;
}
缺陷:数据可能会溢出
异或:
#include <stdio.h>
int main()
{
int a = 3;
//00000000000000000000000000000011
int b = 5;
//00000000000000000000000000000101
a = a ^ b;
//00000000000000000000000000000110
b = a ^ b;
//00000000000000000000000000000011
a = a ^ b;
//00000000000000000000000000000101
printf("a=%d,b=%d\n", a, b);
return 0;
}
缺陷:执行效率不高,可读性差
二
编写一个代码:求一个整数存储在内存中的二进制中1的个数
#include <stdio.h>
int main()
{
int num = 0;
scanf_s("%d", &num);
//此处用scanf亦可
int count = 0;
while (num)
{
if (num % 2 == 1)
count++;
num = num / 2;
}
printf("%d\n", count);
return 0;
}
缺陷:无法计算负数,负数存储时是补码,而代码计算的是原码
#include <stdio.h>
int main()
{
int num = 0;
scanf_s("%d", &num);
//此处用scanf亦可
int count = 0;
int i = 0;
for ( i = 0; i < 32; i++)
{
if (((num >> i) & 1) == 1)
count++;
}
printf("%d\n", count);
return 0;
}
#include <stdio.h>
int main()
{
int num = 0;
scanf_s("%d", &num);
//此处用scanf亦可
int count = 0;
int i = 0;
while (num)
{
count++;
num = num & (num - 1);
}
printf("%d\n", count);
return 0;
}
赋值操作符
可将自己重新赋值
int a = 10; //不满意
a = 20; //重新赋值
int a = 10; //不满意
int b = 20;
a = a + b; //重新赋值
可连续赋值
int x = 1;
int y = 2;
int a = 3;
a = x = y + 1; //不易理解
x = y + 1;
a = x; //更易理解
复合赋值符
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
例
a = a + 2;
a += 2; //等价
其余复合赋值符用法类似
单目操作符
只有一个操作数
a+b有两个操作数,则此时+为双目操作符
! 逻辑反操作
真变假,假变真(假变为1
#include <stdio.h> int main() { int a = 10; if (a) { printf("hello,world\n"); //a为真打印 } if (!a) { printf("hello,world\n"); //a为假打印 } return 0; }
-------------------------------------------------------------------------------------------------------------------------
- 负值
-------------------------------------------------------------------------------------------------------------------------
+ 正值
-------------------------------------------------------------------------------------------------------------------------
& 取地址
一般与指针一起用
#include <stdio.h> int main() { int a = 10; int* p = &a; *p = 20;//* 解引用操作符,此时a变为20 return 0; }
-------------------------------------------------------------------------------------------------------------------------
sizeof 操作数的类型长度
打印 sizeof 时最好用 %zd (%d也可以
#include <stdio.h> int main() { int a = 10; char c = 'r'; char* p = &c; int arr[10] = { 0 }; //sizeof 计算变量所占内存空间的大小,单位是字节 printf("%zd\n", sizeof(a)); //a 两边()可省略 printf("%zd\n", sizeof(int)); //int 两边()不可省略 printf("%zd\n", sizeof(c)); printf("%zd\n", sizeof(char)); printf("%zd\n", sizeof(p)); printf("%zd\n", sizeof(char*)); printf("%zd\n", sizeof(arr)); printf("%zd\n", sizeof(int [10])); //用 %d 也可以 return 0; }
#include <stdio.h> int main() { short s = 0; int a = 10; printf("%d\n", sizeof(s = a + 5)); //打印2 0 printf("%d\n", s); //不管a是什么类型,只管s的类型,s只有两个字节,即为2 return 0; //sizeof里的表达式不会真正去计算,s的值不会发生变化 }
#include <stdio.h> void test1(int arr[]) { printf("%zd\n", sizeof(arr)); } void test2(char ch[]) { printf("%zd\n", sizeof(ch)); } int main() { int arr[10] = { 0 }; char ch[10] = { 0 }; printf("%zd\n", sizeof(arr)); printf("%zd\n", sizeof(ch)); test1(arr); //传过去的是地址,是指针,打印4(32位)或者8(64位) test2(ch); return 0; }
-------------------------------------------------------------------------------------------------------------------------
~ 对一个数的二进制按位取反
#include <stdio.h> int main() { int a = 0; //00000000000000000000000000000000 printf("%d\n", ~a); //11111111111111111111111111111111——补码(-1 //11111111111111111111111111111110——反码 //10000000000000000000000000000001——原码 return 0; }
#include <stdio.h> int main() { int a = 11; //00000000000000000000000000001011 只将1011中的0变为1 a = a | (1 << 2); //00000000000000000000000000000100——1 << 2 //00000000000000000000000000001111——a | (1 << 2) printf("%d\n", a); //再将1111还原回去 int b = a; b = b & (~(1 << 2)); //11111111111111111111111111111011——~(1 << 2) //00000000000000000000000000001011——b & (~(1 << 2)) printf("%d\n", b); return 0; }
-------------------------------------------------------------------------------------------------------------------------
-- 前置、后置--
#include <stdio.h> int main() { int a = 10; int b = 10; printf("%d\n", --a); //前置--,先--,再使用 printf("%d\n", b--); //后置--,先使用,再-- return 0; }
-------------------------------------------------------------------------------------------------------------------------
++ 前置、后置++
#include <stdio.h> int main() { int a = 10; int b = 10; printf("%d\n", ++a); //前置++,先++,再使用 printf("%d\n", b++); //后置++,先使用,再++ return 0; }
-------------------------------------------------------------------------------------------------------------------------
* 间接访问操作符(解引用操作符
-------------------------------------------------------------------------------------------------------------------------
(类型) 强制类型转换
#include <stdio.h> int main() { int a = (int)3.14; //将浮点数强制转换为int类型 return 0; }
逻辑操作符
&& 逻辑与
两个数全为真时才为真,一个为假即为假
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
int c = a && b;
printf("%d\n", c); //均为真,则为真,c为1
return 0;
}
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++; //结果是1 2 3 4,a++,先使用后++,使用时仍未0,则后面的++b和d++不运算
printf("a=%d\nb=%d\nc=%d\nd=%d\n", a, b, c, d);
return 0;
}
|| 逻辑或
两个数全为假才为假,一个为真即为真
#include <stdio.h>
int main()
{
int i = 0, a = 1, b = 2, c = 3, d = 4;
i = a++ || ++b || d++; //结果是1 3 3 4,a++,使用时仍为0,++b使用是为3(真),则d++不计算
printf("a=%d\nb=%d\nc=%d\nd=%d\n", a, b, c, d);
return 0;
}
条件操作符(三目操作符
exp1 ? exp2 : exp3
表达式1的结果为真,表达式2计算,表达式2的结果是整个表达式的结果
表达式1的结果为假,表达式3计算,表达式3的结果是整个表达式的结果
#include <stdio.h>
int main()
{
int a = 0;
int b = 0;
b = (a > 5 ? 3 : -3);
printf("%d\n", b);
return 0;
}
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int max = 0;
max = (a > b ? a : b); //a与b比大小
printf("%d\n", max);
return 0;
}
逗号表达式
exp1,exp2,exp3,......expN
是用逗号隔开的多个表达式,从左到右依次执行,整个表达式的结果=最后一个表达式的结果
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + b);
printf("%d\n", c); //逗号表达式中前面式子成立与否不影响后续表达式的进行
return 0;
}
逗号表达式不易理解,可读性不高
下标引用、函数调用、结构成员
下标引用操作符
[ ] 操作数为数列名及下标
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
arr[9] = 10; //arr和9为[ ]的操作数
return 0;
}
函数调用操作符
( ) 操作数可有多个,必有函数名
#include <stdio.h>
int get_max(int x, int y)
{
return x > y ? x : y;
}
int main()
{
int a = 10;
int b = 20;
int max = get_max(a, b); //调用get_max函数
return 0; //函数名(get_max)和a、b都是函数调用操作符的操作数
}
结构成员
. 结构体 .成员名
-> 结构体指针 ->成员名
#include <stdio.h>
struct Movie //创建一个结构体类型-struct Movie
{
//成员变量
char name[50];
int time;
char id[20];
};
int main()
{
//使用struct Movie这个类型创建了一个电影对象s1
struct Movie s1 = { "Josee, the Tiger and the Fish",98,"20210820" };
printf("%s\n", s1.name);
printf("%d\n", s1.time);
printf("%s\n", s1.id);
//结构体变量 .成员名
return 0;
}
#include <stdio.h>
struct Movie //创建一个结构体类型-struct Movie
{
//成员变量
char name[50];
int time;
char id[20];
};
int main()
{
//使用struct Movie这个类型创建了一个电影对象s1
struct Movie s1 = { "Josee, the Tiger and the Fish",98,"20210820" };
struct Movie* ps = &s1;
printf("%s\n", (*ps).name);
printf("%d\n", (*ps).time);
printf("%s\n", (*ps).id);
return 0;
}
#include <stdio.h>
struct Movie //创建一个结构体类型-struct Movie
{
//成员变量
char name[50];
int time;
char id[20];
};
int main()
{
//使用struct Movie这个类型创建了一个电影对象s1
struct Movie s1 = { "Josee, the Tiger and the Fish",98,"20210820" };
struct Movie* ps = &s1;
printf("%s\n", ps->name);
printf("%d\n", ps->time);
printf("%s\n", ps->id);
//结构体指针->结构名
return 0;
}
表达式求值
表达式求值顺序是由操作符的优先级和结合性决定的
有些操作符在求值过程中可能会进行类型转换
隐式类型转换
整型提升
C的整形算术运算总是至少以缺省整型类型的精度来进行的
为获得这个精度,表达式中的字符和短整型操作数在使用前被转换为普通整型
char a,b,c;
……
a= b + c;
//b与c的值被提升为普通整型,然后才会执行加法运算
//加法运算完成后,结果将被截断,然后再存储于a中
整型提升方式:
整型提升按照变量的数据类型的符号为来提升的
无符号数直接补0
#include <stdio.h>
int main()
{
char a = 3;
//00000000000000000000000000000011——3的二进制
//00000011——3,发生截断(挑最低字节的内容)
char b = 127;
//00000000000000000000000001111111——127的二进制
//01111111——127
//符号位为什么,整型提升时补的就是什么
//a提升:00000000000000000000000000000011
//b提升:00000000000000000000000001111111
//a+b后:00000000000000000000000010000010
char c = a + b;
//c是char类型,只存8个bit位,结果发生截断
//100000010——c
//打印发生整型提升并取原码:
//111111111111111111111111100000010——补码
//111111111111111111111111100000001——反码
//100000000000000000000000011111110——原码
printf("%d\n", c); //结果是-126
return 0;
}
例1:
#include <stdio.h>
int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000;
if (a == 0xb6)
//a是char类型,发生整型提升
printf("a");
//不打印
if (b == 0xb600)
//b是short类型,也发生整型提升
printf("b");
//不打印
if (c = 0xb6000)
//c是int类型,不发生整型提升
printf("c");
//打印
return 0;
}
例2:
#include <stdio.h>
int main()
{
char a = 1;
printf("%u\n", sizeof(a)); //1
printf("%u\n", sizeof(+a)); //4 +a发生整型提升
printf("%u\n", sizeof(!a)); //1
//%u是打印十进制无符号整数
return 0;
}
算术转换
如果某个操作符的各个操作数属于不同类型,就会进行算术转换,转为一个类型
算术转换优先度:
long double
double
float
unsigned long int
long int
unsigned int
int
不同类型进行计算时会将下面的类型转换为上面的类型
操作符的属性
复杂表达式求值有三个影响的因素
1.操作符的优先级
2.操作符的结合性(优先级相同时在考虑结合性
3.是否控制求值的顺序
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = b + a * 3;
//*的优先级比+更高,先计算*再计算+
return 0;
}
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = b + a + 3;
//当优先级相同时,考虑结合性
//+的结合性为从左到右
//即先算左再算右
return 0;
}
能控制求值顺序的操作符如:&&(左边为假就不算右) ||(左边为真就不算右) ,(逗号,决定结果的是最后一个表达式)……
A * B + C * D + E * F;
//*优先级比+高,只能保证相邻时*先计算,但第三个*不一定比第一个+先计算
int C = 1;
int A = C + --C;
//--比+的优先级高,但有歧义
//先准备第一个C,结果为1
//先计算--再准备第一个C,结果为0
//无法得知+操作符的左操作数的获取在右操作数之前还是之后求值