目录
- 前半部分的操作符详解在这个链接——操作符详解1
0.过渡
- 在操作符1中我们最后讲了位操作符的概念
- 这里我想举几个例子加深理解
0.1 不创建临时变量,交换两数
- 这是一道很变态的面试题
- 首先我们可以看看下面两个等式:
- a ^ a =0
- a ^ 0 = a
- 接着我们来理解一下这道变态面试题的答案
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
a = a^b;
b = a^b;//根据上面的代码,这里的a^b=a^b^b=a^0=a,这里相当于把a给b
a = a^b;//根据上面的代码,这里的a^b=a^a^b=0^b=b,这里相当于把b给a
printf("a = %d b = %d\n", a, b);
return 0;
}
- 运行结果如下:
0.2 求整数转成二进制后1的总数
- 首先我们看一下当一个二进制数和1按位与后的结果
- 我们发现当一个二进制的最后一位是 0 时,和1按位与后的结果为0
- 当最后一位为1时,按位与1后的结果时1
- 这样就相当于把二进制的最后一位给取出来了,那么我只要加个循环,把每一位都取出来再相加即可(这里我为了方便只写了八个二进制数,×86环境为32位,×64环境为64位)
- 所以代码如下:
#include <stdio.h>
int main()
{
int num = -1;
int i = 0;
int count = 0;//计数
for(i=0; i<32; i++)
{
if( num & (1 << i) )
count++;
}
printf("⼆进制中1的个数 = %d\n",count);
return 0;
}
- 但是这样有一个缺点,就是不管二进制是多少都要循环32遍,如果二进制只是1,那31遍循环都白循环了
- 所以我们想想是否还有更加高效的代码呢
- 比如我们可以试试 num&(num-1)
- 我们会发现最后的1消失了
- 同理,如果我们把结果再重复一次运算
- 我们会发现倒数第二个1也消失了,如果我们再重复一次会不会倒数第四位的1也消失呢?
- 我们发现确实如此!
- 那么我们只要再加上num != 0 这个条件就可以循环起来了
- 代码如下:
#include <stdio.h>
int main()
{
int num = -1;
int i = 0;
int count = 0;//计数
while(num)
{
count++;
num = num & (num-1);
}
printf("⼆进制中1的个数 = %d\n",count);
return 0;
}
1.单目表达式
- 单目操作符有这些:
- !、++、–、&、*、+、-、~ 、sizeof、(类型)
2. 逗号表达式
- 顾名思义,就是一些表达式用逗号连接起来,如图:
- 逗号表达式,从左向右依次执行。整个表达式的结果是最后⼀个表达式的结果。
3. 下标访问[ ]、函数调用( )
3.1 下标访问[ ]
- 操作数:⼀个数组名 + ⼀个索引值
3.2 函数调用( )
- 接受⼀个或者多个操作数:第⼀个操作数是函数名,剩余的操作数就是传递给函数的参数。
#include <stdio.h>
void test1()
{
printf("hehe\n");
}
void test2(const char *str)
{
printf("%s\n", str);
}
int main()
{
test1(); //这⾥的()就是作为函数调⽤操作符。
test2("hello bit.");//这⾥的()就是函数调⽤操作符。
return 0;
}
4. 结构体成员访问操作符
4.1 结构体
- C语⾔已经提供了内置类型,如:char、short、int、long、float、double等,但是只有这些内置类型还是不够的,
- 假设我想描述学生,描述⼀本书,这时单⼀的内置类型是不行的。描述⼀个学生需要名字、年龄、学号、身高、体重等;描述⼀本书需要作者、出版社、定价等。
- C语言为了解决这个问题,增加了结构体这种自定义的数据类型,让程序员可以自己创造适合的类型。
- 结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如:标量、数组、指针,甚⾄是其他结构体
4.1.1 结构体的申明
- 比如描述一个学生:
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}a;//分号不能丢,这里的a为结构体类型的全局变量
4.1.2 结构体变量的定义和初始化
//变量的定义
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
struct Point p3 = {10, 20};//初始化
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
};
struct Stu s1 = {"zhangsan", 20};//初始化
struct Stu s2 = {.age=20, .name="lisi"};//指定顺序初始化
//嵌套结构
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化
4.2 结构体成员的访问
4.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;
}
- 使用方式:结构体变量 . 成员名
4.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;
}
- 使用方式:结构体指针 -> 成员名
5. 操作符的属性:优先级、结合性
- 链接在这里——操作符优先级
- 结合性是指在遇到相同优先级的操作符的时候的运算顺序
6. 整型提升和算术转换
6.1 整型提升
- C语⾔中整型算术运算总是至少以缺省整型类型的精度来进行的
- 为了获得这个精度,表达式中的字符和短整型操作数在使⽤之前被转换为普通整型,这种转换称为整型提升
- 那这样做有什么意义呢?
- 表达式的整型运算要在CPU的相应运算器件内执⾏,CPU内整型运算器(ALU)的操作数的字节⻓度⼀般就是int的字节⻓度,同时也是CPU的通⽤寄存器的⻓度。
- 因此,即使两个char类型的相加,在CPU执⾏时实际上也要先转换为CPU内整型操作数的标准⻓度。通⽤CPU(general-purpose CPU)是难以直接实现两个8⽐特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。
- 所以,表达式中各种⻓度可能小于int⻓度的整型值,都必须先转换为int或unsigned int,然后才能送⼊CPU去执⾏运算
- 比如:
//实例1
char a,b,c;
...
a = b + c;
- b和c的值被提升为普通整型,然后再执⾏加法运算。
- 加法运算完成之后,结果将被截断,然后再存储于a中
- 那如何进⾏整体提升呢?
- 有符号整数提升是按照变量的数据类型的符号位来提升的
- ⽆符号整数提升,⾼位补0
//负数的整形提升
char c1 = -1;
变量c1的⼆进制位(补码)中只有8个⽐特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,⾼位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的⼆进制位(补码)中只有8个⽐特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,⾼位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//⽆符号整数提升,⾼位补0
- 举一个例子:
#include <stdio.h>
int main()
{
char a = 1;//a= 00000000000000000000000000000001
char b = 128;//b= 00000000000000000000000010000000
char c = a + b;//c= 00000000000000000000000010000001
//接着因为c为char类型,需要对整型提升后的值进行截断,变为c=10000001
//此时最高位为1,表示负数的补码,再转换为原码后为11111111
//即值为-127
printf("%d", c);
return 0;
}
- 运行结果为:
6.2 算数转换
- 如果某个操作符的各个操作数属于不同的类型,那么除⾮其中⼀个操作数的转换为另⼀个操作数的类型,否则操作就⽆法进⾏。下⾯的层次体系称为寻常算术转换
long double
double
float
unsigned long int
long int
unsigned int
int
- 如果某个操作数的类型在上⾯这个列表中排名靠后,那么⾸先要转换为另外⼀个操作数的类型后执⾏运算
最后,
恭喜你又遥遥领先了别人!