目录
二进制和进制转换
原码、反码、补码
移位操作符
位操作符
一道面试题:
练习1:
思考题:
练习2:
逗号表达式
函数调用操作符()
结构成员访问操作符
结构体
操作符的属性:优先级、结合性
优先级:
结合性:
二进制和进制转换
- 2进制转8进制
8进制的数字每一位是0~7,0~7的数字各自写成2进制,最多有3个2进制位就够了,比如7的2进制位是111,所以2进制转8进制的时候,从2进制序列中右边低位开始向左每3个2进制位换算成一个8进制位,剩余不够3个2进制位的直接换算
如:2进制的01101011,换成8进制0153,0开头的数字会被当作8进制
- 2进制转16进制
16进制的数字每一位是0~9,a~f的数字,各自写成2进制,最多有4个2进制位就足够了,比如f的二进制是1111,所以2进制转16进制的时候,从2进制序列中右边低位开始向左每4个2进制位会换算一个16进制位,剩余不够4个二进制位的直接换算
如:2进制的01101011,换成16进制0x6b,16进制表示的时候前面加0x
原码、反码、补码
整数的2进制表示方法有三种,即原码、反码和补码
有符号整数的三种表示方法均有符号位和数值位两部分,2进制序列中,最高位的一位被当做是符号位,剩余的都是数值位
符号位0表示正,1表示负
正整数的原、反、补码都相同
负整数的三种表示方法各不相同
对于整型来说,数据存放内存中存放的是补码;原因:在计算机系统中,数值一律用补码来表示和存储,使用补码,可以将符号位和数值域统一处理,同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码和原码相互转换,其运算过程是相同的(补码取反加一得到原码),不需要额外的硬件电路
移位操作符
- 左移操作符:左边抛弃,右边补0
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 6;
int b = (a << 1);
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
运行结果:
a本身不变
- 右移操作符:
右移分两种:1.逻辑右移:左边用0填充,右边丢弃
2.算术右移:左边用原该值的符号位填充,右边丢弃(推荐)
位操作符
&按位与 &&逻辑与
|按位或 ||逻辑或
^按位异或:相同为0,相异为1
~按位取反 !逻辑取反
他们的操作数必须是整数
一道面试题:
- 最先想到的方法:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
//不添加变量,实现两个数的交换
int main()
{
int a = 1, b = 2;
a = a + b;
b = a - b;
a = a - b;
printf("a=%d,b=%d", a, b);
return 0;
}
运行结果:
此方法有一个弊端,如果两个数很大,就超出了整型的范围
- 用另一种方法(按位异或):
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
//不添加变量,实现两个数的交换
int main()
{
int a = 1, b = 2;
a = a ^ b;//a相当于一把钥匙,在b处找到a,在a处找到b
b = a ^ b;
a = a ^ b;
printf("a=%d,b=%d", a, b);
return 0;
}
运行结果:
代码分析:
1^1=0;
1^0=1;
b=a^b^b=a;
a=a^b^a=b;
实现了交换,也不会溢出;
练习1:
- 编写代码实现:求一个整数存储在内存中的二进制中1的个数
- 首先想到的方法:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
unsigned int n;//正负数都可以!
scanf("%d", &n);
int count = 0;
while (n)
{
if (n % 2 == 1)
count++;
n /= 2;
}
printf("%d", count);
return 0;
}
但是此方法如果不是unsigned,则不适用于负数
- 另一种方法:
int main()
{
unsigned int n;
scanf("%d", &n);
int count = 0;
while (n)
{
if (n & 1 == 1)
//如果一个数最后一位是1,则得到的数为1,否则为0;
//逻辑与&
count++;
//n /= 2;//将最后一位去除,也就是右移一位,即,下面一句话
n >>= 1;
}
printf("%d", count);
return 0;
}
- 另一种方法(不需要unsigned):(不在乎正负数)
int main()
{
int n;
int count = 0;
scanf("%d", &n);
int i = 0;
for (i = 0; i < 32; i++)
{
if ((n >> i) & 1 == 1)
count++;
}
printf("%d", count);
return 0;
}
- 还有一种方法,不易想到:
n=n&(n-1)//能将n的二进制中最右边的1去掉;
int main()
{
int n;
int count = 0;
scanf("%d", &n);
while (n)
{
n = n & (n - 1);
count++;
}
printf("%d", count++);
return 0;
}
思考题:
如何判断一个数是否是2的次方?
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int n;
scanf("%d", &n);
if ((n & (n - 1) )== 0)
printf("yes!");
else
printf("no!");
return 0;
}
练习2:
二进制位置0或者置1
编写代码将n(例如13)的二进制序列的第5位修改为1,然后再改为0
先或后与
int main()
{
int n;
scanf("%d", &n);
n |= (1 << 4);//把第5位改为1
printf("%d\n", n);
n &= (~(1 << 4));//把第5位改为0
printf("%d", n);
return 0;
}
逗号表达式
逗号表达式,从左向右依次执行,整个表达式的结果是最后一个表达式的结果
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 1, b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);//a=12,b=12+1=13;
printf("%d", c);//13
return 0;
}
函数调用操作符()
函数调用操作符的参数至少为1个,如:
void test()
{
printf("hehe\n");
}
int main()
{
test();//1个参数
return 0;
}
结构成员访问操作符
结构体
C语言中有了内置类型,如char,short,int,float,但只有这些是远远不够的,为了解决这一问题,增加了结构体这种自定义的数据类型,来创造出适合的类型
结构体关键字:struct
结构体是用来描述一个复杂对象,里面可以包含多个属性
结构体中涉及2个操作符:. 和 ->,也叫成员访问操作符
结构是一些值的集合,这些值称为成员变量,结构的每个成员可以是不同类型的变量,如:标量,数组,指针,甚至是其他的结构体
结构的声明:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
struct student
{
char name[20];
int age;
int high;
float weight;
char id[16];//成员列表
}s4,s5,s6;//分号不能少
//s4,s5,s6是全局的结构体变量
struct student s7;//全局的
int main()
{
struct student s1={"张三",18,180,75.5f,"2328202311"};//初始化
struct student s2={.age=19,.name="李四",.weight=80.5,.high=177,.id="2328202312"};
//结构体变量.成员名
struct student s3;//局部的
printf("%s %d %d %.1f %s\n",s1.name,s1.age,s1.high,s1.weight,s1.id);
}
操作符的属性:优先级、结合性
C语言的操作符有2个重要的属性:优先级、结合性,这两个属性决定了表达式求值的计算顺序
优先级:
结合性:
如果两个运算符优先级相同,则需看结合性,根据运算符是左结合还是右结合,大部分运算符是左结合(从左到右执行)
圆括号()的优先级最高,可以使用它改变其他运算符的优先级