文章目录
一、 操作符分类
1.算数操作符
2.移位操作符
3.位操作符
4.赋值操作符
5.单目操作符
6.关系操作符
7.逻辑操作符
8.条件操作符
9.逗号表达式
10.下标引用,函数调用和结构体成员
二、算数操作符
+ - *(乘) /(除) %(取模)
整型除法
如果除号两边都为整型,那么求出的商也只会是整型。
在数学上:1/2 = 0.5; 5/2 = 2.5;
在C语言中,就必须舍弃掉小数点后的所有数。1/2 = 0; 2/5 = 2;
int a = 2;
int b = 5;
int c = b/a;
int d = 1/2;
printf("%d\n",c);
printf("%d\n",d);
1. 浮点数除法
如果除号两边存在至少一个浮点数,则求出的商就为浮点数,用浮点类型接收
float a = 5.0/2;
float b = 5/2.0;
float c = 5.0/2.0;
double d = 1/2.0;
printf("%f\n",a);
printf("%f\n",b);
printf("%f\n",c);
printf("%lf\n",d);
2.取模操作符
求的是整除后的余数。取模操作符两端必须是整数
int a = 7%2;// 7/2 = 3......1
printf("%d\n",a);
//非法操作
float a = 7%2.0;
三、原码,反码,补码
由于下面的内容将涉及上述三个知识,我们在这里先进行简单介绍。
原码指的是数据本身转化为二进制形式时的值。
反码指将原码按位取反后的值。
补码指反码+1后的值。
正数的原反补相同,负数的原反补需要进行上述计算。
原码中的最高一位表示符号位。(0表示正,1表示负)
数据在内存中采用补码的形式进行存储,数据进行运算也是在内存中进行运算,数据进行打印时是打印其原码。
int a = 1;
00000000000000000000000000000001 -> 原码/反码/补码
int b = -1;
10000000000000000000000000000001 -> 原码
11111111111111111111111111111110 -> 反码
11111111111111111111111111111111 -> 补码
四、移位操作符
移位操作符实际操作的是二进制位(移动存储在内存中的补码)
<< 左移操作符
>> 右移操作符
// 移位操作符只能操作整数
1.左移操作符
二进制的最左边丢弃,最右边补0
int a = 7;
00000000000000000000000000000111 -> 7的原反补码
int b = a << 1;
//将7的补码左移
00000000000000000000000000001110 -> 14
printf("%d\n",a);
printf("%d\n",b);
------------------------------------------
int a = -7;
10000000000000000000000000000111 -> -7的原码
11111111111111111111111111111000 -> -7的反码
11111111111111111111111111111001 -> -7的补码
int b = a << 1;
//将-7的补码向左移动1位
1111111111111111111111111110010 -> 移动后的补码
//因为打印的是原码,所以需要将补码转换为原码
1111111111111111111111111110001 -> 补码-1得到反码
1000000000000000000000000001110 -> 反码符号位不变,其他位按位取反得到原码
printf("%d\n",a);-7
printf("%d\n",b);-14
2.右移操作符
右移操作符分为:算术右移和逻辑右移
算术右移:右边丢弃,左边补符号位
逻辑右移:右边丢弃,左边补0
大多数编译器都是采用算术右移,以下代码皆以算术右移为主
int a = 7;
00000000000000000000000000000111 -> 7的原反补码
int b = 7 >> 1;
//将7的补码向右移动移位,最左边补符号位0
00000000000000000000000000000011 -> 3
printf("%d\n",a);//7
printf("%d\n",b);//3
------------------------------------
int a = -7;
10000000000000000000000000000111 -> -7的原码
11111111111111111111111111111000 -> -7的反码
11111111111111111111111111111001 -> -7的补码
int b = a >> 1;
//将-7的补码向右移动1位,最左边补符号位
11111111111111111111111111111100 -> 移动后的补码
//打印的是原码
11111111111111111111111111111011 -> 补码-1求出反码
10000000000000000000000000000100 -> 符号位不变,其他位按位取反求出原码
printf("%d\n",a);//-7
printf("%d\n",b);//-4
移动操作符的位数不能移动负数位,这是标准未定义行为
int a = 7;
a << -1;//error
五、位操作符
&(按位与) |(按位或) ^(按位异或)
& - 按(二进制)位与
| - 按(二进制)位或
^ - 按(二进制)位异或
1.&操作符
两个对应的二进制位如果同为1则为1,只要有0,则为0
int a = 3;
00000000000000000000000000000011 -> 3的补码
int b = -5;
10000000000000000000000000000101 -> -5的原码
11111111111111111111111111111010 -> -5的反码
11111111111111111111111111111011 -> -5的补码
int c = a & b;
// 3 & -5
//两个对应的二进制位同时为1才为1,否则为0
00000000000000000000000000000011 -> a & b 的补码/原码
printf("%d\n",c);//3
2.|操作符
两个对应的二进制数如果同为0,则为0,只要有1,则为1
int a = 3;
00000000000000000000000000000011 -> 3的补码
int b = -5;
10000000000000000000000000000101 -> -5的原码
11111111111111111111111111111010 -> -5的反码
11111111111111111111111111111011 -> -5的补码
int c = a | b;
// 3 | -5
//只要有1,就为1;同时为0,才为0
1111111111111111111111111111011 -> 3 | -5 的补码
//因为符号为负,所以需要求原码
1111111111111111111111111111010 ->补码-1得反码
1000000000000000000000000000101 ->反码中符号位不变,其他位按位取反得原码
printf("%d\n",c);//-5
3.^操作符
对应的二进制位相同为0,相异为1
int a = 3;
00000000000000000000000000000011 -> 3的补码
int b = -5;
10000000000000000000000000000101 -> -5的原码
11111111111111111111111111111010 -> -5的反码
11111111111111111111111111111011 -> -5的补码
int c = a ^ b;
//3 ^ -5
//对应的二进制位相同为0,相异为1
11111111111111111111111111111000 -> 3 ^ -5的补码
//因为负数的补码原码不同,需要求原码
11111111111111111111111111110111 -> 补码-1得反码
10000000000000000000000000001000 -> 反码中的符号位不变,其他位按位取反得原码
printf("%d\n",c);//-8
4. ^操作符的运用
不能创建临时变量(第三变量),实现两个数的交换
第一种普遍解法(需要创建临时变量)
int main()
{
int a = 10;
int b = 20;
int tmp = 0;
tmp = a;
a = b;
b = tmp;
return 0;
}
第二种求法,不创建临时变量,但是可能出现溢出
int main()
{
int a = 10;
int b = 20;
a = a + b;
b = a - b;
a = a - b;
return 0;
}
第三种解法,不创建临时变量,也不会出现溢出
int main()
{
int a = 10;
int b = 20;
a = a ^ b;
b = a ^ b;
a = a ^ b;
return 0;
}
原理
1 ^ 1 = 0; 2 ^ 2 = 0; 3 ^ 3 = 0;… n ^ n = 0;
0 ^ 1 = 1; 0 ^ 2 = 2; 0 ^ 3 = 3;… 0 ^ n = n;
3 ^ 3 ^ 5 = 5; 3 ^ 5 ^ 3 = 5;
从上面的式子可以得出:
任何数异或自身都等于0;
0异或任何数都等于任何数本身;
异或支持交换律;
a = a ^ b;// a = 10 ^ 20;
b = a ^ b;// b = 10 ^ 20 ^ 20; -> b = 10 ^ 0 = 10;
a = a ^ b;// b = 10 ^ 20 ^ 10; -> b = 0 ^ 20 = 20;
//这种算法操作于32个二进制位上,所以不会出现溢出现象
//在实际操作中,主要运用第一种方法,此种方法运行效率较低,且只适用于整数
六、赋值操作符
赋值操作符可以将你不满意的值重新进行赋值
int weight = 120;//体重 - 初始化
weight = 90;//赋值
double salary = 1000.0;
sakary = 2000.0;
赋值操作符也可以连续使用
int a = 10;
int x = 0;
int y = 20;
a = x = y + 1;//连续赋值
//语法支持连续赋值,但不建议使用-可读性较差
七、复合赋值符
使用后将值赋给自身
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
八、单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个二进制按位取反
-- 前置,后置--
++ 前置,后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
1.单目操作符:只有1个操作数
2 !逻辑反操作
假的变为真,真的变为假
C语言中,0表示假,非0表示真
int flag = 0;//假
if(!flag)//!假 -> 真 -> 进入循环
{
//进入循环;
}
flag = 1;
if(!flag)//!真 -> 假 -> 不进入循环
{
;
}
3.正号+,负号-
int a = +10;//10
int b = +a;//10
int c = -10;
int d = +c;//-10
//+并没有太大的价值
----------------------------
int a = -10;//-10
int b = -a;//10
//-能将正的变为负的,将负的变为正的
4.&取地址操作符
int a = 10;
printf("%p\n",&a);//取出a的地址,地址用%p进行打印
5.sizeof操作符
int a = 10;
int n = sizeof(a);//计算的是a所占内存的大小,单位是字节
--------------------------
int n = sizeof(int);//计算类型所占内存的大小
printf("%d\n",n);//4
--------------------------
int arr[5] = {0};
int n = sizeof(arr);//计算数组所占内存空间的大小
printf("%u\n",n);//5*4 = 20
证明:sizeof是操作符而不是库函数
int a = 10;
int n1 = sizeof(a);
int n2 = sizeof(int);
int n3 = sizeof a;
int n4 = sizeof int;//error
//sizeof返回值是unsigned int ,所以我们用 %u 来接收
printf("%u\n",n1);
printf("%u\n",n2);
printf("%u\n",n3);
printf("%u\n",n4);
从上述代码中,我们可以得出结论:在求变量所占内存空间大小时,sizeof的操作数可以不打括号,这证明了sizeof是操作数而不是函数;但如果sizeof的操作数是数据类型时,则必须打括号,否则将无法运行;
练习
#include<stdio.h>
void test1(int arr[])
{
printf("%u\n",sizeof(arr));
}
void test2(char ch[])
{
printf("%u\n",sizeof(ch));
}
int main()
{
int arr[10] = {0};
char ch[10] = {0};
printf("%u\n",sizeof(arr));
printf("%u\n",sizeof(ch));
test1(arr);
test2(ch);
return 0;
}
请先进行思考后,再查看解析
解析:
#include<stdio.h>
void test1(int arr[])//这里接收的只是首元素地址:arr+0
{
printf("%u\n",sizeof(arr));//sizeof里的arr实际上是一个指针,指针占空间大小为4/8个字节
//32位下指针为4个字节
//64位下指针为8个字节
}
void test2(char ch[])//接收首元素地址 ch+0
{
printf("%u\n",sizeof(ch));//ch实际上是一个指针,所以占空间大小为4/8个字节
//指针无论是哪种类型,都是4/8个字节
//不能想当然的因为指针是char*类型的就认为它只有1个字节
}
int main()
{
int arr[10] = {0};//创建arr数组,为10个int类型,大小为10*4个字节
char ch[10] = {0};//创建ch数组,为10个char类型,大小为10*1个字节
printf("%u\n",sizeof(arr));//40
printf("%u\n",sizeof(ch));//10
test1(arr);//数组名作参数,只是将数组首元素地址传过去
test2(ch);
return 0;
}
sizeof 与 strlen的区别
1.sizeof是操作符,strlen是库函数;
2.sizeof用于求数据所占内存空间的大小,单位是字节。strlen只能用于求字符串长度,遇’\0’便终止
6.~按位取反操作符
int a = 0;
//~是按二进制按位取反
00000000000000000000000000000000 -> a的补码
int c = ~a;
11111111111111111111111111111111 -> ~a的补码
//打印需要原码
11111111111111111111111111111110 -> 补码-1得到反码
10000000000000000000000000000001 -> 反码中符号位不变,其他位按位取反得到原码
printf("%d\n",c);//-1
将指定二进制位进行修改,修改完后再修改回原来的数
int a = 13;
00000000000000000000000000001101 -> 13的补码
//要求将13的第五位二进制位修改为1
//只需要在第五位的位置上或上一个1即可
int b = 1 << 4;
00000000000000000000000000001101 - a
00000000000000000000000000010000 - b
int c = a | b;//29
//a | b
00000000000000000000000000011101 - c - 补码
--------------------------------------------
//修改回原本的值
//只需要在第五位上与上一个0即可,但仍需保持原本其余位上的1不变,所以只有第五位为0,其余位都为1
11111111111111111111111111101111 - d
int d = ~b;
int e = c & d; //13
7.++ --操作符
前置++/–遵循先++/–,再使用的原则。
后置++/–遵循先使用,再++/–的原则。
int a = 3;
int b = ++a;//先++,后使用
//相当于:
//a += 1;
//b = a;
printf("%d\n",a);//4
printf("%d\n",b);//4
-----------------------------
int c = 3;
int d = c++;
//相当于:
//d = c;
//c += 1;
printf("%d\n",c);//4
printf("%d\n",d);//3
int a = 3;
int b = --a;
//相当于:
//a -= 1;
//b = a;
printf("%d\n",a);//2
printf("%d\n",b);//2
-----------------------
int c = 3;
int d = c--;
//相当于:
//d = c;
//c -= 1;
printf("%d\n",c);//2
printf("%d\n",d);//3;
其余用法
int a = 10;
printf("%d\n",a--);//先使用 a = 10;再-- a -= 1;
printf("%d\n",a);// a = 9
void test(int a)
{
printf("%d\n",a);
}
int main()
{
int a = 10;
test(a--);
return 0;
}
8.*间接访问操作符
int a = 10;
int* pa = &a;
*pa = 20;
printf("%d\n",a);
9.(类型) 强制类型转换操作符
int a = 3.14;//此代码可以运行,但会报警告----3.14是double类型
int b = (int)3.14;//可执行且不会报警告
//将3.14转换为int类型,舍弃小数点后所有位
srand((unsigned int)time(NULL)的强制类型转换
由于unsigned int 与time_t 类型不相同,需要将time的返回值强制类型转换为(unsigned int)
九、关系操作符
>
>=
<
<=
!= 用于测试"不相等"
== 用于测试"相等"
这些关系运算符比较简单,没什么可讲,但是我们要注意一些运算符使用时候的陷阱
比如:
在编程中== 与 = 不小心写错,导致的错误
if(3 == 5)//正确
{
;
}
if(3.0 == 5.0)//需要注意精度问题
{
;
}
//无法用 == 来比较两字符串是否相等
if("abc" == "abcdef")//这样是比较两个字符串的首字符的地址
{
;
}
比较两字符串是否相等,需要用到strcmp
if(strcmp("abc","abcdef")
{
;
}
十、逻辑操作符
&& 逻辑与
|| 逻辑或
注意:
区分逻辑与和按位与
区分逻辑或和按位或
按位与和按位或操作的是二进制位
逻辑与和逻辑或判断的是数据值是否为0
1.逻辑与&
int a = 3;
int b = 5;
int c = a && b;
//a,b都为真,则a&&b 则c为真(1)
//若a,b中有一个为假,则c为假(0)
int d = 0;
int e = a && d;
printf("%d\n",c);
printf("%d\n",e);
2.逻辑或
int a = 3;
int b = 0;
int c = a || b;
//只要a,b中有一个为真,则a || b则为真(1)
//只有a,b都为假,则a || b 才为假(0)
int d = 0;
int e = a || d;
printf("c=%d\n",c);
printf("e=%d\n",e);
3.练习
#include<stdio.h>
int main()
{
int i = 0,a=0,b=2,c=3,d=4;
i = a++ && ++b && d++;
printf("a = %d\n b = %d\n c = %d\n d = %d\n",a,b,c,d);
}
请思考后再查看解析
解析
#include<stdio.h>
int main()
{
int i = 0,a=0,b=2,c=3,d=4;
i = a++ && ++b && d++;
//a = 0;0&&上任意数皆为0,所以后面步骤不需要再进行,i直接等于0,最后再 a += 1;
printf("a = %d\n b = %d\n c = %d\n d = %d\n",a,b,c,d);
//a = 1,b = 2,c = 3,d = 4;
return 0;
}
总结:
&& 左边为假,右边就不计算了
| | 左边为真,右边就不计算了
思考:下述代码执行后打印的是什么,自行检测
#include<stdio.h>
int main()
{
int i = 0,a=0,b=2,c=3,d=4;
i = a++ || ++b || d++;
printf("a = %d\n b = %d\n c = %d\n d = %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++;
printf("a = %d\n b = %d\n c = %d\n d = %d\n",a,b,c,d);
return 0;
}
十一、条件操作符(三目操作符)
expr1 ? expr2 : expr3
//1如果为真,执行表达式2
//1如果为假,执行表达式3
练习1
int main()
{
int a = 3;
int b = 0;
if(a > 5)
b = 3;
else
b = -3;
return 0;
}
利用条件操作符转换上述代码
int main()
{
b = (a > 5) ? 3 : -3;
return 0;
}
练习2:利用条件操作符求两个数较大值
int main()
{
int a = 3;
int b = 5;
int max = 0;
max = a > b ? a : b;
reuturn 0;
}
十二、逗号表达式
expr1,expr2,expr3....exprN
逗号表达式,就是用逗号隔开的多个表达式
逗号表达式,从左到右依次执行,整个表达式的结果是最后一个表达式的结果。
int a = 1;
int b = 2;
int c = (a > b , a = b + 10, b = a + 1);//逗号表达式
//a > b 没啥实际效果
//a = 2 + 10; -> a = 12
//b = 12 + 1; b = 13;
//c的结果是最后一个表达式的结果,即:
//c = b;//13
a = get_val();
count_val(a);
while(a > 0)
{
a = get_val();
count_val(a);
}
//上述代码出现重复,可以使用逗号表达式简化
while(a = get_val(),count_val(a),a>0)
{
;
}
十三、下标引用,函数调用和结构成员
1.下标引用
int arr[10] = {0};
arr[7] = 8;//[]就是下标引用操作符
//arr[7] -> 实质: *(arr+7)
//*(arr+7) -> *(7+arr)
//*(7+arr) -> 7[arr]
printf("%d\n",arr[7]);
printf("%d\n",7[arr]);
2.函数调用
int Add(int x,int y)
{
return x + y;
}
int main()
{
int a = 10;
int b = 20;
//函数调用
int c = Add(a,b);//()就是函数调用操作符,操作数:函数名 + 参数
//函数至少有一个操作数:函数名
return 0;
}
3.结构体成员
struct Stu
{
char name[20];
int age;
double score;
}
void set_stu(struct Stu ss)
{
//字符串不能直接赋值
strcpy(ss.name,"zhangsan");//字符串拷贝
ss.age = 20;
ss.score = 100.0;
};
void print_stu(struct Stu ss)
{
printf("%s %d %lf\n",ss.name,ss.age,ss.score);
}
int main()
{
struct Stu s = {0};
set_stu(s);//错误示范
print_stu(s);
return 0;
}
原因:
要修改结构体成员时,需要进行数据修改,所以需要传地址来间接修改。
正确代码
#include<stdio.h>
#include<string.h>
struct Stu
{
char name[20];
int age;
double score;
};
void set_stu(struct Stu* ss)
{
//字符串不能直接赋值
strcpy(ss->name,"zhangsan");//字符串拷贝
ss->age = 20;
ss->score = 100.0;
//*(ss). 等价于 ss->
}
void print_stu(struct Stu ss)
{
printf("%s %d %lf\n",ss.name,ss.age,ss.score);
}
int main()
{
struct Stu s = {0};
set_stu(&s);//需要修改数据,则用址传递
print_stu(s);
return 0;
}
注意:
如果左边为结构体指针,->可以指向结构体成员
如果左边为结构体对象,. 可以指向结构体成员
在打印时,也可以进行传地址进行打印,这样所创建的空间会大大减小,节约空间。
表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换其他类型。
int a = 2 + 6/3;
// /的优先级比 + 的优先级高,所以先算/再算+
// a = 2 + 2;
int b = 2 + 2 + 3;
//同为+,优先级相同,此时需要比较结合性
1.隐式类型转换
C的整型算术运算总是至少以缺省整型类型得到精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前就被转换为普通整型,这种转换称为整型提升。
char a = 5;
char b = 126;
char c = a + b;
//计算c的大小
printf("%d\n",c);
//char类型以整型进行计算,需要用到整型提升
如何进行整型提升?
整型提升是按照变量的数据类型的符号位来提升的
//负数的整型提升
char c = -1;//-1是整型,是32个bit位
10000000000000000000000000000001//-1的原码
11111111111111111111111111111110//-1的反码
11111111111111111111111111111111//-1的补码
//char只有8个bit位,取补码的后八位存放
11111111 - c
//进行整型提升:
//符号位为1,转换为32位,则全补1
11111111111111111111111111111111//c的整型提升
//正数的整型提升
char d = 1;
//变量d的二进制位(补码)中只有8个bit位
00000001 - d
//因为char 为有符号的 char
//所以整型提升的时候,高位补充符号位,即为0
//提升之后的结果是:
00000000000000000000000000000001//d的整型提升
//无符号整型提升,高位补0
char a = 5;
//a整型提升
00000000000000000000000000000101 - a的补码
00000101 - a
//整型提升
00000000000000000000000000000101 - a的整型提升
char b = 126;
00000000000000000000000001111110 - b的补码
01111110 - b
00000000000000000000000001111110 - b的整型提升
char c = a + b;
//a+b
00000000000000000000000010000011 - c的补码
10000011 - c
11111111111111111111111110000011 - c的整型提升 - 补码
11111111111111111111111110000010 - 反码
10000000000000000000000001111101 - 原码 -> -125
//计算c的大小
printf("%d\n",c);//-125
2.算术转换
如果操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另外一个操作数的类型,否则操作无法进行。下面的层次体系成为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果,某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
但是算术转换要合理,要不然会有潜在的问题。