自定义博客皮肤VIP专享

*博客头图:

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

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

博客底图:

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

栏目图:

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

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

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

原创 C++学习笔记----8、掌握类与对象(六)---- 操作符重载(1)

经常在对象上执行如相加,比较,文件传输等操作。例如,spreadsheet只有在可以在上面执行自述运算才有用,比如对整行的单元格求和。所有这些都可以通过重载操作符来完成。许多人发现操作符重载的语法复杂而令人迷惑。至少一开始是这样。真实情况是想让事情更简单。在本节你会发现,那并不意味着写类时更简单,只是使用类时更简单。关键点是让新类与内建像int与double这样的类型一样简单:使用+来使对象相加要比记住不管是add()或是sum()这样的成员函数名要容易多了。注意:作为服务给客户提供类的操作符重载。

2024-10-08 07:26:16 818

原创 C++学习笔记----8、掌握类与对象(五)---- 嵌套类与类中枚举

现在,Cell类定义在了Spreadsheet类的内部,所以在Spreadsheet类之外对Cell的任何动作,必须给出Spreadsheet::的范围名。可以在另一个类定义中提供一个类定义。例如,你可能决定 让SpreadsheetCell类成为Spreadsheet类的一部分。由于它变成了Spreadsheet类的一部分,你可能也会重新将其命名为Cell。在类中声明的任何东西都在类范围内。在Spreadsheet类内部直接完整地定义嵌套Cell类舍不得Spreadsheet类定义有一点儿臃肿。

2024-10-07 08:10:49 314

原创 C++学习笔记----8、掌握类与对象(四)---- 不同类型的数据成员(2)

Spreadsheet与SpreadsheetCell是伟大的,但是不是它们自己就能成为有用的应用程序。需要代码去控制整个spreadsheet程序,可以将其打包成一个SpreadsheetApplication类。最后,引用数据成员可以被标记为const。推荐在这种情况下使用引用而不是指针,因为Spreadsheet应该总是指向SpreadsheetApplication。不推荐用这种方式将Spreadsheet与SpreadsheetApplication类耦合在一起,应该使用例如MVC的模式。

2024-10-06 07:56:37 360

原创 C++学习笔记----8、掌握类与对象(四)---- 不同类型的数据成员(1)

c++对于数据成员给了你许多选项。除了在类中声明简单的数据成员,可以生成静态数据成员供所有类的对象共享,const成员,引用成员,reference-to-const成员,等等。本节我们解释一下这些不同类型的数据成员的细节。

2024-10-05 07:39:03 872

原创 C++学习笔记----8、掌握类与对象(三)---- CONSTEXPR与CONSTEVAL

在现代c++中,在编译时而不是运行时容易地执行计算是可能的。这提高了代码的运行时性能。有两个重要的关键字用于完成这个:constexpr与consteval。

2024-10-04 07:39:13 1238

原创 C++学习笔记----8、掌握类与对象(二)---- 成员函数的更多知识(3)

注意:高级的c++编译器不再要求将Inline成员函数的定义与类的定义放置在同一文件中,例如,Microsoft Visual C++支持连接时代码生成(LTCG),它会自动地使得小的函数体inline,即使没有声明为inline并且没有与类定义在同一个文件中。在前面的章节中,只有&或者&&在成员函数签名的末尾,但是,它是很容易被忽视的啊,比如,当同事检查你的代码时。注意:如果在调试器中对内联函数使用单步跟踪,有些高级的c++调试器会跳到内联函数的实际源代码中,给你事实上函数调用的代码是内联的印象。

2024-10-03 07:43:28 989

原创 C++学习笔记----8、掌握类与对象(二)---- 成员函数的更多知识(2)

如果你有一个const对象编译器调用const成员函数,如果你有一个non-cost对象它会调用non-const重载。写这校招的两个重载成员函数可能会带来代码重复,因为,通常情况下,const与non-const重载的实现是一样的。例如,Spreadsheet类有一个叫做getCellAt()的成员函数返回一个reference-to-non-const给到SpreadsheetCell。接着,调用getCellAt()的const重载,它返回一个const SpreadsheetCell&。

2024-10-02 07:32:33 1209

原创 C++学习笔记----8、掌握类与对象(二)---- 成员函数的更多知识(1)

c++提供了成员函数的更多选择,我们来进行更多解密。

2024-10-01 07:34:10 964

原创 C++学习笔记----8、掌握类与对象(一)---- 对象中的动态内存分配(7)

到现在为止,建议都是对于非原始函数参数使用reference-to-const参数以避免不必要的昂贵的对于传递给函数的参数的拷贝。既然copy/move省略无法再使用,编译器的下一个选项就是使用move语法,如果对象支持的话,如果不支持,就会使用copy的语法。注意:对于函数要拷贝的参数使用pass-by-value,但是只在参数是一个支持move语法的类型时,并且不需要参数有多态行为的时候。另一方面,下面的代码段使用临时变量调用setData(),它触发了一个对于右值引用的setData()重载的调用。

2024-09-30 07:25:36 752

原创 C++学习笔记----8、掌握类与对象(一)---- 对象中的动态内存分配(6)

每行的右边的数字不是输出的一部分,加上它纯粹是为了继续讨论的方便,因为它指的是确定的行数。该输出以及下面的讨论都是基于使用move-and-swqp习语来实现其move操作的Spreadsheet类的版本,在Microsoft Visual C++ 2022编译环境生成的代码构建。如果Spreadsheet类没有实现move的语法,所有对move构造函数与move赋值操作符的调用都会被替换成对拷贝构造函数与拷贝赋值操作符的调用。另一个Spreadsheet对象生成,s2,使用正常的构造函数(10)。

2024-09-29 07:34:10 963

原创 C++学习笔记----8、掌握类与对象(一)---- 对象中的动态内存分配(5)

编译器在并且只有在类没有用户声明的拷贝构造函数,拷贝赋值操作符,move赋值操作符,或者析构函数的情况下才会自动为类生成一个缺省的move构造函数。在且只有在类没有用户声明的拷贝构造函数,move构造函数,拷贝赋值操作符,或者析构函数的情况下才会为类生成一个缺省的move赋值操作符。注意这个实现里包含了一个在Move赋值操作符中的自我赋值的检测。警告:当你声明一个或多个特殊成员函数(析构函数,拷贝构造函数,move构造函数,拷贝赋值操作符,以及move赋值操作符)时,推荐全部声明这些函数,这被叫做五规则。

2024-09-28 07:32:41 1104

原创 C++学习笔记----8、掌握类与对象(一)---- 对象中的动态内存分配(4)

如果移除掉接收左值引用的handleMessage()函数,用一个命名变量调用handleMessage(),比如handleMessage(b)就会出现编译错误,因为右值引用参数(string&&)不会连接上左值(b)。正常情况下,一个临时对象可以看作是一个const type&,但是当有一个使用右值引用的函数重载时,一个临时对象可以被解释成那个重载。helper()函数需要一个右值引用,而handleMessage()传递了message,它有一个名字,所以它是一个左值,导致一个编译错误。

2024-09-27 07:30:13 1329

原创 C++学习笔记----8、掌握类与对象(一)---- 对象中的动态内存分配(3)

为了实现这样的一个例外安全的赋值操作符,copy-and-swap就要用到了。例如,假设内存成功释放,m_width与m_height进行了正确设置,但是在分配内存的循环中抛出了一个例外。首先,对右边的进行copy,叫做temp。这种模式是推荐的实现赋值操作符的方式,因为它保证了强大的例外安全,意味着如果例外发生,当前Spreadsheet对象的状态保持不变。当你不使用copy-and-swap习语来实现赋值操作符的时候,为了效率,有时也为了正确性,在赋值操作符的第一行代码通常会检查自我赋值。

2024-09-26 07:34:55 1123

原创 C++学习笔记----8、掌握类与对象(一)---- 对象中的动态内存分配(2)

更糟的是,当printSpreadsheet()函数退出时,s的析构函数被调用,就会释放m_cells指向的内存。就会造成s1中的m_cells不再指向有效的内存,如下图所示。Spreadsheet的一个shallow拷贝给出目标对象一个m_cells指针的拷贝,但不是其内部数据的拷贝。现在,不光s1与s2中的m_cells指针指向同样的内存,前面s1指向的m_cells的内存也变成了孤儿。不需要删除任何既有的m_cells,因为这是一个拷贝构造函数,因此在this对象中还不存在m_cells。

2024-09-25 07:41:09 941

原创 C++学习笔记----8、掌握类与对象(一)---- 对象中的动态内存分配(1)

在生产环境的代码中,应该使用标准c++容器,像std::vector,会大幅简化Spreadsheet的实现,但那样的话,你就无法学习如何使用原始指针正确处理动态内存。我们先开始,Spreadsheet只是一个简单的SpreadsheetCell的二维数组,在Spreadsheet中带有成员函数来设置与访问其特定位置的cell。c++允许类声明为其它类,其它类的成员函数,或者非成员函数为friend。一个类,成员函数,或者函数不能声明自己为一些其它类的friend来获得那个类的非public成员的访问。

2024-09-24 07:38:18 1149

原创 C++学习笔记----7、使用类与对象获得高性能(二)---- 理解对象生命周期(8)

如果在构造函数初始化器中省略了一个数据成员,编译器就会对其执行缺省初始化(调用对象的缺省构造函数),在执行构造函数体的代码之前。然而,更好的是,编译器可以自由地(通常也这样要求)实现拷贝省音来优化掉大量的拷贝操作或者移动操作,当返回值的时候。其实质是,如果看起来像是一个声明,就要用拷贝构造函数,如果看起来像赋值语句,就要用赋值操作符来处理。但是,现在s2使得拷贝构造函数被调用,而不是赋值操作符。然而,当给拷贝构造函数体的数据成员赋值时,使用的是赋值操作符,而不是拷贝构造函数,因为它们已经初始化过了。

2024-09-23 07:29:24 789

原创 C++学习笔记----7、使用类与对象获得高性能(二)---- 理解对象生命周期(7)

赋值操作符的实现与拷贝构造函数类似,但也有一些重要的差异。首先,拷贝构造函数只在初始化时调用,所以目标对象还没有有效值。赋值操作符可以覆写对象的当前值。这种考虑直到在对象中动态分配了如内存这样的资源时才会出现,以后会详细讨论。其次,在C++中给对象自身赋值是合法的。赋值操作符需要把自我赋值的可能性考虑进去。在SpreadsheetCell类中,这不重要,因为它只有一个原始的数据类型double。然而,当类中有动态分配内存或者其它资源时,首先要考虑的就是自我赋值问题了,这个我们会在以后讨论。

2024-09-22 07:28:34 766

原创 C++学习笔记----7、使用类与对象获得高性能(二)---- 理解对象生命周期(6)

编译器可以自动为每个类生成一个缺省构造函数和一个拷贝构造函数。然而,编译器自动生成的构造函数依赖于根据下表的规则定义的构造函数:如果你定义的是...然后编译器生成的是...你可以生成一个对象...[无构造函数]一个缺省的构造函数一个拷贝构造函数无参:SpreadsheetCell a;作为一个拷贝:SpreadsheetCell b{a};只有一个缺省构造函数一个拷贝构造函数无参:SpreadsheetCell a;作为一个拷贝:SpreadsheetCell b{a};

2024-09-21 07:46:14 802

原创 C++学习笔记----7、使用类与对象获得高性能(二)---- 理解对象生命周期(5)

注意:当类不仅拥有初始化器列表构造函数,还具有另一个单个参数的构造函数时,就要注意去调用正确的那一个了。调用初始化器列表构造函数是使用大括号的初始化器,{},而调用另外的单独参数的构造函数是使用括号,()。下面的类演示了其用法。如果真的有隐式转化的使用情况,可以将构造函数标记为explicit(false),这样做的目的就是告诉用户你的类就是隐式转化的,是被明明白白允许的。在初始化器构造函数中可以使用基于范围的for循环来访问初始化器列表的元素,使用size()成员函数获得初始化器列表的元素数量。

2024-09-20 07:43:04 1273

原创 C++学习笔记----7、使用类与对象获得高性能(二)---- 理解对象生命周期(4)

当然了,从技术上来说,在拷贝构造函数中可以做你想做的任何事,但是,执行期待的行为并初始化新的对象为原来对象的一个拷贝是个不错的主意。SpreadsheetCell类的doubleToString()成员函数总是返回string值,因为该成员函数的实现生成了一个本地的string对象,在返回给调用者的成员函数的结尾。当传递对象引用时,使用对象引用的函数可能会改变原来的对象。注意:如果拥有数据成员的类有一个删除了的或者私有的拷贝构造函数,该类的拷贝构造函数也自动删除了,即使你显式地给了一个缺省的情况下也不行。

2024-09-19 07:29:00 967

原创 C++学习笔记----7、使用类与对象获得高性能(二)---- 理解对象生命周期(3)

如果类拥有数据成员的类的对象有默认的构造函数,在构造函数初始化器中就不必显式地初始化对象。例如,如果有一个std::string的数据成员,其缺省构造函数将其初始化为空的字符串,所以在构造函数初始化器中将其初始化为””就显得多余了。对于构造函数初始化器有一个警告:要在类定义中以出现的顺序初始化数据成员,而不是在构造函数初始化器中的顺序!然而,有些数据类型一定要在构造函数初始化器中初始化,或者使用类内初始化器。警告:构造函数初始化器依其在类中的定义顺序初始化其数据成员,而不是在构造函数初始化器列表中的顺序。

2024-09-18 12:29:40 1114

原创 C++学习笔记----7、使用类与对象获得高性能(二)---- 理解对象生命周期(2)

然而,因为显式缺省的default关键字的使用,编译器仍然生成了一个标准的编译器生成的缺省构造函数。显式缺省的缺省构造函数的对立面也是可能的,叫做显式删除的缺省构造函数。在c++11之前,如果类需要几个接受参数的显式的构造函数,也需要一个什么也不做的缺省构造函数,你必须显式地写出自己的空的如前所示的缺省构造函数。下面的定义除了加上了一个显式的接受double的构造函数之外与前面的定义一样,它依然没有显式地声明缺省的构造函数。注意:如果类成员有删除的缺省构造函数,类的缺省构造函数也会自动删除。

2024-09-17 08:15:53 1291

原创 C++学习笔记----7、使用类与对象获得高性能(二)---- 理解对象生命周期(1)

对象的生命周期包含三个活动:生成,解体,与赋值。理解如何以及什么时候生成、解体以及赋值对象,以及如何客户化其行为非常重要。

2024-09-16 07:49:37 1073

原创 C++学习笔记----7、使用类与对象获得高性能(一)---- 书写类(3)

setValue()的第一个参数现在就是显式对象参数,通常叫做self,但是你可以使用任何你想使用的名字。self的类型以this关键字开头。每个正常的成员函数调用都会隐含地传递一个指针给到对象,它就是被可能我的天this的隐藏参数。从c++23开始,不用再依赖于编译器提供隐式的this参数,可以使用显式对象参数,通常是一个引用类型。下面的代码段用显式对象参数实现了前面的SpreadsheetCell的setValue()成员函数。也可以使用this指针来调用一个作为参数的对象的成员函数的指向对象的指针。

2024-09-15 08:09:24 668

原创 C++学习笔记----7、使用类与对象获得高性能(一)---- 书写类(2)

前面对SpreadsheetCell类的定义足以让你生成类的对象。然而,如果想调用setValue()或者getValue()成员函数,连接器就会抱怨这些函数没有定义。这是因为到目前为止,这些成员函数只有原型,而还没有实现。通常,类的定义会在模块接口文件。对于成员函数的定义,你有一个选择:可以在模块定义文件或者在模块实现文件。public:private:与头文件不同,c++模块中把成员函数定义放在模块接口文件中并没有什么不好。这个我们以后再讨论。

2024-09-14 12:38:30 996

原创 C++学习笔记----7、使用类与对象获得高性能(一)---- 书写类(1)

public:private:第一行指出这是一个叫做spreadsheet_cell的模块的定义。每一类的定义都是以class关键字开始后接类的名字。如果类定义在一个模块中,该类一定要import进来才可见,定义时一定要在class关键字之前加上export。类的定义就是声明并且以分号结束。类的定义通常在一个其名字的文件中。例如,SpreadsheetCell类定义在一个叫做SpreadsheetCell.cppm的文件中。有些编译器要求使用特定的扩展名;有些则不要求。

2024-09-13 19:40:25 1131

原创 C++学习笔记----6、内存管理(五)---- 智能指针(4)

在C++中还有一个与shared_ptr相关的智能指针叫做weak_ptr。weak_ptr可以包含一个被shared_ptr管理的资源的引用。weak_ptr自身不拥有资源,所以shared_ptr不被禁止释放资源。当weak_ptr被破坏时(如当其不在活动范围内),weak_ptr不破坏指向的资源;然而,它可以用于决定资源是否被相关shared_ptr释放。weak_ptr的构造函数要求shared_ptr或者另一个weak_ptr作为参数。

2024-09-12 20:10:59 975

原创 C++学习笔记----6、内存管理(五)---- 智能指针(3)

与指向特定类型的原始指针可以转化为不同类型的指针一样,shared_ptr保存特定的类型可以转化为一个另一种类型的shared_ptr。转化shared_ptr的函数是const_pointer_cast(),dynamic_pointer_cast(),static_pointer_cast()和reinterpret_pointer_cast()。前面简要提过,当拥有共享属主的智能指针,例如shared_ptr不在活动范围或者被重置时,只有它是最后指向的智能指针时才能释放其指向的资源。

2024-09-11 12:42:08 1220

原创 C++学习笔记----6、内存管理(五)---- 智能指针(2)

书接上回!make_unique()使用值初始化。例如,将初始类型初始化为0,对象为缺省构造。如果不需要这样的值初始化,例如,因为不管怎么样你都会覆写共初始值,你就可以省略值初始化,通过使用make_unique_for_overwrite()函数提高性能,该函数会使用缺省初始化值。对于初始类型,这意味着它们不会被初始化,会在内存中包含任何可能的值,而对象仍会是初始构造时的值。你也可以通过直接调用其构造函数来生成unique_ptr。

2024-09-10 12:32:10 956

原创 C++学习笔记----6、内存管理(五)---- 智能指针(1)

就像前面讨论到的,C++中的内存管理是错误与bug的罪恶之源。这许多的bug由于使用动态内存与指针遭遇不断上升。当你在程序中大量使用动态内存分配,在对象之间传递许多指针时,就很难记住对每一个指针只在正确的时间进行一次delete调用。弄错了的后果是很严重的:当你将动态分配的内存释放了多次,或者使用指向已经释放了内存的指针时,就会造成内存崩溃或者严重的运行时错误;而当你忘记对动态分配的内存进行释放时,又会造成内存渗露。智能指针会帮助你管理动态分配内存,是推荐的避免内存渗露的技巧。

2024-09-09 20:37:17 1117

原创 C++学习笔记----6、内存管理(四)---- 通常的内存陷阱(2)

内存渗露很难跟踪是因为你无法很容易地看着内存并且看到什么对象处于使用中,一开始在哪儿分配的内存。然而,是有程序可以为你做到这一点的。内存渗露检测工具有昂贵的专业软件包,也有免费下载的工具。如果你是在Microsoft Visual C++环境下工作,它的排错工具库有内建的对于内存渗露检测的支持。该内存检测默认没有打开,除非你生成了一个MFC项目。在其他项目中打开这个工具,需要在一开始包含下面三行代码。使用了#define的预处理宏,这个我们以后再讲。现在,只要使用这三行就行了。这三行的顺序不能调整。

2024-09-08 09:18:18 912

原创 C++学习笔记----6、内存管理(四)---- 通常的内存陷阱(1)

使用new/delete/new[]/delete[]处理动态内存以及底层内存操作是非常容易出错的。对于引起内存有关的问题还特别难以定位。每一个内存泄露与错误指针都有其细微差别。没有能够解决内存问题的银弹。我们就来谈一谈一些通常问题以及能够检测和解决的一些工具。

2024-09-07 09:48:50 763

原创 C++学习笔记----6、内存管理(三)---- 底层内存操作

C++相对于C的一个非常大的优势就是你不必太担心内存。如果你的代用到了对象,只需要确信每个类可以好好管理自己的内存。通过构造与析构函数,编译器通过告诉你什么时候去做来帮助你管理内存。在类内隐藏了对内存的管理在使用上带来了很大的不同,就像标准库类展示的那样。然而,对于一些应用或者遗留代码,你可能也会碰到需要在底层处理内存。不管是遗留代码、效率、排错或者是好奇,懂得一些对于原始字节的操作技巧还是很有帮助的。

2024-09-06 19:05:16 1442

原创 C++学习笔记----6、内存管理(二)---- 数组指针的双向性

你可能已经看到指针与数组之间的一些重叠。自由内存空间分配的数组由其第一个元素的指针进行访问。栈上的数组通过使用数组语法([])或者正常变量声明来访问。你还会看到的是,其重叠不仅如此,指针与数组有更复杂的关系。

2024-09-05 19:55:07 1245

原创 C++学习笔记----6、内存管理(一)---- 使用动态内存(4)

如果需要在运行时决定多维数组的维度,可以使用在自由内存空间上的数组。与一维动态分配的数组通过指针访问一样,多维动态分配的数组也可以通过指针访问。不同的地方在于在二维数组中,需要用一个指向指针的指针;在一个N维的数组中,需要N层的指针。一开始,好像正确的方式是声明并且分配一个动态分配的多维数组如下:

2024-09-04 18:46:36 1433

原创 C++学习笔记----6、内存管理(一)---- 使用动态内存(3)

例如,对于二维数据,有同样长的行,你可以考虑写(当然也可以重用)一个Matrix或者Table类模板,把内存分配/释放与用户访问元素算法隐藏下来,我们以后会专门讨论写类模板的细节。好的解决方案是分配一个单独的内存块,足够保存xDimension * yDimension个元素,用像x*yDimension + y的公式来访问(x,y)位置的元素。对于有些编译器,只有数组的第一个元素的析构函数会被调用,因为编译器只知道你在删除一个指向对象的指针,数组的其他元素就会变成孤儿对象。

2024-09-03 20:01:44 1123

原创 C++学习笔记----6、内存管理(一)---- 使用动态内存(2)

记住对每一个new[]的调用都应该对应地调用delete[],所以在这个例子中,createDocumentArray()的调用者很重要的一点就是要使用delete[]来清理返回的内存。别用它,在C中,realloc()用于通过分配新的内存空间来有效改变数组的大小,把旧的数据拷贝到新的位置,删除原来的内存块。例如,以下代码片断接收了从一个假想叫做askUserForNumberOfDocuments()的函数的一定数量的文件,使用这个结果生成Document对象的数组。还有一种版本的new,它不抛出异常。

2024-09-02 19:36:39 994

原创 C++学习笔记----6、内存管理(一)---- 使用动态内存(1)

当你使用现代结构,例如std::vector,std::string等等,从一开始到现在以及到未来,C++是一个安全的编程语言。该语言提供了许多的道路,路线以及红绿灯,比如C++核心指导,静态代码分析器来分析代码的正确性,等等。然而,C++依然允许你出轨。一个出轨的例子就是手动管理内存(分配与释放内存)。对于C++编程这种手动管理内存是一个特别容易出错的领域。为了写出高质量的C++程序,专业的C++程序员需要理解内存在底层是怎么工作的。

2024-09-01 15:58:35 928

原创 C++学习笔记----5、重用之设计(四)---- 设计一个成功的抽象与SOLID原则

经验与迭代对于好的抽象非常重要。真正好的设计的接口来自于经年的编写与使用其他的抽象。你也可以通过重要既存的、以标准设计模式形式存在的设计好的抽象来利用其他人的经年的编写与使用抽象。当你碰到其他的抽象,尝试记住什么起作用什么不起作用。在上周使用的Windows文件系统API有发现什么缺陷吗?如果你不使用同事的而要自己写网络包装器,会有什么不同吗?最好的接口鲜有一蹴而就,所以要保持迭代。把你的设计让你的同事看,寻求反馈。如果你的公司使用代码互查,在开始实现前做一个接口规范的检查。

2024-08-31 12:01:55 593

原创 C++学习笔记----5、重用之设计(三)---- 设计可用的接口(3)

通用接口可以应用于多种任务。如果你将应该通用的接口做成了只能为一个应用所用,那么对于其他目的则不可用。以下是几个要记住的指导思想。

2024-08-30 14:52:09 913

空空如也

空空如也

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

TA关注的人

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