目录
1 操作符分类
- 算术操作符
- 移位操作符
- 位操作符
- 赋值操作符
- 单目操作符
- 关系操作符
- 逻辑操作符
- 条件操作符
- 逗号表达式
- 下标引用、函数调用和结构成员
2 算术运算符
+ - * / %
3 移位操作符
<< 左移操作符>> 右移操作符注:移位操作符的操作数只能是整数。
3.1 左移操作符
3.1.1 原码、反码、补码
整数的二进制表示形式有3种:原码、反码、补码。
正数
原码、反码、补码相同。
负数
原码:按照数值的正负,直接写出的二进制序列就是原码。
反码:原码的符号位不变,其他位按位取反。补码:反码的二进制+1就得到补码。
整数在内存中存储的都是补码的二进制序列,所以整数在计算机的时候也使用的补码!
对于有符号的整数来说,最高位的1位是符号位。符号位是1表示负数,符号位是0表示正数。
对于无符号数整数来说,没有符号位,所有位都是有效位。
例如:
补码得到原码的办法:两种途径
3.1.2左移操作符的使用
左边丢弃,右边补0
注意:都是补码在操作。
由于7是正数,所以可以直接移动。
#include<stdio.h>
int main()
{
//左移操作:左边丢弃,右边补0
//00000000000000000000000000000111
//00000000000000000000000000001110
//正数,原码反码补码相同
int m = 7;
int n = m << 1;
printf("%d\n", n); //14
printf("%d\n", m); //7
return 0;
}
负数:原码符号位不变,其他位取反得到反码,然后+1得补码,补码左移,左边丢弃右边补0,然后再+1获得反码,最后取反得原码。
#include<stdio.h>
int main()
{
int m = -7;
//负数,原码符号位不变,其他位取反得到反码,然后+1得补码
//10000000000000000000000000000111
//11111111111111111111111111111000
//11111111111111111111111111111001
//补码左移,左边丢弃右边补0
//11111111111111111111111111110010
//然后再-1获得反码
//11111111111111111111111111110001
//最后取反得原码
//10000000000000000000000000001110
//-14
int n = m << 1;
printf("%d\n", n); //-14
printf("%d\n", m); //-7
return 0;
}
最后我们可以发现,左移1‘<<1’相当于×2的效果。
3.2 右移操作符
分两种操作方式:算术右移和逻辑右移。
1. 逻辑移位左边用 0 填充,右边丢弃2. 算术移位左边用原该值的符号位填充,右边丢弃。原来是负数,左边补1;原来是正数,左边补2。
例如:-10
首先写出-10的原码、反码和补码
然后根据算术右移,写出新的补码,进而写出反码和原码。
最后答案就是-5。
最后我们可以发现,右移1‘>>1’相当于➗2的效果。
注意:对于移位运算符,不要移动负数位,这个是标准未定义的。
4位操作符
位操作符有:
& // 按位与| // 按位或^ // 按位异或注:他们的操作数必须是整数。
4.1 按位与
int main()
{
int a = 3;
int b = -5;
int c = a & b;//按(2进制)位与
//00000000000000000000000000000011 --- 3的补码
//
//10000000000000000000000000000101
//11111111111111111111111111111010
//11111111111111111111111111111011 --- -5的补码
//00000000000000000000000000000011 --- 3的补码
//00000000000000000000000000000011
//
printf("%d\n", c);
return 0;
}
比较补码,两个均相同则为1, 不相同则为0。算出3&-5的十进制是3。
特点:如果想知道某一位是几,直接将其右移到最后一位,然后按位与1即可。
4.2 按位或
int main()
{
int a = 3;
int b = -5;
int c = a | b;//按(2进制)位与
//00000000000000000000000000000011 --- 3的补码
//
//10000000000000000000000000000101
//11111111111111111111111111111010
//11111111111111111111111111111011 --- -5的补码
//00000000000000000000000000000011 --- 3的补码
//11111111111111111111111111111011 --- |出来的结果,由于是负数,所以要经过计算得到原码
//11111111111111111111111111111010
//10000000000000000000000000000101 -5
printf("%d\n", c);
return 0;
}
比较补码,两个有1则为1, 均为0才为0。算出3|-5的十进制是-5。
4.3 按位异或
//异或运算的特点:
//相同为0,相异为1
int main()
{
int a = 3;
int b = -5;
//00000000000000000000000000000011 --- 3的补码
//10000000000000000000000000000101
//11111111111111111111111111111010
//11111111111111111111111111111011 --- -5的补码
int c = a ^ b;
//00000000000000000000000000000011
//11111111111111111111111111111011
//11111111111111111111111111111000
//10000000000000000000000000000111
//10000000000000000000000000001000
//
printf("%d\n", c);
return 0;
}
相同为0,相异为1
a^a = 0
0^a = a
4.4 笔试题
不能创建临时变量(第三个变量),实现两个数的交换。
int main()
{
int a = 3;
int b = 5;
printf("a=%d b=%d\n", a, b);
a = a ^ b;//a = 3^5
b = a ^ b;//b=3
a = a ^ b;//a= 3^5^3
printf("a=%d b=%d\n", a, b);
return 0;
}
异或是支持交换律的。只支持整数型。
5 赋值操作符
=
支持连续赋值,但是可读性不高。
复合赋值符
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
这些运算符都可以写成复合的效果。
6 单目操作符
6.1 介绍
! 逻辑反操作- 负值+ 正值& 取地址sizeof 操作数的类型长度(以字节为单位)~ 对一个数的二进制按位取反-- 前置、后置 --++ 前置、后置 ++* 间接访问操作符 ( 解引用操作符 )( 类型 ) 强制类型转换
!——取反
- + 变负变正
&——取出该变量在内存的位置
sizeof —— 在X86编译器下地址的字节是4位,在64位的平台下,字节大小是8位。
#include <stdio.h>
int main()
{
int a = -10;
int* p = NULL;
printf("%d\n", !2);
printf("%d\n", !0);
a = -a;
p = &a;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof a);//这样写行不行?
printf("%d\n", sizeof int);//这样写行不行?
return 0;
}
sizeof 是在计算类型创建变量或者变量的大小,单位是字节
sizeof 计算的结果是size_t 类型的
size_t 是无符号整型的
对size_t 类型的数据进行打印,可以使用%zd
sizeof 后面的括号在括号中写的不是类型的时候,括号可以省略,这样就说sizeof不是函数
sizeof是操作符 - 单目操作符
int main()
{
int a = 10;
printf("%zd\n", sizeof(a));
printf("%zd\n", sizeof a);
printf("%zd\n", sizeof(int));
int arr[10] = {0};
printf("%zd\n", sizeof(arr));
printf("%zd\n", sizeof(arr[0]));
printf("%zd\n", sizeof(arr) / sizeof(arr[0]));
return 0;
}
~ 不管符号位还是别的,全部取反。
int main()
{
int a = 0;
printf("%d\n", ~a);
//0
//00000000000000000000000000000000
//11111111111111111111111111111111
//10000000000000000000000000000000
//10000000000000000000000000000001
//-1
return 0;
}
int main()
{
int a = 10;
//00000000000000000000000000001010
//00000000000000000000000000010000
//00000000000000000000000000011010
//11111111111111111111111111101111
//1<<4
a |= (1 << 4);
printf("%d\n", a); //26
a &= (~(1 << 4));
printf("%d\n", a); //10
return 0;
}
*
int main()
{
//int a = 10;
//int*p = &a;//a变量的地址
//int arr[10];
//int(*pa)[10] = &arr;//这是数组的地址
int a = 10;
int* p = &a;
*p;//对p进行解引用操作,*p是通过p中存放的地址,找到p指向的对象。*p其实是a
return 0;
}
二进制位的操作符
>> << & | ^ ~
++
++a 和 a++
int main()
{
int a = 5;
//a++;
//++a;
//++ 操作是是一种自增1的操作
//前置++:计算口诀:先+1,后使用
int b = ++a;
//a=a+1, b=a;
//后置++
printf("a = %d\n", a);//6
printf("b = %d\n", b);//6
return 0;
}
int main()
{
int a = 5;
//a++;
//++a;
//++ 操作是是一种自增1的操作
//后置++:口诀:先使用,后+1
int b = a++;
//b=a,a=a+1
printf("a = %d\n", a);//6
printf("b = %d\n", b);//5
return 0;
}
b的值不同了。
--同理。
6.2 sizeof 和 数组
#include <stdio.h>
void test1(int arr[])
{
printf("%d\n", sizeof(arr));//(2)
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch));//(4)
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%d\n", sizeof(arr));//(1)
printf("%d\n", sizeof(ch));//(3)
test1(arr);
test2(ch);
return 0;
}
//问:
//(1)、(2)两个地方分别输出多少?
//(3)、(4)两个地方分别输出多少?
函数在传数组名的时候,传过去的是首元素的地址,所以两个函数打印的是首元素的大小。
×64环境下打印
×86环境下打印
7 关系操作符
>>=<<=!= 用于测试 “ 不相等 ”== 用于测试 “ 相等 ”
8 逻辑操作符
&& 逻辑与|| 逻辑或
1 & 2 -----> 01 && 2 ----> 11 | 2 -----> 31 || 2 ----> 1
短路操作
&& -- 左边操作数如果为假,右边无需计算
|| -- 左边操作数如果为真,右边无需计算
几个例子:
#include <stdio.h>
int main()
{ //1 3
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
//i先使用a的值,即为0,后面的都不用算,因为i=0了,为假。此时a为1,所以打印出来是12340
printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
printf("i = %d\n", i);
return 0;
}
#include <stdio.h>
int main()
{
int i = 0, a = 1, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
//i先使用a的值,即为1,++b为3,满足,d++为4,满足,此时i就=1;
printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);//2 3 3 5 1
printf("i = %d\n", i);
return 0;
}
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ || ++b || d++;
//i先使用a的值,即为0,++b为3,满足了,后面就不用算了,此时i就=1;
printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);//1 3 3 4 1
printf("i = %d\n", i);
return 0;
}
#include <stdio.h>
int main()
{
int i = 0, a = 1, b = 2, c = 3, d = 4;
i = a++ || ++b || d++;
//i先使用a的值,即为1,后面都不用算,此时i就=1;
printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);//2 2 3 4 1
printf("i = %d\n", i);
return 0;
}
9 条件操作符
exp1 ? exp2 : exp3
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int m = 0;
/*if (a > b)
m = a;
else
m = b;*/
m = (a > b ? a : b);
printf("%d\n", m);
return 0;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d", &a);
//if (a > 5)
// b = 3;
//else
// b = -3;
b = (a > 5 ? 3 : -3);
return 0;
}
10 逗号表达式
exp1 , exp2 , exp3 , …expN
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);
c是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)
{
//业务处理
}
11 下标引用、函数调用和结构成员
int arr [ 10 ]; // 创建数组arr [ 9 ] = 10 ; // 实用下标引用操作符。[ ] 的两个操作数是 arr 和 9 。
int Add(int a, int b)
{
return a + b;
}
int main()
{
printf("hehe\n");//()函数调用操作符
int r = Add(3, 5);//()函数调用操作符
return 0;
}
. 结构体 . 成员名-> 结构体指针 -> 成员名
struct Book
{
char name[20];
int price;
};
//结构体变量.成员
//结构体指针->成员
void Print(struct Book* pb)
{
printf("%s %d\n", (*pb).name, (*pb).price);
printf("%s %d\n", pb->name, pb->price);
}
int main()
{
struct Book b = {"苹果", 5};
printf("%s %d\n", b.name, b.price);
//或者写一个函数
Print(&b);
return 0;
}
结构体变量.成员
结构体指针->成员
这两种写法是完全等价的。
12 表达式求值
12.1 隐式类型转换
表达式的整型运算要在 CPU 的相应运算器件内执行, CPU 内整型运算器 (ALU) 的操作数的字节长度一般就是 int 的字节长度,同时也是 CPU 的通用寄存器的长度。因此,即使两个 char 类型的相加,在 CPU 执行时实际上也要先转换为 CPU 内整型操作数的标准长度。通用 CPU ( general-purpose CPU )是难以直接实现两个 8 比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于 int 长度的整型值,都必须先转换为 int 或 unsigned int ,然后才能送入 CPU 去执行运算。
// 实例 1char a , b , c ;...a = b + c ;
b和c的值被提升为普通整型,然后再执行加法运算。
int - signed int
unsigned int
char 到底是signed char还是unsigned char是不确定的,C语言标准没有明确指定,是取决于编译器的
在当前使用的VS上,char == signed char
整形提升是按照变量的数据类型的符号位来提升的。
// 负数的整形提升char c1 = - 1 ;变量 c1 的二进制位 ( 补码 ) 中只有 8 个比特位:1111111因为 char 为有符号的 char所以整形提升的时候,高位补充符号位,即为 1提升之后的结果是:11111111111111111111111111111111// 正数的整形提升char c2 = 1 ;变量 c2 的二进制位 ( 补码 ) 中只有 8 个比特位:00000001因为 char 为有符号的 char所以整形提升的时候,高位补充符号位,即为 0提升之后的结果是:00000000000000000000000000000001// 无符号整形提升,高位补 0
int main()
{
char a = 5;
//00000000000000000000000000000101
//00000101
char b = 126;
//00000000000000000000000001111110
//01111110
char c = a + b;
//00000000000000000000000000000101-a
//00000000000000000000000001111110-b
//00000000000000000000000010000011
//10000011-c
//c是有符号位,补1
//11111111111111111111111110000011
//10000000000000000000000001111100 -取反
//10000000000000000000000001111101 -加1
//-125
printf("%d\n", c);
return 0;
}
12.2 算术转换
long doubledoublefloatunsigned long intlong intunsigned intint
12.3 操作符的属性
// 表达式的求值部分由操作符的优先级决定。// 表达式 1a * b + c * d + e * f
a * bc * da * b + c * de * fa * b + c * d + e * f或者:a * bc * de * fa * b + c * da * b + c * d + e * f
例如:
// 表达式 2c + -- c ;