自定义博客皮肤VIP专享

*博客头图:

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

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

博客底图:

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

栏目图:

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

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+
  • 博客(107)
  • 收藏
  • 关注

原创 C++ Primer笔记——tuple类型

定义一个tuple对象时,可以使用默认的构造函数,它会为每个成员进行默认初始化。当然也可以手动初始化。//使用默认构造进行初始化//自定义直接初始化//错误,并非直接初始化值得注意的是,《C++ Primer》说“tuple的构造函数是explicit,因此必须使用直接初始化”,因此tp3应该编译失败。但是在VS2019环境实测中,编译是正常通过的,应该是VS对tuple类型进行了优化,支持了隐式类型转换。类似make_pair函数,标准库定义了make_tuple函数。

2023-08-24 09:00:13 447 2

原创 C++ Primer笔记——查找算法

页数:P771(A.2.1查找对象的算法)算法头文件:first:序列的起始迭代器last:序列的结尾迭代器val:特定的一个值unaryPred:自定义,用于自定义查找方式binaryPred:自定义,用于自定义查找方式。

2023-05-23 10:18:36 665 2

原创 C++ Primer笔记——排列算法(next_permutation、prev_permutation、is_permutation)

页数:P778 (A.2.7 排列算法)头文件:函数名:next_permutation & prev_permutation & is_permutationC++为我们提供了专门用于排列的算法。这些算法可以自动将内容按照字典序进行排列。举个例子:现在有字符a、b、c,我们想要知道这三个字符有多少种组合方式,那么就可以用到排列算法。

2023-04-25 17:13:42 850 6

原创 Linux——TCP协议与相关套接字编程

这里需要注意的是因为缓冲区的存在,一份数据可能会拆开来发送,这样接收方一次收到的数据可能就是不完整的,因此需要自定义通信协议(检测数据是否已经完整)确保目标数据完整后再使用,具体方式在套接字编程代码中会说明。当然,TCP的服务端处除了需要建立监听外,在连接一个客户端后,需要创建一个子进程负责和这个客户端通信,父进程则继续循环等待新的客户端来连接。但有一点需要强调,write和send的第一个参数在服务端是服务套接字,因为每一个客户端(对端)的服务套接字各不相同,使用服务套接字可以标定唯一的客户端。

2023-03-21 17:20:17 563 1

原创 项目:手把手实现高并发内存池

高并发内存池(ConCurrentMemoryPool),其原型是google的开源项目tcmalloc。全称是thread-cache-malloc,即线程缓存malloc。应用场景是多线程环境下管理内存,相较于malloc库函数而言,tcmalloc效率更高,多线程环境下完全可以替代malloc。其中go语言直接用tcmalloc作为内存分配器。本项目实现了tcmalloc最核心的功能,可以理解为简化版tcmalloc。

2023-03-05 00:00:00 824 4

原创 Linux——UDP协议与相关套接字编程

套接字英文名为socket,直译是“插座”。插座是用于连接电源和电器,达到通电的效果。而套接字是让应用层与传输层通过IP和端口号port锁定其他网络中的进程来达到通信的目的。因此套接字由IP地址、端口号port以及通信协议方式(TCP/UDP)组成。我们知道,在网络通信中通过一个IP地址可以锁定一台主机,通过端口号可以锁定一台主机上某一个进程。因此IP地址+端口号可以锁定全网中唯一的进程,进而达到通信的目的。

2023-02-24 16:08:46 1515 8

原创 Linux——线程同步(条件变量、POSIX信号量)和线程池

thrmgr防止线程长期得不到调度的方式也很简单,当线程没有任务时,采用pthread_cond_timedwait函数阻塞等待,该函数第三个参数为设定的timespec时间类型结构体,当检测超时后自动停止阻塞并返回特定值,根据返回值就能判断线程是否是因为超时而停止阻塞,一旦判断超时,线程跳出等待任务的循环然后结束本线程。首先简单说一下什么是循环队列,本质就是长度固定的数组,从头开始插入资源,当插入资源位于最后一个位置的下一个时,再从头开始插入资源,也就是把线性的数组“头尾相连”,变成逻辑上的环形结构。

2023-02-15 00:00:00 896 7

原创 Linux——生产消费者模型(阻塞队列形式)

生产消费者模型是操作系统里非常经典模型,可应用于多线程并发协作,本质即通过一个容器(即缓冲区,本质是一种数据结构)来解决生产者与消费者的强耦合问题,也就是实现数据交互的低耦合。模型图示如下:在生产消费者模型中,生产者将产生的数据放入容器里,消费者从容器中获取数据来使用。而不是由生产者直接将数据“交给”消费者。相当于生产者把数据放入了一个“仓库”,消费者直接从“仓库”里拿,从而避免了生产者和消费者的直接接触。

2023-02-08 00:15:00 1454 11

原创 Linux——死锁概念介绍和解决方式

死锁指的是在多线程环境中,每个执行流(线程)都有未释放的资源,且互相请求对方未释放资源,从而导致陷入永久等待状态的情况。就好比和绑匪交赎金时,家属要求先放人再给钱,绑匪要求先交钱再给人,双方达不成一致意见,导致出现“僵死”的局面。程序层面而言,两个线程各加一个互斥锁mutex,在临界区中试图获取对方的互斥锁,陷入“互相请求对方的锁,但不释放自己锁”的尴尬局面,这就是死锁的一种场景。同一时间只能有一个执行流拥有资源执行流请求资源时,不能释放已有资源执行流已有资源不能被强制剥夺。

2023-02-03 00:00:00 1822 16

原创 Linux——什么是互斥与互斥锁

所谓互斥,其实就是在某一时刻只能有一个线程访问临界区,且完整的使用临界资源没有其他线程打扰,即原子性。简单来说就是当前线程使用完临界资源后其他线程才能来使用。互斥锁通俗来讲就是用来完成互斥行为的对象,锁住的范围一般就是临界区。需要互斥操作的多线程共享一个互斥锁,当一个线程获得互斥锁后,其他线程会阻塞等待,直到当前线程归还互斥锁后,其他线程争抢互斥锁,谁获得了谁能进入临界区执行代码。

2023-01-28 00:45:00 1184 5

原创 Linux——一文彻底了解进程id和线程id的关系(什么是pid、tgid、lwp、pthread_t)

​首先,我们要清楚,在系统层面只有两个编号:pid、tgid。不妨拿出task_struct结构体(进程PCB)看一看:pid_t pid; pid_t tgid; 我们知道,在linux中进程和线程都使用task_struct结构体实现。在task_struct中:pid即线程id。也就是task_struct的编号,每个task_struct各不相同。tgid叫线程组id。也就是一个进程的id。同一个进程里每一个线程的task_struct中tgid值都一样。当使用ps axj或ps -aL查看进程

2023-01-21 00:00:00 5413 7

原创 C++ Primer笔记——默认移动操作、移动迭代器、左右值引用成员函数、标准库仿函数、function包装器

标准库定义了一些类,重载了小括号,可以在泛型算法中用于替换函数类型参数,即仿函数。求和:plus示例(其他仿函数类似,不再演示)://将first开始5个元素与second的5个元素相加结果写入results数组中求差值:minus求积:multiplies求差:divides求余:modulus内部实现:求相反数:negate判断是否相等:equal_to //是:true,否:false,下同。

2023-01-19 00:15:00 1696 1

原创 Linux——线程概念及私有数据和优缺点

线程与进程共享虚拟地址空间、页表等内核数据结构、数据和代码。线程的数据结构理论上与进程的不同,但基于线程和进程的相似性,linux的线程数据结构依旧采用task_struct结构体,与进程的一致。但是其他的操作系统线程有专属的结构,与进程的不同。也就是说,linux没有真正意义上的线程结构,而是采用进程的task_struct结构体模拟的线程。即便线程与进程都采用task_struct结构体,但是内核数据结构依旧属于进程,线程只是与进程共享。

2023-01-17 02:30:00 959 5

原创 Linux——页表的分页机制

以32位机器为例,页框的个数为4GB / 4KB也就是2^20次方个,假设一个表项占有4Byte,总共有4MB的连续空间需要使用,也就是说,每次调度进程时,这4MB的空间都要被使用。每个页框4KB,共有4x1024个地址,即2^12次方,页内偏移即虚拟地址后12位,也是2^12次方,正好可以偏移出一个页框中全部地址。虚拟地址前20位是两级页表的索引,共能索引出2^20次方个页框,内存共4GB即2^20次方个页框,因此正好与之一一对应。linux将虚拟地址的32位分为前10位、中间10位、后置12位。

2023-01-15 00:30:00 1498 4

原创 Linux——信号知识归纳(下)

linux将进程的状态分为用户态(user mode)和内核态(kernel mode)。内核态时CPU执行代码不受任何限制,而用户态会做代码安全的相关检查,不能直接访问内核数据。一般而言,执行代码不会进入内核态,防止用户破坏内核数据和操作系统。但有时需要进入内核态,比如当执行系统调用接口时就是典型进程状态转换过程。以执行系统提供的open函数,其过程如下:1.进程执行到open的代码,CPU取得open函数地址。(用户态)2.跳转到open函数地址(在虚拟地址空间的内核区)。(用户态)

2023-01-13 00:00:00 936 4

原创 Linux——信号知识归纳(中)

硬件产生异常并使程序崩溃的方式有很多,比如除0错误、空指针、数组越界等。大致上,当硬件产生异常后,会“汇报”给操作系统,再由操作系统发送相关信号给进程,促使进程异常退出。以除0错误为例,当cpu检测到除0后,其中状态寄存器会标记异常,操作系统识别到该寄存器标记了异常后,发送信号给当前进程,进而进程崩溃退出。再比如空指针问题,当虚拟地址为空时,页表通过MMU(硬件)映射时会出错,MMU内部寄存器会标记异常,操作系统识别后发送信号给进程,进程崩溃退出。

2023-01-11 01:30:00 461 3

原创 Linux——信号知识归纳(上)

核心转储功能,应用于代码出错的调试,如果进程被信号异常终止且该信号有核心转储功能,那么就会生成一个数据文件到磁盘中,在gdb调试时可用于查看异常信息。信号是操作系统控制进程的一种方式,比如ctrl C、栈溢出程序崩溃、kill -9命令等底层都是操作系统发送信号给进程执行特定操作。同时,进程接收到信号后,不一定会立即处理,信号发送方也不一定会阻塞等待信号处理结果,即信号与进程是。其中,1 - 31号信号是普通信号,34 - 64号是实时信号。情况下,信号的处理方式就是不处理,接收后略过该信号执行过程。

2023-01-09 05:15:00 845 4

原创 跳表SkipList介绍与实现

跳表是一种随机化数据结构,主要用于快速检索数据。实质上是一种可以进行二分查找的有序链表。时间复杂度可以达到O(log^n)。在性能上与红黑树、AVL树相当。当然因为结构具有随机性,最坏情况下时间复杂度为O(n)。跳表结构如下图:与普通链表相比,跳表每个结点有不止一个指向后续的指针,具体数量是随机出来的。这些指针结构上从低到高排列,指向后面与自己同层的指针所在的结点。检索数据时,从head结点开始,按指针从高到低的所指元素大小进行比较,直到找到或走到结尾。

2023-01-07 00:00:00 977 2

原创 Qt——项目:翻转金币游戏

这个窗口需要使用菜单栏,因此可以用QMainWindow类。此外要设置背景图片,可以使用绘图事件paintEvent完成。窗口标题及图片直接在构造函数完成,使用setWindowTitlesetWindowIcon完成按钮要设置成点击后会向下弹并返回,可以专门定义一个按钮类,使用QPropertyAnimation特效类编写下降和上升的函数。关卡窗口完成后,使connect通过信号和槽完成点击按钮就关闭本窗口打开关卡窗口的操作。该按钮通过QLabel标签类完成关卡号的书写。但这还不够,如果此时connect

2023-01-05 01:30:00 1826 8

原创 B树的原理及代码实现、B+树和B*树介绍及应用

B树主要用于磁盘文件的检索操作。众所周知,平衡二叉树(AVL树、红黑树)搜索的时间复杂度是O(log^n)。虽然很快,但如果数据在磁盘中且有上亿量级的数据,即便只有30次左右的IO操作,速度也是非常慢的。因为磁盘IO速度极慢,主要是寻道操作影响,平均8ms左右。因此,磁盘数据的检索不适合使用平衡二叉树,B树正式上线。B树可以看成是压缩版的平衡二叉树,每一个节点上都有保存有多个值,且有多个叶子节点。一般而言,B树的检索次数在个位量级,这取决于每个节点上能保存多少个值。

2023-01-03 08:15:00 1828 6

原创 C++ Primer笔记——allocator、unique_ptr和release、智能指针与动态数组、阻止拷贝

声明但不定义一个函数是合法的,但是有一个例外,就是派生类对基类虚函数的覆盖,如果只有声明将在多态调用时发生链接错误,无法找到绑定的虚函数的定义。与new不同的是,它一般是提前分配了一大块未初始化的空间,使用时再根据所需空间大小进行初始化,其实就是将一块空间资源让使用者自行维护。,因此使用动态数组时只需要在对象类型后提供一对方括号,表明指针指向的是一个数组。值得注意的是,此时的空间并不能够直接使用,因为没有进行初始化操作。参数p是指针,指向释放资源起始位置,n是要释放的该类型资源的数量。

2023-01-01 09:30:00 1630 6

原创 C++ Primer笔记——lower/upper_bound、hash、make_shared、智能指针与new结合使用

当函数结束时,sp2会先析构,因为sp2是独立创建的(sp2并不知道空间来自智能指针sp),因此引用计数为1,会直接释放该空间。众所周知,shared_ptr构造函数是explicit的,因此,不要希望通过通过赋值的形式完成初始化操作,那样会有隐式类型转换发生,而shared_ptr会报错。当func函数结束时,sp会调用析构函数,因为只有sp一个智能指针指向p空间,因此p指针空间会被释放。,当回到main函数时,p已经指向一块被释放的空间,即悬空指针,如果继续使用p,其行为是未定义的。

2022-12-30 07:45:00 1203 7

原创 Qt——定时器QTimer

QTimer是qt中的单次和重复定时器。其主要有两种定时方式,一种是定时事件,一种是通过信号与槽。在需要定时器的类中使用startTimer(x)进行定时设置和启动。int id = startTimer(x);其中,x是定时事件,单位是毫秒ms,startTimer会自动启动该定时器。返回值是该定时器独一无二的id。同时需要使用定时事件timerEvent完成具体的定时任务。大多数情况下我们通过使用信号与槽connect的形式完成定时任务。首先创建一个QTimer类对象,调用start函数完成定时和启

2022-12-28 05:30:00 14155 5

原创 Qt——QMainWindow不同功能栏基础操作总结

注:addAction添加action类不报错,但是无效。4.选择浮动属性(是否必须停靠),默认浮动。1.创建并添加浮动窗口,需要给定初始化位置。3.选择移动属性(能否移动),默认移动。2.添加工具项,可添加控件。2.添加控件,默认在左侧。2.选择浮动方式,默认浮动。1.创建并添加工具栏,2.将菜单放入主窗口。5.选择后续停靠状态。1.创建并添加状态栏。

2022-12-26 07:30:00 1494 7

原创 Qt——信号与槽知识总结归纳

SIGNAL和SLOT是宏,编译时会将信号函数和槽函数用字符串替换,并且不会判断参数是否相互匹配,因此只要不是Qt版本问题,并不推荐使用这种方式。connect( 发送方地址,SIGNAL( 信号函数(参数) ),接收方地址,SLOT( 槽函数(参数) ) );信号函数与槽函数的参数类型必须匹配,且信号函数参数个数可以比槽函数多,但绝对不能少。简单来讲,就是通过发送一个函数(信号),接收方通过另一个函数(槽)完成任务。使用disconnect可以断开信号与槽的连接,参数与connect一致。

2022-12-24 10:00:00 886 7

原创 Qt——基本介绍、详解对象树

{w.show();}其中,含义分别如下:QApplicata:应用程序对象,必须有且只能有一个Qwidget:qt自带空窗口类,即运行时的窗口。myWindow:自定义窗口类,继承自QWidget。a.exec():进入消息循环,除非人为结束进程,否则阻塞在这里等待用户输入,死循环。qmake:qt的编译器。.pro文件:qt的项目文件。其内部代码含义如下:我们自定义的myWindow类中默认使用宏Q_OBJECT。其含义是允许使用信号与槽。

2022-12-22 07:15:00 2084 9

原创 C++ Primer笔记——lambda表达式与bind函数

lambda是C++11所规定的一种新方法。一般用于泛型算法传递自定义函数。[ 捕捉列表 ] ( 参数列表 ) ->返回值类型 { 函数体 }lambda表达式会返回一个函数对象。};f(a, b);等价于下列函数{}//按降序排序});使用lambda表达式时,1.捕捉列表可以为空2.尾置返回类型和参数列表可以忽略,即[]{};3.捕捉列表和函数体不能忽略。

2022-12-20 09:00:00 1094 4

原创 C++ Primer笔记——unqiue、插入迭代器、流迭代器、insert返回值、关联容器的删与查

unqiue函数会将容器相邻的元素的重复项“删除”。这里需要注意几点:1.只会将容器内相邻元素重复项删除,使用前请确保同样元素相邻如果内部这样存储:1,2,2,3,2,4,3,5那么得到的将是:1,2,3,2,4,3,5,52.参数范围是左闭右开,右边参数是最后一个参与unique的下一个参数。3.返回值是最后一个不重复元素的下一个位置。

2022-12-18 09:30:00 761 2

原创 C++ Primer笔记——explicit、string流、vector比较、emplace

举个例子,如果构造函数参数是string类型,那么当使用赋值符号进行初始化操作时,实际上是将string隐式转换成临时目标类,再将临时对象通过拷贝构造给真正对象。当不希望发生隐式类型转换时,需要使用explicit关键字。A a(s);//正确//正确A a = s;//错误1.只能用于构造函数2.只能用于单参数构造函数3.类外定义构造函数时不能重复书写。4.可以显式强制转换。

2022-12-16 13:45:00 1176 4

原创 C++ Primer笔记——constexpr、成员函数const、mutable、类的声明、委托构造

完成初始化应该指的是完成初始化列表,构造函数内的行为叫做赋值,因此改成直到构造函数执行完才真正获得const属性可能更好。这是因为同类成员需要初始化,但成员处在自己类的内部,类对象本身都还未完成初始化,内部同类成员又怎么可能完成初始化呢。如果成员函数是const修饰,那么其成员变量将不能修改,但是用mutable关键字修饰的成员变量依旧可以修改。因为静态成员需要在类外进行,定义类内只是声明,且只属于类本身,不属于任何类对象。constexpr修饰的函数,可以用于常量表达式,其返回值是一个右值。

2022-12-14 13:15:00 895 5

原创 Linux——详解共享内存shared memory

共享内存本质上就是内存中的一块区域,用于进程间通信使用。该内存空间由操作系统分配与管理。与文件系统类似的是,操作系统在管理共享内存时,不仅仅有内存数据块,同时还会创建相应结构体来记录该共享内存属性,以便于管理。因此,共享内存不只有一份,可以根据需求申请多个。进程之间进行通信的时候,会获取 到共享内存的地址,写端进程写入数据,读端进程通过直接访问内存完成数据读取。

2022-12-12 14:15:00 11764 4

原创 C++语法——详解智能指针的概念、实现原理、缺陷

最初的智能指针是C++98提出来的auto_ptr,不过因为使用时缺陷很大,Beman G.Dawes(C++委员会成员之一)所成立的boost社区(专门面向C++程序员,提供许多免费好用的自制库)贡献了scoped_ptr、shared_ptr、weak_ptr被C++11采纳,修改为官方的unique_ptr、shared_ptr、weak_ptr。,即只能有一个指针指向申请的资源空间,不存在两个指针同时指向一个空间,可以说这个就是C++11中对auto_ptr的改进,将它作为智能指针的一种形式。

2022-12-09 08:30:00 1879 4

原创 Linux——文件系统inode与软硬链接

值得注意的是,虽然一个文件只能有一个inode,但是一个inode可以对应多个文件名(这些文件本质是同一个文件,因为对应inode相同也就是使用的块相同)。同时inode中会记录映射的文件名数量(引用计数方式),当数量为0时才会真正删除文件信息。格式化时,并没有删除inode与block table内容,只是把映射取消,因为inode与block可以覆盖。当删除文件时,只需要把对应的bitmap置0,同时把inode映射取消即可。硬链接删除时也不会删除相关文件,但是硬链接与目标文件是同一个文件。

2022-12-06 09:45:00 814 4

原创 Linux——匿名管道、命名管道及进程池概念和实现原理

管道是linux中一种非常古老的进程间通信方式,本质上就是一个内存级的文件。一般用于父子进程间通信。概念上就是父进程与子进程共同使用一个管道文件来传输数据。虽然父子进程都有对管道的读和写功能,但在使用时只能读或者写,因此管道是单向通信,半双工模式。

2022-12-04 10:00:00 2147 9

原创 C++语法——map与set的封装原理

list迭代器是用类封装,迭代器内部定义一个指针指向节点,对迭代器的操作底层是对迭代器内部指针的操作。迭代器的begin和end只能在rb_tree中实现,对于begin而言,返回红黑树最左节点的迭代器,end返回root的父节点迭代器(空节点迭代器)。其原理就是如果是父节点的左子节点,那么说明父节点在自己之后,如果是右子节点那么说明在父节点之后。对比3号和5号节点,都是叶子节点,但是3号节点++后是1号节点,5号++后是6号节点。对比节点3和节点5,节点3--之后是节点2,节点5--之后是节点4。

2022-12-01 05:30:00 885 9

原创 Primer笔记——显式转换、返回数组指针的函数、const形参函数重载

指针本身是const属性(常量指针),例:int const* p;:指针指向const类型对象空间(指向常量的指针),例:const int* p;只要不是底层const都可以通过该函数转换。该函数可以用来找回存于void*指针中的值。值得注意,如果指针转化后类型并不是数据本身类型,也会报错,结果未定义。使用函数重载时,顶层const不能进行重载,底层可以。原因:传入指针实参时,常量指针和普通指针都能被常量指针指针接收。但指向常量的指针不能被普通指针形参接收,只能被指向常量的指针形参接受。

2022-11-29 06:45:00 481 6

原创 Primer笔记——typedef指针类型别名时的const陷阱

只要typedef中没有声明const,那么指针永远指向变量。只要类型别名在使用时有const修饰,那么指针永远是常量指针。即typedef中const决定指向对象属性,定义时const决定指针自身属性。说的官方一点就是typedef决定底层const,定义决定顶层const。typedef定义替换形式含义int* p指向变量的指针指向变量的常量指针指向常量的指针指向常量的常量指针先解决问题再写代码——John Johnson如有错误,敬请斧正。

2022-11-27 10:45:00 904 4

原创 Rust——猜数游戏、数据类型、函数、循环和if条件表达式

默认函数最后一个函数表达式作为返回,必须没有分号,如果最后一句带有分号相当于不返回任何数据。随机生成一个数字供玩家猜测,直到猜中为止,并提示玩家是否继续玩。既可以接收i32之类,也可以接收String之类类型。loop默认死循环,直到遇见break语句时跳出循环,for的使用与C++范围for类似,同样不加"()"。Rust不关心函数声明为止,在调用位置之后也可以。使用与C语言类似,但是没有"()"。也可以使用return来显式返回。if在使用时可以不加括号"()"。使用方式与C语言类似。

2022-11-25 18:01:40 1401 8

原创 Linux——文件描述符(fd)与重定向、dup/dup2

操作系统根据调用的write的进程,通过task_struct结构体内部指针,找到files_struct,再通过files_struct内的fd_array指针数组,依照write传入的具体fd值(下标),找到对应的file结构体,就找到了相关文件。即标准输入、标准输出、标准错误。fd_array是一个数组,里面存放的是指针,每一个指针都指向一个file结构体。关闭一个文件时,fd_array中相关指针会指向空,当再其他文件打开时,按照从0开始寻找空指针的方式,找到该空指针,让其指向新的文件。

2022-11-24 09:45:00 2325 6

原创 Rust学习笔记——安装、创建、编译、输入输出

这是Rust官网。直接下载自己对应系统版本即可,小编是linux版。如果有需要,可以下载相关编辑器(如:vscode)更利于学习和使用。

2022-11-22 21:12:24 1073

空空如也

空空如也

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

TA关注的人

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