前言
本篇博客讲解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)https://legacy.cplusplus.com/reference/string/string/?kw=string
成员函数
构造函数
、这边只需要看这两种,其他的都了解一下,构造函数其实大家都很熟悉,因为我们在类中就学习过了,所以这里string也是一个类。
Default constructor (默认构造函数):
1string();
- 用途:创建一个空的字符串对象。
- 行为:创建一个长度为 0 的字符串。
Copy constructor (复制构造函数):
1string(const string& str);
- 用途:创建一个与给定
std::string
对象相同的字符串。- 行为:创建一个与
str
完全相同的字符串副本。Substring constructor (子串构造函数):
1string(const string& str, size_t pos, size_t len = npos);
- 用途:从现有的
std::string
对象中创建一个子串。- 行为:创建一个从
str
的位置pos
开始,长度为len
的子串。如果len
为npos
(表示最大可能长度),则从pos
到字符串的末尾。From C-string constructor (从 C 风格字符串构造):
1string(const char* s);
- 用途:从 C 风格字符串(以零字符结尾的字符数组)创建一个
std::string
。- 行为:创建一个包含
s
中字符的字符串。From sequence constructor (从字符序列构造):
1string(const char* s, size_t n);
- 用途:从 C 风格字符串的前
n
个字符创建一个std::string
。- 行为:创建一个包含
s
中前n
个字符的字符串。Fill constructor (填充构造函数):
1string(size_t n, char c);
- 用途:创建一个由重复字符组成的字符串。
- 行为:创建一个长度为
n
的字符串,其中所有字符都是c
。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_str = new char[s._capacity + 1];
这行代码分配了一个新的字符数组,其大小等于原始字符串的
_capacity
加一。加一是为了容纳字符串的零终止字符。复制字符串
1strcpy(_str, s._str);
使用
strcpy
函数将原始字符串s._str
复制到新分配的_str
数组中。设置成员变量:
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}
- 成员交换:
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}
临时对象构造:
string tmp(s._str);
: 使用传入的字符串s._str
构造一个临时string
对象tmp
。这里需要注意,这行代码实际上并没有正确地复制整个字符串和相关成员变量,因为它只传递了_str
而不是整个string
对象。
s._str
: 这是一个指向s
对象内部存储的字符数组的指针。通常情况下,s._str
指向的是一个以零字符结尾的字符数组,即 C 风格的字符串。成员交换:
swap(tmp);
: 调用swap
方法交换当前对象和临时对象tmp
的所有成员变量。这个方法就等于请了一个人(tmp)帮你干
这里注意这里tmp调的不是默认构造,而是我们写的构造,注意这里的传的是字符串
析构函数
这个析构函数string也是由默认的但是我还是建议自己写,因为一般库中不符合实际要求
~string() {
delete[] _str;
_str = nullptr;
_capacity = _size = 0;
};
重载运算符=
这个必须是两个存在的对象进行赋值(可以看参数),其实这个我前面的博客我也讲过,我们来看一下他的用法
迭代器
迭代器和范围for
auto关键字
++11 中
auto
的含义在 C++11 中,
auto
被赋予了全新的含义,它不再是一个存储类型指示符,而是作为一个类型指示符来指示编译器在编译时期推导变量的类型。这使得代码更加简洁,尤其是在处理复杂的类型时。用法示例
声明普通变量:
auto x = 10; // x 的类型被推断为 int auto y = 3.14; // y 的类型被推断为 double
声明指针变量:
- 使用
auto
和auto*
没有任何区别int i = 10; auto p = &i; // p 的类型被推断为 int* auto* q = &i; // q 的类型同样被推断为 int*
声明引用变量:
- 使用
auto
声明引用变量时,必须加上&
。int j = 20; auto& r = j; // r 的类型被推断为 int&
在同一行声明多个变量:
- 如果在同一行声明多个变量,这些变量必须是相同的类型。
auto x1 = 1, x2 = 2; // x1 和 x2 的类型都推断为 int // auto x1 = 1, y1 = 3.14; // 错误,x1 和 y1 的类型不同
auto
作为函数返回类型:
auto
可以用于函数的返回类型,此时必须结合->
操作符来指定返回类型。auto add(int a, int b) -> int { return a + b; }
auto
不能作为函数的参数:
auto
不能直接用作函数参数的类型。// 错误用法 void func(auto param) { // ... }
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;
}
代码说明
读取文件内容:
- 用户输入文件名。
- 使用
fopen
打开文件,并检查是否成功。- 逐字符读取文件内容并打印。
- 关闭文件。
获取文件扩展名:
- 使用
rfind
查找最后一个点号的位置。- 使用
substr
提取从点号开始的子串作为扩展名。替换元音字母:
- 使用
find_first_of
查找元音字母的位置。- 将找到的元音字母替换为星号。
- 重复查找和替换直到没有更多的元音字母。
分割文件路径:
- 定义一个函数
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