一、标准库中的string类
string类的文档介绍
在使用string类时,必须包含#include头文件以及using namespace std;
1. auto
在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。
C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器, auto声明的变量必须由编译器在编译时期推导而得。
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错, 因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
auto不能直接用来声明数组
#include<iostream>
using namespace std;
int func1()
{
return 10;
}
// 不能做参数
void func2(auto a)
{}
// 可以做返回值,但是建议谨慎使用
auto func3()
{
return 3;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = func1();
// 编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项
auto e;
cout << typeid(b).name() << endl;//查看变量类型
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
int x = 10;
auto y = &x;
auto* z = &x;
auto& m = x;
cout << typeid(x).name() << endl;
cout << typeid(y).name() << endl;
cout << typeid(z).name() << endl;
auto aa = 1, bb = 2;
// 编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型
auto cc = 3, dd = 4.0;
// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型
auto array[] = { 4, 5, 6 };
return 0;
}
2. 范围for
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。
因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
范围for可以作用到数组和容器对象上进行遍历
范围for的底层很简单,容器遍历实际就是替换为迭代器。
#include<iostream>
#include <string>
using namespace std;
int main()
{
int array[] = { 1, 2, 3, 4, 5 };
// C++98的遍历
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
array[i] *= 2;
}
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
cout << array[i] << endl;
}
// C++11的遍历
for (auto& e : array)
e *= 2;
for (auto e : array)
cout << e << " " << endl;
string str("hello world");
for (auto ch : str)
{
cout << ch << " ";
}
cout << endl;
return 0;
}
3. string类的常用接口说明
3.1、 string类对象的常见构造
void Teststring()
{
string s1; // 构造空的string类对象s1
string s2("hello bit"); // 用C格式字符串构造string类对象s2
string s3(s2); // 拷贝构造s3
}
3.2、string类对象的容量操作
注意:
- size()与length()方法底层实现原理完全相同, 引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
- clear()只是将string中有效字符清空,不改变底层空间大小。
- resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
- reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
3.3、string类对象的访问及遍历操作
3.4、 string类对象的修改操作
注意:
- 在string尾部追加字符时,s.push_back( c ) / s.append(1, c) / s += 'c’三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
- 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留
好。
3.5、 string类非成员函数
3.6、vs和g++下string结构的说明
注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。
vs下string的结构
string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义
string中字符串的存储空间:
当字符串长度小于16时,使用内部固定的字符数组来存放
当字符串长度大于等于16时,从堆上开辟空间
union _Bxty
{ // storage for small buffer or pointer to larger one
value_type _Buf[_BUF_SIZE];
pointer _Ptr;
char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;
这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建
好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。
其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量
最后:还有一个指针做一些其他事情。
故总共占16+4+4+4=28个字节。
g++下string的结构
G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个
指针,该指针将来指向一块堆空间,内部包含了如下字段:
- 空间总大小
- 字符串有效长度
- 引用计数
- 指向堆空间的指针,用来存储字符串
struct _Rep_base
{
size_type _M_length;
size_type _M_capacity;
_Atomic_word _M_refcount;
};
二、string类的模拟实现
基本结构
class string
{
public:
//接口...
private:
char* _str;//字符串
size_t _size;//字符串的大小
size_t _capacity;//现在的容量
//static const size_t npos = -1;
static const size_t npos;//一个比较常量
};
const size_t string::npos = -1;
1. 构造、析构、赋值重载=
构造
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
拷贝构造
string(const string& str)
{
_str = new char[str._capacity + 1];
strcpy(_str, str.c_str());
_size = str._size;
_capacity = str._capacity;
}
赋值重载
string& operator=(const string& str)
{
if (this != &str)
{
delete[] _str;
_str = new char[str._capacity + 1];
strcpy(_str, str._str);
_size = str._size;
_capacity = str._capacity;
}
return *this;
}
析构
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
注意这里的深浅拷贝问题:
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。
深拷贝:如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。
拷贝构造,赋值重载现代写法
交换函数
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
拷贝构造
string(const string& s)
{
string tmp(s.c_str());
swap(tmp);
}
赋值重载
string& operator=(string s)
{
swap(s);
return *this;
}
2. 迭代器
//重命名->迭代器
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;
}
3. 空间的操作
//扩容
void reserve(size_t n);
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
4. 插入与删除
void push_back(const char ch);
void append(const char* str);
void insert(size_t pos, const char ch);
void insert(size_t pos, const char* str);
void erase(size_t pos, size_t len = npos);
void string::push_back(const char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size++] = ch;
_str[_size] = '\0';
}
void string::append(const char* str)
{
size_t len = strlen(str);
if (len + _size > _capacity)
{
reserve(len + _size > 2 * _capacity ? len + _size : 2 * _capacity);
}
strcpy(_str + _size, str);
_size += len;
}
void string::insert(size_t pos, const char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
//int end = _size;
/*while (end >= (int)pos)
{
_str[end + 1] = _str[end];
end--;
}*/
size_t end = _size + 1;
while (end > pos)
{
_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 + _size > _capacity)
{
reserve(len + _size > 2 * _capacity ? len + _size : 2 * _capacity);
}
size_t end = _size + len;
//while(end >= pos + 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)
{
assert(pos < _size);
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;
}
}
5. substr
string substr(size_t pos = 0, size_t len = npos);
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
if (len > _size - pos)
{
len = _size - pos;
}
string ptr;
ptr.reserve(len);
for (size_t i = 0; i < len; i++)
{
ptr += _str[pos + i];
}
return ptr;
}
6.find
size_t find(char ch,size_t pos = 0);
size_t find(const char* str,size_t pos = 0);
size_t string::find(char ch, size_t pos)
{
assert(pos < _size);
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;
}
}
7. operator之类的
operator[]
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
operator +=
string& operator+=(const char ch);
string& operator+=(const char* str);
string& string::operator+=(const char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
operator >,>=, <, <=,==, !=
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)
{
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);
}
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);
}
operator >>
istream& operator>>(istream& in, string& str)
{
str.clear();
const int N = 256;
char buff[N];
int i = 0;
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == N - 1)
{
buff[i] = '\0';
str += buff;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
buff[i] = '\0';
str += buff;
}
return in;
}
operator <<
ostream& operator<<(ostream& out, const string& str)
{
for (auto ch : str)
{
out << ch;
}
return out;
}
三、源代码
string.h
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
namespace mihayou
{
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 = nullptr;//error
// _str = new char[1] {'\0'};
// _size = 0;
// _capacity = 0;
//}
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
string(const string& str)
{
_str = new char[str._capacity + 1];
strcpy(_str, str.c_str());
_size = str._size;
_capacity = str._capacity;
}
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
//扩容
void reserve(size_t n);
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
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)
{
delete[] _str;
_str = new char[str._capacity + 1];
strcpy(_str, str._str);
_size = str._size;
_capacity = str._capacity;
}
return *this;
}
string& operator+=(const char ch);
string& operator+=(const char* str);
void push_back(const char ch);
void append(const char* str);
void insert(size_t pos, const 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 = 0, size_t len = npos);
private:
char* _str;//字符串
size_t _size;//字符串的大小
size_t _capacity;//现在的容量
//static const size_t npos = -1;
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& str);
istream& operator>>(istream& in, string& str);
void test01();
void test02();
void test03();
void test04();
void test05();
void test06();
}
string.cpp
#include "string.h"
namespace mihayou
{
const size_t string::npos = -1;
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void string::push_back(const char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size++] = ch;
_str[_size] = '\0';
}
string& string::operator+=(const 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 (len + _size > _capacity)
{
reserve(len + _size > 2 * _capacity ? len + _size : 2 * _capacity);
}
strcpy(_str + _size, str);
_size += len;
}
void string::insert(size_t pos, const char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
//int end = _size;
/*while (end >= (int)pos)
{
_str[end + 1] = _str[end];
end--;
}*/
size_t end = _size + 1;
while (end > pos)
{
_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 + _size > _capacity)
{
reserve(len + _size > 2 * _capacity ? len + _size : 2 * _capacity);
}
size_t end = _size + len;
//while(end >= pos + 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)
{
assert(pos < _size);
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)
{
assert(pos < _size);
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);
if (len > _size - pos)
{
len = _size - pos;
}
string ptr;
ptr.reserve(len);
for (size_t i = 0; i < len; i++)
{
ptr += _str[pos + i];
}
return ptr;
}
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);
}
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& str)
{
for (auto ch : str)
{
out << ch;
}
return out;
}
istream& operator>>(istream& in, string& str)
{
str.clear();
const int N = 256;
char buff[N];
int i = 0;
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == N - 1)
{
buff[i] = '\0';
str += buff;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
buff[i] = '\0';
str += buff;
}
return in;
}
}
test.cpp
#include "string.h"
namespace mihayou
{
void test01()
{
string s1 = "hello world";
cout << s1.c_str() << endl;
string s2;
cout << s2.c_str() << endl;
cout << s1[1] << endl;
s2 = s1;
cout << s2.c_str() << endl;
string s3 = "123456789aaaaab";
s3.push_back('k');
cout << s3.c_str() << endl;
for (auto e : s3)
{
cout << e;
}
cout << endl;
string::iterator it = s3.begin();
while (it != s3.end())
{
cout << *it;
it++;
}
cout << endl;
}
void test02()
{
string s1 = "hello world";
s1 += 'k';
cout << s1.c_str() << endl;
for (size_t i = 0; i < s1.size(); i++)
{
s1[i] += 2;
}
cout << s1.c_str() << endl;
string s2 = "hello world";
s2.append(" mi ha youp1111111111111");
s2 += " mi ha youp1111111111111";
cout << s2.c_str() << endl;
string s3 = "hello world";
s3.insert(s3.size(), 'k');
s3.insert(5, 'k');
s3.insert(0, 'k');
cout << s3.c_str() << endl;
string s4 = "hello world";
s4.insert(s4.size(), "hhh");
cout << s4.c_str() << endl;
s4.insert(0, "hhhaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
cout << s4.c_str() << endl;
s4.insert(5, "hhh");
cout << s4.c_str() << endl;
}
void test03()
{
string s1 = "hello world";
s1.erase(6);
cout << s1.c_str() << endl;
string s2 = "hello world";
s2.erase(6, 1000);
cout << s2.c_str() << endl;
string s3 = "hello world";
s3.erase(6, 3);
cout << s3.c_str() << endl;
string s4 = "hello world";
cout << s4.find('w',2) << endl;
string s5 = "hello world";
cout << s5.find("llo",1) << endl;
}
void test04()
{
string s1 = "hello world";
size_t pos = s1.find("ll");
string s2 = s1.substr(pos,5);
cout << s2.c_str() << endl;
string k(s1);
cout << k.c_str() << endl;
string s3 = s1;
cout << s3.c_str() << endl;
s3 = s3;
cout << s3.c_str() << endl;
}
void test05()
{
string s1 = "hello world";
string s2 = "hello world";
cout << (s1 < s2) << endl;
cout << (s1 <= s2) << endl;
cout << (s1 > s2) << endl;
cout << (s1 >= s2) << endl;
cout << (s1 == s2) << endl;
cout << (s1 != s2) << endl;
cout << ("hello world" == s1) << endl;
}
void test06()
{
string s1 = "hello world";
cin >> s1;
cout << s1 << endl;
}
}
int main()
{
mihayou::test01();
//mihayou::test02();
//mihayou::test03();
//mihayou::test04();
//mihayou::test05();
//mihayou::test06();
return 0;
}