文章目录
一、操作符分类
- 算数操作符: + 、- 、* 、/ 、%
- 移位操作符: << 、>>
- 位操作符:& 、 | 、^
- 赋值操作符:= 、+= 、-= 、*= 、/= 、%= 、<<= 、 >>= 、 &= 、|= 、 ^=
- 单目操作符:!、++ 、– 、& 、* 、+ 、- 、~ 、sizeof 、()
- 关系操作符: && 、||
- 条件操作符:? 、:
- 逗号表达式:,
- 下标引用:[ ]
- 函数调用: ()
- 结构成员访问:. 、->
二、二进制和进制转换
1、各种进制的区别
计算机中使用的是二进制,只有1、0组成这样更容易硬件实现,也包括八进制、十进制、16进制等。
这些进制只是不同的形式而已
生活中长用的进制为10进制
十进制:由0~9组成
八进制:由0~7组成
十六进制:由0~9和A~F组成
A=10,B=11,C=12,D=13,E=14,F=15
二进制满2进1
八进制满8进1
十进制满10进1
十六进制满16进1
15用不同进制表示:
二进制:1111
八进制:17
十进制:15
十六进制:F
2、二进制转十进制
其实10进制的123表⽰的值是⼀百⼆⼗三,为什么是这个值呢?其实10进制的每⼀位是权重的,10进制的数字从右向左是个位、⼗位、百位…,分别每⼀位的权重是100,101,102……
如下图:
2进制和10进制是类似的,只不过2进制的每⼀位的权重,从右向左是:20 , 21 , 22 …
如果是2进制的1101,该怎么理解呢?
3、二进制转八进制和十六进制
二进制转八进制,从右向左每三位二进制算一位八进制
二进制转十六进制,从右向左每四位二进制算一位十六进制
三、原码、反码、补码
原码、反码、补码是二进制的三种不同的形式
我们看到的是原码的值,当存储这个值时在内存里用补码存储。
有符号的整数,最高位为符号位,0表示“正”,1表示“负”。
正数的原、反、补码相同。
负数的原、反、补都不同。
负数的反码按原码除符号位外按位取反
负数补码是反码加一
补码转原码也可以符号位不变取反加以
对于整形来说:数据存放内存中其实存放的是补码。
为什么呢?
在计算机系统中,数值⼀律⽤补码来表⽰和存储。原因在于,使⽤补码,可以将符号位和数值域统⼀
处理;同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算
过程是相同的,不需要额外的硬件电路。
四、移位操作符
<<左移操作符
>>右移操作符
注意:移位操作符的操作数为整数,移的是在内存中补码的二进制位。
1、左移操作符
移位规则:左边丢弃、右边补0
#include<stdio.h>
int main()
{
int a = -1;
a = a << 1;//向左移动1位二进制
printf("%d\n", a);
return 0;
}
这里可以看出左移有乘2的效果,移n位乘n个二。
2、右移操作符
移位规则:
1、逻辑右移:左边补0,右边丢弃
2、算数右移:左边补原符号位,右边丢弃
#include <stdio.h>
int main()
{
int num = 10;
int n = num >> 1;
printf("n= %d\n", n);
printf("num= %d\n", num);
return 0;
}
这里在VS编译器运行时算数右移,有除2的效果,但会丢弃余数。
五、位操作符。
他们的操作数必须是整数。
1、按位与&
对内存中的补码进行操作,全1得1否则为0。
2、按位或|
对内存中的补码进行操作,见1得1否则为0.
3、按位异或^
不同为1,相同为0。
支持结合率
5 ^ 5 =0
5 ^ 0 = 5
5 ^ 5 ^ 3 = 3
5 ^ 3 ^ 5 = 3
4、按位取反~
~15=0000
~0000=1111
六、单目操作符
单目操作符的特点只有一个操作数。
1、!非运算符
!0=1
!1=0
2、++加加运算符
分前置++,后置++
前置++先加后使用,后置++先使用后加
效果为加一
#include <stdio.h>
int main()
{
int a = 1;
int b = 1;
printf("a=%d\n", a++);
printf("b=%d\n", ++b);
return 0;
}
3、–减减运算符
分前置–,后置–
前置–先加后使用,后置–先使用后加
效果为减1
#include <stdio.h>
int main()
{
int a = 1;
int b = 1;
printf("a=%d\n", a--);
printf("b=%d\n", --b);
return 0;
}
4、&取地址操作符
取得一个变量的地址,可以结合scanf()函数往里面输入值
#include <stdio.h>
int main()
{
int a = 0;
scanf("%d", &a);
printf("%d\n", a);
return 0;
}
5、解引用操作符*
可以把一个地址对应的值解出来,经常用于指针操作
#include <stdio.h>
int main()
{
int a = 5;
int b = 0;
b = *&a;
printf("%d\n", b);
return 0;
}
6、正号+
没有实质作用打印时会省略正号
#include <stdio.h>
int main()
{
int a = 5;
int b = 0;
b = +a;
printf("%d\n", b);
return 0;
}
6、负号-
把正数变负数,负数变正数
#include <stdio.h>
int main()
{
int a = 5;
int b = 0;
b = -a;
printf("%d\n", b);
return 0;
}
7、取反操作符~
在位操作符中说到过,对二进制位取反
8、sizeof
可以算出类型占用字节数
#include <stdio.h>
int main()
{
int a = 0;
printf("%zd\n", sizeof a);//变量名是可以省略括号由此证明sizeof是操作符,而不是函数
printf("%zd\n", sizeof(int));//类型名不能省略括号
return 0;
}
变量名是可以省略括号由此证明sizeof是操作符,而不是函数
返回类型为size_t可以用%zd或%zu打印结果
9、强制类型转换()
#include <stdio.h>
int main()
{
int a = 0;
int b = 0;
b = sizeof((char)a);
printf("%zu\n", b);
return 0;
}
本来int占4个字节,转换成char类型只占一个字节。
七、逗号表达式 ,
#include <stdio.h>
int main()
{
int a = (3, 4, 5, 6);
int b = 0;
b = 3, 4, 5, 6;
printf("a=%d\n", a);
printf("b=%d\n", b);
return 0;
}
在初始化时加括号,结果为最右边的表达式
在不初始化赋值时不用加括号写逗号表达式,结果为最左边的表达式值。
八、下标访问[],函数调用()
1、下标访问[]操作符
对数组的操作的操作符,可以指定数组元素的下标。
数组元素的下标从0开始
#include <stdio.h>
int main()
{
int a[] = { 1,2,3,4,5,6,7,8,9,10 };
printf("a[0]=%d\n", a[0]);
printf("a[9]=%d\n", a[9]);
return 0;
}
2、函数调用操作符()
在调用函数时需要加上括号()
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 10;
int b = 5;
int c=Add(a, b);
printf("%d\n", c);
return 0;
}
上面代码中自己创建了两数相加的函数Add使用时加(),还有库函数printf()也要加括号()
九、结构体访问操作符
1、结构体直接访问操作符.
是一个英文状态下的点号.
使⽤⽅式:结构体变量.成员名
#include<stdio.h>
#include<string.h>
struct student
{
char name[10];
int age;
};
int main()
{
struct student s;
//字符串赋值不能直接等于要用到strcpy()函数,引用string.h头文件
strcpy(s.name, "小明");
s.age = 15;
printf("姓名:%s\n", s.name);
printf("年龄:%d\n", s.age);
}
2、结构体的间接访问
当指针指向结构体可以用->这个箭头来访问
使⽤⽅式:结构体指针->成员名
#include<stdio.h>
#include<string.h>
struct student
{
char name[10];
int age;
};
int main()
{
struct student s;
//字符串赋值不能直接等于要用到strcpy()函数,引用string.h头文件
strcpy(s.name, "小明");
s.age = 15;
struct student* p = &s;
printf("姓名:%s\n", p->name);
printf("年龄:%d\n", p->age);
}
十、操作符的属性:优先级、结合性
C语⾔的操作符有2个重要的属性:优先级、结合性,这两个属性决定了表达式求值的计算顺序。
参考:https://zh.cppreference.com/w/c/language/operator_precedence
十一、表达式求值。
1、整形提升
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执⾏,CPU内整型运算器(ALU)的操作数的字节⻓度⼀
般就是int的字节⻓度,同时也是CPU的通⽤寄存器的⻓度。
因此,即使两个char类型的相加,在CPU执⾏时实际上也要先转换为CPU内整型操作数的标准⻓
度。
通⽤CPU(general-purpose CPU)是难以直接实现两个8⽐特字节直接相加运算(虽然机器指令中
可能有这种字节相加指令)。所以,表达式中各种⻓度可能⼩于int⻓度的整型值,都必须先转换为
int或unsigned int,然后才能送⼊CPU去执⾏运算。
一般整形提升的是cahr 和 short 类型
2、算数转换
但不同类型相互计算时会把排名的的类型转换成排名高的类型进行运算
按照上图排名进行转换
在我发的一篇C语言易错题中就体现了这一性质