C++ | string

news2024/11/26 10:37:40

前言

本篇博客讲解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 与 vector
    std::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;

    // 替换所有空格
    // 创建一个包含空格的字符串 s2
    std::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, fgetc

int 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 = s1
		string(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 begin
		iterator begin() {
			return _str;//char* _str
		}
		const_iterator begin() const {
			return _str;
		}
		//end + cinst end
		iterator 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 npos
		static 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.coloradmin.cn/o/1962349.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在 Elasticsearch 中实现采集自动扩展

作者&#xff1a;来自 Elastic Pooya Salehi, Henning Andersen, Francisco Fernndez Castao 正确调整 Elasticsearch 集群的大小并不容易。集群的最佳大小取决于集群正在经历的工作负载&#xff0c;而工作负载可能会随着时间的推移而变化。自动扩展会自动调整集群大小以适应工…

【JavaScript】详解JavaScript语法

文章目录 一、变量和数据类型二、运算符三、条件语句四、循环语句五、函数六、对象和数组七、ES6新特性八、实际应用案例 JavaScript是一门广泛应用于Web开发的编程语言。掌握JavaScript语法是成为前端开发者的第一步。本文将详细介绍JavaScript的基本语法&#xff0c;包括变量…

“Assistants“ has no attribute “files“ in openAI assistants

题意&#xff1a;在 OpenAI 的助手&#xff08;assistants&#xff09;中&#xff0c;“Assistants” 没有 “files” 这个属性。 问题背景&#xff1a; assistant_file client.beta.assistants.files.create(assistant_id st.session_state.assistant_id,file_id st.sessi…

2024年最佳骨传导耳机推荐:五款不容错过的选择!

作为音乐爱好者的我&#xff0c;也一直在寻找一款好的骨传导耳机&#xff0c;听音乐对我来说不仅仅是一种消遣方式&#xff0c;更多是一种对生活、工作上压力和困难的舒缓&#xff0c;所以今天给大家推荐几款骨传导耳机。今天推荐这几款骨传导耳机都是比较有性价比&#xff0c;…

Oracle VM VirtualBox 异常退出,如何解决??

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

向量数据库性能测试工具(VectorDBBench.com)性价比排名

排名 向量数据库(不同硬件配置) 价格/性能比 QP$(每百万次查询所花费的价格)中型数据集, OpenAI 无标量过滤 QP$(每百万次查询所花费的价格)中型数据集, OpenAI 低标量过滤 QP$(每百万次查询所花费的价格)中型数据集, OpenAI 高标量过滤 QP$(每百万次查询所花费的价…

Linux系统之ftp服务配置

&#xff08;1&#xff09;查看vsftpd服务软件是否安装。 若缺少相关软件请使用yum方式安装相关软件。 &#xff08;2&#xff09;关闭防火墙和selinux保护。 &#xff08;3&#xff09;设置匿名访问。 vim /etc/vsftpd/vsftpd.conf 并通过ftp的方式在共享文件夹中创建名为“…

生成式 AI 时代的数据库:Databend 与大模型的融合探索

生成式人工智能&#xff08;Generative AI&#xff09;近年来快速崛起&#xff0c;从图像生成、自然语言处理到个性化推荐系统&#xff0c;生成式 AI 的应用范围越来越广泛。在这其中&#xff0c;数据可以说是企业在生成式 AI 时代取得成功的关键&#xff0c;每个公司都能访问相…

python100day(31-35) 玩转Linux操作系统

目录 玩转Linux操作系统操作系统发展史没有操作系统&#xff08;手工操作&#xff09;批处理系统分时系统和实时系统通用操作系统 Linux概述Linux系统优点Linux系统发行版本基础命令实用程序文件和文件夹操作管道和重定向别名文本处理 用户管理文件系统文件和路径目录结构访问权…

MVC三层框架

什么是MVC &#xff1a; Model模型 view视图 Controller控制器 早先架构&#xff1a; 用户直接访问控制层&#xff0c;控制层就可以直接操作数据库 弊端&#xff1a;程序十分臃肿&#xff0c;不利于维护 servlet的代码中&#xff1a;处理请求、响应、视图跳转、处理JDBC、处理…

鄂维南院士:人工智能的零数据、小数据、大数据和全数据方法

源自&#xff1a; 中国计算机学会 注&#xff1a;若出现无法显示完全的情况&#xff0c;可 V 搜索“人工智能技术与咨询”查看完整文章 人工智能、大数据、多模态大模型、计算机视觉、自然语言处理、数字孪生、深度强化学习 课程也可加V“人工智能技术与咨询”报名参加学习 致…

【漏洞复现】泛微E-Cology9 WorkPlanService 前台SQL注入漏洞(XVE-2024-18112)

0x01 产品简介 泛微e-cology是一款由泛微网络科技开发的协同管理平台&#xff0c;支持人力资源、财务、行政等多功能管理和移动办公。 0x02 漏洞概述 该漏洞是由于泛微e-cology未对用户的输入进行有效的过滤&#xff0c;直接将其拼接进了SQL查询语句中&#xff0c;导致系统出…

【RT-Thread】串口接收数据并找出一帧完整的报文

本文主要记录基于 RT-Thread的串口接收数据,并找出完成的一帧报文 实现: 完整的一帧数据发送出去,提示【找到一帧数据】不完整的一帧数据发出去,不做解析,2s后未收到数据,清空缓冲区单个字节接收的时间间隔定义为2s,间隔 2s 未收到数据,默认清空缓冲区【测试结果】 目…

【漏洞复现】Bazaar CVE-2024-40348 任意文件读取漏洞

声明&#xff1a;本文档或演示材料仅用于教育和教学目的。如果任何个人或组织利用本文档中的信息进行非法活动&#xff0c;将与本文档的作者或发布者无关。 一、漏洞描述 Bazaar是一个功能强大的版本控制系统&#xff0c;它能够帮助用户详细记录项目的历史变化&#xff0c;并简…

基于SpringBoot+Vue的原创歌曲分享平台(带1w+文档)

基于SpringBootVue的原创歌曲分享平台(带1w文档) 基于SpringBootVue的原创歌曲分享平台(带1w文档) 平台为了数据库结构的灵活性选择MySQL来设计&#xff0c;而java技术&#xff0c;B/S架构则保证了较高的平台适应性。本文主要介绍了平台开发背景&#xff0c;需要完成的功能与开…

Sentinel 入门与实战

一、Sentinel概念 1.1 什么是Sentinel Spring Cloud Alibaba Sentinel 是一个开源的流量控制和熔断框架&#xff0c;它是 Alibaba 开源的微服务框架 Spring Cloud Alibaba 中的一个组件。Sentinel 旨在解决分布式系统中的流量控制和熔断问题&#xff0c;帮助开发人员保护微服…

U盘格式化后数据能恢复吗?恢复方法盘点!

在数字化时代&#xff0c;U盘已成为我们日常生活和工作中不可或缺的数据存储设备。然而&#xff0c;在使用过程中&#xff0c;我们有时可能会因为各种原因对U盘进行格式化&#xff0c;从而不慎删除了重要数据。那么&#xff0c;U盘格式化后数据能恢复吗&#xff1f; 首先&…

CANoe在使用时碰到的一些很少见的Bug

CANoe作为一款成熟且稳定的总线仿真与测试工具&#xff0c;深受汽车工程师们的喜爱。CANoe虽然稳定&#xff0c;但作为一个软件来说&#xff0c;在使用中总会出现一些或大或小的Bug。最近全球范围内的大规模蓝屏事件&#xff0c;是由某个安全软件引起的。而很多CANoe使用者最近…

vue3+fetch请求+接收到流式的markdown数据+一边gpt打字机式输出内容,一边解析markdown语法+highlight.js实现代码高亮

这个问题终于解决了&#xff01;好开心。 先看最终效果&#xff1a; video_20240724_141543_edit 项目背景&#xff1a;vue3 场景&#xff1a;像gpt一样可以对话&#xff0c;当用户发送问题之后&#xff0c;ai回复&#xff0c;ai是一部分一部分回复&#xff0c;像打印机式输出…

如何将本地代码上传到github

将本地文件上传到GitHub仓库的过程通常包括以下几个步骤&#xff1a; 一 创建GitHub仓库&#xff1a; 如果你还没有一个GitHub仓库&#xff0c;首先需要在GitHub上创建一个新的仓库。登录到你的GitHub账户&#xff0c;然后点击“New repository”按钮&#xff0c;填写仓库的相关…