算数操作符
+ - * / % ( 加法,减法,乘法,除法(商),取模(取余) )
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
int a = 5%2; //商2余1--结果为1
int A = 5/2; // --结果为2
//float b=5%2.0; 无法使用
float b =5/2.0; //正确(用%f),结果为2.500000
printf("a=%d\n",a);
return 0;
}
还要注意:
除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数% 操作符的两个操作数必须为整数,返回的是整除之后的余数
移位操作符
>> 右移操作符 ;
整数二进制有三种表示形式 --- 原码 , 反码 , 补码
存储到内存中的是 补码
正数三码相同
负数 -1 --- 原码 : 10000000 00000000 00000000 00000001
反码: 11111111 11111111 11111111 11111110 (符号位不变,其他位按位取反)
补码: 11111111 11111111 11111111 11111111 (反码加1)
右移操作符
移位规则:
- 算术右移 --- 右边丢弃 , 左补原符号(正0负1)
- 逻辑右移 --- 右边丢弃 , 左边补0
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
int a=16; // 00000000 00000000 00000000 00010000 --16
int b=a>>1; // 00000000 00000000 00000000 00001000 --8
return 0;
}
左移操作符
移位规则: 左边抛弃、右边补0。
注意:对于移位运算符,不要移动负数位,这个是标准未定义的
int num=10;
num>>-1; //error
位操作符
& 按位与
| 按位或
^ 按位异或(相同为0,不同为1)
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
// & 按位与 --- 按 二进制位 与 --- (有零为零,全一则一)
int a = 3; //00000000 00000000 00000000 00000011
int b = 5; //00000000 00000000 00000000 00000101
int c =a&b; //00000000 00000000 00000000 00000001 --- 1
// | 按位或 --- 按 二进制位 或 --- (有一为一,全零则零)
int a = 3; //00000000 00000000 00000000 00000011
int b = 5; //00000000 00000000 00000000 00000101
int c =a|b; //00000000 00000000 00000000 00000111 --- 7
// ^按位异或 --- 按 二进制位 异或 ---(相同为0,相异为1)
int a = 3; //00000000 00000000 00000000 00000011
int b = 5; //00000000 00000000 00000000 00000101
int c =a|b; //00000000 00000000 00000000 00000110 --- 6
return 0;
}
练习 1: 不创建临时变量完成a,b的互换
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
int a=3; //011
int b=5; //101
//方案 1 - 加减法(缺陷:可能溢出)
a = a+b; //a=3+5=8
b = a-b; //b=8-5=3
a = a-b; //a=8-3=5
//方案 2 - 异或法
a=3; //011
b=5; //101
a = a^b; //110 --- 6
b = a^b; //011 --- 3
a = a^b; //101 --- 5
return 0;
}
练习 2: 求一个整数储存在内存中的二进制中的1的个数
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
int a=0;
int count=0;
scanf("%d",&num)
//方案 1
while(num) //只能算正数
{
if(num%2==1)
count++;
num=num/2;
}
//方案 2
int i = 0;
for (i=0;i<32;i++)
{
if(1==(num>>1)&1)
count++;
}
return 0;
}
赋值操作符
=
复合赋值符:
+= ---> a+=1 <---> a=a+1
*=
/=
%=
>>=
<<=
& =
| =
^ =
单目操作符
- ! 逻辑反操作
- -/+ 负值/正值
- & 取地址
- sizeof 操作数的类型长度(以字节为单位) --- ()中的表达式不会真实执行; 传地址(指针变量)占4个字节
- ~ 对一个二进制取反
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
int a=11; // a 00000000 00000000 00000000 00001011
a=a|(1<<2); //1<<2 00000000 00000000 00000000 00000100
printf("%d\n",a) // 新a 00000000 00000000 00000000 00001111
a=a & (~(1<<2)); //~(1<<2) 11111111 11111111 11111111 11111011
// 新a 00000000 00000000 00000000 00001011
return 0;
}
- --/++ 前置(先--/++,再使用),后置(先使用,再--/++)
- * 间接访问操作符(解引用操作符)
- (类型) 强制类型转换
关系操作符
- >
- >=
- <
- <=
- != 用于测试 ' 不相等 '
- == 用于测试 ' 相等 '
逻辑操作符
- && 逻辑 与(全真为真)
- | | 逻辑 或(一真为真)
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
int c = a && b;
printf("%d\n",c); //结果--1
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
int i=0,a=0,b=2,c=3,d=4;
i=a++ && ++b && d++; //i=0, a=1, b=2, c=3, d=4
//电脑偷懒 左边为假 && 右边不再运算
int i=0,a=1,b=2,c=3,d=4;
i=a++ || ++b || d++; //i=1, a=2, b=2, c=3, d=4
int i=0,a=1,b=2,c=3,d=4;
i=a++ && ++b && d++; //i=8, a=2, b=3, c=3, d=5
return 0;
}
条件操作符(三目操作符)
exp1 ? exp2 : exp3 (解释: 1如果真,执行2 ; 1如果假,执行3 )
#include<stdio.h>
int main()
{
int a = 10;
int b = 20;
int max;
//if (a > 5)
// b = 3;
//else
// b = -3;
//b = a > 5 ? 3 : -3;
max = a > b ? a : b;
printf("a = %d\nb = %d\nmax = %d\n", a, b, 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 + 1); //c=13
printf("%d\n", c);
return 0;
}
下标引用,函数调用 和 结构成员
下标引用操作符 [ ]
#include<stdio.h>
int main()
{
//1.定义一个数组
int a[10] = { 0 };
//2.如果我们想要访问第5个数组元素,并给其赋值
a[4] = 10;//3.用数组变量名+[]+下标索引数字
//4.[ ] 对应的两个操作数一个是变量名a 另外一个就是下标/索引值 4
printf("%d\n", a[4]);
return 0;
}
函数调用操作符 ( )
接收一个或者多个操作数,第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
#include<stdio.h>
//2.这个地方的()不是函数调用操作符,是函数定义的语法规则
int get_max(int x, int y)
{
return x > y ? x : y;
}
int main()
{
int a = 10;
int b = 20;
//1.调用函数的时候使用的() 就是函数调用操作符
int max = get_max(a, b);
//3.这里的函数调用操作符()的操作数为 函数名get_max,函数参数a,函数参数b 总共三个操作数
//4.对于函数调用操作符()而言,其操作数至少要有一个(函数名),可以有多个
printf("max = %d\n", max);
return 0;
}
访问一个结构的成员 . ->
结构体. 成员名
结构体指针->成员名
思考:如何创建结构体类型和结构体类型变量呢?
#include<stdio.h>
//1.现实中为了描述复杂的对象,构建结构体类型
//2.比如学生,有姓名,年龄,学号等信息
//创建一个结构体类型
struct Stu //3.struct Stu是一个结构体类型,表示学生类型
//类型是用来创建变量的
{
char name[20]; //姓名
int age; //年龄
char id[20]; //学号
};
int main()
{
int a = 10;
//使用struct Stu这个类型创建了一个学生对象s1,并初始化
struct Stu s1 = { "张三",20,"20240803" };
printf("%s\n", s1.name);
printf("%d\n", s1.age);
printf("%s\n", s1.id);
struct Stu* ps = &s1;
//结构体指针->成员名
printf("%s\n", ps->name); //等价于 printf("%s\n", (*ps).name)
printf("%d\n", ps->age);
printf("%s\n", ps->id);
return 0;
}
表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
隐式类型转换(悄悄的进行类型转换)
整型提升
- C的整型算术运算总是至少以缺省整型类型的精度来进行的。
- 为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
- 是不是看的不太懂?这里我们举例说明:
#include<stdio.h>
int main()
{
char a = 3;
//00000000 00000000 00000000 00000011 整数3
//0000 0011 ->a a是char类型,只有一个字节,这时候就会发生截断
//截断的规则:挑最小最低位的字节内容
char b = 127;
//00000000 00000000 00000000 01111111 整数127
//0111 1111 ->b
//a+b a和b如何相加 按照变量数据类型的符号位来提升的
//00000011 ->00000000 00000000 00000000 00000011
//01111111 ->00000000 00000000 00000000 01111111
// 00000000 00000000 00000000 10000010
char c = a + b;//这时候a,b被提升为普通整型
//10000010 ->c
//后面我们要以%d的形式打印,需要进行整型提升:按照变量数据类型的符号位(第一位)来提升的
//11111111 11111111 11111111 10000010 ---补码
//11111111 11111111 11111111 10000001 ---反码
//10000000 00000000 00000000 01111110 ---原码 -126
printf("%d\n", c);
printf("%c\n", c);
return 0;
}
printf("%u\n"),sizeof(c); //1
printf("%u\n"),sizeof(+c); //4
printf("%u\n"),sizeof(!c); //1
算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
(比如说int类型跟float类型参与运算,先要将int转换为float类型,然后float类型与float类型运算)
操作符的属性
复杂表达式的求值有三个影响的因素。
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序。
两个相邻的操作符先执行哪个 ? 取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
常见问题解答:
1、如何记住运算符的15种优先级和结合性?
C语言中运算符种类比较繁多,优先级有15种,结合性有两种。
2、如何记忆两种结合性和15种优先级?下面讲述一种记忆方法。
结合性有两种,一种是自左至右,另一种是自右至左,大部分运算符的结合性是自左至右,只有单目运算符、三目运算符的赋值运算符的结合性自右至左。
优先级有15种。
记忆方法:
记住一个最高的:构造类型的元素或成员以及小括号。
记住一个最低的:逗号运算符。
剩余的是一、二、三、赋值。
意思是单目、双目、三目和赋值运算符。 在诸多运算符中,又分为:
算术、关系、逻辑。
两种位操作运算符中,移位运算符在算术运算符后边,逻辑位运算符在逻辑运算符的前面。再细分如下: 算术运算符分 * , / , % 高于 + , - 。 关系运算符中,〉,〉 = , < , <= 高于 == ,! = 。
逻辑运算符中,除了逻辑求反(!)是单目外,逻辑与( && )高于逻辑或( || )。 逻辑位运算符中,除了逻辑按位求反(~)外,按位与(&)高于按位半加(^),高于按位或( | )。
这样就将15种优先级都记住了,再将记忆方法总结如下: 去掉一个最高的,去掉一个最低的,剩下的是一、二、三、赋值。双目运算符中,顺序为算术、关系和逻辑,移位和逻辑位插入其中。
问题表达式
下面我们来看一些问题表达式:
//表达式的求值部分由操作符的优先级决定。
表达式1
a * b + c * d + e * f ;
注释∶代码1在计算的时候,由于 * 比 + 的优先级高,只能保证,* 的计算是比 + 早,但是优先级并不能决定第三个 * 比第一个 + 早执行。所以表达式的计算机顺序就可能是︰
a * b
c * d
a * b + c * d
e * f
a * b + c * d + e * f
或者 :a * b
c * d
e * f
a * b + c * d
a * b + c * d + e * f
表达式2
c + --c;
注释︰同上,操作符的优先级只能决定自减–的运算在 + 的运算的前面,但是我们并没有办法得知, + 操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。