C++【string类用法详细介绍string类模拟实现解析】
文章目录
- string 类用法介绍及模拟实现
- 一、string介绍
- 二、string类常用接口
- 1. string类对象的常见构造接口
- 2.string类对象的常见容量接口
- 3.string类对象的常见修改接口
- 4. string类对象的常见访问及遍历接口
- 5.string其他接口
- 1.不常用查找接口
- 2.字符替换
- 3.字符串拼接
- 4.字符串排序
- 5.字符串比较
- 6.细看string中某个函数接口的用法
- 三、string类模拟实现及函数解析
- (1)迭代器开始起始位置返回
- (2)构造和拷贝函数
- (3)赋值重载和析构函数
- (4)返回重载和打印打符串
- (5)字符串字符个数和容量大小函数
- (6)字符串比较重载函数
- (7)字符串两类扩容函数
- (8)字符串插入函数和删除函数
- (9)字符串拼接和拼接重载函数
- (10)字符串交换和查找函数及清理函数
- (11)流插入和流提取的重载函数
- 四、string类模拟实现代码
- (1)simulate_string.h
- (2)test.cpp
- (3)运行结果
- 五、深浅拷贝及其他说明
string 类用法介绍及模拟实现
一、string介绍
背景:在C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。这时候就要用到C++的string类。这样就不必担心内存是否足够、字符串长度等。
介绍:string 是 C++ 中常用的一个类,也是C++标准库的重要组成部分,主要用于字符串处理 。string类本不是STL的容器,但是它与STL容器有着很多相似的操作。而且作为一个类出现,他集成的操作函数基本能完成我们大多数情况下的需要。我们可以把它看成是C++的基本数据类型。
另外还要注意这里的头文件是<string>
,不是<string.h>
,它是C字符串头文件。
关键结论:
string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类
1.string是表示字符串的字符串类
2.该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator>string。
4. 不能操作多字节或者变长字符的序列。
二、string类常用接口
1. string类对象的常见构造接口
string str:生成空字符串
string s(str):生成字符串为str的复制品
string s(str,begin,strlen):将字符串str中从下标begin开始、长度为strlen的部分作为字符串初值
string s(str, len):以str前len个字符串作为字符串s的初值
string s(num ,c):生成num个c字符的字符串
string s(str, x):将字符串str中从下标x开始到字符串结束的位置作为字符串初值
string(const string&s) :拷贝构造函数
string str1; //生成空字符串string str2("asdf"); //生成"asdf"的复制品string str3("12345", 1, 4);//结果为"2345"string str4("012345", 5); //结果为"01234"string str5(6, 'a'); //结果为"aaaaaa"string str6(str2, 2); //结果为"df"string s3(s2); // 拷贝构造s3
2.string类对象的常见容量接口
size: 返回字符串有效字符长度
length :返回字符串有效字符长度
capacity:返回空间总大小
empty :检测字符串释放为空串,是返回true,否则返回false
clear:清空有效字符
reserve:为字符串预留空间,不改变有效元素个数,当reserve的参数小于
string的底层空间总大小时,reserve不会改变容量大小。
resize:将有效字符的个数该成n个,多出的空间用字符填充,不够进行扩容再初始化。删除数据,容量大小不会变。如果将元素个数增多,size会变,可能会改变底层容量的大小。
在扩容的时候vs下是从15开始的,先以二倍进行扩容一次,后面都是以1.5倍进行扩容。
string s("hello nza");cout << s.size() << endl;//9cout << s.length() << endl;//9cout << s.capacity() << endl;//15s.clear(); // 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小cout << s << endl;空cout << s.size() << endl;//0cout << s.capacity() << endl;//15s.resize(10, 'a');//将s中有效字符个数增加到10个,多出位置用'a'进行填充cout << s.size() << endl;//10cout << s.capacity() << endl;//15s.resize(16);// 将s中有效字符个数增加到16个,多出位置用缺省值'\0'进行填充,空间原来为15,不够进行扩容cout << s.size() << endl;//16cout << s.capacity() << endl;//已扩容cout << s << endl;s.resize(5);// 将s中有效字符个数缩小到5个cout << s.size() << endl;//5cout << s.capacity() << endl;//31cout << s << endl;//aaaaastring s("aaa");s.reserve(166);//175cout << s.size() << endl;//3cout << s.capacity() << endl;//175s.reserve(50);cout << s.size() << endl;//3cout << s.capacity() << endl;//175
}
3.string类对象的常见修改接口
push_back:在字符串后尾插字符c
append:在字符串后追加一个字符串
operator+= :在字符串后追加字符串str
c_str:返回C格式字符串
find: 在当前字符串的pos(默认0)索引位置开始,查找子串s,返回找到的位置索引.
rfind:从字符串pos(默认npos)位置开始往前找字符c,返回该字符在字符串中的位置
substr:在str中从pos位置开始,截取n个字符,然后将其返回
insert(pos,char):在指定的位置pos前插入字符char
iterator erase(iterator p);删除字符串中p所指的字符
iterator erase(iterator first, iterator last):删除字符串中迭代器区间[first,last)上所有字符
string& erase(size_t pos = 0, size_t len = npos):删除字符串中从索引位置pos开始的len个字符
string str;str.append("hello"); // 在str后追加一个字符"hello"str += 'n'; // 在str后追加一个字符'n' str += "za"; // 在str后追加一个字符串"za"cout << str << endl;//hellonzacout << str.c_str() << endl; // 以C语言的方式打印hellonzastring f("string.cpp");size_t pos = f.rfind('.');6string sub1(f.substr(pos, 3));cout << sub1<< endl;.cpstring url("http://www.nzanzanza.com/kd/kd/kd/");size_t start = url.find("://");//4start += 3;//变为7size_t finish = url.find('/', start);//24string address = url.substr(start, finish - start);cout << address << endl;//www.nzanzanza.com//插入一个字符string s("aaaa");s.insert(1,"555");cout << s << endl;//a555aaastring s1("rrrr");s1.insert(0, 1, 'z');cout << s1;//zrrrrstring s1("abc");cout<<s1<<endl; // s1:abcs1.insert(s1.begin(),'1');// insert(pos,char):在指定的位置pos前插入字符charcout<<s1<<endl; // s1:1abc// 尾插一个字符string s1;s1.push_back('a');s1.push_back('b');s1.push_back('c');cout<<s1<<endl; // s1:abc//删除一个字符string s1 = "123456789";s1.erase(s1.begin()+1); // 结果:13456789s1.erase(s1.begin()+1,s1.end()-2); // 结果:189s1.erase(1,6); // 结果:189
4. string类对象的常见访问及遍历接口
operator[] :返回pos位置的字符,const string类对象调用
begin+ end:(正向迭代器)begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
rbegin + rend:(反向迭代器)begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
范围for :C++11支持更简洁的范围for的新遍历方式
string s("hello nza");// 1. for+operator[]for (size_t i = 0; i < s.size(); ++i){cout << s[i];}cout << endl;// 2.迭代器string::iterator it = s.begin();while (it != s.end()){cout << *it ;++it;}cout << endl;//hellonzastring::reverse_iterator rit = s.rbegin();// C++11之后,直接使用auto定义迭代器,让编译器推到迭代器的类型//auto rit=s.rbegin();while (rit != s.rend()){cout << *rit ;++rit;}cout << endl;//aznolleh// 3.范围forfor (auto ch : s)cout << ch;//hellonza
5.string其他接口
1.不常用查找接口
1.size_tfind_first_of (const char* s, size_t pos = 0) const:在当前字符串的pos索引位置开始,查找子串s的字符,返回找到的位置索引,-1表示查找不到字符
2.size_tfind_first_not_of (const char* s, size_t pos = 0) const:在当前字符串的pos索引位置开始,查找第一个不位于子串s的字符,返回找到的位置索引,-1表示查找不到字符
3.size_t find_last_of(const char* s, size_t pos = npos) const:当前字符串的pos索引位置开始,查找最后一个位于子串s的字符,返回找到的位置索引,-1表示查找不到字符
4.size_tfind_last_not_of (const char* s, size_t pos = npos) const:在当前字符串的pos索引位置开始,查找最后一个不位于子串s的字符,返回找到的位置索引,-1表示查找不到子串
string s("ddd bbrb ccccccc bbrb ccc");cout << s.find_first_of("mmmmbr98") << endl; // 结果是:4cout << s.find_first_not_of("hhh ddd ") << endl;// 结果是:4cout << s.find_last_of("13r98") << endl; // 结果是:19cout << s.find_last_not_of("teac") << endl; // 结果是:21
2.字符替换
- string& replace(size_t pos, size_t n, const char *s);将当前字符串从pos索引开始的n个字符,替换成字符串s
- string& replace(size_t pos, size_t n, size_t n1, char c); 将当前字符串从pos索引开始的n个字符,替换成n1个字符c
- string& replace(iterator i1, iterator i2, const char* s);将当前字符串[i1,i2)区间中的字符串替换为字符串s
string s1("hello world?");cout<<s1.size()<<endl; // 结果:12s1.replace(s1.size()-2,2,3,'.'); // 结果:hello worl...s1.replace(6,5,"green"); // 结果:hello green.// s1.begin(),s1.begin()+5 是左闭右开区间s1.replace(s1.begin(),s1.begin()+5,"red"); // 结果:red green.cout<<s1<<endl;
3.字符串拼接
append() & + 操作符:拼接字符串
string s1("aaa");s1.append("bbb");cout<<"s1:"<<s1<<endl; // s1:aaabbb// 方法二:+ 操作符string s2 = "aaa";string s3 = "bbb";s2 += s3.c_str();cout<<s2<<endl; // s2:aaabbb
4.字符串排序
sort(s.begin(),s.end()):排序 (要加#include <algorithm>
头文件)
string s = "cdefba";sort(s.begin(),s.end());cout<<s<<endl; // 结果:abcdef
5.字符串比较
- C ++字符串支持常见的比较操作符(>,>=,<,<=,==,!=),两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇’\0’为止。
cout << (string("1") < string("4")) << endl;//打印为1string str1("666");string str2("222");bool ret;ret = str1 > str2;cout << ret << endl; // 1str1 = "111";ret = str1 > str2;cout <<ret << endl; // 0str1 = "111";ret = str1 <str2;cout <<ret<< endl;//1string s1("fds");cout << (s1 < "few") << endl;//1
- 第二种用成员函数compare()。支持多参数处理,用索引值和长度定位子串来进行比较。 他返回一个整数来表示比较结果:1表示大于 ,-1表示小于, 0表示相等。
string A("dag");string B("cvb");string C("2111");string D("9850");cout<< A.compare(B) << endl; // dag和cvbv比较 打印为1cout << A.compare(1, 1, B) << endl;// 拿A的第一个位置起一个字符和B比较 结果:-1cout << C.compare(0, 3, D, 0, 3) << endl;//C第0位置前三个字符211和D前三个字符985比较,结果为-1
6.细看string中某个函数接口的用法
可登录www.cplusplus.com查看
三、string类模拟实现及函数解析
(1)迭代器开始起始位置返回
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;}
开始直接返回首元素地址,结束返回的是最后一个元素的下一个位置的地址,另外迭代器还有const类型的,与它构成函数重载。
(2)构造和拷贝函数
string(const char* str = ""):_size(strlen(str)){_capacity = _size == 0 ? 3 : _size;_str = new char[_capacity + 1];strcpy(_str, str);}string(const string& s):_size(s._size), _capacity(s._capacity){_str = new char[s._capacity + 1];strcpy(_str, s._str);}
1、构造函数:在构造函数里面传常量,那么私有域里面的_str就要加const,就变成在常量区,这样_str就不允许修改,扩容也不扩不了,而这里还需要修改内容,不够还需要扩容,所以最好空间是new出来的,从堆上申请出来的。
2、现在初始化列表就初始化一个_size,因为初始化列表是按private里面的顺序进行初始化,私有域一修改,可能会出现问题,而且用多了strlen,会造成时间浪费,所以后面的开空间和_capacity初始化操作放在里面。capacity+1存的是有效字符。
3、里面并给缺省值,这里面不能给空指针,strlen会对它进行解引用会崩溃,遇到\0终止,也不能’\0‘,类型不匹配,char转变成了int,也会当成空指针,还是一个崩溃,正确写法"\0",这是一个常量字符串,遇到\0终止,长度为0或者写成" "因为默认都是以\0结束的。
4、拷贝构造函数:如果是默认生成是浅拷贝,如果涉及空间问题就得手动写,因为会指向同一个空间,析构两次,会崩溃,修改还会互相影响。所以要有独立的空间,先把size和capacity放在初始化列表,里面开一个和是s.capacity一样大的空间,最后strcpy。
(3)赋值重载和析构函数
string& operator=(const string& s){if (this != &s){char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}
1、赋值重载:赋值同样会涉及到深拷贝和浅拷贝问题,如果被赋值的空间比要赋值的空间大,赋值过去,前者另外的空间会浪费掉,那就先开一个和后者一样大的临时空间,再把旧空间(前者)释放掉,再把开的空间赋值给前者,这样还间接解决了如果被赋值的空间比要赋值的空间小的问题。还有一个问题自己给自己赋值,会出现随机值,因为旧空间释放掉了,会变成随机值,所以加一个if判断语句,如果相等直接返回this。(而且因new失败而破坏旧空间,需要把delete放到new后面)。
2、析构函数:先释放_str,并把它置为空,并把size和capacity置为0。
(4)返回重载和打印打符串
const char* c_str(){return _str;}const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}
1、打印字符串,直接返回首地址,进行打印。
2、返回值重载:为了能让实例化的对象进行[]访问元素,进行赋值重载,为了防止越界,需要断言一下,返回_str数组对应的下标元素。为了适应传来的const对象并能够进行顺利返回,需要进行函数重载。
(5)字符串字符个数和容量大小函数
size_t size() const{return _size;}size_t capacity() const{return _capacity;}
1、字符个数:直接返回_size,因为实例化对象调用构造函数就算出来_size。
2、容量大小:同理。
(6)字符串比较重载函数
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 || s == *this;}bool operator<(const string& s) const{return !(*this >= s);}bool operator<=(const string& s) const{return !(*this > s);}bool operator!=(const string& s) const{return !(*this == s);}
1、字符串比较重载函数: 为了能让对象进行比较,进行比较重载,先写出两个>,==,里面直接调用strcmp,其余的进行复用。
(7)字符串两类扩容函数
void resize(size_t n, char ch = '\0'){if (n < _size){// 删除数据--保留前n个_size = n;_str[_size] = '\0';}else if (n > _size){if (n > _capacity){reserve(n);}size_t i = _size;while (i < n){_str[i] = ch;++i;}_size = n;_str[_size] = '\0';}}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}
1、resize扩容函数:它的特点是开空间初始化,size会随传入的n的大小而变化,如果是<当前的_size会删除数据,反之会补充剩余的字符。用一个if esle语句去解决,如果小于就把_size更新成n,并把最后一个位置置为0,else里面需要看是否扩容,如果需要直接复用reserve,反之用while循环进行插入,最后更新_size。
2、reserve扩容函数: 它的特点是提前开好空间,size不会变,如果传入的n大于当前容量,就异地扩,开辟另一个空间,把_str的内容赋值过去,再释放掉_str,然后把tmp重新赋给_str,再更新容量。
(8)字符串插入函数和删除函数
void push_back(char ch){/*if (_size + 1 > _capacity){reserve(_capacity * 2);}_str[_size] = ch;++_size;_str[_size] = '\0';*/insert(_size, ch);}void push_back(const char* str){insert(_size, str);}string& insert(size_t pos, char ch){assert(pos <= _size);if (_size + 1 > _capacity){reserve(2 * _capacity);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size;return *this;}string& insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}// 挪动数据size_t end = _size + len;while (end > pos + len - 1){_str[end] = _str[end - len];--end;}// 拷贝插入strncpy(_str + pos, str, len);_size += len;return *this;}string& erase(size_t pos, size_t len = npos){assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}
1、**push_back函数:**尾插插入用常规思路去写,如果需要扩容,就reserve,反之就进行尾插赋值,最后一个位置置为\0。
第二种就复用insert,因为它是在pos位置前面插入,所以只需要传最后一个位置的参数即可,也就是在\0前面插入就形成了尾插效果。另外为了方便进行字符串尾插,进行函数重载一下即可。
2、insert函数:
插入字符:先看是否能扩容,接下来就是挪动,一般是从最后一个位置开始挪动加上while(end>=pos)条件,可结果出错,这里的end是无符号,无符号end再–,减到-1,被当成全1,是无符号最大值,造成错误,同样这里也不能改成int,因为会发生隐式转换,有符号变成无符号,除非上面参数pos也改成int,就能解决或者把pos强转int。但是这是模拟实现,下标都常用无符号。
解决:可以把end位置往后挪一位,也即从最后一个位置的下一个位置开始挪动。把end-1的数赋给end,这样end等于pos的时候就停止,而且第一个位置也已经被挪动。就能成功运行。再进行赋值并更新_size。
插入字符串 :大致思路和上面一样,注意的是终止条件并不是pos位置,如果是pos位置的话会发生越界,因为字符串已经挪完,end还没有到pos,end是从end+len的位置开始的。所以这里应该是挪完的时候在pos+len,停的位置就再减一。最后空出的位置直接进行字符串拷贝,更新_size。
3、erase函数: 删除分为两种情况,一种是pos+len长度大于等于size,意思是把pos位置之后的全删掉,那就把pos位置置为\0,在更新_size。
第二种是长度小于size,就把后面的数据往前挪动,可以用拷贝函数进行从前往后覆盖,直接注意的是len == npos要单独写,否则会溢出,因为如果n是npos,已经是最大值了,再加就会绕回去,所以防止用缺省值和pos相加出错,分开来写。
(9)字符串拼接和拼接重载函数
void append(const char* str){//size_t len = strlen(str);//if (_size+len > _capacity)//{// reserve(_size + len);//}strcat(_str, str);//_size += len;insert(_size, str);}string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){push_back(str);//append(str);return *this;}
1、append拼接函数:两种方法,一种是算长度,看是否要扩容,然后直接调用字符串追加函数strcat进行追加,另一种是复用insert。
2、拼接重载函数:字符重载函数,直接复用push_back,字符串重载函数复用append和push_back都可行。
(10)字符串交换和查找函数及清理函数
void swap(string& s){std::swap(_str, s._str);std::swap(_capacity, s._capacity);std::swap(_size, s._size);}size_t find(char ch, size_t pos = 0){assert(pos < _size);for (size_t i = pos; i < _size; ++i){if (_str[i] == ch){return i;}}return npos;}size_t find(const char* str, size_t pos = 0){assert(pos < _size);char* p = strstr(_str + pos, str);if (p == nullptr){return npos;}else{return p - _str;}}void zero(){_str[0] = '\0';_size = 0;}
1、交换函数
string库里面的交换函数是void swap(string& x, string& y),里面需要创建临时变量进行赋值,发生了三次深拷贝,而这模拟实现可以是void swap(string& s),在里面调用C++标准库里面的swap把三个成员换一下就可以,效率可以提升很多。
2、查找函数:对于字符查找函数,用for循环进行遍历,如果找了就返回下标,否则返回npos。
对于字符串查找函数,直接调用子串搜索函数并返回它的首地址,如果找不到返回npos,否则返回它的地址减去首地址。
3、清0函数:把size的位置置为\0,并把size置为0和流插入函数结合用。
(11)流插入和流提取的重载函数
ostream& operator<<(ostream& out, const string& s)
{for (auto ch : s){out << ch;}return out;
}
istream& operator>>(istream& in, string& s)
{s.zero();char ch = in.get();char tmp[128];size_t i = 0;while (ch != ' ' && ch != '\n'){tmp[i++] = ch;if (i == 127){tmp[127] = '\0';s += tmp;i = 0;}ch = in.get();}if (i != 0){tmp[i] = '\0';s += tmp;}return in;
}
这两个函数不能写到里成员函数,因为this指针就把第一个位置抢了,cout做不了第一个左操作数,得写成全局,不一定要用友元函数。
流插入函数: 直接cout<<s,和cout<<c_str,有区别,后者遇到\0停止,里面直接用for循环进行打印。
流提取函数:1、C/C++规定输入多个字符,会把\n和空格当成多个字符之间的间隔,cin流对象缓冲区会拿不到\n和空格,用get就不会把它两当成间隔,把它们拿到,wihle就能终止。
2、流提取前要清理一下,设置\0,防止出现乱码。
3、如果插入的字符串很长啊,+=会频繁扩容,先创建一个tmp临时数组,输入一个字符,填到数组里,如果快要填满的时候,就在最后填上\0,把这个数组加到s上去,i置为0。每满一次往s上加。如果最后i不等于0,就直接就把tmp i的位置置为\0,最后还要判断一下i是否为0,如果不为0,说明tmp里面还没有加进去完。
四、string类模拟实现代码
(1)simulate_string.h
#pragma once
#include<assert.h>
namespace mould
{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 == 0 ? 3 : _size;_str = new char[_capacity + 1];strcpy(_str, str);}string(const string& s):_size(s._size), _capacity(s._capacity){_str = new char[s._capacity + 1];strcpy(_str, s._str);}// s1 = s3;// s1 = s1;string& operator=(const string& s){if (this != &s){char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}const char* c_str(){return _str;}const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}size_t size() const{return _size;}size_t capacity() const{return _capacity;}// 不修改成员变量数据的函数,最好都加上constbool 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 || s == *this;}bool operator<(const string& s) const{return !(*this >= s);}bool operator<=(const string& s) const{return !(*this > s);}bool operator!=(const string& s) const{return !(*this == s);}void resize(size_t n, char ch = '\0'){if (n < _size){// 删除数据--保留前n个_size = n;_str[_size] = '\0';}else if (n > _size){if (n > _capacity){reserve(n);}size_t i = _size;while (i < n){_str[i] = ch;++i;}_size = n;_str[_size] = '\0';}}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void push_back(char ch){insert(_size, ch);}void push_back(const char* str){insert(_size, str);}void append(const char* str){insert(_size, str);}string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}string& insert(size_t pos, char ch){assert(pos <= _size);if (_size + 1 > _capacity){reserve(2 * _capacity);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size;return *this;}string& insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}// 挪动数据size_t end = _size + len;while (end > pos + len - 1){_str[end] = _str[end - len];--end;}// 拷贝插入strncpy(_str + pos, str, len);_size += len;return *this;}string& erase(size_t pos, size_t len = npos){assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}void swap(string& s){std::swap(_str, s._str);std::swap(_capacity, s._capacity);std::swap(_size, s._size);}size_t find(char ch, size_t pos = 0){assert(pos < _size);for (size_t i = pos; i < _size; ++i){if (_str[i] == ch){return i;}}return npos;}size_t find(const char* str, size_t pos = 0){assert(pos < _size);// kmpchar* p = strstr(_str + pos, str);if (p == nullptr){return npos;}else{return p - _str;}}void zero(){_str[0] = '\0';_size = 0;}private:char* _str;size_t _capacity;size_t _size;static const size_t npos;};const size_t mould::string::npos = -1;
}
(2)test.cpp
#include<iostream>
#include"simulate_string.h"
using namespace std;
void Print(const mould:: string& s)
{for (size_t i = 0; i < s.size(); ++i){cout << s[i] ;}cout << endl;mould::string::const_iterator it = s.begin();while (it != s.end()){//*it = 'x';++it;}for (auto ch : s){cout << ch ;}
}
ostream& operator<<(ostream& out, const string& s)
{for (auto ch : s){out << ch;}return out;
}
istream& operator>>(istream& in, string& s)
{s.clear();char ch = in.get();char tmp[128];size_t i = 0;while (ch != ' ' && ch != '\n'){tmp[i++] = ch;if (i == 127){tmp[127] = '\0';s += tmp;i = 0;}ch = in.get();}if (i != 0){tmp[i] = '\0';s += tmp;}return in;
}void _string1()
{mould::string s1("power latent");cout << s1.c_str() << endl;s1[0]++;cout << s1.c_str() << endl;cout << endl;
}
void _string2()
{mould::string s2("power latent");mould::string s3(s2);mould::string s1;cout << s2.c_str() << endl;cout << s3.c_str() << endl;s2[0]++;cout << s2.c_str() << endl;cout << s3.c_str() << endl;s1 = s2;cout << s1.c_str() << endl;cout << s2.c_str() << endl;cout << endl;}
void _string3()
{mould::string s1("power latent");for (size_t i = 0; i < s1.size(); ++i){s1[i]++;}for (size_t i = 0; i < s1.size(); ++i){cout << s1[i];}cout << endl;Print(s1);mould::string::iterator it = s1.begin();while (it != s1.end()){(*it)--;++it;}cout << endl;it = s1.begin();while (it != s1.end()){cout << *it ;++it;}cout << endl;for (auto ch : s1){cout << ch ;}cout << endl;cout << endl;}
void _string4()
{mould::string s1("power latent");mould::string s2("power latent");mould::string s3("aa");cout << (s1 < s2) << endl;cout << (s1 == s2) << endl;cout << (s1 >= s2) << endl;cout << endl;
}void _string5()
{mould::string s1("power latent");s1 += ' ';s1 += "ssssssssssssssssssssssssssss";cout << s1.c_str() << endl;s1.insert(6, 'c');cout << s1.c_str() << endl;s1.insert(0, 'q');cout << s1.c_str() << endl;cout << endl;
}void _string6()
{mould::string s1("hello world66666666666");cout << s1.capacity() << endl;s1.reserve(10);cout << s1.capacity() << endl;cout << endl;
}
void _string7()
{mould::string s1;s1.resize(10, 'k');cout << s1.c_str() << endl;s1.resize(15, 'd');cout << s1.c_str() << endl;s1.resize(3);cout << s1.c_str() << endl;cout << endl;
}
void _string8()
{mould::string s1("6666666");s1.insert(0, 'a');cout << s1.c_str() << endl;s1.insert(2, 'a');cout << s1.c_str() << endl;s1.insert(3, "bbb");cout << s1.c_str() << endl;s1.insert(1, "bbb");cout << s1.c_str() << endl;cout << endl;
}
void _string9()
{mould::string s1("0123456789");cout << s1.c_str() << endl;s1.erase(4, 3);cout << s1.c_str() << endl;s1.erase(4, 30);cout << s1.c_str() << endl;s1.erase(2);cout << s1.c_str() << endl;cout << endl;
}
void _string10()
{string s1("0123456789");s1 += '\0';s1 += "xxxxxxxx";cout<<s1<<endl ;cout<<s1.c_str() << endl;string s2;cin>>s2;cout<<s2<< endl;cin>>s1;cout<<s1<< endl;
}int main()
{_string1();_string2();_string3();_string4();_string5();_string6();_string7();_string8();_string9();_string10();return 0;
}
(3)运行结果
五、深浅拷贝及其他说明
浅拷贝:也叫位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还在,所以当继续对资源进项操作时,就会发生发生了非法访问。
深拷贝:可以用深拷贝解决浅拷贝问题,每个对象都有一份独立的资源,不要和其他对象共享,如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。保证多个对象不会因共享资源造成多次释放造成程序崩溃问题。
vs下string的结构:
string总共占28个字节,内部结构复杂。
1、先是有一个联合体,联合体用来定义string中字符串的存储空间:当字符串长度小于16时,使用内部固定的字符数组来存放,当字符串长度大于等于16时,从堆上开辟空间。
2、Mysize存字符串有效长度。
3、Myres存空间容量。
4、还有一个指针。
g++下string的结构:g++下,string是通过写时拷贝实现的,string对象总共占4个字节(64位下是8个字节),内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
1、空间总大小
2、字符串有效长度
3、引用计数
相关文章:
C++【string类用法详细介绍string类模拟实现解析】
文章目录string 类用法介绍及模拟实现一、string介绍二、string类常用接口1. string类对象的常见构造接口2.string类对象的常见容量接口3.string类对象的常见修改接口4. string类对象的常见访问及遍历接口5.string其他接口1.不常用查找接口2.字符替换3.字符串拼接4.字符串排序5…...
常见的开发模型和测试模型
软件的生命周期软件开发阶段的生命周期需求分析->计划->设计->编码->测试->运维软件测试阶段的生命周期需求分期->测试计划->测试设计与开发->执行测试->测试评估开发模型瀑布模型可以看到,这个模型和我们上面的软件开发生命周期很相似采用的是线性…...
印度和印度尼西亚有什么关系吗?
印度和印度尼西亚,这两个国家很多人都比较熟悉。因为两国都是人口大国,而且经济总量也比较高,在全球还是有很大影响的。不过很多人刚看到这两个国家的时候,都会觉得这两个国家肯定有什么关系,要不然国名也不会这么像。…...
单调栈(C/C++)
目录 1. 单调栈的定义 2. 单调栈的常见用途 3. 案例分析 3.1 暴力解法 3.2 单调栈 4. 单调栈总结 1. 单调栈的定义 单调栈顾名思义,就是栈内的元素是单调的。根据栈内元素的单调性的不同,可以分为: 单调递增栈:栈内元素是单…...
算法设计与智能计算 || 专题一: 算法基础
专题一: 算法基础 文章目录专题一: 算法基础1. 算法的定义及特点1.1 算法的基本特征1.2 算法的基本要素1.3 算法的评定2 算法常见执行方法2.1 判断语句2.2 循环语句2.3 综合运用3. 计算复杂度4. 代码的重用5. 类函数的定义与使用5.1 定义类5.2 调用类函数1. 算法的定义及特点 …...
用javascript分类刷leetcode13.单调栈(图文视频讲解)
239. 滑动窗口最大值 (hard) 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例 1: 输入:nums [1,…...
英语基础语法学习(B站英语电力公司)
1. 句子结构 五大基本句型: 主谓主谓宾主谓宾宾主谓宾宾补主系表 谓语: 一般来说,谓语是指主语发出的动作。(动词)但是很多句子是没有动作的,但是还是必须要有谓语。(此时需要be动词&#x…...
【计算机网络】网络层IP协议
文章目录一、认识IP协议二、IP协议头部格式三、IP地址划分1. IP地址分类2. 子网划分四、IP地址数量危机1. IP地址的数量限制2. NAT技术五、私网IP和公网IP六、路由1. 认识路由2. 路由表生成算法一、认识IP协议 IP协议是Internet Protocol(互联网协议)的…...
Eclipse快捷键大全
编辑类快捷键Ctrl1: 快速修复(最经典的快捷键, 可以解决很多问题, 比如import类、try catch包围等)CtrlShiftF: 格式化当前代码CtrlShiftM: 添加类的import导入CtrlShiftO: 组织类的导入(既有CtrlShiftM的作用,又可以去除没用的导入, 一般用这个导入包)CtrlY: 重做(与CtrlZ相反…...
JavaScript 高级2 :构造函数和原型 d331702016e84f54b3594ae05e0eeac
JavaScript 高级2 :构造函数和原型 Date: January 16, 2023 Text: 构造函数和原型、继承、ES5中的新增方法 目标 能够使用构造函数创建对象 能够说出原型的作用 能够说出访问对象成员的规则 能够使用 ES5新增的一些方法 构造函数和原型 概述 在典型的 OOP 的…...
maven-war-plugin插件 overlays maven-war-plugin翻译
说明 翻译maven-war-plugin插件的部分内容 官方地址为:https://maven.apache.org/plugins/maven-war-plugin/index.html Overview 概述 Introduction 介绍 Apache Maven WAR Plugin apache maven war 插件 The WAR Plugin is responsible for collecting all artifa…...
【数据结构】初识二叉树(二叉树的入门知识)
初识二叉树一、树概念及结构1、树的概念2、树的相关概念3、树的表示4、树在实际中的运用(表示文件系统的目录树结构)二、二叉树概念及结构1、概念2、特殊的二叉树3、二叉树的性质4、二叉树的存储结构三、结语一、树概念及结构 1、树的概念 树是一种非线…...
RV1126笔记三十二:基于 FastDeploy 在 RV1126 上的部署示例(RV1126 上部署 YOLOv5 检测模型测试)
若该文为原创文章,转载请注明原文出处。 FastDeploy是一款全场景、易用灵活、极致高效的AI推理部署工具, 支持云边端部署。提供超过 🔥160+ Text,Vision, Speech和跨模态模型📦开箱即用的部署体验,并实现🔚端到端的推理性能优化。包括 物体检测、字符识别(OCR)、…...
JVM垃圾回收——G1垃圾收集器
目录 一、什么是G1垃圾收集器 二、G1垃圾收集器的内存划分 三、G1垃圾收集器的收集过程 四、G1收集器的优缺点 五、G1收集器的JVM参数配置 一、什么是G1垃圾收集器 Garbage First(简称G1)收集器是垃圾收集器技术发展史上里程碑式的成果,它摒弃了传统垃圾收集器的…...
C语言深度剖析:关键字
C语言深度剖析:关键字C语言深度剖析:关键字前言定义与声明(补充内容)最宏大的关键字-auto最快的关键字-register关键字static被冤枉的关键字-sizeof整型在内存中的存储原码、反码、补码大小端补充理解变量内容的存储和取出为什么都是补码整型取值范围关于…...
聊一聊过度设计!
文章目录什么是过度设计?过度设计的坏处如何避免过度设计充分理解问题本身保持简单小步快跑征求其他人的意见总结新手程序员在做设计时,因为缺乏经验,很容易写出欠设计的代码,但有一些经验的程序员,尤其是在刚学习过设…...
程序员在小公司(没有大牛,人少)怎么成长?
大多数小公司都是创业公司,所以它们有着非常独特的“创业心态”。所谓创业心态通常表现为关注快速增长,竭尽所能让公司盈利,或者达成其他一些迫切目标。 在这样一家公司工作的软件开发人员,你极有可能要身兼多职,不能…...
【Fastdfs实战】在本地如何将文件上传到Linux虚拟机
作者:狮子也疯狂 专栏:《Fastdfs连续剧》 坚持做好每一步,幸运之神自然会驾凌在你的身上 目录一. 🦁 前言二. 🦁 上传原理Ⅰ. 🐇 原理图解Ⅱ. 🐇 传输原理三. 🦁 实战演示Ⅰ. &…...
ERP 系统的应用对企业财务会计信息系统内部控制的影响
(一)对企业的财务信息数据进行实时和动态管理传统的财务会计信息系统一般都是采用单一的软件系统,所以在信息的传递及处理上常常不能满足企业的需要,信息与其他部门存在不对称及滞后的现象。而ERP 系统是通过有效的技术手段将企业的各种分散的数据进行完…...
智慧物联网源码带手机端源码 物联网系统源码
在智慧工厂领域,智慧城市领域,都需要对设备进行监控。比如工厂需要对周围环境温度、湿度、气压、电压,灯的开关进行监控。这时候就需要物联网平台来进行管理。 推荐一个基于java开发的物联网平台,前端HTML带云组态、可接入视频监…...
AI绘画进军三次元,有人用它打造赛博女友?(diffusion)
目录0 写在前面1 AI绘画技术飞跃2 效果展示3 环境配置3.1 下载基础模型3.2 更新.NET和模型3.3 下载绘画模型3.4 启动项目3.5 标签配置4 结语0 写在前面 机器学习强基计划聚焦深度和广度,加深对机器学习模型的理解与应用。“深”在详细推导算法模型背后的数学原理&a…...
计算机网络高频知识点
目录 一、http状态码 二、浏览器怎么数据缓存 三、强缓存与协商缓存 1、强缓存 2、协商缓存 四、简单请求与复杂请求 五、PUT 请求类型 六、GET请求类型 七、GET 和 POST 的区别 八、跨域 1、什么时候会跨域 2、解决方式 九、计算机网络的七层协议与五层协议分别指…...
谈谈前端性能优化-面试版
前言 当我们去面试的时候,很大概率会被面试官问这么一个问题:你有尝试过对项目做性能优化吗?或者你了解哪些性能优化的方法?听到这个问题的你可能是这样的: 似曾相识但又说不清楚,往往只能零散地说出那么几…...
JAVA连接数据库——JDBC的简单使用
JDBC即Java数据库连接.用来实现Java程序对数据库增删查改。 为了对接Java程序和数据库,java.sql提供了很多api包含在java.sql和javax.sql里面 结构: DriverManager接口: 每一个数据库的驱动程序都必须去到DriverManager注册,生成一个Connection Conn…...
Pandas数据查询
Pandas数据查询 Pandas查询数据的几种方法 df.loc方法,根据行、列的标签值查询 df.iloc方法,根据行、列的数字位置查询 df.where方法 df.query方法 .loc既能查询,又能覆盖写入,强烈推荐! Pandas使用df.loc查询数据…...
NLP-统计词频之处理停用词
前言 本文是该专栏的第1篇,后面会持续分享NLP的各种干货知识,值得关注。 一般来说,自然语言处理(NLP)就是开发能够理解人类语言的应用程序或者应用服务。 举个例子,如Facebook News Feed这种社交网站推送,它的算法知道你的兴趣是自然语言处理,就会推送相关的广告或者…...
sort 定制排序规则(配合functools.cmp_to_key())
sort 定制排序规则(配合functools.cmp_to_key()) 配合例题学习 题目链接:179. 最大数 题目大意:给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。 注意&a…...
【华为OD机试模拟题】用 C++ 实现 - 内存池(2023.Q1)
最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 去重求和(2023.Q1) 文章目录 最近更新的博客使用说明内存池题目输入输出示例一输入输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址:…...
Python--深入浅出的装饰器--1
本章一起深入浅出一下装饰器。前面我们讲过一章装饰器了。不知道各位看懂了多少。每太看懂也没关系,本章就一起实操一下。简单的例子例1例2上述的两个例子,执行结果为:1423.为什么呢???解析语法糖ÿ…...
如何从0创建Spring Cloud Alibaba(多模块)
以一个父工程带两个Module(test1、test2)为例。 一、创建父工程 由于是模块化项目,那么父工程不需要实际的代码逻辑,因此无需创建src,那么可以有几种方式创建,例如: 使用Spring Initializr脚…...
java 框架用来做网站/最新军事头条
项目中原始数据都是DWG格式,里面只有两个层,一个0层,一个TX层,0层存储了图廓信息,所有其他要素信息全部存在TX层中,包括房屋,植被,道路,水系,独立地物等等。没…...
做美食网站的意义/互动营销经典案例
Scrum一直以来争论不断。虽然创始人Ken在演讲中曾说过即使是白痴也可以用Scrum,但是依然有很多人认为Scrum对团队成员的素质要求非常高。另据统计,75%以上的Scrum都可以称得上失败。 去年十月,有幸参加了Outsofting鲍央舟老师的Scrum培训。培…...
东台建设企业网站/如何优化搜索引擎的搜索功能
数据结构是以某种形式将数据组织在一起的集合,它不仅存储数据,还支持访问和处理数据的操作。算法是为求解一个问题需要遵循的、被清楚指定的简单指令的集合。下面是自己整理的常用数据结构与算法相关内容,如有错误,欢迎指出。 为了…...
wordpress 用法/百度搜索榜排名
application对象 application对象负责提供应用程序在服务器中运行时的一些全局信息,常用的方法有getMimeType和getRealPath等。 运用实例:网页访问计数器。 <% page language"java" import"java.util.*" pageEncoding"UTF-8…...
做网站软件流程/武汉百度推广外包
写原生的时候,我们经常会用到广播,接口,回调等方法来实现发送和接受通知以及通信的。 那么在RN中,也有一套发送和接收通知的方法,用的组件是DeviceEventEmitter。下面看一下,RN中是如何发送和接收事件的&am…...
云南网站建设优选平台/长沙seo关键词
1 .责任分配矩阵展示项目资源在各个()中的任务分配? A.工作包 B.项目活动 4 .在虚拟团队环境中,()规划变得日益重要? A.资源 B.沟通 参考答案 A B...