• STL学习总结之<容器>


    1 容器的共通能力和共通操作

    1.1容器的共通能力

    容器三个核心能力是:

    (1)容器元素必须能够拷贝

    所有容器提供的都是"value"语意而非"reference语意"。元素插入容器内时,实施的是拷贝操作,因此STL容器的每一个元素都必须能够拷贝(存在public  copy构造函数),否则容器内的元素只能是指向对象的指针。

    (2)每个容器都提供“可返回迭代器”的函数

    迭代器是容器和算法之间的“桥梁”,算法通过迭代器来操纵容器内的元素。

    (3)调用者必须确保传给操纵函数的参数符合需求

    一般而言,各项操作并非绝对安全,调用者必须确保传给操纵函数的参数符合需求。比如:对于利用索引取vector容器内元素的操作(vec[index]),vector本身并不检查index是否合法,调用者必须自己确保index的合法性。

    1.2 容器的共通操作

    操作

    效果

    ContType    c

    产生一个空容器

    ContType    c1(c2)

    产生一个与c2元素相同的元素

    ContType    c(beg,end)

    复制[beg,end)内的元素,作为容器初值

    c.~ContType()

    删除元素,释放内存

    c.size()

    返回容器内元素数量

    c.empty()

    判断容器是否为空(比size()==0快)

    c.max_size()

    返回容器可容纳元素的最大数量

    c1==c2、c1!=c2、c1<c2、c1>c2、c1<=c2、

    c1>=c2

    c1=c2

    将c2的所有元素赋值给c1

    c1.swap(c2)

    c1、c2交换元素

    swap(c1,c2)

    同上,是个全局函数

    c.begin()

    返回指向第一个元素的迭代器

    c.end()

    返回指向第二个元素的迭代器

    c.rbegin()

    返回逆向迭代器,指向逆向遍历的第一个元素

    c.rend()

    返回逆向迭代器,指向逆向遍历的最后一个元素的下一个位置

    c.insert(pos,elem)

    将elem的一个副本插入pos处

    c.erase(begin,end)

    移除[begin,end)内的所有元素

    c.clear()

    清空容器

    c.get_allocator()

    返回容器的内存模型

    注:

    (1)赋值与swap()

    如果两个容器的型别相同,而且拷贝后原容器不再使用,那么可以用性能更优异的swap()方法实现赋值。

    (2)利用empty()通常比运用size()==0判断容器是否为空速度更快。

    2 vector容器

    2.1 vector函数

    (1)构造、拷贝、析构

    操作

    效果

    vector<Elem> c

    产生空vector

    vector<Elem> c1(c2)

    产生同型vector的副本

    vector<Elem>c(n)

    利用default构造函数产生n个元素的vector

    vector<Elem>c(n,elem)

    产生n个元素为elem的vector

    vector<Elem>c(begin,end)

    以区间[begin,end)内的元素产生vector

    c.~vector<Elem>()

    析构函数

    (2)非变动性操作

    操作

    效果

    c.size()

    返回元素数量

    c.empty()

    判断大小是否为0

    c.max_size()

    返回可容纳元素的最大数量

    capacity()

    返回重新分配空间前容器的容纳元素的能力

    reserve()

    如果容量不足,则扩大(不会减小容量,并如果容量扩大了则原来指向容器的iterators、pointers、references都会失效)

    c1==c2、c1!=c2、c1<c2、c1>c2、c1<=c2、c1>=c2

    (3)赋值

    操作

    效果

    c1=c2

    将从全部元素赋值给c1

    c.assign(n,elem)

    将n个elem,赋值给c (原有内容全部清除)

    c.assign(beg,end)

    将区间[beg,end)内的元素赋值给c (原有内容全部清除)

    c1.swap(c2)

    将c1和c2元素互换

    swap(c1,c2)

    同上,是全局函数

    (4)元素存取

    操作

    效果

    c.at(idx)

    返回idx位置的元素,如果idx越界会抛出异常

    c[idx]

    返回idx位置的元素,不进行范围检查

    c.front()

    返回第一个元素,不检查是否存在

    c.back()

    返回最后一个元素,不检查是否存在

    (5)迭代器相关函数

    操作

    效果

    c.begin()

    返回指向第一个元素的随机迭代器

    c.end()

    返回指向最后一个元素的随机迭代器

    c.rbegin()

    返回指向逆向迭代第一个元素的逆向迭代器

    c.rend()

    返回逆向迭代最后一个元素的下一个位置

    (6)安插和移除元素

    c.insert(pos,elem)

    在pos处插入elem的副本,并返回新元素的位置

    c.insert(pos,n,elem)

    在pos处插入n个elem副本。无返回值

    c.insert(pos,beg,end)

    在pos处插入[beg,end)区间内的元素,无返回值

    c.push_back(elem)

    在尾部添加一个elem副本

    c.pop_back()

    移除最后一个元素

    c.erase(pos)

    移除pos处的元素,返回下一个元素的位置

    c.erase(beg,end)

    移除[beg,end)区间内的元素,返回下一个元素位置

    c.resize(num)

    将元素数量改为num(如果size()变大了,多出来的元素以default构造函数构造完成)

    c.resize(num,elem)

    将元素数量改为num(如果size()变大了,多出来的元素是elem的副本

    c.clear()

    清空容器

    2.2

    (1)移除“与某值相等”的所有元素

    std:vector<Elem> coll;

    ...

    coll.erase(remove(coll.begin(),coll.end(),val),coll.end())//移除所有与val相等的元素

    remove操作完成后返回删除后逻辑的终点,remove删除的方式是后面的元素覆盖掉前面要删除的元素,而后面的元素并没有实际清除掉(详见:《C++标准程序库》)。调用erase后删除掉已经移到到容器前面的元素。

    (2)大小和容量

    在定义vector容器时,可以利用reserve()函数提前分配足够大的空间,这样可以在以后的操作中提供性能。

    reserve()不可以缩减vector的容量,如果传入的大小比vector原来的容量小则不会引起任何反应。

    删除操作时,references、pointers、iterators不会失效,继续指向动作发生前的位置。

    插入操作可以使references、pointers、iterators失效,因为安插肯能导致重新分配区间。

    可以利用swap()函数间接缩减vector的容量,因为两个vector交换内容后,它们的容量也会互换。std::vector<T> tmp(v).swap(v);操作完成后原来的references、pointers、iterators失效。

    (3)元素存取

    只有at()为进行范围检查

    (4)迭代器失效的情况

    在较小索引位置处插入或移动元素; 由于容量变化而引起重新分配内存。

    (5)vector与一般Arrays

    虽然vector内所有的元素不一定保存在连续的内存空间中,但&v[i] = = &v[0] + i是成立的。

    可以在任何需要动态数组的地方使用vector,然后将vector的第一个元素的地址传进去(&v[0]),但要保证vector的空间足够大,不能利用第一个元素的地址(&v[0])来传递vector。

    3 deque容器

    deque在首尾两端都可以插入删除元素,采用动态数组来管理元素,提供随机存取能力。

    3.1 操作函数

    (1)构造函数和析构函数

    操作

    效果

    deque<Elem> c

    产生空deque

    deque <Elem> c1(c2)

    产生同型deque的副本

    deque <Elem>c(n)

    利用default构造函数产生n个元素的deque

    deque <Elem>c(n,elem)

    产生n个元素为elem的deque

    deque <Elem>c(begin,end)

    以区间[begin,end)内的元素产生deque

    c.~ deque <Elem>()

    析构函数

    (2)非变动性操作

    操作

    效果

    c.size()

    返回元素数量

    c.empty()

    判断大小是否为0

    c.max_size()

    返回可容纳元素的最大数量

    c1==c2、c1!=c2、c1<c2、c1>c2、c1<=c2、c1>=c2

    c.at(idx)

    返回索引idx所指元素,会进行范围检查

    c[idx]

    返回索引idx位置元素,不会进行范围检查

    c.front()

    返回第一个元素,不检查是否存在

    c.back()

    返回最后一个元素,不检查是否存在

    c.begin()

    返回指向第一个元素的随机迭代器

    c.end()

    返回指向最后一个元素的随机迭代器

    c.rbegin()

    返回指向逆向遍历第一个元素的逆向迭代器

    c.rend()

    返回指向逆向遍历最后一个元素的逆向迭代器

    (3)变动性操作

    操作

    效果

    c1=c2

    将c2的所有元素赋予c1

    c.assign(n,elem)

    将n个elem副本赋值给c

    c.assign(beg,end)

    将区间[beg,end)内的元素赋值给c

    c1.swap(c2)

    将c1和c2的元素互换

    swap(c1,c2)

    同时,全局函数

    c.insert(pos,elem)

    在pos处插入elem的副本并返回新元素位置

    c.insert(pos,n,elem)

    在pos处插入n个elem的副本无返回值

    c.insert(pos,beg,end)

    在pos处插入[beg,end)内元素的副本无返回值

    c.push_back(elem)

    在尾部添加elem的一个副本

    c.pop_back()

    移除最后一个元素,不返回

    c.push_front(elem)

    在头部添加一个elem的副本

    c.pop_front()

    移除第一个元素,不返回

    c.erase(pos)

    移除pos位置上的元素,返回下一个元素位置

    c.erase(beg,end)

    移除[beg,end)内的所有元素,返回下一元素位置

    c.resize(num)

    将容器大小改为num(元素个数),如果size()增长,新增元素以default构造函数产生出来

    c.resize(num,elem)

    将大小改为num,如果size()增长,新增元素都是elem的副本

    c.clear()

    清空容器

    3.2

    (1) deque不提供容量操作(capacity()和resrve())

    (2)安插和删除元素可能导致内存重新分配,所以指向deque元素的pointers、references、iterators会失效。

    4 list容器

    list使用一个双向链表来管理元素。

    list不支持随机操作。

    list在任何位置上安插和移除元素都非常快。

    list安插和删除动作不会造成指向其他元素的各个pointers、references、iterators失效

    list不提供下标和at()操作。

    list不提供容量和空间重新分配操作。

    仅list提供了remove()作为成员函数,list优先选择成员函数remove,其他容器使用remove()算法。

    4.1list操作函数

    (1) 构造函数和析构函数

    操作

    效果

    list<Elem> c

    产生一个空list

    list<Elem> c1(c2)

    产生一个与c2同型的list

    list<Elem> c(n)

    产生一个有n个元素的list

    list<Elem> c(n,elem)

    产生一个有n个elem元素的list

    list<Elem> c(beg,end)

    以[beg,end)内的元素为初值产生一个list

    c.~list<Elem>()

    销毁所有元素,释放内存

    (2) 非变动性操作

    操作

    效果

    c.size()

    返回元素个数

    c.empty()

    判断容器大小是否为0,比size()==0快

    c.max_size()

    返回元素的最大可能数量

    c1 == c2、c1!=c2、c1<c2、c1>c2、c1<=c2、c1>=c2

    (3)赋值

    操作

    效果

    c1==c2

    将c2的元素赋值给c1

    c.assign(n,elem)

    将n个elem的拷贝赋值给c

    c.assign(beg,end)

    将区间[beg,end)

    c.swap(c2)

    将c1和c2的元素互换

    swap(c1,c2)

    同上,此为全局函数

    (4)元素存取

    list不支持随机存取,只有front()和back()能够直接存取元素

    操作

    效果

    c.front()

    返回第一个元素,不检查是否存在

    c.back()

    返回最后一个元素,不检查是否存在

    (5)迭代器相关函数

    list迭代器函数都返回双向迭代器

    操作

    效果

    c.begin()

    返回指向第一个元素的双向迭代器

    c.end()

    返回指向最后一个元素的双向迭代器

    c.rbegin()

    返回指向逆向迭代第一个元素的逆向迭代器

    c.rend()

    返回指向逆向迭代最后一个元素的逆向迭代器

    (6) 安插、移除操作函数

    操作

    效果

    c.insert(pos,elem)

    在迭代器pos所指位置上安插一个elem副本,并返回新元素位置

    c.insert(pos,n,elem)

    在迭代器pos所指位置安插n个elem副本,无返回值

    c.insert(pos,beg,end)

    在迭代器pos所指位置安插[beg,end)内所有元素的副本,无返回值

    c.push_back(elem)

    在尾部追加一个elem副本

    c.pop_back()

    移除最后一个元素(不返回)

    c.push_front(elem)

    在头部安插一个elem副本

    c.pop_front()

    移除第一个元素(不返回)

    c.remove(val)

    移除所有值为val的元素(被移除元素占用的空间会被释放掉)

    c.remove_if(op)

    移除所有造成op(elem)断言为true的元素

    c.erase(pos)

    移除迭代器pos所指元素,返回下一个元素位置

    c.erase(beg,end)

    移除[beg,end)区间内所有元素,返回下一个元素位置

    c.resize(num)

    将元素容量变为num,如果size()变大,则以default构造函数产生新增元素

    c.resize(num,elem)

    将元素容量变为num,如果size()变大,则新增元素为elem的副本

    c.clear()

    移除所有元素,情况容器

    (7) list的特殊变动性操作

    操作

    效果

    c.unique()

    如果存在若干相邻而数值相等的元素,就移除重复的元素,只保留一个

    c.unique(op)

    如果存在若干相邻,都使op()的结果为true,则移除重复元素,只保留一个

    c1.splice(pos,c2)

    将c2的所有元素转移到c1之内、迭代器pos之前

    c1.splice(pos,c2,c2pos)

    将c2内的c2pos所指元素转移到c1内的pos位置处(c1和c2可相同)

    c1.splice(pos,c2,c2beg,c2end)

    将c2内的[c2beg,c2end)区间内的所有元素转移到c1内的pos之前(c1和c2可相同)

    c.sort()

    以operator <为准则,对所有元素排序

    c.sort(op)

    以op()为准则,对所有元素排序

    c1.merge(c2)

    将已序容器c2与已序容器c1合并,合并后仍已序

    c1.merge(c2,op)

    同上,排序规则为op()

    c.reverse()

    将所有元素反序

    5 set和multiset

    set和multiset会根据特定的排序准则,自动将元素排序,multiset允许元素重复而set不允许。

    5.1 set和multiset的操作函数

    (1) 构造函数和析构函数

    操作

    效果

    set c

    产生一个空set/multiset

    set c(op)

    以op为排序准则,产生一个空set/multiset

    set c1(c2)

    产生某个set/multiset的副本,所有元素均被复制

    set c(beg,end)

    以区间[beg,end)内的元素产生一个set/multiset

    set c(beg,end,op)

    以op为排序准则,利用[beg,end)内的元素生成一个set/multiset

    c.~set()

    销毁所有元素,释放内存

    (2) 非变动性操作

    操作

    效果

    c.size()

    返回容器大小

    c.empty()

    判断容器大小是否为0,比size()==0快

    c.max_size()

    返回可容纳的最大元素数量

    c1==c2、c1!=c2、c1<c2、c1>c2、c1<=c2、c1>=c2

    比较操作只限于型别相同容器,元素和排序准则必须有相同的型别,如果要仅仅比较元素是否相等可以用比较算法equal()

    (3)set和multiset的搜寻函数

    操作

    效果

    count(elem)

    返回“元素值为elem”的元素个数

    find(elem)

    返回“元素值为elem”的第一个元素,如果找不到就返回end()

    lower_bound(elem)

    返回elem的第一个可安插位置,也就是“元素值>=elem”的第一个元素位置

    upper_bound(elem)

    返回elem的最后一个可安插位置,也就是“元素值>elem”的第一个元素位置

    equal_range(elem)

    返回elem可安插的第一个位置和最后一个位置,也就是元素值==elem的元素区间

    eg1: lower_bound()、upper_bound()、equal_range()

    set: 1 3 5 6 7

    *lower_bound(4) == 5;  *upper_bound(4) == 5;  *equal_range(4) == { 5,5 }

    *lower_bound(5) == 5;  *upper_bound(5) == 6;  *equal_range(5) == { 5,6 };

    (4) set和multiset的赋值操作函数

    操作

    函数

    c1 = c2

    将c2中所有元素赋值给c1

    c1.swap(c2)

    将c1和c2的元素互换

    swap(c1,c2)

    同上,全局函数

    (5)迭代器相关操作函数

    set和multiset只能通过迭代器读取元素,但不能通过迭代器修改元素值,通过迭代器返回的元素被视为常数,不能通过迭代器调用任何变动性算法。

    操作

    效果

    c.begin()

    返回指向第一个元素的双向迭代器

    c.end()

    返回指向最后一个元素的双向迭代器

    c.rbegin()

    返回指向逆向遍历第一个元素的逆向迭代器

    c.rend()

    返回指向逆向遍历最后一个元素的逆向迭代器

    (6) set和multiset的元素安插和移除

    操作

    效果

    c.insert(elem)

    安插一份elem副本,返回新元素位置(不论是否成功)

    c.insert(pos,elem)

    安插一份elem的副本,返回新元素的位置

    c.insert(beg,end)

    将区间[beg,end)内所有元素的副本安插到c,无返回值

    c.erase(elem)

    移除“与elem相等”的所有元素,返回被移除的元素个数

    c.erase(pos)

    移除迭代器pos所指位置上的元素,无返回值

    c.erase(beg,end)

    移除区间[beg,end)内的所有元素,无返回值

    c.clear()

    移除全部元素,将容器清空

    5.2 注:

    (1) set和multiset不提供直接存取元素的任何操作,要改变元素值必须先删除旧元素,再插入新元素

    (2) 通过迭代器间接存取元素,从迭代器角度看,元素值是常数。

    (3) 定义排序准则的方式

    A: 以template参数定义

    eg: std::set<int, std::greater<int> > coll。这样定义时,排序准则是型别的一部分,如: std::set<int , std::less<int> > 产生的对象和coll是不同的类型。

    B: 以构造函数参数定义之

    当需要在执行期处理排序准则,或可能需要对同一种数据型别采用不同的排序准则时,可以用此方法

    (3) 元素比较动作只能用于型别相同的容器,元素和排序准则都必须相同。

    (4) set和multiset赋值过程中,如果排序准则不同,则排序准则也会被赋值或交换。赋值操作两端容器的型别必须相同。以构造函数指定排序准则时可以产生型别相同,排序准则不同的容器,这样的两个容器相互赋值或交换时排序准则也会赋值或交换(参考《C++标准程序库P181、P191》)。

    (5) 安插函数

    set提供的接口:

    pair<iterator, bool> insert(const value_type& elem);//仅有元素值

    iterator          insert(iterator pos_hint, const value_type& elem)//有提示位置和元素值

    multiset提供的接口:

    iterator insert(const value_type& elem);//因multiset允许元素值重复,故不用返回是否成功标识

    iterator insert(iterator pos_hint, const value_type& elem);

    所有容器的有位置提示且仅插入一个元素副本的安插函数都返回一个迭代器,在设计所有容器通用的安插操作函数时可用此类安插函数作为接口。

    (6) erase函数

    set和multiset利用erase来移除与某元素elem值相等的操作。序列式容器和关联式容器erase函数的不同:

    序列式容器:

    iterator erase(iterator pos);

    iterator erase(iterator beg, iterator end);

    关联式容器:

    int erase(TYPE elem);

    void erase(iterator pos);

    void erase(iterator beg, iterator end);

    在编写容器通用操作函数时,注意返回值的处理。

    6 map和multimap

    map和multimap将key/value 对当做元素,进行管理,它们可以根据key的排序准则自动将元素排序。

    key/value必须具备assignable和copyable性质。

    对排序准则而言,key必须是comparable.

    6.1 map和multimap操作函数

    (1) 构造函数和析构函数

    操作

    效果

    map    c

    产生一个空map/multimap

    map    c(op)

    以op为排序准则产生一个map/multimap

    map    c1(c2)

    产生c2的副本

    map    c(beg,end)

    以区间[beg,end)内的元素产生一个map/multimap

    map    c(beg,end,op)

    以op为准则,利用[beg,end)内的元素产生一个map/multimap

    c.~map()

    销毁所有元素,释放内存

    (2) map和multimap非变动性操作

    操作

    效果

    c.size()

    返回容器大小

    c.empty()

    判断是否为空,比size()==0快

    c.max_size()

    返回可容纳的最大元素量

    c1==c2、c1!=c2、c1<c2、c1>c2、c1<=c2、c1>=c2

    元素比较动作只能用于同型别容器

    (3) map和multimap特殊搜寻函数

    操作

    效果

    count(key)

    返回键值为key的元素个数

    find(key)

    返回键值为key的第一个元素,找不到返回end()

    lower_bound(key)

    同set

    upper_bound(key)

    同set

    equal_range(key)

    同set

    (4) map和multimap的赋值操作

    操作

    效果

    c1 == c2

    将c2所有元素赋值给c1

    c1.swap(c2)

    将c1和c2的元素互换

    swap(c1,c2)

    同上,此为全局函数

    (5) map和multimap的迭代器相关函数

    操作

    效果

    c.begin()

    返回指向第一个元素的双向迭代器

    c.end()

    返回指向最后一个元素的双向迭代器

    c.rbegin()

    返回指向逆序遍历第一个元素的逆向迭代器

    c.rend()

    返回指向逆序遍历最后一个元素的逆向迭代器

    (6) map和multimap元素的安插和删除函数

    操作

    效果

    c.insert(elem)

    安插一份elem副本,返回新元素位置

    c.insert(pos,elem)

    安插一份elem副本,返回新元素位置

    c.insert(beg,end)

    将区间[beg,end)内所有元素的副本插入c,无返回值

    c.erase(elem)

    移除与elem相等的元素,返回被移除的元素个数

    c.erase(pos)

    移除pos所指位置的元素,无返回值

    c.erase(beg,end)

    移除区间[beg,end)内所有的元素,无返回值

    c.clear()

    移除全部元素将容器清空

    (7) 直接存取操作

    操作

    效果

    m[key]

    返回一个reference,指向键值为key的元素,如果该元素不存在则插入该元素。

    6.2

    (1)为了不破坏容器内元素的次序,不可以直接改变元素的key,要修改元素的key,必须先移除该key,然后插入新的key/value的元素。从迭代器的观点来看,元素的key是常数,非常数形态的value可以修改。

    (2) 定义排序准则的方法和set类似。

    (3) 安插key/value pair时要注意key为const.三种安插方法

    A: 运用value_type,value_type是容器本身提供的型别

    std::map<std::string,float> coll;

    coll.insert(std::map<std::string,float>::value_type("otto",22.3));

    B: 运用pair<>

    std::map<std::string,float> coll;

    coll.insert(std::pair<const std::string,float>("otto",22.3));

    C: 运用make_pair()

    根据传入的两个参数构造出一个pair对象

    std::map<std::string, float> coll;

    coll.insert(std::make_pair("otto",22.3));

    (4) 注意set和map的erase(pos)无返回值,操作后pos失效。

    7 各种容器运用时机

    (1) 最简单的情况下使用vector.

    (2) 经常在头部安插和移除元素用deque。如果希望容器删除元素时自动缩进内存且可以随机存取元素也用deque.

    (3) 如果经常在容器内部安插、移除和移动元素则用list。

    (4) 如果需要以某个准则来搜寻元素,应当使用以该排序准则进行排序的set或multiset.

    (5) 想要处理key/value pair,则用map或multimap.

    8 容器内的型别

    container::value_type

    元素型别

    用于set和multiset是常数

    用于map和multimap时是pair<const key-type, value-type>

    container::reference

    元素的引用型别

    典型定义:container::value_type&

    container::const_reference

    常数元素的引用型别

    典型定义:const container::value_type&

    container::iterator

    迭代器型别

    container::const_iterator

    常数迭代器型别

    container::reverse_iterator

    反向迭代器型别

    container::const_reverse_iterator

    常数反向迭代器

    container::size_type

    无正负号整数型别,用以定义容器大小。

    container::difference_type

    有正负号整数型别,用以定义距离

    container::key_type

    用以定义关联式容器内元素的key型别

    用于set和multise时相当于value_type

    在set/multiset、map/multimap中有定义

    container::mapper_type

    用以定义关联式容器内的元素内的value型别

    map和multimap内有定义

    container::key_compare

    关联式容器内的比较准则型别

    在set/multiset/map/multimap内有定义

    container::value_compare

    用于整个元素的比较准则的型别

    在key和multiset中相当于key_compare

    在map和multimap中仅比较两个元素的key

    在set/multiset/map/multimap中有定义

    container::allocator_type

    配置型别

    在所有容器中都有定义。

  • 相关阅读:
    解决Oracle SQL Developer无法连接远程服务器的问题
    [备忘] Automatically reset Windows Update components
    在ASP.NET MVC的Action中直接接受客户端发送过来的HTML内容片段
    Rehosting the Workflow Designer
    解决Onedrive经常无法访问的问题
    最好的简明NodeJS学习材料
    最好的Python简明教程
    在Linux(ubuntu server)上面安装NodeJS的正确姿势
    在Windows中安装NodeJS的正确姿势
    在Windows环境中开始Docker的学习和体验
  • 原文地址:https://www.cnblogs.com/landy126/p/2946514.html
Copyright © 2020-2023  润新知