C++总结

来源:互联网 发布:c语言最大公约数流程图 编辑:程序博客网 时间:2024/06/01 21:23

王桂林老师的C++课程笔记

Day01:

1.const 更严格的类型检查,C中可以暗度陈仓,通过指针来改一个const修饰的变量,但在C++中会被查出,等式左右两侧必须类型完全相同。

2.更严格的enum

3.表达式可以做左值。

4.布尔类型,其实是enum{true,false}。

5.C++ 不用指针 不用字符数组不用类型强转

6.所有字符串有关的用string定义,C中从键盘中读取字符到一个字符数组中,不安全,因为输入可能大于数组的长度,用fgets()人为的控制数据读取的大小,C++中定义的string类型,输入多少则读取多少,也不会有C中gets()恶意大小输入导致系统崩溃的问题。

7.cin>>a>>b  等价于cin>>a,cin>>b  a和b是先后问题,不是输入流先到a  再从a到b

    Cout<<a<<b<<endl  等价于cout<<a, cout<<b,endl是end line 表示回车\n

8 cout format

C++后缀名:VC里用cpp作后缀名, 在GCC里默认采用C、cc、cxx.c++作为后缀名

9.函数重载(静多态)

    函数重载条件:

1.函数名相同

2.函数参数列表不同,包括类型,个数,顺序

3.函数名相同,参数列表相同但返回值不同并不构成重载,即重载与返回值无关,主要大部分与返回值无关。

    匹配原则:

1.严格匹配

2.隐式转换

Double到float ,int 会产生二义性,编译器不知道该调用哪个函数,同样还有int到long,double

10.C++中为了实现函数重载,对每一个函数会进行倾轧

eg:int add(int a)

    C++会自动将该函数倾轧为int add_ i (inta)

    int add(int a,char b)

    C++会自动将该函数倾轧为int add_ ic (inta)

这样,即使源文件中有同样名字的add函数,但经过倾轧后,函数名后加上了参数缩写,在编译器中这些函数名还是不同的。

对于C++,它一个特点是完全兼容C中的库函数,也就是include<stdio.h>之后,C++中也可以调用所有stdio.h头文件中声明的函数,这里有个冲突,就是在C++中include<stdio.h>之后,将该stdio.h中所有内容复制粘贴到C++源文件中后,在其它函数调用stdio.h中的函数的时候,会自动进行倾轧,那么链接时候,在stdio.h库文件中是找不到倾轧后的函数名称的,因此,为了C++的兼容性,用extern  “C”{} ,{}中的内容就不会进行倾轧,打开stdio.h头文件中开头几行就会看到,用#ifdefine Cplusplus 判断如果是C++版本,则多加一行extern  “C”,并将头文件内容全部包在{}内,这样在c++里include<stdio.h>后,因为头文件中声明的函数没有进行倾轧,那么在其他函数调用头文件里的函数时也自动不会进行倾轧,这样在链接的时候就能在库里找到对应的函数定义了。(有个注意点,头文件的函数是什么形式,调用或定义的时候也是什么形式,这点没想通,但课程是这么说的,也就是声明时进行倾轧,则调用或定义时也倾轧,如果声明时没有倾轧,则调用或定义也不会倾轧)。

11. C++认为 一切操作符都是函数,函数是可以重载的,因此可以将一个操作符例如+ 重载为一个函数 函数名为operator+

12.函数默认值,在形参里直接给定一个值作为默认参数,当实参里不赋值是使用默认参数。

默认规则:必须从右向左默认,中间不能跳跃

    注意声明和定义分开的时候,默认值要放在声明里,而不能写到定义中。否则会报:重定义默认参数。

    函数重载和默认值绝对不能同时存在!否则会有二义性

*13引用

&:放在右边都是取地址,放在左边(&前面有类型)则是引:

day02: 

1.引用只是关系的一个声明,不分配内存,地址大小全都和被引用的值相同

2.一经引用,不可变更

3可以定义指针的引用,int * &ra=a;但不能定义引用的引用。

优点:C中裸露地址,在形参中要开辟空间,C++中则更简单明了,直接将引用作为实参传递,相当于直接将原数传递了,扩大了原数的作用域。引用是对指针的一次包装。

4.引用不是一种类型!!但在函数定义中引用作为形参时,声明处也要加&。

5.引用前加const:

1.当左右两侧类型相同时,引用前加const的目的是防止通过修改引用来修改原数的值。

2.当左右边类型不同时(也可以右边是常数,这些在C++中都是允许的),此时会产生一个中间变量,存在其他地方,值不允许修改。

double b=3.14;

const int &a=b;

会发现a的地址和b的地址其实并不一样,修改b的大小是无法改变a的。

6.指针和引用的汇编代码其实完全一样的。

*7:引用的本质是一个const *的指针,和一个数建立引用关系后不能在与其他数建立引用关系,但引用的数的值可以更改,打印引用的大小会发现是4字节,因为引用本身就是一个指针,只是指针的包装。

8.malloc  在C中很多多余的部分,如int*p =(int* )mallo c(sizeof ( int) ),光一个int就要多写两遍,类型转换肯定是与分配的大小有关的,C++中对此进行了简化,用new代替malloc,new int 表示新分配一个int大小的空间,并有一个int*指针指向它。

对于二维数组,二维数组指针本来就不好写,C中很麻烦 int (*p)[3]= ( int(*)[3])malloc(2*3*4);但在C++用new可以直接int (*p)[3]=new[2][3];三维数组int a[2][3][4]指针类型为int (*p)[3][4] ,只能第一个未定义,这样才可以确定a+1指向的地址。

 Delete  q; 释放一个q的大小的空间 ,Delete[]q  释放所有q分配的空间,[]非常重要,不加会造成内存泄漏。

C++多了异常机制,不需要判断new后的返回值,如果new中发生错误,直接去异常处理,或者将可能发生错误的而又希望程序继续运行的语句放入try{}中,处理函数(一般是出错原因)放在catch}{}中,如果try中出现错误程序不会崩溃而会继续运行,并把catch中的信息打印出来。缺点是代码比较臃肿。

New和delete在C++中重要用处是在类对象的申请。

    int *pc = new char(15);//开辟一个内存单元,并用括号里的初始化

int *pca = new char[15];  //开辟一个数组

()[]绝对不能用错!很重要

Delete时也不同,分别对应delete和delete[]

9.内联函数。

对于宏函数,内嵌到目标代码,减少函数调用,在预处理阶段完成替换,缺少语法检查;函数完成了对某一类操作的抽象,避免了相同功能的开发,但无法避免压栈与出栈的开销。

C++提供了inline关键字,将两者优点集中,而避免两者缺点。优点:内嵌到目标代码,同时有类型检查

  缺点:增加代码段长度,以牺牲代码段空间,提高程序的运行时间。适用于体积小,频繁调用的函数。低于10行。Inline和register一样,都很容易被编译器优化。Register定义的变量可能并不一定在寄存器里,因为并没有那么多寄存器,所以优化掉的话就在内存里了,inline同样,如果过长也会优化掉不进行替换。

10.   C++中的const相当于#define。

        用const修饰的是常量,任何方法都不能改变常量的值。包括  const_cast.

       Const_cast<待转化的类型 指针或引用>(要脱const的值)

    对const类型的脱const后的指针和引用,的写操作C++未定义,意味着const类型的数据脱const之后不要进行写,只有某些特定情况如必须传一个非const的实参到一个函数,但可以确定在那个函数中不会修改这个值,则传实参是可以脱const。

11.重解释

    普通类型转换用static_cast

    指针类型转化用reinterpret_cast 重解释指向的二进制数据

*12.命名空间

    大型项目中,为了防止函数名或全局变量名重名而引用。

    *命名空间是对全局作用域的进一步划分。

    Usingnamespace::Myspace  之后可以直接用所有Myspace中的所有定义的变量。

    Using Myspace::x    之后可以直接用x引用Myspace命名空间里的x变量,其他变量如a 必须用Myspace::a 来引用。

    Std是C++标准库的一个命名空间,里面有cin,cout等标准输入输出

    同时使用两个命名空间时,还得用命名空间::变量名使用,平时尽量在更小的作用域里使用一个命名空间,免去冲突。

    同一个文件中的多个命名空间的定义有累计的效果,而不会冲突,也可以一个命名空间里嵌套一个命名空间。

13.string

    初始化是将string当做了一个字符串指针。多了一个例子string str2(”China”);两个string用‘+’相加,会把两个字符串连在一起。String与字符数组最大的区别,数组必须提前知道要分配的内存大小,而string是在堆里类似于malloc以链表的形式动态的分配空间,所以两个string‘+’时,就是用指针连上就可以。

14.vector

Day03

1.封装

    从struct到class:struct里是数据的封装,而行为是对struct内数据的处理和分析,struct并没有将行为也封装起来,同时这些数据是public的,可以随意改变里面的数据,非常危险。因此使用class,默认里面的都是private的,外部不可以改变,或者指定public型,可以在外部改变。

    Class内将行为也封装起来,并且如果行为的实参也在这个class内,不需要专门将该数据传递进去,可以直接使用(将clss内的数据当做了全局变量使用)。

    一般数据是私有的,用privare表示,其下的数据外部不可以进行访问。行为作为对外接口是public的。

    类本身就是一个命名空间。

2.构造器(系统自动调用,完成初始化的作用)

     有函数特性,但又不像函数,因此不叫构造函数。它是用来构造对象的。在定义好一个类后,我们每次用这个类构造对象时都会先用一个函数对它内部封装的数据进行初始化,这样很麻烦,构造器定义在类的内部,构造器名字与类名相同,{}里面是对类的数据的初始化,这样以后在定义一个对象时,程序会自动跳到构造器中对类的数据进行初始化,而不需要由程序员手动进行初始化。

    构造器可以重载,也就是说可手动写多个构造器,但参数不能相同。

    构造器特性:与类同名,无返回值(因此不叫函数);

手动不写构造器时,系统默认会有一个空的无参构造器。一旦手写了构造器,系统不再提供


3.析构器(与构造器相对于,对象消失的时候自动调用,完成对象销毁前的清理工作,处理构造器中申请的堆空间)

    名字与类相同,前面加~。

    无返回无参数(因此不叫函数)。

    对栈对象,离开作用域时自动调用。堆对象delete时自动调用。

    通常情况下析构器并不是必须的,如果对象里的数据都在栈上,则离开栈自动消失。


4.构造器解析器中初始化的一个简便赋值写法,在参数列表里初始化

    Stack::Stack(int size)

:space(newint[size]),top(0)

{

}

Stack是一个类::构造器名字(初始化的形参)

           :将new int[size]的结果赋值给space,将0赋值给top

这样快速,B格高。

*初始化的顺序与类中的声明顺序有关,与赋值顺序无关。假设类Stack的定义为

Class Stack{

Public:

                     Stack(int size)

:space(new int[size]),top(0)

{

}

Pricate:

Int    top;

              Int* space;

}

那么初始化是,是先将0赋给top,再讲new的结果赋给space。

5.string也是一个类,因此可以string a(“China”),相当于传了参数给了构造器。

6.拷贝构造.

用另一个已初始化的对象的值来初始化新的对象。例如

Stack a(5);这个对象在类里的构造器里用5进行初始化

Stack b (a);用对象a的值来初始化b。

系统默认进行的是等位拷贝(浅拷贝),即将数值进行了简单拷贝。如果是数值没有问题,但是如果拷贝的是指针会有问题,比如a在结束是对指针指向的堆进行了释放,而拷贝构造的b,也有一个指针指向同一个堆,结束时也会释放,会造成多次释放,称为重析构。如果要避免这种,要自己去写拷贝(深拷贝),即申请一块内存空间,将a中指针指向的区域的数据拷贝到新申请的空间中,b的指针指向这个区域。

       同类之间无private,可以访问。


7.this指针&

       指向当前对象的指针。 是一个const型常指针。

用于多重串联调用时,除了指针,还有引用


8.注意获得字符串长度一定要用strlen。Strlen不会计算\0,因此要申请空间时最后要加1。Sizeof(字符串首地址)结果是4,算的是地址长度,sizeof(*字符串首地址)结果是1,算的是第一个元素char的大小。

9.运算符重载 定义一个类A 

A a;

A b;

a=b;

这个=号其实是系统默认提供的运算符重载函数,即用b的值对a重新赋值。

在类中为 A& operator=(const A& another)返回值为A& ,不能const这样可以执行a=b=c;=本身就是在类A里定义的,所以a=b这句话可以看做a.=(b).

      

      

总结:*在类中系统默认提供的四个函数为

              1.构造器。无参无返回值 A();

              2.析构器。无参无返回值 ~A();

              3.拷贝构造器。A(const A& another);

              4.运算符重载。A& Operator=(A& another)

      

Day04

1.返回引用的作用是,可以对返回值进行赋值等操作,如果返回的是一个普通值,那仅仅是一个值,不能作为左值。

2.对象数组初始化。定义一个类A,如果要定义一个对象数组,当构造器里的形参只有一个参数时,可以A stu[3]={1,2,3};但是很多情况下构造器的参数不止一个,形式变为 Astu[3]={A(1,2),A(3,4),A(5,6)}.

    一般最好写一个无参的构造器,如果只写一个有参的,定义对象数组时,对未给实参的对象进行的是无参初始化,但是系统默认如果自己写了有参构造器则不会提供无参构造器。

3.类的大小只跟数据成员有关系,而跟函数没有关系,函数代码是存放在代码段,并且所有对象都调用共同的函数代码段。每个对象是如何在函数内区分的呢?在写A.dis();其实这句话背后应该是A.dis(&A),而在A::dis(){}函数内有个this指针,即将&A作为参数传进去,用this来接收。打印this的值和&A,会发现这两个值完全相同。

4.const

    1修饰类中的数据成员。该成员只能在构造器的初始化列表中初始化。不能以任何方式被修改。

    2.修饰类中的成员函数。const放的位置在函数声明之后,函数实现体之前。

意义:const函数承诺不会已任何形式修改类中的数据成员,包括调用别的函数来修改数据成员,也是不能修改的,因此const修饰的成员函数,只能访问const修饰的成员函数。

可以构成重载,即void add()和void add()const是两个不同的函数。

声明和定义都要加const。

   3.修饰类对象。

Const修饰函数,是从函数的层面,不修改对象数据,只能调用const成员函数。

Const修饰对象,是从对象的层面,不修改对象数据,只能调用const成员函数。

非const对象可以优先调用非const成员函数,若无则调用const成员函数。

*Const函数可以改变static变量的值,因为static变量属于类,不属于对象。

5.static

    在类内的表现,用来实现族类对象共同访问。不占对象的数据空间,和成员函数一样。

    在生成对象的时候,普通数据成员才有空间。而static成员和成员函数一样,在类声明的时候就已经开辟了空间。

*Static数据成员,即属于类,也属于对象,但终归属于类。

类内定义,类外初始化。初始化格式:type 类名::变量名 = 初值,在main()之前初始化完毕。

 

*Static修饰的成员函数和数据成员,都是在类的命名空间里的,所以可以直接类::Static修饰的成员函数和数据成员来访问,所以没有this指针,找不到具体是哪个对象,所以也无法访问修改该对象里的数据。而对于其他普通的数据和函数,必须指定具体的对象才可以。因此Static修饰成员函数,只有一个作用,就是管理访问 static成员。

*有些关键词声明定义都要有,比如const,但有些关键词不是,例如static,在外部对它进行初始化的时候不需要加static

 

Day05

1.指向类数据成员的指针。(该指针定义后不分对象,只指向固定类的固定的一个成员)

定义时要和具体的类联系,使用时要和具体的对象联系。

定义与C不同的地方只在于要加类::

实际上不能成为指针,它其实是存的偏移量。

2.友元:不是类的成员函数,但是该函数想访问类对象内的私有数据时,在该类内将该函数声明为friend。

    对于构造器,由于不算严格意义上的函数(没有返回值),因此不可以作为声明为友元函数。

3.运算符重载的两种方法:

1.作为类成员函数operator+(another)

2作为友元函数operator+(one,another)

重点:-的重载

    对于常量n, -(-n)可以通过编译,但是-n=100不能通过,因此用两个const

    const stu operator-(const stu &another) const

    前一个const保证返回值不可以进行修改,因此-n(现在n是对象)=n1不能通过,而后一个const保证-(-n)可以通过,因为返回的是const的对象,只能调用const型函数,因此加上const。

day06

一.类型转换

    在默认类型中,存在默认类型转换,例如

float a;

a=3/4;

此时编译器会默认将3/4的值(int/int 默认是int型)转换成float型,同样在类中,我们也期望有类型转换。类的类型转换分两种:

class complex

{

public:

    complex(double a, double b) :x(a), y(b){}//构造函数

    complex(double a):x(a), y(0){}//隐式转换构造函数

    friend complex operator+(complex one, complex another);

    ~complex();

    void dis();

private:

    double x;

    double y;

};

 

complex operator+(complexone, complex another){

    return complex(one.x + another.x, one.y + another.y);

}

int main(){

    complex c(1, 2), c1(3, 4), c2(1, 2);;

     c2 = c + 2.0;

    return 0;

}  

1.隐式类构造转换函数。

       在main中的 c2 = c + 2.0,由于运算符重载+号的两个形参都是complex类型的,则隐式将2.0转换为complex,这里可以将2作为参数让它构造出一个complex对象,其实就是构造器函数,只是这个构造器的参数是一个值double(2.0).

    也可以将c2 = c + 2.0改为c2 = 2.0+c,因为运算符+重载是友元函数,因此实现了交换律。但是如果运算符+放在类中成为了类成员函数则实现不了交换律,因为c2 = c + 2.0会看作c.operator+(2.0),因此只能+号左边放对象,右边放数值,无法实现交换律,这就是为什么一般双目运算符会写为友元函数的原因。

2.显式类构造转换函数

    将构造函数声明为explicit,在之后需要时使用。

即在类中改为

eplicit complex::complex(doublea):x(a), y(0){}//隐式转换构造函数

在main中使用时改为:

     c2 = c + complex(2.0);

3.  类型转换函数

class complex

{

public:

  complex(double a, double b) :x(a), y(b){}

  operator double(){

     return x + y;

  }

  ~complex();

  void dis();

private:

  double x;

  double y;

};

 

int main(){

    complex c(1, 2);

    double a;

    a = c + 3.0;

    cout << a<< endl;

    return 0;

}

在main中a = c + 3.0;c是类对象,则会期望将它转换为double类型,这里用转换函数来实现,定义在类里,格式为:

operator 转换的目标类型(){

    return 转换的目标值

}

在本例中要转换成double型,因此在类里定义为

operator double(){

    return  x + y;(仍会将本对象地址以this指针形式传递进来,可以直接用)

}

总结:类的转换就两个方法:将别的类型构造出自身类类型或者将自身类类型转换为别的类型,前者将它看做构造器函数生成一个新对象,后者用类里定义的转换函数来实现。

二.运算符重载高级篇

    1.仿函数,将类以函数的方法运行。

       对()运算符进行重载,例如在类Sqrt中的成员函数:

void operator()(int a)

    在之后使用的时候,正常可以是

Sqrt a;

a()(5);等同于a.operator()(5),但由于两个括号重复,因此省略一个()直接a(5);这种形式类似于函数。

    2.-> .重载

三.继承和派生

    1.public,private,protect区别(public继承方式)

 

public

protect

private

基类:类内成员函数

可以访问

可以访问

可以访问

基类:类外

可以访问

不可以访问

不可以访问

子类:类内成员函数

可以访问

可以访问

不可以访问

子类:类外

可以访问

不可以访问

不可以访问

可看出,public类型无论基类子类还是类内类外都是可以访问的,没有任何权限,而protect和private的区别在于继承时,子类的类内函数对protect型可以访问,对private型不可以,private实现了数据的隐藏性。

2.继承时不指定,默认继承方式为private。

3.子类的初始化方式。是通过父类的构造器在子类的构造器参数列表中进行初始化的。主要原因是父类的很多数据成员是private的,子类没办法直接对其进行赋值操作,则利用父类的构造器,将初始化参数传进去。

4.对于基类的private数据子类无法进行访问,只能通过基类的一些成员函数来访问。

5.类内的成员对象,要么在构造器的参数列表中显示初始化,要么自动调用无参构造器。

6.在定义子类时,父类必须在它之前有完整的定义,不能只有class  father;这种不完整声明,因为定义时必须要知道父类内部的具体结构。

7.对子类初始化的顺序为:父类对象,类对象,类成员,析构顺序与之相反。

总结:

    对父类的继承部分,可看做一个白匣子(因为不像黑匣子一样内部完全不可见,哈),其中public以及protect的成员,子类内部是可以访问的,但是其他不可以,包括对它进行的初始化,必须要通过白匣子自己的构造器来进行,如果没有显式的调用父类构造器给它参数,则它会默认运行无参构造器生成这个白匣子。

    8.继承时,除了构造器和析构器,子类会完全继承父类的所有函数和数据成员。

day07

1.派生类的拷贝和赋值

    对派生类进行拷贝和赋值时,如果不手动写自定义拷贝和赋值函数,编译器会进行默认拷贝和复制,对基类调用基类的拷贝构造器,而赋值运算符重载函数已经完全继承下来,因此派生类用继承下来的赋值运算符重载函数对基类进行赋值。

    如果有自定义拷贝或赋值函数,系统不会自动调用基类的拷贝构造器或者赋值运算符重载函数,这些都要手动的实现,值得注意的一点是,在拷贝函数中,由于派生类并没有将基类的拷贝构造器继承下来,因此对基类的初始化只能放在参数列表中。

2.多继承

    当一个子类有多个父类时,这些父类中可能有重名的数据成员和函数成员,这为我们的数据访问带来的难度,通过提取公因式的方法,即将这些重名的成员放在一个类中,让多个父类都继承这个类,这个类称为祖父类,将三角问题转为四角问题,但是这种方法仍然解决不了问题,因为父类继承祖父类后,在每个父类中仍然会有这些重名的数据成员,如何让多个父类继承同一份数据,就引入了virtual,虚继承的概念,将这个祖父类称为虚基类,多个父类采用虚继承的方式继承,可以理解为多个父类共享这一份重名的数据,同时在子类继承多个父类时,要对虚类进行初始化。

    父类继承了虚基类后,是正常使用的,只有当涉及到子类时,virtual才会起作用。

3.多态

三个必要的步骤

1.父类中要有虚函数。前面加virtual,作为对外公共接口。

2.子类中对父类的虚函数进行覆写。返回值,名称参数必须完全相同,覆写虚函数之后也是虚函数,最好前面也加virtual。

3.定义一个指向父类的指针,用子类的地址对其赋值。

当调用父类中的虚函数时,会自动调用子类的覆写的虚函数。每次对该指针用不同的子类地址进行赋值,会自动调用不同子类的虚函数。

    实验发现,多态只能父类和子类,不能祖父类调用子类的虚函数。

4.

重载:

1.同一个类(作用域)中

2.函数名相同

3.参数不同。

4.返回值无关。

5.virtual关键字无关。

覆盖

1.父类与子类之间

2.函数名相同

3.参数相同

4.返回值相同

5.有virtual关键字

shadow隐藏:

1.子类与父类之间

2.函数名相同

3.当参数不同或者参数相同但是无virtual关键字,父类函数被隐藏

5.声明关键字:static virtual friend  只需要在声明中加上,具体定义时不需加,参数给定默认值的时候也同样。

  实现关键字:const 声明和定义都必须要加上const

    声明和定义写在一起的时候,都只写一次。

day08

1.纯虚函数

    1.在virtual定义的函数名后加 =0,并且纯虚函数没有实现体。

2.含有纯虚函数的类叫抽象基类,abstract class,不能实例化,既不能生成一个该类的对象。它的存在仅仅是为了提供族类的接口。

3.如果子类中没有覆写纯虚函数,那子类也为抽象基类,也不能实例化。

2.凡是含有虚函数的类,其析构函数也要声明为虚函数,这样可以实现完整的析构,当delete父类指针时,不只调用父类析构,也会调用子类析构器。

3.虚函数实现浅析

    对于类中的所有虚函数,在类中共同占4个字节的位置,这个位置是占位符,指向了虚函数表,这个虚函数表是一个函数指针数组。定义父类的时候,这个虚函数表内是父类中的虚函数,但当定义一个子类时,子类中的虚函数会将虚函数表中同名的函数覆盖掉。

    虚函数表的建立发生在子类对象构造完成后,在子类对象析构后,虚函数表也消失。

4.模板

    类模板中,类只是抽象,将类模板实例化(在后面加<>)以后,就变为类,可以生成对象。

    对于函数,实例化是默认发生的。

    当类中的函数成员定义在类外时,第一要在函数定义前加template <typename  T>,一般写作两行;第二,此时的类是模板类并不是真正的类,因此要在类名后加<T>,表明这是模板类。

5.template模板不支持声明和实现分开为.h和.cpp,因为它并不是真正的函数(类),而是编译器生成具体函数(类)的一个模子,所以不能分开放在不同的文件中

0 0
原创粉丝点击