目录
成员初始化列表语法
01)问题的提出: 对于一个Queue类的声明(不存在问题,注意在类中声明一个常量): class Queue { private: int items; const int qsize; //常量 pubilc: Queue(int qs); //构造函数 ... }; Queue类构造函数的定义(存在问题,因为不能给常量qsize赋值): Queue::Queue(int qs) { items = 0; qsize = 0; //不合法! } 02)解决的方法:使用成员初始化列表 Classy::Classy(int n,int m) : mem1(n), mem2(0), mem3(n*m+2) { ... } 以上将n的值赋给mem1,将0赋给mem2,将n*m+2的值赋给mem3。从概念上说,这些初始化构造是在对象创建时完成的,此时还未执行 大括号中的任何代码。请注意一下几点: A 成员初始化列表只能用于构造函数 B 必须用这种格式来初始化非静态const数据成员 C 必须用这种格式来初始化引用数据成员 D 不能讲成员初始化列表用于构造函数之外的其他类方法 E 成员初始化列表的括号方式也可以用于常规舒适化,可以将下述代码: int games = 162; double talk = 2.71828; 替换为: int games(162); double talk(2.71828); 03)C++11的类内初始化: class Classy { int mem1 = 10; const int mem2 = 20; }; 与在构造函数中使用成员初始化列表等价: Classy::Classy() : mem1(10), mem2(20) { ... } 也可以将初始值替换为其他的数值: Classy::Classy(int n) : mem1(n) { ... } //此时mem1=n, mem2=20
派生类
01)加入有一个类TableTennisPlayer类,在这个类的基础上派生出一个类RatedPlayer,方法如下 class RatedPlayer : public TableTennisPlayer //公有继承方式(public) TableTennisPlayer为基类,RatedPlayer 为派生类 { private: ... public: ... }; 02)上述语句是写在h文件中的;冒号指出RatedPlayer是派生类,TableTennisPlayer是基类 派生类对象储存了基类的数据成员(派生类继承了基类的实现); 派生类对象可以使用基类的方法(派生类继承了基类的接口); 派生类需要自己的构造函数,可以根据需要添加额外的数据成员和成员函数; 03)派生类不可以直接访问基类的私有数据成员,而必须通过基类方法进行访问,派生类构造函数必须使用基类构造函数, 因为要肯定是要创建派生类对象的,那么在创建派生类对象的时候,也必须加上基类的私有数据,所以派生类构造函数必须使用基类构造函数 对基类中的数据成员进行赋值; 创建培生类对象时,必须首先创建基类对象。从概念上说,这意味着基类对象应该在程序进入派生类构造函数之前被创建,那么我们使用 成员初始化列表的语法来完成这项工作, 例如: RatedPlayer::RatedPlayer(unsiged int r,const string & fn,const string & ln,bool ht) : TableTennisPlayer(fn,ln,ht) { rating = r; };//该语句是在对应的类cpp文件中 I 最后的TableTennisPlayer(fn,ln,ht)表示首先创建基类对象,并对该对象进行赋值,即将fn,ln,ht赋给基类对应的参数 II 在主函数文件中将可以执行如下代码: RatedPlayer ratedPlayer(1140,"Mallory","Duck",true); //创建派生类对象ratedPlayer RatedPlayer构造函数将把实参"Mallory","Duck",true赋给形参fn,ln,ht,然后将这些参数作为实参传递给TableTennisPlayer构造函数对象 并将数据"Mallory","Duck",true存储在该对象中。然后进入RatedPlayer构造函数体,完成ratedPlayer的创建 III 如果省略成员初始化列表,那么程序将会使用基类的默认构造函数,完成数据的赋值操作 04)来看第二个派生类构造函数代码: RatedPlayer::RatedPlayer(unsiged int r,const TableTennisPlayer & tp) : TableTennisPlayer(tp) { rating = r; } 由于tp的类型为指向TableTennisPlayer类对象的引用,因此将调用基类的复制构造函数。基类如果没有定义赋值构造函数,则编译器 将自动生成一个。由于该例程中没有使用动态内存分配(string成员确实是使用了动态内存分配,但是成员复制将使用string类的复制构造函数 来复制string成员),所以使用编译器自动生成的复制构造函数是可行的。 05)如果愿意,也可以使用成员初始化列表的方法对派生类成员进行初始化,在这时,应该使用成员名(rating),而不是类名,如下: RatedPlayer::RatedPlayer(unsiged int r,const TableTennisPlayer & tp) : TableTennisPlayer(tp),rating(r) {
} 06)有关派生类构造函数的要点如下: I 首先要创建基类对象; II 派生类构造函数应该通过成员初始化列表将基类信息传递给基类构造函数; III 派生类构造函数应初始化派生类新增的数据成员; IV 创建派生类对象时,程序首先调用基类构造函数,然后再调用派生类构造函数。基类构造函数负责初始化继承的数据成员;派生类 构造函数负责初始化新增的数据成员。派生类构造函数总是调用一个基类构造函数,可以使用成员初始化列表的方法指明要使用的 基类构造函数,否则将使用默认的基类构造函数; IIV 派生类对象过期时,程序将首先调用派生类析构函数,然后再调用基类析构函数; IIIV 初始化成员列表只能用于构造函数。
1 /* 派生类 */
2 /*
3 01)加入有一个类TableTennisPlayer类,在这个类的基础上派生出一个类RatedPlayer,方法如下
4 class RatedPlayer : TableTennisPlayer
5 {
6 private:
7 ...
8 public:
9 ...
10 };
11 02)上述语句是写在h文件中的;冒号指出RatedPlayer是派生类,TableTennisPlayer是基类
12 派生类对象储存了基类的数据成员(派生类继承了基类的实现);
13 派生类对象可以使用基类的方法(派生类继承了基类的接口);
14 派生类需要自己的构造函数,可以根据需要添加额外的数据成员和成员函数;
15 03)派生类不可以直接访问基类的私有数据成员,而必须通过基类方法进行访问,派生类构造函数必须使用基类构造函数,
16 因为要肯定是要创建派生类对象的,那么在创建派生类对象的时候,也必须加上基类的私有数据,所以派生类构造函数必须使用基类构造函数
17 对基类中的数据成员进行赋值;
18 创建培生类对象时,必须首先创建基类对象。从概念上说,这意味着基类对象应该在程序进入派生类构造函数之前被创建,那么我们使用
19 成员初始化列表的语法来完成这项工作,例如:
20 RatedPlayer::RatedPlayer(unsiged int r,const string & fn,const string & ln,bool ht) : TableTennisPlayer(fn,ln,ht)
21 {
22 rating = r;
23 };//该语句是在对应的类cpp文件中
24 I 最后的TableTennisPlayer(fn,ln,ht)表示首先创建基类对象,并对该对象进行赋值,即将fn,ln,ht赋给基类对应的参数
25 II 在主函数文件中将可以执行如下代码:
26 RatedPlayer ratedPlayer(1140,"Mallory","Duck",true); //创建派生类对象ratedPlayer
27 RatedPlayer构造函数将把实参"Mallory","Duck",true赋给形参fn,ln,ht,然后将这些参数作为实参传递给TableTennisPlayer构造函数对象
28 并将数据"Mallory","Duck",true存储在该对象中。然后进入RatedPlayer构造函数体,完成ratedPlayer的创建
29 III 如果省略成员初始化列表,那么程序将会使用基类的默认构造函数,完成数据的赋值操作
30 04)来看第二个派生类构造函数代码:
31 RatedPlayer::RatedPlayer(unsiged int r,const TableTennisPlayer & tp) : TableTennisPlayer(tp)
32 {
33 rating = r;
34 }
35 由于tp的类型为指向TableTennisPlayer类对象的引用,因此将调用基类的复制构造函数。基类如果没有定义赋值构造函数,则编译器
36 将自动生成一个。由于该例程中没有使用动态内存分配(string成员确实是使用了动态内存分配,但是成员复制将使用string类的复制构造函数
37 来复制string成员),所以使用编译器自动生成的复制构造函数是可行的。
38 05)如果愿意,也可以使用成员初始化列表的方法对派生类成员进行初始化,在这时,应该使用成员名(rating),而不是类名,如下:
39 RatedPlayer::RatedPlayer(unsiged int r,const TableTennisPlayer & tp) : TableTennisPlayer(tp),rating(r)
40 {
41
42 }
43 06)有关派生类构造函数的要点如下:
44 I 首先要创建基类对象;
45 II 派生类构造函数应该通过成员初始化列表将基类信息传递给基类构造函数;
46 III 派生类构造函数应初始化派生类新增的数据成员;
47 IV 创建派生类对象时,程序首先调用基类构造函数,然后再调用派生类构造函数。基类构造函数负责初始化继承的数据成员;派生类
48 构造函数负责初始化新增的数据成员。派生类构造函数总是调用一个基类构造函数,可以使用成员初始化列表的方法指明要使用的
49 基类构造函数,否则将使用默认的基类构造函数;
50 IIV 派生类对象过期时,程序将首先调用派生类析构函数,然后再调用基类析构函数;
51 IIIV 初始化成员列表只能用于构造函数。
52 */
53
54 #ifndef TABTENN1_H_
55 #define TABTENN1_H_
56
57 #include <string >
58
59 using std::string ;
60
61 /* 基类的声明 */
62 class TableTennisPlayer
63 {
64 private :
65 string firstname;
66 string lastname;
67 bool hasTable; // 是否有桌子
68 public :
69 TableTennisPlayer(const string & fn = " none " ,const string & ln = " none " , bool ht = false ); // 带默认参数的构造函数
70 // 由于"none"是const char类型的,故要fn的类型也要为const类型
71 void ShowName() const ; // 最后一个const表示不可以对对象中的数据进行修改
72 bool HasTable() const { return hasTable; } // 返回私有数据,进而可以使对象可以访问私有数据
73 void ResetTable(bool v) { hasTable = v; } // 对是否有球桌进行重新选择
74 };
75 /* 派生类 */
76 class RatedPlayer : public TableTennisPlayer // 表明TableTennisPlayer是基类,RatedPlayer是派生类
77 {
78 private :
79 unsigned int rating; // 派生类新增的数据成员
80 public :
81 RatedPlayer(unsigned int r = 0 , const string & fn = " none " , const string & ln = " none " , bool ht = false ); // RatedPlayer构造函数,以具体数据的方式将数据传递给基类构造函数
82 RatedPlayer(unsigned int r, const TableTennisPlayer & tp); // 以指向基类对象的引用作为参数传递给基类构造函数
83 unsigned int Rating() const { return rating; } // 返回私有数据,进而可以使对象可以访问私有数据
84 void ResetRating(unsigned int r) { rating = r; } // 派生类新增的类方法
85 };
86
87
88 #endif
tabletenn1.h
1 // tabletenn1.cpp
2
3 #include " tabletenn1.h "
4 #include <iostream>
5
6 using std::cout;
7 using std::endl;
8
9 /* 基类构造函数(使用初始化成员列表方法初始化数据成员)
10 01)注意啦,构造函数前也是要加类限定符TableTennisPlayer::的好不啦!!!
11 02)注意在h文件中的默认参数是不可以带过来的啦!!!
12 */
13 TableTennisPlayer::TableTennisPlayer(const string & fn, const string & ln, bool ht)
14 :firstname(fn), lastname(ln), hasTable(ht) {}
15 // 将fn赋值给私有数据firstname,ln赋值给lastname,ht赋值给hasTable,函数体为空
16
17 void TableTennisPlayer::ShowName() const
18 {
19 cout << lastname << " , " << firstname;
20 }
21
22 /* 派生类构造函数 */
23 RatedPlayer::RatedPlayer(unsigned int r, const string & fn, const string & ln, bool ht)
24 :TableTennisPlayer(fn,ln,ht)
25 {
26 rating = r;
27 }
28 // 首先使用基类构造函数TableTennisPlayer(const string & fn = "none", const string & ln = "none", bool ht = false)将数据传入基类中
29 // 然后创建派生类对象,最后执行rating = r;
30 RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp)
31 :TableTennisPlayer(tp),rating(r)
32 {
33
34 }
35 // 首先使用基类构造函数TableTennisPlayer(const string & fn = "none", const string & ln = "none", bool ht = false)将数据传入基类中
36 // tp是指向基类对象的形参,实参赋值给形参,会调用基类的复制构造函数,如果没有使用new(在基类中可以不定义复制构造函数),将使用基类的默认复制构造函数也是可以的
37 // 然后创建派生类对象,最后执行rating = r;
38 /*
39 也可以这样写:
40 RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp)
41 :TableTennisPlayer(tp)
42 {
43 rating = r;
44 }
45 */
tabletenn1.cpp
1 #include <iostream>
2 #include " tabletenn1.h "
3
4 int main()
5 {
6 using std::cout;
7 using std::endl;
8
9 TableTennisPlayer player1(" Tara " , " Boomadea " , false ); // 创建基类对象player1
10 RatedPlayer rplayer1(1140 , " Mallory " , " Duck " , true ); // 创建派生类对象rplayer1
11
12 player1.ShowName(); // 基类对象调用基类方法
13 if (player1.HasTable())
14 cout << " : has a table " << endl;
15 else
16 cout << " : hasn't a table " << endl;
17
18 rplayer1.ShowName(); // 派生类对象调用基类方法
19 if (rplayer1.HasTable())
20 cout << " : has a table " << endl;
21 else
22 cout << " : hasn't a table " << endl;
23
24 cout << " Name: " ;
25 rplayer1.ShowName();
26 cout << " ; Rating: " << rplayer1.Rating() << endl;
27
28 RatedPlayer rplayer2(1212 , player1); // 使用基类对象player1作为实参创建派生类对象rplayer2
29 cout << " Name: " ;
30 rplayer2.ShowName();
31 cout << " ; Rating: " << rplayer2.Rating() << endl;
32
33 system(" pause " );
34 return 0 ;
35
36 }
user_main.cpp
执行结果:
基类指针或引用可指向派生类对象
01)假如有基类TableTennisPlayer,派生类RatedPlayer RatedPlayer rplayer(1140,"Mallory", "Duck", true); //创建派生类对象rplayer TableTennisPlayer & rt = rplayer; //基类引用rt指向派生类对象rplayer TableTennisPlayer * pt = &rplayer; //基类指针指向派生类对象rplayer rt.name(); //调用基类方法name() 这里有隐藏的问题,全都是调用基类方法,如果想调用派生类方法怎么办?方法是使用虚函数 pt->name(); //调用基类方法name() 需要注意的是,基类指针或引用只能调用基类方法,而不能调用派生类中增加的方法 02)不可以将基类对象和地址赋给派生类引用和指针: TableTennisPlayer player("Mallory", "Duck", true); RatedPlayer & rr = player; //不合法 RatedPlayer * pr = &player; //不合法 03)可以将基类引用或者是指针作为形参,将基类或者是派生类对象作为实参: 假如有函数:show(const TableTennisPlayer & rt); //注意该函数的形参 TableTennisPlayer player("Mallory", "Duck", true); RatedPlayer rplayer(1140,"Mallory", "Duck", true); show(player); //合法 show(rplayer); //合法 04)同理指针: 假如有函数:show(const TableTennisPlayer * pt); TableTennisPlayer player("Mallory", "Duck", true); RatedPlayer rplayer(1140,"Mallory", "Duck", true); show(&player); //合法 show(&rplayer); //合法 05)可以使用派生类对象创建基类对象: RatedPlayer olaf1(1140,"Mallory", "Duck", true); TableTennisPlayer olaf2(olaf1); //这将调用基类的复制构造函数,TableTennisPlayer(const TableTennisPlayer &),即使基类中没有定义该函数,
//使用默认的复制构造函数也是可以的 06)可以将派生类对象赋值给基类对象: RatedPlayer olaf1(1140,"Mallory", "Duck", true); TableTennisPlayer winner; winner = olaf1; //将调用基类的重载赋值运算符TableTennisPlayer & operator=(const TableTennisPlayer &) const;
多态公有继承
01)正如上面01)中所说的: 假如有基类TableTennisPlayer,派生类RatedPlayer RatedPlayer rplayer(1140,"Mallory", "Duck", true); //创建派生类对象rplayer TableTennisPlayer & rt = rplayer; //基类引用rt指向派生类对象rplayer TableTennisPlayer * pt = &rplayer; //基类指针指向派生类对象rplayer rt.name(); //调用基类方法name() 这里都是调用的基类方法啊!!! pt->name(); //调用基类方法name() 这里都是调用的基类方法啊!!! 那么问题来了,我想用基类指针或引用调用派生类方法,该怎么办?方法是使用虚函数 02)在函数声明前加上关键字virtual即可,在定义不用加关键字virtual,如下声明: virtual void ViewAcct() const; //声明虚函数 03)那么下面介绍多态公有继承,假如ViewAcct()是在基类和派生类中分别定义的虚函数,则有如下示例: RatedPlayer rplayer(1140,"Mallory", "Duck", true); TableTennisPlayer player("Mallory", "Duck", true); TableTennisPlayer & b1_ref = rplayer; TableTennisPlayer & b2_ref = player; b1_ref.ViewAcct(); //根据引用指向的对象选择要使用的函数,调用派生类中的ViewAcct() b2_ref.ViewAcct(); //根据引用指向的对象选择要使用的函数,调用基类中的ViewAcct() 04)同理对于指针: RatedPlayer rplayer(1140,"Mallory", "Duck", true); TableTennisPlayer player("Mallory", "Duck", true); TableTennisPlayer * b1_ref = &rplayer; TableTennisPlayer * b2_ref = &player; b1_ref->ViewAcct(); //根据引用指向的对象选择要使用的函数,调用派生类中的ViewAcct() b2_ref->ViewAcct(); //根据引用指向的对象选择要使用的函数,调用基类中的ViewAcct()
总结:如果没有使用虚方法,那么调用基类还是派生类方法,要看指针的类型
如果使用了虚方法,那么调用基类还是派生类方法(的虚方法),要看指针指向的对象的类型,进而调用相应的虚方法
静态联编和动态联编 m9
01)如果在基类和派生类中没有将ViewAcct()声明为虚函数,则b1_ref->ViewAcct()将根据指针(b1_ref)类型调用对应的ViewAcct(); 总之编译器对非虚方法使用静态联编; 02)如果如果在基类和派生类中将ViewAcct()声明为虚函数,则b1_ref->ViewAcct()将根据对象(指针b1_ref指向的对象)类型调用对应的ViewAcct(); 总之编译器对虚方法使用动态联编。
复习类特殊成员函数
/* (01)复制构造函数 */ 假如Star是一个类,类Star的复制构造函数如下; Star(const Star &); 在下述情况下,将使用复制构造函数: 01)将新对象初始化为一个同类的对象; 02)按值传递给函数(当作实参); 03)函数按值返回对象(当作返回值); 04)编译器生成临时对象。
/* (02)赋值构造函数 */ 01)不要将赋值和初始化混淆了: Star sirius; //创建类对象sirius Star alpha = sirius; //初始化,调用复制构造函数 Star dogstar; dogstar = sirius; //赋值,调用赋值构造函数 02)Star类的赋值运算符(对=的重载)定义如下: Star & Star::operator=(const Star &) {...} //返回一个Star类对象引用 03)如果希望将字符串赋值给类对象,方法之一是定义下面的运算符: Star & Star::operator=(const char*) {...} 另一种方法是使用转换函数,但可能导致编译器出现混乱。‘
/* (03)构造函数 */ 构造函数不同于其他方法,因为他创建对象,而其他类方法是被现有的对象调用,这是构造函数 不被继承的原因之一。继承以为着派生类对象可以调用基类方法。
/* (04)按值传递对象与传递引用 */ 通常,编写使用对象做为参数的函数时,应按引用传递,而不是使用按值本身来传递。这样做的原因是 为了提高效率。按值传递对象涉及到生成临时拷贝,即调用复制构造函数生成对象副本,然后调用析构函数删除临时对象副本, 因此使用按值传递对象效率不高。 直接返回对象和按值传递对象类似:他们都生成对象的临时副本,从而效率不高; 返回引用和传递对象的引用类似;调用和被调用的函数都将对一个对象进行操作。 如果函数返回在函数中创建的临时对象,因为在函数结束时,临时对象会消失,因此应该返回对象(实际上返回的是该对象的副本), 而不是返回指向对象的引用。 如果是返回按照实参传递给该函数或者是调用该类方法的对象,则应该按引用返回对象,如: const Stock & Stock::topval(const Stock & s) { if(s.total_val > total_val) return s; //返回按照实参传递给该函数的对象 else *this; //返回调用该类方法的对象 }
/* (04)使用const */ const Stock & Stock::topval(const Stock & s) const //最后一个const相当于const Stock* this { if(s.total_val > total_val) return s; //返回按照实参传递给该函数的对象 else *this; //返回调用该类方法的对象 } 由于返回值s和*this均为const类型,所以在函数头中第一个const也是必须要加上的
/* (05)将派生类对象赋值给基类对象: 允许 */ Brass blips; //创建基类对象 BrassPlus snips("Rafe Plosh", 91191, 3993.19, 600.0, 0.12);//创建派生类对象 blips = snips; //实际调用方法为blips.operator=(snips),因此该句将调用基类的赋值运算符 snips做为一个实参传入基类的赋值运算符,对应的形参为Brass & s ,由于允许基类引用指向派生类对象,所以 此处实参赋值给形参是合理的。最终的效果是基类的赋值运算符忽略派生类中额外添加的数据,将属于基类中的数据 赋值给基类对象
但是将基类对象赋值给派生类对象是不允许的,例如: Brass gp("Rafe Plosh", 21234, 1200); BrassPlus temp; temp = gp; //不合法的,除非有转换构造函数BrassPlus(const Brass &); 此时实际调用方法为temp.operator=(gp),因此该句将调用派生类赋值运算符,在这时,gp作为实参, 对应的形参为BrassPlus & s,由于不可以将派生类引用指向基类对象,所以此时是不合法的。
抽象基类
01)抽象基类中至少要包含一个纯虚函数 ,纯虚函数是在虚函数(在函数前加上关键字virtual)的基础上,在声明的最后加上 =0 即可 例如:virtual double Area() const = 0; //声明一个纯虚函数 virtual Move(int x,y) = 0; //声明一个纯虚函数 纯虚函数可以没有定义,也可以有定义,如果有定义的话,在定义中是不需要加=0的 02)提出抽象基类目的是为了出相处两个或更多类的共性,将这些共性放到一个类(抽象类)中做为基类,然后再去派生类 03)当类声明中包含纯虚函数时,则不能创建该类的对象 ***** 04)在抽象类中声明的纯虚函数,在派生类中对应的声明为虚函数即可
1 #ifndef ACCTABC_H_
2 #define ACCTABC_H_
3 #include <iostream>
4 #include <string >
5
6 /* 抽象类 */
7 class AccABC
8 {
9 private :
10 std::string fullname;
11 long accNum;
12 double balance;
13 // 声明若干保护成员,派生类中的方法可以直接访问保护成员,除派生类意外的类或者是函数,均不可访问保护成员
14 // 且类方法是不可以直接在类外访问私有数据成员,但是在类内(比如类中方法的定义)类方法是可以访问私有数据的
15 protected :
16 struct Formatting // 声明一个结构体
17 {
18 std::ios_base::fmtflags flag;
19 std::streamsize pr;
20 };
21 const std::string & Fullname() const { return fullname; } // 直接在h文件中定义类方法
22 long AccNum() const { return accNum; }
六六分期app的软件客服如何联系?不知道吗?加qq群【895510560】即可!标题:六六分期
阅读:19242| 2023-10-27
今天小编告诉大家如何处理win10系统火狐flash插件总是崩溃的问题,可能很多用户都不知
阅读:10002| 2022-11-06
今天小编告诉大家如何对win10系统删除桌面回收站图标进行设置,可能很多用户都不知道
阅读:8332| 2022-11-06
今天小编告诉大家如何对win10系统电脑设置节能降温的设置方法,想必大家都遇到过需要
阅读:8702| 2022-11-06
我们在使用xp系统的过程中,经常需要对xp系统无线网络安装向导设置进行设置,可能很多
阅读:8648| 2022-11-06
今天小编告诉大家如何处理win7系统玩cf老是与主机连接不稳定的问题,可能很多用户都不
阅读:9674| 2022-11-06
电脑对日常生活的重要性小编就不多说了,可是一旦碰到win7系统设置cf烟雾头的问题,很
阅读:8633| 2022-11-06
我们在日常使用电脑的时候,有的小伙伴们可能在打开应用的时候会遇见提示应用程序无法
阅读:8007| 2022-11-06
今天小编告诉大家如何对win7系统打开vcf文件进行设置,可能很多用户都不知道怎么对win
阅读:8670| 2022-11-06
今天小编告诉大家如何对win10系统s4开启USB调试模式进行设置,可能很多用户都不知道怎
阅读:7541| 2022-11-06
请发表评论