zoukankan      html  css  js  c++  java
  • C++_基础2-复合数据类型

    C语言使用术语“派生类型”,C++对类关系使用术语“派生”。所以就改用“复合类型”。

     

    数组

    数组是一种数据格式,能够存储多个同类型的值。

    数组声明应指出以下三点:

           存储在每个元素中的值的类型;

           数组名;

           数组中的元素数;

    通用的声明格式: typeName arrayName[arraysize];

    声明中所有的值在编译时都是已知的。arraysize不能是变量,变量的值是在程序运行时设置的。C++可以使用new运算符来避开这种限制;

    数组的特性

           可以单独访问数组元素;

           方法是使用下标或索引来对元素进行编号;

           C++数组从0开始编号;

           C++使用带索引的方括号表示法来指定数组元素,称为随机访问;

           最后一个元素的索引比数组长度小1

    编译器不会检查数组下标是否有效。本着信任程序员的原则,程序员必须确保下标的有效性。下标无效会引发数组越界,可能会修改破坏数据或代码,后果很严重。

    数组的初始化

           只有在定义数组时才能使用初始化。

           例:int yamcosts[3]={20,30,5};

           不能将一个数组赋值给另一个数组;

           初始化数组时,提供的值可以少于数组的元素数目;

           也可以把数组元素都初始化为零,例:long totals[500]={0};

           如果初始化数组中的方括号[]为空,C++编译器将计算元素个数:

           例:short things[ ] ={1,5,3,8};

    C++11数组初始化方法

           可以使用大括号

     

    C++标准模板库(STL)提供了一种数组替代品——模板类vector,而C++新增了模板类array

    ======================================

    字符串

           字符串是存储在内存的连续字节中的一系列字符。C++处理字符串的方式有两种:第一种来自C语言,常被称为C-风格字符串。另一种是基于string类库的方法

           字符串可以存储在char数组中。其实字符串可以解释为以结尾的char类型数组。空字符被写作,其ASCII码为0,用来标记字符串的结尾。没有以结尾的char类型数组不是字符串,只是字符数组而已。

           第一种字符串初始化方法:

                  char dog[8] ={‘b’ , ‘e’, ‘a’, ‘u’, ‘x’, ‘’ };

           这种方法比较冗长乏味,还有更好的初始化方法,只需使用双引号括起来的字符串即可。这种字符串被称为字符串常量字符串字面值

                  char fish[] = “Bubbles”;

           双引号括起来的字符串隐式地包括结尾的空字符。另外各种C++输入工具通过键盘输入,将字符串读入到char数组中,将自动在结尾加上字符。

           当然应该确保数组足够大,能够存储字符串中所有字符——包括空字符。

           数组的长度比字符串长没有什么坏处,只是会浪费一些空间而已。因为处理字符串的函数根据空字符的位置,而不是数组的长度来处理。

           在确定存储字符串所需的最短数组时,别忘了将结尾的空字符计算在内。

           字符串常量(使用双引号)和字符常量(使用单引号)不能互换。字符常量(‘S’)是字符串编码的简写表示。在ASCII系统上,‘S’只是83的另一种写法。因此可以:

           char shirt_size = ‘S’;

           “S”不是字符常量,它表示的是两个字符(字符S)组成的字符串。“S”实际上表示的是字符串所在的内存地址。

    拼接字符串常量

           字符串很长,无法放到一行中。C++允许拼接字符串字面值,即将两个用引号括起来的字符串合并为一个。事实上,任何两个由空白(空格,制表,换行符)分隔的字符串常量都将自动拼接成一个。

           cout <<”I’d give my right arm to be” ”a great violinist. ”;

    在数组中使用字符串

    sizeof运算符指出整个数组的长度,单位是字节。

    strlen()函数返回的是存储在数组中的字符串的长度,而不是数组本身的长度。

    strlen()只计算可见字符,而不把空字符计算在内。

    使用符号常量的好处:修改更加方便,只需在定义符号常量的地方修改即可。

    字符串输入

    cin有个缺陷,它使用空白(空格、制表符、换行符)来确定字符串的结束位置。这意味着cin在获取字符数组输入时只读取一个单词。读取该单词后,cin将该字符串放到数组中,并自动在结尾添加字符串。

           很多程序都依赖字符串输入,因此有必要对该主题做进一步探讨。必须使用cin的高级属性。

    每次读取一行字符串输入

           需要采用面向行而不是面向单词的输入方法

           iostream中的类提供了一些面向行的类成员函数:getline()get()[j周1] 。这两个函数都获取一行的输入,直到到达换行符。

           区别在于:getline()丢弃换行符get()将换行符保留在输入序列中。

    1、面向行的输入:getline()

    getline()读取整行,它使用通过回车输入的换行符来确定输入结尾。

    要调用这种方法,可以使用cin.getline()。该函数有两个参数,第一个是用来存储输入行的数组名,第二个参数时要读取的字符数。如果这个参数是20,则该函数最多读取19个字符。

    2、面向行的输入:get()

    get()不读取换行符,将换行符留在输入队列中。这样连续两次使用get()函数,会导致get()将不能跨过该换行符。

    get()有一个变体,使用不带任何参数cin.get()调用可读取下一个字符(即使是换行符)。因此可以用它来处理换行符,为读取下一行输入做准备。

    假设用get()将一行读入数组中,如果是换行符,说明已读取了整行。否则,说明该行中还有其他输入。

    如何知道停止读取的原因是由于读取了整行,而不是由于数组已填满?

    getline()使用起来更简单,get()使得检查错误更简单些。

    3、空行和其他问题

    getline()get()读取空行时,将发生什么情况?

    下一条输入语句将在前一条getline()get()结束读取的位置开始读取。但是当前的做法是:当get()读取空行后,将设置失效位。这意味着接下来的输入将被阻断。但可以用一下的命令来恢复 cin.clear();

    另一个潜在的问题,输入字符串可能比分配的空间长。则getline()get()会把余下的字符留在输入队列中,而getline()还会设置失效位,并关闭后面的输入。

                  后续章节会讨论如何避免这些问题。

     

    混合输入字符串和数字

    cout <<”What year was your house built? ”;

    int year;

    cin >> year;

    cout << “What is its street address? ”;

    char address[80];

    cin.getline(address,80);

     

    这个程序有一个问题,没等用户有输入地址的机会。问题在于,当cin读取年份时,将回车键生成的换行符留在了输入队列中。后面的cin.getline()看到换行符后,将认为是一个空行,并将一个空字符串赋值给address。这个问题的解决方法是:在读取地址之前先读取并丢弃换行符。

    C++常用指针而不是数组来处理字符串。

    ======================================

    string类简介

           ISO/ANSI C++98标准通过添加string类扩展了C++库,因此现在可以用string类型的变量而不是字符数组来存储字符串。

           要使用string类,必须在程序中包含头文件stringString类位于名称空间std中,因此必须提供一条using编译指令,或者使用str::string来引用它。String类定义隐藏了字符串的数组性质,让您能够像处理普通变量那样处理字符串。

           可以使用cin来将键盘输入存储到string对象中;

           可以使用cout来显示string对象;

           可以使用数组表示法来访问存储在string对象中的字符;

           可以使用C-风格字符串来初始化string对象;

    C++11字符串初始化

           C++11允许将列表初始化用于C-风格字符串和string对象:

           char first_date[ ] = {“Le Chapon Dodu”};

           string third_date = {“The Bread Bowl”};

    赋值、拼接和附加

           使用string类的某些操作比数组简单。可以将一个string对象赋给另一个string对象。

           string str1;

           string str2 = “panther”;

           str1 = str2;

     

           string类简化了字符串合并操作。可以使用运算符+将两个string对象合并起来,还可以用运算符+=将字符串附加到string对象的末尾。

           string str3;

           str3 =str1 +str2;   //str3str1str2相加组成;

           str1 += str2;          //str2加到str1末尾构成了str1

    string类的其他操作

          

          

          

    stringI/O

           使用cin和运算符>>来将输入存储到string对象中。

           使用cout和运算符<<来显示string对象。

           每次读取一行而不是一个单词时,使用的句法不同。

          

    char charr[20];

    对于未初始化的数组内容是未定义的。函数strlen()从数组的第一个元素开始计算,直至遇到空字符为止。对于未初始化的数据,第一个空字符的出现位置是随机的。因此在运行程序的时候,得到的数组长度可能不同。

    其它形式的字符串字面值

    除了char类型外,还有wchar_tchar16_tchar32_t

    可以创建这些类型的数组和这些类型的字符串字面值;

    对于这些类型的字符串字面值,要使用前缀LuU表示。

    wchar_t  title[ ] = L”Chief Astrogator”;

    char16_t name[ ] = u”Felonia Ripova”;

    char32_t car[ ] = U”Humber Super Snipe”;

     

    新增一种Raw类型字符串,在原始字符串中,字符表示的就是自己。没有转义。定界符采用的是”+*( )*+”的形式。前缀使用R

    ==================================

    结构简介

           结构是一种比数组更灵活的数据格式。同一结构可以存储多种类型的数据,从而将数据的表示合并到一起。结构也是OOP堡垒的基石。

           结构是用户定义的类型结构声明定义了这种类型的数据属性。定义了类型后,便可以创建这种类型的变量

           声明结构模板,创建结构变量

           关键字struct表明,这些代码定义的是一个结构的布局。

           标识符inflatable是这种数据格式的名称。称为结构标记。标记成为新类型的名称。

     

    struct inflatable

    {

           char name[20];

           float volume;

           double price;

    }

    C++中在创建结构类型的变量时,省略了struct关键字。

    inflatable hat;

    在程序中使用结构

    声明的位置:外部声明(可以被其后的任何函数使用)、内部声明只能被声明所属的函数使用。

     

    C++结构初始化

    使用列表初始化;

    如果大括号内未包含任何东西,各个成员都将被设置为零;

    结构可以将string类作为成员吗

     

    其他结构属性

    C++使用户定义的类型与内置类型尽可能相似。

    结构作为参数传递给函数,函数返回一个结构,使用赋值运算符(=),

    结构赋值,这种赋值被称为成员赋值。

    C++的结构特性比C更多。但是这些特性更多用于类中,而不是结构中。

    结构数组

     

    结构数组本身是个数组,

    结构数组的初始化方式:遵循初始化数组的规则,用逗号分隔每个元素的值,并将这些值用花括号括起来。然后用初始化结构的规则取初始化数组元素。

     

    结构中的位字段

     

     

    ======================================

    共用体

     

    共用体(Union是一种数据格式,它能存储不同的数据类型,但只能同时存储其中的一种类型。

    共用体的用途:当数据项使用两种或多种格式时,可节省空间。

    匿名共用体:没有名称,其成员将成为位于相同地址处的变量。显然,每次只有一个成员是当前的成员。

    共用体常用于节省内存。尤其是对于嵌入式系统编程,共用体常用于操作系统数据结构或硬件数据结构。

    ===================================

    枚举

    enum工具提供另一种创建符号常量的方式。这种方式可以替代const

     

    enum spectrum {red, orange, yellow, green, blue, violet, indigo, ultraviolet};

    这条语句做了两件事,第一是让spectrum成为了枚举类型,像struct变量被称为结构一样。

    里面的redorange等被作为符号常量。对应的整数值0~7。这些常量叫作枚举量

     

    枚举变量,只能使用定义枚举类型时,初始化的那些枚举量来给枚举变量赋值。

    spectrum band;

    band = blue;

    所以spectrum变量会受到限制,只有8个可能的值。如果试图将一个非法值赋值给它,编译器会报错。

     

    枚举量是整型,可被提升为int类型。但int类型不能自动转换为枚举类型。

    设置枚举量的值

           可以使用赋值运算符来显式地设置枚举量的值;

           enum bits{one=1,  two=2, four=4, eight=8};

           指定的值必须是整数,也可以只显式地定义其中一些枚举量的值;

           enum bigstep{first, second =100, third};

           first在默认情况下为0,后面没有被初始化的枚举量的值将比前面的枚举量大1。因此,third的值为101

          

    枚举的取值范围

     

    ======================================

    指针和自由存储空间

    计算机程序在创建数据时必须跟踪3种属性:

           信息存储在何处;

           存储的值为多少;

           存储的信息是什么类型;

    使用一种策略达到上述目的:定义一个简单的变量。声明语句指出了值的类型和符号名。

     

    接下来是另外一种策略:在开发C++类时非常重要,这种策略以指针为基础,指针是一个变量,其存储的值是地址。

     

    查看常规变量地址的方法:取址运算符(&);

     

    指针策略是C++内存管理编程理念的核心。

     

    指针与C++基本原理

           面向对象编程传统的过程性编程的区别在于,OOP强调的是运行阶段(而不是编译阶段)进行决策。运行阶段是指程序正在运行时,编译阶段指的是编译器将程序组合起来时。

           运行阶段决策提供了灵活性,可以根据当时的情况进行调整。C++采用的方法是,使用关键字new请求正确数量的内存以及使用指针来跟踪新分配的内存的位置。

     

    处理存储数据的新策略刚好相反,将地址视为指定的量,而将值视为派生量。

    一种特殊类型的变量:指针。用于存储值的地址,指针名表示的是地址,*运算符被称为间接值或解除引用运算符,将其应用于指针,可以得到该地址处存储的值。

     

    int updates = 6;

    int * p_updates;

    p_updates = &updates;

    变量updates表示值,并使用&运算符表示地址;

    指针p_updates表示地址,并使用*运算符表示指向变量的值;

    声明与初始化指针

    指针声明必须指定指针所指向的值的类型。

    p_updates是指针,*p_updatesint,不是指针;

    C++风格的指针声明:

    int*  ptr;  //用于强调int*是一种类型,指向int的指针。可以把int*理解成一种复合类型

    指针是一种特殊类型的变量,指针变量不仅仅是指针,而是指向特定类型的指针。

     

    可以在声明语句中初始化指针,但是被初始化的是指针,而不是它指向的值。

    int higgens =5;

    int * pt = &higgens;

     

    指针的危险

    C++创建指针时,计算机将分配用来存储地址的内存。但不会分配用来存储指针所指向的数据的内存。为数据提供空间是一个独立的步骤。

    声明了指针,没有初始化,指针就不知道指向哪里。

    指针一定要初始化。在使用指针应用解除引用*运算符之前。

    指针和数字

     

    使用new来分配内存

    如何实现在程序运行时分配内存。将指针初始化为变量的地址;变量是在编译时分配的有名称的内存。指针只是为可以通过名称直接访问的内存提供了一个别名。

     

           指针真正的用武之地在于:在运行阶段分配未命名的内存以存储值。在这种情况下,只能通过指针来访问内存。在C语言中,可以用库函数malloc()来分配内存;在C++中仍可这样做,但C++还有更好的方法——new运算符

           在运行阶段为一个int值分配未命名的内存,并用指针来访问这个值:

           int * pn = new int;

           这里的关键所在是C++new运算符,程序员要告诉new,需要为那种数据类型分配内存,new找到一个长度正确的内存块,并返回该内存块的地址。程序员的责任就是把该地址赋给一个指针。

           new分配的内存没有名称,该怎么称呼它呢,我们用一个术语叫:数据对象。数据对象指的是为数据项分配的内存块。变量也是数据对象。

     

    为一个数据对象获得并指定分配内存的通用格式:

    typeName * pointer_name = new typeName;

     

    注意:new分配的内存块通常与常规变量声明分配的内存块不同。普通变量的值都存储在被称为栈(stack)的内存区域中。而new从被称为堆(heap)或自由存储区(free store的内存区域分配内存。

    使用delete释放内存

           在使用完内存后,需要将其归还给内存池。

           使用delete,后面要加上指向内存块的指针。

    例如

    int * ps = new int;

    delete ps;

           这将释放ps指向的内存,但不会删除ps本身。

           一定要配对地使用newdelete。否则将发生内存泄漏,也就是说分配的内存块再也无法使用了。如果内存泄漏严重,则程序将由于不断寻找更多内存而终止。

           不要尝试释放已经释放的内存。这样做的结果将是不确定的,这意味着什么情况都有可能发生。

           一般来说,不要创建两个指向同一内存块的指针,因为这将增加错误地删除同一个内存块两次的可能性。

    使用new来创建动态数组

           通常,对于大型数据(数组、字符串,结构),应使用new。这正是new的用武之地。在编译时给数组分配内存被称为静态联编(static binding,这意味着数组在编译时被加入到程序中。但使用new时,如果在运行阶段需要数组,则创建它;如果不需要,则不创建。还可以在程序运行时选择数组的长度。这被称为动态联编(dynamic binding。这种数组叫作动态数组(dynamic array

           关于动态数组的两个基本问题:如何使用C++new运算符创建数组以及如何使用指针访问元素

           使用new创建动态数组:只要将数组的元素类型和元素数目告诉new即可。

                  int * psome = new int [10];

                  new运算符返回第一个元素的地址,该地址被赋值给指针。

                  当使用完new分配的内存块时,应使用delete来释放它们。

                  对于使用new创建的数组,应使用另一种格式的delete来释放:

                  delete [ ] psome;

                  方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素。要注意指针和delete之间的方括号。

                  如果使用new时,不带方括号,则使用delete时,也不应带方括号。

                  为数组分配内存的通用格式如下:

                  Type_name * pointer_name = new type_name [num_elements];

           使用动态数组:

                  创建动态数组后

                  int * psome = new int [10]; 如何访问其中的元素?只要把指针当做数组名使用即可。这样使用指针来访问动态数组就很方便了。

    ===================================

    指针、数组和指针算术

    整数变量增加1后,其值将增加1

    指针变量增加1后,其值增加的量等于它指向的类型的字节数;

    另外指针把数组名解释为地址;

    C++把数组名解释为数组第1个元素的地址;

    数组名更像是指针常量。

    程序说明

    sizeof 数组   ---> 得到数组的长度(这种情况下数组名不被解释为地址)

    sizeof 指针   ---> 得到指针的长度

     

    short tell[10];

    cout << tell <<  endl;  //&tell[0],第一个元素的地址;

    cout << &tell << endl;  //&tell是一个20字节内存块的地址;

    指针小结

    声明指针

           要声明指向特定类型的指针:typeName * pointerName;

          

    给指针赋值

           应该将内存地址赋值给指针。可以对变量名应用&运算符,来获得被命名内存的地址。new运算符返回未命名内存的地址。

          

    对指针解除引用

           对指针解除引用来获得指针指向的值。对指针应用解除引用或间接值运算符(*)。

     

    区分指针和指针所指向的值

           如果pt是指向int的指针,则*pt不是指向int的指针,而是完全等同于一个int类型的变量。pt才是指针。

     

    数组名

           在多数情况下,C++将数组名视为数组的第一个元素的地址。

           sizeof运算符用于数组名用时,此时将返回整个数组的长度(单位为字节)。

     

    指针算术

           C++允许指针与整数相加,加1的结果等于原来的地址值加上指向的对象占用的总字节数。

           还可以将两个指针相减,获得两个指针的差,这种运算仅当两个指针指向同一个数组时,才有意义,这将得到两个元素的间隔。

     

    数组的动态联编和静态联编

           使用数组声明来创建数组时,采用静态联编,即数组的长度在编译时设置;

           使用new[ ]运算符创建数组时,采用动态联编,即将在运行时为数组分配空间,其长度也将在运行时设置。

     

    数组表示法和指针表示法

           使用方括号数组表示法等同于对指针解除引用;

     

    指针和字符串

           char flower[10] =”rose”;

           cout << flower << “s are red ”;

           如果给cout一个字符的地址,则它将从该字符开始打印,直到遇到空字符为止。

     

           对于数组中的字符串用引号括起来的字符串常量以及指针所描述的字符串,处理方式是一样的,都将传递它们的地址。与逐个传递字符串中的所有字符相比,这样的工作量确实要少。都将解释为字符串第一个字符的地址。

    使用new创建动态结构

           结构指针访问结构成员不能使用句点运算符;

           应该使用:

                  箭头成员运算符(->);

                  或者(* ps.price  这样的格式;

          

          

    自动存储、静态存储和动态存储

           C++3种管理内存的方法:自动存储、静态存储、动态存储(自由存储空间或堆);

    自动存储

           函数内部定义的常规变量使用自动存储空间,被称为自动变量,这意味着它们在所属的函数被调用时自动产生,在该函数结束时消亡。

           自动变量是一个局部变量,其作用域为包含它的代码块。

           自动变量通常存储在栈中。这意味着执行代码块时,其中的变量将依次加入到栈中。而在离开代码块时,将按相反的顺序释放这些变量,这被称为后进先出。在程序执行的过程中,栈将不断地增大和缩小。

    静态存储

           静态存储是整个程序执行期间都存在的存储方式。使变量成为静态的方式有两种:一种是在函数外面定义它,一种是在声明变量时使用关键字static

    动态存储

           newdelete运算符提供了一种比自动变量和静态变量更灵活的方法。它们管理了一个内存池,这在C++中被称为自由存储空间(free space堆(heap。该内存池和用于静态变量及自动变量的内存是分开的。

           数据的生命周期完全不受程序或函数的生存时间控制。这使得程序员对内存有更大的控制权,然而内存管理也变得复杂了。

     

    栈、堆和内存泄漏:

           内存泄漏就是指用new在自由内存空间创建变量后,没有用delete释放,并且在指针无效后,无法访问该内存了。这将导致内存泄漏。后果很严重,可能导致内存被耗尽,程序崩溃。要避免内存泄漏,要养成一种习惯,即同时使用newdelete运算符。

    ===================================

    类型组合

    数组、结构和指针

    ================================== 

    数组的替代品

    模板类vectorarray是数组的替代品。

    模板类vector

           类似于string类,也是动态数组。

           可以在运行阶段设置vector对象的长度,可在末尾附加新数据,还可在中间插入新数据。基本上,它是使用new创建动态数组的替代品。

           vector<typename> vt(n_elem);

          

    模板类 array(C++11)

           vector类的功能比数组强大,但付出的代价是效率低下。

           如果需要长度固定的数组,使用数组时更佳的选择,但代价是不那么方便和安全。

           C++11新增了模板类array,它也位于名称空间std中。与数组一样,array对象的长度也是固定的,也使用栈(静态内存分配),而不是自由存储区。

    比较数组、vector对象和array对象

    ========================================

    总结

    1 数组、结构和指针是C++3种复合类型。数组可以在一个数据对象中存储多个同类型的值。通过使用索引或下标,可以访问数组中各个元素。

    2 结构可以将不同类型的值存储在同一个数据对象中。可以使用成员关系运算符(.)来访问其中的成员。使用结构的第一步是创建结构模板,它定义了结构存储哪些成员。模板的名称将成为新类型的标识符,然后就可以声明这种类型的结构变量。

    3 共用体可以存储一个值,但是这个值可以是不同类型。

    4 指针是被设计用来存储地址的变量。指针指向它存储的地址。指针声明指出了指针指向的对象的类型。对指针应用解除引用运算符,将得到指针指向的位置中的值。

    5 字符串是以空字符为结尾的一系列字符。字符串可用引号括起来的字符串常量表示,其中隐式包含了结尾的空字符。

    6 头文件string支持的C++ string提供了另一种对用户更友好的字符串处理方法

    7 new运算符允许在程序运行时为数据对象请求内存。该运算符返回获得内存的地址,用来赋值给指针。这样程序能够用指针来访问这块内存。如果数据对象是普通变量,可以使用解除引用(*)来获得其值。如果数据对象是数组,则可以使用数组名那样使用指针来访问元素。如果数据对象是结构,则可以用指针解除引用运算符(->)来访问其成员。

    8 指针和数组密切相关。如果ar是数组名,则表达式ar[i]被解释为*(ar+i)。其中数组名被解释为数组第一个元素的地址。数组名的作用和指针相同。反过来,可以用数组表示法,通过指针名来访问new分配的数组中的元素。

    9 运算符newdelete允许显式控制何时给数据对象分配内存,何时将内存归还给内存池。自动变量是函数中声明的变量。静态变量是在函数外部或者使用关键字static声明的变量。这两种变量都不太灵活。自动变量在进入代码块时产生,离开代码块时终止。静态变量在整个程序周期内都存在。

    10 C++98新增的标准模板库(STL提供了模板类vector,它是动态数组的替代品。C++11提供了模板类array,它是定长数组的替代品。

     

  • 相关阅读:
    HDU 3951 (博弈) Coin Game
    HDU 3863 (博弈) No Gambling
    HDU 3544 (不平等博弈) Alice's Game
    POJ 3225 (线段树 区间更新) Help with Intervals
    POJ 2528 (线段树 离散化) Mayor's posters
    POJ 3468 (线段树 区间增减) A Simple Problem with Integers
    HDU 1698 (线段树 区间更新) Just a Hook
    POJ (线段树) Who Gets the Most Candies?
    POJ 2828 (线段树 单点更新) Buy Tickets
    HDU 2795 (线段树 单点更新) Billboard
  • 原文地址:https://www.cnblogs.com/grooovvve/p/10467778.html
Copyright © 2011-2022 走看看