1.string
basic_string<char>
是 C++ 标准库中定义的一个模板类型,用于表示一个字符串。这个模板类接收一个字符类型作为模板参数。
typedef basic_string<char> string:string类是basic_string类模板的实例化,它使用 char作为其字符类型。
2.iterator迭代器
迭代器是类似指针的东西,但不一定是指针,迭代器可以用自定义类型实现
C++标准库提供了多种迭代器,不同类型的迭代器可以在不同的场景中使用:
1. 输入迭代器(单向,只读) (Input Iterator)
- 允许只读访问元素。
- 可以对序列进行一个方向的遍历。
- 只能*解引用一次,每个元素只能读取一次。
- 示例:std::istream_iterator
2. 输出迭代器(单向,只写) (Output Iterator)
- 将数据写入序列中
- 允许对元素的写操作。
- 只能对序列进行一个方向的遍历。
- 和输入迭代器一样,每个元素只能写入一次。
- 示例:std::ostream_iterator
3. 前向迭代器(单向,可读可写,可以多次访问) (Forward Iterator)
- 可以多次解引用。
- 支持单向遍历。
- 可以在迭代中保留状态,适合于需要多次访问同一个元素的场景。
- 示例:std::forward_list,std::list
4. 反向迭代(单向,可读可写,可以多次访问)(Reverse Iterator)
- 可以多次解引用。
- 支持反向向遍历。
- 可以在不改变容器本身的情况下,从容器的最后一个元素开始向前访问每个元素,直到访问到第一个元素
-在 C++ STL 中,反向迭代器通常通过 rbegin()
和 rend()
方法获取
5. 双向迭代器(双向,可读可写,可以多次访问) (Bidirectional Iterator)
- 可以在两个方向上遍历(即前向和后向)。
- 支持多次解引用。
- 没有减法,不能[]访问
- 示例:std::list, std::set, std::map
6. 随机访问迭代器 (双向,可读可写,可以多次访问,支持随机访问)(Random Access Iterator)
- 允许在常数时间内访问任意元素。
- 支持所有双向迭代器的特性,并且可以进行加法、减法、比较等操作,可以[]访问。
- 示例:std::vector, std::deque, std::array
3.string的使用
3.1 构造函数
void test1()
{string s1;//默认构造string s2(s1);//拷贝构造char s[] = "abc123";string s3(s);//对c风格字符串进行拷贝string s4(s3, 3, 2);//从s3的pos=3位置开始拷贝两个字符string s5(s3, 2);//从s3的pos=2位置开始拷贝直到s3结束string s6("abcdddd", 4);//对c风格字符串进行4个元素拷贝string s7('s', 3);//用3个‘s’初始化///用迭代器初始化//函数模板,支持不同类的迭代器vector<char> v(10);char ch = 'a';for (auto& c : v){c = ch++;}string s8(v.begin(), v.end());
}
size_t len = npos:
npos等于-1,size_t的-1赋值给len表示整型最大值,实际意思是用npos缺省时拷贝到底,有多少拷贝多少,len超出原有长度也是拷贝到底。
3.2析构函数
不需要自己调用
3.3operator=赋值运算符重载
void test2()
{string s1,s2;s1 = "123456";s2 = s1;cout << s1 << endl;cout << s2 << endl;s1 = 'a';s2 = "abcdef";cout << s1 << endl;cout << s2 << endl;
}
使用赋值运算符时,会对原有数据进行覆盖
要分清拷贝构造和赋值:
3.4 begin()
返回字符串开头的迭代器
迭代器是随机访问迭代器
const_iterator begin() const用于const对象,返回的迭代器解引用无法进行修改
迭代器it++向后遍历
void test3()
{string s("abcdefg");std::string::iterator it = s.begin();while (it != s.end()){cout << *it;it++;} cout << endl;it = s.begin();while (it != s.end()){*it += 2;cout << *it;it++;}
}
void test4()
{const string s("11111222233333");std::string::const_iterator it = s.begin();while (it != s.end()){cout << *it;it++;}cout << endl;//it = s.begin();//while (it != s.end())//{// *it += 2;//it指向内容不可修改// cout << *it;// it++;//}
}
3.5end()
返回字符串最后一位有效数据的下一位的迭代器
迭代器是随机访问迭代器
const_iterator begin() const用于const对象,返回的迭代器解引用无法进行修改
如果对象是空字符串,begin()和end()返回相同的迭代器(迭代器支持比较)
begin()与end()的范围是左闭右开[)
3.6 rbegin() 和 rend()
返回最后一位有效数据的反向迭代器
返回反向迭代器以反向开始
反向迭代器++向字符串的开头遍历
void test5()
{string s("abcdefg");std::string::reverse_iterator rit = s.rbegin();while (rit != s.rend()){cout << *rit;rit++;}cout << endl;;rit = s.rbegin();while (rit != s.rend()){*rit += 3;cout << *rit;rit++;}
}
void test6()
{const string s("abcdefg");std::string::const_reverse_iterator rit = s.rbegin();while (rit != s.rend()){cout << *rit;rit++;}//cout << endl;;//rit = s.rbegin();//while (rit != s.rend())//{// *rit += 3;//const对象,const_reverse_iterator指向的内容不可以修改// cout << *rit;// rit++;//}
}
3.7 rend()
将反向迭代器返回到反向结束
返回一个反向迭代器,该迭代器指向字符串第一个有效数据的前一个(作为反向结尾)
rbegin()和rend()表示范围:(]
3.8 cbegin() 和 cend(()
与begin()和end()的区别:begin()和end()返回的迭代器可读可写,cbegin() 和 cend() 返回的迭代器是const_iterator只读不写,即便调用的对象本身是可读可写的。
3.9 crbegin() 和 crend()
与rbegin()和rend()的区别:rbegin()和rend()返回的迭代器可读可写,crbegin() 和 crend() 返回的迭代器是const_reverse_iterator只读不写,即便调用的对象本身是可读可写的。
3.10 size()
返回字符串的长度(不算'\0')
3.11 length()
返回字符串的长度(不算'\0')
跟size()作用是一样的
在 C++ 早期版本的标准库及其与 STL(标准模板库)的设计过程中,可能出于兼容性和可读性的考虑,保留了这两个方法。length()
方法是更早设计的,而 size()
方法也在其他 STL 容器中得到了广泛应用。
许多 STL 容器(如 std::vector
、std::deque
等)也提供了 size()
方法。因此,为了保持与其他容器的一致性,std::string
中也引入了 size()
方法。调用 size()
对于开发者来说,更符合 STL 的使用习惯。
3.12 max_size()
返回字符串可以达到的最大长度。
但实际上并不能达到最大长度,所以max_size()没什么用
3.13 resize()
作用:将字符串的长度调整至n个长度;如果n小于原长,则会删除n长度以外的数据;如果n大于原长且没有指定字符c当初始值,则使用空字符('\0')来初始化,而且此时的长度是n;指定字符c当初始值则将原数据以外的位置全部给c初始值
void test10()
{string s("abcdefg");cout << s.size() << endl;s.resize(3);cout << s.size() << endl;cout << s << endl;s.resize(10);cout << s.size() << endl;cout << s << endl;
}
3.14 capacity()
返回已分配容量的大小(单位:字节)
长度size不一定等于容量大小capacity。
3.15 reserve()
请求将字符串容量调整为计划的大小更改,长度最多为n个字符
当n大于当前字符串容量时,扩容到n个大小
当n小于等于字符串容量时,这被认为是一个非强制性的请求,具体操作不同编译器下不同
在vs2022下:没办法通过reserve缩容(即使有些空间没有使用):
3.16 clear()
清空字符串,将长度size置为0,不改变容量capacity
3.17 empty()
判断是否为空字符串,如果为空返回true,如果不为空返回false
3.18 shrink_to_fit()
请求字符串减少其容量以适合其大小。
3.19 operator[]
返回pos位置的引用(const对象数据不可修改)
当pos等于size() 长度时,返回'\0'
普通数组[]访问越界可能没有报错,string[]访问越界直接报错(有assert进行越界检查)
3.20 at()
返回pos位置的引用(const对象数据不可修改)
当pos等于size() 长度时,返回'\0'
at()与operator[] 的区别:底层处理越界不同,[]是暴力检查assert,at可以被捕获异常
3.21 back()
返回字符串最后一个字符的引用
空字符串无法调用
3.22 front()
返回字符串第一个字符的引用
空字符串无法调用
3.23 operator+=
在当前字符串后面追加字符串(或字符)并返回追加后的字符串的引用
3.24 append()
在当前字符串后面追加字符串(或字符)并返回追加后的字符串的引用
void test20()
{string s("123456789");s.append(s);//s后面追加一个ss.append(s, 4,5);//从s的4位置开始9后面5个字符追加到s后面s.append("abc");//s后面追加c风格的字符串s.append("def", 2);//s后面追加c风格的前2个字符s.append(10,'u');//s后面追加10个字符'u'cout << s << endl;vector<char> v(10);for (int i = 0; i < 10; i++){v[i] = 'a' + i;}s.append(v.begin(), v.end());//使用迭代器范围追加cout << s << endl;
}
3.25 push_back()
尾插一个字符
3.26 assign()
为字符串分配一个新值,替换其当前内容
3.27 insert()
指定原字符串的pos位置开始插入字符或字符串
重载函数返回iterator指向新插入字符的位置
3.28 erase()
删除字符串的一部分
重载函数返回iterator指向被删除部分的下一位
3.29 replace()
替换字符串的一部分
3.30 swap()(string成员函数)
交换两个对象的数据
3.31 pop_back()
删除最后一个字符
3.32 c_str()
将对象转换成c风格的字符串,并返回其const修饰的指针,不可修饰指针指向的内容
3.33 data()
返回一个指向字符串内部字符数组的指针。这个指针可以用来直接访问字符串中的字符。
3.34 get_allocator()
用于获取与该容器关联的分配器(allocator)
返回一个分配器对象,这个对象用于在容器内部分配和释放内存
分配器是一个用于管理动态内存分配的对象,允许用户在使用 STL 容器时自定义内存管理行为
void test3()
{string s("abcdefghijklmnopq");// 获取与 s 关联的分配器std::allocator<char> alloc = s.get_allocator();//使用分配器分配内存--分配10个字符的内存char* p = alloc.allocate(10);//使用分配器释放内存alloc.deallocate(p, 10);
}
3.35 copy()
将字符串对象的当前值的子字符串复制到 s 指向的数组中。此子字符串包含从位置pos开始的len个字符
返回复制的字符个数
注意:函数不会在复制内容的末尾附加 '\0 '字符
3.36 find()
在字符串中正序搜索由其参数指定的序列的第一次出现的字符或字符串,并返回第一个字符的pos
void test5()
{string s("abc11112345");size_t pos = s.find("123",0);cout << pos << endl; //6size_t pos1 = s.find("bcdef", 0, 2);//搜索"bcdef"的前两位"bc"cout << pos1 << endl; //1size_t pos2 = s.find('1');cout << pos2 << endl; //3
}
3.37 rfind()
在字符串中逆序搜索由其参数指定的序列的最后一次出现的字符或字符串,并返回第一个字符的pos
void test5()
{string s("abc123612345");size_t pos = s.rfind("123", s.size() - 1);cout << pos << endl; //7size_t pos1 = s.rfind("bcdef", s.size() - 1, 2);//搜索"bcdef"的前两位"bc"cout << pos1 << endl; //1size_t pos2 = s.rfind('1');cout << pos2 << endl; //7
}
3.38 find_first_of()
在字符串中正序搜索与其参数中指定的序列中任何字符匹配(有一个字符匹配即可)的第一个字符,并返回其pos
void test6()
{string s("112334567");size_t pos = s.find_first_of("asdf2");cout << pos << endl; // 2
}
3.39 find_last_of()
在字符串中逆序搜索与其参数中指定的序列中任何字符匹配(有一个字符匹配即可)的第一个字符,并返回其pos
void test6()
{string s("1123345267");size_t pos = s.find_last_of("asdf2");cout << pos << endl; // 7
}
3.40 find_first_not_of()
在字符串中正序搜索第一个字符,该字符与其参数中指定的任何字符都不匹配,并返回对应的pos
void test7()
{string s("123456665");size_t pos = s.find_first_not_of("asdf", 0);cout << pos << endl; //0size_t pos1 = s.find_first_not_of("12346", 0);cout << pos1 << endl; // 4
}
3.41 find_last_not_of()
在字符串中逆序搜索第一个字符,该字符与其参数中指定的任何字符都不匹配,并返回对应的pos
void test7()
{string s("123456665");size_t pos = s.find_last_not_of("asdf", s.size() - 1);cout << pos << endl; // 8size_t pos1 = s.find_last_not_of("12346", s.size() - 1);cout << pos1 << endl; // 8
}
3.42 substr()
构造一个子字符串并返回
3.43 compare()
将字符串对象(或子字符串)的值与其参数指定的字符序列进行比较
>返回1, == 返回0, <返回-1
3.44 operator+(非成员函数)
构造一个新字符串,lhs的后面尾接rhs
3.45 各种运算符重载
3.46 swap()(非成员函数)
假设调用两个string对象调用上面的swap,swap会先拷贝构造一个对象,再进行交换,这样做代价太大,实际上只需要交换这两个对象的数据即可,所以标准库里重载了一个非成员函数专门给string使用,以防调用上面代价大的方式
这个swap会直接交换两个对象的数据
3.47 operator>> 和 operator<< (非成员函数)
cin默认规定空格或换行是多个值之间的分割
3.48 getline() (非成员函数)
getline可以自定义分隔符,遇到分割符才截至,默认遇到换行才截至,空格不截至,当自定义分割符时,换行不再充当分割符
4.string遍历
4.1 下标+[] 访问
void test12()
{string s = "abcdefgh";for (int i = 0; i < s.size(); i++){s[i] += 1;cout << s[i] << ' ';}
}
4.2 迭代器访问(主流的通用访问方式)
void test13()
{string s = "abcdefgh";string::iterator it = s.begin();while (it != s.end()){(*it) += 1;cout << (*it) << ' ';it++;}
}
4.3 范围for(底层是用迭代器实现的)
void test14()
{string s = "abcdefgh";for (auto& c : s){c += 1;}for (auto c : s){cout << c << ' ';}
}
数组和所有容器都支持范围for,因为底层是由迭代器实现的,容器都可以通过迭代器遍历
5.const_iterator 和 iterator
void test15()
{string s("123456");string::iterator it = s.begin();//const_iterator指向的内容不能修改const string s1("abc");string::const_iterator it1 = s.begin();//const iterator指it2不能修改,it2指向的内容可以修改string s2("aaaa");const string::iterator it2 = s2.begin();*it2 = 'b';cout << s2 << endl;
}
6.sort() 算法
sort()函数是算法库里面模板函数,支持随机访问迭代器
sort()函数默认升序排序(<升序,>降序)
实现降序需要传递仿函数
void test16()
{string s("anfaskdnvqruqwoetufvvzx");sort(s.begin(), s.end());cout << s << endl;//gt仿函数是greater类型的对象,greter类型支持大小的比较(>)greater<char> gt;sort(s.begin(), s.end(), gt);cout << s << endl;//ls仿函数是less类型的对象,less类型支持大小的比较(<)less<char> ls;sort(s.begin(), s.end(), ls);cout << s << endl;//匿名对象写法sort(s.begin(), s.end(), greater<char>());cout << s << endl;
}
7.reverse()
用于反转范围内的元素
8.string的扩容
string如何扩容,c++标准没有规定,具体实现取决于编译器实现
vs2022下:
vs2022下,string默认有个16字节的_Buf数组,其中一个字节是留给'\0'的,剩下15字节作为容量,当所需容量小于15字节,string字符存在_Buf数组中;当所需空间大于15字节时,再动态开辟内存将所有字符存在开辟的内存中
Linux下:
9. shrink_to_fit() 的缩容
由于空间不能分段释放,所以缩容时会另开一块更小的空间,再将数据进行拷贝,释放原来的空间
10.to_string() 和 stoi
将数值转换成string并返回
string转换成整型并返回
11.string部分底层简单模拟实现
//string.h#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <iostream>
#include <assert.h>using namespace std;namespace myString
{class string{public://可以不用iterator直接用char*//把迭代器进行封装typedef char* iterator;typedef const char* const_iterator;iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;//string();//""默认有一个'\0'string(const char* str = "");string(const string& str);~string();const char* c_str() const;//string& operator=(const string& str);string& operator=(string str);size_t size() const;char& operator[] (size_t pos);const char& operator[] (size_t pos)const;void reserve(size_t n);void push_back(char ch);void append(const char* str);string& operator+= (char ch);string& operator+=(const char* str);void insert(size_t pos, char ch);void insert(size_t pos, const char* str);void earse(size_t pos, size_t len = npos);void swap(string& str);string substr(size_t pos = 0, size_t len = npos);size_t find(char ch, size_t pos = 0);size_t find(const char* str, size_t pos = 0);bool operator<(const string& str)const;bool operator<=(const string& str)const;bool operator>(const string& str)const;bool operator>=(const string& str)const;bool operator==(const string& str)const;bool operator!=(const string& str)const;void clear(); private://char _buff[16];char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;//静态成员变量不能给缺省值,因为不走初始化列表//特例:static的const的整型可以给缺省值--既是声明也是定义const static size_t npos;};ostream& operator<<(ostream& os, const string& str);istream& operator>>(istream& is, string& str);
}
//string.cpp#include "string.h"//这样是因为多个文件同一个命名空间会合并
namespace myString
{//static 成员变量类内声明,类外定义//static成员变量属于静态区,若在string.h定义,在多文件展开就会冲突const size_t string::npos = -1;string::iterator string:: begin(){return _str;}string::iterator string::end(){return _str + _size;}string::const_iterator string::begin() const{return _str;}string::const_iterator string::end() const{return _str + _size; }//错误写法//可能出现解引用nullptr的错误//string::string()//{// _str = nullptr;// _size = _capacity = 0;//}//strlen不算入'\0'//_size不算入'\0'//_capacity不算入'\0'string::string(const char* str):_size(strlen(str)){//""会被strcpy拷贝过去//+1是因为'\0'_str = new char[_size + 1];_capacity = _size;strcpy(_str, str);}传统写法//string::string(const string& str)//{// _str = new char[str._capacity + 1];// strcpy(_str, str._str);// _size = str._size;// _capacity = str._capacity;//}// //现代写法(特点:让别人干活,再交换)string::string(const string& str){//调用构造干活string tmp(str._str);swap(tmp);}string::~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}const char* string::c_str() const{return _str;}传统写法//string& string::operator=(const string& str)//{// if(this != &str)// {// char* tmp = new char[str._capacity + 1];// strcpy(tmp, str._str);// delete[] _str;// _str = tmp;// _size = str._size;// _capacity = str._capacity;// }// return *this;//}现代写法//string& string::operator=(const string& str)//{// if (this != &str)// {// string tmp(str._str);// swap(tmp);// }// return *this;//}//现代写法再进化string& string::operator=(string str){//str局部对象,出了作用域析构swap(str);return *this;}size_t string::size() const{return _size;}char& string::operator[] (size_t pos){assert(pos < _size && pos >= 0);return _str[pos];}const char& string::operator[] (size_t pos)const{assert(pos < _size && pos >= 0);return _str[pos];}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(char ch){if(_size == _capacity){size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newCapacity);}_str[_size] = ch;_str[++_size] = '\0';}void string::append(const char* str){size_t len = strlen(str);if(_size + len > _capacity){reserve(_size + len);}//不用strcat('\0'问题,而且从头遍历效率不高),用strcpy就行strcpy(_str+_size, str);_size += len;}string& string::operator+= (char ch){push_back(ch);return *this;}string& string::operator+=(const char* str){append(str);return *this;}void string::insert(size_t pos, char ch){assert(pos >= 0 && pos <= _size);if (_size == _capacity){size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newCapacity);}//方法1://int end = _size;//while (end >= (int)pos)//类型不同,发生隐式类型转换,有符号转换成无符号//{// _str[end + 1] = _str[end];// --end;//}//方法2: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 >= 0 && pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}int end = _size;while (end >= (int)pos){_str[end + len] = _str[end];--end;}memcpy(_str + pos, str, len);_size += len;}void string::earse(size_t pos, size_t len){assert(pos >= 0 && pos < _size);if (len >= _size - pos){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}}void string::swap(string& str){std::swap(_str, str._str);std::swap(_size, str._size);std::swap(_capacity, str._capacity);}string string::substr(size_t pos, size_t len){if (len > _size - pos){string sub(_str + pos);return sub;}else{string sub;sub.reserve(len);for (size_t i = 0; i < len; i++){sub += _str[pos + i];}}}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){const char* p = strstr(_str + pos , str);return p - _str;}bool string::operator<(const string& str)const{return strcmp(_str, str._str) < 0;}bool string::operator<=(const string& str)const{return *this < str || *this == str;}bool string::operator>(const string& str)const{return strcmp(_str, str._str) > 0;}bool string::operator>=(const string& str)const{return !(*this < str);}bool string::operator==(const string& str)const{return strcmp(_str, str._str) == 0;}bool string::operator!=(const string& str)const{return !(*this == str);}void string::clear(){_str[0] = '\0';_size = 0;}ostream& operator<<(ostream& os, const string& str){for (size_t i = 0; i < str.size(); i++){os << str[i];}return os;}缺点:频繁扩容//istream& operator>>(istream& is, string& str)//{// str.clear();// char ch = is.get();// while (ch != ' ' && ch != '\n')// {// str += ch;// ch = is.get();// }// return is;//}//cin优化方案--模拟缓冲区减少扩容的次数istream& operator>>(istream& is, string& str){str.clear();char buff[128];int i = 0;char ch = is.get();while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[i] = '\0';str += buff;i = 0;}ch = is.get();}if (i != 0){buff[i] = '\0';str += buff;}return is;}
}
12.实现过程中的一些问题
12.1 #include问题
#include "" 双引号默认在源文件当前目录下查找,找不到再去标准库里查找
#include <>是直接在标准库里查找
12.2 不同文件相同命名空间
多个文件同一个命名空间会合并
12.3 delete和free可以对nullptr
在C++中,delete 和 free 对 nullptr 的行为是安全的,可以安全地对 nullptr 调用,而不会造成任何问题。
12.4 实现范围for
范围for的底层就是迭代器,自己定义的类需要自己实现迭代器就能使用范围for(一定要实现begin()和end(), 且函数名字不能变,iterator可以变名字)
12.5 strcat()
strcat会从头开始遍历找'\0',覆盖找到的'\0'开始拼接,并在拼接后的字符串添加'\0'
12.6 静态成员变量
静态成员变量不能给缺省值,因为不走初始化列表
特例:static的const的整型可以给缺省值--既是声明也是定义
static 成员变量类内声明,类外定义
static 成员变量属于静态区,若在string.h定义,在多文件展开就会冲突
12.7 引用计数的写时拷贝(延时拷贝)
深拷贝需要重新开辟空间再进行赋值,浅拷贝只是将地址按字节拷贝,假设构造的这个对象不进行修改操作,可以直接用浅拷贝减少深拷贝的消耗。
浅拷贝的问题及其解决:
1.不同对象指向同一块资源,导致析构两次报错
解决方法:用引用计数,在析构函数内部对引用计数进行判断,使最后一个析构的对象释放空间。
2.一个修改影响另一个
解决方法:写时拷贝,需要修改的对象重新开辟并指向新的空间,然后进行拷贝并修改,不修改的对象仍然指向原来的空间
不修改就赚了,可以提升效率,linux下使用这个方案