设计网站,网站建设源代码版权问题,上海稼禾建设装饰集团网站,网络系统管理与维护机考#x1f31f;个人主页#xff1a;落叶 #x1f31f;当前专栏: C专栏 目录 为什么学习string类
C语言中的字符串
标准库中的string类 auto和范围for
auto关键字 迭代器
范围for
string类的常用接口说明和使用
1. string类对象的常见构造
2.string类对象的容量操作
3… 个人主页落叶 当前专栏: C专栏 目录 为什么学习string类
C语言中的字符串
标准库中的string类 auto和范围for
auto关键字 迭代器
范围for
string类的常用接口说明和使用
1. string类对象的常见构造
2.string类对象的容量操作
3.string类对象的访问及遍历操作
4. string类对象的修改操作
【push_back】在字符串后尾插字符c 【append】在字符串后追加一个字符串
【find npos(重 点)】从字符串pos位置开始往后找字符c返回该字符在字符串中的 位置
【rfind】从字符串pos位置开始往前找字符c返回该字符在字符串中的 位置。
【erase】从字符串中删除字符
5.string类非成员函数
【operator】尽量少用因为传值返回导致深拷贝效率低
【operator 重点】输入运算符重载
【operator 重点】输出运算符重载
【getline 重点】获取一行字符串
【relational operators 重点】大小比较
string类的模拟实现
经典的string类问题 浅拷贝 深拷贝
浅拷贝Shallow Copy
深拷贝Deep Copy
写时拷贝(了解) 为什么学习string类
C语言中的字符串 C语言中字符串是以\0结尾的一些字符的集合为了操作方便C标准库中提供了一些str系列 的库函数但是这些库函数与字符串是分离开的不太符合OOP的思想而且底层空间需要用户 自己管理稍不留神可能还会越界访问。 标准库中的string类 在使用string类时必须包含#include头文件以及using namespace std; auto和范围for
auto关键字
在这里补充2个C11的小语法方便我们后面的学习。 在早期C/C中auto的含义是使用auto修饰的变量是具有自动存储器的局部变量后来这个 不重要了。C11中标准委员会变废为宝赋予了auto全新的含义即auto不再是一个存储类型 指示符而是作为一个新的类型指示符来指示编译器auto声明的变量必须由编译器在编译时期 推导而得。用auto声明指针类型时用auto和auto*没有任何区别但用auto声明引用类型时则必须加当在同一行声明多个变量时这些变量必须是相同的类型否则编译器将会报错因为编译器实际 只对第一个类型进行推导然后用推导出来的类型定义其他变量。auto不能作为函数的参数可以做返回值但是建议谨慎使用auto不能直接用来声明数组 auto声明的变量编译器会推导而得类型。
这个typeid是获取变量的真实的类型
我们可以看到类型是对得上的。 不能没有值
// 编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项auto e;auto不能作为函数的参数可以做返回值但是建议谨慎使用 auto不能用来声明数组 当在同一行声明多个变量时这些变量必须是相同的类型否则编译器将会报错因为编译器实际 只对第一个类型进行推导然后用推导出来的类型定义其他变量。 迭代器
https://legacy.cplusplus.com/reference/string/string/begin/ 迭代器类似于指针它指向容器中的一个元素并且可以通过递增或递减来移动到下一个或前一个元素。迭代器提供了一组操作如解引用*递增递减--比较, !等。 add.begin()是指向第一个元素
add.end()是指向最后一个元素
通过i遍历每个字符 在这个例子中add.begin()返回一个指向vec第一个元素的迭代器add.end()返回一个指向add最后一个元素之后位置的迭代器。循环中i被用来遍历add中的元素*i用来访问当前元素的值。 迭代器使得算法可以与容器解耦提高了代码的复用性和灵活性。 范围for 对于一个有范围的集合而言由程序员来说明循环的范围是多余的有时候还会容易犯错误。因此 C11中引入了基于范围的for循环。for循环后的括号由冒号“ ”分为两部分第一部分是范围 内用于迭代的变量第二部分则表示被迭代的范围自动迭代自动取数据自动判断结束。范围for可以作用到数组和容器对象上进行遍历范围for的底层很简单容器遍历实际就是替换为迭代器这个从汇编层也可以看到。 范围for底层就是迭代器范围for可以作用到数组和容器对象上进行遍历。 为什么要在auto后面加引用呢 通过引用遍历可以避免不必要的拷贝构造函数的调用从而提高性能。 这样做有几个优点 性能优化如果容器中的元素很大或者元素是复杂类型如类对象通过引用遍历可以避免不必要的拷贝构造函数的调用从而提高性能。 修改元素如果需要在循环中修改容器中的元素必须通过引用来遍历它们。如果不使用引用循环变量将是容器中元素的一个副本对 副本的修改不会影响到原始容器中的元素。 举个例子auto 告诉编译器num 是一个引用 add容器中的实际元素而不是一个副本。因此如果对 num 的修改会直接 add中的元素。 string类的常用接口说明和使用
在C中std::string 类是标准库的一部分它提供了一个方便的接口来处理字符串。std::string 位于 string 头文件中并且是 std 命名空间的一部分。
#includestring
1. string类对象的常见构造
https://legacy.cplusplus.com/reference/string/string/string/ (constructor)函数名称功能说明 string() 重点 构造空的string类对象即空字符串 string(const char* s) 重点 用C-string来构造string类对象 string(size_t n, char c)string类对象中包含n个字符c string(const strings) 重点 拷贝构造函数 #includeiostream
using namespace std;
#includestringint main()
{string s1; //构造空的string对象string s2(hello);//用字符串构造string对象string s3(10, w);//用10个w构造string对象string s4(s2); //拷贝构造函数return 0;
}
2.string类对象的容量操作
函数名称 功能说明size重点返回字符串有效字符长度capacity 返回空间总大小empty 重点检测字符串释放为空串是返回true否则返回false clear 重点清空有效字符 reserve 重点为字符串预留空间** resize 重点将有效字符的个数该成n个多出的空间用字符c填充
size我们可以看到字符串有效字符长度是5
https://legacy.cplusplus.com/reference/string/string/size/ capacity返回空间总大小
https://legacy.cplusplus.com/reference/string/string/capacity/
明明只有4个字符空间大小怎么会是15呢
因为底层有个16大小的数组存放数据 当我们给满16个字符会自动扩容到32大小的空间。 empty重点检测字符串释放为空串是返回true否则返回false
https://legacy.cplusplus.com/reference/string/string/empty/
是空字符串返回true就是1不是空字符串返回false就是0 clear 重点清空有效字符
https://legacy.cplusplus.com/reference/string/string/clear/
我们可以看到清空了有效字符但是空间大小不变。 reserve 重点为字符串预留空间**
https://legacy.cplusplus.com/reference/string/string/reserve/
reserve 也就是扩容空间的意思。
数据很多的话我们可以提前扩容空间避免多次2倍扩容。 resize 重点将有效字符的个数改成n个多出的空间用\0填充
https://legacy.cplusplus.com/reference/string/string/resize/
字符不变多出来的空间都用\0填充。 这里是3abc保留cdef被清除。 注意 1. size()与length()方法底层实现原理完全相同引入size()的原因是为了与其他容器的接 口保持一致一般情况下基本都是用size()。 2. clear()只是将string中有效字符清空不改变底层空间大小。 3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个不 同的是当字符个数增多时resize(n)用0来填充多出的元素空间resize(size_t n, char c)用字符c来填充多出的元素空间。注意resize在改变元素个数时如果是将元素个数 增多可能会改变底层容量的大小如果是将元素个数减少底层空间总大小不变。 4. reserve(size_t res_arg0)为string预留空间不改变有效元素个数当reserve的参 数小于string的底层空间总大小时reserver不会改变容量大小。 3.string类对象的访问及遍历操作
函数名称功能说明operator[] 重 点返回pos位置的字符const string类对象调用begin endbegin获取一个字符的迭代器 end获取最后一个字符下一个位 置的迭代器rbegin rendbegin获取一个字符的迭代器 end获取最后一个字符下一个位 置的迭代器范围forC11支持更简洁的范围for的新遍历方式 operator[] 重 点返回pos位置的字符const string类对象调用】
https://legacy.cplusplus.com/reference/string/string/operator[]/ rbegin rend begin获取一个字符的迭代器 end获取最后一个字符下一个位 置的迭代器。
https://legacy.cplusplus.com/reference/string/string/rbegin/
正向迭代器是iterator反向迭代器是reverse_iterator。 reverse_iterator 是标准库提供的一种迭代器适配器它允许我们以逆序的方式遍历容器。reverse_iterator 的设计是为了简化反向遍历容器的过程使得我们可以像使用普通迭代器一样使用它但方向相反。 以下是使用 reverse_iterator 的几个主要原因 1.简化代码使用 reverse_iterator 可以避免手动计算容器的结束位置和开始位置之间的偏移量从而简化代码。 2.一致性它提供了一种与普通迭代器使用方式一致的方法来反向遍历容器使得代码更加直观和易于理解。 3.与算法兼容许多标准库算法都设计为与迭代器一起工作。通过使用 reverse_iterator我们可以将这些算法应用于容器的反向遍历而无需修改算法本身。 4.安全性使用 reverse_iterator 可以减少由于手动计算迭代器位置而可能引入的错误。 4. string类对象的修改操作
函数名称push_back在字符串后尾插字符cappend在字符串后追加一个字符串operator (重 点)在字符串后追加字符串strc_str(重点)返回C格式字符串find npos(重 点)从字符串pos位置开始往后找字符c返回该字符在字符串中的 位置rfind从字符串pos位置开始往前找字符c返回该字符在字符串中的 位置substr在str中从pos位置开始截取n个字符然后将其返回erase从字符串中删除字符
【push_back】在字符串后尾插字符c
https://legacy.cplusplus.com/reference/string/string/push_back/ 【append】在字符串后追加一个字符串
https://legacy.cplusplus.com/reference/string/string/append/
abcdefg插入了1111的后面 【operator (重 点)】 在字符串后追加字符串str
https://legacy.cplusplus.com/reference/string/string/operator/
可以字符串和字符 也可以一个对象 【c_str(重点)】返回C格式字符串
https://legacy.cplusplus.com/reference/string/string/c_str/
可以返回字符串 【find npos(重 点)】从字符串pos位置开始往后找字符c返回该字符在字符串中的 位置
https://legacy.cplusplus.com/reference/string/string/find/
在字符串中查找内容
下面我们可以看到从0下标位置开始查找9这个字符找到了返回9的下标没有找到返回-1 没有找到交换返回-1 【rfind】从字符串pos位置开始往前找字符c返回该字符在字符串中的 位置。
https://legacy.cplusplus.com/reference/string/string/rfind/
我们不写下标默认从最后一个下标开始往前查找。
找到了返回下标没有找到返回-1。 【substr 】 在str中从pos位置开始截取n个字符然后将其返回
https://legacy.cplusplus.com/reference/string/string/substr/
从3下标开始截取4个字符给s2。 我们也可以用find查找a这个下标 然后通过i下标这个位置截取3个字符。 【erase】从字符串中删除字符
https://legacy.cplusplus.com/reference/string/string/erase/
下面我们可以看到从下标1删除到下标5就只剩下a和g了。 5.string类非成员函数
函数功能说明operator尽量少用因为传值返回导致深拷贝效率低operator 重点输入运算符重载operator 重点输出运算符重载getline 重点获取一行字符串relational operators 重点大小比较 【operator】尽量少用因为传值返回导致深拷贝效率低
https://legacy.cplusplus.com/reference/string/string/operator/
我们可以看到s1是您好s2是落叶加起来就是您好落叶。 我们也可以加字符串 【operator 重点】输入运算符重载
https://legacy.cplusplus.com/reference/string/string/operator%3E%3E/
可以往s1对象输入字符串, 但是遇到空格就会停止 【operator 重点】输出运算符重载
https://legacy.cplusplus.com/reference/string/string/operator%3C%3C/ 【getline 重点】获取一行字符串
https://legacy.cplusplus.com/reference/string/string/getline/
这个它遇到空格不会停止会继续往后读取字符串也会把空格给读取了。 【relational operators 重点】大小比较
https://legacy.cplusplus.com/reference/string/string/operators/
计算比较大小如果大于为真返回1为假返回0。 string类的模拟实现
经典的string类问题
上面已经对string类进行了简单的介绍大家只要能够正常使用即可。在面试中面试官总喜欢让 学生自己来模拟实现string类最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析 构函数。大家看下以下string类的实现是否有问题
// 为了和标准库区分此处使用String
class String
{
public:/*String():_str(new char[1]){*_str \0;}*///String(const char* str \0) 错误示范//String(const char* str nullptr) 错误示范String(const char* str ){// 构造String类对象时如果传递nullptr指针可以认为程序非if (nullptr str){assert(false);return;}_str new char[strlen(str) 1];strcpy(_str, str);}~String(){if (_str){delete[] _str;_str nullptr;}}
private:char* _str;
};
// 测试
void TestString()
{String s1(hello bit!!!);String s2(s1);
} 说明上述String类没有显式定义其拷贝构造函数与赋值运算符重载此时编译器会合成默认 的当用s1构造s2时编译器会调用默认的拷贝构造。最终导致的问题是s1、s2共用同一块内 存空间在释放时同一块空间被释放多次而引起程序崩溃这种拷贝方式称为浅拷贝。 浅拷贝 浅拷贝也称位拷贝编译器只是将对象中的值拷贝过来。如果对象中管理资源最后就会导致 多个对象共享同一份资源当一个对象销毁时就会将该资源释放掉而此时另一些对象不知道该 资源已经被释放以为还有效所以当继续对资源进项操作时就会发生发生了访问违规。 就像一个家庭中有两个孩子但父母只买了一份玩具两个孩子愿意一块玩则万事大吉万一 不想分享就你争我夺玩具损坏。 可以采用深拷贝解决浅拷贝问题即每个对象都有一份独立的资源不要和其他对象共享。父 母给每个孩子都买一份玩具各自玩各自的就不会有问题了。 深拷贝
如果一个类中涉及到资源的管理其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给 出。一般情况都是按照深拷贝方式提供。 浅拷贝Shallow Copy 浅拷贝通常指的是对象之间的简单位拷贝bitwise copy这意味着新对象和原对象将共享相同的资源。在C中如果你没有显式地定义拷贝构造函数和赋值运算符编译器会为你生成默认的版本这些默认版本执行的是浅拷贝。 对于 std::string 来说浅拷贝是不存在的因为 std::string 内部管理着自己的动态内存。当你进行拷贝构造或赋值操作时std::string 会执行深拷贝。这意味着新创建的字符串对象拥有自己独立的内存空间与原字符串对象不共享任何资源。 深拷贝Deep Copy 深拷贝指的是创建一个新对象并且递归地复制原对象中的所有元素使得新对象和原对象完全独立。在 std::string 的情况下每次你进行拷贝构造或赋值操作时都会执行深拷贝 stringl类模拟实现【代码】
string.h
#define _CRT_SECURE_NO_WARNINGS 1#pragma once
#includeiostream
#includeassert.h
using namespace std;namespace bit
{class string{public://迭代器typedef char* iterator;static const int npos;string(const char* str );string(const string s);~string();///扩容void reserve(size_t n);//尾插字符void push_back(char ch);//尾插字符串void append(const char* str);string operator(char ch);string operator(const char* str);//传统string operator(const string s);//现代string operator(string s);//清除字符void clear(){_str[0] \0;_size 0;}//首字符iterator begin(){return _str;}//最后的iterator end(){return _str _size;}//返回_strchar* c_str() const{return _str;}//size_t size()const{return _size;}size_t capacity()const{return _capacity;}//指定位置修改字符char operator[](size_t index){assert(index _size);return _str[index];}//指定位置修改字符const char operator[](size_t index)const{assert(index _size);return _str[index];}////查询字符size_t find(char ch, size_t pos 0);//查询字符串size_t find(const char* str, size_t pos 0);//指定位置插入字符string insert(size_t pos, char c);//指定位置插入字符串string insert(size_t pos, const char* str);// 删除pos位置上的元素并返回该元素的下一个位置string erase(size_t pos, size_t len);//调整字符串大小void resize(size_t n, char c \0);//判断字符串是不是空bool empty()const;//字符串交换void swap(string s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}void swap(string s, string s1){s.swap(s1);}//流插入friend ostream operator(ostream _cout, const bit::string s);//流提取friend istream operator(istream _cin, bit::string s);private:char* _str;size_t _size;size_t _capacity;};//判断大于-小于-大于等于 - 小于等于 - 等于 - 不等于bool operator(const string lhs, const string rhs);bool operator(const string lhs, const string rhs);bool operator(const string lhs, const string rhs);bool operator(const string lhs, const string rhs);bool operator(const string lhs, const string rhs);bool operator!(const string lhs, const string rhs);
}
string.cpp
#includestring.hnamespace bit
{const int string::npos -1;//构造string::string(const char* str):_size(strlen(str)){_capacity _size;_str new char[_size 1];strcpy(_str, str);}深拷贝-(传统写法)//string::string(const string s)//{// _str new char[s._capacity 1];// strcpy(_str, s._str);// _size s._size;// _capacity s._capacity;//}//深拷贝-(现代写法)string::string(const string s){string tmp(s._str);swap(tmp);}//析构string::~string(){delete[] _str;_str nullptr;_size 0;_capacity 0;}/////扩容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 ch){if (_size _capacity){reserve(_capacity 0 ? 4 : _capacity * 2);}_str[_size] ch;_size;}//尾插字符串void string::append(const char* str){size_t len strlen(str);if ((_size len) _capacity){size_t tmp _capacity * 2;if (tmp (_size len)){tmp (_size len);}reserve(tmp);}strcpy(_str _size, str);_size len;}string string::operator(char ch){push_back(ch);return *this;}string string::operator(const char* str){append(str);return *this;}赋值-(传统写法)//string string::operator(const string s)//{// if (this ! s)// {// delete[] _str;// _str new char[s._capacity 1];// strcpy(_str, s._str);// _size s._size;// _capacity s._capacity;// }// return *this;//}//赋值-(现代写法) string string::operator(string s){swap(s);return *this;}//查询字符size_t string::find(char ch, size_t pos){assert(pos _size);for (size_t i pos; i _size; i){if (ch _str[i]){return i;}}return npos;}//查询字符串size_t string::find(const char* str, size_t pos){assert(pos _size);const char* tmp strstr(_str pos, str);if (tmp __nullptr){return npos;}return tmp - _str;}//在字符串里插入字符string string::insert(size_t pos, char ch){assert(pos _size);if (_size _capacity){reserve(_capacity 0 ? 4 : _capacity * 2);}//循环移动字符int i _size;while ((int)pos i){_str[i 1] _str[i];i--;}//在pos位置插入字符_str[pos] ch;_size;return *this;}//指定位置插入字符串string string::insert(size_t pos, const char* str){size_t len strlen(str);if ((_size len) _capacity){size_t tmp _capacity * 2;if (tmp (_size len)){tmp (_size len);}reserve(tmp);}//循环往后移动字符size_t i _size len;while (pos i){_str[i] _str[i - len];i--;}//循环把str的字符一个一个赋值给_str[posi]的位置for (size_t i 0; i len; i){_str[pos i] str[i];}_size len;return *this;}//指定位置删除字符串string string::erase(size_t pos, size_t len){if (len _size - pos){_str[pos] \0;_size pos;}else{size_t i pos len;while (i _size){_str[i - len] _str[i];i;}_size _size - len;}return *this;}//判断大于-小于-大于等于 - 小于等于 - 等于 - 不等于bool operator(const string lhs, const string rhs){return strcmp(lhs.c_str(), rhs.c_str()) 0;}bool operator(const string lhs, const string rhs){return !(lhs rhs);}bool operator(const string lhs, const string rhs){return (lhs rhs) || (lhs rhs);}bool operator(const string lhs, const string rhs){return !(lhs rhs);}bool operator(const string lhs, const string rhs){return strcmp(lhs.c_str(), rhs.c_str()) 0;}bool operator!(const string lhs, const string rhs){return !(lhs rhs);}//调整字符串大小void string::resize(size_t n, char c){_str[n] c;}//删除pos位置上的元素并返回该元素的下一个位置bool string::empty()const{if (_size ! 0){return false;}else{return true;}}//流插入ostream operator(ostream _cout, const bit::string s){for (size_t i 0; i s._size; i){_cout s._str[i];}return _cout;}//流提取istream operator(istream _cin, bit::string str){str.clear();int i 0;char buff[256];char ch;ch _cin.get();while (ch ! ch ! \n){buff[i] ch;if (i 255){buff[i] \0;str buff;i 0;}ch _cin.get();}if (i 0){buff[i] \0;str buff;}return _cin;}}
test.cpp【测试】
#includestring.h//int main()
//{
// bit::string n(asdasdf);
// n w;
// cout n.c_str() endl;
//}//int main()
//{
// bit::string n(asfsddfgdf);
// //迭代器
// bit::string::iterator i n.begin();
// while (i ! n.end())
// {
// cout *i endl;
// i;
// }
//}//int main()
//{
// bit::string n(qweeqqqw);
// //n.clear();
// //cout n.c_str() endl;
// n.insert(0, 11111111);
// cout n.c_str() endl;
//}//int main()
//{
// bit::string n(11223344556677);
// /*n.erase(4, 99);*/
// n[2] x;
// cout n.c_str() endl;
//}//int main()
//{
// bit::string n(11223344556677);
// bit::string s1(n);
// cout (n s1) endl;
// cout (n s1) endl;
// cout (n s1) endl;
// cout (n s1) endl;
// cout (n s1) endl;
// cout (n ! s1) endl;
//
//}//int main()
//{
// bit::string n(11223344556677);
// /*n.resize(8,y);
// n.resize(8);
// cout n.c_str() endl;*/
//
// cout n.empty() endl;
//}//int main()
//{
// string n(1122);
// string s1(qqwee);
// n.swap(s1);
// cout n endl;
// cout s1 endl;
//}int main()
{bit::string n(112233);bit::string n1 n;//bit::string s1(qweqwer);//n.swap(s1);//cout n endl;//cout s1 endl;/*cin n;cout n endl;*///getline(cin, n);//n n1;//swap(n, n1);//swap(n,n1);//cout n endl;cout n1 endl;
}
写时拷贝(了解) 写时拷贝就是一种拖延症是在浅拷贝的基础之上增加了引用计数的方式来实现的。 引用计数用来记录资源使用者的个数。在构造时将资源的计数给成1每增加一个对象使用该 资源就给计数增加1当某个对象被销毁时先给该计数减1然后再检查是否需要释放资源 如果计数为1说明该对象时资源的最后一个使用者将该资源释放否则就不能释放因为还有 其他对象在使用该资源。 写时拷贝
写时拷贝在读取是的缺陷