【C++】string类的使用

简介: 【C++】string类的使用

前言:在前面我们说过,前面的绝大部分内容都是在为了后面真正进入C++这块大门做铺垫,今天我们将正式的步入string类来进一步了解C++的奥妙。

d6dc0126edd141a985d72de501ef756b.jpg

string类的使用

string类对象的常见构造

  1. 字符串是表示字符序列的类
  2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
  3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)。
  4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits
    和allocator作为basic_string的默认参数(根于更多的模板信息请basic_string)。
  5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。

构造函数

1string().默认构造函数,创建一个空字符串。

int main()
{
  string emptyString;// 使用默认构造函数创建空字符串对象
  string emptyString1 = "";// 使用空字符串字面值构造函数创建空字符串对象
  string StrOutput("hello");
  emptyString = StrOutput;
  emptyString1 = StrOutput;
  cout << emptyString << endl;
  cout << emptyString1<<endl;
  return 0;
}

这两种方法都将创建一个空的字符串对象,可以在后续的代码中对其进行赋值或修改。


2.string(const char* str): 使用C风格的字符串来初始化一个string对象

int main()
{
  string StrOutput = "Hello string";//构建一个对象并赋值
  cout << StrOutput << endl;//打印对象

  const char* str_cur = "weiweiwei";//
  printf("%s\n", str_cur);//printf无法直接打印string类对象中的内容,需要转化成char*后才能打印
  string s(str_cur);
  cout << s<< endl; //cout可以直接打印
  return 0;
}


  1. string 对象(size_t n, char c) string类对象中包含n个字符c
int main()
{
  string m(10, 'a'); //string类对象中包含10个字符a
  //myString = "aaaaaaaaaa";
  cout << m << endl;
  return 0;
}

  1. string 对象(拷贝的对象,size_t,count)从拷贝对象的第t个数开始,往后拷贝count个数到 string构建的新的对象中。
int main()
{
  string s1("weiweizhoudapang");
  string s2(s1, 2, 2);//下标就是从1开始的,指代从s1中第二个字符开始一直往后两个字符都拷贝到s3中
  cout << s2 << endl;
  string s3(s1, 3);//省略最后一个参数,指从s1中第三个参数开始一直往后的所有参数全部拷贝到s1中
  cout << s3 << endl;
  cout << string(s1, 2, 2);//匿名对象直接调用
  return 0;
}


  1. string(const string&s) 是一个构造函数,它的作用是将一个传入的字符串变量s作为参数,创建一个新的字符串对象。该构造函数是通过引用的方式传递参数的,这意味着它不会创建s的拷贝,而是直接使用s的引用。
#include <iostream>
#include <string>
using namespace std;

int main() {
    // 定义一个字符串变量s并赋值
    string s = "Hello World";

    // 使用构造函数创建一个新的字符串对象s2,将s作为参数传入
    string s2(s);
    // 输出s和s2的值
    cout << "s: " << s << endl;
    cout << "s2: " << s2 << endl;
    return 0;
}

总结:

  1. cout 可直接输出 string 类的对象的内容;
  2. 使用 c_str() 方法转换 string 类型到 char* 类型时,需要为char*添加 const 关键字;
  3. printf() 函数不能直接打印 string 类的对象的内容,可以通过将 string 转换为 char* 类型,再使用 printf() 函数打印。

string类对象的容量操作

1.size():返回字符串中字符的数量,也就是字符串的长度。

int main()
{
  string str = "hello string";
  int len = str.size();
  cout << len << endl;//返回12
  return 0;
}

  1. length():同样返回字符串的长度,与size()方法功能相同。
int main()
{
  string str = "hello string";
  int len = str.length();//返回12
  cout << len;
  return 0;
}

  1. capacity():返回字符串在内存中分配的总容量,即字符串实际占用的内存大小。
int main()
{
  string str = "hello string";
  int cap = str.capacity();//返回实际字符串占用的内存
  cout << cap;
  return 0;
}

注意事项

在 C++ 的 string 类中,capacity() 函数返回当前字符串的容量,即字符串在不重新分配内存的情况下可以容纳的字符数。容量包括字符串实际存储的字符数以及额外的预留空间。

预留空间是为了避免频繁地进行内存重新分配操作,当字符串的空间不足时,string 类会自动分配一块更大的内存空间,并将原来的字符拷贝到新的内存中。

因此,capacity() 返回的容量可能会比字符串的长度(通过 length() 或 size() 函数获得)大一些。这样设计的目的是提高字符串操作的效率,避免每次添加字符时都需要重新分配内存。

需要注意的是,capacity() 返回的容量不一定和实际分配的内存大小相等。实际分配的内存大小可以通过 sizeof(string) 来获得,这个大小包括了除了存储字符之外的其他开销,比如指针、长度信息等。

另外,可以使用 reserve() 函数来显式地设置字符串的容量,这样可以避免后续的内存重新分配。但是需要注意,reserve() 并不会改变字符串的长度,只是保证了容量足够存储指定长度的字符。

  1. empty():用于判断字符串是否为空。当字符串中没有任何字符时,即长度为0时,empty()函数返回true;否则返回false。
int main() {
    std::string str1 = "Hello";
    std::string str2;

    if (str1.empty()) {
        std::cout << "str1为空" << std::endl;
    }
    else {
        std::cout << "str1不为空" << std::endl;
    }

    if (str2.empty()) {
        std::cout << "str2为空" << std::endl;
    }
    else {
        std::cout << "str2不为空" << std::endl;
    }
    return 0;
}


5.clear() :用于清空字符串的内容,即将字符串的长度设置为0,删除所有字符

int main() {
    std::string str = "Hello, World!";
    std::cout << "清空前的字符串:" << str << std::endl;
    str.clear();
    std::cout << "清空后的字符串:" << str << std::endl;
    return 0;
}


  1. reserve():用于预分配字符串的存储空间,以提高字符串的效率和性能
int main() {
    std::string str;
    std::cout << "空字符串的容量:" << str.capacity() << std::endl;

    str.reserve(100);
    std::cout << "预分配100个字符后的容量:" << str.capacity() << std::endl;

    return 0;
}

注意:

在上述示例中,我们首先创建了一个空字符串str,并使用capacity()函数获取了该空字符串的容量,结果为15(可能会因实现而异)。

接着,我们调用reserve(100)函数,将字符串str的容量预分配为100个字符,以提供更多的存储空间。再次使用capacity()函数获取容量,结果为100。

通过使用reserve()函数,我们可以提前分配足够的内存空间,避免字符串频繁重新分配内存的开销,以提高性能。请注意,reserve()函数只会增加字符串的容量,但不会改变字符串的长度。reserve(size_t res_arg=0):为string预留间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。


  1. resize()用于修改字符串的大小。将有效字符的个数该成n个,多出的空间用字符c填充。
int main() {
    std::string str = "Hello";
    std::cout << "修改前的字符串:" << str << std::endl;

    str.resize(8,'b');//将有效字符的个数该成8个,多出的空间用字符b填充。
    std::cout << "增加字符后的字符串:" << str << std::endl;

    str.resize(3);
    std::cout << "减少字符后的字符串:" << str << std::endl;

    return 0;
}


string类对象的访问及遍历操作

  1. 访问字符:可以通过下标运算符([ ])和at()函数访问字符串中的单个字符。下标从0开始计数,可以使用str[0]或者str.at(0)访问字符串str的第一个字符。

int main() {
    std::string str = "Hello string";

    for (int i = 0; i < str.size(); i++)//下标运算符([])访问字符串 
    {
        printf("%c ", str[i]);
    }
    printf("\n");
    for (int i = 0; i < str.size(); i++)//at()函数访问字符串
    {
        printf("%c ", str.at(i));
    }
    return 0;
}


迭代器(非常重要)

概念:在C++中,迭代器是一种通用的抽象概念,用于遍历容器中的元素。使用迭代器可以方便地访问容器中的元素,并进行插入、删除等操作。

  1. 获取迭代器:对于容器类对象,可以使用成员函数begin()和end()获取迭代器,begin()返回指向容器第一个元素的迭代器,end()返回指向容器最后一个元素之后的位置的迭代器。(如果你无法理解,你可以把它理解成一个指针,但是它又不是指针等到后面我们会讲)
int main()
{
  string s1 = "Hello String";
  auto it = s1.begin();//指向第一个字符的迭代器
  //或者像下面这样写
  string::iterator item;
  item = s1.begin();
  int i = 0;
  for (i = 0; i < s1.size(); i++)
  {
    cout << *(it + i);
  }
  cout << endl;
  auto its = s1.end();//指向最后一个字符的迭代器
  cout << *(its- 1) << endl;
  return 0;
}


  1. 在C++中,反向迭代器(reverse_iterator)可以用于从容器的末尾向前遍历元素。使用反向迭代器可以方便地逆序访问容器中的元素。

注意:在C++中,rbegin()和rend()是两个成员函数,可用于返回反向迭代器的起始位置和终止位置。

rbegin()函数返回一个指向容器中最后一个元素的反向迭代器(即反向迭代器的起始位置),而rend()函数返回一个指向容器中第一个元素之前位置的反向迭代器(即反向迭代器的终止位置,不包括在遍历范围内)

int main() {
    string s1 = "Hello reverse_iterator";
    // 使用反向迭代器遍历容器中的元素
    std::string::reverse_iterator rit;//构建一个反向迭代器rit
    int i = 0;
    for (rit = s1.rbegin(); rit != s1.rend(); ++rit)//反向遍历字符 
    {
        std::cout << *rit << " ";
    }
    return 0;
}


  1. 在C++中,常量迭代器(const iterator)用于遍历容器并保证容器中的元素不可修改。常量迭代器可以指向容器中的元素,并允许读取元素的值,但不允许修改元素的值。常量迭代器用于在不改变容器元素的情况下遍历容器,特别适用于需要只读访问容器的情况。
int main() {
    string s1 = "Hello const_iterator";
    // 使用 const_iterator 遍历容器
    string::iterator it;
    int i = 0;
    for (it = s1.begin(); it != s1.end(); it++)
        cout << *it;
    return 0;
}


  1. C++中的范围for语句是一种方便的循环语句,可以用来遍历一个容器(如数组、向量、列表等)中的元素。使用范围for语句可以简化代码,并且更易于阅读和理解。
    基本语法:
for (element_declaration : container) 
{
  // 循环体
}
//其中,element_declaration是一个新的变量,
//用于接收容器中的每个元素,container表示被遍历的容器。

示例演示:

int main() 
{
    int arr[] = { 1, 2, 3, 4, 5 };
    for (int element : arr)//范围for遍历数组
    {
       cout << element << " ";
    }
    cout << endl;
    string s1("weiwei zhou da pang");//范围for遍历string类的对象
    for (char element : s1)
    {
        cout << element;
    }
    return 0;
}


string类中,operator[ ]重载的使用

在C++的std::string类中,operator[]被重载了,可以用于访问字符串中的单个字符。operator[]返回一个引用,可以用于读取或修改该位置处的字符。

int main() {
    std::string str = "Hello, world!";

    // 使用 operator[] 读取字符
    char c = str[0];
    std::cout << "First character: " << c << std::endl;

    // 使用 operator[] 修改字符
    str[7] = 'W';
    std::cout << "Modified string: " << str << std::endl;

    return 0;
}

注意

  1. operator[]是一个方便且常用的方法,可以用于读取和修改字符串中的单个字符。但要注意,operator[]不会进行边界检查,因此需要确保访问的位置在字符串的有效范围内。
  2. operator[]是一个方便且常用的方法,可以用于读取和修改字符串中的单个字符。但要注意,operator[]不会进行边界检查,因此需要确保访问的位置在字符串的有效范围内。

string类对象的修改操作

C++中string类对象的修改操作可以使用以下方法:

  1. 使用赋值运算符(=)将一个字符串赋给另一个字符串对象。
string str1 = "Hello";
string str2 = "World";
str1 = str2; // str1现在的值为"World"

  1. 使用成员函数assign()将一个字符串赋给另一个字符串对象。
int main()
{
  string s1 = "weiweizhoudapang";
  string s2 = " ";
  cout << s2.assign(s1)<<endl;//把s1中的值分给了s2;
  cout << s1 << endl;//s1中的值不变
  return 0;
}


  1. 使用+=操作符将一个字符串附加到另一个字符串对象的末尾。
int main()
{
  string s1 = "weiwei";
  string s2 = "zhoudapang";
  s1 += s2;//现在s1的值就是:weiweizhoudapang
  cout << s1 << endl;
  return 0;
}


  1. 使用append()成员函数将一个字符串附加到另一个字符串对象的末尾。
string str1 = "Hello";
string str2 = "World";
str1.append(str2); // str1现在的值为"HelloWorld"
  1. 使用成员函数insert()在指定的位置插入一个字符串。
string str = "Hello";
string insertStr = " World";
str.insert(5, insertStr); // str现在的值为"Hello World"
  1. 使用成员函数erase()删除指定位置的字符。
string str = "Hello World";
str.erase(6); // str现在的值为"Hello orld"
  1. 使用成员函数replace()替换指定位置的字符串。
string str = "Hello World";
string replaceStr = "C++";
str.replace(6, 5, replaceStr); // str现在的值为"Hello C++"

  1. 使用成员函数push_back尾插一个字符
int main()
{
  string s1 = "weiwei";
  s1.push_back('c');//在s1的末尾尾插一个字符c
  cout << s1 << endl;//此时s1就是weiweic
  return 0;
}


  1. 在C++中,npos是一个常量,表示一个无效的或不存在的位置。它在string和vector等容器中经常用于表示找不到元素的情况。
    find函数用于在字符串或容器中搜索指定的元素。它返回一个迭代器,指向第一个匹配到的元素。如果没有找到匹配的元素,则返回npos。

语法使用:(需注意的是find是从前往后找)

size_t 变量名 = string 对象名.find(查找的元素,pos)
//pos指代从pos这个位置前往后找


int main()
{
    std::string str = "abcdefg";
    std::size_t found = str.find("abf");
    //如果找到会返回该字符串第一个字符的下标
    if (found != std::string::npos)//如果没有找到会返回npos 
    {
        std::cout << "子字符串找到,位置为:" << found <<"字符是: "<<str[found]<<std::endl;
    }
    else {
        std::cout << "子字符串未找到" << std::endl;
    }
    std::size_t found1 = str.find("ef");
    if (found1 != std::string::npos) {
        std::cout << "子字符串找到,位置为:"<<found1 <<"字符是: " << str[found1] << std::endl;
    }
    else {
        std::cout << "子字符串未找到" << std::endl;
    }
    std::size_t found2 = str.find("efg", 2);
        if (found2 != std::string::npos) {
        std::cout << "子字符串找到,位置为:"<<found2 <<"字符是: " << str[found2] << std::endl;
    }
    else {
        std::cout << "子字符串未找到" << std::endl;
    }
    return 0;
}


  1. 在C++中,rfind()函数用于在字符串或容器中从后往前搜索指定的元素。它返回最后一个匹配元素的位置,如果没有找到匹配的元素,则返回npos。

语法说明:size_t rfind(const string& str, size_t pos = npos) const; 其中str表示要搜索的子字符串,pos表示从哪个位置开始搜索,默认值为npos,表示从字符串的末尾开始搜索

int main() {
    std::string str = "hello world hello";
    std::string target = "hello";
    size_t found = str.rfind(target);

    if (found != std::string::npos) {
        std::cout << "子字符串找到,位置为:" << found << std::endl;
    }
    else {
        std::cout << "子字符串未找到" << std::endl;
    }

    return 0;
}


  1. 在C++中,substr()函数用于从字符串中提取子字符串。它接受两个参数:起始位置和子字符串的长度。substr()函数的语法如下:string substr(size_t pos = 0, size_t len = npos) const;

其中pos表示起始位置,默认为0,len表示子字符串的长度,默认为npos,表示提取从起始位置到字符串末尾的所有字符

int main() {
    std::string str = "Hello, World!";
    std::string sub1 = str.substr(str.find('W')); // 从字母W开始提取到末尾
    std::string sub2 = str.substr(str.find('H'), str.find('o')+1); //从字符H一直到字符o

    std::cout << "sub1: " << sub1 << std::endl; // 输出: World!
    std::cout << "sub2: " << sub2 << std::endl; // 输出: Hello
    return 0;
}

需要注意的是,substr()函数返回的是一个新的字符串,原字符串并没有被修改。因此,你可以将提取的子字符串赋值给另一个字符串变量,或者直接在输出语句中使用。

  1. 在C++中,c_strstring类的一个成员函数,它用于返回一个指向以空字符结尾的字符数组(C风格字符串)的指针。

c_str函数的语法如下:

const char* c_str() const;

该函数返回一个指向以空字符结尾的字符数组的指针,这个指针指向string对象中的字符数据。由于返回的指针是一个指向常量字符的指针,因此无法通过该指针修改string对象中的字符。同时,返回的指针是一个const指针,表示不能通过该指针修改字符串的内容。

以下是一个示例,演示如何使用c_str函数将string对象转换为C风格字符串:

#include <iostream>
#include <string>

int main() {
   std::string str = "Hello World!";
   
   const char* cstr = str.c_str();
   
   std::cout << "C风格字符串: " << cstr << std::endl;
   
   return 0;
}

输出结果为:

C风格字符串: Hello World!


在上面的示例中,我们将string对象str通过c_str函数转换为一个C风格字符串,并将它输出到控制台上。

c_str函数在需要将string对象传递给需要C风格字符串作为参数的函数或方法时非常有用。它可以提供一个与C风格字符串兼容的指针,而无需进行显式的类型转换。

以上是C++中string类对象的一些常见的修改操作方法,你可以根据需要选择合适的方法来修改字符串对象。


C++中的string与C语言字符串的区别

C++中的string类和C语言中的字符串(C风格字符串)有几个主要区别:

  1. 类型:string是C++中的标准库类,而C语言中的字符串是由字符数组表示的。
  2. 动态内存管理:string类负责管理其内部分配的内存,它会根据需要进行内存的分配和释放。而C语言中的字符串需要手动管理内存,使用mallocfree来分配和释放内存。
  3. 长度:string类中的字符串长度可以动态增长或缩小,而C语言中的字符串长度是固定的,需要事先分配足够的内存空间。
  4. 函数和操作符:string类提供了一系列成员函数来处理字符串,例如查找、替换、拼接等操作。而C语言中需要使用一些库函数来完成类似操作,如strlenstrcpystrcat等。
  5. 字符串字面量:C++中的string类可以直接使用双引号包裹的字符串字面量进行初始化,而C语言中的字符串字面量需要手动创建字符数组。
  6. 异常处理:string类在内部处理内存分配和字符串操作时,会自动抛出异常来处理错误情况。而C语言中的字符串操作通常不提供异常处理机制,需要通过返回值或指针来判断是否发生错误。

综上所述,C++中的string类提供了更加方便和安全的字符串操作方式,封装了许多底层的操作,提高了代码的可读性和可维护性。但在某些情况下,C语言的字符串仍然是必需的,特别是在需要与C语言库函数或其他C语言代码进行交互的情况下。


string类中的运算符重载

  1. 在C++中,string类支持使用"+"运算符来进行字符串的拼接操作。这个操作符允许将两个字符串连接起来产生一个新的字符串。下面是一个示例
int main() {
    std::string str1 = "Hello";
    std::string str2 = "World";
    std::string result = str1 + str2;
    str1 = str1 + " string";
    std::cout << "Concatenated string: " << result << std::endl;
    std::cout << str1 << std::endl;
    return 0;
}



  1. 在 C++ 中,std::string 类没有重载 operator>> 运算符。operator>> 运算符主要用于输入流(std::istream) 和基本数据类型之间的输入操作。如果你想要从输入流中读取并存储字符串,可以使用 std::getline() 函数。以下是一个示例:
int main() {
    std::string str;

    std::cout << "Enter a string: ";
    std::getline(std::cin, str);//输入一个字符串并存放到str中
    std::cout << "You entered: " << str << std::endl;
    return 0;
}

在上面的示例中,我们使用 std::getline() 函数从 std::cin 输入流中读取一行字符串,并将其存储在 str 变量中。然后,我们将字符串输出到控制台。

请注意,std::getline() 函数会读取整行输入,包括空格和换行符。如果你只想读取一个单词或一段不包含空格的字符串,可以使用运算符 >> 进行输入,如下所示:

int main() {
    std::string str;

    std::cout << "Enter a word: ";
    std::cin >> str;//输入一个字符串并存在str中,遇到空格和回车会停止
    std::cout << "You entered: " << str << std::endl;
    return 0;
}

在上面的示例中,我们使用 operator>> 运算符从 std::cin 输入流中读取一个单词,并将其存储在 str 变量中。然后,我们将字符串输出到控制台。


  1. 在 C++ 的 std::string 类中,提供了一组关系运算符(relational operators),用于比较两个字符串的大小关系。这些运算符包括:
  • ==:判断两个字符串是否相等,如果相等返回 true,否则返回 false。
  • !=:判断两个字符串是否不相等,如果不相等返回 true,否则返回 false。
  • <:判断一个字符串是否小于另一个字符串,如果小于返回 true,否则返回 false。
  • 大于号:判断一个字符串是否大于另一个字符串,如果大于返回 true,否则返回 false。
  • <=:判断一个字符串是否小于或等于另一个字符串,如果小于或等于返回 true,否则返回 false。
  • 小于等于:判断一个字符串是否大于或等于另一个字符串,如果大于或等于返回 true,否则返回 false。

这些运算符可以直接用于 std::string 对象之间的比较,例如:

int main()
{

    std::string str1 = "hello";
    std::string str2 = "world";

    if (str1 == str2)//判断是否相等 
    {
        std::cout << "str1 equals str2" << std::endl;
    }

    if (str1 != str2)//是否不相等 
    {
        std::cout << "str1 does not equal str2" << std::endl;
    }

    if (str1 < str2)//比较大小
    {
        std::cout << "str1 is less than str2" << std::endl;
    }

    if (str1 > str2)//同理 
    {
        std::cout << "str1 is greater than str2" << std::endl;
    }

    if (str1 <= str2) {
        std::cout << "str1 is less than or equal to str2" << std::endl;
    }

    if (str1 >= str2) {
        std::cout << "str1 is greater than or equal to str2" << std::endl;
    }
  return 0;
}

请注意,在比较字符串时会按照字典序进行比较,即通过逐个比较字符的 ASCII 值来确定大小关系。


好啦,今天的内容就到这里啦,下期内容预告string类的模拟实现,博主最近比较忙可能更新的会比较慢请见谅


结语:今天的内容就到这里吧,谢谢各位的观看,如果有讲的不好的地方也请各位多多指出,作者每一条评论都会读的,谢谢各位。


相关文章
|
16小时前
|
Java 编译器 ice
【Java开发指南 | 第十五篇】Java Character 类、String 类
【Java开发指南 | 第十五篇】Java Character 类、String 类
8 1
|
5天前
|
编译器 C++
【C++】继续学习 string类 吧
首先不得不说的是由于历史原因,string的接口多达130多个,简直冗杂… 所以学习过程中,我们只需要选取常用的,好用的来进行使用即可(有种垃圾堆里翻美食的感觉)
7 1
|
5天前
|
算法 安全 程序员
【C++】STL学习之旅——初识STL,认识string类
现在我正式开始学习STL,这让我期待好久了,一想到不用手撕链表,手搓堆栈,心里非常爽
11 0
|
5天前
|
存储 安全 测试技术
【C++】string学习 — 手搓string类项目
C++ 的 string 类是 C++ 标准库中提供的一个用于处理字符串的类。它在 C++ 的历史中扮演了重要的角色,为字符串处理提供了更加方便、高效的方法。
15 0
【C++】string学习 — 手搓string类项目
|
5天前
|
存储 C++ 容器
【C++从练气到飞升】09---string语法指南(二)
【C++从练气到飞升】09---string语法指南(二)
|
5天前
|
存储 Linux C语言
【C++从练气到飞升】09---string语法指南(一)
【C++从练气到飞升】09---string语法指南(一)
|
5天前
|
Java C++ Python
【C++从练气到飞升】06---重识类和对象(二)
【C++从练气到飞升】06---重识类和对象(二)
|
6天前
|
设计模式 安全 算法
【C++入门到精通】特殊类的设计 | 单例模式 [ C++入门 ]
【C++入门到精通】特殊类的设计 | 单例模式 [ C++入门 ]
16 0