9.27
按位运算
按位取与-&
两个位上两个都是必须都是1 结果才是1,否则结果为0;
常用:
1️⃣.让某一位或某些位为0:x& 0xFE
2️⃣.取一个数中的一段:x& 0xFF
FE⬆️ 使另一个数最低位变成0;
按位取或-I
两个位上有一个是一 结果就为1 否则结果就为0;
常用:
使一位或几位为1:xI 0x01(希望最后那位数上的比特是1 用±做不到)
把两个数拼起来:0x00FF I 0xFF00(结果为四个F)
FE⬆️;取或:只要对应位上有1的它就是1
按位取反-~
把每个比特位从1变成0,从0变成1(与算补码不同)每个比特取反(每个位)!!
- 想得到全部位为1的数:~0
- 7的二进制是0111,x I 7 使得低三位为1,而x&~7使得低三位为0.
逻辑运算VS按位运算
- 逻辑运算只看到两个值(0、1)
- so可以认为逻辑运算相当于把所有非零值都变成1 然后做按位运算
- 5&4——>而5&&4——>1&1——>1
- 5 I 4——>5 而5 II4——>1I1 ——>1
- ~4——>3而!4——>!1——>0
按位异或^
- 两个位相等,结果为0;两个位不相等,结果为1.
- 如果x=y,则x^y=0;
- 对同一个变量用同一个值异或两次,等于什么也没做
- xyy ——> x
移位运算
左移<<
- i << j
- i中所有的位向左移动j个位置,而右边留下的空位置填入0;
- 所有<int的类型,移位以int的方式来做,结果是int
- x <<= 1等价于x *= 2^n(最多移动的位数取决于你的int有多大)
右移>>
- i中所有位向右移j位
- 所有小于int的类型,移位以int的方式来做,结果是int
- 对于unsigned的类型,左边填0
- 对signed的类型,左边填原来的最高位(保持符号不变)
- x >>=1等价于 x /= 2^n
- x >>= n等价于 x /= 2^n
移位的位数不要用负数,这是没有定义的行为,如:x<< -2(✖)
位段
-
把一个int的若干位组成一个结构
取地址再解指针,实际上传入的是数(一个struct把它当成一个int来做些事情):
定义了一个位段之后: -
可直接用位段的成员名来访问
-
比移位、与、或方便
-
编译器会安排其中的位的排列,不具有可移植性
-
当所需求位超过一个int时 会采用多个int(大小端的问题)
可变数组
- 可增长
- 获取当前大小
- 访问元素 单元
能提供 能增长能变大小的数组:
Arrayarray_create(int init_size);
void array_free(Array *a);
int array_size(const Array a);
intarray_at(Array *a,int index);
void array_inflate(Array *a,int more_size);
array 表示用来创建这个数组
free 把该数组回收
size 数组里面有哪些单元可以用
at 获得并访问数组中的某个单元(可以读可以写 可作左值也可以作右值)
inflate 让该数组长大
返回一个结构体 使得我们在外面可以比较灵活使用这个array_create返回的 制造出来的那个array
可变数组的数据访问
封装:
返回值是指针 解引用后的返回值可以赋值!方便通过指针转入值(听不太懂。。。)
可变数组的自动生长
本malloc里面出来的东西不可自动生长
so要重新去申请一块新的空间,然后把老空间的东西用循环复制到新的空间去,扩大一次=遍历一次数组,扩容n个容量,来保证程序运行的效率
如果直接加BLOCK_SIZE,那么只要index<size数组都会增长,比如index101到120时数组会增长20次,但这期间需要增长一次就够了
inflate函数需要的参数是需要扩展的空间的大小,所以才减去a->size,前面那些算出来的是index所在block块的总体的大小
可变数组的缺陷
每次膨胀时分配新内存是一种简单、干净的方法,但复制需要时间,而且在内存受限的情况下可能会失败
申请更大块内存然后被free 回收
主要是先申请后释放的问题,如果先释放,就没法进行数据迁移了。不严格的讲只能储存到计算机内存一半的数据,因为旧数据是复制后再删除。because要先申请新的空间再释放掉之前的。
so这个方法不够高效,需要用到链表连接起来!⬇️
链表
图解:
结点把它定义成一个结构
不是递归,就是结构体里定义了一个指向这种结构体的指针
自己调用自己
Node* createList()
{
return NULL; // 返回空指针表示空链表
}
插入节点:
(头)
Node* insertAtHead(Node* head, int data)
{
Node* newNode = (Node*)malloc(sizeof(Node)); // 动态分配内存
newNode->data = data;
newNode->next = head; // 新节点指向原链表的头
return newNode; // 返回新的头指针
}
(尾)
Node* insertAtTail(Node* head, int data)
{
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = NULL;
if (head == NULL)
{
return newNode; // 如果链表为空,返回新节点
}
Node* temp = head;
while (temp->next != NULL)
{
temp = temp->next; // 遍历到链表的最后一个节点
}
temp->next = newNode; // 将新节点添加到尾部
return head; // 返回头指针
}
链表的函数
×:
1.函数传递参数的方式是直接创建一个相等类型的变量
2.这意味着函数内的“head”是一个新指针指向主函数内同一地址
3.但是并不知道主函数内head的地址(即指针的地址)
👆传进去的是地址,但是我们要改变的是地址,所以要用地址的地址
它要修改的是指针(head=p),而不是指针所指向的结构体,所以要传入head的地址,函数传入head参数,函数会在函数块内创造一个新的指针变量head,改变内部的head,main中的head不会改变。我们平时用的指针都是指向某一个变量的,指针传入函数里面实际上只是同名指针指向地址相同,但是这里指向的是NULL,而不是变量,所以实际上是不一样的,指针就是指地址,你传入head这个地址,但是在add函数中要把传进来的地址换掉,而不是改掉访问地址的值
链表的搜索
搜索链表中是否存在某个值:
Node* search(Node* head, int value)
{
Node* current = head;
while (current != NULL)
{
if (current->data == value)
{
return current; // 找到值,返回对应节点
}
current = current->next; // 继续遍历
}
return NULL; // 未找到,返回NULL
}
链表的删除
删除链表中指定值的节点:
Node* deleteNode(Node* head, int value)
{
if (head == NULL) return NULL; // 链表为空
if (head->data == value) { // 删除头节点
Node* temp = head->next;
free(head); // 释放内存
return temp; // 返回新的头指针
}
Node* current = head;
while (current->next != NULL && current->next->data != value)
{
current = current->next; // 遍历链表
}
if (current->next != NULL)
{ // 找到节点
Node* temp = current->next;
current->next = temp->next; // 断开连接
free(temp); // 释放内存
}
return head; // 返回头指针
}
链表的清除
释放链表中所有节点的内存:
void clearList(Node* head)
{
Node* current = head;
Node* temp;
while (current != NULL)
{
temp = current;
current = current->next; // 先保存下一个节点
free(temp); // 释放当前节点内存
}
}