C++ string的常用函数以及简单模拟实现

embedded/2024/10/24 3:42:27/

在C语言,字符串就是一个以'\0'结尾的char类型的数组,管理字符串可以使用string库中提供的一系列函数。然而,这些函数与字符串是分开的,不方便操作,还容易越界访问。

在C++中,string是代表字符顺序的对象,标准的string类提供了类对象的支持,其接口与标准字符容器接口相似。

string类是basic_string类的一个实例化(char类型),并用char_traits 和allocator作为basic_string的默认参数。

一、string的常用函数

1.构造函数

string();  //构造空字符串 (默认构造函数)string (const string& str);  //拷贝构造函数string (const char* s);  //用C-string来构造string类对象string (size_t n, char c);  //构造包含n个c的字符串

size_t是无符号整型

void test_string1()
{string s1;string s2("Hello world!");string s3(6, 'b');string s4(s2);
}

2.容量大小函数

size_t size() const;  
size_t length() const;  //这两个函数作用相同,都是返回字符串的长度,不包含'\0'size_t max_size() const;  //返回字符串能存放的最大长度void reserve (size_t n = 0);  //申请将字符串的容量扩大n个字符void resize (size_t n);
void resize (size_t n, char c);  //重新调整字符串的长度至n,c用于填充扩充的新空间

*注意 resize 和 reserve 的区别:

        简单来说,reserve 只是调整容量(内存预留),不改变内容;resize 则直接修改字符串的长度及其内容。

  • reserve 一般只能增加或保持当前的容量(capacity),不能缩小它。
    • 但是实际reserve的容量实际可能会比要求的更多,这是因为编译器为了满足内存对齐,这个取决于编译器的底层实现
  • resize 用来改变字符串的长度,调整 string 的大小(size),如果新的大小比当前小,字符串将被截断;如果比当前大,字符串会扩展并用字符(默认通常是 '\0')填充。
  • 扩充,并以指定字符填充
  • 缩小,直接截断字符串
    • 但是注意,截断并不会在字符串截断后自动添加 `\0`,因为 stirng 自己管理长度,不需要依赖 `\0` 作为结尾标志。
void test_string2()
{string str = "hello world";cout << str.size() << endl;cout << str.length() << endl;cout << str.max_size() << endl;cout << str.capacity() << endl;str.reserve(20);cout << str << endl;str.resize(15, 'x');cout << str << endl;cout << str << endl;
}

利用 reserve 可以提高插入数据的效率,在需要多次插入字符时能避免多次异地扩容带来的开销。

3.访问及遍历操作函数

      char& operator[] (size_t pos);
const char& operator[] (size_t pos) const;  //返回字符串在pos位置的引用iterator begin();
const_iterator begin() const;  //返回指向字符串起始位置的迭代器iterator end();
const_iterator end() const;  //返回指向字符串末尾位置的迭代器
void test_string3()
{string s1("Hello world!");//用[]遍历s1for (size_t i = 0; i < s1.size(); i++){cout << s1[i] << " ";}cout << endl;for (size_t i = 0; i < s1.size(); i++){s1[i]++;}cout << s1 << endl;//用迭代器遍历s1string::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";++it;}cout << s1 << endl;
}


基于迭代器,还有一种迭代方式:范围for循环(range-based for loop)

范围for循环底层是通过迭代器实现的。在C++中,当使用范围for循环时,编译器实际上会调用begin()和end()函数来获取迭代器,然后通过这些迭代器遍历集合中的元素。

for (auto ch : s1)
{cout << ch << " ";
}
cout << endl;

(反汇编代码,可以看到调用了begin 和 end 来获取迭代器) 

4.修改操作函数

void push_back (char c);  //在字符串后增加一个字符c//在字符串后拼接另一个字符串
string& append (const string& str);  //另一个字符串的拷贝
string& append (const char* s);   //C形式的字符串//在字符串后拼接另一个字符串
string& operator+= (const string& str);  
string& operator+= (const char* s);const char* c_str() const;  //返回C形式的字符串(数组容器)size_t find (const string& str, size_t pos = 0) const;  //从字符串pos位置开始往后找字符串str,返回该字符在字符串中的位置size_t find (char c, size_t pos = 0) const;  //从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置string substr (size_t pos = 0, size_t len = npos) const;  //在str中从pos位置开始,截取n个字符,然后将其返回
//npos 意思是 "字符串的结尾"
std::string str1 = "Hello";
str.append(" C++");
std::cout << str1 << std::endl;  // 输出: Hello C++std::string str2 = "Hello";
str += " World";
std::cout << str2 << std::endl;  // 输出: Hello Worldstd::string str3 = "Hello";
str += " C++";
std::cout << str3 << std::endl;  // 输出: Hello C++std::string str4 = "Hello";
const char* cstr = str4.c_str();
std::cout << cstr << std::endl;  // 输出: Hellostd::string str5 = "Hello, World!";
size_t pos = str5.find("World");
if (pos != std::string::npos) 
{std::cout << "Found at position: " << pos << std::endl;// 输出: Found at position: 7  
}std::string str6 = "Hello, World!";
std::string sub = str6.substr(7, 5);
std::cout << sub << std::endl;  // 输出: World

分析 += 和 + 的函数重载的定义

string& operator+= (const string& str);
string operator+ (const string& lhs, const string& rhs);

可以发现,+= 是传引用返回 + 是传参返回,所以,能用 +=  就用 +=,  + 的代价大(产生临时对象,两次拷贝构造)

substr的应用场景常是提取文件后缀,url域名等等......

// 提取文件的的后缀
string file1("string.cpp");
size_t pos = file.rfind('.');
string suffix(file.substr(pos, file.size()-pos));
cout << suffix << endl;

二、string的简单模拟实现 

这里提供一种模拟实现的方式

#pragma once
#include<assert.h>namespace buider
{class string{public:typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}string(const char* str = ""):_size(strlen(str)),_capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity; }~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}size_t capacity() const{return _capacity;}size_t size() const{return _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=(const string& str){if (this != &str){char* temp = new char[str._capacity + 1];strcpy(temp, str._str);delete[] _str;_str = temp;_size = str._size;_capacity = str._capacity;}return *this;}const char* c_str() const{return _str;}void push_back(char ch){if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;++_size;_str[_size] = '\0';}void reserve(size_t n){if (n > _capacity){char* temp = new char[n+1];strcpy(temp, _str);delete[] _str;_str = temp;_capacity = n;}}void append(const char* str){size_t len = strlen(str);if (_capacity <= _size + len){reserve(_size + len);}strcpy(_str + _size, str);_size += len;}string& operator+=(const char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}void insert(size_t pos, const char ch){assert(pos <= _size);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 insert(size_t pos, const char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size;while (end >= (int)pos){_str[end + 1] = _str[end];--end;}_str[pos] = ch;_size++;}*/void insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_capacity <= _size + len){reserve(_size + len);}for (size_t i = _size; i >= pos; i--){_str[i + len] = _str[i];}for (size_t i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;//_str[_size] = '\0';}void erase(size_t pos, size_t len = npos){assert(pos <= _size);for (size_t i = 0; i < (_size - len - pos); i++){_str[pos + i] = _str[pos + i + len];}_size = _size - len;_str[_size] = '\0';}bool operator<(const string& s) const {return strcmp(_str, s._str) < 0;}bool operator==(const string& s) const {return strcmp(_str, s._str) == 0;}bool operator<=(const string& s) const{return *this < s || *this == s;}bool operator>=(const string& s) const{return !(*this < s);}bool operator!=(const string& s) const{return !(*this == s);}void clear(){_str[0] = '\0';_size = 0;}private:char* _str;size_t _size;size_t _capacity;const static size_t npos;};//类外初始化const size_t string::npos = -1;ostream& operator<<(ostream& out, const string& s) //类外重载,左操作符为ostream&对象{for (auto ch : s) //auto ch 自动推导出 ch 的类型。在这里,ch 会被推导为 char 类型,因为 s 是 string 类型,而 string 类的迭代器遍历的是字符。out << ch; //不是正在重载的<<, 而是标准库中已经定义的输出运算符 std::ostream& operator<<(std::ostream& out, char c);return out;}istream& operator>>(istream& in, string& s){s.clear();//确保在执行输入操作时,s能够从空白状态开始存储输入数据char buff[129];size_t i = 0;char ch;ch = in.get();while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 128){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i != 0){buff[i] = '\0';s += buff;}return in;} 
}

 其中,拷贝构造函数还有一种写法:

//拷贝并交换
string(const string& s)
{string tmp(s._str);  // 这里调用的是 string(const char*)swap(_str, tmp._str);swap(_size, tmp._size);swap(_capacity, tmp._capacity);
}

拷贝-交换 是一种常用的 C\++ 编程技巧,这个方法通过交换资源来确保代码的简洁性,避免了内存泄漏和自赋值的问题。

在这里,tmp 是 s._str 的副本,接下来通过三个 swap 实现了 tmp 的赋值,这意味着 tmp 会拥有与 s 相同的字符串数据。当 tmp 出作用域时,它会自动调用析构函数,释放原本在当前对象中的数据,避免了多余的内存拷贝和释放。


http://www.ppmy.cn/embedded/129976.html

相关文章

Java中的Vector,看着陌生?

Vector和ArrayList都是AbstractList的子类&#xff0c;二者有啥区别呢&#xff1f;Vector很少用&#xff0c;但是其父类Stack却常见在各类项目工程中。 public class Vector<E>extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.i…

Redis设计与实现 学习笔记 第九章 数据库

第9章到第14章属于本书第二部分&#xff1a;单机数据库的实现。 9.1 服务器中的数据库 Redis服务器将所有数据库都保存在服务器状态结构redis.h/redisServer的db数组中&#xff0c;db数组的每一项都是一个redis.h/redisDb结构&#xff0c;每个redisDb结构代表一个数据库&…

Widget结构(一)

1、概念 Widget 是 UI 控件的基本抽象&#xff0c;它负责描述 UI 的一部分应该如何构建。每个Widget 都有一个对应的 RenderObject&#xff0c;负责实际的布局和绘制工作。Widget 不直接参与布局或绘制过程&#xff1b;它们只是描述了如何构建用户界面的一部分&#xff0c;并且…

基于SpringBoot+Vue+uniapp的C语言在线评测系统的详细设计和实现

详细视频演示 请联系我获取更详细的演示视频 项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不…

视频云存储/音视频流媒体视频平台EasyCVR视频汇聚平台在欧拉系统中启动失败是什么原因?

视频监控/视频集中存储/磁盘阵列EasyCVR视频汇聚平台具备强大的拓展性和灵活性&#xff0c;支持多种视频流的外部分发&#xff0c;如RTMP、RTSP、HTTP-FLV、WebSocket-FLV、HLS、WebRTC、fmp4等&#xff0c;这为其在各种复杂环境下的部署提供了便利。 安防监控EasyCVR视频汇聚平…

关于QT cmake项目添加了.ui文件build未自动生成ui_xxx.h,错误提示找不到这个头文件问题处理

文章目录 问题分析举例 问题 当我们想使用cmakelist来编译运行一个QT项目时&#xff0c;当项目中存在Ui文件时&#xff0c;我们可能会遇到ui_XXX.h头文件找不到的问题。这里我们来分析并解决一下问题。 分析 不管是在cmake下或者qmake下的ui_XXX.h都是根据XXX.ui文件自动生成…

sealed class-kotlin中的封闭类

在 Kotlin 中&#xff0c;sealed class&#xff08;密封类&#xff09;是一种特殊的类&#xff0c;用于限制继承的类的数量。密封类可以被用来表示一组有限的类型&#xff0c;通常用于状态管理或表达多种可能的错误类型。 密封类用 sealed 关键字定义&#xff0c;这意味着只能…

Windows系统PyCharm右键运行.sh文件

在参考了Windows系统下pycharm运行.sh文件&#xff0c;执行shell命令_shell在pycharm-CSDN博客 和深度学习&#xff1a;PyCharm中运行Bash脚本_pycharm bash-CSDN博客 配置了右键执行.sh文件之后&#xff0c;发现在Windows的PyCharm中直接右键运行sh文件&#xff0c;存在如下…