自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+

Taopper的博客

技术交流,技术分享

  • 博客(67)
  • 收藏
  • 关注

原创 C++ :多重继承

在现实生活中,一些新事物往往会拥有两个或者两个以上事物的属性,为了解决这个问题,C++引入了多重继承的概念,C++允许为一个派生类指定多个基类,这样的继承结构被称做多重继承。(派生类有两个或两个以上的直接基类)当一个派生类要使用多重继承的时候,必须在派生类名和冒号之后列出所有基类的类名,并用逗好分隔。存在多继承的类层次图是一个图(graph)。只有单继承的类层次图是一个树(tree)。

2022-09-21 12:08:42 1950 1

原创 Linux:信号处理原理与实现

程序错误:除零,非法内存访问等。外部信号:终端 Ctrl-C 产生 SGINT 信号,定时器到期产生SIGALRM等。显式请求:kill函数允许进程发送任何信号给其他进程或进程组。目前 Linux 支持64种信号。信号分为非实时信号(不可靠信号)和实时信号(可靠信号)两种类型,对应于 Linux 的信号值为 1-31 和 34-64。信号是异步的,一个进程不必通过任何操作来等待信号的到达。事实上,进程也不知道信号到底什么时候到达。

2022-09-19 14:22:52 565

原创 算法:哈夫曼编码

通常的编码方式有固定长度编码和不定长度编码两种。哈夫曼编码是不定长度编码的一种,它利用字符的使用频率来编码,经常使用的字符编码较短,不常使用的字符编码较长。我们可以把每一个字符作为叶子,它们对应的频率作为其权值,为了方便,可以对其同时扩大100倍,得到a~f分别对应5, 32, 18, 7, 25, 13。(1) 初始化:构建单结点树集合T = {a, b, c, d, e, f},如图1所示。(3) 哈夫曼树构造成功后,约定左分支编码为0,右分支编码为1,如图7所示。(2) 选择,具体过程见图2~图6。

2022-09-15 19:52:18 3251 1

原创 Java:BIO、NIO、AIO

同步指的是必须等待IO缓冲区内的数据就绪,而非阻塞指的是,用户线程不原地等待IO缓冲区,可以先做一些其他操作,但是要定时轮询检查IO缓冲区数据是否就绪。普通的NIO是线程轮询查看一个IO缓冲区是否就绪,而Java中的new IO指的是线程轮询地去查看一堆IO缓冲区中哪些就绪,这是一种IO多路复用的思想。不需要新开一个线程。AIO可以做到真正的异步的操作,但实现起来比较复杂,支持纯异步IO的操作系统非常少,目前也就windows是IOCP技术实现了,而在Linux上,底层还是是使用的epoll实现的。

2022-09-15 16:45:12 202

原创 操作系统:生产者-消费者问题

有一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费.为使生产者进程与消费者进程能并发执行,在两者之间设置了一个具有n个缓冲区的缓冲池,生产者进程将它所生产的产品放入一个缓冲区中;由于缓冲区是由生产者和消费者共享的存储空间,应当互斥访问,即:生产者修改缓冲区时,禁止消费者从缓冲区中取走数据;为此,可以利用互斥信号量mutex实现进程对缓冲区的互斥访问。最后,在每个程序中的多个p操作不能颠倒,应该先执行对资源信号量的p操作,然后再执行对互斥信号量的v操作,否则可能引起进程死锁。

2022-09-15 12:32:11 522

原创 操作系统:同一进程中线程共享和独占的资源

由于线程间是并发运行的,每个线程有自己不同的运行线索,当从一个线程切换到另一个线程上时,必须将原有的线程的寄存器集合的状态保存,以便将来该线程在被重新切换到时能得以恢复。线程函数可以调用函数,而被调用函数中又是可以层层嵌套的,所以线程必须拥有自己的函数堆栈,使得函数调用可以正常执行,不受其他线程的影响。堆栈是保证线程独立运行所必须的(在一个进程的线程共享堆区,而进程中的线程各自维持自己堆栈)。2. 进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)一.同一进程中的线程共享的资源。

2022-09-14 14:27:38 1278

原创 操作系统:虚拟地址翻译为物理地址的过程

我们可以根据物理地址的组号定位到映射表的一个组,然后看看这个组下的有效位是不是为1,如果不为1,那么表示这一组的内容都无效了,没有必要比较下去了,因此缓冲没有命中,如果为1呢,那么比较这个组下的标记位和物理地址中标记位,如果不相等,那就是没有命中,如果相等呢,则继续根据物理地址中的块号去这个组相应的块号下找,如果找到数据,则表示命中了,否则缓冲没有命中。由上图得知,组号(CI)占用4位,因为我们假设高速缓冲有16个组,每个组下有4个块,因此块号(CO)占用2位,剩下的6位就是标记位(CT)。

2022-09-14 12:18:29 1728

原创 操作系统:如何理解虚拟内存

关于虚拟内存内部的结构可以参考进程虚拟地址空间的区域划分每个进程创建加载的时候,会被分配一个大小为4G的连续的虚拟地址空间,虚拟的意思就是,其实这个地址空间时不存在的,仅仅是每个进程“认为”自己拥有4G的内存,而实际上,它用了多少空间,操作系统就在磁盘上划出多少空间给它,等到进程真正运行的时候,需要某些数据并且数据不在物理内存中,才会触发缺页异常,进行数据拷贝。

2022-09-14 11:44:36 216

原创 操作系统:epoll高效运行的原理

笔者准备介绍完epoll和NIO等知识点,然后写一篇Java网络IO模型的介绍,这样可以使Java网络IO的知识体系更加地完整和严谨。初学者也可以等看完IO模型介绍的博客之后,再回头看这些博客,会更加有收获。如果你顺利啃下这篇博客,恭喜你,nginx、redis和NIO等核心思想已经被你掌握了,可以顺势去拓展自己的理解。否则,只是孤立的看epoll,时间一长会很快忘记的。当然,这些核心思想,笔者也会在之后的博客慢慢做详细讲解,欢迎关注。

2022-09-14 10:23:12 211

原创 gcc:-pthread和-lpthread的区别

可见编译选项中指定 -pthread 会附加一个宏定义 -D_REENTRANT,该宏会导致 libc 头文件选择那些thread-safe的实现;由于 libc 用于适应 thread-safe 的宏定义可能变化,因此在编译和链接时都使用 -pthread 选项而不是传统的 -lpthread 能够保持向后兼容,并提高命令行的一致性。最近在使用linux mint15,里面自带的gcc时4.7的,当我编译多线程程序时,使用-lpthread居然说没有找到线程库函数!

2022-09-08 15:06:42 1332

原创 http:请求中get和post方法的区别

一般我们在浏览器输入一个网址访问网站都是GET请求;再FORM表单中,可以通过设置Method指定提交方式为GET或者POST提交方式,默认为GET提交方式。HTTP定义了与服务器交互的不同方法,其中最基本的四种:GET,POST,PUT,DELETE,HEAD,其中GET和HEAD被称为安全方法,因为使用GET和HEAD的HTTP请求不会产生什么动作。不会产生动作意味着GET和HEAD的HTTP请求不会在服务器上产生任何结果。但是安全方法并不是什么动作都不产生,这里的安全方法仅仅指不会修改信息。

2022-09-05 17:27:04 3218

原创 C++:mutable 关键字

类中的mutablemutable从字面意思上来说,是「可变的」之意。若是要「顾名思义」,那么这个关键词的含义就有些意思了。显然,「可变的」只能用来形容变量,而不可能是「函数」或者「类」本身。然而,既然是「变量」,那么它本来就是可变的,也没有必要使用mutable来修饰。那么,mutable就只能用来形容某种不变的东西了。C++ 中,不可变的变量,称之为常量,使用const来修饰。然而,若是const mutable联用,未免让人摸不着头脑——到底是可变还是不可变呢?事实上,

2022-08-24 09:20:41 178 1

原创 C++:const用于函数重载

但是char *a和char * const a,这两个都是指向字符串变量,不同的是char *a是指针变量 而char *const a是指针常量,这就和int i和const int i的关系一样了,所以也会提示重定义。最后说一下,对于引用,比如int &i 和const int & i 也是可以重载的,原因是第一个i引用的是一个变量,而第二个i引用的是一个常量,两者是不一样的,类似于上面的指向变量的指针的指向常量的指针。(1)const是函数类型的一部分,在实现部分也要带该关键字。

2022-08-24 08:52:40 1000

原创 C++:实现委托机制

为了解决GUI 跨线程操作界面的导致主线程崩溃的情况, C++ 11 终于推出了自己的thread 类thread 类提供detach() 的方法, 让worker线程也具备了操作GUI的能力.在GUI开发中, 通过使用function和thread等C++ 11 新特性,能够更便捷的开发出高质量的程序, 同时回避开发过程中的问题。...

2022-08-17 17:46:03 709

原创 操作系统:临界资源与临界区的区别

进程进入临界区的调度原则是:①如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入。如已有进程进入自己的临界区,则其它所有试图进入临界区的进程必须等待。每个进程中访问临界资源的那段程序称为临界区(CriticalSection)(临界资源是一次仅允许一个进程使用的共享资源)。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。答:每个进程中访问临界资源的那段程序称为临界区(临界资源是一次仅允许一个进程使用的共享资源)。...

2022-08-15 20:34:13 948

转载 MySQL:如何实现事务提交和回滚

事务是由数据库中一系列的访问和更新组成的逻辑执行单元事务的逻辑单元中可以是一条SQL语句,也可以是一段SQL逻辑,这段逻辑要么全部执行成功,要么全部执行失败举个最常见的例子,你早上出去买早餐,支付宝扫码付款给早餐老板,这就是一个简单的转账过程,会包含两步从你的支付宝账户扣款10元早餐老板的账户增加10元这两步其中任何一部出现问题,都会导致整个账务出现问题假如你的支付宝账户扣款10元失败,早餐老板的账户增加成功,那你就Happy了,相当于马云请你吃早餐了,O(∩_∩)O哈哈~...

2022-08-11 11:51:40 5092

原创 算法:抢红包算法

随机法,每次抢红包时计算出本次能够获得的最小金额和最大金额,然后在这个区域间中取一个随机值并计算得出这次抢到的红包金额,这种方法,优点是实现简单,但是,先抢的人会很赚,抢到大红包的概率很高,越到后面的人越吃亏。第一个人抢的金额是 (0,20),抢到的数值,根据正态分布,应该是10左右,远低于10的概率很小,同样远大于10的概率和很小,这里假设第一个人抢到的数值是10;假设总金额是M元,N个人,每次抢的金额=(0, (M/N) *2),比如,还是之前说的条件,金额100,人数10,...

2022-08-08 16:23:24 319

原创 计算机网络:以太网中的MTU与MSS

当两台远程PC互联的时候,它们的数据需要穿过很多的路由器和各种各样的网络媒介才能到达对端,网络中不同媒介的MTU各不相同,就好比一长段的水管,由不同粗细的水管组成(MTU不同 :))通过这段水管最大水量就要由中间最细的水管决定。为了达到最佳的传输效能,TCP协议在建立连接的时候通常要协商双方的MSS值,这个值TCP协议在实现的时候往往用MTU值代替(需要减去IP数据包包头的大小20Bytes和TCP数据段的包头20Bytes)所以。以太网帧的帧头的14字节和帧尾CRC校验4字节共占了18字节,剩下的。...

2022-08-08 15:44:38 1951

原创 Linux 扩容 / 根分区(LVM+非LVM)

目录:1,概述2,CentOS7,LVM根分区扩容步骤3,CentOS7,非LVM根分区扩容步骤:一、背景,概述对于传统的MBR分区方式,有很多的限制:1:最多4个主分区(3个主分区+1个扩展分区(扩展分区里面可以放多个逻辑分区)),无法创建大于2TB的分区,使用fdisk分区工具,而GPT分区方式不受这样的限制。2:GPT分区方式将不会有这种限制,使用的工具是parted;LVM管理导图1LVM管理导图2上图所示: 如果直接扩展/home逻辑卷目录,会提示逻辑卷组没有空间。LVM扩容思维流程:创建一个物理

2022-08-01 14:57:29 4599 1

原创 技术:什么是docker

作为程序员我们应怎样理解docker?容器技术的起源假设你们公司正在秘密研发下一个“今日头条”APP,我们姑且称为明日头条,程序员自己从头到尾搭建了一套环境开始写代码,写完代码后程序员要把代码交给测试同学测试,这时测试同学开始从头到尾搭建这套环境,测试过程中出现问题程序员也不用担心,大可以一脸无辜的撒娇,“明明在人家的环境上可以运行的”。测试同学测完后终于可以上线了,这时运维同学又要重新从头到尾搭建这套环境,费了九牛二虎之力搭建好环境开始上线,糟糕,上线系统就崩溃了,这时心理素质好的程序员又可以施展演技了,

2022-06-21 16:52:39 95

原创 Unity:版本下载列表

Unity历史版本下载列表Unity2019系列最新版本:Unity 2019.1.3Unity2018系列最新版本:Unity 2018.4.0Unity2017系列最新版本:Unity 2017.4.27Unity5.x系列最新版本:Unity 5.6.7Unity4.x系列最新版本:Unity 4.7.2Unity3.x系列最新版本:Unity 3.5.7注:最后更新2019.05.22[UnityHub]For Mac:https://public-cdn.cloud.unity3d.c

2022-06-14 11:07:42 5573

原创 Lua:只读表的实现

__index:当你通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的__index 键。如果__index包含一个表格,Lua会在表格中查找相应的键。__newindex:当你给表的一个缺少的索引赋值,解释器就会查找__newindex 元方法:如果存在则调用这个函数而不进行赋值操作。__newindex 元方法对表更新操作:创建并返回空表 proxy, 重写 __newindex 使其一直保持空表形式。使得赋值操作

2022-06-13 19:31:27 474

原创 网络:127.0.0.1和0.0.0.0地址的区别

IP地址由两个部分组成,net-id和host-id,即网络号和主机号。net-id:表示ip地址所在的网络号。host-id:表示ip地址所在网络中的某个主机号码。即:IP地址一共分为5类,即A~E,它们分类的依据是其net-id所占的字节长度以及网络号前几位。其中,ABC三类地址为单播地址(unicast),用于一对一通信,是最常用的。特殊IP地址就是用来做一些特殊的事情。RFC1700中定义了以下特殊IP地址。4、问题解答接下来我们来看之前问过的问题:127.0.0.1和0.0.0.0地址

2022-06-10 17:13:19 623 1

原创 UE:网络架构

网络同步,就是使各个客户端上的角色表现保持一致,属于游戏引擎的高级功能,所以一般我们都将其归于Gameplay模块当中。不过具体的实现方案其实会深刻影响到底层的网络架构(甚至是整个游戏架构),我们既要决定通过哪种网络协议来完成,又要决定游戏各个模块的循环执行顺序,其实已经不单单是“Gameplay”层面的东西了。虚幻引擎属于标准的CS架构(经过无数次改版的),内置状态同步功能,其同步频率与游戏的帧率相同,属于变长步更新。由于帧率完全受CPU、GPU性能的影响,所以网络同步的频率与整个项目的性能息息相关。..

2022-06-08 11:58:32 752

原创 游戏开发:游戏内小红点的实现方案

游戏内小红点算是一个极其常用的功能了,之前在德州里面也有过实现。然而之前的实现实在是乱七八糟,所以这次也是将其做了彻底的重写,并把方案跟大家分享一下。我们将游戏内小红点可以分为三类: 服务器小红点-服务器自动清除 比如,我们常见的每日任务,成长任务,活动等。 以每日任务举例: 当有任务奖励可以领取时,在每日任务按钮上就显示小红点。 当任务奖励全部领取完毕后,小红点消失。 服务器小红点-客户端告知服务器清除 比如,信箱功能,新好友通知,好友申请通知。 以信箱举例: 有新

2022-05-23 17:51:09 977

原创 Lua:5.1.3 GC过程

一、概述lua实现了对象间的引用管理,在对象不被其他对象应用时,自动释放对象的内存。二、lua对象类型和数据结构1、哪些类型需要GClua的基本类型包括nil、boolean、number、lightuserdata、string、table、function、userdata、thread九种数据类型,另外还有proto、upvalue两种内部类型。其中string、table、function、userdata、thread、proto、upvalue是引用类型,可以被其他多个对象同时引用,

2022-04-25 11:40:10 605

原创 算法:各种排序算法的稳定性

排序算法稳定性  如果在一个待排序的序列中,存在2个相等的数,在排序后这2个数的相对位置保持不变,那么该排序算法是稳定的;否则是不稳定的。举个例子  对五个同学(A,B,C,D,E)进行成绩排序,他们的成绩分别为:90,88,79,88,92,按成绩从高到低排(92,90,88,88,79):E,A,B,D,C——稳定的(B,D的相对位置没有变化)E,A,D,B,C——不稳定的(B,D的相对位置发生了变化)排序算法稳定的意义排序算法如果是稳定的,从一个键上排序,然后在从另一个键.

2022-04-24 22:24:14 277

原创 算法:各种排序算法总结

一、时间复杂度O(n²)的排序算法由于时间复杂度太高,下述三个排序方法提交LeetCode均会超时。冒泡排序,空间复杂度O(1)冒泡排序是最基本的排序算法,排序思路如下:1.一边比较一边向后两两交换,将最大值冒泡到最后一位。2.循环该过程n-1次(n为数组长度),数组此时为升序排列。class Solution {public: vector<int> sortArray(vector<int>& nums) { ...

2022-04-24 21:11:22 97

原创 网络:常考面试点总结

网络分层结构计算机网络体系大致分为三种,OSI七层模型、TCP/IP四层模型和五层模型。一般面试的时候考察比较多的是五层模型。TCP/IP五层模型:应用层、传输层、网络层、数据链路层、物理层。应用层:为应用程序提供交互服务。在互联网中的应用层协议很多,如域名系统DNS、HTTP协议、SMTP协议等。 传输层:负责向两台主机进程之间的通信提供数据传输服务。传输层的协议主要有传输控制协议TCP和用户数据协议UDP。 网络层:选择合适的路由和交换结点,确保数据及时传送。主要包括IP协议。 数

2022-04-24 11:29:58 416

原创 Redis:面试常考点总结

Redis是什么?Redis(Remote Dictionary Server)是一个使用 C 语言编写的,高性能非关系型的键值对数据库。与传统数据库不同的是,Redis 的数据是存在内存中的,所以读写速度非常快,被广泛应用于缓存方向。Redis可以将数据写入磁盘中,保证了数据的安全不丢失,而且Redis的操作是原子性的。Redis优缺点?优点:基于内存操作,内存读写速度快。 Redis是单线程的,避免线程切换开销及多线程的竞争问题。单线程是指网络请求使用一个线程来处理,即一个线程处理所有

2022-04-23 10:12:13 474

原创 Mysql:面试常考点总结

事务的四大特性?事务特性ACID:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。 一致性是指一个事务执行之前和执行之后都必须处于一致性状态。比如a与b账户共有1000块,两人之间转账之后无论成功还是失败,它们的账户总和还是1000。 隔离性。跟隔离级别相关,如read committed,一个事务只能读到已经提交的修改。 持久性是指一个事务一旦被提交了,那

2022-04-23 10:09:59 260

原创 Skynet:logger服务

涉及的结构体为:struct logger { FILE * handle; //用于存储log的输出,默认是控制台,否则为文档 char * filename; //如果是输出文件则保存文件名 int close; //标记是否需要关闭文档};logger服务主要用于log输出,在前面的1.3节中有介绍到,在函数skynet_start中会创建一个logger服务,在1.4节也介绍了服务的创建过程skynet_context_new

2022-04-21 19:55:23 395

原创 Skynet:套接字线程工作原理

涉及的到的相应结构体以及预定义注释为://用于标记socket结构体的状态#define SOCKET_TYPE_INVALID 0 //socket结构体未被使用#define SOCKET_TYPE_RESERVE 1 //socket结构体已被分配,但是还没有实际进行网络连接#define SOCKET_TYPE_PLISTEN 2 //已经绑定套接字监听端口号,但是没有添加到epoll监听事件,调用st

2022-04-21 19:53:44 2167

原创 Skynet:定时器原理

定时器涉及到的结构体为:struct timer_event { //记录每个节点回复消息的信息,存储在节点的后面 uint32_t handle; //记录定位服务的编号 int session; //记录用于接收消息响应时,定位到是响应哪一条消息,由发送消息的服务生成};struct timer_node { //节点 struct timer_node *next; //指向下一个节点

2022-04-21 19:51:12 883

原创 Skynet:模块

在skynet_start.c文件中的skynet_start函数调用了skynet_module.c文件中的skynet_module_init函数对需要加载的动态库进行了相应的初始化,skynet_module_init函数的参数默认为动态库的路径"./cservice/?.so",动态库的信息保存在全局变量M中://初始化需要加载的动态库的路径void skynet_module_init(const char *path) { struct modules *m = skynet_mal

2022-04-21 11:39:56 118

原创 Skynet:消息处理

在工作线程中调用skynet_server.c文件中的skynet_context_message_dispatch函数进行消息分发,该函数的原理为:如果传入的第二个参数服务队列为NULL则从全局队列中取服务队列信息,通过服务队列信息获得定位服务的编号和服务信息,默认线程只处理服务队列中的一条消息,通过每个工作线程的weight可以改变每次处理的消息的数量,从服务队列中取出消息如果该服务有回调函数则调用回调函数进行处理(在dispatch_message函数中),如果服务队列中的消息都处理完了,则该服务队列

2022-04-21 11:36:48 241

原创 Skynet:消息队列

在Skynet中将消息队列分为:全局队列和服务队列,每个服务都有一个相应的服务队列用于存放于该服务相关的消息,而每个服务队列都会被全局队列所引用,每个工作线程通过从全局队列中取出相应的服务队列进行消息处理。相应的结构体为://每个服务队列的结构体struct message_queue { struct spinlock lock; //锁 uint32_t handle; //服务handle,用于定位服务,高8位为节点的编号

2022-04-21 09:55:02 361

原创 Skynet:服务管理

每个服务通过一个32位的无符号整数进行标识,整数的高8位用于定位属于哪个节点,低24位用于定位属于哪个服务。本地节点号通过HARBOR进行存储位于文件skynet_harbor.c中,全局的所有服务信息通过H进行存储位于文件skynet_handle.c中。其中涉及到的结构有:struct handle_name { char * name; //服务名字 uint32_t handle; //用于定位服务,高8位为节点编号,低24位为服务号};//全局服务信息结

2022-04-21 09:53:37 199

原创 Skynet:目录结构

README.md 简单介绍了怎么编译和测试Skynet。LICENSE 许可证信息,采用MIT,很宽松的协议。Makefile 编译规则文件,用于编译Skynetplatform.mk 编译与平台相关的设置HISTORY.md 各个版本的修改信息3rd 第三方库,例如lua和jemalloc等examples 附带的例子lualib 使用lua写的库lualib-src 使用C写并封装给lua使用的库service 使用lua写的Skynet的服务模块service-src 使用C写的

2022-04-20 20:43:11 60

原创 Skynet:工作逻辑

​当从终端输入命令“./skynet examples/config”命令时,启动程序skynet,首先调用skynet_main.c文件中的main函数,examples/config将作为argv[1]参数传入。在main函数中主要的工作为:初始化全局信息,加载配置文件,调用skynet_start.c中的skynet_start函数。skynet_start函数的主要的工作为:根据加载的配置信息初始化全局信息,创建一个logger服务,调用bootstrap函数创建一个snlua服务,调用start

2022-04-20 20:41:21 144

空空如也

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除