操作符
算数操作符
+ - * / %
1.除了%操作符以外,其他几个操作符可用于整数和浮点数
2.对于/操作符如果两个操作数都为整数,执行整数除法。除法想得到小数的结果,必须保证除数和被除数中至少有一个小数(浮点数)
3.%操作符的两个操作数必须是整数,返回的是整除之后的余数
5.0%2; //ERROR
移位操作符
<< 左移操作符
>> 右移操作符
注:移位操作符的操作数只能是整数
移位操作符:移动的是二进制
我们先引入进制的概念:
例如数值15 15-十进制表示=1*101+5*100 而15换算成八进制即为17
那么我们知道,15的二进制表示为1111,1111称为15的二进制序列
整型的二进制表示:原码、反码、补码
15 整数,在C语言可以存放到int类型的变量中,int类型是4个字节-32bit,其中最高位(32位)叫做符号位
15 00000000 00000000 00000000 00001111 -原码
15 00000000 00000000 00000000 00001111 -反码
15 00000000 00000000 00000000 00001111 -补码
正整数的原码、反码、补码相同
-15 由于它是负数,它的最高位是1表示负数
-15 10000000 00000000 00000000 00001111 -原码 :负数的原码最高位为1,其它位不变
-15 11111111 11111111 11111111 11110000 -反码:原码的符号位不变,其他位按位取反
-15 11111111 11111111 11111111 11110001 -补码:反码的二进制位加一即得补码
整数在内存中的存储是二进制的补码,移位操作符移动的是存储在内存中的补码
左移
#include<stdio.h>
int main(){
int a = 4;
//00000000 00000000 00000000 00000100 - 4的原码、补码
int b = a << 1; //把a向左移动一位
printf("a=%d b=%d\n", a, b);
return 0;
}
运行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8oFg7wXS-1662482063880)(E:\Typora图片\image-20220906192729968.png)]
00000000 00000000 00000000 00000100 - 4的补码向左移动移位后移动后结果是00000000 000000000 00000000 00001000(补码向左移动一位),那么对应的值b为8
左移有扩大一倍的效果
右移
1.逻辑右移:右边丢弃,左边补0
2.算术右移:右边丢弃,左边补原符号位(一般编译器采用算术右移)
右移方式取决于编译器
对于移位操作符,不要移动负数位,这个标准是未定义的
右移有缩小一倍的效果
位操作符
& //按(二进制)位与
| //按(二进制)位或
^ //按(二进制)位异或
注:它们的操作数必须是整数
#include<stdio.h>
int main(){
int a = 3;
int b = -5;
int c = a & b;
//a=3 00000000 00000000 00000000 00000011
//b=-5 10000000 00000000 00000000 00000101 -5的原码
// 11111111 11111111 11111111 11111010 -5的反码
// 11111111 11111111 11111111 11111011 -5的反码
//因此:a&b:
//a=3 00000000 00000000 00000000 00000011
//b=-5 11111111 11111111 11111111 11111011
//做且运算:(离散数学内容)
//c= 00000000 00000000 00000000 00000011 -3
printf("%d", c);
return 0;
}
按两者的二进制位的补码进行运算
异或是什么意思呢?异或的运算规律为:相同为0,相异为1
应用:
不创建临时变量实现两个数的交换
方法1:
#include<stdio.h>
int main(){
int a = 3;
int b = 5;
a = a ^ b;
b = a ^ b; // b=a^b^b------>b=a
a = a ^ b; // a=a^a^b------>a=b
printf("%d %d", a, b);
return 0;
}
好处:二进制异或操作不存在进位问题,因此不会溢出
注意:异或是支持交换律的
方法2:
#include<stdio.h>
int main(){
int a=3;
int b=5;
a = a + b;
b = a - b;
a = a - b;
printf("% d % d", a, b);
return 0;
}
赋值操作符
赋值顺序:从右边赋值给左边(应用:连续赋值)
int a=1;
int b=a=a+3;
连续赋值,不太好
复合赋值符
+= -= *= /= &= >>= <<= |= &= ^=
单目操作符
! 逻辑反操作
& 取地址操作符
* 解引用操作符
sizeof 计算操作数的类型长度(以字节为单位)
~ 对一个数的二进制位取反
++a 前置++,先++再使用
a++ 后置++,先使用后++
(类型) 强制类型转换
‘&‘与’*’
创建变量------>在内存中开辟一块空间------>地址:放在内存中的位置 打印地址:%p
用于存放地址的变量------>指针变量,加*是告诉我们它是指针变量
int *pa = &a;
*pa;
pa:代表a的地址,而*pa则可以通过*来操作a(拿到变量a的地址之后找回去)
任何内存中的对象,我们都可以对它进行取地址操作
随便使用一个地址,在未分配的时候使用它,这样就形成了野指针(一个没有明确指向内存中的空间或指向的空间不属于我们称为野指针)
sizeof
int a=10;
int arr[10];
printf("%zu\n",sizeof(a)); //a是int类型-4
printf("%zu\n",sizeof(arr))
%zu用于打印sizeof返回值
arr是由有10个元素的int类型的数组,因此sizeof的值为4*10=40;数组名代表的是首元素地址,而sizeof这里属于特例,sizeof(数组名)计算的是整个数组的大小
特别的:
short s=10;
int a=2;
printf("%d\n",sizeof(s=a+5));
printf("%d\n",s);
输出结果为2和10
首先是a是int类型的+5后是亦是整型,整型数据放在短整型(short)中它会发生截断,整型成为短整型(2个字节);而sizeof()括号内的表达式是不参与计算的,因此s的值仍等于2
截断:同样的,还是二进制,现在short类型只有16位,因此截断的意思是将高位的删去,只保留低位(16位)
我们来分析下面这个程序:
#include <stdio.h>
void test1(int arr[])
{
printf("%zu\n", sizeof(arr));
}
void test2(char ch[])
{
printf("%zu\n", sizeof(ch));
}
int main()
{
int arr[10] = {0};
char ch[10] = {0};
test1(arr);
test2(ch);
return 0;
}
运行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ujJ2w63P-1662482063882)(E:\Typora图片\image-20220906213148560.png)]
这个程序数组传入函数,数组传入时传入的是首元素的地址,因此函数形参arr[]和ch[]的本质是指针,那么打印sizeof指针类型的大小,实际上是一样的,指针保存的是地址,无论是int类型还是char类型的指针它的大小都是一样的(一般是4或者8)
‘~’
#include<stdio.h>
int main(){
int a=0;
//00000000 00000000 00000000 00000000 a的原码补码
//11111111 11111111 11111111 11111111 ~a的补码,而打印输出是是输出的原码,因此要换算回来
//11111111 11111111 11111111 11111110
//10000000 00000000 00000000 00000001 最高位不变,其它位按位取反
printf("%d\n",~a);
return 0;
}
运行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FRkNmaHs-1662482063883)(E:\Typora图片\image-20220906220614225.png)]
应用,对于a的二进制表示:
int a=10;
int n=0;
scanf("%d",&n);
a=a|(1<<(n-1)); //把a的第n(二进制)位置为1
a=a&~(1<<(n-1)); //把a的第n(二进制)位置为0
关系操作符
>
>=
<
<=
!= 不等号
== 判等符号
注意:不是所有对象能作比较(如结构体变量、字符串之间)
逻辑操作符
&& 逻辑与
|| 逻辑或
逻辑与、逻辑或只关注真假(0/1),而按位与、按位或的对象是二进制位
条件操作符
exp1?exp2:exp3 又称为三目操作符
逻辑:exp1=1------>exp2(且exp2作为整个表达式的值)
exp1=0------>exp3(且exp3作为整个表达式的值)
理解exp1作为一个条件判断使用,而exp2和exp3作为两种情况,两者是否则关系
//找a,b中的较大值
int m=((a>b)?a:b); //a如果大于b则输出a否则输出b
逗号表达式
exp1,exp2,exp3......expN
逗号表达式,从左到右依次执行。整个表达式的结果是最后一个表达式的结果
因此,如果if语句的if()括号内有逗号表达式,那么起关键作用的即使最后那个语句**(但是最后这个语句的前几个语句依然会执行)**
例如程序:
a = get_val();
count_val(a);
while(a>0){
//业务处理
a = get_val();
count_val(a);
}
我们发现while循环内重复了前面的步骤,而while循环的条件,可以放在逗号表达式的末尾,程序可改进为:
while(a = get_val(),count_val(a),a>0){ //跟do-while循环相同的逻辑
//业务处理
}
这样这个循环亦然会从左到右依次执行a = get_val()------>count_val(a),然后通过末尾的a>0作为循环条件
下标引用、函数调用和结构成员
1.[ ]下标引用操作符
操作数:一个数组名+一个索引值
2.( )函数调用操作符
操作数:函数名+函数参数(不一定要有)
3.访问一个结构成员
. 结构体.成员名
-> 结构体指针->成员名
#include<stdio.h>
struct book
{
char name[30];
int price;
};
int main(){
struct book book1 = {"symphony of Destiny",30};
printf("%s %d\n", book1.name, book1.price); //结构体变量.结构体成员名
struct book *ps = &book1; //这里定义了一个结构体类型的指针变量指向结构体变量book1
printf("%s %d", (*ps).name, (*ps).price); //对指针ps解引用*ps即代表的是book1
printf("%s %d",ps->name,ps->price); //结构体指针->结构体成员名
return 0;
}
运行结构如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wWrLEfKn-1662482063883)(E:\Typora图片\image-20220906230140194.png)]
注意:(*ps).name不能去掉括号,因为’.'的优先级更高
表达式求值
隐式类型转换
C的整型算术运算总是至少以普通整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行, CPU内整型运算器(ALU)的操作数的字节长度 一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU (general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
思路大概就是:char的加法------>化为int型(整型提升)------->计算完后------>截断
整型提升是以最高位进行提升,只要类型大小小于4字节的都会发生整型提升
例如1:
#include<stdio.h>
int main(){
char a = 5;
char b = 126;
char c = a + b;
//整型提升
//a 00000000 00000000 00000000 00000101 原码=反码=补码(这里在内存中用的是补码)
//截断 000000101 -a
//整型提升
//b 00000000 00000000 00000000 01111110
//截断 01111110 -b
//整型提升
//a 00000000 00000000 00000000 00000101
//b 00000000 00000000 00000000 01111110
//相加:00000000 00000000 00000000 10000011
//截断 10000011 -c
printf("%d\n",c);
//%d 以十进制的方式打印有符号整数
//11111111 11111111 11111111 10000011 补码
//11111111 11111111 11111111 10000010 反码
//10000000 00000000 00000000 01111101 原码(printf输出的)
//因此根据最高位为1是个负数,然后1111101转化为10进制是125,所以输出结果为 - 125
return 0;
}
运行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-19uyBLYu-1662482063884)(E:\Typora图片\image-20220906235703790.png)]
得到-125的原因是char类型的取值范围太小了
例如2:
#include <stdio.h>
int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if (a == 0xb6)
{
printf("a");
}
if (b == 0xb600)
{
printf("b");
}
if (c == 0xb6000000)
{
printf("c");
}
return 0;
}
运行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G2LeJzLU-1662482063884)(E:\Typora图片\image-20220907001427103.png)]
由此可知:只有c是不变的(原因就是因为short和char类型变量都小于4个字节都会发生整型提升)
例如3:
#include <stdio.h>
int main()
{
char c = 1;
printf("%u\n",sizeof(c));
printf("%u\n",sizeof(c+1));
printf("%u\n",sizeof(+c));
printf("%u\n",sizeof(-c));
return 0;
}
运行结果如下:
可以看出,这里只要涉及到运算时就会有整型提升
算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类 型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
操作符的属性
操作符有三个属性:
1.操作符的优先级
2.操作符的结合性
3.是否控制求值顺序
操作符 | 描述 | 用法示例 | 结果类型 | 结合性 | 是否控制求值 顺序 |
---|---|---|---|---|---|
() | 聚组 | (表达式) | 与表达式同 | N/A | 否 |
() | 函数调用 | rexp (rexp , …,rexp) | rexp | L- R | 否 |
[ ] | 下标引用 | rexp[rexp] | lexp | L- R | 否 |
. | 访问结构成员 | lexp.member_name | lexp | L- R | 否 |
-> | 访问结构指针成员 | rexp->member_name | lexp | L- R | 否 |
++ | 后缀自增 | lexp ++ | rexp | L- R | 否 |
– | 后缀自减 | lexp – | rexp | L- R | 否 |
! | 逻辑反 | ! rexp | rexp | R- L | 否 |
~ | 按位取反 | ~ rexp | rexp | R- L | 否 |
+ | 单目,表示正值 | + rexp | rexp | R- L | 否 |
- | 单目,表示负值 | - rexp | rexp | R- L | 否 |
++ | 前缀自增 | ++ lexp | rexp | R- L | 否 |
– | 前缀自减 | – lexp | rexp | R- L | 否 |
* | 间接访问 | * rexp | lexp | R- L | 否 |
& | 取地址 | & lexp | rexp | R- L | 否 |
sizeof | 取其长度,以字节 表示 | sizeof rexp sizeof(类 型) | rexp | R- L | 否 |
(类型) | 类型转换 | (类型) rexp | rexp | R- L | 否 |
* | 乘法 | rexp * rexp | rexp | L- R | 否 |
/ | 除法 | rexp / rexp | rexp | L- R | 否 |
% | 整数取余 | rexp % rexp | rexp | L- R | 否 |
+ | 加法 | rexp + rexp | rexp | L- R | 否 |
- | 减法 | rexp - rexp | rexp | L- R | 否 |
<< | 左移位 | rexp << rexp | rexp | L- R | 否 |
>> | 右移位 | rexp >> rexp | rexp | L- R | 否 |
> | 大于 | rexp > rexp | rexp | L- R | 否 |
>= | 大于等于 | rexp >= rexp | rexp | L- R | 否 |
< | 小于 | rexp < rexp | rexp | L- R | 否 |
<= | 小于等于 | rexp <= rexp | rexp | L- R | 否 |