ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C++-----string基本使用详解

C++-----string基本使用详解 一string的概念与使用1.string到底是什么在 C 语言中字符串本质上是一个以\0结尾的字符序列比如char str[] hello;它实际上类似h e l l o \0C 语言虽然提供了strlen、strcpy、strcat等函数但字符串的数据和操作字符串的函数是分离的而且底层空间经常需要程序员自己管理因此容易出现越界、空间不足等问题C 中可以直接#include string using namespace std; string s hello;你可以把string暂时理解为C 标准库封装好的“可自动管理空间的字符串类”。它不仅保存字符串本身还提供了大量成员函数s.size(); s world; s.find(or); s.substr(...); s.clear();所以学习string时实际上要掌握两个层次第一层会使用 string ↓ 构造、访问、遍历、增删、查找、截取、输入输出 第二层理解 string 为什么能这样工作 ↓ 动态内存、构造函数、析构函数 ↓ 浅拷贝 / 深拷贝 ↓ 拷贝构造 / operator2.string的构造怎么创建一个字符串对象有几个常见构造方式string s1;创建空字符串s1 可以使用 C 风格字符串构造string s2(hello);也可以string s2 hello;还可以创建 n 个相同字符string s3(5, A);结果AAAAA还可以进行拷贝构造string s4(s2);相当于s2 hello s4 hello因此最常见的四种情况就是string s1; // 空字符串 string s2(hello); // C字符串构造 string s3(5, x); // xxxxx string s4(s2); // 拷贝构造二:auto和范围for1.autoauto的核心含义是让编译器根据初始化表达式自动推导变量类型。例如int a 10; auto b a; auto c A;编译器实际上会推导成int b a; char c A;特别适合类型很长的情况。这里要记住一个很重要的区别int x 10; auto a x; auto b x;a是一个新的变量a ── 10 x ── 10修改a不影响x。而auto b x;b是x的引用修改b就是在修改x2.范围for传统遍历string s hello; for (int i 0; i s.size(); i) { cout s[i]; }C11 可以写成for (auto ch : s) { cout ch; }范围for可以用于数组和容器对象它会自动完成迭代、取数据和结束判断对于容器底层遍历思想可以理解为利用迭代器完成这里尤其重要的是for (auto ch : s)和for (auto ch : s)是不一样的。前者for (auto ch : s) { ch A; }ch是字符的副本所以不会真正修改字符串。而for (auto ch : s) { ch A; }ch是原字符的引用可以直接修改string。比如可以对字符进行乘法操作for (auto e : array) { e * 2; }三string类的常用接口一string类对象的容量操作1.size(),length(),capacity()size()和length()都是返回字符串有效字符长度capacity()是返回空间总大小他们的返回值都是size_t类型的理解它们之前一定要区分size 当前有多少个有效字符 capacity 当前已经准备了多少存储空间例如string s hello;逻辑上有效字符 h e l l o ↑ ↑ 共 5 个 size() 5而capacity()可能比 5 大因为string为了以后追加字符可能提前准备额外空间。可以类比成宿舍当前住 5 人 → size 5 宿舍最多能住 10 人 → capacity 10例如两者底层作用相同通常更常使用size()因为它和其他容器的接口保持一致2.empty()empty()是检测字符串释放为空串是返回true否则返回false它的返回值类型是bool类型string s2 ; if (s2.empty()) { cout 字符是空 endl; } else cout 字符不为空 endl;相当于判断s.size() 0但s.empty()可读性更好。3.clear()clear()只清字符不一定释放空间例如string s hello; s.clear();此时s.size() 0但是特别强调clear()只是清除有效字符并不会因此改变底层已经拥有的空间大小可以理解为原来 size 5 capacity 15 clear以后 size 0 capacity 15宿舍的人走了但房间没有拆4.reserve()reserve()提前准备空间例如你准备往字符串里放很多字符string s; s.reserve(100);意思不是字符串现在有100个字符而是提前准备能够容纳大约100个字符的空间所以s.size()仍然是0注意reserve改变的是预留空间不改变有效元素个数它的用途主要是提高效率。假如你不断s a; s b; s c; ...空间不够时可能需要申请新空间 ↓ 复制旧数据 ↓ 释放旧空间如果提前知道大概需要 1000 个字符s.reserve(1000);就可以减少重新申请空间的次数。所以如果能够预估字符串大概会存放多少字符可以提前使用reserve。5.resize()resize()将有效字符的个数该成n个多出的空间用字符c填充注意resize()真正改变size()它与reserve()不同假设string s hello;执行s.resize(3);得到hel因为有效字符数量变成了 3。如果s.resize(8, x);得到helloxxx当增加字符个数时resize(n)会用默认值填补新增位置而resize(n, c)会用字符c填补如果缩小字符串则底层空间总大小通常不会因此缩小所以你可以牢牢记住二string类对象的访问及遍历操作1.operator:返回pos位置的字符最常见的方式operator[]例如string s hello; cout s[0];输出h也可以修改s[0] H;得到Hello可以把s[i]理解成访问字符串中的第 i 个字符下标从0开始所以hello 012342.beginend首先我们要制动什么是迭代器呢你可以先把它理解成一种“类似指针”的对象用来访问容器中的元素。它的主要作用是按照一定顺序遍历vector、string、list、map等容器中的元素而不需要关心这些容器内部到底是怎么存储数据的begin()指向第一个字符例如string s hello; auto it s.begin(); cout *it;输出h因为hello ↑ begin()也可以修改字符string s hello; auto it s.begin(); *it H; cout s;输出Hello因为*it代表当前迭代器指向的字符end()指向最后一个字符后面这是最容易搞错的地方。对于string s hello;不是h e l l o ↑ end()而是h e l l o [ ] ↑ end()所以不能直接cout *s.end();这是错误的因为end()不指向有效字符。如果想通过end()得到最后一个字符可以先往前移动一次string s hello; auto it s.end(); --it; cout *it;输出o因为初始 h e l l o [ ] ↑ it 执行 --it 后 h e l l o [ ] ↑ it3.rbeginrendrbegin()反向遍历的起点rbegin()可以理解成reverse begin也就是反向遍历时的第一个位置。对于string s hello;rbegin()指向最后一个字符h e l l o ↑ rbegin()例如string s hello; auto it s.rbegin(); cout *it;输出orend()反向遍历的结束位置rend()是reverse end反向遍历结束的位置。它位于第一个字符之前[ ] h e l l o ↑ rend()所以反向遍历#include iostream #include string using namespace std; int main() { string s hello; for (auto it s.rbegin(); it ! s.rend(); it) { cout *it ; } return 0; }输出o l l e h注意这里一个比较有意思的地方it;虽然写的是但是由于这是反向迭代器所以实际方向是从右往左o → l → l → e → h也就是说普通迭代器 是向右走。而反向迭代器 是向左走。4.三种非常重要的遍历方式第一种是下标for (size_t i 0; i s.size(); i) { cout s[i]; }第二种是迭代器for (auto it s.begin(); it ! s.end(); it) { cout *it; }begin() end()用于获得遍历区间另外还有rbegin() rend()进行反向遍历第三种就是最方便的范围forfor (auto ch : s) { cout ch; }需要修改时for (auto ch : s) { ch 1; }(三)string类对象的修改操作1.push_back、append、push_back:在字符串后尾插字符cappend:在字符串后追加一个字符串:在字符串后追加字符串str例如string s hello; s.push_back(!);得到hello!push_back主要添加一个字符。而s.append( world);得到hello world最常见的还是s !; s world;既可以连接字符也可以连接字符串因此实际使用中很方便2.c_str()c_str()是std::string提供的一个成员函数用来把 C 的string转成C 风格字符串也就是const char*比如#include iostream #include string using namespace std; int main() { string s hello; const char* p s.c_str(); cout p endl; return 0; }输出hello你可以把它理解为string s hello;内部保存的是字符串内容而s.c_str()会返回一个指向字符序列的指针h e l l o \0 ↑ p最后的\0是 C 风格字符串的结束标志所以c_str()最常见的用途就是在一些只接受const char*的 C 函数或旧式接口里使用std::string。例如printf#include cstdio #include string using namespace std; int main() { string s hello; printf(%s\n, s.c_str()); return 0; }因为%s需要的是const char*而不是std::string所以不能直接这样写printf(%s, s); // 错误而要写printf(%s, s.c_str()); // 正确3.find和nposfind()用来查找字符或子串的位置如果没找到就返回string::npos假设有string s hello world;查找字符size_t pos s.find(o); cout pos;输出4因为字符串下标是从0开始的h e l l o w o r l d 0 1 2 3 4 5 6 7 8 9 10 ↑ o所以第一个o的位置是4。如果查找一个不存在的字符size_t pos s.find(x);这时候不会返回-1来表示失败而是返回string::npos因此通常这样写string s hello world; size_t pos s.find(x); if (pos string::npos) { cout 没有找到; } else { cout 找到了位置是 pos; }输出没有找到find()也可以查找子字符串string s hello world; size_t pos s.find(world); cout pos;输出6因为hello world ↑ 6也就是world从下标6开始。完整写法通常是string s hello world; size_t pos s.find(world); if (pos ! string::npos) { cout 找到了 endl; cout 起始位置 pos endl; } else { cout 没有找到 endl; }find()默认找第一个匹配位置例如string s banana; size_t pos s.find(a); cout pos;输出:1虽然banana中有很多个ab a n a n a 0 1 2 3 4 5 ↑ ↑ ↑但是s.find(a)只返回第一个1find可以指定从哪里开始找例如string s banana; size_t pos s.find(a, 2); cout pos;这里表示从下标2开始寻找a。字符串b a n a n a 0 1 2 3 4 5 ↑ 从这里开始所以找到的是3,而不是1。如何找到所有相同字符例如string s banana;我们想找到所有asize_t pos s.find(a); while (pos ! string::npos) { cout pos ; pos s.find(a, pos 1); }输出1 3 54.rfindrfind()是std::string中用来从后往前查找字符或子字符串的函数例如string s banana;字符串下标是b a n a n a 0 1 2 3 4 5如果写cout s.find(a);输出1因为find()找的是第一个a。而cout s.rfind(a);输出5因为rfind()从后往前找所以找到的是最后一个a虽然rfind()是从右往左查找但是它返回的仍然是字符串正常的下标查找子字符串rfind()不仅能查一个字符也可以查字符串。例如string s abcabcabc; size_t pos s.rfind(abc); cout pos;输出6因为a b c a b c a b c 0 1 2 3 4 5 6 7 8 ↑ abcabc一共出现了三次abc abc abc ↑ ↑ ↑ 0 3 6rfind()找最后一次出现的位置6而s.find(abc)返回0所以区别非常直观s.find(abc); // 第一次出现的位置 s.rfind(abc); // 最后一次出现的位置找不到时仍然返回string::npos这和find()完全一样。例如string s hello; size_t pos s.rfind(x); if (pos string::npos) { cout 没有找到; } else { cout 找到了 pos; }输出没有找到所以标准写法依然是if (s.rfind(abc) ! string::npos) { cout 找到了; }可以指定从哪个位置往前找它还有一种形式s.rfind(要找的内容, 起始位置);不过这里要特别注意rfind()的第二个参数表示从这个下标开始向前查找。例如string s banana; size_t pos s.rfind(a, 4); cout pos;字符串b a n a n a 0 1 2 3 4 5 ↑ 从这里开始从下标4开始向左4 → 3 → 2 → 1 → 0首先遇到a的位置是3所以输出35.substr语法可以理解为s.substr(pos, n);即从pos位置开始取n个字符。定义为从pos开始截取n个字符并返回例如string s hello world; string t s.substr(6, 5);得到world所以hello world ↑ pos 6取 5 个worldfind substr经常一起出现size_t pos s.find(:); string left s.substr(0, pos); string right s.substr(pos 1);
返回列表