●个人主页:你帅你先说.
●欢迎点赞关注收藏
●既选择了远方,便只顾风雨兼程。
●欢迎大家有问题随时私信我!
●版权:本文由[你帅你先说.]原创,CSDN首发,侵权必究。
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。
1.STL库的更新太慢了。这个得严重吐槽,上一版靠谱是C++98,中间的C++03基本一些修订。C++11出来已经相隔了13年,STL才进一步更新。
2.STL现在都没有支持线程安全。并发环境下需要我们自己加锁。且锁的粒度是比较大的。
3.STL极度的追求效率,导致内部比较复杂。比如类型萃取,迭代器萃取。
4.STL的使用会有代码膨胀的问题,比如使用vector/vector/vector这样会生成多份代码,当然这是模板语法本身导致的。
C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
讲到string类就必须科普一些知识
我们知道,数据是以二进制的形式在内存中存储的,因为计算机只认识
0
和1
,比如我们存一个数字10
,在计算中就会存入1010
,除了数字,我们还可能会存入字母、符号等,计算机不会直接就把这些符号存进去,而是制定了一个规则,这些符号和字母对应一个值,这就是我们所熟知的ASCII码,比如存字符'A'
,对应65
,计算机中就会存入1000001
,符号也是类似的原理,早期计算机是欧美那些国家发明出来的,但随着国家之间加强合作,最终每个国家都会使用计算机编程,所以每个国家都会制定一套各自语言的存储规则,所以就有人制定了一个表示全世界的编码表
,叫做utf
(Unicode Transformation Format),它有很多编码方式,例如utf-8、utf-16、utf-32。现在用的最多的是utf-8
,所以编码实际上就是值和符号建立映射关系。当然,我们国家也有自己一套中文量身定制的编码表,叫gbk
函数名称 | 功能说明 |
---|---|
string() | 构造空的string类对象,即空字符串 |
string(const char* s) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s) | 拷贝构造函数 |
void Teststring()
{
string s1; // 构造空的string类对象s1
string s2("hello bit"); // 用C格式字符串构造string类对象s2
string s3(s2); // 拷贝构造s3
}
- 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不会改变容量大小。
- 在string尾部追加字符时,s.push_back© / s.append(1, c) / s += 'c’三种的实现方式差不多,一般 情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
- 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
c_str
我们发现fopen函数只支持const char*
类型(即C语言格式的字符串)的数据,此时就可以用c_str来转换。
find/rfind && npos && substr
现在我们有一个需求,要求取出文件的后缀名,就需要用上这些函数了。
npos
是一个常数,用来表示不存在的位置,一般是取-1,转换成size_t
类型后也是一个很大的数,可以认为不存在。
接下来我们还有个需求,要把http://www.cplusplus.com/reference/string/string/
这条网址分成协议
、域名
、虚拟目录
getline
第一个参数需要传入输入流
,第二个参数需要传一个字符串。
为了避免与库里面的stirng类冲突,我们可以自己定义个命名空间。
#pragma once
#include
namespace ljt
{
class string
{
public:
string(const char* str) :_str(new char[strlen(str) + 1])
{
strcpy(_str, str);
}
~string()
{
delete[] _str;
_str = nullptr;
}
private:
char* _str;
};
}
这里说几个小细节,构造函数中不能写成_str(str)
,因为这样传过来的字符串是无法更改的,所以必须在堆上申请空间进行存储。strlen(str)+1
这里面加1的原因是还有一个'\0'
也占空间大小。
此时我们测试一下程序,发现程序奔溃了。
我们通过调试来看看是什么问题。
这个时候我们就发现问题了,s1和s2在类里面定义的指针都指向了同一块空间,当程序结束时,s2析构函数释放空间,s1空间又释放了一次空间,一块空间被释放了两次,造成了程序崩溃。
这里就涉及到了深浅拷贝问题,浅拷贝就是完全复制粘贴,就是上面这个例子。在这里用浅拷贝显然不行,所以我们可以使用深拷贝,刚刚我们发现程序崩溃的原因是一个空间释放多次,为了解决这个问题,我们可以自己写一个拷贝构造函数,且每次构造都开一个空间,这样就能避免重复释放相同空间。
#pragma once
#include
namespace ljt
{
class string
{
public:
string(const char* str) :_str(new char[strlen(str) + 1])
{
strcpy(_str, str);
}
string(const string& s):_str(new char[strlen(s._str)+1])
{
strcpy(_str, s._str);
}
~string()
{
delete[] _str;
_str = nullptr;
}
private:
char* _str;
};
}
此时通过调试发现就不是同一块空间了。
刚刚我们写的深拷贝是传统写法,深拷贝还有一种现代写法。
string(const string& s):_str(nullptr)
{
string tmp(s._str);
swap(_str,tmp._str);
}
这种写法是这样的,首先让tmp开一块和s._str一样的空间,然后交换_str和tmp._str所指向的空间,然后出了作用域tmp会调用析构函数,释放空间。
当然,深浅拷贝不仅仅这么简单。
我们发现,这种情况下又崩溃了。
图解原因
此时s1指向了s3开的空间,而s1开的空间又没有释放,可我们刚刚不是已经解决这个问题了吗?在这里需要区分一下不同写法所调用的函数是哪些
int main()
{
String s1("hello"); //调用构造函数
String s2 = "world"; //调用构造函数
String s3(s1); //调用拷贝构造函数
String s4 = s1; //调用拷贝构造函数
String s5; //调用构造函数
s5 = s1; //调用拷贝赋值运算符
return 0;
}
尤其要注意string s4 = s1
和s5 = s1
,一个是初始化,一个是赋值。
所以要想解决刚刚那个问题,我们还得写一个拷贝赋值运算符,也就是重载=运算符。
string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s);
swap(_str,tmp._str);
}
return *this;
}
这段代码一般来说大多数情况下是没有问题了,但有一种特殊情况,如果new失败呢?前面我们说过new失败会抛异常,那你失败就失败吧,但空间已经被你释放了,所以这段代码还要再优化一下。
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[strlen(s._str) + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
}
return *this;
}
同样地,这段代码也是传统写法,它也有现代写法。
string& operator=(const string& s)
{
if(this != &s)
{
string tmp(s);
swap(_str,tmp._str);
}
return *this;
}
思想和上面深拷贝的类似。
这段代码还可以写的更简洁
string& operator=(string s)
{
swap(_str,s._str);
return *this;
}
//这边不用判断自己给自己赋值,因为判断不了,因为传的不是引用,地址不一样了。
接下来我们开始写string类
的增删查改
我们增加_size
和_capacity
两个变量,然后对构造函数进行完善
namespace ljt
{
class string
{
public:
string(const char* str = "") :_size(strlen(str)),_capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
string(const string& s):_str(nullptr) :_size(0), _capacity(0)
{
string tmp(s._str);
swap(_str,tmp._str);
swap(_size,tmp._size);
swap(_capacity,tmp._capacity);
}
string& operator=(string s)
{
swap(_str,s._str);
swap(_size,s._size);
swap(_capacity,tmp._capacity);
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
实现c_str()函数
const char* c_str()const
{
return _str;
}
实现size()函数
size_t size()
{
return _size;
}
实现[]重载
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
实现[]重载后,不仅可以得到某个位置的值,还可以修改某个位置的值,因为返回的是引用。
如果把引用去掉,就只能访问,不能修改。
迭代器
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str+_size;
}
reserve()函数
void reserve(size_t n)
{
if(n > _capacity)
{
char* tmp = new char[n+1];
strcpy(tmp,_str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
resize()函数
void resize(size_t n, char ch = '\0')
{
if (n <= _size)
{
_size = n;
}
else
{
if (n > _capacity)
{
reserve(n);
}
memset(_str + _size,ch,n - _size);
_size = n;
}
_str[n] = '\0';
}
push_back()函数
void push_back(char ch)
{
if(_size == capacity)
{
reserve(_capacity == 0 ? 4:capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
append()函数
void append(const char* str)
{
size_t len = strlen(str);
if(_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size,str);
_size += len;
}
string类里的swap()
C++里除了标准库里有swap()函数,string类里面也有swap()函数,那为什么string类还要单独写一个swap()函数?相信大家可以猜到,可能string类里的swap()函数是专门针对string类写的,所以效率可能会更高,确实是这样的。
这是C++标准库里的swap()函数
我们看到,标准库里的方法要进行三次拷贝构造,且都是深拷贝。
而string类里的swap()函数只是简单进行值的交换,所以效率更高。
string类+=运算符重载
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
string类比大小运算符重载
bool operator<(const string& s1,const string& s2)
{
retrun strcmp(s1.c_str(),s2.c_str()) < 0;
}
bool operator==(const string& s1,const string& s2)
{
retrun strcmp(s1.c_str(),s2.c_str()) == 0;
}
bool operator<=(const string& s1,const string& s2)
{
retrun s1 < s2 || s1 == s2;
}
bool operator>(const string& s1,const string& s2)
{
retrun strcmp(s1.c_str(),s2.c_str()) > 0;
}
bool operator>=(const string& s1,const string& s2)
{
retrun !(s1 < s2);
}
bool operator!=(const string& s1,const string& s2)
{
retrun !(s1 == s2);
}
find()函数
size_t find(char ch)
{
for(size_t i = 0;i < _size;i++)
{
if(ch == _str[i])
{
return i;
}
}
return npos;
}
size_t find(const char* s,size_t pos = 0)
{
const char* ptr = strstr(_str + pos,s);
if(ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
insert()函数
string& insert(size_t pos,char ch)
{
assert(pos <= _size);
if(_size == capacity)
{
reserve(_capacity == 0 ? 4 :_capacity * 2)
}
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* s)
{
assert(pos <= _size);
size_t len = strlen(s);
if(_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size + len;
while(end > pos + len)
{
_str[end] = _str[end-1en];
end--;
}
strncpy(_str + pos,s,len);
//这里不能用strcpy的原因是因为strcpy会把'\0'拷过去
_size += len;
return *this;
}
erase()函数
string& erase(size_t pos = 0, 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;
}
clear()
void clear()
{
_str[0] = '\0';
_size = 0;
}
<<运算符重载
ostream& operator<<(ostream& out,const string& s)
{
for(int i = 0;i < s.size();i++)
{
out<<s[i];
}
//out<
return out;
}
>>运算符重载
istream& operator>>(istream& in,string& s)
{
s.clear();
char ch = in.get();
while(ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get()
}
return in;
}
喜欢这篇文章的可以给个一键三连
点赞关注收藏