【C++】30h速成C++从入门到精通(STL介绍、string类)

STL简介

什么是STL

STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。

STL的版本

  • 原始版本

Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。 HP 版本--所有STL实现版本的始祖。

  • P.J.版本

由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。

  • RW版本

由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。

  • SGI版本

由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。我们后面学习STL要阅读部分源代码主要参考的就是这个版本。

STL的六大组件

仿函数、算法、迭代器、空间适配器、容器、配接器

STL的缺陷

  1. STL库的更新太慢了。这个得严重吐槽,上一版靠谱是C++98,中间的C++03基本一些修订。C++11出来已经相隔了13年,STL才进一步更新。

  1. STL现在都没有支持线程安全。并发环境下需要我们自己加锁。且锁的粒度是比较大的。

  1. STL极度的追求效率,导致内部比较复杂。比如类型萃取,迭代器萃取。

  1. STL的使用会有代码膨胀的问题,比如使用vector/vector/vector这样会生成多份代码,当然这是模板语法本身导致的。

string类

为什么学习string类

C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

标准库中的string类

string类的文档介绍:

https://cplusplus.com/reference/string/string/?kw=string

  1. 字符串是表示字符序列的类

  1. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。

  1. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)。

  1. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。

  1. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。

总结:

  1. string时表示字符串的字符串类。

  1. 该类的接口与常规容器接口基本相同,再添加了一些专门用来操作string的常规操作。

  1. string在底层实际是:basic_string模板类的别名,typedef basic_string<char,char_traits,allocator> string;

  1. 不能操作多字节或者变长字符的序列。

string类的常用接口说明(最常用的接口)

  • string类对象的常见构造

(constructor)函数名称

功能说明

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
}

string类对象的容量操作

函数名称

功能说明

size

返回字符串有效字符长度

length

返回字符串有效字符长度

capacity

返回空间总大小

empty

检测字符串释放为空,是返回true,否则返回false

clear

清空有效字符

reserve

为字符串预留空间

resize

将有效字符的个数改成n个,多出的空间用字符c填充

// size/clear/resize
void Teststring1()
{
 // 注意:string类对象支持直接用cin和cout进行输入和输出
 string s("hello, bit!!!");
 cout << s.size() << endl;
 cout << s.length() << endl;
 cout << s.capacity() << endl;
 cout << s <<endl;
 
 // 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
 s.clear();
 cout << s.size() << endl;
 cout << s.capacity() << endl;
 // 将s中有效字符个数增加到10个,多出位置用'a'进行填充
 // “aaaaaaaaaa”
 s.resize(10, 'a');
 cout << s.size() << endl;
 cout << s.capacity() << endl;
 // 将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
 // "aaaaaaaaaa\0\0\0\0\0"
 // 注意此时s中有效字符个数已经增加到15个
 s.resize(15);
 cout << s.size() << endl;
 cout << s.capacity() << endl;
 cout << s << endl;
 // 将s中有效字符个数缩小到5个
 s.resize(5);
 cout << s.size() << endl;
 cout << s.capacity() << endl;
 cout << s << endl;
}
//==============================================================================
void Teststring2()
{
 string s;
 // 测试reserve是否会改变string中有效元素个数
 s.reserve(100);
 cout << s.size() << endl;
 cout << s.capacity() << endl;
 // 测试reserve参数小于string的底层空间大小时,是否会将空间缩小
 s.reserve(50);
 cout << s.size() << endl;
 cout << s.capacity() << endl;
}
// 利用reserve提高插入数据的效率,避免增容带来的开销
//==============================================================================
void TestPushBack()
{
 string s;
 size_t sz = s.capacity();
 cout << "making s grow:\n";
 for (int i = 0; i < 100; ++i)
 {
 s.push_back('c');
 if (sz != s.capacity())
 {
 sz = s.capacity();
 cout << "capacity changed: " << sz << '\n';
 }
 }
}
void TestPushBackReserve()
{
 string s;
 s.reserve(100);
 size_t sz = s.capacity();
 
 cout << "making s grow:\n";
 for (int i = 0; i < 100; ++i)
 {
 s.push_back('c');
 if (sz != s.capacity())
 {
 sz = s.capacity();
 cout << "capacity changed: " << sz << '\n';
 }
 }
}

【注意】

  1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。

  1. clear()只是将string中有效字符清空,不改变底层空间大小。

  1. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。

  1. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

string类对象的访问及遍历操作

函数名称

功能说明

operator[]

返回pos位置的字符,const string类对象调用

begin+end

begin获取一个字符的迭代器+end获取最后一个字符下一个位置的迭代器

rbegin+rend

begin获取一个字符的迭代器+end获取最后一个字符下一个位置的迭代器

范围for

C++11支持更简洁的范围for的新遍历方式

void Teststring()
{
 string s1("hello Bit");
 const string s2("Hello Bit");
 cout<<s1<<" "<<s2<<endl;
 cout<<s1[0]<<" "<<s2[0]<<endl;
 
 s1[0] = 'H';
 cout<<s1<<endl;
 
 // s2[0] = 'h'; 代码编译失败,因为const类型对象不能修改
}
void Teststring()
{
 string s("hello Bit");
 // 3种遍历方式:
 // 需要注意的以下三种方式除了遍历string对象,还可以遍历是修改string中的字符,
 // 另外以下三种方式对于string而言,第一种使用最多
 // 1. for+operator[]
 for(size_t i = 0; i < s.size(); ++i)
 cout<<s[i]<<endl;
 
 // 2.迭代器
 string::iterator it = s.begin();
 while(it != s.end())
 {
 cout<<*it<<endl;
 ++it;
 }
 
 string::reverse_iterator rit = s.rbegin();
 while(rit != s.rend())
 cout<<*rit<<endl;
 
 // 3.范围for
 for(auto ch : s)
 cout<<ch<<endl;
}

string类对象的修改操作

函数名称

功能说明

push_back

在字符串后尾插字符c

append

在字符串后追加一个字符串

operator+=

在字符串后追加字符串str

c_str

返回C格式的字符串

find+npos

从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置

rfind

从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置

substr

在str中从pos位置开始,截取n个字符,然后将其返回

void Teststring()
{
 string str;
 str.push_back(' '); // 在str后插入空格
 str.append("hello"); // 在str后追加一个字符"hello"
 str += 'b'; // 在str后追加一个字符'b' 
 str += "it"; // 在str后追加一个字符串"it"
 cout<<str<<endl;
 cout<<str.c_str()<<endl; // 以C语言的方式打印字符串
 
 // 获取file的后缀
 string file1("string.cpp");
 size_t pos = file.rfind('.');
 string suffix(file.substr(pos, file.size()-pos));
 cout << suffix << endl;
 
 // npos是string里面的一个静态成员变量
 // static const size_t npos = -1;
 
 // 取出url中的域名
 sring url("http://www.cplusplus.com/reference/string/string/find/");
 cout << url << endl;
 size_t start = url.find("://");
 if (start == string::npos)
 {
 cout << "invalid url" << endl;
 return;
 }
 start += 3;
 size_t finish = url.find('/', start);
 string address = url.substr(start, finish - start);
 cout << address << endl;
 
 // 删除url的协议前缀
 pos = url.find("://");
 url.erase(0, pos+3);
 cout<<url<<endl;
}

【注意】

  1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。

  1. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

string类非成员函数

函数

功能说明

operator+

尽量少用,因为传值返回,导致深拷贝效率低下

operator>>

输入运算符重载

operator<<

输出运算符重载

getline

获取一行字符串

relational operators

大小比较

运用

  • 仅仅反转字母

class Solution {
public:
 string reverseOnlyLetters(string S) {
 char* pLeft = (char*)S.c_str();
 char* pRight = pLeft + (S.size()-1);
 while(pLeft < pRight)
 {
 // 从前往后找,找到一个字母
 while(pLeft < pRight)
 {
 // 找到有效字母后停下来
 if(isalpha(*pLeft))
 break;
 
 ++pLeft;
 }
 // 从后往前找,找一个字母
 while(pLeft < pRight)
 {
 // 找到有效字母后停下来
 if(isalpha(*pRight))
 break;
 
 --pRight;
 }
 if(pLeft < pRight)
 {
 swap(*pLeft, *pRight);
 ++pLeft;
 --pRight;
 }
 }
 return S;
 }
};
  • 找字符串中第一个只出现一次的字符

class Solution {
public:
 int firstUniqChar(string s) {
 
 // 统计每个字符出现的次数
 int count[256] = {0};
 int size = s.size();
 for(int i = 0; i < size; ++i)
 count[s[i]] += 1;
 
 // 按照字符次序从前往后找只出现一次的字符
 for(int i = 0; i < size; ++i)
 if(1 == count[s[i]])
 return i;
 
 return -1;
 }
};
  • 字符串里面最后一个单词的长度

#include<iostream>
#include<string>
using namespace std;
int main()
{
 string line;
 // 不要使用cin>>line,因为会它遇到空格就结束了
 // while(cin>>line)
 while(getline(cin, line))
 {
 size_t pos = line.rfind(' ');
 cout<<line.size()-pos-1<<endl;
 }
 return 0;
}
  • 验证一个字符串是否回文

class Solution {
public:
 bool isLetterOrNumber(char ch)
 {
 return (ch >= '0' && ch <= '9')
 || (ch >= 'a' && ch <= 'z')
 || (ch >= 'A' && ch <= 'Z');
 }
 
 bool isPalindrome(string s) {
 // 先小写字母转换成大写,再进行判断
 for(auto& ch : s)
 {
 if(ch >= 'a' && ch <= 'z')
 ch -= 32;
 }
 
 int begin = 0, end = s.size()-1;
 while(begin < end)
 {
 while(begin < end && !isLetterOrNumber(s[begin]))
 ++begin;
 
 while(begin < end && !isLetterOrNumber(s[end]))
 --end;
 
 if(s[begin] != s[end])
 {
 return false;
 }
 else
 {
 
 ++begin;
 --end;
 }
 }
 
 return true;
 }
};
  • 字符串相加

class Solution {
public:
 string addstrings(string num1, string num2) 
 {
 // 从后往前相加,相加的结果到字符串可以使用insert头插
 // 或者+=尾插以后再reverse过来
 int end1 = num1.size()-1;
 int end2 = num2.size()-1;
 int value1 = 0, value2 = 0, next = 0;
 string addret;
 while(end1 >= 0 || end2 >= 0)
 {
 if(end1 >= 0)
 value1 = num1[end1--]-'0';
 else
 value1 = 0;
 
 if(end2 >= 0)
 value2 = num2[end2--]-'0';
 else
 value2 = 0;
 
 int valueret = value1 + value2 + next;
 if(valueret > 9)
 {
 next = 1;
 valueret -= 10;
 }
 else
 {
 next = 0;
 }
 
 //addret.insert(addret.begin(), valueret+'0');
 addret += (valueret+'0');
 }
 
 if(next == 1)
 {
 //addret.insert(addret.begin(), '1');
 addret += '1';
 }
 
 reverse(addret.begin(), addret.end());
 return addret;
 }
};

string类的模拟实现

namespace bit
{
 class string
 {
 public:
 typedef char* iterator;
 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);
 this->Swap(tmp);
 }
 string& operator=(string s)
 {
 this->Swap(s)
 return *this;
 }
 ~string()
 {
 if (_str)
 {
 delete[] _str;
 _str = nullptr;
 }
 }
 /
 // iterator
 iterator begin() {return _str;}
 iterator end(){return _str + _size;}
 /
 // modify
 void push_back(char c)
 {
 if (_size == _capacity)
 Reserve(_capacity*2);
 
 _str[_size++] = c;
 _str[_size] = '\0';
 }
 string& operator+=(char c)
 {
 PushBack(c);
 return *this;
 }
 
 // 作业实现
 void append(const char* str);
 string& operator+=(const char* str); 
 void clear()
 {
 _size = 0;
 _str[_size] = '\0';
 }
 void swap(string& s)
 {
 swap(_str, s._str);
 swap(_size, s._size);
 swap(_capacity, s._capacity);
 }
 const char* c_str()const
 {
 return _str;
 }
 /
 // capacity
 size_t size()const
 size_t capacity()const
 bool empty()const
 
 void resize(size_t newSize, char c = '\0')
 {
 if (newSize > _size)
 {
 // 如果newSize大于底层空间大小,则需要重新开辟空间
 if (newSize > _capacity)
 {
 Reserve(newSize);
 }
 memset(_str + _size, c, newSize - _size);
 }
 _size = newSize;
 _str[newSize] = '\0';
 }
 void reserve(size_t newCapacity)
 {
 // 如果新容量大于旧容量,则开辟空间
 if (newCapacity > _capacity)
 {
 char* str = new char[newCapacity + 1];
 strcpy(str, _str);
 // 释放原来旧空间,然后使用新空间
 delete[] _str;
 _str = str;
 _capacity = newCapacity;
 }
 }
 
 // access
 char& operator[](size_t index)
 {
 assert(index < _size);
 return _str[index];
 }
 const char& operator[](size_t index)const
 {
 assert(index < _size);
 return _str[index];
 }
 
 // 作业
 bool operator<(const string& s);
 bool operator<=(const string& s);
 bool operator>(const string& s);
 bool operator>=(const string& s);
 bool operator==(const string& s);
 bool operator!=(const string& s);
 // 返回c在string中第一次出现的位置
 size_t find (char c, size_t pos = 0) const;
 // 返回子串s在string中第一次出现的位置
 size_t find (const char* s, size_t pos = 0) const;
 
 // 在pos位置上插入字符c/字符串str,并返回该字符的位置
 string& insert(size_t pos, char c);
 string& insert(size_t pos, const char* str);
 // 删除pos位置上的元素,并返回该元素的下一个位置
 string& erase(size_t pos, size_t len);
 private:
 friend ostream& operator<<(ostream& _cout, const bit::string& s);
 friend istream& operator>>(istream& _cin, bit::string& s);
 private:
 char* _str;
 size_t _capacity;
 size_t _size;
 };
}
ostream& bit::operator<<(ostream& _cout, const bit::string& s)
{
 // 不能使用这个
 //cout << s._str;
 for(size_t i = 0; i < s.size(); ++i)
 {
 _cout<<s[i];
 }
 return _cout;
}
///对自定义的string类进行测试
void TestBitstring()
{
 bit::string s1("hello");
 s1.push_back(' ');
 s1.push_back('b');
 s1.append(1, 'i');
 s1 += 't';
 cout << s1 << endl;
 cout << s1.size() << endl;
 cout << s1.capacity() << endl;
 // 利用迭代器打印string中的元素
 string::iterator it = s1.begin();
 while (it != s1.end())
 {
 cout << *it<<" ";
 ++it;
 }
 cout << endl;
 
 // 这里可以看到一个类只要支持的基本的iterator,就支持范围for
 for(auto ch : s1)
 cout<<ch<<" ";
 cout<<endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值