1. 操作符的分类
2. 二进制和进制转换
15 的 2 进制: 111115 的 8 进制: 1715 的 10 进制: 1515 的 16 进制: F//16 进制的数值之前写: 0x//8 进制的数值之前写 :0
我们重点介绍⼀下二进制:
2.1 2进制转10进制
2.1.1 10进制转2进制数字
2.2 2进制转8进制和16进制
2.2.1 2进制转8进制
2.2.2 2进制转16进制
3. 原码、反码、补码
在计算机系统中,数值⼀律⽤补码来表⽰和存储。原因在于,使⽤补码,可以将符号位和数值域统⼀处理;同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
4. 移位操作符
4.1 左移操作符
移位规则:左边抛弃、右边补0
#include<stdio.h>
int main()
{
int n = 10;
int m = n << 1;
printf("%d ",n);
printf("%d ",m);
return 0;
}
左移相当于*2的效果
4.2 右移操作符
1. 逻辑右移:左边⽤0填充,右边丢弃
2. 算术右移:左边⽤原该值的符号位填充,右边丢弃
两种右移的取决于编译器,例如大部分编译器(例如vs2022)使用的是算术右移,即补充的是符号位。
#include<stdio.h>
int main()
{
int n = 10;
int m = n >> 1;
printf("%d ",n);
printf("%d ",m);
return 0;
}
int num = 10 ;num>> -1 ; //error
5. 位操作符:&、|、^、~
&-----按位与,有0则为0,同时为1则为1| -----按位或,有1则为1,同时为0则为0^-----按位异或,相同为0,相异为1~-----按位取反,二进制位取反(包括符号位)
注:他们的操作数必须是整数
#include <stdio.h>
int main()
{
int num1 = -3;
int num2 = 5;
printf("%d\n", num1 & num2);
printf("%d\n", num1 | num2);
printf("%d\n", num1 ^ num2);
printf("%d\n", ~0);
return 0;
}
解析:
100000011 -3的原码
1111111100 -3的反码
1111111101 -3的补码
000000101 5的原码,反码,补码
-3&5=5
1111111101
000000101得
000000101,看到符号位为0,故得出的为正数的源反补码
-3|5=-3
1111111101
000000101
1111111101,符号位为1,故结果为负数
100000010
100000011
-3^5=-8
1111111101
000000101
1111111000,符号位为1,故结果为负数
100000111
100001000
一道变态的面试题:
不能创建临时变量(第三个变量),实现两个整数的交换。
方法一:创建临时变量的方法(常用,且计算机负荷小)
int main()
{
int a = 3;
int b = 5;
int tmp = 0;
printf("before:a=%d,b=%d",a,b);
tmp = a;
a = b;
b = tmp;
printf("after:a=%d,b=%d",a,b);
return 0;
}
a看作醋,b看作酱油,tmp看作空瓶子
tmp=a;把醋倒进空瓶子
a=b;;把酱油倒进原醋瓶子(现空瓶子)
b=tmp;把原空瓶子(现装醋)倒进酱油瓶子
方法二:加减法
缺陷:可能会溢出
a、b是整形,占4个字节的空间是有限的,当a,b(或a+b)的值大于4个字节时,转化成为的二进制序列就会丢失,导致结果运行错误。
int main()
{
int a = 3;
int b = 5;
printf("before:a=%d,b=%d", a, b);
a = a + b;
b = a - b;
a = a - b;
return 0;
}
方法三:异或法
这种方法就不会产生溢出,异或相同为0,相异为1,不存在产生多余的二进制
int main()
{
int a = 3;
int b = 5;
printf("before:a=%d,b=%d\n", a, b);
a = a ^ b;
b = b ^ a;
a = a ^ b;
printf("after:a=%d,b=%d\n", a, b);
return 0;
}
异或运算的一些特殊场景
相同为0,相异为1
①a^a=0 ②a^0=a ③异或也支持交换律
解析:a=a^b,将a带入b=b^a中得,b=b^a^b=a,
将a=a^b,b=a带入a=a^b中得a=a^b^a=b
int Count_one(int m)
{
int count = 0;
while (m)
{
if (m % 2 == 1)
count++;
m /= 2;
}
return count;
}
可是当接收到负数时,例如-2,-2%2=-1,-2/2=0,m=0,while停止,结果为0。
可将传入函数的参数强制类型转换为无符号数unsigned 。int Count_one(unsigned int m)
故该方法不适用于负数。
方法二:
利用&1,如果末尾为1,则结果输出为1,再将m右移,再次&1,重复32次。
int Count_one(int m)
{
int count = 0;
for (int i = 1; i < 32; i++)
{
if (1 & (m >> i))
count++;
}
return count;
}
重复性太高,效率也比较低,也不太合适
方法三:n&=(n-1)
最开始为谷歌的面试题,每次n&=(n-1)都会使最末位变为0。
例如
n为15--1111,n-1=1110,n&(n-1)=1110
n=1110,n-1=1101,n&(n-1)=1100......
代码如下:
int Count_one(int m)
{
int count = 0;
while (m)
{
m &= (m - 1);
count++;
}
}
练习2:⼆进制位置0或者置1
13 的 2 进制序列: 00000000000000000000000000001101将第 5 位置为 1 后: 00000000000000000000000000011101将第 5 位再置为 0 : 00000000000000000000000000001101
代码如下:
int main()
{
int num1 = 13;
printf("%d\n",num1);
int num2 = num1 | (1 << 4);
printf("%d\n",num2);
int num3 = num2 & (~(1 << 4));
printf("%d\n",num3);
return 0;
}
6. 单目操作符
!、 ++ 、 -- 、 & 、 * 、 + 、 - 、 ~ 、 sizeof 、 ( 类型 )
7. 逗号表达式
exp1, exp2, exp3, …expN
//代码1
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
c是多少?
//代码2
if (a =b + 1, c=a / 2, d > 0)
//代码3
a = get_val();
count_val(a);
while (a > 0)
{
//业务处理
//...
a = get_val();
count_val(a);
}
如果使⽤逗号表达式,改写:
while (a = get_val(), count_val(a), a>0)
{
//业务处理
}
8. 下标访问[]、函数调用()
8.1 [ ] 下标引用操作符
int arr[ 10 ]; // 创建数组arr[ 9 ] = 10 ; // 实⽤下标引⽤操作符。[ ] 的两个操作数是 arr 和 9 。
8.2 函数调用操作符
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int Add(int x,int y)
{
return x + y;
}
int main()
{
test(); //操作数为函数名add,这⾥的()就是函数调⽤操作符。
Add(2,3);//操作符为函数名与括号内的数字,共三个
return 0;
}
函数调用操作符最少有1个操作数——函数名
9. 结构成员访问操作符
9.1 结构体
C语言已经提供了内置类型,如:char、short、int、long、float、double等,但是只有这些内置类型还是不够的,假设我想描述学生,描述一本书,这时单⼀的内置类型是不行的。描述⼀个学生需要名字、年龄、学号、身高、体重等;描述⼀本书需要作者、出版社、定价等。C语言为了解决这个问题,增加了结构体这种自定义的数据类型,让程序员可以自己创造适合的类型。
结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如:标量、数组、指针,甚至是其他结构体。
9.1.1 结构的声明
struct tag
{
member-list;
}variable-list;
tag是自定义的,member-list是成员
struct Student
{
char name[20];//英文一个字母占据一个字符,一个汉字占据2个字符
char sex[10];
int age;
char id[20];//必须要多一位来放置\0
}s5,s6,s7;//s5,s6,s7都是结构体变量(全局变量)
上述只是创建了类型,类似于"模具“
而创建变量才会向内存申请一块空间
//全局变量
int main()
{
//局部变量
struct Student s1;
return 0;
}
9.1.2 结构体变量的定义和初始化
(嵌套)结构体的定义
struct Student
{
char name[20];//英文一个字母占据一个字符,一个汉字占据2个字符
char sex[10];
int age;
char id[20];//必须要多一位来放置\0
};
struct Point
{
int x;
int y;
}p2 = { 0,0 }, p3 = {1,2};
struct Note
{
int data;
struct Point p;//嵌套式要编写嵌套结构体的名字p
struct Note* next;//嵌套自身结构体的指针名为next
};
(嵌套)结构体的初始化
int main()
{
//结构体变量的初始化
struct Point p1 = { 2,3 };
struct Student s1 = { "屈致远","男",20,"20225096015" };//按顺序初始化
struct Student s2 = { .sex = "男",.name = "屈致远",.id = "20225096015",.age = 18 };//按自己的顺序初始化结构体变量
struct Note n1 = { 200,{2,3},NULL };//结构体嵌套的初始化
struct Note n2 = { .p = {2,5},.next = NULL,.data = 2022 };//按自己的顺序初始化结构体变量
//打印结构体变量
scanf("%d", &(s1.age));//直接访问成员
printf("%s %s %d %s\n",s1.name,s1.sex,s1.age,s1.id);
//.操作符,结构体变量.结构体成员
return 0;
}
9.2 结构成员访问操作符
9.2.1 结构体成员的直接访问
#include <stdio.h>
struct Point
{
int x;
int y;
}p = {1,2};
int main()
{
printf("x: %d y: %d\n", p.x, p.y);
return 0;
}
9.2.2 结构体成员的间接访问
#include <stdio.h>
struct Point
{
int x;
int y;
};
int main()
{
struct Point p = {3, 4};
struct Point *ptr = &p;
ptr->x = 10;
ptr->y = 20;
printf("x = %d y = %d\n", ptr->x, ptr->y);
return 0;
}
#include <stdio.h>
#include <string.h>
struct Stu
{
char name[15];//名字
int age; //年龄
};
void print_stu(struct Stu s)
{
printf("%s %d\n", s.name, s.age);
}
void set_stu(struct Stu* ps)
{
strcpy(ps->name, "李四");
ps->age = 28;
}
int main()
{
struct Stu s = { "张三", 20 };
print_stu(s);
set_stu(&s);
print_stu(s);
return 0;
}
10. 操作符的属性:优先级、结合性
10.1 优先级
3 + 4 * 5 ;
10.2 结合性
5 * 6 / 2 ;
11. 表达式求值
11.1 整型提升
char ——是字符类型char也属于整形家族——存储的是ASCII码值,ASCII码值是整数zh
char a = 5;
char b = 125;
char c = a + b;
这里char——>int
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执⾏,CPU内整型运算器(ALU)的操作数的字节⻓度⼀般就是int的字节⻓度,同时也是CPU的通⽤寄存器的⻓度。因此,即使两个char类型的相加,在CPU执⾏时实际上也要先转换为CPU内整型操作数的标准⻓度。通⽤CPU(general-purpose CPU)是难以直接实现两个8⽐特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种⻓度可能⼩于int⻓度的整型值,都必须先转换为int或unsigned int,然后才能送⼊CPU去执⾏运算
char a,b,c;
...
a = b + c;
b和c的值被提升为普通整型,然后再执行加法运算。
1. 有符号整数提升是按照变量的数据类型的符号位来提升的2. 无 符号整数提升,高位补0
char中存储的数字字符,也和int类型一样可以进行运算,但他们的运算过程不一样,所得到的结果也不一样。存在char,unsigned char。int,unsigned int
int main()
{
char a = 5;
//00000101
char b = 125;
//01111101
char c = a + b;
//a和b都是char类型,长度是小于int类型的长度的
//在计算的时候,默认会发生整型提升
//00000000000000000000000000000101 -a
//00000000000000000000000001111101 -b
//10000010 c=a+b
//整型提升,前面补符号位
//11111111111111111111111110000010 c的补码
//10000000000000000000000001111101
//10000000000000000000000001111110 c的原码,结果为-126
//%d - 按照有符号整数的形式打印
printf("%d\n",c);
return 0;
}
表达式里面如果是char short类型的会整型提升,本身是整形就不需要提升
11.2 算术转换
long doubledoublefloatunsigned long intlong intunsigned intint
11.3 问题表达式解析
11.3.1 表达式1
// 表达式的求值部分由操作符的优先级决定。// 表达式 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
解决方案:
1,加括号 2,拆表达式
11.3.2 表达式2
// 表达式 2c + --c;
例如:c=5。如果先进行--在+之前进行,则(c=5)+(c--=4)=9 ;如果在进行--后再+,则
(c--)+(c--)=8
11.3.3 表达式3
//表达式3
int main()
{
int i = 10;
i = i-- - --i * ( i = -3 ) * i++ + ++i;
printf("i = %d\n", i);
return 0;
}
依旧是单目操作符--和++与双目操作符之间混杂,一般的优先级为后置++-->+或->前置++--
最主要的问题是先将单目操作符进行完再进行双目操作符,还是进行部分单目操作符之后进行双目操作符,最后进行剩余的单目操作符,这都取决于编译器
11.3.4 表达式4
11.3.5 表达式5:
//表达式5
#include <stdio.h>
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n", ret);
printf("%d\n", i);
return 0;
}
//尝试在linux 环境gcc编译器,VS2013环境下都执⾏,看结果。
VS2022运行结果: