一、算数操作符
1、+(加操作符)
用于将两个数相加,例:3 + 3结果为6
2、-(减操作符)
用于将两个数相减,例:3 - 3结果为0
3、*(乘操作符)
用于将两个数相乘,例:3 * 3结果为9
4、/(除操作符)
用于将两个数相除,例:3 / 3结果为1
有两种模式,整数除法和浮点数除法:
整数除法,/的两个操作数都是整数,例:1 / 3结果为0,因为为整数除法,商0余1;
浮点数除法,/的两个操作数其中有一个是浮点数,例:1.0 / 4结果为0.25。
5、%(取余操作符)
%的两个操作数必须是整数,作用是在整数除法中获取余数,例:1 % 3的结果为1
#include <stdio.h>
int main()
{
int a = 1, b = 2;
printf("%d\n", a + b);
printf("%d\n", a - b);
printf("%d\n", a * b);
printf("%d\n", a / b);
printf("%d\n", a % b);
return 0;
}
6、++(自增操作符)
将操作数加一,例a = 3,则a++为4
int a = 1;
a++;
printf("%d\n",a);
(1)后置++
先赋值,后运算;先操作,后计算。
int a = 0;
int b = a++;//后置++,先赋值后运算
//相当于b = a;a = a + 1
printf("a = %d\nb = %d\n", a, b);//最终a = 1,b = 0
(2)前置++
先运算,后赋值;先计算,后操作。
int a = 0;
int b = ++a;//前置++,先运算后赋值
//相当于a = a + 1;b = a
printf("a = %d\nb = %d\n", a, b);//最终a = 1,b = 1
7、--(自减操作符)
将操作数减一,例a = 3,则a--为2
int a = 1;
a--;
printf("%d\n",a);
(1)后置--
先赋值,后运算;先操作,后计算。
int a = 0;
int b = a--;//后置--,先赋值后运算
//相当于b = a;a = a - 1
printf("a = %d\nb = %d\n", a, b);//最终a = -1,b = 0
(2)前置--
先运算,后赋值;先计算,后操作。
int a = 0;
int b = --a;//前置--,先运算后赋值
//相当于a = a - 1;b = a
printf("a = %d\nb = %d\n", a, b);//最终a = -1,b = -1
自增和自减操作符分为前置和后置,详细请看我之前的文章《C语言——自增与自减-CSDN博客》。
二、关系操作符
1、==
检查两个操作数的值是否相等,如果相等则条件为真,否则为假。
(1 == 2) 为假。
2、!=
检查两个操作数的值是否相等,如果不相等则条件为真,否则为假。
(1 != 2) 为真。
3、>
检查左操作数的值是否大于右操作数的值,如果是则条件为真,否则为假。
(1 > 2)为假
4、<
检查左操作数的值是否小于右操作数的值,如果是则条件为真,否则为假。
(1 < 2)为真
5、>=
检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真,否则为假。
(1 >= 2)为假
6、<=
检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真,否则为假。
(1 <= 2)为真
#include <stdio.h>
int main()
{
int a = 1, b = 2;
if (a == b)
{
printf("a == b ture\n");
}
if (a != b)
{
printf("a != b ture\n");
}
if (a > b)
{
printf("a > b ture\n");
}
if (a < b)
{
printf("a < b ture\n");
}
if (a >= b)
{
printf("a >= b ture\n");
}
if (a <= b)
{
printf("a <= b ture\n");
}
return 0;
}
结果为:
三、逻辑操作符
1、&&
称为逻辑与运算符。如果两个操作数都非零,则条件为真。
(1 && 0) 为假。
一些性质:
(1)短路性质:当运算符&&
前的表达式为假(即为0)时,整个逻辑表达式的结果已经确定为假,那么编译器不会再去计算&&
后面的表达式。这被称为"短路",因为后面的表达式被"短路"掉了。这样不仅可以提高效率,有时也可以用来在&&
后面的表达式中避免潜在的错误(比如除0错误)。
(2)结合性:&&
具有左结合性,即多个&&
运算符在表达式中从左向右依次结合和计算。
例:
#include <stdio.h>
int main()
{
int a = 0, b = 1, m = 1, n = 0;
(m = a > b) && (n = a < b);//&&有从左向右的结合性,这里>优先级大于=,表示将a > b的值赋给m,这里a > b为假,返回0,所以m为0,但是&&有短路性质,所以后面的表达式不会进行,所以n还是0
printf("m = %d\nn = %d\n", m, n);
return 0;
}
2、||
称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。
(1 || 0) 为真。
(1)短路性质:如果||
运算符前的表达式为真(即非0),那么整个逻辑表达式的结果已经确定为真,编译器将不会计算||
后面的表达式。这被称作“短路”,因为计算过程在确定结果后就停止了,不再继续执行后面的表达式。
(2)结合性:||
运算符具有左结合性,这意味着在没有括号改变计算顺序的情况下,从左向右依次对表达式进行逻辑或运算。
#include <stdio.h>
int main()
{
int a = 0, b = 1, m = 1, n = 0;
(n = a < b) || (m = a > b);//||有从左向右的结合性,这里>优先级大于=,表示将a < b的值赋给n,这里a < b为真,返回1,所以n为1,但是||有短路性质,所以后面的表达式不会进行,所以m还是1
printf("m = %d\nn = %d\n", m, n);
return 0;
}
3、!
称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。
!(1 && 0) 为真。
#include <stdio.h>
int main()
{
if (1 && 0)
{
printf("1 && 0 ture\n");
}
if (1 || 0)
{
printf("1 || 0 ture\n");
}
if (!(1 && 0))
{
printf("!(1 && 0) ture\n");
}
return 0;
}
四、位运算符
位操作符的操作对象都是二进制位。整数数据在计算机中存储是以补码的形式存储的,位操作符的操作对象是补码。
1、<<(左移操作符)
整数数据在计算机中存储是以补码的形式存储的,所以移位操作符的操作对象也是补码,相关致死可以看我之前的文章《C语言——原码,反码,补码-CSDN博客》。
左移操作符的规则:
将操作数的所有位向左移动指定的位数。左移 n 位相当于乘以 2 的 n 次方。
二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。
例1:正数左移
int a = 7;
int b = a << 1;
printf("%d\n",b);
左移操作不会不会对原来的变量造成改变。
将7左移一位,正数的原码、反码、补码是一样的
最后变成
转换为十进制为14。
例2:负数左移
int a = -7;
int b = a << 1;
printf("%d\n",b);
将-7左移一位
最后变成
转化为原码
转换为十进制为-14
2、>>(右移操作符)
右移操作符相对与左移操作符有些复杂,因为它有两种移位方式:
(1)逻辑移位
右边丢弃,左边补零。
例1:正数逻辑右移
正数7
转化为十进制为3
例2、负数逻辑右移
负数-7
原码
补码
逻辑右移
变成
因为符号位为0,检测为正数,正数的补码与原码一致
转化为十进制为
(2)算数移位:
将操作数的所有位向右移动指定的位数。如果是偶数右移n位相当于除以 2 的 n 次方,如果是奇数,向下取偶后除以2的n次方。二进制右移运算符。将一个数的各二进制位全部右移若干位,左补原符号位(即正数左补 0,负数左补 1),右边丢弃。
大多数编译器使用的是算数右移。
例3:正数算数右移
正数7
右移左边补零
变为
转化为十进制为3
7向下取偶后除以2,6 / 2 = 3
例4、负数算数右移
负数-7
原码
补码
右边丢弃,左边补1
变成
符号位为1,检测为负数,转换为原码为
转化为十进制为-4
-7向下取偶后除以2,-8 / 2 = -4
3、& (按位与)AND
对两个操作数的每一位执行逻辑与操作,如果两个相应的位都为 1,则结果为 1,否则为 0。
按位与操作,按二进制位进行"与"运算。运算规则:
0&0=0;
0&1=0;
1&0=0;
1&1=1;
例:3 & -7
转换为补码
进行按位与操作
因为结果为正数,结果转换为十进制为1
int b = 3 & -7;
printf("%d\n",b);
4、| (按位或)OR
对两个操作数的每一位执行逻辑或操作,如果两个相应的位其中有一个为1或两个都为1,则结果为1,否则为0。
按位或运算符,按二进制位进行"或"运算。运算规则:
0|0=0;
0|1=1;
1|0=1;
1|1=1;
例:3 | -7
转换为补码
进行按位或操作
由于结果为负数,将结果转换为原码
结果转换为十进制为-5
int b = 3 | -7;
printf("%d\n",b);
5、^ (按位异或)XOR
对两个操作数的每一位执行逻辑异或操作,如果两个相应的位值相同,则结果为 0,否则为 1。
异或运算符,按二进制位进行"异或"运算。运算规则:
0^0=0;
0^1=1;
1^0=1;
1^1=0;
(1)例:3 ^ -7
转换为补码
进行按位异或操作
由于结果为负数,将结果转换为原码
结果转换为十进制为-6
int b = 3 ^ -7;
printf("%d\n",b);
(2)异或的特点
(i)a ^ a = 0
3 ^ 3 = 0
其实任意整数自己对自己异或的结果都是0,即a ^ a = 0。
(ii)0 ^ a = a
0 ^ 3 = 3
其实0对任意整数进行异或操作结果都是此整数,即0 ^ a = a。
(iii)交换律a ^ b ^ c = b ^ a ^ c = c ^ b ^ a = a ^ c ^ b
2 ^ 3 ^ 4 =
3 ^ 2 ^ 4=
4 ^ 3 ^ 2=
2 ^ 4 ^ 3
int a = 2 ^ 3 ^ 4;
int b = 3 ^ 2 ^ 4;
int c = 4 ^ 3 ^ 2;
int d = 2 ^ 4 ^ 3;
printf("%d %d %d %d\n",a,b,c,d);
(3)例:不创建临时变量,交换两个数
#include <stdio.h>
int main()
{
int a = 3, b = 7;
printf("交换之前a = %d,b = %d\n", a, b);
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, b = 7;
printf("交换之前a = %d,b = %d\n", a, b);
a = a ^ b;//得到a的值为3 ^ 7
b = a ^ b;//得到b的值为3 ^ 7 ^ 7,符合交换律,交换成7 ^ 7 ^ 3,由于a ^ a = 0,所以结果为3,这时b的值为3
a = a ^ b;//得到a的值为3 ^ 7 ^ 3,符合交换律,交换为3 ^ 3 ^ 7,由于a ^ a = 0,所以结果为7,这时a的值为7
//完成交换
printf("交换之后a = %d,b = %d\n", a, b);
return 0;
}
这种方法只适用于整数,浮点数无法进行位操作。而且这种操作是用时间换空间,操作需要的算力较大,时间较长,虽然节省了空间,但是没有直接创建临时变量更好。
6、~ (按位取反)
对操作数的每一位执行逻辑取反操作,即将每一位的 0 变为 1,1 变为 0。
取反运算符,按二进制位进行"取反"运算。运算规则:
~00000001得到11111110
对整数3按位取反
进行取反操作
结果为负数,转换为原码
转换为十进制为-4
位运算符作用于位,并逐位执行操作。&、 | 和 ^ 的真值表如下所示:
p | q | p & q | p | q | p ^ q |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
五、赋值操作符
简单赋值操作符
1、=
赋值操作符,简单的赋值运算符,把右边操作数的值赋给左边操作数。
a = 1;
C语言支持连续赋值,对于下面的几种赋值操作符也可以连续赋值。
a = b = 1;
从右向左运算,将1赋值给b,将b赋值给a。
但是不建议使用,在调试是无法看到具体过程,而且可读性较低。
复合赋值操作符
2、+=
加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数。
C += A 相当于 C = C + A
3、-=
减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数。
C -= A 相当于 C = C - A
4、*=
乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数。
C *= A 相当于 C = C * A
5、/=
除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数。
C /= A 相当于 C = C / A
6、%=
求模且赋值运算符,求两个操作数的模赋值给左边操作数。
C %= A 相当于 C = C % A
7、<<=
左移且赋值运算符。
C <<= 2 等同于 C = C << 2
8、>>=
右移且赋值运算符。
C >>= 2 等同于 C = C >> 2
9、&=
按位与且赋值运算符。
C &= 2 等同于 C = C & 2
10、|=
按位或且赋值运算符。
C |= 2 等同于 C = C | 2
11、^=
按位异或且赋值运算符。
C ^= 2 等同于 C = C ^ 2
六、其他操作符
1、-(负号)
与数学上的负号作用相似。
int a = 1;
int b = -1;
printf("-a = %d\n-b = %d\n", -a, -b);
2、+(正号)
与数学上的正号作用相似。
int a = 1;
int b = -1;
printf("+a = %d\n+b = %d\n", +a, +b);
3、&(取地址操作符)
取出一个变量在内存中的地址。
int a = 1;
printf("a的地址是 %p\n", &a);
4、sizeof()
sizeof是用来计算变量(或类型)所占内存空间大小,不关注内存中的存储内容,单位是字节。
printf("%zu\n", sizeof(int));
printf("%zu\n", sizeof(float));
printf("%zu\n", sizeof(char));
sizeof输出的数据类型是size_t,格式字符串可用%zu或%zd。
在用sizeof求变量的大小时,可以不带括号,但求类型时不能不带括号。
int i = 6;
printf("%zu\n", sizeof i);
由此也可以证明sizeof是操作符,不是函数。
5、*间接访问操作符(解引用操作符)
通过一个地址找到地址指向的变量。
int i = 0;
int* p = &i;//p为指向i的指针变量
*p = 1;//通过p指针找到i变量,对i变量操作,将1赋值给i
printf("%d", i);
6、(类型)强制转换类型
将数字或变量强制转换为一种类型。。
int i = 6.6;
可改成
int i = (int)6.6;
7、? : (条件操作符)三目操作符
表达式1 ? 表达式2 : 表达式3
表达式1为真时,表达式2执行,表达式3不执行,整个表达式的结果为表达式2的结果;表达式1为假时,表达式2不执行,表达式3执行,整个表达式的结果为表达式3的结果。
例:
#include <stdio.h>
int main()
{
int a = 0, b = 1;
int max = a > b ? a : b;
int min = a < b ? a : b;
printf("max = %d\nmin = %d\n", max, min);
return 0;
}
8、exp1 , exp2, ... , expn(逗号表达式)
逗号表达式是用逗号隔开的一系列表达式,从左向右依次执行,表达式的结果是最后一个表达式的结果。
int a = 0, b = 1,c = 0;
int d = (a = 2, b = a + 1, c = b);
printf("%d %d %d %d\n", a, b, c, d);
9、[](下标引用操作符)
[]的两个操作数是数组名和索引值,使用索引值和数组名可以找到这个索引对应的数据。
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", arr[0]);//索引为0的数据为1
这里有个很好玩的,既然[]的操作数是两个,那例如3 + 2中的+是操作符,而它的操作数3和2可以交换,即3 + 2 等价于 2 + 3,那arr[0]可不可以写成0[arr]呢,答案是可以的。
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", 0[arr]);//索引为0的数据为1
这更能证明它的操作数是两个。
10、()(函数调用操作符)
使用可以进行对函数进行调用,接收多个操作数,其中一个是函数名,其他的是传给函数的实参。
#include <stdio.h>
void Print(int x)
{
printf("%d\n", x);
}
int main()
{
int a = 0;
Print(a);//这里的()就是函数调用操作符
return 0;
}
11、. 点操作符(结构成员访问操作符)
用于直接访问结构体变量或联合体变量的成员。当你有一个结构体变量,而不是指针时,你会使用点操作符来访问其成员。
#include <stdio.h>
struct Student
{
char name[10];
int age;
int high;
};
int main()
{
struct Student z = { "张三",18,180};
printf("姓名 %s\n年龄 %d\n身高 %d\n", z.name, z.age, z.high);
return 0;
}
12、-> 箭头操作符(结构成员访问操作符)
用于通过结构体指针访问其成员。当你有一个指向结构体的指针而不是结构体变量本身时,你会使用箭头操作符来间接访问其成员。
#include <stdio.h>
struct Student
{
char name[10];
int age;
int high;
};
int main()
{
struct Student z = { "李四",19,190};
struct Student* pz = &z;
printf("姓名 %s\n年龄 %d\n身高 %d\n", pz->name ,pz->age ,pz->high);
return 0;
}
七、操作符优先级
类别 | 操作符 | 结合性 |
---|---|---|
后缀 | () [] -> . ++ - - | 从左到右 |
一元 | + - ! ~ ++ - - (type)* & sizeof | 从右到左 |
乘除 | * / % | 从左到右 |
加减 | + - | 从左到右 |
移位 | << >> | 从左到右 |
关系 | < <= > >= | 从左到右 |
相等 | == != | 从左到右 |
位与 AND | & | 从左到右 |
位异或 XOR | ^ | 从左到右 |
位或 OR | | | 从左到右 |
逻辑与 AND | && | 从左到右 |
逻辑或 OR | || | 从左到右 |
条件 | ?: | 从右到左 |
赋值 | = += -= *= /= %=>>= <<= &= ^= |= | 从右到左 |
逗号 | , | 从左到右 |