单链表详解(无哨兵位),实现增删改查

1.顺序表对比单链表的缺点

  1. 中间或头部插入时,需要移动数据再插入,如果数据庞大会导致效率降低
  2. 每次增容就需要申请空间,而且需要拷贝数据,释放旧空间
  3. 增容造成浪费,因为一般都是以2倍增容

2.链表的基础知识

  1. 链表也是线性表的一种。物理结构:不一定线性,逻辑结构:一定是线性的
  2. 链表物理结构也不是线性的。链表由一个一个的节点组成
  3. 节点由数据和指向下一个地方的指针。每一个节点都会存储下一个节点的地址
  4. List 表示链表, S表示single ,Node表示节点

2.1链表基本结构

typedef int SLDataType;//节点类型,S 表示节点,L表示链表
typedef struct SListNode
{
	SLDataType data;
	struct SListNode* next;
}SLNode;

3.代码实现

  1. 设置三个文件,SList.h 头文件 SList.c 功能实现文件,test.c测试文件
  2. 声明写在头文件里面 SList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>//用一个就申请一个空间
#include <assert.h>

typedef int SLDataType;
typedef struct SListNode
{
	int data;
	struct SListNode* next;
}SLNode;

//链表打印
void SLPrint(SLNode* phead);

3.1.实现思路

  1. 为两个节点开辟空间,并通过赋值给他们链接起来,通过节点内的地址
  2. 通过传过来的头节点去找尾节点,从而达到遍历链表
  3. 通过改变pcur的指向,相当于可以访问下一块空间了

void SLPrint(SLNode* phead)//phead 表示头节点
{
	SLNode* pcur = phead;//pcur 临时的节点
	while (pcur != 0)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

3.1.1测试方法

  1. 开辟了空间并且把他们都串起来。
void SListTest1()
{
	SLNode* Node1 = (SLNode*)malloc(sizeof(SLNode));//初始化
	Node1->data = 1;
	SLNode* Node2 = (SLNode*)malloc(sizeof(SLNode));
	Node2->data = 2;
	SLNode* Node3 = (SLNode*)malloc(sizeof(SLNode));
	Node3->data = 3;
	Node1->next = Node2;
	Node2->next = Node3;
	Node3=->next = NULL;//最后一个置为NULL

	//打印链表
	SLPrint(Node1);
}

3.1.2当while循环条件不一样时

  1. 这里用图说明问题,当pcur 和pcur->next 。pcur不为NULL,还会多走一步。
  2. pucr->next 为空不往下走,就是说,pcur 比pcur->next,多走一步

4.0尾插

  1. 在尾插开始之前,需要判断两种情况
    1. 第一种就是空链表和非空链表,如果为空链表,肯定要开辟空间的
    2. 在这个新开辟的空间里面,顺便一起把需要插入的数据放这里一起插入,也就说涉及到插入会对空间进行增加的就要新空间
    3. 开辟好了空间,判断有效性,然后放入数据,再给个NULL
//新空间
SLNode* SLBuyNode(SLDataType x)
{
	SLNode* Newnode = (SLNode*)malloc(sizeof(SLNode));
	if (Newnode == NULL)
	{
		perror("malloc New");
		exit(1);//非0表示异常返回,会导致直接跳出整个程序
	}
	//开辟完新空间记得放入数据
	Newnode->data = x;
	Newnode->next = NULL;

	return Newnode;//开辟完空间要记得返回
}
  1. 如果不是空链表,就要通过头结点找到尾节点,一定是要找到存放下一个节点的next
  2. 找了尾节点,就可以直接在尾节点放上新开辟好,因为对应数据已经在新空间里弄好了
//链表尾插
void SLPushBack(SLNode** pphead, SLDataType x)
{
	assert(pphead);//这里不能判断*pphead,因为有可能本来就是空链表
	//尾插的时候需要看是否为NULL,空链表和非空链表
	SLNode* Newnode = SLBuyNode(x);//把需要插入的值传过去
	if (*pphead == NULL)
	{
		*pphead = Newnode;//如果是NULL就把新开辟的给到头节点
	}
	else
	{
		SLNode* ptail = *pphead;
		while (ptail->next != NULL)//查找尾节点
		{
			ptail = ptail->next;
		}
		ptail->next = Newnode;//新节点里面已经置为空指针
	}
}

4.1测试方法

  1. 往后的测试方法都不写了,学会了后根据对应参数测试就OK了。
  2. 这里注意一定要传二级指针的地址,对一级指针改变指向就是要传二级指针
  3. 举个例子,看下面一个传址调用,另外一个是传值调用

SListTest2()
{
	SLNode* node = NULL;//新创建的节点要初始化
	//测试尾插
	//SLPushBack(&node, 2);
	//SLPushBack(&node, 3);
	//SLPushBack(&node, 4);
	//SLPrint(node);
	//测试头插
	SLPushFront(&node, 4);
	SLPushFront(&node, 3);
	SLPushFront(&node, 2);
	SLPushFront(&node, 1);
	SLPrint(node);
}

1.可以看上面你要&node的地址才能对node改变,而不是他创建node,而你就传node,这个就和传值调用不就一样了吗?

5.0头插

  1. assert(pphead);//这里不能判断*pphead,因为有可能本来就是空链表
  2. 头插当然就很简单了,直接让新开辟的空间指向头节点
  3. 然后把头结点权限给新空间,让他newnode来当第一个
//链表头插
void SLPushFront(SLNode** pphead, SLDataType x)
{
	assert(pphead);//这里不能判断*pphead,因为有可能本来就是空链表
	SLNode* newnode = SLBuyNode(x);
	newnode->next = *pphead;//把之前的头节点给到新节点
	*pphead = newnode;//头插成为新节点
}

6.0尾删

  1. 第一种:如果是一个节点就可以直接删除
  2. 第二种:在进行尾删的时候也要找尾,而且还有保留前一个节点的地址
  3. 然后保留的前一个节点的地址,他指向的下一个地址就可以置为NULL
//链表尾删
void SLDelBack(SLNode** pphead)
{
	//传过来的地址必须有效
	assert(pphead && *pphead);
	//首先要考虑2种情况,有链表的情况,一个节点的情况
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLNode* ptail = *pphead;//再创建一个临时的指针更好,可以避免优先级问题,还有能保留源地址
		SLNode* pcur = *pphead;
		while (ptail->next)
		{
			pcur = ptail;//ptail指向前的一个节点
			ptail = ptail->next;
		}
		free(ptail);//直接free ptail
		ptail = NULL;
		pcur->next = NULL;
	}
}

6.1尾删最后一步,倒推图

7.头删

  1. 链表不能为空,为NULL删什么啊,当然对应的二级指针也不能为空
  2. -> 符号优先级 比 * 星号高
  3. 两个问题点
    1. 一个节点:这个问题怎么提出的因为链表为空不能删会报错,那么本身方法无意义了
    2. 多个节点:先把指向的节点给到*pphead,也就是我们的头节点,然后再删除之前的头节点
  4. 头删的时候要把这个节点删除,但是我们要能找到下一个节点并成为头节点
  5. 由此可以得出先把next = Node1->next。不能直接给赋值给*pphead,因为后面还要删除头节点,所以我们先保存下一个节点的地址
void SLDelFront(SLNode** pphead)
{
	assert(pphead && *pphead);//链表为空就没有删除的意义了
	SLNode* next = (*pphead)->next;//保存下一个节点
	free(*pphead);
	*pphead = next;
}

8.链表查找节点

  1. 在查找链表的时候,你可能要想想,指针走到后面的NULL,万一后面还有需要查找的值呢?所以还要备份一个源数据
  2. 遍历向后查找,找到返回这个节点。
  3. 这个查找代码就是向后遍历,如果相等就返回,否则返回NULL
SLNode* SLFind(SLNode* phead, SLDataType x)
{
	assert(phead);//等价与 phead != NULL
	SLNode* pcur = phead;//可能需要多次查找
	while (pcur)//结束调试是最后一个节点的后一个NULL,往下查找无意义
	{
		if (pcur->data == x)
		{
			return pcur;//如果到了当前节点,就找到当前节点下面的值
		}
		pcur = pcur->next;//访问到当前节点指向的下一个,没有向下查找条件会死循环
	}
	return NULL;
}

9.指定位置之前插入

  1. insert 插入 after 在什么之后 prev 在什么之前,对数据增加和删除,就需要二级指针
  2. 链表的二级指针不能为空,而且链表也不能为空 因为要pos要通过链表找打指定位置,当然pos也不能为NULL,在NULL的位置插入吗
  3. prev需要找到pos之前的位置while(prev->next ! = 0),没有找到就继续往下走
  4. prev 和 pos都找到了,那么要在两者之前插入
  5. 注意:关于pos参数,是Find查找到前的,因为find是查找函数,找到指定位置然后返回
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{
	assert(pphead && *pphead);
	assert(pos);//不能为空,为你还找啥呢?
	if (pos == *pphead)
	{
		SLPushFront(pphead, x);
	}
	else
	{
		SLNode* prev = *pphead;//prev 表示 pos的前一个节点
		SLNode* newnode = SLBuyNode(x);
		while (prev->next != pos)//如过指向的下一个节点不是pos,则继续往下找
		{
			prev = prev->next;
		}
		newnode->next = pos;
		prev->next = newnode;
	}
}

10.在指定位置之后插入数据

  1. 从图中可以看出正确的方式是的二种,第一种导致找不到pos->next的节点
  2. 在指定位置之后不需要第一个有效节点,用不到第一个节点,通过pos就可以找到后面一个节点
//在指定位置之后插入
void SLInsetAfter(SLNode* pos, SLDataType x)
{
	assert(pos);//指定为空,还怎么删除
	SLNode* newnode = SLBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;//pos->next 原先指向的数据已经给到newnode->next
}

11.pos位置删除

  1. 首先就是要断言,传过来的二级指针里面装着的链表,当然都不能为空,pos节点为空了就说明指定位置没有该数据
  2. 情况1:pos不是第一个有效节点的位置
  3. 情况2:pos是第一个有效节点的位置
//删除pos节点
void SLErase(SLNode** pphead, SLNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);
	//两种情况:1.pos节点没有指向第一个有效节点,反之就是指向了
	if (pos == *pphead)
	{
		//相当于头删了
		SLDelFront(pphead);
	}
	else
	{
		SLNode* prev = *pphead;
		while (prev->next != pos)//他指向的节点,等于了pos就相当于找到了前一个节点
		{
			prev = prev->next;//通过当前的找到下一个,直到找到pos
		}
		//prev -> pos -> pos->next
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

12.pos之后的节点删除

  1. pos和pos->next,的节点都不能为NULL,下一个节点为NULL你删除什么?
  2. 在删除pos之后的节点之前,我们要先保存pos->next这个节点,防止内存泄漏,创建一个临时的del 变量来保存
//删除pos节点之后的
void SLEraseAfter(SLNode* pos)
{
	assert(pos && pos->next);//都不能为空
	SLNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

13.销毁节点

  1. 销毁节点需要一个一个的销毁
  2. 大致的思路是
    1. 先保存下一个节点的地址,然后再释放这个节点
    2. 释放完后,把下一个节点的地址给pcur,从而构成循环
    3. 直到为NULL为止
//销毁链表
void SLDesTroy(SLNode** pphead)
{
	SLNode* pcur = *pphead;
	while (pcur)
	{
		SLNode* del = pcur->next;//在销毁之前保存下一个节点的地址
		free(pcur);
		pcur = del;
	}
	pcur = NULL;
}

4.最后的完整代码

4.1头文件部分

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int SLDataType;
typedef struct SListNode
{
	SLDataType val;
	struct SListNode* next;
}SLNode;

//链表打印
void SLPrint(SLNode* phead);

//链表尾插和头插
void SLPushBack(SLNode** pphead, SLDataType x);
void SLPushFront(SLNode** pphead, SLDataType x);

//链表头删和尾删
void SLDelBack(SLNode** pphead);
void SLDelFront(SLNode** pphead);

//链表查找
SLNode* SLFind(SLNode* phead, SLDataType x);

//链表pos之前插入
void SLInset(SLNode** pphead, SLNode* pos, SLDataType x);

//链表pos之后插入
void SLInsetAfter(SLNode* pos, SLDataType x);

//链表pos节点删除
void SLErase(SLNode** pphead,SLNode* pos);

//链表pos之后删除
void SLEraseAfter(SLNode* pos);

//链表的销毁
void SLDesTroy(SLNode* pphead);

4.2方法部分

#include "SList.h"
void SLPrint(SLNode* phead)
{
	SLNode* pcur = phead;
	while (pcur)//如果是pcur->next为结束条件会导致,提前结束不进入循环
	{
		printf("%d->", pcur->val);
		pcur = pcur->next;
	}
	printf("NULL\n");
}
//新节点空间
SLNode* SLBuyNode(SLDataType x)
{
	SLNode* Newnode = (SLNode*)malloc(sizeof(SLNode));
	if (Newnode == NULL)
	{
		perror("malloc new");
		exit(1);//异常返回退出整个工程
	}
	Newnode->val = x;
	Newnode->next = NULL;
	return Newnode;
}
//尾插
void SLPushBack(SLNode** pphead, SLDataType x)
{
	assert(pphead);
	SLNode* newnode = SLBuyNode(x);
	//空链表和非空链表
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLNode* ptail = *pphead;
		while (ptail->next != NULL)
		{
			ptail = ptail->next;
		}
		ptail->next = newnode;
	}
}
//头插
void SLPushFront(SLNode** pphead, SLDataType x)
{
	assert(pphead);
	SLNode* newnode = SLBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;//直接把第一个节点的位置给newnode,成为新的头结点
}

//尾删
void SLDelBack(SLNode** pphead)
{
	assert(pphead && *pphead);
	//一个节点的情况
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLNode* prev = *pphead;//保留的前一个节点
		SLNode* ptail = *pphead;
		while (ptail->next != NULL)//跳出循环就表示找到前一个节点了
		{
			prev = ptail;//保留了结束前的前一个地址
			ptail = ptail->next;
		}
		free(ptail);
		ptail = NULL;
		prev->next = NULL;//指向的下一个节点为NU
	}
}

//头删
void SLDelFront(SLNode** pphead)
{
	assert(pphead && *pphead);
	SLNode* pcur = (*pphead)->next;
	free(*pphead);
	*pphead = pcur;
}

//查找
SLNode* SLFind(SLNode* phead, SLDataType x)
{
	assert(phead);
	SLNode* pcur = phead;
	while (pcur)
	{
		if (pcur->val == x)
			return pcur;
		pcur = pcur->next;
	}
	return NULL;
}

//pos位置之前插入
void SLInset(SLNode** pphead, SLNode* pos, SLDataType x)
{
	assert(pphead && *pphead);
	assert(pos);
	SLNode* newnode = SLBuyNode(x);
	if (*pphead == pos)//因为下面一种会找不到
	{
		SLPushFront(pphead, x);//这里要传二级指针因为,要对链表进行改变
	}
	else
	{
		SLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//perv pos
		newnode->next = pos;
		prev->next = newnode;
	}

}
//pos之后插入
void SLInsetAfter(SLNode* pos, SLDataType x)
{
	assert(pos);
	SLNode* newnode = SLBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

//pos位置处删除
void SLErase(SLNode** pphead, SLNode* pos)
{
	assert(pos);
	if (*pphead == pos)//调用头删
	{
		SLDelFront(pphead);
	}
	else
	{
		SLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

//pos之后删除
void SLEraseAfter(SLNode* pos)
{
	assert(pos && pos->next);//pos后面的一个节点也不能为NULL
	SLNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

//销毁链表
void SLDesTroy(SLNode** pphead)
{
	SLNode* pcur = *pphead;
	while (pcur)
	{
		SLNode* del = pcur->next;//在销毁之前保存下一个节点的地址
		free(pcur);
		pcur = del;
	}
	pcur = NULL;
}

4.3测试部分

#include "SList.h"
SListTest1()
{
	SLNode* node = NULL;//新创建的节点要初始化
	//测试尾插
	//SLPushBack(&node, 2);
	//SLPushBack(&node, 3);
	//SLPushBack(&node, 4);
	//SLPrint(node);
	//测试头插
	SLPushFront(&node, 4);
	SLPushFront(&node, 3);
	SLPushFront(&node, 2);
	SLPushFront(&node, 1);
	SLPrint(node);

	测试尾删
	//SLDelBack(&node);
	//SLDelBack(&node);
	//SLDelBack(&node);
	//SLDelBack(&node);
	//SLPrint(node);

	测试头删
	//SLDelFront(&node);
	//SLDelFront(&node);
	//SLDelFront(&node);
	//SLDelFront(&node);
	//SLDelFront(&node);
	//SLPrint(node);

	//测试查找
	SLNode* find = SLFind(node,3);
	if (find)//非0值,NULL其实原本意思也是0
		printf("找到了\n");
	else
		printf("没找到\n");

	//pos前插入
	//SLInset(&node, find, 11);
	//SLPrint(node);
	//pos后插入
	//SLInsetAfter(find, 22);
	//SLPrint(node);

	//删除pos位置
	//SLErase(&node, find);
	//SLPrint(node);

	//删除pos位置之后的
	SLEraseAfter(find);
	SLPrint(node);

	SLDesTroy(&node);
}
int main()
{
	SListTest1();
	return 0;
}

总结:

  1. 做代码题的时候,把可能发生情况按照步骤写清楚,分为几点等等
  2. 最好把每个情况里的推导步骤也要写清楚

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/541495.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯 — — 数数

数数 友情链接&#xff1a;数数 题目&#xff1a; 思路&#xff1a; 这道题目主要用到了埃氏筛法&#xff08;Sieve of Eratosthenes&#xff09;来快速求解质数的方法&#xff0c;思路很巧妙&#xff0c;并且用到了动态规划的思想。 我们首先定义两个数组mk和p&#xff0c…

LPA3399Pro搭建Qt开发环境

将以前的开发文档在此做一个记录。 一、介绍 Qt是一个跨平台的应用程序开发框架&#xff0c;支持多种操作系统和硬件架构&#xff0c;包括ARM架构的Linux。 RK3399Pro是一款基于ARM架构的处理器&#xff0c;用于嵌入式系统。可以在RK3399上搭建Qt开发环境&#xff0c;进行项目…

C语言学习笔记之结构体(一)

目录 什么是结构体&#xff1f; 结构体的声明 结构体变量的定义和初始化 结构体成员的访问 结构体传参 什么是结构体&#xff1f; 在现实生活中的很多事物无法用单一类型的变量就能描述清楚&#xff0c;如&#xff1a;描述一个学生&#xff0c;需要姓名&#xff0c;年龄&a…

演示:单包攻击,扫描类攻击,畸形报文攻击[Land攻击,泪滴攻击,ip地址欺骗]。配置防火墙进行防御

浏览上篇博客进行环境搭建 单包攻击 单包攻击&#xff08;Single Packet Attack&#xff09;是一种利用网络协议或应用程序中的漏洞进行的攻击方式。这种攻击通常只需要发送一个精心构造的数据包&#xff0c;就能够触发目标系统的漏洞&#xff0c;导致攻击者能够执行非授权的…

JVM修炼之路【12】- GC调优 、性能调优

上一篇中 我们详细讲了内存溢出 内存泄漏 还有相关的案例。 这篇博客中我们主要了解一下GC调优。 有些新手可能会有一点 疑问—— 这两者不是一回事吗&#xff1f;&#xff1f; 其实说一回事 也没错 因为GC调优本质上还是针对 堆上的内存 只不过前面我们关注的侧重点在于 不合…

关于机器学习中贝叶斯学习(Bayesian Learning)计算公式的理解

一、引言 在《统计学习的分类概述》中介绍了贝叶斯学习的概念和计算公式&#xff0c;可以看到这个公式就是概率统计理论中的贝叶斯公式&#xff0c;但在机器学习中这个公式与概率统计中的理解要复杂得多。 二、贝叶斯学习公式及各组成因子的含义 要理解贝叶斯学习公式&#…

【Spring Security】1.Spring Security介绍 功能介绍

文章目录 一、Spring Security介绍二、功能介绍 一、Spring Security介绍 官方文档&#xff1a;https://docs.spring.io/spring-security/reference/index.html 官网解释&#xff1a;Spring Security 是一个提供 身份验证、授权 和 针对常见攻击的保护 的框架。 它为 保护命令…

运放噪声评估的来龙去脉

运放噪声评估的来龙去脉 友情提示&#xff0c;运放电路的噪声分析还是比较复杂的&#xff0c;不论是基础理论还是对应的推导过程&#xff0c;都不是特别容易。考虑到兄弟们的基础参差不齐&#xff0c;所以我还是尽量说清楚点&#xff0c;这样导致看起来就有点罗里吧嗦&#xff…

刷题之动态规划-回文串

前言 大家好&#xff0c;我是jiantaoyab&#xff0c;开始刷动态规划的回文串类型相关的题目 动态规划5个步骤 状态表示 &#xff1a;dp数组中每一个下标对应值的含义是什么>dp[i]表示什么状态转移方程&#xff1a; dp[i] 等于什么1 和 2 是动态规划的核心步骤&#xff0c;…

市场复盘总结 20240412

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 二进三&#xff1a; 进级率 50% 最常用的二…

iOS开发之为什么需要引用计数

iOS开发之为什么需要引用计数 在iOS开发中&#xff0c;Objective-C与Swift语言都是通过引用计数进行内存管理&#xff0c;实际上Python、Ruby、C等语言也提供了基于引用计数的内存管理方式&#xff0c;它们有一个共同点&#xff0c;那就是都是面向对象的编程语言。 引用计数可…

响应式布局(其次)

响应式布局 一.响应式开发二.bootstrap前端开发框架1.原理2.优点3.版本问题4.使用&#xff08;1&#xff09;创建文件夹结构&#xff08;2&#xff09;创建html骨架结构&#xff08;3&#xff09;引入相关样式&#xff08;4&#xff09;书写内容 5.布局容器&#xff08;已经划分…

Cascader 级联选择器 - 选择器最后一级数据为空

原因&#xff1a;将扁平数据转化为树形数据时&#xff0c;给每个项都添加了 children export const transList2Tree (list, rootPid) > {const result []list.forEach(item > {if (item.pid rootPid) {const children transList2Tree(list, item.id)item.children …

c语言多功能计算软件170

定制魏&#xff1a;QTWZPW&#xff0c;获取更多源码等 目录 题目 要求 主要代码片段 题目 设计一个计算器软件&#xff0c;具备如下功能提示界面。 要求 设计出界面&#xff0c;注意界面名称最后为自己的姓名&#xff1b;&#xff08;20分&#xff09;能够实现加、减、乘、…

【Godot4.2】CanvasItem绘图函数全解析 - 3.绘制纹理

概述 前两节我们讲述了常见几何图形绘制以及对几何图形应用变换的基础知识。 本节我们来讲如何在CanvasItem中绘制纹理。 系列目录 0.概述1.绘制简单图形2.设定绘图变换3.绘制纹理4.绘制样式盒5.绘制字符和字符串6.TextLine和TextParagraph详解7.自定义节点TextBoard8.绘制点…

C语言 函数——函数封装与程序的健壮性

目录 函数封装&#xff08;Encapsulation&#xff09; 如何增强程序的健壮性&#xff1f; 如何保证不会传入负数实参&#xff1f; 函数设计的基本原则 函数封装&#xff08;Encapsulation&#xff09; 外界对函数的影响——仅限于入口参数 函数对外界的影响——仅限于一个…

MySQL前缀索引(3/16)

前缀索引 前缀索引&#xff1a;MySQL支持前缀索引&#xff0c;允许定义字符串的一部分作为索引。如果不指定前缀长度&#xff0c;索引将包含整个字符串。前缀索引可以节省空间&#xff0c;但可能会增加查询时的记录扫描次数&#xff08;因为会查询到多个前缀相同的数据&#x…

MySQL数据库的增删改查(进阶)

1.新增 将一个表中的内容插入到另一个表中. 这里需要确保查询集合的列数,类型,顺序要和插入表的列数,类型,顺序一致,这里列的名称可以不一样. values 替换成了select 查询的临时表. 2. 查询 2.1 聚合查询 2.1.1 聚合查询 函数 说明COUNT([DISTINCT] expr)返回…

python爬虫--------Beautiful Soup 案列(二十一天)

&#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; &#x1f388;&#x1f388;所属专栏&#xff1a;python爬虫学习&#x1f388;&#x1f388; ✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天…

同城O2O系统搭建实战:外卖跑腿APP发全攻略

在同城服务领域&#xff0c;外卖和跑腿服务成为了人们生活中不可或缺的一部分。接下来小编将带领读者进入同城O2O系统搭建的实战领域&#xff0c;详细介绍如何打造一款外卖跑腿APP。 第一步&#xff1a;需求分析 这包括对目标用户群体的调研&#xff0c;明确用户的需求和痛点。…