- 博客(49)
- 收藏
- 关注
原创 C++ map 与 set 详解:有序关联容器的使用、边界与实战
默认的形成升序。需要降序时,可以直接使用4 3 2 1int score;return 0;Alice:92Carol:92Bob:88比较器不仅决定输出顺序,还决定两个键是否等价。如果只比较score,那么set会把两个同分对象视为等价键,只保留一个。加入name作为第二比较条件,才能区分同分对象。比较器必须形成严格弱序。不要使用<=代替,也不要让比较结果随外部可变状态随意变化。不只如此。比较器还定义键的等价关系,直接影响能否插入、如何查找以及区间边界。
2026-07-20 22:26:49
233
原创 C++ 二叉搜索树详解:查找、插入、删除与完整模板实现
二叉搜索树(Binary Search Tree,BST)也叫二叉排序树。它可以是一棵空树;左子树中的键都小于当前结点的键;右子树中的键都大于当前结点的键;左右子树本身也分别是二叉搜索树。这里先采用“不允许重复键”的定义。对于重复键,后面会单独讨论。把序列8/ \3 10/ \ \1 6 14/ \ /4 7 13结点8左侧的所有键都比8小,右侧都比8大;结点361014的子树同样满足这个规则;对它进行中序遍历,结果恰好是升序序列。
2026-07-20 19:34:52
232
原创 C++ 多态详解:虚函数、动态绑定、抽象类与虚表原理
在非静态成员函数声明前添加virtualpublic:虚函数属于类的成员函数。普通非成员函数和static成员函数都不能声明为虚函数,因为动态分派依赖调用对象,而静态成员函数没有this指针。派生类重写虚函数时,即使省略virtualpublic:现代 C++ 更推荐使用override。它既表达意图,也让编译器检查这个函数是否真的完成了重写。可以。抽象类不能直接实例化,不代表它不能保存基类状态或提供公共实现。创建派生对象时,抽象基类的构造函数仍会参与基类子对象的初始化。
2026-07-16 19:30:49
201
原创 C++ 继承详解:访问控制、对象模型、菱形继承与设计取舍
执行时,目标对象的类型就是Person。复制过程只保留Student中的Person部分,不会进入新对象,这种现象称为对象切片。Student: [ Person 部分 ][ student_id_ ]| 复制到 Person 对象vPerson : [ Person 部分 ]引用和指针转换不会创建新的基类对象,因此不会切掉派生类部分。它们只是以基类视角访问原来的对象,这也是多态通常使用基类引用或指针的原因。通常不算。它们位于不同作用域,派生类函数会隐藏基类同名函数。可以通过作用域限定符或。
2026-07-16 13:55:57
203
原创 C++ 模板进阶:非类型参数、模板特化与分离编译
/ add.hpp调用方包含add.hppint main().hpp只是常见命名习惯,用来提醒读者其中包含模板实现;使用.h也可以,语言并不依赖扩展名。只有main.cpp和add.hpp34编译器在实例化点需要看到完整定义。只有声明时,它知道函数存在,却无法为当前类型生成代码。非类型模板参数把编译期常量纳入类型生成过程,适合固定容量和静态策略;模板特化让通用实现遇到特殊类型时切换规则;模板定义在实例化点必须可见,则解释了为什么模板通常以头文件实现为主。
2026-07-15 20:46:33
168
原创 C++ stack、queue 与 priority_queue:容器适配器原理与实战
适配器的思路是:内部保存一个已有容器,对外换一组更受限制的接口。以栈为例,只要底层容器支持back()和pop_back()这也解释了两个常见问题。stack和queue没有begin()end(),因为任意遍历会绕开它们规定的访问顺序。如果业务必须查看中间元素,说明需要的可能是普通容器,而不是受限接口。pop()不返回被删除的值。要取出元素,需要先读取top()或front(),再调用pop()不必为了放入优先级队列而修改类型本身。int main()输出是restart。
2026-07-15 18:30:27
195
原创 C++ list 原理与实践:双向链表、迭代器与简化实现
list的行为都可以从节点模型推出来:节点彼此独立,所以已知位置的插入和删除只需改链接;节点不连续,所以不能随机访问,遍历的缓存表现也不如连续容器稳定。insert在迭代器之前插入,erase会使当前迭代器失效并返回下一位置,插入删除的O(1)需要以已经找到位置为前提。理解哨兵节点和迭代器封装后,这些规则就不再需要死记。
2026-07-14 15:42:00
243
原创 C++ vector 原理与实践:从常用接口到扩容和迭代器失效
理解vector可以抓住一条主线:它是连续存储的动态数组。连续存储带来了O(1)随机访问和良好的局部性,也意味着扩容时需要搬运全部元素,中间插入和删除时需要移动后续元素。日常使用中,分清size与capacity,知道reserve不会创建元素,并在修改容器后谨慎处理迭代器,已经能避开大部分问题。再往下看,三个位置的存储模型、逐对象构造和资源转移规则,则解释了扩容、迭代器失效以及不能滥用memcpy的根本原因。
2026-07-14 10:04:36
182
原创 C++ string 详解:常用接口、字符串算法与深拷贝实现
输入可能带有尾部空格,因此只调用一次rfind(' ')并不稳妥。--end;这种写法同时处理空字符串、全空格字符串、无空格字符串和尾部存在多个空格的情况。考虑一个只保存char*public:private:如果没有定义拷贝构造函数,编译器生成的版本会逐成员复制_data指针:fill:#333;important;important;fill:none;color:#333;color:#333;important;fill:none;
2026-07-12 19:00:00
248
原创 C++ STL 入门:六大组件、协作机制与学习路线
STL 的全称是 Standard Template Library,中文通常译为标准模板库。它是 C++ 标准库中的重要组成部分,提供了通用的数据结构、算法和配套抽象。C++ 标准库范围更广,还包括输入输出、字符串、时间、线程、正则表达式等设施。STL 通常指以容器、迭代器和算法为中心的泛型编程体系。STL 大量使用模板。以vector三种vector保存的元素不同,但动态扩容、随机访问和尾部插入等基本行为一致。模板让这些公共逻辑只需要实现一套。end()指向末尾之后,不能解引用。
2026-07-12 13:55:56
190
原创 C++ 模板入门:函数模板、类模板与实例化机制
泛型编程是编写能够适用于多种类型的通用代码。算法关注类型需要具备什么能力,而不是把类型名称写死。交换两个对象需要的能力很简单:对象可以被复制或移动,并且可以赋值。至于对象是intdouble还是自定义类型,交换步骤并没有本质区别。int a = 10;int b = 20;Swap(a, b);Swap(x, y);编译器根据实参推导T。第一次调用得到T = int,第二次调用得到T = double,再分别生成可用的函数版本。模板并不是“一个函数可以接收任意类型”。
2026-07-11 23:15:40
207
原创 C++ 内存管理:从内存分区到 new/delete 底层原理
对类对象使用new时,构造函数会执行;使用delete时,析构函数会先执行。./resourcedestroy: 7destroy: 0destroy: 0数组中的对象按顺序构造,析构时通常按相反顺序执行。即使示例里的两个对象看不出差别,delete[]仍然必须与new[]配对。分析内存位置时,要区分变量本身和它所指向的对象。局部指针可以位于栈中,它指向的动态对象位于堆中;
2026-07-11 19:40:35
205
原创 C++ 类和对象下篇:初始化列表、static、友元与对象优化
A a();是函数声明。默认构造对象写A a;或A a{};。类的初始化发生在构造函数体执行之前,初始化列表才是成员建立初始状态的位置。引用、const成员和没有默认构造函数的成员对象必须在那里处理,而真实初始化顺序由成员声明顺序决定。转换构造函数让类型之间可以自然衔接,explicit则负责阻止不受欢迎的隐式转换。静态成员表达类级共享状态;友元和内部类解决紧密协作问题,但都要控制使用范围,避免类之间形成不必要的依赖。匿名对象适合立即使用的临时值。
2026-07-10 12:42:23
156
原创 C++ 类和对象中篇:默认成员函数、拷贝控制与运算符重载
默认成员函数指的是:当我们没有显式实现时,编译器可能自动生成的特殊成员函数。一个类通常会涉及 6 个默认成员函数:fill:#333;important;important;fill:none;color:#333;color:#333;important;fill:none;fill:#333;height:1em;默认成员函数初始化与清理拷贝控制取地址重载构造函数析构函数拷贝构造函数赋值运算符重载普通对象取地址const 对象取地址这 6 个函数并不是都需要我们亲自写。
2026-07-09 18:00:00
219
原创 C++ 类和对象入门:类的定义、封装、对象大小与 this 指针
类定义本质上是在定义一种类型,语法要求类体结束后必须有分号。少了分号,后面的代码会被编译器错误解析。定义在类体内的成员函数默认具有内联属性。注意这只是给编译器的建议,不代表一定展开。类和对象是 C++ 从过程式编程走向面向对象编程的起点。类把成员变量和成员函数组织在一起;public提供接口,private隐藏实现细节;类只是类型描述,对象实例化后才真正占用内存;对象中主要存成员变量,成员函数代码只保留一份;对象大小受成员变量和内存对齐影响,空类大小通常为 1;成员函数通过隐含的this。
2026-07-09 12:35:46
185
原创 C++ 入门基础:从命名空间到引用、inline 与 nullptr
namespace解决命名冲突;iostream提供类型更友好的输入输出方式;缺省参数让函数调用更灵活;函数重载让同一操作可以适配不同参数;引用让传参和返回更自然,但要关注生命周期;const引用解决权限控制和临时对象绑定问题;inline用更安全的方式替代宏函数的部分场景;nullptr比NULL更适合表达空指针。这些内容看起来分散,本质上都在增强 C++ 的表达能力和工程组织能力。把这些基础打稳,再继续学习类和对象、STL、模板、智能指针等内容时,很多语法设计就会顺起来。
2026-07-07 22:21:12
253
原创 Reactor 反应堆模式详解
epoll_wait负责等待事件;TcpServer负责维护 fd 和连接对象的关系;Connection保存单个连接的状态、缓冲区和回调;Accepter负责接收新连接;负责普通连接的读、写和异常处理;协议层负责拆包、反序列化、业务处理和序列化响应。这种设计让事件循环保持简洁,把复杂度分散到更清晰的模块里。后续如果要扩展协议、增加业务、支持更多连接状态,也不必把所有逻辑都塞进一个巨大的epoll_wait循环。事件循环只分发,不做业务;连接对象保存状态,回调函数响应事件。
2026-07-06 19:32:48
259
原创 Linux epoll 多路转接详解
epoll先注册关注的 fd,事件真正就绪时再通过就绪队列返回给应用程序。创建 epoll 句柄;epoll_ctl负责添加、修改、删除 fd 事件;epoll_wait返回已经就绪的事件数组;内核通过红黑树管理已注册事件,通过就绪链表保存待返回事件;LT 模式会反复提示未处理完的就绪事件;ET 模式只在状态变化时提示,需要配合非阻塞读写把数据处理干净;epoll 适合大量连接、少量活跃的场景。理解 epoll 时,不要只记接口名。真正重要的是看懂它相对selectpoll。
2026-07-06 18:39:59
260
原创 Linux select 多路转接详解
select把多个 fd 放进集合里,让内核帮我们等待它们的状态变化,返回后再由应用程序逐个处理就绪 fd。掌握selectnfds是最大 fd 加 1;fd_set本质是位图,用FD_ZEROFD_SETFD_CLRFD_ISSET操作;select会修改传入的 fd 集合,所以每轮调用前要重新准备;监听 socket 可读表示可以accept新连接;普通连接 socket 可读表示可以read或recv;select。
2026-07-05 23:20:36
221
原创 Linux 五种 IO 模型详解
阻塞 IO:等待和拷贝都让调用者停住;非阻塞 IO:数据没准备好就立即返回,需要应用程序重试;信号驱动 IO:数据就绪时通知应用程序,读取动作仍由应用程序发起;IO 多路转接:用一个入口等待多个描述符就绪;异步 IO:等待和拷贝都由内核完成,完成后再通知应用程序。真正写网络程序时,没有哪一种模型永远最好。简单程序用阻塞 IO 足够清晰;需要同时管理多个连接时,多路转接更合适;想让等待过程更可控,就会接触非阻塞和事件驱动;而异步 IO 则把完成通知推进到更彻底的层次。
2026-07-05 12:12:28
229
原创 NAT、代理服务与内网穿透详解
局域网内能访问,换到外网就访问不了。这背后通常和私有 IP、NAT、路由器出口地址有关。NAT 让大量内网设备能够共享公网出口访问互联网,但它也带来了一个副作用:外部网络很难主动连接到内网机器。代理服务和内网穿透又是在这个背景下经常出现的概念。它们看起来都像“中间转发”,但工作层次、解决的问题和部署方式并不一样。NAT 是 IPv4 地址不足背景下的重要技术,它通过地址转换让大量私有地址设备共享公网出口。NAPT 进一步使用 IP 和端口建立映射,解决多台内网主机同时访问同一外部服务的问题。
2026-07-04 20:18:14
205
原创 Linux 数据链路层详解:以太网帧、MAC 地址、MTU 与 ARP 协议
前面理解网络层时,我们经常说“路由器把 IP 数据包转发给下一跳”。下一跳知道了,网卡到底把这一帧发给谁?这就是数据链路层要解决的问题。网络层关注的是从源主机到目标主机的整体路径,数据链路层关注的是同一种链路上相邻设备之间的一段传递。换句话说,IP 地址描述的是“整段旅程”的起点和终点,MAC 地址描述的是“每一小段路”的起点和终点。数据链路层负责相邻节点之间的数据传递。以太网通过帧格式封装目的 MAC、源 MAC、类型、数据和 CRC;MAC 地址用于标识当前链路上的节点;
2026-06-29 22:34:45
333
原创 Linux 网络层详解:IP 协议、网段划分、CIDR、NAT 与路由转发
目标主机不在当前局域网里,数据包应该往哪里走?这就是网络层的核心任务:在复杂网络环境中确定合适路径。在网络层里,最重要的协议就是 IP。平时说的 IP 地址、子网掩码、网关、路由表、NAT,大多都和这一层有关。概念含义主机配有 IP 地址,但不进行路由控制的设备路由器配有 IP 地址,并且能进行路由控制的设备节点主机和路由器的统称网络层的核心任务是路径选择。IP 报头记录源地址、目的地址、TTL、分片等信息;IP 地址通过网络号和主机号描述网络位置;CIDR 用子网掩码灵活划分网段;
2026-06-29 22:05:56
268
原创 tcp-transport-layer-protocol
UDP 的特点是简单:无连接、不保证可靠、面向数据报。TCP 正好走了另一条路,它希望在复杂网络环境中尽可能保证数据可靠、有序地到达对端,同时还要尽量提高传输效率。这也是 TCP 难学的原因:它不是单靠一个机制实现可靠传输,而是由一组机制共同配合完成的。可靠性:校验和、序列号、确认应答、超时重传、连接管理、流量控制、拥塞控制性能:滑动窗口、快重传、延迟应答、捎带应答应用视角:面向字节流,需要应用层处理消息边界下面按这条线展开。TCP 的复杂性来自它的目标:既要可靠,又要尽量高效。
2026-06-28 22:00:00
239
原创 Linux 传输层 UDP 协议详解
学习网络编程时,很多人会先接触socketbindsendtorecvfrom这些接口,然后再反过来问一个更底层的问题:为什么 UDP 发送数据时不需要建立连接?为什么一次sendto发出去的数据,接收端不能像 TCP 那样随便分多次读取?这些问题的答案,都在 UDP 的协议设计里。UDP 位于传输层。传输层要解决的核心问题,是让数据从一台主机上的某个进程,交付到另一台主机上的某个进程。网络层能把数据送到目标主机,但到达主机之后,还需要知道该交给哪个应用程序,这就是端口号存在的意义。
2026-06-28 17:40:25
400
原创 Linux应用协议HTTP 入门
前面理解了 TCP Socket 之后,我们已经知道:TCP 负责把字节可靠地传到对端,但业务数据怎么组织、怎么解释,需要应用层自己约定。HTTP 就是一套已经被广泛使用的应用层约定。浏览器访问网站时,会向服务器发送 HTTP 请求;服务器处理请求后,再返回 HTTP 响应。一个网页、一张图片、一次表单提交、一次接口调用,背后基本都离不开这种请求-响应模型。很多初学者第一次接触 HTTP 时,会把它理解成“浏览器地址栏里的网址”。这只说对了一部分。
2026-06-26 17:13:21
260
原创 Linux应用层自定义协议与序列化
写完 TCP Socket 之后,很多人会有一个疑问:客户端send了一次,服务端recv一次,不就拿到数据了吗?简单 echo 程序看起来确实如此,但真实业务通常不会这么理想。比如我们要实现一个网络计算器,客户端发送两个操作数和一个运算符,服务端计算后返回结果。datax = 10datay = 20oper = +问题随之出现:服务端如何知道这三个字段分别是什么?一次recv读到的内容一定是一个完整请求吗?如果客户端连续发送两次请求,服务端如何区分第一条和第二条?
2026-06-26 12:19:27
177
原创 计算机网络Socket入门:TCP
UDP 编程中,服务端通常是。TCP 的流程要多一些,因为 TCP 是面向连接的协议,通信前需要先建立连接。写 TCP 程序时,最容易混淆的地方有几个:服务端为什么要listen?accept返回的新文件描述符和监听 socket 有什么区别?客户端为什么通常不需要显式bind?为什么最简单的 TCP 服务器只能服务一个客户端?多进程、多线程、线程池版本分别解决什么问题?这篇文章按从基础到工程演进的顺序,把 TCP Socket 编程完整梳理一遍。
2026-06-25 17:00:00
336
原创 计算机网络Socket入门:UDP
学完网络基础后,真正进入网络编程,绕不开 socket。Socket 是应用层与传输层之间的抽象接口,它屏蔽了底层网络协议的复杂性,让开发者能够以类似文件读写的方式操作网络连接。对 TCP 来说,我们经常会看到listenacceptconnect这些接口,它们构成了 TCP 面向连接、可靠传输的基石。recvfrom 接收数据sendto 发送数据UDP 简单,不代表不用认真理解。为什么服务端要bind?因为服务端需要在一个众所周知的端口上等待客户端请求,bind。
2026-06-25 10:24:24
466
原创 计算机网络基础
很多同学刚学网络时会直接接触socketbindlistenaccept这些接口,但如果不知道数据在网络中到底经历了什么,就很容易把 API 背成“咒语”。两台甚至更多台主机,隔着不同设备、不同线路、不同操作系统,如何把数据准确交给对方的某个进程。为了回答这个问题,我们需要先理解几个核心概念:协议、分层、封装、解包、IP、MAC、端口号和 socket。
2026-06-24 19:00:00
367
原创 Linux多线程同步与互斥
在 Linux 多线程编程中,创建线程并不难,真正容易出问题的是:多个线程同时访问同一份数据。共享资源被多个执行流并发访问。如果没有同步机制,程序可能偶尔正确、偶尔异常,排查起来非常痛苦。这篇文章基于线程同步与互斥相关课件内容,把知识按“问题 -> 原理 -> 接口 -> 示例 -> 易错点”的顺序重新梳理一遍。线程同步与互斥的核心不是记 API,而是理解共享资源的访问规则。互斥锁解决“同一时刻只能有一个线程进入临界区”;条件变量解决“条件不满足时线程如何等待”;信号量适合表达“资源数量”;
2026-06-24 10:47:56
373
原创 Linux线程概念与控制
先看定义:线程是一个进程内部的控制序列,也可以看作程序中的一条执行路线。一个进程至少有一个线程。之前写的很多单进程程序,可以看作只有一个执行流的进程。线程运行在进程内部,依托的是进程的虚拟地址空间。进程:承担资源分配线程:承担调度执行如果从 CPU 和内核角度看,Linux 中线程并不是一个完全独立于进程的新物种。Linux 内核并没有像某些教材那样,把“进程 PCB”和“线程 TCB”拆成两个完全割裂的体系。Linux 更统一:线程也是被内核调度的执行实体,只是它与同组线程共享了更多资源。
2026-06-23 19:00:00
239
原创 Linux进程信号
一句话:信号是进程之间事件异步通知的一种方式,属于软中断。每个信号都有编号和宏名。2信号编号常见来源默认动作SIGINT2Ctrl+C终止进程SIGQUIT3Ctrl+\终止并 core dumpSIGABRT6abort()终止并 core dumpSIGFPE8除零等算术异常终止并 core dumpSIGKILL9强制杀死进程终止,不可捕捉SIGSEGV11非法内存访问终止并 core dumpSIGPIPE13向无读端管道写入终止SIGALRM14alarm()定时到期。
2026-06-23 10:16:59
305
原创 Linux_进程间通信
进程间通信的核心矛盾是:进程需要保持独立,又必须能够协作。Linux 通过一系列 IPC 机制解决这个问题。匿名管道通过父子进程继承文件描述符,实现简单高效的单向字节流通信。命名管道通过文件系统路径,让无亲缘关系的进程也能建立通信。共享内存通过地址空间映射,提供最快的数据交换方式,但需要额外同步。消息队列提供带类型的结构化消息传递。信号量用于同步和互斥,解决多个执行流访问共享资源的秩序问题。如果只背接口,IPC 会显得零散;
2026-06-22 09:47:35
353
原创 Linux_库制作与原理
库就是已经写好的、成熟的、可以复用的代码。现实中的程序不可能每个人都从零开始写。比如字符串处理、文件 IO、网络通信、数学计算、界面绘制,这些通用能力通常都会被封装成库。从本质上说:库是一种二进制形式的可执行代码,可以被操作系统加载到内存中执行。类型LinuxWindows静态库.a.lib动态库.so.dll不同发行版路径可能不一样,但核心思想一致。现在绝大多数程序默认使用动态链接。原因很现实。静态链接会把所有需要的代码合并到可执行文件中。文件体积变大多个程序重复携带相同库代码。
2026-06-22 08:35:18
337
原创 Linux_Ext系列文件系统
Ext 系列文件系统这一章,看似从磁盘硬件讲到软硬链接跨度很大,但主线其实非常清楚。磁盘物理上由盘片、磁头、磁道、柱面、扇区构成;早期使用 CHS 寻址,后来通过 LBA 把磁盘抽象成线性数组。操作系统不会按单个扇区低效读写,而是以 block 为单位访问磁盘。分区只是磁盘空间的切分,格式化才是在分区中写入文件系统管理结构。
2026-06-21 19:56:00
253
原创 Linux基础IO
基础 IO 看似只是几个读写接口,实际上它串起了 Linux 文件系统、进程、系统调用、内核对象和 C 标准库。从用户层看,很好用;从系统层看,它们最终都要落到。open()返回的 fd 本质是当前进程文件描述符表的下标,0/1/2分别对应标准输入、标准输出、标准错误。重定向的本质不是修改printf,而是通过close/open或dup2改变 fd 的指向。再往内核看,fd 会关联到,而中的通过函数指针把统一的接口映射到不同设备的具体实现,这就是 Linux “一切皆文件”的底层支撑。最后,
2026-06-21 16:33:11
343
原创 Linux进程控制
进程控制这一章,本质上讲的是 Linux 如何管理进程生命周期。fork()负责创建子进程,让一个执行流变成两个执行流;写时拷贝保证创建过程足够高效。exit()和_exit()负责进程终止,其中exit()会进行用户态清理,而_exit()更接近直接退出。wait()和waitpid()负责父进程回收子进程,避免僵尸进程,同时获取子进程退出状态。exec函数族负责程序替换,让子进程从父进程代码切换成一个全新的程序。读取命令 -> 解析参数 -> fork -> exec -> wait。
2026-06-20 16:04:11
376
原创 Linux进程概念
本文深入探讨了Linux操作系统中的进程概念,从操作系统管理思想出发,系统性地讲解了进程的本质和相关技术要点。主要内容包括: 操作系统管理的核心方法论:"先描述,再组织",通过创建数据结构描述对象(如进程的task_struct),再使用合适的数据结构组织管理这些对象。 进程的本质:不仅是"运行中的程序",更是操作系统分配系统资源的基本实体。程序是静态文件,进程是动态执行的资源分配单位。 进程控制块(PCB):具体实现为task_struct结构体,包含进程ID、状态、优先级、内存指针等上百个字段,是操作系
2026-06-16 21:57:20
391
原创 Linux基础
a: 列出目录下的所有文件,包括以 . 开头的隐含文件。-d: 将目录像文件一样显示,而不是显示其下的文件。-i: 输出文件的i节点的索引信息。-k: 以k字节的形式显示文件的大小。-l: 列出文件的详细信息。-n: 用文件的UID,GID代替名称。-F: 在每个文件名后附上一个字符以说明该文件的类型," * "表示可执行的普通文件;" / "表示目录;" @ "表示符号链接;" | "表示FIFOs;“ = ”表示套接字(sockets)。(目录类型识别)-r: 对目录反向排序。
2026-05-09 23:40:01
361
空空如也
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人
RSS订阅