STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。 STL的六大组成:仿函数,算法,迭代器,空间配置器,容器,配接器。 注意: 这里我是按照功能归类讲string归类到了STL里面,如果按照发展史其实并不属于STL中的容器。 并且从现在开始我们会更频繁的开始使用这个网站了: cpulspuls
为什么要学习string呢?C语言中字符串是以‘\0’结尾的,C语言当中提供的str库函数是与字符串分开的,很可能越界访问,也不方便管理,C++主要是OOP思想(抽象”、“封装”、“继承”、“多态),所以出现了string,并且更方便增删查改。
计算机是二进制,但是如果让我们去理解一堆0和1组成的是什么意思很难理解,所以就有了编码(计算机存值——文字符号,对应关系):
这些符号就可以组成很多东西了,比如数值,单词等等。 如果你想储存abc,那么在内存中储存的就是
97 98 99
想让计算机流通全世界,那么就要支持各个国家的语言,这个时候就出现了统一码。 在UTF-8中将常用的汉字用两个字节去存储,256*256=65536。(但是这里要兼容ASCII码值所以没有这么多) 有些生僻字需要三个或者四个字节去储存。 UTF-16不兼容ASCII码值,起步用2个字节。 UTF-32不兼容ASCII码值,起步用4个字节。
但是中华文化博大精深,国内又针对函数定义了新的标准,GBK。
string是字符串的类,该类添加了很多接口便于操作string类。 string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator> string; 不能操作多字节和变长字节序列。 cplusplus中是这样描述的:
string()构造空string类的对象 string(const char* s)用字符串初始化新构造string类的的对象 string(const string&s)用string类对象构造新的string类对象 这类接口是调用构造函数。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("abc");//创建string类s,给s初始化abc
cout << s << endl;
string s1;//创建string类s1,不进行初始化
cout << s1 << endl;
string s2(s);//将s拷贝到s2中
cout << s2 << endl;
return 0;
}
比较的是字符串的ASCII码值。 这些接口都是对于运算符进行重载。 先来看赋值:
string& operator= (const string& str);赋值string类的对象 string& operator= (const char* s);赋值字符串 string& operator= (char c);赋值一个字符
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1("abc");
cout << s1 << endl;
string s2;
s2 = s1;//赋值string类的对象
cout << s2 << endl;
string s3;
s3 = "adc";//赋值字符串
cout << s3 << endl;
s3 = 's';//赋值一个字符
cout << s3 << endl;
return 0;
}
等于,大于,小于等: 返回值为bool,参数是引用类。 正确返回1,错误返回0.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1("abc");
string s2("adc");
string s3("abc");
string s4("abb");
cout << "==:" << endl;
cout << (s1 == s2) << endl;
cout << (s1 == s3) << endl;
cout << "< <=:" << endl;
cout << (s1 < s2) << endl;
cout << (s1 <= s2) << endl;
cout << (s1 < s3) << endl;
cout << (s1 <= s3) << endl;
cout << (s1 <= s4) << endl;
cout << "> >=:" << endl;
cout << (s1 > s2) << endl;
cout << (s1 >= s2) << endl;
cout << (s1 >= s3) << endl;
cout << (s1 > s4) << endl;
cout << (s1 >= s4) << endl;
return 0;
}
这里要注意容量和有效字符长度的区别。 size返回字符串有效长度 capacity返回有效容量长度
size_t size() const; size_t capacity() const;
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("qwert");
cout << s.size() << endl;
cout << s.capacity() << endl;
return 0;
}
这里有一个与size功能相同的接口length。(原因是因为命名的习惯)
empty检测对象是否为空字符串,空返回1,非空返回0 clear清空对象中的有效字符串
bool empty() const; void clear();
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("qwert");
cout << s.empty() << endl;
s.clear();//这里并不改变容量大小
cout << s.empty() << endl;
return 0;
}
reserve为字符串预留空间 resize将有效字符串变成指定长度,多出的长度用指定字符补上
void reserve (size_t n = 0); void resize (size_t n); void resize (size_t n, char c);
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
s.reserve(1000);//内存对齐会多出一些空间,总大小至少是n
cout << s.capacity() << endl;
s.reserve(15);//VS编译器下15及以下才会缩容,前提是原来对象中字符串不能超过15个长度
cout << s.capacity() << endl;
string s1("qwert");
cout << s1 << endl;
int sz = s1.size();
s1.resize(4);//不到原本字符串长度就将后面的字符串覆盖
cout << s1 << endl;
s1 = "qwert";
s1.resize(4, 'x');//不到原本字符串长度就将后面的字符串覆盖
cout << s1 << endl;
s1 = "qwert";
s1.resize(8, 'x');//不足就用字符x补上
cout << s1 << endl;
return 0;
}
push_back在后面追加一个字符 append在后面追加一个字符串 operator+=在后面追加一个字符串或字符还有对象
void push_back (char c); string& append (const char* s); string& operator+= (const string& str); string& operator+= (const char* s); string& operator+= (char c);
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("qwe");
string s1("123");
s += 'r';
cout << s << endl;
s += "ty";
cout << s << endl;
s += s1;
cout << s << endl;
string s2("zxc");
s2.push_back('v');
cout << s2 << endl;
s2.append("cpp");
cout << s2 << endl;
return 0;
}
insert在指定位置插入指定内容(不推荐,时间复杂度高) pop_back尾删 erase从指定位置删除指定长度的字符(不推荐,时间复杂度高) c_str返回C语言的字符串
string& insert (size_t pos, const string& str); string& insert (size_t pos, const char* s); void pop_back(); string& erase (size_t pos = 0, size_t len = npos); const char* c_str() const;
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("qwert");
string s1("zxc");
s.insert(1, "ccc");
cout << s << endl;
s.insert(0, s1);
cout << s << endl;
s.pop_back();
cout << s << endl;
s.erase(2, 4);
cout << s << endl;
cout << s.c_str() << endl;//这里是为了方便做返回值
}
find + npos从指定位置向后查找指定位置的字符(npos是string类中特殊的静态常量-1) rfind从指定位置向前查找指定位置的字符 substr从指定位置截取n个字符返回
size_t find (const string& str, size_t pos = 0) const; size_t find (const char* s, size_t pos = 0) const; size_t rfind (const string& str, size_t pos = npos) const; size_t rfind (const char* s, size_t pos = npos) const; string substr (size_t pos = 0, size_t len = npos) const;
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("test.cpp");
int num = s.find("st", 0);
cout << num << endl;
num = s.find("st", 5);
cout << num << endl;
num = s.rfind("cpp", 7);
cout << num << endl;
num = s.rfind("cpp", 4);
cout << num << endl;
string s1 = s.substr(0, 4);
cout << s1 << endl;
return 0;
}
operator[]返回对象下标的元素 begin+ end迭代器,begin是字符串最开始的元素,end是字符串末尾的‘\0’ rbegin + rend反向迭代器
char& operator[] (size_t pos); const char& operator[] (size_t pos) const; iterator begin(); const_iterator begin() const; iterator end(); const_iterator end() const; reverse_iterator rbegin(); const_reverse_iterator rbegin() const; reverse_iterator rend(); const_reverse_iterator rend() const;
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("qwert");
cout << s[2] << endl;
s = "123456";
string::iterator left1 = s.begin();//可读可写
string::const_iterator left2 = s.begin();//可读不可写
while (left1 != s.end())
{
*left1 += 1;
cout << *left1;
left1++;
}
cout << endl;
while (left2 != s.end())
{
cout << *left2;
left2++;
}
cout << endl;
string::reverse_iterator left3 = s.rbegin();
string::const_reverse_iterator left4 = s.rbegin();
while (left3 != s.rend())
{
*left3 += 1;
cout << *left3;
left3++;
}
cout << endl;
while (left4 != s.rend())
{
cout << *left4;
left4++;
}
cout << endl;
return 0;
}
C++11语法中的for循环就是与迭代器有关。
这里还有一个类似于operator[]的接口是at,找到的就返回,找不到会抛异常。
string::swap
void swap (string& x, string& y); 交换两个同类对象中的成员
#include <iostream>
#include <assert.h>
using namespace std;
int main()
{
string s1("asdq7777777777777777777");
string s2("qwe");
s1.swap(s2);
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << s2 << endl;
cout << s2.size() << endl;
cout << s2.capacity() << endl;
return 0;
}
C++库swap 这是一个模板
template <class T> void swap ( T& a, T& b )
{
T c(a); a=b; b=c;//这里其实就相当于调用了构造函数,因为T会有自定义类型
}
平时我们的内置类型都是不需要进行析构和构造的,但是内置类型进入模板之后就需要了。 使用时要注意深浅拷贝问题,自定义类型要调用 = 。
operator>> operator<<流插入流提取
istream& operator>> (istream& is, string& str); ostream& operator<< (ostream& os, const string& str);
这个就很熟悉了上面不知道用了多少。
#include <iostream>
#include <assert.h>
using namespace std;
namespace baiye
{
class string
{
public:
typedef char* iterator;
string(const char* s = "")//对象初始化,缺省值有一个隐藏的\0
{
_arr = new char[strlen(s) + 1];
strcpy(_arr, s);
_size = strlen(_arr);
_capacity = _size;//实际可以储存的大小,不算\0的位置
}
~string()//析构
{
delete[] _arr;
_arr = nullptr;
_size = 0;
_capacity = 0;
}
string(const string& s)//拷贝构造
:_arr(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(s._arr);
swap(tmp);
}
string& operator=(string s)//这里不用引用就是临时变量,我们可以让它成为打工人
{
swap(s);
return *this;
}
//iterator
iterator begin()const
{
return _arr;
}
iterator end()const
{
return _arr + _size - 1;
}
// modify
const char* c_str()const
{
return _arr;
}
void push_back(char c)
{
if (_size == _capacity)
{
size_t len = _size == 0 ? 4 : _capacity * 2;//防止空对象扩容失败
reserve(len);
}
_arr[_size] = c;
_size++;//注意\0
_arr[_size] = '\0';
}
void append(const char* str)
{
if (_size + strlen(str) > _capacity)
{
reserve(_size + strlen(str) + 1);//不能二倍扩容,万一新加的字符串更大就不行了
}
strcpy(_arr + _size, str);
_size = strlen(_arr);
}
string& operator+=(char c)
{
push_back(c);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
void clear()
{
_arr[0] = '\0';
_size = 0;
}
void swap(string& s)
{
std::swap(_arr, s._arr);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//capacity
size_t size()const//返回长度
{
return _size;
}
size_t capacity()const//返回容量
{
return _capacity;
}
bool empty()const
{
return _size;
}
void resize(size_t n, char c = '\0')
{
if (n <= _size)
{
_arr[n] = '\0';
_size = n;
}
else
{
reserve(n);
while (_size < n)
{
_arr[_size] = c;
_size++;
}
_arr[n] = '\0';
_size = n;
}
}
void reserve(size_t n)
{
char* tmp = new char[n + 1];//+1是为了算\0
strcpy(tmp, _arr);
delete[] _arr;
_arr = tmp;
_capacity = n;
}
// access
char& operator[](size_t x)//可读可写
{
assert(x < _size);
return _arr[x];
}
const char& operator[](size_t x)const//可读不可写
{
assert(x < _size);
return _arr[x];
}
//relational operators
int judge(const string& s)
{
return strcmp(_arr, s._arr);//1大于,0等于,-1小于
}
bool operator<(const string& s)
{
int a = judge(s);
if (a < 0)
return 1;
return 0;
}
bool operator<=(const string& s)
{
int a = judge(s);
if (a > 0)
return 0;
return 1;
}
bool operator>(const string& s)
{
int a = judge(s);
if (a > 0)
return 1;
return 0;
}
bool operator>=(const string& s)
{
int a = judge(s);
if (a < 0)
return 0;
return 1;
}
bool operator==(const string& s)
{
return !judge(s);
}
bool operator!=(const string& s)
{
return judge(s);
}
// 返回c在string中第一次出现的位置
size_t find(char c, size_t pos = 0) const
{
assert(pos < _size);
while (pos < _size)
{
if (_arr[pos] == c)
return pos;
pos++;
}
return npos;
}
// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0) const
{
assert(pos < _size);
const char * p =strstr(_arr + pos, s);
if (p == nullptr)
{
return npos;
}
else
{
return p - _arr;
}
}
// 在pos位置上插入字符c/字符串str,并返回该字符的位置
string& insert(size_t pos, char c)
{
if (_size == _capacity)
{
size_t len = _size == 0 ? 4 : _capacity * 2;//防止空对象扩容失败
reserve(len);
}
int end = _size + 1;
while (end > pos)
{
_arr[end] = _arr[end - 1];
end--;
}
_arr[pos] = c;
_size++;
return *this;
}
string& insert(size_t pos, const char* str)
{
int len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len + 1);//不能二倍扩容,万一新加的字符串更大就不行了
}
int end1 = _size + 1;
int end2 = _size + len;
while (end1 > pos)
{
_arr[end2] = _arr[end1 - 1];
end1--;
end2--;
}
strncpy(_arr + pos, str, len);
_size = strlen(_arr);
return *this;
}
// 删除pos位置上的元素,并返回该元素
string& erase(size_t pos, size_t len = npos)//nops是默认值
{
if (len == npos || pos + len >= _size)//删除pos后面所有的数据
{
_arr[pos] = '\0';
_size = pos;
}
else
{
strcpy(_arr + pos, _arr + pos + len);
_size -= len;
}
return *this;
}
private:
char* _arr;//字符串储存的地方
int _size;//大小
int _capacity;//容量
const static size_t npos = -1;//C++只允许整形的静态成员在类的内部进行初始化
};
ostream& operator<<(ostream& out, const string& s)//不一定非要是友元
{
for (int i = 0; i < s.size(); i++)
{
out << s[i];
}
return out;
}
istream& operator >> (istream& in, string& s)
{
s.clear();//清空原来s中的内容
char buff[128] = { '\0' };//创建一个容纳输入数据的数组
char c = in.get();
int i = 0;
while (c != '\n')
{
if (i == 127)
{
i = 0;
s += buff;//数组满了放入s中,防止s不断扩容
}
buff[i++] = c;
c = in.get();
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
}
这里最重要的是深浅拷贝的现代写法,之前说过类的默认拷贝函数只能实现浅拷贝无法实现深拷贝,需要自定义拷贝函数,类与对象二。 现代写法是让一个新创建的对象帮你拷贝,然后与你进行交换,相当于你有了一个打工人。
string(const string& s)//拷贝构造
:_arr(nullptr)//将被拷贝的对象进行初始化
,_size(0)
,_capacity(0)
{
string tmp(s._arr);//打工人tmp
swap(tmp);//这里虽然只传参了一个tmp,但是还有一个隐藏的this指针
}
string& operator=(string s)//这里不用引用就是临时变量,我们可以让它成为打工人
{
swap(s);
return *this;
}
void swap(string& s)
{
std::swap(_arr, s._arr);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
之后tmp的成员 _arr指向的就是空指针,进行析构也不会报错,其他成员全都变成0,而需要被拷贝的*this就成功的得到了想要的数据。