目录
find(从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置)+npos
rfind(从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置)
substr(在str中从pos位置开始,截取n个字符,然后将其返回)
substr(在str中从pos位置开始,截取n个字符,然后将其返回)
string类运用
(constructor)
(constructor)函数名称 | 功能说明 |
string() (重点) | 构造空的string类对象,即空字符串 |
string(const char* s) (重点) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s) (重点) | 拷贝构造函数 |
size
length
resize
capacity
reserve
clear
empty
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_arg=0):为string预留空间,不改变有效元素个数,当reserve的参
数小于string的底层空间总大小时,reserve不会改变容量大小
reserve和resize
- reserve
reserve
只影响字符串的容量,不会改变字符串的当前大小。- 如果请求的容量大于当前容量,
reserve
将分配新的内存,并可能导致字符串的内容移动(如果字符串已经有内容)。- 如果请求的容量小于或等于当前容量,
reserve
不会做任何操作。- resize
resize
方法用于调整字符串的大小。如果新大小大于当前大小,resize
会在字符串的末尾添加空字符(或指定字符);如果新大小小于当前大小,resize
会截断字符串
operator[]
begin+end
rbegin+rend
push_back(在字符串后尾插字符c)
append(在字符串后追加一个字符串)
operator+=
c_str(返回C格式字符串)
strtok
find(从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置)+npos
rfind(从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置)
substr(在str中从pos位置开始,截取n个字符,然后将其返回)
1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差
不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可
以连接字符串。
2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留
好。
string类非成员函数
函数 | 功能说明 |
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
operator>> (重点) | 输入运算符重载 |
operator<< (重点) | 输出运算符重载 |
getline (重点) | 获取一行字符串 |
relational operators (重点) | 大小比较 |
vs和g++下string结构的说明
32位平台下指针占4个字节
vs下string的结构
string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义
string中字符串的存储空间:
当字符串长度小于16时,使用内部固定的字符数组来存放
当字符串长度大于等于16时,从堆上开辟空间
这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建
好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。
其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量
最后:还有一个指针做一些其他事情。
故总共占16+4+4+4=28个字节
g++下string的结构
G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个
指针,该指针将来指向一块堆空间,内部包含了如下字段:
空间总大小
字符串有效长度
引用计数
指向堆空间的指针,用来存储字符串
".h"文件在类外定义的函数,同时会在另外两个文件展开,重定义(类里的内联)
解决方案:a.内联(inline)/静态(static)只在当前文件呈现
b.声明和定义分离
string模拟实现
构造函数
string()//不能初始化字符串为空,需要至少给一个空间给'\0'
:_str(new char[1]{'\0'})
,_size(0)
,_capacity(0)
{}
//无参构造,初始化必须给空间
string(const char* str=" ")//约等于\0
{
_size = strlen(str);
_capacity = _size;
//capacity不包含\0
_str = new char[_capacity + 1];
strcpy(_str, str);// \0拷贝过去,先拷贝再判断
}
//有动态分配,故不能走初始化列表
//第2个构造函数涵盖第1个
析构函数
~string()
{
delete[] _str;//调用析构函数,释放空间
_str = nullptr;
_size = 0;
_capacity = 0;
}
范围for(底层迭代器)
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;
}
void test_string1()
{
string s1;
string s2("hello world");
cout << s1.c_str() << endl;
cout << s2.c_str() << endl;
for (size_t i = 0; i < s2.size(); i++)
{
s2[i] += 2;
}
cout << s2.c_str() << endl;
for (auto e : s2)
{
cout << e << " ";
}
cout << endl;
string::iterator it = s2.begin();
while (it != s2.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
修改字符串
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
reserve
void string::reserve(size_t n)
{
if (n > _capacity)
{
cout << "reserve:" << n << endl;
char* tmp = new char[n + 1];//多开一个给\0
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
push_back(_capacity不含'\0')
void string::push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
append
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
//大于2倍,需要多少开多少
reserve(_size + len>2*_capacity?_size+len:2*_capacity);
}
strcpy(_str + _size, str);
_size += len;
}
operator+=
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
insert
-----第一个难点
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
//空间不够扩容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//挪动
int end = _size;//size_t 无符号整数,不会小于0,减到整形的最大值
//一个操作符两边的操作数不同时,编译器会进行类型转换/提升,范围小的向范围大的提
升
//可以用强转,因为pos为无符号,end会强转为无符号,所以pos要强转为int
while (end >= (int)pos)
{
_str[end + 1] = _str[end];
--end;
}
_str[pos] = ch;
++_size;
}
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (len == 0) return;
if (_size + len > _capacity)
{
//大于2倍,需要多少开多少
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
size_t end = _size + len;
while (end >pos + len-1)
{
_str[end] = _str[end - len];
--end;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
erase
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;
}
find
size_t string::find(char ch, size_t pos )
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t string::find(const char* str, size_t pos )
{
assert(pos < _size);
const char* ptr=strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
查找在str1中的str2,找到则返回str2在str1中的指针
-----第二个难点
substr(在str中从pos位置开始,截取n个字符,然后将其返回)
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
//len大于剩余字符,更新len
if (len > _size - pos)
{
len = _size - pos;
}
string sub;
sub.reserve(len);
for (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;//传值返回,调用拷贝构造
//拷贝一个临时对象返回,优化直接拷贝掉
//浅拷贝,sub和suffix指向同一块空间,出了作用域sub销毁,suffix指向野指针
//故拷贝构造用深拷贝,显示写拷贝构造
}
//01:20:15
release版本直接用suffix拷贝sub,优化掉了sub和临时对象
debug版本就会触发bug
operator操作
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 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 strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator!=(const string& s1, const string& s2)
{
return !(s1 == s2);
}
流插入,流提取
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;//输出到对象out中
}
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
//s.reserve(1024);
const int N = 2;
int i = 0;
char buff[N];//开的临时空间,且很快
char ch;
//in >> ch;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
//s += ch;
buff[i++] = ch;
if (i == N-1)
{
buff[i] = '\0';
s += buff;
i = 0;
}
//in >> ch;
ch = in.get();
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
拷贝构造
//s2(s1)
string(const string& s)
{
string tmp(s._str);
swap(tmp);
}
//这种交换方式在赋值语句中,有利于预防内存泄漏,因为交换完成的临时对象,
一旦生存作用域结束,就会自动调用其析构函数,释放对应的资源
引用计数(写时拷贝)-----解决析构两次问题
引用计数---有几个对象指向这块资源:前几个对象减计数,最后一个释放空间
深拷贝时
浅拷贝时
c_str
const char* c_str() const
{
return _str;
}
赋值拷贝
//赋值1
//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;
// }
//}
//赋值2
/*string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s._str);
swap(tmp);
}
return *this;
}*/
//s1=s3;
//赋值3
string& operator=(string tmp)//s3拷贝构造给tmp
{
swap(tmp);
return *this;
}
项目实现
string.h
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include"iostream"
#include"assert.h"
using namespace std;
namespace bit{
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()
:_str(new char[1]{'\0'})
,_size(0)
,_capacity(0)
{}*/
//短小频繁调用的函数,可以定义到类里面,默认是inline
string(const char* str=" ")//约等于\0
{
_size = strlen(str);
_capacity = _size;
//capacity不包含\0
_str = new char[_capacity + 1];
strcpy(_str, str);// \0拷贝过去,先拷贝再判断
}
//string(const string& s)//拷贝构造
//{
// _str = new char[s._capacity + 1];
// strcpy(_str, s._str);
// _size = s._size;
// _capacity = s._capacity;
//}
void swap(string& s)
{
std::swap(_str, s._str);//调用算法库的swap
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//s2(s1)
string(const string& s)
{
string tmp(s._str);
swap(tmp);
}
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
void push_back(char ch);
void append(const char* str);
string& operator+=(char ch);
string& operator+=(const char* str);
void reserve(size_t n);
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
void erase(size_t pos, size_t len=npos);
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);
string substr(size_t pos, size_t len = npos);
//赋值1
//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;
// }
//}
//赋值2
/*string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s._str);
swap(tmp);
}
return *this;
}*/
//s1=s3;
//赋值3
string& operator=(string tmp)//s3拷贝构造给tmp
{
swap(tmp);
return *this;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
private:
char* _str=nullptr;
size_t _size=0;
size_t _capacity=0;
//static const size_t npos=-1;//此处static在类里赋值相当于定义,且必须有const和整形,但不建议
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);
ostream& operator<<(ostream& out, const string& s);
istream& operator>>(istream& in, string& s);
}
string.cpp
#include"string.h"
namespace bit
{
const size_t string::npos = -1;
void string::reserve(size_t n)
{
if (n > _capacity)
{
cout << "reserve:" << n << endl;
char* tmp = new char[n + 1];//多开一个给\0
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;
_str[_size] = '\0';
}
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
//大于2倍,需要多少开多少
reserve(_size + len>2*_capacity?_size+len:2*_capacity);
}
strcpy(_str + _size, str);
_size += len;
}
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
//空间不够扩容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
挪动
//int end = _size;//size_t 无符号整数,不会小于0,减到整形的最大值
// //一个操作符两边的操作数不同时,编译器会进行类型转换/提升,范围小的向范围大的提升
// //可以用强转
//while (end >= (int)pos)
//{
// if (end == 0)
// {
// int i = 0;
// }
// _str[end + 1] = _str[end];
// --end;
//}
//_str[pos] = ch;
//++_size;
size_t end = _size+1;
while (end >pos)
{
if (end == 0)
{
int i = 0;
}
_str[end ] = _str[end-1];
--end;
}
_str[pos] = ch;
++_size;
}
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (len == 0) return;
if (_size + len > _capacity)
{
//大于2倍,需要多少开多少
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
size_t end = _size + len;
while (end >pos + len-1)
{
_str[end] = _str[end - len];
--end;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_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 ch, size_t pos )
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t string::find(const char* str, size_t pos )
{
assert(pos < _size);
const char* ptr=strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
//len大于剩余字符,更新len
if (len > _size - pos)
{
len = _size - pos;
}
string sub;
sub.reserve(len);
for (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;//传值返回,调用拷贝构造
//拷贝一个临时对象返回,优化直接拷贝掉
//浅拷贝,sub和suffix指向同一块空间,出了作用域sub和临时对象销毁,suffix指向野指针
//故拷贝构造用深拷贝
}
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 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 strcmp(s1.c_str(), s2.c_str()) == 0;
}
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();
//s.reserve(1024);
const int N = 2;
int i = 0;
char buff[N];//开的临时空间,且很快
char ch;
//in >> ch;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
//s += ch;
buff[i++] = ch;
if (i == N-1)
{
buff[i] = '\0';
s += buff;
i = 0;
}
//in >> ch;
ch = in.get();
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
}
test.cpp
#include"string.h"
namespace bit
{
void test_string1()
{
string s1;
string s2("hello world");
cout << s1.c_str() << endl;
cout << s2.c_str() << endl;
for (size_t i = 0; i < s2.size(); i++)
{
s2[i] += 2;
}
cout << s2.c_str() << endl;
for (auto e : s2)
{
cout << e << " ";
}
cout << endl;
string::iterator it = s2.begin();
while (it != s2.end())
{
cout << *it << " ";
++it;
}
cout << endl;
string s3("hello bit"); const
string::const_iterator iy = s3.begin();
cout << iy << " ";
cout << endl;
}
void test_string2()
{
string s1("hello my world");
s1.operator+=('#');
s1.push_back('@');
cout << s1.c_str() << endl;
s1 += "hello";
cout << s1.c_str() << endl;
s1.insert(15, '$');
cout << s1.c_str() << endl;
string s3("hello world");
s3.insert(5, "&&&");
cout << s3.c_str() << endl;
string s4("hello my life");
cout << s4.c_str() << endl;
s4.erase(5,3);
cout << s4.c_str() << endl;
}
void test_string3()
{
string s("test.cpp.sip");
size_t pos = s.find(".");
string suffix = s.substr(pos);
cout << suffix.c_str() << endl;
string copy(s);
cout << copy.c_str() << endl;
s = suffix;
cout << suffix.c_str() << endl;
cout << s.c_str() << endl;
s = s;
cout << s.c_str() << endl;
}
void test_string4()
{
string s1("hello");
string s2("bit");
cout << (s1 > s2) << endl;
cout << (s1 == s2) << endl;
cout << ("hello world" == s1) << endl;//单参数构造函数支持隐式类型转换
cout << ("hello world" == "hello world") << endl;//运算符重载必须有个类类型的对象
}
void test_string5()
{
/*string s1("hello");
string s2("bit");
cout << s1 <<" "<< s2 << endl;*/
string s3("hello");
cin >> s3;
cout << s3 << endl;
}
void test_string6()
{
string s1("hello world");
string s2 = s1;//拷贝构造
cout << s1 << endl;
cout << s2 << endl;
string s3("XXXXXXXXXXXXXXXXXXXXXXX");
s1 = s3;
cout << s1 << endl;
cout << s3 << endl;
}
void test_string7()
{
string s("hello world");
/*s[0] = '&';
cout << s.c_str() << endl;*/
s.append(" hi");
cout << s.c_str() << endl;
}
}
int main()
{
//bit::test_string1();
//bit::test_string2();
//bit::test_string3();
//bit::test_string4();
//bit::test_string5();
//bit::test_string6();
bit::test_string7();
}
现代写法
namespace bit
{
class string
{
public:
//构造函数
string(const char* str = "")
{
// 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言下
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
//拷贝构造函数
string(const string& s)
: _str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
}
//赋值语句 初级阶段的实现,如果考虑异常安全,可以参考剑指offer一书的面试题1
string& operator=(const string& s)
{
if (this != &s)
{
delete[]_str;
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
return *this;
}
//析构函数
~string()
{
delete[]_str;
_str = nullptr;
}
private:
char* _str;
};
}
/*方法二: String的临时对象交换现代版*/
namespace bit
{
class string
{
public:
//构造函数 考虑str为空的情形
string(const char* str = "")
{
// 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言下
if (nullptr == str)
{
assert(0);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
//拷贝构造函数 通过临时对象的交换方式进行拷贝构造,构造对象借助之前实现的普通构造函数来构造
//对象,然后通过交换对象的内部指针的指向,从而达到拷贝构造对象,好处就是如果出错,其根本就是
//构造函数的错误,我们相当于把错误最终归结为构造函数
string(const string& s)
: _str(nullptr)
{
string strTmp(s._str);
swap(_str, strTmp._str);
}
//赋值语句 通过临时对象的交换方式进行实现,
//如果考虑异常安全,可以参考剑指offer一书的面试题1
//这种交换方式在赋值语句中,有利于预防内存泄漏,因为交换完成的临时对象,一旦生存作用域结
//束,就会自动调用其析构函数,释放对应的资源
string& operator=(const string& s)
{
if (this != &s)
{
string strTmp(s);
swap(_str, strTmp._str);
}
return *this;
}
//析构函数,当_str不为空时才进行释放工作并且以数组方式进行释放
~string()
{
delete[] _str;
_str = nullptr;
}
private:
char* _str;
};
}