C++ | string

前言

本篇博客讲解c++中的string类的使用(常用接口)

💓 个人主页:普通young man-CSDN博客

⏩ 文章专栏:C++_普通young man的博客-CSDN博客

⏩ 本人giee:普通小青年 (pu-tong-young-man) - Gitee.com

      若有问题 评论区见📝

🎉欢迎大家点赞👍收藏⭐文章

string的简介

        在C++中,std::string 类是用于处理文本字符串的一个非常强大的工具。它定义在 <string> 头文件中,并且是标准模板库 (STL) 的一部分。std::string 提供了一种方便的方式来存储和操作字符序列,使得开发人员能够轻松地执行常见的字符串操作,而不需要直接管理内存。

标准库中的string

接下来我会根据这个文档来讲解string

string - C++ Reference (cplusplus.com)icon-default.png?t=N7T8https://legacy.cplusplus.com/reference/string/string/?kw=string

成员函数

        构造函数

、这边只需要看这两种,其他的都了解一下,构造函数其实大家都很熟悉,因为我们在类中就学习过了,所以这里string也是一个类。

  1. Default constructor (默认构造函数):

    1string();
    • 用途:创建一个空的字符串对象。
    • 行为:创建一个长度为 0 的字符串。
  2. Copy constructor (复制构造函数):

    1string(const string& str);
    • 用途:创建一个与给定 std::string 对象相同的字符串。
    • 行为:创建一个与 str 完全相同的字符串副本。
  3. Substring constructor (子串构造函数):

    1string(const string& str, size_t pos, size_t len = npos);
    • 用途:从现有的 std::string 对象中创建一个子串。
    • 行为:创建一个从 str 的位置 pos 开始,长度为 len 的子串。如果 len 为 npos(表示最大可能长度),则从 pos 到字符串的末尾。
  4. From C-string constructor (从 C 风格字符串构造):

    1string(const char* s);
    • 用途:从 C 风格字符串(以零字符结尾的字符数组)创建一个 std::string
    • 行为:创建一个包含 s 中字符的字符串。
  5. From sequence constructor (从字符序列构造):

    1string(const char* s, size_t n);
    • 用途:从 C 风格字符串的前 n 个字符创建一个 std::string
    • 行为:创建一个包含 s 中前 n 个字符的字符串。
  6. Fill constructor (填充构造函数):

    1string(size_t n, char c);
    • 用途:创建一个由重复字符组成的字符串。
    • 行为:创建一个长度为 n 的字符串,其中所有字符都是 c
  7. Range constructor (范围构造函数):

     
    1template <class InputIterator>
    2  string(InputIterator first, InputIterator last);
    • 用途:从两个迭代器之间的范围创建一个 std::string
    • 行为:创建一个字符串,该字符串包含从 first 到 last 之间的所有字符。、
#include <iostream>
#include <string>int main() {// (1) 默认构造函数std::string empty;std::cout << "空字符串长度: " << empty.size() << std::endl;// (2) 复制构造函数std::string hello("Hello, World!");std::string copy_hello(hello);std::cout << "hello 的副本: " << copy_hello << std::endl;// (3) 子串构造函数std::string sub_hello(hello, 7, 5);std::cout << "子串: " << sub_hello << std::endl;// (4) 从 C 风格字符串构造const char* c_string = "Another example";std::string from_c_string(c_string);std::cout << "从 C 风格字符串: " << from_c_string << std::endl;// (5) 从字符序列构造const char* c_string_seq = "Sequence";std::string from_sequence(c_string_seq, 8);std::cout << "从字符序列: " << from_sequence << std::endl;// (6) 填充构造函数std::string fill_string(10, 'x');std::cout << "填充 x: " << fill_string << std::endl;// (7) 范围构造函数(迭代器)string c_array("abcdefg");std::string range_string(c_array.begin(), c_array.end());std::cout << "范围: " << range_string << std::endl;return 0;}

接下来我们看一下string的结构

class string{
public:private:char *_str;//字符指针(动态开辟)int _size;//有效字符(有效字符的下一个位置,也就是'\0')int _capacity//最大容量}

从这里你就会发现他和数组的初始化方式一样,所以这里的构造函数应该这样写:

不传参(默认构造) || 传参(普通构造)
string(const char* str = "") {_size = strlen(str);//_capacity不包含'\0'_capacity = _size;_str = new char[_capacity + 1];//这里+1是为了给'\0'//将数据拷贝到开辟的空间strcpy(_str, str);
}
拷贝构造
	string(const string& s) {_str = s._str;strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}

上面的构造函数是一种浅拷贝,这个也是编译器默认的拷贝构造,比如我们看一个场景

你会发现s3通过s1的数据来构造的时候,他们的地址是一样的

这样的话就非常危险了,假如我们s1销毁了,那这个块空间也会销毁,这个s3就会变成一个野指针,简单说就这个这两个指针都可以修改同一块空间,我们来例子

我想打印一下都不行,为什么?因为他调用了析构函数,本来这个块空间只析构一次,但是由于两个指针都指向这个块空间,所以这个空间会析构两次


我要改变的是s3的[0]小标位置的h,他却把s1也修改了,这也证明了他们是在一个空间(地址)

深拷贝构造有两种写法(传统写法 / 现代写法)

接下来我们就来解决一下上面的问题

传统写法
string(const string& s) {_str = new char[s._capacity + 1]; // 分配新的字符数组strcpy(_str, s._str);             // 复制字符串_size = s._size;                  // 设置字符串长度_capacity = s._capacity;          // 设置数组容量
}

解析:

  1. 分配内存:

    1_str = new char[s._capacity + 1];

    这行代码分配了一个新的字符数组,其大小等于原始字符串的 _capacity 加一。加一是为了容纳字符串的零终止字符。

  2. 复制字符串

    1strcpy(_str, s._str);

    使用 strcpy 函数将原始字符串 s._str 复制到新分配的 _str 数组中。

  3. 设置成员变量:

    1_size = s._size;
    2_capacity = s._capacity;

    这两行代码将新字符串的 _size_capacity 设置为与原始字符串相同的值。

现代写法
//交换
void swap(string& str) {std::swap(str._str, _str);std::swap(str._size,_size);std::swap(str._capacity, _capacity);}string(const string& s) {string tmp(s._str);swap(tmp);}

swap 方法

1void swap(string& str) {
2    std::swap(str._str, _str);
3    std::swap(str._size, _size);
4    std::swap(str._capacity, _capacity);
5}
  1. 成员交换:
    • std::swap(str._str, _str);: 交换 str._str 和 _str 指针。
    • std::swap(str._size, _size);: 交换 str._size 和 _size 的值。
    • std::swap(str._capacity, _capacity);: 交换 str._capacity 和 _capacity 的值。

这个 swap 方法将两个 string 对象的所有成员变量进行交换。这样可以快速有效地交换两个字符串的内容。

深拷贝构造函数

1string(const string& s) {
2    string tmp(s._str);
3    swap(tmp);
4}
  1. 临时对象构造:

    • string tmp(s._str);: 使用传入的字符串 s._str 构造一个临时 string 对象 tmp。这里需要注意,这行代码实际上并没有正确地复制整个字符串和相关成员变量,因为它只传递了 _str 而不是整个 string 对象。
    • s._str: 这是一个指向 s 对象内部存储的字符数组的指针。通常情况下,s._str 指向的是一个以零字符结尾的字符数组,即 C 风格的字符串。

  2. 成员交换:

    • swap(tmp);: 调用 swap 方法交换当前对象和临时对象 tmp 的所有成员变量。

这个方法就等于请了一个人(tmp)帮你干

这里注意这里tmp调的不是默认构造,而是我们写的构造,注意这里的传的是字符串

        析构函数

这个析构函数string也是由默认的但是我还是建议自己写,因为一般库中不符合实际要求

	~string() {delete[] _str;_str = nullptr;_capacity = _size = 0;};

        重载运算符=

这个必须是两个存在的对象进行赋值(可以看参数),其实这个我前面的博客我也讲过,我们来看一下他的用法


迭代器

迭代器和范围for

auto关键字

++11 中 auto 的含义

在 C++11 中,auto 被赋予了全新的含义,它不再是一个存储类型指示符,而是作为一个类型指示符来指示编译器在编译时期推导变量的类型。这使得代码更加简洁,尤其是在处理复杂的类型时。

用法示例
  1. 声明普通变量:

    auto x = 10; // x 的类型被推断为 int
    auto y = 3.14; // y 的类型被推断为 double
  2. 声明指针变量:

    • 使用 auto 和 auto* 没有任何区别
    int i = 10;
    auto p = &i; // p 的类型被推断为 int*
    auto* q = &i; // q 的类型同样被推断为 int*
  3. 声明引用变量:

    • 使用 auto 声明引用变量时,必须加上 &
    int j = 20;
    auto& r = j; // r 的类型被推断为 int&
  4. 在同一行声明多个变量:

    • 如果在同一行声明多个变量,这些变量必须是相同的类型。
    auto x1 = 1, x2 = 2; // x1 和 x2 的类型都推断为 int
    // auto x1 = 1, y1 = 3.14; // 错误,x1 和 y1 的类型不同
  5. auto 作为函数返回类型:

    • auto 可以用于函数的返回类型,此时必须结合 -> 操作符来指定返回类型。
    auto add(int a, int b) -> int {return a + b;
    }
  6. auto 不能作为函数的参数:

    • auto 不能直接用作函数参数的类型。
    // 错误用法
    void func(auto param) {// ...
    }
  7. auto 不能直接声明数组:

    • auto 不能直接用于声明固定大小的数组,因为编译器无法推断数组的大小和类型。
    // 错误用法
    auto arr[5]; // 错误,无法推断数组类型
示例代码

下面是一个综合示例,展示 auto 的不同用法:

#include <iostream>
#include <vector>int main() {// 声明普通变量auto x = 10;std::cout << "x: " << x << std::endl;// 声明指针变量int i = 10;auto p = &i;std::cout << "p: " << *p << std::endl;// 声明引用变量int j = 20;auto& r = j;std::cout << "r: " << r << std::endl;// 同一行声明多个变量auto x1 = 1, x2 = 2;std::cout << "x1: " << x1 << ", x2: " << x2 << std::endl;// 使用 auto 作为函数返回类型auto add(int a, int b) -> int {return a + b;}std::cout << "add(3, 4): " << add(3, 4) << std::endl;// 使用 auto 与 vectorstd::vector<int> vec = {1, 2, 3, 4, 5};for (auto elem : vec) {std::cout << elem << " ";}std::cout << std::endl;return 0;
}
总结
  • auto 在 C++11 中用于简化类型声明,使代码更简洁。
  • 使用 auto 时要注意变量类型的一致性和正确性。
  • auto 不能用于声明函数参数或直接声明数组。

begin 和 end

首先我从这里切入正题,先看一下迭代器的写法,我们用迭代器遍历一个string、

注意这里的*不一定是解引用,但是可以理解成解引用,begin就是起始位置,end就是结束位置

下面我们再看一个人写法:

你会发现他的值是可以改变的,这个时候他是可读可写,但如果我这样写

这个是加了const的效果,指针指向的值不能改变,这里可以看一下他的原型

这边也用一下范围for

可以看到只要加了&就可以改变这个值,等于引用,

rbegin 和 rend

这两个就是反向迭代,和上面用法一样

int main() {string s1("123456789");//利用迭代器遍历cout << "迭代器 ";std::string::reverse_iterator  it = s1.rbegin();while (it != s1.rend()){//*it = 'y';cout << *it;++it;}cout << endl;cout << "范围for ";for (auto ch = s1.rbegin(); ch != s1.rend();++ch) {cout << *ch;}}

这四个就是在前面修饰了const,所有有点冗余了,本来迭代器就是有const版本,就不需要这个。


容量

size和length和max_size

这两个成员函数都是返回字符字节的长度,想必大家都很熟悉,这边我直接展示:

  

string s1("hello C++");
cout << s1.size() << endl;
cout << s1.length() << endl;
cout << "字符串最大长度" << s1.max_size() << endl;

capacity

capacity为字符串分配的存储空间的大小,以字节为单位表示,其实这个大家也不陌生,我们先看文档

这里可以看出分配的是15这个空间是系统来帮我们分的,所以别在意是咋分的。这个我会把我的实现的string放在本篇文章的最后。

resize 和 reserve

你会发现这两个函数都是调整容量的,首先我们看一下resize

缩容等于 size - -

这边我扩容后

多余的空间他会用'\0'来填补,当然你也可以用你想填写的字符来填充,比如

reserve其实就是你提前开辟好一个大小的空间,然后就对这块空间操作,这个有什么好处?就是节约扩容成本,这个函数也可以缩容

这个是在linux下的演示,我用的vs编译器无法缩容

看一下这个函数的用法

你会发现为什么多开了11个?你想一下我们之前的容量是10字符+1个'\0'是不是刚好110

clear 和 empty 和 shrink_to_fit

这三个函数都非常好理解

clear就是清除字符串/empty这个很好理解就是判断是否为空/shrink_to_fit就是将空间缩小的适合字符的大小,也就是说假如你的字符只要10,但是你开了50,它可以把你的空间缩小到靠近10(由编译器决定);


元素访问

这边我就介绍at back front这三个函数,[]这个是下标引用,大家在数组中都用过,其实这三个接口都不如用[]

你看这三个接口我是不是都可以用[ ]来实现他们的功能,所以这里我就不演示了,太冗余了! 


修饰符

这里只介绍几个接口,因为其他接口不常用且冗余

  • swap交换字符串内容
  • += 运算符 和 append: 用于追加字符串或字符。

  • push_back: 用于追加单个字符。

  • assign: 用于重新分配字符串的内容。

  • insert: 用于在指定位置插入字符串或字符。

  • erase: 用于删除字符串中的一部分。

  • replace: 用于替换字符串中的一部分。

#include <iostream>
#include <string>int main() {// 创建一个字符串对象 s1,并初始化为 "hello"std::string s1("hello");std::cout << s1 << std::endl;// 拼接字符串// 使用 += 运算符追加字符串 " world" 和 "c++"s1 += " world";s1 += "c++";// 使用 append 方法追加字符串 " Cccccc"s1.append(" Cccccc");// 使用 push_back 方法追加单个字符 's's1.push_back('s');std::cout << "尾插:" << s1 << std::endl;// 覆盖字符串内容// 使用 assign 方法重新分配字符串的内容为 10 个字符 'X's1.assign(10, 'X');std::cout << "覆盖:" << s1 << std::endl;// 在字符串开头插入字符// 使用 insert 方法在字符串开头插入 "MDX"s1.insert(0, "MDX");std::cout << "头插:" << s1 << std::endl;// 删除字符串中的字符// 使用 erase 方法删除第一个字符s1.erase(0, 1);std::cout << "删除:" << s1 << std::endl;// 使用 erase 方法删除从位置 5 开始到倒数第二个字符之间的所有字符s1.erase(s1.begin() + 5, s1.end() - 1);std::cout << "删除:" << s1 << std::endl;// 替换字符串中的字符// 使用 replace 方法替换位置 1 和 2 之间的字符为 "%%"s1.replace(1, 2, "%%");std::cout << "pos位置替换:" << s1 << std::endl;// 提取子串// 使用 substr 方法从位置 1 开始提取长度为 5 的子串std::string s3 = s1.substr(1, 5);std::cout << "pos位置开始返回子串:" << s3 << std::endl;// 替换所有空格// 创建一个包含空格的字符串 s2std::string s2("A     B C D E F G");// 使用 find 方法查找第一个空格的位置size_t pos = s2.find(' ');while (pos != std::string::npos) {// 使用 replace 方法替换空格为 "##"s2.replace(pos, 1, "##");// 继续查找下一个空格的位置pos = s2.find(' ', pos + 2);std::cout << s2 << std::endl;}return 0;
}

#include <iostream>
#include <string>int main() {// 创建两个字符串对象 s1 和 s2,并初始化它们std::string s1 = "Hello";std::string s2 = "World";// 输出交换前的字符串内容std::cout << "交换前: s1 = " << s1 << ", s2 = " << s2 << std::endl;// 使用 swap 成员函数交换 s1 和 s2 的内容s1.swap(s2);// 输出交换后的字符串内容std::cout << "交换后: s1 = " << s1 << ", s2 = " << s2 << std::endl;return 0;
}


字符串操作

这里只介绍几个接口,因为其他接口不常用且冗余

1. c_str

返回指向字符串的 C 风格字符串的指针。

语法
const char* c_str() const;
返回值
  • 返回一个指向字符串的 C 风格字符串的指针。
示例
std::string str = "Hello, World!";
const char* cstr = str.c_str();

2. data

返回指向字符串的首字符的指针。

语法
const char* data() const;
char* data();
返回值
  • 返回一个指向字符串的首字符的指针。
示例
std::string str = "Hello, World!";
const char* ptr = str.data();

3. get_allocator

返回一个关联的分配器对象。

语法
allocator_type get_allocator() const;
返回值
  • 返回一个关联的分配器对象。
示例
std::string str = "Hello, World!";
std::allocator<char> alloc = str.get_allocator();

4. copy

将字符串的一部分复制到另一个数组中。

语法
size_type copy(char* dest, size_type n, size_type pos = 0) const;
参数
  • dest: 目标数组的起始地址。
  • n: 要复制的字符数量。
  • pos: 复制开始的位置。
返回值
  • 返回实际复制的字符数量。
示例
std::string str = "Hello, World!";
char buf[6];
str.copy(buf, 5, 0);

5. find

在字符串中查找指定的字符或子串。

语法
size_type find(const string& str, size_type pos = 0) const;
size_type find(char c, size_type pos = 0) const;
参数
  • str: 要查找的子串。
  • c: 要查找的字符。
  • pos: 开始查找的位置。
返回值
  • 如果找到,则返回找到的子串或字符的起始位置;如果没有找到,则返回 npos
示例
std::string str = "Hello, World!";
size_t pos = str.find("World");

6. rfind

在字符串中从右往左查找指定的字符或子串。

语法
size_type rfind(const string& str, size_type pos = npos) const;
size_type rfind(char c, size_type pos = npos) const;
参数
  • str: 要查找的子串。
  • c: 要查找的字符。
  • pos: 开始查找的位置。
返回值
  • 如果找到,则返回找到的子串或字符的起始位置;如果没有找到,则返回 npos
示例
std::string str = "Hello, World!";
size_t pos = str.rfind("l");

7. find_first_of

在字符串中查找指定集合中的任意字符。

语法
size_type find_first_of(const string& str, size_type pos = 0) const;
size_type find_first_of(char c, size_type pos = 0) const;
参数
  • str: 包含要查找的字符的子串。
  • c: 要查找的字符。
  • pos: 开始查找的位置。
返回值
  • 如果找到,则返回找到的字符的起始位置;如果没有找到,则返回 npos
示例
std::string str = "Hello, World!";
size_t pos = str.find_first_of("eo");

8. find_last_of

在字符串中从右往左查找指定集合中的任意字符。

语法
size_type find_last_of(const string& str, size_type pos = npos) const;
size_type find_last_of(char c, size_type pos = npos) const;
参数
  • str: 包含要查找的字符的子串。
  • c: 要查找的字符。
  • pos: 开始查找的位置。
返回值
  • 如果找到,则返回找到的字符的起始位置;如果没有找到,则返回 npos
示例
std::string str = "Hello, World!";
size_t pos = str.find_last_of("o");

9. find_first_not_of

在字符串中查找不在指定集合中的第一个字符。

语法
size_type find_first_not_of(const string& str, size_type pos = 0) const;
size_type find_first_not_of(char c, size_type pos = 0) const;
参数
  • str: 包含要排除的字符的子串。
  • c: 要排除的字符。
  • pos: 开始查找的位置。
返回值
  • 如果找到,则返回找到的字符的起始位置;如果没有找到,则返回 npos
示例
std::string str = "Hello, World!";
size_t pos = str.find_first_not_of("He");

10. find_last_not_of

在字符串中从右往左查找不在指定集合中的最后一个字符。

语法
size_type find_last_not_of(const string& str, size_type pos = npos) const;
size_type find_last_not_of(char c, size_type pos = npos) const;
参数
  • str: 包含要排除的字符的子串。
  • c: 要排除的字符。
  • pos: 开始查找的位置。
返回值
  • 如果找到,则返回找到的字符的起始位置;如果没有找到,则返回 npos
示例
std::string str = "Hello, World!";
size_t pos = str.find_last_not_of("d!");

11. substr

从字符串中提取子串。

语法
string substr(size_type pos = 0, size_type n = npos) const;
参数
  • pos: 子串开始的位置。
  • n: 子串的长度。
返回值
  • 返回从位置 pos 开始的长度为 n 的子串。
示例
std::string str = "Hello, World!";
std::string sub = str.substr(7, 5);

12. compare

比较两个字符串。

语法
int compare(const string& str) const;
int compare(size_type pos1, size_type n1, const string& str, size_type pos2 = 0, size_type n2 = npos) const;
int compare(size_type pos1, size_type n1, const char* s, size_type n2 = npos) const;
int compare(size_type pos1, size_type n1, const char* s) const;
参数
  • str: 要比较的字符串。
  • pos1: 当前字符串的起始位置。
  • n1: 当前字符串的长度。
  • pos2: 要比较的字符串的起始位置。
  • n2: 要比较的字符串的长度。
  • s: C 风格字符串。
返回值
  • 如果当前字符串小于 str,返回负数。
  • 如果当前字符串等于 str,返回 0。
  • 如果当前字符串大于 str,返回正数。
std::string str1 = "Hello, World!";
std::string str2 = "Hello, World!";
int result = str1.compare(str2);

1. 读取文件内容

#include <iostream>
#include <string>
#include <cstdio> // For fopen, fclose, fgetcint main() {std::string File;std::cout << "请输入文件名: ";std::cin >> File;FILE* fout = fopen(File.c_str(), "r");if (fout == nullptr) {std::cerr << "无法打开文件 " << File << std::endl;return 1;}char ch = fgetc(fout);while (ch != EOF) {std::cout << ch;ch = fgetc(fout);}fclose(fout);return 0;
}

2. 获取文件扩展名

#include <iostream>
#include <string>int main() {std::string s1("test.cpp.svg.c");size_t pos = s1.rfind('.');std::string s2 = s1.substr(pos);std::cout << "扩展名: " << s2 << std::endl;return 0;
}

3. 替换元音字母

#include <iostream>
#include <string>int main() {std::string str("Please, replace the vowels in this sentence by asterisks.");size_t found = str.find_first_of("aeiou");while (found != std::string::npos) {str[found] = '*';found = str.find_first_of("aeiou", found + 1);}std::cout << "替换后的字符串: " << str << std::endl;return 0;
}

4. 分割文件路径

#include <iostream>
#include <string>
void SplitFilename(const std::string& str) {std::cout << "分割: " << str << std::endl;size_t found = str.find_last_of("/\\");std::cout << "路径: " << str.substr(0, found) << std::endl;std::cout << "文件名: " << str.substr(found + 1) << std::endl;
}int main() {std::string str1("/usr/bin/man");std::string str2("c:\\windows\\winhelp.exe");SplitFilename(str1);SplitFilename(str2);return 0;
}

代码说明

  1. 读取文件内容:

    • 用户输入文件名。
    • 使用 fopen 打开文件,并检查是否成功。
    • 逐字符读取文件内容并打印。
    • 关闭文件。
  2. 获取文件扩展名:

    • 使用 rfind 查找最后一个点号的位置。
    • 使用 substr 提取从点号开始的子串作为扩展名。
  3. 替换元音字母:

    • 使用 find_first_of 查找元音字母的位置。
    • 将找到的元音字母替换为星号。
    • 重复查找和替换直到没有更多的元音字母。
  4. 分割文件路径:

    • 定义一个函数 SplitFilename,接受一个字符串参数。
    • 使用 find_last_of 查找最后的斜杠或反斜杠。
    • 使用 substr 分割路径和文件名部分。

成员常量

我就以这个接口举例吧

npos 是一个预定义的静态常量,通常用于表示“找不到”的概念,即“not-a-position”。在标准库中的 std::basic_string 类模板里,npos 被定义为 std::string::npos,并且它的值通常是 std::size_t(-1) 或者 std::numeric_limits<size_t>::max(),这取决于实现。

可以发现他是整形的最大值

substr的len是个缺省参数,所以我只写pos位置,当在字符串的成员函数中用作 len(或 sublen)参数的值时,此值表示“直到字符串的末尾”。

这里是因为编译器认为字符不可能比unsigned int大,其实实际开发确实用不到这么大

其实文档已经解释的很清楚了

非成员函数重载

这几个函数你可以参考我string的实现来理解

string常用函数的实现

string.h

#pragma once
#include<iostream>
#include<string.h>
#include<assert.h>
using namespace std;
namespace Yang {class string{public:string(const char* str = "") {_size = strlen(str);//_capacity不包含'\0'_capacity = _size;_str = new char[_capacity + 1];//这里+1是为了给'\0'//将数据拷贝到开辟的空间strcpy(_str, str);}//string(const string& s) {//	_str = s._str;//	strcpy(_str, s._str);//将目标数组的字符拷贝到现在的数组//	_size = s._size;//	_capacity = s._capacity;//}深拷贝//string(const string& s) {//	_str = new char[s._capacity + 1];//	strcpy(_str, s._str);//将目标数组的字符拷贝到现在的数组//	_size = s._size;//	_capacity = s._capacity;//}void swap(string& str) {std::swap(str._str, _str);std::swap(str._size,_size);std::swap(str._capacity, _capacity);}//s2 = s1string(const string& s) {string tmp(s._str);swap(tmp);}string& operator=(const string& s) {if (this != &s){string tmp(s._str);swap(tmp);}return *this;}//直接在参数中构造/*	string& operator=(string tmp) {swap(tmp);return *this;}*/~string() {delete[] _str;_str = nullptr;_capacity = _size = 0;};//小函数const char* c_str()const {return _str;}//返回有效个数size_t size()const {return _size;}//返回字符长度size_t length() const{return strlen(_str);}//返回容量size_t capacity()const {return _capacity;}//清除有效数据void clear() {_str[0] = '\0';_size = 0;}//迭代器typedef char* iterator;typedef const char* const_iterator;//begin + const beginiterator begin() {return _str;//char* _str}const_iterator begin() const {return _str;}//end + cinst enditerator end() {return _str + _size;}const_iterator end()const {return _str + _size;}char& operator[](size_t pos) {assert(pos <= _size);return _str[pos];}const char& operator[](size_t pos)const {assert(pos <= _size);return _str[pos];}string& operator+=(char ch);//单个字符string& operator+=(const char* str);//字符串void reserve(size_t n);void Push_back(char c);void append(const char* str);void insert(int pos, const char ch);void insert(int pos, const char* str);void erase(size_t pos,size_t len = npos);size_t find(char c, size_t pos = 0) const;size_t find(const char* str, size_t pos = 0) const;string substr(size_t pos = 0, size_t len = npos) const;private:char* _str;size_t _size;size_t _capacity;//限定当前namespace nposstatic const size_t npos;};bool operator==(const string& s1, const string& s2);bool operator<(const string& s1, const string& s2);bool operator<=(const string& s1, const string& s2);bool operator>=(const string& s1, const string& s2);bool operator>(const string& s1, const string& s2);bool operator>=(const string& s1, const string& s2);bool operator!=(const string& s1, const string& s2);ostream& operator<<(ostream& out, const string& s);istream& operator>>(istream& in, string& s);
}

构造函数

string(const char* str = "") {_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);
}

构造函数接受一个 C 风格字符串作为参数,计算其长度,并分配足够的内存来存储该字符串加上终止符 \0。然后使用 strcpy 函数将字符串拷贝到新分配的内存中。

拷贝构造函数

string(const string& s) {string tmp(s._str);swap(tmp);
}

拷贝赋值运算符用于实现深拷贝。它首先检查自赋值的情况,然后创建一个临时对象 tmp 并使用 swap 方法交换内容。

析构函数

~string() {delete[] _str;_str = nullptr;_capacity = _size = 0;
};

析构函数负责释放分配的内存,并将成员变量设置为初始状态。

成员函数

小函数
  • c_str():返回 C 风格字符串。
  • size():返回字符串的有效长度。
  • length():返回字符串的实际长度(包括 \0)。
  • capacity():返回字符串的容量。
  • clear():清除字符串内容,将其置为空字符串。
迭代器
  • iterator 和 const_iterator:定义迭代器类型。
  • begin() 和 end():返回迭代器,分别指向字符串的起始位置和结束位置。
下标访问运算符
  • operator[]:允许通过索引访问字符串中的字符。
其他成员函数
  • reserve(size_t n):预留内存。
  • Push_back(char c):向字符串末尾添加单个字符。
  • append(const char* str):向字符串末尾添加 C 风格字符串。
  • insert(int pos, const char ch) 和 insert(int pos, const char* str):在指定位置插入单个字符或 C 风格字符串。
  • erase(size_t pos, size_t len = npos):删除指定位置的字符。
  • find(char c, size_t pos = 0) 和 find(const char* str, size_t pos = 0):查找单个字符或 C 风格字符串的位置。
  • substr(size_t pos = 0, size_t len = npos):生成子串。

静态成员变量

static const size_t npos;

定义了一个静态成员变量 npos,用于表示未找到的索引位置。

string.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
//#include<string>
namespace Yang {const size_t string::npos = -1;//开空间void string::reserve(size_t n) {cout << "reserve" << n << endl;if (n > _capacity) {char* tmp = new char[n + 1];strcpy(tmp, _str);//释放原来的空间delete[] _str;//改变原来的指向_str = tmp;_capacity = n;}}//尾插void string::Push_back(char c) {if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size++] = c;_str[_size] = '\0';// \0结尾}void string::append(const char* str) {size_t len = strlen(str);if (_size + len > _capacity){//大于二倍就要多少给多少,小于二倍就二倍扩容reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);}//从size位置拷贝strcpy(_str + _size, str);_size += len;}string& string::operator+=(char c) {Push_back(c);return *this;}string& string::operator+=(const char* str) {append(str);return *this;}//头插void string::insert(int pos,const char ch) {if (_size ==  _capacity){//大于二倍就要多少给多少,小于二倍就二倍扩容reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}//所有数据移动完_str[pos] = ch;++_size;}void string::insert(int pos, const char* str) {size_t len = strlen(str);if (_size + len > _capacity){//大于二倍就要多少给多少,小于二倍就二倍扩容reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);}size_t end = _size + len + 1;while (end > pos){_str[end] = _str[end - len];--end;}for (int i = 0; i < len; i++){_str[pos + i] = *str;++str;}_size += len;}void string::erase(size_t pos, size_t len) {if (len >= _size - pos){_str[pos] = '\0';_size = pos;}else{for (size_t i = pos + len; i < _size; i++){_str[i-len] = _str[i];}_size -= len;}}size_t string::find(char c, size_t pos)const {assert(_size > pos);for (size_t i = pos; i < _size; i++){if (_str[i] == c){return i;}}	return npos;}size_t string::find(const char* str, size_t pos) const {assert(_size > pos);char* ptr = strstr(_str + pos, str);if (ptr == nullptr){return npos;}else{return ptr - _str;//指针 - 指针的到之间的个数,得到的数字就是子串的下标}}string string::substr(size_t pos, size_t len) const {assert(_size > pos);//如果len超出pos到——size范围,就直接给pos——size的范围if (len > _size - pos){len = _size + pos;}string ret;ret.reserve(len);for (int i = 0; i < len; i++){ret += _str[pos+i];}return ret;}//运算符重载bool operator==(const string& s1, const string& s2) {return strcmp(s1.c_str(), s2.c_str()) == 0;}bool operator!=(const string& s1, const string& s2) {return !(s1 == s2);}bool operator<(const string& s1, const string& s2) {return strcmp(s1.c_str(),s2.c_str() ) < 0;}bool operator<=(const string& s1, const string& s2) {return s1 < s2 || s1 == s2;}bool operator>=(const string& s1, const string& s2) {return !( s1 <= s2);}bool operator>(const string& s1, const string& s2){return !(s1 <= s2);}ostream& operator<<(ostream& out, const string& s) {for (auto ch : s){out << ch;}return out;}istream& operator>>(istream& in, string& s) {s.clear();//清除原来的数据//控制扩容,这样可以开多少空间给多少,防止浪费const int N = 100000;char buff[N];int i = 0;char ch;//in >> ch;ch = in.get();while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == N-1)//N-1也就是最后一个位置的下标{buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;i = 0;}return in;}}

命名空间

namespace Yang {...
}

所有代码都在 Yang 命名空间内,这有助于避免命名冲突。

静态成员变量

const size_t string::npos = -1;

这是 string 类的一个静态成员变量,用来表示未找到字符串的位置。

reserve 方法

void string::reserve(size_t n) {...
}

这个方法为 _str 分配至少 n 个字符的新内存,并将旧内容复制过去。如果 n 小于当前容量,则不会执行任何操作。

Push_back 方法

void string::Push_back(char c) {...
}

这个方法在字符串的末尾添加一个字符。如果添加后超过当前容量,则先调用 reserve 扩容。

append 方法

void string::append(const char* str) {...
}

这个方法在字符串的末尾追加一个新的 C 风格字符串。如果追加后超过当前容量,则先调用 reserve 扩容。

operator+= 方法

string& string::operator+=(char c) {...
}
string& string::operator+=(const char* str) {...
}

这两个重载的 operator+= 方法允许通过 += 运算符来扩展字符串,分别追加单个字符和 C 风格字符串。

insert 方法

void string::insert(int pos, const char ch) {...
}
void string::insert(int pos, const char* str) {...
}

这两个 insert 方法分别允许在指定位置插入单个字符或 C 风格字符串。如果插入后超过当前容量,则先调用 reserve 扩容。

erase 方法

void string::erase(size_t pos, size_t len) {...
}

这个方法删除从位置 pos 开始的 len 个字符。如果 len 超过了从 pos 到字符串末尾的距离,则删除从 pos 到字符串末尾的所有字符。

find 方法

size_t string::find(char c, size_t pos) const {...
}
size_t string::find(const char* str, size_t pos) const {...
}

这两个 find 方法分别用于查找单个字符和 C 风格字符串在字符串中的位置。如果找不到则返回 npos

substr 方法

string string::substr(size_t pos, size_t len) const {...
}

这个方法返回从位置 pos 开始,长度为 len 的子字符串。如果 len 超出了从 pos 到字符串末尾的距离,则返回从 pos 到字符串末尾的子字符串。

运算符重载

bool operator==(const string& s1, const string& s2) {...
}
...

这些运算符重载实现了字符串之间的比较,包括相等 (==)、不等 (!=)、小于 (<)、小于等于 (<=)、大于等于 (>=) 和大于 (>).

流操作符重载

ostream& operator<<(ostream& out, const string& s) {...
}
istream& operator>>(istream& in, string& s) {...
}

这两个流操作符重载允许通过标准输出流 (<<) 输出 string 对象,并允许通过标准输入流 (>>) 读取 string 对象。

这里面有很多的细节大家一定要仔细看看,这些是一些关于string的题可以做一做
letcode - string-CSDN博客字符串最后一个单词的长度_牛客题霸_牛客网 (nowcoder.com)string str;//这个接口可以指定换行符才读取结束//利用rfind找最后一个空格//输出长度引入必要的头文件引入输入输出流库,使得我们可以使用std::cin和std::cout。引入字符串处理库,使得我们可以使用类。使用命名空间使得我们可以直接使用std命名空间下的标识符,如coutcin和string。主函数定义int main()定义了程序的入口点。读取输入定义一个类型的变量str。https://blog.csdn.net/2302_78381559/article/details/140810076?spm=1001.2014.3001.5501

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/387610.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Magento2 常用命令以及技巧

1.Magento 命令行工具 Magento2 带有一个命令行工具&#xff0c;在windows下&#xff0c;用管理员权限打开MS-DOS命令提示符&#xff0c;然后cd到Magento根目录&#xff0c;运行下面命令&#xff0c;就可 以看到这个强大的命令行工具的命令清单&#xff1a; php bin/magentoU…

【C++】C++11中R字符串的作用

在 C11 中添加了定义原始字符串的字面量 1.定义和基本使用 定义方式为&#xff1a; R"xxx(原始字符串)xxx"其中 () 两边的字符串可以省略&#xff0c;R只会处理括号中的字符串。 原始字面量 R 可以直接表示字符串的实际含义&#xff0c;而不需要额外对字符串做转义…

半球房屋:高性价比的创新建筑选择—轻空间

在现代建筑领域&#xff0c;半球房屋因其独特的设计和高性价比受到广泛关注。这种建筑形式不仅在外观上引人注目&#xff0c;更在功能和经济效益上表现出色。以下将介绍半球房屋的主要优势&#xff0c;特别是其在成本和效益方面的突出表现。 降低建造成本&#xff0c;节约资源 …

资金管理平台 -SAP创建凭证测试程序及增强!

文章目录 主要程序创建程序程序 代码解析变量定义抬头和项目初始值表头赋值调用BAPI其他的子例程 核心内表增强部分LFACIF5D程序FI_DOCUMENT_CHECK完整程序 BADI增强 主要程序 创建程序 程序 &---------------------------------------------------------------------* *…

Matplotlib面积图绘制秘籍:让你的数据‘膨胀’起来,但不吹泡泡哦!

1. 引言 嘿&#xff0c;数据迷们&#xff01;想不想让你的数据‘活’起来&#xff0c;跳一曲色彩斑斓的面积舞&#xff1f;Matplotlib面积图&#xff0c;不只是数字的堆砌&#xff0c;它是故事的讲述者&#xff0c;让复杂数据变得一目了然&#xff0c;还带点小幽默。快来一探究…

鸿蒙应用框架开发【首选项】 本地数据与文件

首选项 简介 本示例使用ohos.data.preferences接口&#xff0c;展示了使用首选项持久化存储数据的功能。 效果预览 使用说明 1.点击顶部titleBar的右侧切换按钮&#xff0c;弹出主题菜单&#xff0c;选择任意主题则切换相应的主题界面&#xff1b; 2.退出应用再重新进入&a…

C++客户端Qt开发——界面优化(美化登录界面)

美化登录界面 在.ui中拖入一个QFream&#xff0c;顶层窗口的QWidget无法设置背景图片&#xff0c;套上一层QFrame将背景图片设置到QFrame上即可 用布局管理器管理元素&#xff1a;用户名LineEdit&#xff0c;密码LineEdit&#xff0c;记住密码ComboBox&#xff0c;登录Button…

Windows本地构建镜像推送远程仓库

下载 Docker Desktop https://smartidedl.blob.core.chinacloudapi.cn/docker/20210926/Docker-win.exe 使用本地docker构建镜像和推送至远程仓库&#xff08;harbor&#xff09; 1、开启docker的2375端口 2、配置远程仓库push镜像可以通过http harbor.soujer.com:5000ps&am…

【C语言】数据类型全解析:编程效率提升的秘诀

目录 C语言数据类型详解1. 基本数据类型1.1 整型示例代码输出结果 1.2 浮点型示例代码输出结果 1.3 字符型示例代码输出结果 2. 派生数据类型2.1 数组示例代码输出结果 2.2 指针示例代码输出结果 2.3 结构体示例代码输出结果 2.4 共用体示例代码输出结果 3. 类型限定符3.1 cons…

为什么Word中正文总会变成标题?

问题 选中文字之后点击正文&#xff0c;格式总是会自动变成标题&#xff0c;然后出现在目录中&#xff0c;改不掉。 方法 是因为段落样式的大纲级别设置了标题级别 选中识别成标题的正文&#xff0c;右键选择段落&#xff0c;把大纲级别设置成正文就好。

微信小程序云开发订单微信支付与小票和标签打印的完整高效流程

一个字“全”&#xff01;&#xff01;&#xff01; 前言一、流程设定1、如何开通云支付流程2、以订单下单为例的支付流程2.1 业务场景介绍2.2 业务场景流程图 二、代码与代码文件组成1、页面JS2、云函数payPre3、支付回调函数pay_cb3.1 准备条件3.2 必要认知3.3 pay_cb 完整函…

Llama 4训练已开启!Meta科学家最新采访,揭秘Llama 3.1是如何炼成的

Llama 3.1的诞生标志着人工智能领域的一个重要里程碑&#xff0c;它不仅是Meta在大型语言模型&#xff08;LLM&#xff09;研发上的一次重大突破&#xff0c;也代表了开源AI模型在技术进步和应用潜力上的新高度。以下是对Llama 3.1的炼成过程、观点阐述以及未来发展趋势的分析。…

莫斯科的社会生态环境之一瞥

题记 社会生态&#xff0c;它是指人类随着利用科技对环境的作用所呈现出的人和人的关系&#xff0c;人和人群的关系&#xff0c;人群和环境的关系 。而生态文明的理念&#xff0c;提倡的不止是尊重自然、顺应自然、保护自然&#xff0c;也包含注重规律的和谐性、可持续性、稳定…

如何使用CANoe自带的TCP/IP Stack验证TCP的零窗口探测机制

如果想利用CANoe自带的TCP/IP协议栈验证TCP的零窗口探测机制,就必须添加一个网络节点并配置独立的CANoe TCP/IP协议栈,作为验证对象。而与它进行TCP通信的对端也是一个网络节点,但不要配置TCP/IP协议栈,而是使用CAPL代码在底层组装TCP报文模拟TCP通信过程。这样可以尽量减少…

2024年最强网络安全学习路线,详细到直接上清华的教材!

关键词&#xff1a;网络安全入门、渗透测试学习、零基础学安全、网络安全学习路线 首先咱们聊聊&#xff0c;学习网络安全方向通常会有哪些问题前排提示&#xff1a;文末有CSDN官方认证Python入门资料包 &#xff01; 1、打基础时间太长 学基础花费很长时间&#xff0c;光语…

医院体检信息管理系统,C#体检系统源码,健康体检系统PEIS

体检服务全流程 检前 检前注意事项提醒-体检预约-套餐选择-体检签到-费用缴纳 检中 科室队列提醒-增项检中支付 检后 报告查询-体检百科-报告解读-问卷调查 体检管理系统模块介绍 一、登记管理模块 登记体检者基本信息&#xff0c;包括唯一的体检编号&#xff0c;姓名、…

【Golang 面试 - 基础题】每日 5 题(八)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/UWz06 &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏…

Miniconda快速安装conda

关注B站可以观看更多实战教学视频&#xff1a;hallo128的个人空间 安装官方网址&#xff1a;https://docs.anaconda.com/miniconda/#quick-command-line-install 1. Miniconda for Windows curl https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe -o …

【LeetCode】56. 区间合并

区间合并 题目描述&#xff1a; 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 。 示例 1&#xff1a; …

捷径,这世上有没有捷径

Q&#xff1a;大师&#xff0c;这个世界上有没有捷径&#xff1f; A&#xff1a;有呀&#xff0c;有捷径呀 Q&#xff1a;大师&#xff0c;那我要怎么走&#xff1f; A&#xff1a;你错啦&#xff0c;不要想着走捷径&#xff0c;因为捷径不是用来走的&#xff0c;捷径是用来飞的…