各种操作符的介绍
在C语言中,可以单独操控变量中的位。读者可能好奇,竟然有人想这么做。有时必须单独操控位,而且非常有用。例如,通常向硬件设备发送一两个字节来控制这些设备,其中每个位( bit )都有特定的含义。另外,与文件相关的操作系统信息经常被存储,通常使用特定位表明特定项。许多压缩和加密操作都是直接处理单独的位。高级语言一般不会处理这级别的细节,C在提供高级语言便利的同时,还能在为汇编语言所保留的级别上工作,这使其成为编写设备驱动和嵌入式代码的首选语言。
-首先要介绍位、字节、二进制记数法和其他进制记数系统的一些背景知识。
八进制位 | 等价的二进制位 |
---|---|
0 | 000 |
1 | 001 |
2 | 010 |
3 | 011 |
4 | 100 |
5 | 101 |
6 | 110 |
7 | 111 |
十六进制 | 等价二进制 |
---|---|
0 | 0000 |
1 | 0001 |
2 | 0010 |
3 | 0110 |
4 | 0100 |
5 | 0101 |
… | … |
F | 1111 |
+ - * / %(取余操作符)
#include
int main()
{
int a = 10;
printf("%d\n", a / 3); // 3
printf("%f\n", a / 3.0); // 3.333333
printf("%d\n", a % 3); // 1
return 0;
}
下面介绍C的移位操作符,移位操作符向左或向右移动位。
flags = flags & MASK;
把 flags 中除1号位以外的所有位都设置为0,因为使用 & 任何位与0组合都得0。
1号位的值不变(如果1号位是1,1 & 1得1;如果是0,0 & 1 还是得0)。
这个过程叫做“使用掩码”,因为掩码中的0隐藏了 flags 中相应的位。
if((flags & MASK) == MASK)
puts("nice!");
这里,检查 flags 中的1号位是否被设置为1,由于按位与运算符的优先级比==低,所以要加上圆括号。
flags = flags | MASK;
把 flags 的1号位设置为1,且其他位不变。
因为使用 | 运算符,任何位与0组合,结果都是本身;任何位与1组合,结果都为1。
flags = flags & ~MASK;
由于MASK 除1号位为1以外,其他位全为0,所以~MASK 将1号位变成0,其他位全都变成1。
使用& ,任何位与1组合都得本身,所以这条语句保持除1号位以外的其他位不变。
使用&,任何位与0组合都得0,所以无论1号位的初始值是什么,都将其设置为0。
MASK中为1的位在结果中都被设置(清空)为0.
flags = flags ^ MASK;
如果使用^ 组合一个值和一个掩码,将切换该值与MASK 为1的位相对应的位,该值与MASK为0的位相对应的位不变。
flags 中与MASK为1的位相对应的位都被切换了,MASK为0的位相对应的位不变。
#include
int main()
{
int a = 3;
int b = 5;
int tmp = a;
a = b;
b = tmp;
printf("%d %d\n", a, b);
return 0;
}
交换两个数,一般,我们会创建一个临时变量充当中间变量来进行交换,但是现在不允许这么操作。
于是,我们想到了 ^
#include
int main()
{
int a = 10;
int b = 20;
a = a ^ b;
b = a ^ b; //b = a ^ b ^ b;
a = a ^ b;
printf("%d %d\n", a, b);
return 0;
}
一个数与其自身按位取反就得到了0;
0与任何数按位取反不会改变。
//binbit.c --- 使用位操作显示二进制
#include
#include //提供 CHAR_BIT 的定义,CHAR_BIT 表示每字节的位数
char* itobs(int, char*);
void show_bstr(const char *);
int main()
{
char bin_str[CHAR_BIT * sizeof(int) + 1] = { 0 };
int number = 0;
puts("Enter integers and see them in binary.");
puts("Non-numeric input terminates program.");
while (scanf("%d", &number) == 1)
{
itobs(number, bin_str);
printf("%d is ", number);
show_bstr(bin_str);
putchar('\n');
}
puts("Bye!");
return 0;
}
char* itobs(int n, char* ps)
{
int i;
const static int size = CHAR_BIT * sizeof(int);
for (i = size - 1; i >= 0; i--, n >>= 1)
{
ps[i] = (01 & n) + '0'; //这一步可以把n的最后一位表示出来
ps[size] = '\0';
}
return ps;
}
void show_bstr(const char* str)
{
int i = 0;
while (str[i])
{
putchar(str[i]);
if (++i % 4 == 0 && str[i])
{
putchar(' ');
}
}
}
赋值操作符是一个很棒的操作符,它可以让你得到一个你之前不满意的值,也就是你可以给自己重新符赋值。
int a = 10;
int x = 0;
int y = 20;
a = x = y+1; //连续赋值
x = y+1;
a = x;
同样的语义,这样的写法更加清晰爽朗且易于调试
注意:赋值操作符是自右向左来运算的。
! | 逻辑反操作 |
---|---|
- | 负值 |
+ | 正值 |
& | 取地址 |
sizeof | 操作数的类型长度(以字节为单位) |
~ | 对一个数的二进制按位取反 |
- - | 前置、后置- - |
++ | 前置、后置++ |
* | 间接访问操作符(解引用操作符) |
(类型) | 强制类型转换 |
int flag = 3;
if(flag) //flag 为真做什么
;
if(!flag) //flag为假做什么
;
#include
void test1(int arr[])
{
printf("%d\n", sizeof(arr)); // 4
}
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)); // 40
printf("%d\n", sizeof(ch)); // 10
test1(arr);
test2(ch);
return 0;
}
sizeof 后面的括号里单独放数组名,表示整个数组,所以两个数组的大小分别是40和10
当把数组名作为参数传给函数时,传过去的是地址,也就是指针变量,指针变量是4个字节或8个字节。
while(~scanf("%d",&n))
{
;
}
当scanf 读取失败时为EOF,EOF = 1
对1进行按位取反变为 0
while(0)表示循环终止
#include
int main()
{
int a = 10;
int x = ++a;
printf("%d\n", x);
//先对a进行自增,然后再使用a,x为11
int y = --a;
//先对a进行自减,然后再使用a,y为10
printf("%d\n", y);
return 0;
}
#include
int main()
{
int a = 10;
int x = a++;
//对a先使用,再自增,这样x的值是10,之后a变成11
int y = a--;
//对a先使用,再自减,这样y的值是11,之后a变成10
return 0;
}
上面就是减减和加加的用法,前置后用,后置先用。
> | >= |
---|---|
< | <= |
!= | == |
#include
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
printf("a = %d b = %d c = %d d = %d\n", a, b, c, d);
//1 2 3 4
i = a++ || ++b || d++;
printf("a = %d b = %d c = %d d = %d\n", a, b, c, d);
// 1 3 3 4
return 0;
}
要想读懂上面这两段代码,就要知道逻辑与和逻辑或的一些规则
&&操作符,左边为假,右边不再计算
||操作符,左边为真,右边不再计算
exp1 ?exp2:exp3 |
---|
使用规则: 当exp1为真,执行exp2;当exp1为假,执行exp3。
if(a > 5)
b = 1;
else
b = -1;
可以将其转换成条件表达式
b = ((a > 5) ? 1 : -1);
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);
printf("%d\n", c);
return 0;
}
像上面这个例子,从左往右进行运算,但最终表达式的结果是括号中最后一个表达式的结果。
int arr[10]; //创建数组
arr[9] = 10; //使用下标引用操作符
void test1()
{
printf("hehe\n");
}
void test2(const char *str)
{
printf("%s\n", str);
}
int main()
{
test1(); //使用()作为函数调用操作符
test2("hello world."); //使用()作为函数调用操作符
return 0;
}
. | 结构体 . 成员名 |
---|---|
-> | 结构体指针 -> 成员名 |
下面就简单演示了下怎么样使用结构成员访问
struct Stu
{
char name[10];
int age;
char sex[5];
double score;
};
void set_age1(struct Stu stu)
{
stu.age = 18; //访问结构体中的 age 成员
}
void set_age2(struct Stu* pStu)
{
pStu->age = 19;
}
int main()
{
struct Stu stu;
struct Stu* pStu = &stu;
stu.age = 21;
set_age1(stu);
pStu->age = 20;
set_age2(pStu);
printf("%d", stu.age);
return 0;
}
int main()
{
char a = 3;
//00000011 - 先进行阶段,char只有8个比特位
//00000000000000000000000000000011 -进行整形提升
char b = 127;
//01111111
//00000000000000000000000001111111
char c = a + b;
//10000010
//11111111111111111111111110000010 -整型提升的时候,高位补充符号位
printf("%d\n", c ); // -126
return 0;
}
int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if (a == 0xb6)
printf("a");
if (b == 0x600)
printf("b");
if (c == 0xb6000000)
printf("c");
return 0;
}
int main()
{
char c = 1;
printf("%u\n", sizeof(c)); //1
printf("%u\n", sizeof(+c)); //4
printf("%u\n", sizeof(-c)); //4
return 0;
}
操作符 | 描述 | 结合性 | 是否控制求值顺序 |
---|---|---|---|
() | 聚组 | N/A | 否 |
() | 函数调用 | L-R | 否 |
[ ] | 下标引用 | L-R | 否 |
. | 访问结构成员 | L-R | 否 |
-> | 访问结构指针成员 | L-R | 否 |
++ | 后缀自增 | L-R | 否 |
– – | 后缀自减 | L-R | 否 |
! | 逻辑反 | R-L | 否 |
~ | 按位取反 | R-L | 否 |
+ | 单目,表示正值 | R-L | 否 |
- | 单目,表示负值 | R-L | 否 |
++ | 前缀自增 | R-L | 否 |
– – | 前缀自增 | R-L | 否 |
* | 间接访问 | R-L | 否 |
& | 取地址 | R-L | 否 |
sizeof | 取其长度,以字节表示 | R-L | 否 |
(类型) | 类型转换 | R-L | 否 |
* | 乘法 | L-R | 否 |
/ | 除法 | L-R | 否 |
% | 整数取余 | L-R | 否 |
+ | 加法 | L-R | 否 |
- | 减法 | L-R | 否 |
<< | 左移位 | L-R | 否 |
<< | 右移位 | L-R | 否 |
> | 大于 | L-R | 否 |
>= | 大于等于 | L-R | 否 |
< | 小于 | L-R | 否 |
<= | 小于等于 | L-R | 否 |
== | 等于 | L-R | 否 |
!= | 不等于 | L-R | 否 |
& | 按位与 | L-R | 否 |
^ | 按位异或 | L-R | 否 |
l | 按位或 | L-R | 否 |
&& | 逻辑与 | L-R | 是 |
ll | 逻辑或 | L-R | 是 |
? : | 条件操作符 | N/A | 是 |
= | 赋值 | R-L | 否 |
+= | 加等 | R-L | 否 |
-= | 减等 | R-L | 否 |
*= | 乘等 | R-L | 否 |
/= | 除等 | R-L | 否 |
%= | 取模等 | R-L | 否 |
<<= | 左移等 | R-L | 否 |
>>= | 右移等 | R-L | 否 |
&= | 按位与等 | R-L | 否 |
^= | 按位异或等 | R-L | 否 |
l= | 按位或等 | R-L | 否 |
, | 逗号 | L-R | 是 |
//代码1
a*b + c*d + e*f
上面代码在计算的时候,由于 * 比 + 的优先级高,所以只能保证 * 的计算比 + 早,但是优先级并不能决定第三个 * 比第一个 + 早执行。,所以,这是一个非法的表达式。
//代码2
c + --c;
同上,操作符的优先级只能决定自减 - - 的运算在 + 的运算的前面,但是我们并没有办法得知,+ 操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。
//代码3
int fun()
{
static int count = 1;
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();
printf("%d\n", answer);
return 0; // -10
}
虽然在大多数的编译器上求结果都是相同的。
但是上述代码 answer = fun( ) - fun( ) * fun( );中我们只能通过操作符的优先级得知:
先算乘法,再算减法。
函数的调用先后顺序无法通过操作符的优先级确定。
我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。
这一章,我们学习了各种操作符的用法和注意事项
二进制的表示和存储,
以及整型算术中的整型提升和隐式类型转换
位操作符的特点和运用场景
操作符的优先级和结合性对表达式的影响