- 博客(131)
- 收藏
- 关注
原创 C++学习笔记----11、模块、头文件及各种主题(二)---- 函数模板(2)
很明显的解决方案是使用公共的getWidth()与getHeight()成员函数,但是让我们看一个如何能够使函数模板成为类模板的朋友。你其实想要的是每个特别的类型T的operator+的每个实例都成为Grid模板相同类型的实例的一个朋友。该模板朋友的声明有点搞:你要说的是,对于类型T的类模板的实例,operator+的T的实例化是一个朋友。如下的代码首先定义了两个辅助函数模板:fillGrid(),它用递增的数字填充了Grid,printGrid(),它打印任何Grid到控制台。
2024-11-12 07:32:23
513
原创 C++学习笔记----11、模块、头文件及各种主题(二)---- 函数模板(1)
也可以为单独的函数写模板。语法与类模板类似。Find函数模板可以用于任何类型的数组。例如,可以用其查找int数组中的int的索引或SpreadsheetCell数组中的SpreadsheetCell。可以用两种方式调用函数:用<>显式指定模板类型参数或省略类型让编译器从参数中推断类型参数。上面的Find()函数的实现要求作为其中的一个参数给出数组的大小。有时编译器是知道数组的确切大小的,例如,对于基于堆的数组。不必要传递数组的大小就能够调用Find()会比较好。可以通过添加如下的函数模板来达到目的。
2024-11-12 07:26:09
444
原创 C++学习笔记----11、模块、头文件及各种主题(一)---- 模板概览与类模板(8)
继承的语法看起来是正常的,除了基类为Grid<T>,而不是Grid之外。而是,每一个特定类型的GameBoard模板的实例继承自同样类型的Grid实例。:public Grid<T>语法说明该类继承自任何Grid实例,对于T类型的参数都是合理的。还有别的选择,可以继承自类模板的特定实例,这种情况下继承类就不必为模板。还有,也可以只指定一些类型而保持模板类型参数中的其它类型不变。是的:继承类包含了所有基类的数据成员与成员函数。不是:模板的每一个实例类型是一个不同的类型。是的:继承类的对象可以代表基类的对象。
2024-11-11 07:32:00
665
原创 C++学习笔记----11、模块、头文件及各种主题(一)---- 模板概览与类模板(7)
例如,你可能决定Grid的const char*(C风格的字符串)的行为是讲不通的。作为一个例子,const char*特殊化的Grid实现at()成员函数返回optional<string>,而不是optional<const char*>。当写一个类模板特殊化时,必须指定它是基于模板的,并且是在写一个特定类型的一个模板的版本。下面是Grid的const char*特殊化的语法。在这个实现中,原来的Grid类模板被移到了一个叫做main的模块接口分区中,而特殊化在一个叫做string的模块接口分区中。
2024-11-11 07:26:39
491
原创 C++学习笔记----11、模块、头文件及各种主题(一)---- 模板概览与类模板(6)
这是因为编译器开始处理带有Self&&参数的成员函数时,类模板参数Self早已解析成了具体的类型,例如int,并且在当时,该成员函数的参数类型也已经被替换成了int&&。总结一下,有了成员函数模板,显式对象参数,传递引用,与forward_like()的组合,就能够声明与定义单个成员函数来提供const与non-const的实例了。实现使用了传递引用Self&&;我们运行的Grid类模板的例子带有一个单独的模板类型参数T,包含了at()成员函数的两个重载,const与non-const。
2024-11-10 09:08:51
796
原创 C++学习笔记----11、模块、头文件及各种主题(一)---- 模板概览与类模板(5)
也可以书写自己的用户定义的推演指导来帮助编译器。允许书写规则如何让模板类型参数进行推演。下面是一个演示用法的例子。public:private:由于有了CTAD,可以用std::string类型生成一个SpreadsheetCell。" };然而,如果传递const char*给SpreadsheetCell构造函数,类型T推演为const char*,这不是你想要的!该指导要定义在类定义之外,但要在与SpreadsheetCell类相同的命名空间内。通用语法如下。
2024-11-10 08:59:34
739
原创 C++学习笔记----11、模块、头文件及各种主题(一)---- 模板概览与类模板(4)
可以将成员函数定义直接放到定义类模板自身的模块接口文件中。当在另一个使用模板的源文件中导入这个模块时,编译器具有所有需要代码的访问权限。这项技术用在了前面 Grid的实现中。换一种方式,可以将类模板成员函数定义放至独立的模块接口分区文件中。这样也需要将类模板定义放在自身的模块接口分区中。导入与导出两个模块接口分区:definition与implementation。import std;import std;
2024-11-09 07:55:17
851
原创 C++学习笔记----11、模块、头文件及各种主题(一)---- 模板概览与类模板(3)
template <typename T>模板头必须放在Grid类模板的每个成员函数定义前面。注意:类模板的成员函数需要对任何使用类模板的代码可见。这就出现了这样的成员函数定义可以被放置在什么地方的一些限制。通常,它们只是被放置到与类模板定义自身相同的文件中。关于这个限制我们会在本章进行讨论。注意在::之前的名字是Grid<T>,不是Grid。构造函数的函数体与GameBoard构造函数一样。注意:如果一个类模板成员函数的实现需要一个特定模板类型参数的缺省值,例如T,那么就可以使用T{}语法。
2024-11-09 07:46:37
720
原创 C++学习笔记----11、模块、头文件及各种主题(一)---- 模板概览与类模板(2)
要理解类模板,研究一下其语法是很有帮助的。下面的例子展示了如何修改GameBoard类生成一个参数化的Grid类模板。代码后会对语法细节进行解释。注意名字由GameBoard修改为了Grid。Grid也应该能用于原始数据类型,如int与double。这就是为什么优选使用值语法而不是与用于GameBoard实现的多态指针语法相比的多态,来实现该解决方案。m_cells容器保存了真实的对象,而不是指针。与指针语法相比使用值语法的向下转化,使得不能有真正的空网格。也就是说,网格必须要有值。
2024-11-08 07:35:21
834
原创 C++学习笔记----11、模块、头文件及各种主题(一)---- 模板概览与类模板(1)
GameBoard代表了一个二维的网格,所以保存GameBoard中的GamePiece的一个选项可以是unique_ptr的vectors的vector。GameBoard作为一个二维数组的抽象,所以它应该提供数组访问语法,通过返回在任何位置的真实对象的引用,而不是该对象的拷贝。使用模板,可以书写不只给定的值是独立的,这些值的类型也是独立的。例如,C++中的sqrt()函数计算调用者提供的值的平方根。面向对象的编程范式添加了对象的概念,它将相关的数据与行为分组,但是不改变函数与成员函数的参数化值的方式。
2024-11-08 07:29:47
728
原创 C++学习笔记----10、模块、头文件及各种主题(六)---- C风格可变长度参数列表
在传统代码中,可能会碰到使用C风格变量长度的参数列表。在新的代码中,应该避免使用这些,而要用类型安全的可变长度参数列表的可变参数函数模板,关于该模板,我们以后再讨论。这样你就知道了C风格的可变长度参数列表,考虑<cstdio>中的C函数printf()。C/C++提供了语法与书写带有可变参数数量的自己的函数的一些工具宏。这些函数通常看起来很像printf()。例如,假定你想写一个应急的debug函数来打印字符串到stderr,如果debug标志被设置,如果debug标志没有设置就什么也不做。
2024-11-07 07:30:57
856
原创 C++学习笔记----10、模块、头文件及各种主题(五)---- 核心语言特性的特性测验宏与静态关键字
另外一个例子,__has_cpp_attribute(nodiscard)的值为201603,也就是说,是2016年3月,它是[[nodiscard]]属性首次引入的日期。在结束static变量主题的讨论之前,考虑一个这样的变量的初始化顺序。static数据成员不像非static数据成员,不是每个对象的部分,只有数据成员的一个拷贝,存在于该类任何对象的外部。不同源文件中的非本地变量以未定义的顺序被初始化,意味着析构的顺序也是未定义的。警告:非本地变量在不同的源文件中的初始化顺序是未定义的。
2024-11-07 07:25:27
1252
原创 C++学习笔记----10、模块、头文件及各种主题(四)---- 头文件
单个翻译单元可以只有一个变量、函数、类类型、枚举类型、概念或者模板的定义。对于有些类型,允许多重声明,但是不允许多重定义。更进一步,在整个程序中只能有一个非内联函数与非内联变量的定义。对于头文件,比较容易破坏单一定义规则,造成重复定义。下一节我们讨论如何通过头文件避免这样的重复定义。在模块之间,破坏单一定义原则是比较难的,因为每一个模块与其它模块进行了更好的隔离。这样做的一个主要原因是在模块中的实体不会从其它模块导出,其他模块拥有模块连接,因此从其它模块的代码无法访问。
2024-11-06 07:36:36
1023
原创 C++学习笔记----10、模块、头文件及各种主题(三)---- 连接
本节讨论C++中连接的概念。C++源文件首先被预处理器处理,它处理所有的预处理指令,结果为翻译单元。所有的翻译单元独立地被编译为对象文件,包含了机器执行码,但是函数的引用还没有定义。解析这些引用是最后阶段,连接器,连接所有的对象文件一起形成最终的可执行文件。技术上讲,在编译过程中还有一些步骤,但对于我们讨论来讲,这个简化的观点已经足够了。在C++翻译单元中的每个名字,包含了函数与全局变量,有连接或无连接,这指出了在哪儿可以定义该名字,从哪儿可以访问到它。
2024-11-06 07:30:23
1150
原创 C++学习笔记----10、模块、头文件及各种主题(二)---- 预处理指令
使用#include预处理指令来包含头文件的内容。还有一些预处理指令。下面列表展示了一些常用的预处理指令:预处理指令功能通用场景[file]文件名的内容插入到指令位置的代码中几乎总是用于包含头文件以便代码可以使用定义在其它地方的功能每个标识符[id]出现的地方都用[value]替换常用于C定义常数或宏。c++对于常数与大部分类型的宏提供了更好的方法。宏可能会危险,所以要小心使用。使前面使用#define定义的[id]失效如果定义的标识符只要求在限定范围的代码中使用。#else。
2024-11-05 07:25:00
1121
原创 C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(5)
还有,在前面章节中提到过,导入合适的模块,比如:std或std.compat,不会使任何定义在模块中的C风格的宏对导入的代码可用。在使用C标准库中的C风格的宏时要记住,这特别重要。既然命名的std与std.compat模块不会使assert()宏对于导入代码可用,既然<cassert>是一个C标准库头文件,因此不保证可导入,必须使用#include <cassert>来访问assert()。注意:直到c++23,<name.h>的使用C标准库头文件是过时的,从c++23开始,它们的使用不再过时,但不鼓励。
2024-11-05 07:19:48
797
1
原创 C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(4)
这是正确的--它没有显式导出,但是它是隐式导出的,因为Adder类被导出,Impl类在Adder类中被声明。在该章节中,解决方案要求两个文件:一个主模块接口文件和一个模块实现文件。在模块接口分区文件中不需要声明分区,也可以在模块实现分区文件中进行声明,在一个以.cpp为扩展名的正常源代码文件中,在这种情况下,它是一个实现分区,有时候叫内部分区。原因是Adder::Impl类是私有模块部分的一部分,因此Adder模块的消费者无法访问。在这个私有模块部分定义的任何东东都不会导出,因此对模块的消费者也不可见。
2024-11-04 07:40:54
311
原创 C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(3)
子模块与分区的的区别是子模块结构对于模块的用户是可见的,允许用户单独只导入那些想要用的子模块。如前所述,当在另外一个不是person模块的一部分的源文件中导入person模块,例如在test.cpp文件的盒子中,就没有隐式地继承person模块接口文件中的std导入声明。为了打破这种循环依赖,可以将在datamodel接口文件中需要的datamodel:person分区的功能移到另外一个分区,该分区按顺序被datamodel:person接口分区文件与datamodel接口文件导入。对分区来说不是这样。
2024-11-04 07:33:11
1078
原创 C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(2)
模块文件只包含类定义,文件原型,等等,但是不包含任何函数或成员函数的实现,即使这些实现就在接口文件中。这意味着修改在模块接口文件中的一个函数或成员函数实现不会要求重新编译使用那个模块的用户,只要不涉及接口部分,例如,函数头(=函数名,参数列表,与返回类型)。例如,如果模块有一个很大的公共接口,可能不要用实现来妨碍接口就会好一些,这样用户可以有一个提供了什么的整体更好的观感。模块实现文件通过以.cpp结尾。注意:所有模块中与模块实现中的导入声明必须在文件的头部,在命名模块声明之后,但是在其它声明之前。
2024-11-03 07:44:53
892
1
原创 C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(1)
在使用模块之前,头文件用于提供代码重用的接口。头文件确实有许多问题,比如避免同一头文件的多重包含以及确保头文件的包含顺序正确。还有,简单的#include,例如,<iostream>就添加了几千行代码,编译器不得不编译。如果几个源文件#include <iostream>,所有这些翻译单元变得更大了。这还只是包含了一个简单的头文件。想像一下,如果需要<iostream>,<vector>,<format>等等。模块解决了所有这些问题,甚至更多。模块被import的顺序并不重要。
2024-11-03 07:39:25
1099
原创 C++学习笔记----9、发现继承的技巧(七)---- 转换(2)
下面的表总结了应用于不同情的转换。情形转换去除常量特性显式进行语言支持的转换(例如,int到double,int到bool)显式进行支持用户定义的构造函数或转换的转换将一个类的对象转换为另一个(不相关的)类的对象bit_cast()相同层次结构的类中一个类的对象指针到另一个类的对象指针推荐dynamic_cast()或static_cast()相同层次结构的类中一个类的对象引用到另一个类的对象引用推荐dynamic_cast()或static_cast()类型指针到无关的类型指针。
2024-11-02 08:02:37
1274
原创 C++学习笔记----9、发现继承的技巧(七)---- 转换(1)
我们来看下将一种数据类型转换为另一种数据类型中的一些令人迷惑的地方。C++提供了五种类型转换:const_cast(),static_cast(),reinterpret_cast(),dynamic_cast()与std::bit_cast()。对于static_cast对于继承还有一些内容要讨论。现在你应该能够很熟练地书写自己的类,理解了类继承,是时候来详细探索一下这些转换了。注意旧的C风格的比如(int)myFloat在C++中仍然可以用,并且在既有代码中还大量使用着。
2024-11-02 07:56:43
1017
原创 C++学习笔记----9、发现继承的技巧(六)---- 有趣且令人迷惑的继承问题(7)
在这种情况下,当从继承类的构造函数中调用时,编译器使对Dog与Bird构造函数中的对Animal构造函数的调用失效,取而代之的是调用了Animal基类的缺省构造函数,这样就需要Animal的protected缺省构造函数。C++有另外的方法,叫做虚基类,来解决这种问题,如果你确实想让共享父类有自身的功能的话。在不用虚基类的情况下,在DogBird对象上调用sleep()会是不明确的,因为DogBird有两个Animal的子对象,一个来自于Dog,一个来自于Bird,所以会产生编译器错误。
2024-11-01 07:29:30
573
原创 C++学习笔记----9、发现继承的技巧(六)---- 有趣且令人迷惑的继承问题(6)
然而,c++的属性提供了对象的运行时观点。这在本章前面也讨论过了。如果继承类省略了拷贝构造函数或operator=,在继承类中的数据成员会被提供缺省的拷贝构造函数或operator=,基类拷贝构造函数或operator=会被用于基类中指定的数据成员。注意:当需要在继承层次结构中的拷贝功能时,专业c++开发者通用的方法是实现多态clone()成员函数,因为单纯依赖标准拷贝构造函数与拷贝赋值操作符是不够的。前面已经学到,重载成员函数好用的原因是在成员函数与其实现之间是间接层次,而不是对象有内建的自身类的知识。
2024-10-31 07:30:00
509
原创 C++学习笔记----9、发现继承的技巧(六)---- 有趣且令人迷惑的继承问题(5)
如果不想修改实现,只想改变成员函数的访问标识符,优选的方式是简单添加一个using声明在重载类定义中用期望的访问标示符。继承类中重载的成员函数与基类相比可以有不同的缺省参数。在Shy中的talk()的受保护的版本恰当地重载的Gregarious::talk()成员函数。它证明了使成员函数在继承类中受保护确实重载了成员函数(因为重载类版本被正确调用),但是它也证明如果基类使其为公共的,无法整体加强受保护的访问。唯一有用的修改成员函数访问标示符的方式是通过提供限制更小的访问符给到受保护的成员函数。
2024-10-30 07:33:20
829
原创 C++学习笔记----9、发现继承的技巧(六)---- 有趣且令人迷惑的继承问题(4)
记住,例如,Java与C#只是允许重载公共的与受保护的成员函数,私有的成员函数不行。例如,下面的类是汽车模拟器的一部分,预测了基于它的油箱与所剩的燃料的英里数。getMilesLeft()成员函数执行了基于它自身的两个成员函数:getGallonsLeft()是公共的,和getMilesperGallon()是私有的,的结果的计算。通过重载这个私有的成员函数,新的类完全改变了基类中既有的,没有改变的,公共的成员函数的行为。注意:重载私有的与受保护的成员函数是一个好的方式来改变类的某些属性而不需要大的改造。
2024-10-29 07:32:21
637
原创 C++学习笔记----9、发现继承的技巧(六)---- 有趣且令人迷惑的继承问题(3)
在c++中,可以使用一个对象调用一个static成员函数,但是因为成员函数是static,它没有this指针,也不能访问对象自身,所以与用类名调用是等价的。注意:static成员函数用它被定义的类的名字圈定,但是它们不是应用于特定对象的成员函数。如果有一个继承类中的static成员函数与基类中的static成员函数一样,实际上拥有的是两个独立的成员函数。它调用了Derived中的继承Base的构造函数。因为static成员函数属于它的类,在两个不同的类上调用同样名字的成员函数实际上调用的是各自的成员函数。
2024-10-28 07:26:34
533
原创 C++学习笔记----9、发现继承的技巧(六)---- 有趣且令人迷惑的继承问题(2)
Derived类可以定义与在基类中继承的构造函数同样的参数列表的构造函数。然而,因为Derived类用单独的float类型的参数定义它自己的构造函数,使用单独的float类型的参数的基类中继承的构造函数就被隐藏了。例如,如下的Derived类尝试继承Based1与Base2的所有构造函数,对于基于float的构造函数就产生了不确定性。这个技术使用了using声明来显式包含了继承类中的成员函数的基类定义。只用提供的Base构造函数就可以构建一个Base对象,或者是缺省的构造函数,或者是接受int的构造函数。
2024-10-27 07:57:56
1048
原创 C++学习笔记----9、发现继承的技巧(六)---- 有趣且令人迷惑的继承问题(1)
扩展类为许多问题打开了潘多拉魔盒。类的什么特点可以或不可以修改?什么是非公共继承?什么是虚的基类?这些问题,可能更多,会在接下来进行讨论。
2024-10-26 08:04:02
922
原创 C++学习笔记----9、发现继承的技巧(五)---- 多重继承(2)
DogBird类仍然需要显式指出哪个父类的eat()成员函数被使用,但是Dog与Bird产生的任何不明确都有同样的成员函数,而不是因为它们都继承自同样的类。这种层次结构的类类型在c++中是允许的,但名字不明确仍会产生。例如,如果Animal类有一个公共的成员函数叫做sleep(),该成员函数在DogBird对象上不能被调用,因为编译器不知道到底是去调用Dog还是Bird继承的版本。使用这些“钻石形状”的类层次结构的最好的方式是使最顶部的类是一个所有成员函数声明为干净的virtual的抽象类。
2024-10-25 07:25:14
730
原创 C++学习笔记----9、发现继承的技巧(五)---- 多重继承(1)
我们前面提到过,多重继承常被认为是面向对象编程中复杂且没有必要的部分。这就仁者见仁,智者见智了,留给大家去评判。本节解释c++中的多重继承。
2024-10-24 07:34:00
823
原创 C++学习笔记----9、发现继承的技巧(四)---- 多态继承(3)
这意味着下面的代码将一个double单元格到一个string单元格是可以的,即使只提供了两个operator+的实现:一个是两个double单元格相加,一个是两个string单元格相加。首先,除了提升的设计,还缺失一个属性:从一个单元格类型转换为另一个的能力。与拷贝构造函数外观类似,但是它不是同一个类的对象的引用,而是一个同胞类的对象的引用。从面向对象设计的角度,新的SpreadsheetCell层次结构的实现确定是一次提升。其次,怎么实现单元格的重载的操作符是一个有趣的总是,有几种可能的方法。
2024-10-23 07:33:34
1226
原创 C++学习笔记----9、发现继承的技巧(四)---- 多态继承(2)
StringSpreadsheetCell类定义在了叫做string_spreadsheet_cell的模块中。写StringSpreadsheetCell类定义的第一步是继承SpreadsheetCell。为了做到这一点,spreadsheet_cell模块应该被import进来。下一步,继承的干净的virtual成员函数被重载,这一次没有被设置为0.最后,string单元格添加 一个私有的数据成员,m_value,它保存了真实的单元格数据。
2024-10-22 07:28:18
879
原创 C++学习笔记----9、发现继承的技巧(四)---- 多态继承(1)
现在理解了继承类与父类的关系,可以在最强大的场景--多态上使用继承了。以前我们讨论过多态允许用通用父类交换使用对象,在使用父类的地方使用本尊。
2024-10-21 07:29:30
896
原创 C++学习笔记----9、发现继承的技巧(三)---- 尊重父类(2)
如果函数应该在不同的继承类上工作,所有从Based继承的,会找一个使用多态的解决方案,这个我们后面再讨论。如果presumptuous()的作者也去写调用presumptuous()的代码,可能一切都没有问题,虽然很丑,因为作者知道函数椟要Derived*类型的参数。例如,一个重载的成员函数会保持基类实现的行为,加上其它的一些。然而,这样是不灵的,因为,在c++命名解析规则之下,它首先解析的是本地范围,然后解析类范围,以调用MyWeatherPrediction::getTemperature()结束。
2024-10-20 07:29:43
861
原创 C++学习笔记----9、发现继承的技巧(三)---- 尊重父类(1)
当写继承类的时候,需要清楚父类与子类之间的交互。像生成顺序,构造函数链,以及转化都可以是问题的根源。
2024-10-19 08:05:00
1182
原创 C++学习笔记----9、发现继承的技巧(二)---- 重用目的的继承
现在你对继承的基本语法已经比较熟悉了,是时候探索继承是c++语言中重要属性的一个主要原因了。继承是一个装备允许你平衡既有代码。本节会举出基于代码重用目的的继承的例子。
2024-10-18 07:30:16
1270
原创 C++学习笔记----9、发现继承的技巧(一)---- 使用继承构建类(5)
在大部分情况下对性能的影响是微乎其微的,但是c++的设计者认为,至少在当时是这样,让程序员自己去决定其性能影响是必要的,会更好一些。然而,在今天的CPU的情况下,性能的影响是以纳秒计的,对于未来的CPU影响会更小。本章的例子中没有这样做,是由于保持准确到位的考虑。同样地,如果继承类有在类实例被破坏时自动删除的成员,例如std::unique_ptr,那么如果析构函数不被调用的话,那些成员也不会被删除。如下代码所示,如果它是non-virtual的话,是很容易的“糊弄”编译器,让它忽略对析构函数的调用的。
2024-10-17 07:30:31
1294
原创 C++学习笔记----9、发现继承的技巧(一)---- 使用继承构建类(4)
用这种方式,当在一个对应对象的指针或引用上调用一个成员函数时,它的vtable指南就跟随着,恰当版本的成员函数基于运行时对象的实际类型执行。没有这个关键字,可能会意外在继承类中而不是重载基类中的成员函数生成一个新的(virtual)成员函数,而有效地隐藏了基类中的成员函数。该Derived的定义生成一个编译错误,因为带上override关键字,你的意思就是someFunction()是要重载基类的一个成员函数,而基类没有someFunction()接受整数,只有一个接受double。
2024-10-16 07:29:06
1453
原创 C++学习笔记----9、发现继承的技巧(一)---- 使用继承构建类(3)
不管你是有一个Derived或Base引用或者是指向Derived的指针,盒子是不会变的--只是有了一个新的访问的方式。例如,如果你有一个Base引用指向了一个实际是Derived的对象,调用了someFunction()实际上调用了继承类的版本,下面会展示。其它的成员函数someFunction(),从Base继承,在继承类中的表现与在基类中完全一致。一旦成员函数或析构函数被标记为virtual,对于所有的继承类它都是virtual的,即使在继承类中移除virtual关键字也改变不了。
2024-10-15 07:27:13
1045
空空如也
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人