C++ string 使用

2022年9月13日 568点热度 0人点赞 0条评论

使用场合

string 是 C++ 标准库的一个重要的部分, 主要用于字符串处理. 可以使用输入输出流方式直接进行操作, 也可以通过文件等手段进行操作. 同时 C++ 的算法库对 string 也有着很好的支持, 而且 string 还和 C 语言的字符串之间有着良好的接口.

声明和初始化

想使用 string 首先要在头文件当中加入 #include <string>.

声明

string s;      //声明一个 string 对象
string ss[10]; //声明一个 string 对象的数组

初始化

使用等号的初始化叫做 拷贝初始化, 不使用等号的初始化叫做 直接初始化.

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    string s;                //默认初始化, 一个空字符串
    string s1("ssss");       //s1 是字面值"ssss" 的副本
    string s2(s1);           //s2 是 s1 的副本
    string s3=s2;            //s3 是 s2 的副本
    string s4(10,'c');       //把 s4 初始化
    string s5="hiya";        //拷贝初始化
    string s6=string(10,'c');//拷贝初始化, 生成一个初始化好的对象, 拷贝给 s6

    //string s(cp, n)
    char cs[]="12345";
    string s7(cs, 3);        //复制字符串 cs 的前 3 个字符到 s 当中

    //string s(s2, pos2)
    string s8="asac";
    string s9(s8, 2);        //从 s2 的第二个字符开始拷贝, 不能超过 s2 的 size

    //string s(s2, pos2, len2)
    string s10="qweqweqweq";
    string s11(s10, 3, 4);   //s4 是 s3 从下标 3 开始 4 个字符的拷贝, 超过 s3.size 出现未定义
    return 0;
}

字符串处理

追加字符

想要往 string 末尾追加字符的话,用的不是 append 函数,而是 push_back 函数。

substr 操作

注意 substr 没有迭代器作为参数的操作.

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    string s="abcdefg";
    s.substr(pos1, n)        // 返回字符串位置为 pos1 后面的 n 个字符组成的串
    string s2=s.substr(1, 5);// bcdef
    s.substr(pos)            //得到一个 pos 到结尾的串
    string s3=s.substr(4);   //efg
    return 0;
}

如果输入的位置超过字符的长度, 会抛出一个 out_of_range 的异常

insert 操作

注意用迭代器当参数和无符号数当参数的区别.

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    string str="to be question";
    string str2="the ";
    string str3="or not to be";
    string::iterator it;

    //s.insert(pos, str)//在 s 的 pos 位置插入 str
    str.insert(6, str2);                 // to be the question

    //s.insert(pos, str, a, n) 在 s 的 pos 位置插入 str 中插入位置 a 到后面的 n 个字符
    str.insert(6, str3, 3, 4);             // to be not the question

    //s.insert(pos, cstr, n)//在 pos 位置插入 cstr 字符串从开始到后面的 n 个字符
    str.insert(10,"that is cool", 8);    // to be not that is the question

    //s.insert(pos, cstr) 在 s 的 pos 位置插入 cstr
    str.insert(10,"to be ");            // to be not to be that is the question

    //s.insert(pos, n, ch) 在 s.pos 位置上面插入 n 个 ch
    str.insert(15, 1,':');               // to be not to be: that is the question

    //s.insert(s.it, ch) 在 s 的 it 指向位置前面插入一个字符 ch, 返回新插入的位置的迭代器
    it = str.insert(str.begin()+5,','); // to be, not to be: that is the question

    //s.insert(s.it, n, ch)//在 s 的 it 所指向位置的前面插入 n 个 ch
    str.insert (str.end(), 3,'.');       // to be, not to be: that is the question...

    //s.insert(it, str.ita, str.itb) 在 it 所指向的位置的前面插入[ita, itb) 的字符串
    str.insert (it+2, str3.begin(), str3.begin()+3); // to be, or not to be: that is the question...

    return 0;
}

erase 操作

用来执行删除操作, 删除操作有三种:

  • 指定 pos 和 len, 其中 pos 为为起始位置, pos 以及后面 len-1 个字符串都删除
  • 迭代器, 删除迭代器指向的字符
  • 迭代器范围, 删除这一范围的字符串, 范围左闭右开
#include <iostream>
#include <string>

int main () {
  std::string str ("This is an example sentence.");
  std::cout << str << '\n';
  str.erase (10, 8);
  //直接指定删除的字符串位置第十个后面的 8 个字符
  std::cout << str << '\n';
  str.erase (str.begin()+9);
  //删除迭代器指向的字符
  std::cout << str << '\n';
  str.erase (str.begin()+5, str.end()-9);
  //删除迭代器范围的字符
  std::cout << str << '\n';
  return 0;
}

append 和 replace 操作

  • append 函数可以用来在字符串的末尾追加字符和字符串. 由于 string 重载了运算符, 也可以用 += 操作实现.
  • repalce 顾名思义, 就是替换的意思, 先删除, 后增加.
#include <iostream>
#include <string>

int main () {
    std::string str;
    std::string str2="Writing ";
    std::string str3="print 10 and then 5 more";

    //直接追加一个 str2 的字符串
    str.append(str2);                       // "Writing "
    //后面追加 str3 第 6 个字符开始的 3 个字符串
    str.append(str3, 6, 3);                   // "10 "
    //追加字符串形参的前 5 个字符
    str.append("dots are cool", 5);          // "dots "
    //直接添加
    str.append("here: ");                   // "here: "
    //添加 10 个'.'
    str.append(10u,'.');                    // ".........."
    //添加 str3 迭代器范围的字符串
    str.append(str3.begin()+8, str3.end());  // " and then 5 more"
    //最后这个比较特殊, 意思是添加 5 个'A', 实际上参数里面的 65 对应的 asc 码就是 65
    str.append<int>(5, 65);                // "....."
    //字符串追加也可以用重载运算符实现
    str+="lalala";
    std::cout << str << '\n';
    return 0;
}

replace 的使用方法, replace 支持使用无符号整数寻找位置, 也支持用迭代器寻找位置.

#include <iostream>
#include <string>

int main () {
    std::string base="this is a test string.";
    std::string str2="n example";
    std::string str3="sample phrase";
    std::string str4="useful.";

    // replace signatures used in the same order as described above:

    // Using positions:                 0123456789*123456789*12345
    std::string str=base;           // "this is a test string."
    //第 9 个字符以及后面的 4 个字符被 str2 代替
    str.replace(9, 5, str2);          // "this is an example string." (1)
    //第 19 个字符串以及后面的 5 个字符用 str 的第 7 个字符以及后面的 5 个字符代替
    str.replace(19, 6, str3, 7, 6);     // "this is an example phrase." (2)
    //第 8 个字符以及后面的 9 个字符用字符串参数代替
    str.replace(8, 10,"just a");     // "this is just a phrase."     (3)
    //第 8 个字符以及后面的 5 个字符用字符串参数的前 7 个字符替换
    str.replace(8, 6,"a shorty", 7);  // "this is a short phrase."    (4)
    //第 22 以及后面的 0 个字符用 3 个叹号替换
    str.replace(22, 1, 3,'!');        // "this is a short phrase!!!"  (5)
    //迭代器的原理同上
    // Using iterators:                                               0123456789*123456789*
    str.replace(str.begin(), str.end()-3, str3);                    // "sample phrase!!!"      (1)
    str.replace(str.begin(), str.begin()+6,"replace");             // "replace phrase!!!"     (3)
    str.replace(str.begin()+8, str.begin()+14,"is coolness", 7);    // "replace is cool!!!"    (4)
    str.replace(str.begin()+12, str.end()-4, 4,'o');                // "replace is cooool!!!"  (5)
    str.replace(str.begin()+11, str.end(), str4.begin(), str4.end());// "replace is useful."    (6)
    std::cout << str << '\n';
    return 0;
}

以上的 replace 操作可以用 insert 和 erase 的操作组合替换, 但是 replace 操作更加方便.

assign 操作

assign 操作在一起列容器当中都存在, 比如 vector 等等. 是一个很基本的操作函数, string 使用 assign 可以灵活的对其进行赋值.

#include <iostream>
#include <string>

int main () {
    std::string str;
    std::string base="The quick brown fox jumps over a lazy dog.";

    // used in the same order as described above:
    //直接把 base 赋值给 str
    str.assign(base);
    std::cout << str << '\n';
    //把 base 第 10 个字符以及后面的 8 个字符赋给 str
    str.assign(base, 10, 9);
    std::cout << str << '\n';         // "brown fox"
    //把参数中的 0 到 6 个字符串赋给 str
    str.assign("pangrams are cool", 7);
    std::cout << str << '\n';         // "pangram"
    //直接使用参数赋值
    str.assign("c-string");
    std::cout << str << '\n';         // "c-string"
    //给 str 赋值 10 个'*' 字符
    str.assign(10,'*');
    std::cout << str << '\n';         // "**********"
    //赋值是 10 个'-'
    str.assign<int>(10, 0x2D);
    std::cout << str << '\n';         // "----------"
    //指定 base 迭代器范围的字符串
    str.assign(base.begin()+16, base.end()-12);
    std::cout << str << '\n';         // "fox jumps over"

    return 0;
}

string 的搜索操作

string 类中提供了很多性能优秀, 使用方便的成员方法. 而且在泛型算法当中也有很多实用的技巧.

find 和 rfind 函数:

find 函数主要是查找一个字符串是否在调用的字符串中出现过, 大小写敏感.

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    std::string str ("There are two needles in this haystack with needles.");
    std::string str2 ("needle");

    // different member versions of find in the same order as above:
    //在 str 当中查找第一个出现的 needle, 找到则返回出现的位置, 否则返回结尾
    std::size_t found = str.find(str2);
    if (found!=std::string::npos)
    std::cout << "first 'needle' found at: " << found << '\n';
    //在 str 当中, 从第 found+1 的位置开始查找参数字符串的前 6 个字符
    found=str.find("needles are small", found+1, 6);
    if (found!=std::string::npos)
    std::cout << "second 'needle' found at: " << found << '\n';
    //在 str 当中查找参数中的字符串
    found=str.find("haystack");
    if (found!=std::string::npos)
    std::cout << "'haystack' also found at: " << found << '\n';
    //查找一个字符
    found=str.find('.');
    if (found!=std::string::npos)
    std::cout << "Period found at: " << found << '\n';
    //组合使用, 把 str2 用参数表中的字符串代替
    // let's replace the first needle:
    str.replace(str.find(str2), str2.length(),"preposition");
    std::cout << str << '\n';
    return 0;
}

rfind 函数就是找最后一个出现的匹配字符串, 返回的位置仍然是从前往后数的.

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    std::string str ("The sixth sick sheik's sixth sheep's sick.");
    std::string key ("sixth");//                    ^
    //rfind 是找最后一个出现的匹配字符串
    std::size_t found = str.rfind(key);
    if (found!=std::string::npos) {
        cout<<found<<endl;//输出 23
        str.replace (found, key.length(),"seventh");//找到的 sixth 替换成 seventh
    }

    std::cout << str << '\n';
    return 0;
}

查找的效率非常高, 我没看过 stl 源码剖析, 但是感觉是用 kmp 实现的. 呵呵, 可以自己写一个.

find_….of 函数:

  • find_first_of(args) 查找 args 中任何一个字符第一次出现的位置
  • find_last_of(args) 最后一个出现的位置
  • find_fist_not_of(args) 查找第一个不在 args 中的字符
  • find_last_not_of 查找最后一个不在 args 中出现的字符
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    std::string str1 ("Please, replace the vowels in this sentence by asterisks.");
    std::size_t found1 = str1.find_first_of("aeiou");
    //把所有元音找出来用*代替
    while (found1!=std::string::npos) {
        str1[found1]='*';
        found1=str1.find_first_of("aeiou", found1+1);
    }
    std::cout << str1 << '\n';

    //在 str2 中找到第一个不是消协英文字母和空格的字符
    std::string str2 ("look for non-alphabetic characters...");
    std::size_t found2 = str2.find_first_not_of("abcdefghijklmnopqrstuvwxyz ");
    if (found2!=std::string::npos) {
        std::cout << "The first non-alphabetic character is " << str2[found2];
        std::cout << " at position " << found2 << '\n';
    }
    return 0;
}

find_last_of 和 find_last_not_of 与 first 基本相同, 就不写例子代码了.

比较与转换

类似 c 语言的字符串比较函数 strcmp 函数一样, 支持字符串比较操作, 同时也类似 python, C# 语言中的函数一样, 支持把数字和字符串转换. 有些特性是 C++11 当中才有.

注意编译器 bug: 在 MinGW 编译器当中如果版本低于 3.8, 虽然支持 c++11 但是里面有一个 bug, 就是不支持字符串和数组的转换! 要更新 MinGW 的版本才可以, 或者直接使用 g++.

compare 函数

和 strcmp 函数一样, 如果两个字符串相等, 那么返回 0, 调用对象大于参数返回 1, 小于返回-1.
在 compare 当中还支持部分比较, 里面有 6 个参数可以设置.

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    string s1="123", s2="123";
    cout<<s1.compare(s2)<<endl;//0

    s1="123", s2="1234";
    cout<<s1.compare(s2)<<endl;//-1

    s1="1234", s2="123";
    cout<<s1.compare(s2)<<endl;//1

    std::string str1 ("green apple");
    std::string str2 ("red apple");

    if (str1.compare(str2) != 0)
    std::cout << str1 << " is not " << str2 << '\n';
    //str1 的第 6 个字符以及后面的 4 个字符和参数比较
    if (str1.compare(6, 5,"apple") == 0)
    std::cout << "still, " << str1 << " is an apple\n";

    if (str2.compare(str2.size()-5, 5,"apple") == 0)
    std::cout << "and " << str2 << " is also an apple\n";
    //str1 的第 6 个字符以及后面的 4 个字符和 str2 的第 4 个字符以及后面的 4 个字符比较
    if (str1.compare(6, 5, str2, 4, 5) == 0)
    std::cout << "therefore, both are apples\n";
    return 0;
}

由于 string 重载了运算符, 可以直接用>,<,==来进行比较, 也很方便.

数值转换

在 io 的部分有过数值和字符串相互转换的例子, 使用的是 stringstream 函数, 在 c++11 当中有定义好的现成的函数取调用, 非常方便.

函数 作用
to_string(val) 把 val 转换成 string
stoi(s, p, b) 把字符串 s 从 p 开始转换成 b 进制的 int
stol(s, p, b) long
stoul(s, p, b) unsigned long
stoll(s, p, b) long long
stoull(s, p, b) unsigned long long
stof(s, p) float
stod(s, p) double
stold(s, p) long double

注意, 下段代码在 MinGw 中会报错! 即使使用 c++11 编译也一样, 无法识别 to_string!

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    string s1;
    s1=to_string(100);
    cout<<s1<<endl;
    int a=stoi(s1, 0, 10)+1;
    cout<<a<<endl;

    return 0;
}

原文链接:https://blog.csdn.net/tengfei461807914/article/details/52203202

获取字符串元素:[]和 at()

在通常情况下, string 是 C++ 中的字符串. 字符串是一种特殊类型的容器, 专门用来操作字符序列.

字符串中元素的访问是允许的, 一般可使用两种方法访问字符串中的单一字符: 下标操作符[] 和 成员函数 at(). 两者均返回指定的下标位置的字符. 第 1 个字符索引 (下标) 为 0, 最后的字符索引为 length()-1.

需要注意的是, 这两种访问方法是有区别的:
下标操作符 [] 在使用时不检查索引的有效性, 如果下标超出字符的长度范围, 会示导致未定义行为. 对于常量字符串, 使用下标操作符时, 字符串的最后字符 (即 '\0') 是有效的. 对应 string 类型对象 (常量型) 最后一个字符的下标是有效的, 调用返回字符 '\0'.
函数 at() 在使用时会检查下标是否有效. 如果给定的下标超出字符的长度范围, 系统会抛出 out_of_range 异常.

#include <iostream>
#include <string>
int main() {
    const std::string cS ("c.biancheng.net");
    std::string s ("abode");
    char temp =0;
    char temp_1 = 0;
    char temp_2 = 0;
    char temp_3 = 0;
    char temp_4 = 0;
    char temp_5 = 0;
    temp = s [2]; //" 获取字符 'c'
    temp_1 = s.at(2); //获取字符 'c'
    temp_2 = s [s.length()]; //未定义行为, 返回字符'\0', 但 Visual C++ 2012 执行时未报错
    temp_3 = cS[cS.length()]; //指向字符 '\0'
    temp_4 = s.at (s.length ()); //程序异常
    temp_5 = cS.at(cS.length ()); //程序异常
    std::cout << temp <<temp_1 << temp_2 << temp_3 << temp_4 << temp_5 << std::endl;
    return 0;
}

通过对上述代码的分析可知, 要理解字符串的存取需要多实践, 多尝试, 并且要牢记基础知识和基本规则.

为修改 string 字符串的内容, 下标操作符 [] 和函数 at() 均返回字符的"引用". 但当字符串的内存被重新分配以后, 可能会发生执行错误.

#include <iostream>
#include <string>
int main() {
    std::string s ("abode");
    std::cout << s << std::endl ;
    char& r = s[2] ; //建立引用关系
    char*p=&s[3]; //建立引用关系
    r='X' ;//修改内容
    *p='Y' ;//修改内容
    std::cout << s << std::endl; //输出
    s = "12345678"; //重新赋值
    r ='X'; //修改内容
    *p='Y'; //修改内容
    std::cout << s << std::endl; //输出
    return 0;
}
程序输出结果为:
abode
abXYe
12XY5678

在例 2 中, 使用 Visual C++ 2012 编译器编译, 字符串被重新赋值后, 修改其中某位置字符的值, 仍然成功. 这与前面所述的"可能会发生执行错误"其实并不矛盾. 因为, 从意义上讲, 字符串被重新赋值后, 只是其原来的引用关系已经没有意义了.

参考

string 原理

string 是一个包含多个数据成员的字符串对象, 这里只是补充《C++ Primer》这本书关于 string 对象内部机制没详细阐述, 做个笔录, 这里不会罗列所有的 string 对象的 api, 不熟悉的同学可以看《C++ Primer》相关内容.

c_str() 是一个指针指向动态分配空间中的字符数组中第一个字符的地址.

size() 包含字符串的长度.

capacity() 包含当前可能存储在数组中的有效字符数 (额外的 NUL 字符不计算在内).

malloc 内存分配
一个涉及到 malloc 内存管理程序的实现需要 3 个字段, 每个字段都是三个不同指针:

指向已分配内存的指针;
字符串的逻辑大小 (该字符串末尾是 NUL 字符);
分配的内存大小 (必须大于或等于逻辑大小);

在 for 循环前: 我们通过调用 string 三个提到三个基本方法, 起初分配的内存是 24 字节, 但允许容纳有效的字符是 22 个, 为什么呢?

因为 HELLO 后的第 6 个位置 (索引 5) 包含一个 NUL 字符 (即'\0'), 而 malloc 初始化分配的 24 个字节里的最后一个字节位置也包含一个界定符, 我认为也是 NUL 字符. 有效字符的长度是不将 NUL 字符计算在内的, 所有 capacity 方法才显示 22. 初始化的状态如下图所示 (重申: 另外不同的计算机硬件, 不同的 OS 和编译器环境, malloc 初始化时, 申请的内存空间是不一样的):

上面的代码, for 的循环的长度加大, 总之大于 24 的任意一个正整数, 多实验几次. 会得到如下基本特征.

当 size() 方法得出字符数达到 capacity() 得出的有效容纳的字符数, string 对象内部就会触发 malloc 的内存重新扩容.
每次 malloc 扩容后的申请的内存空间尺寸是之前的内存空间尺寸的 2 倍.
基于这两点, C++的 string 对象内部封装了涉及 malloc 操作的指针操作, 这大大减轻了程序猿对指针操作不当, 带来程序不可预测的可能性. 同时使用双倍扩容的方法也最大限度减少了因字符串长度后续增加的频繁 malloc 操作带来的系统消耗. 但是它是以内存空间为代价的, 堆里和字面量池总有着相同一份相同的字符串副本.

调试 C++程序的时候, 有时你需要查看 string 对象内部的指针, 虽然 c_str() 可以输出字符串首个字符的内存地址, 但标准库 cout 操作会自动对 c_str() 的内部指针做解引操作, 因此 cout 不能直接得出字符串的地址, 而是打印对应的字符串. 不需要折腾 cout, 直接简单的粗暴方法是用 C 的 printf 函数

作者: 铁甲万能狗
链接:https://www.jianshu.com/p/6469fad4d5d5
来源: 简书
著作权归作者所有. 商业转载请联系作者获得授权, 非商业转载请注明出处.

为什么在处理"Hello, Word!!"只在栈中进行内存分配?

为什么在处理"Hello, My name is peter!!"这样的字符串, 就会在堆中进行内存分配?

没错, 答案就是字符串字面量的长度决定的.

string 对象内部约定:

  • 只要传入的字符串字面量小于上表的阀值, string 内部实现在栈中分配内存, 有个很骚的名字小型字符串优化 (Small String Optimisation).
  • 只要大于上述 C++编译器指定阀值, string 对象内部会隐式执行 new 操作在堆中根据指定的字符串尺寸分配初次内存.
  • 如果后续任何字符串的 push_back 操作, string 会根据"double 方案"的内存分配方式对堆内存执行扩容操作, 见前文《对[C/C++]指针与字符串的总结》.
  • 还有根据 RAII 的约定, C++编译器会对 string 对象在其调用函数的生命周期结束之时自动执行垃圾回收.(见上图的输出).

作者: 铁甲万能狗
链接:https://www.jianshu.com/p/f3fb85b1a647
来源: 简书
著作权归作者所有. 商业转载请联系作者获得授权, 非商业转载请注明出处.

string 主要有三种实现方式:

  1. 是直接拷贝 (eager copy)--- 类似 vector 内嵌 char* 指针 现在基本不用
  2. 是 copy on write---对多线程性能不好 g++采用这个方式
  3. 则是短字符串优化 SSO---string 对象本身空间存储字符串 现在用的多

主要是第三种 内部有个 char buff [15]数组, 短字符串直接放 string 对象的 buff 数组里面 (栈中, 性能好), 长字符才会回退成第一种 (类似 vector) 放在堆里面, 性能就比较底下

所以 string 的增长方式和 vector 一样, 都是 2 倍或 1.5 倍扩容 具体看编译器

在 Class 实现中, 最重要的就是 Class 的定义, 内存结构, 常用构造器,= 操作符, 析构方法,

char* 构造器其实就是申请了一块内存并进行了字符串的拷贝操作.

如果字符串可共享, 进行引用拷贝, 否则进行深度拷贝.

正常情况下, 字符串都是可共享的. 只有个别情况下不可共享, 比如这个字符串正在被写入时就不可被共享.

在引用拷贝的方法实现 _M_refcopy 中, 对字符串的引用计数+1, 然后直接返回源字符串的字符串地址.

方法返回后, 用源字符串的地址构造新的字符串, 也就是说新的 std::string 内部保存了源字符串同样的地址, 只是引用计数增加了 1.

写时拷贝的特性导致存在一些问题, 包括:

  • 存在多线程风险. 比如某个 std::string 通过 COW 进行拷贝后, 一个堆上的字符串有可能会被多个线程同时访问, 存在有多线程风险.
  • 可能增加内存拷贝情况. 比如 A 和 B 共享同一段内存, 在多线程环境下同时对 A 和 B 进行写操作, 可能会有如下序列:A 写操作, A 拷贝内存, B 写操作, B 拷贝内存, A 对引用计数减一, B 对引用计数减一, 加上初始的一次构造总共三次内存申请, 如果使用全拷贝的 string, 只会发生两次内存申请.

内存结构

libc++ string 的内存结构更巧妙一些, 针对使用过程中更多的字符串都是短字符串, 且字符串经常是入栈申请, 出栈销毁的情况.libc++ string 将字符串的内存结构分为了长字符串模式和短字符串模式.

长字符串模式下, 在栈上保存字符串容量, 大小和堆上申请的字符串地址.
短字符串模式下, 直接将其数据存在栈中, 而不去堆中动态申请空间, 避免了申请堆空间所需的开销 .

既然一个空间既可以表示长字符串又可以表示短字符串, 那么如何判断这个字符串到底是长字符串还是短字符串呢?

libc++ string 是通过一个 bit 标志位来判断的.

长字符串 __cap_ 最后一个字节的末位 bit 固定为 1
短字符串 __size_ 的末位 bit 固定为 0
由于引入了这个标志位:

长字符串的容量就必须为偶数 (末位只作为标志位,真实容量 = _cap - 1)
短字符串的长度保存时需要左移一位, 而取出是需要右移一位, 用于保存末位的 0

左值拷贝构造

在介绍拷贝构造之前, 先回顾一下之前学习的 C++ 知识: 左值, 右值, 转移语义.

左值: 非临时变量. 如 std::string a, a 为左值;
右值: 临时的对象, 只在当前语句有效. 如 std::string() 为右值;
转移语义可以将资源 (堆, 系统对象等) 从一个对象转移到另一个对象, 这样能够减少不必要的临时对象的创建, 拷贝以及销毁, 能够大幅度提高 C++ 应用程序的性能;
拷贝语义&转移语义约等于拷贝&剪切.
C++ 中 & 用于表示左值引用,&& 用于表示右值引用.

如果拷贝构造时, 源字符串是一个左值, 将调用左值拷贝构造函数.

右值拷贝构造

libc++ string 实现时就很好的使用了转移语义. 如果源字符串为右值, 可以直接将源字符串的数据转移到新的字符串, 而不用重新申请空间. 其实就是将源 string 堆上申请的空间直接交给新的 string 管理, 源 string 不再管理原来的内存.

string 的坑

C++开发的项目难免会用到 STL 的 string, 使用管理都比 char 数组 (指针) 方便的多, 但在得心应手的使用过程中也要警惕几个小陷阱, 避免我们项目出 bug 却迟迟找不到原因.

结构体中的 string 赋值问题

#include <iostream>  
#include <string>  
#include <stdlib.h>  

using namespace std;  

struct flowRecord            
{  
    string app_name;                                                              
    struct flowRecord *next;  

};  

int main() {  
    flowRecord *fr = (flowRecord*)malloc(sizeof(flowRecord));  
    fr->app_name = "hello";  
    out << fr->app_name << endl;  
    return 0;  
    }

嗯, 当然不是简单的输出 "hello"了, 在 Linux 下用 g++编译后运行试试, 会出现"Segmentation fault (core dumped)", why? 问题就出在给 fr 指针分配内存的时候, 注意这里用的是 C 中的 malloc 而不是 new, 如果你换成 new 再运行, 就不会报错 了, 成功的输出"hello", 那为什么 malloc 就不行呢? 这就要看 malloc() 与 new() 的区别了, 关于两者的区别是程序员面试中屡问不爽的经典面试题, 所以相信一般的程序员都知道它们之间有一个非常重要的区别就是: new 在分配内存时会调用默认的构造函数, 而 malloc 不会调用. 而 STL 的 string 在赋值之前需要调用默认的构造函数以初始化 string 后才能使用, 如赋值, 打印等操作, 如果使用 malloc 分配内存, 就不会调 用 string 默认的构造函数来初始化结构体中的 app_name 字符串, 因此这里给其直接赋值是错误的, 应该使用 new 操作符. 这也 提示我们用 C++开发程序时, 就尽量使用 C++中的函数, 不要 C++与 C 混合编程, 导致使用混淆, 比如有时候 new 分配的内存却用 free 释放.

c_str() 函数问题

c_str() 函数用于 string 与 const char* 之间的转换, 也经常能用到, 下面的例子你说输出啥?

#include <iostream>  
#include <string>  

using namespace std;  

int main() {  
    string s = "Alexia";  
    const char *str = s.c_str();  
    cout << str << endl;  
    s[1] = 'm';  
    cout << str << endl;  

    return 0;  
}  

嗯, 第一个不用多说, 第二个输出是"Alexia"还是"Amexia"呢? 答案是后者, 咋一看 const char的值应该是个常量啊, 怎么还能改变值呢? 哈, 又是个经典的面试题: const char, char const, char const 的区别是什么? 老生常谈的问题, const char与 char const是等价的, 指的是指向字符常量的指针, 即指针可以改变指向但其指向的内容不可以改变, 而 char const 相反, 指的是常量指针, 即指向不可以改变但指针指向的内容可以改变. 因此这里的 const char指向的内容本类是不可以改变的, 那么这里为什么改变了呢? 这跟 str 这个 const char的生命周期及 string 类的实现有关, string 的 c_str() 返回的指针是由 string 管理的, 因此它的生命期是 string 对象的生命期, 而 string 类的实现实际上封装着一个 char的指针, 而 c_str() 直接返回该指针的引用, 因此 string 对象的改变会直接影响已经执行过的 c_str() 返回的指针引用.

字符串字面值与标准库 string 不是同一种类型

直接看下面的例子:

string s("hello");  
cout<<s.size()<<endl;        //OK  
cout<<"hello".size()<<endl;  //ERROR  
cout<<s+"world"<<endl;       //OK  
cout<<"hello"+"world"<<endl; //ERROR 

可以看出两者是非常不同的, 不能混淆使用.

C++ string 中的几个小陷阱, 你掉进过吗?

std:;string 的析构

发布于 2019-04-01 14:51:06 阅读 1.3K0
越学 C++越觉得自己菜了
之前写服务端程序有一个往消息队列里面推 json 的过程, 然后发现推进去 C# 端取到的无论如何都是个空指针
简单复现一下现场

string str1 = string("hello1");
string str2 = string("hello2");
const char* ptr1 = str1.substr(1).data();    // 取字符串从下标 1 到结尾的部分
const char* ptr2 = str2.substr(1).data();
cout << ptr1 << ptr2 << endl;

这样看起来输出是 ello1ello2 的输出, 在我笔记本上面挺正常的, 实际上我集成到服务器上面的时侯炸了, 一直取到一组奇怪的字符串
跟踪调试了一早上 (虽然写了 3 年 C++工程, 但是还是菜吧, 折腾了好久).
原来在 str1.substr(1) 创建了一个临时对象存储 str1 的子串.data() 函数获取了这个子串的内存指针.

执行到到了下一行的时候, 存储 str1 子串的字符串对象被析构, msvc 发现这是个临时对象, 代码块还没结束的十好几就把内存析构掉, 临时对象的内存被释放, 同时 str2.substr(1), 这个时候新的临时变量被注册到刚才 str1.substr(1) 被析构掉的内存地址上面, 此时再去调用 data(), 拿到了 ptr1 同一个地址的指针, 此时内存的数据变更为 s2 的子串, 然后压到消息队里面的数据穿就乱了, 当我把程序增加一个临时 string 去接收上面产生的子串的时候, 问题就会解决了

string str1 = string("string1");
string str2 = string("string2");
string t_str1 = str1.substr(1);
string t_str2 = str2.substr(1);
const char* ptr1 = t_str1.data();
const char* ptr2 = t_str2.data();
cout << ptr1 << ptr2 << endl;

此时输出正常
踩坑日常, 加班使我快乐

     (adsbygoogle = window.adsbygoogle || []).push({});  

C++踩坑记录 (一)std:;string 的析构

返回 char*型时

错误示范

因为 string 这个类在函数结束时就调用析构函数了, 导致返回的 result 指向一个已经释放的地址

char* TestFun(){
    string test_string = " 广东佛山机会开 io";
    const char* result = test_string.c_str();
    return result;
}

填坑日记: 关于 std::string::npos 的坑

正确示范

char* TestFun(){
    string test_string = " 广东佛山机会开 io";
    const char* result = test_string.c_str();
    int tail = 0;
    while (result[tail] != '\0') {
        tail++;
    }
    char* real_result = (char*)malloc(tail+1);
    for (int i = 0; i <= tail; i++) {
        real_result[i] = result[i];
    }
        return real_result;
}

[https://blog.csdn.net/yxpandjay/article/details/90289095](c++ string.c_str() 转 const char*的一个坑点)

length() 的类型是 size_t

其实原因很简单.size_t 是 unsigned int 的 typedef, 这个坑实际上是无符号数赋负值的老问题. 对无符号数赋值-1, 内存中表示为全 1, 即相当于赋 unsigned int 最大值. 以下一段代码可以说明这个问题:

其他

  1. size_type find_first_of( const basic_string &str, size_type index = 0 );
    查找在字符串中第一个与 str 中的某个字符匹配的字符, 返回它的位置. 搜索从 index 开始, 如果没找到就返回 string::npos
  2. string& replace (size_t pos, size_t len, const string& str);
    从当前字符串的 pos 位置开始, 长度为 len 的段落, 替换成成 str
  3. int compare (const string& str)
    结果为 0, 表示字符串相等, 等价于字符串间的=
  4. data()c_str()的区别
    • data()是指返回字符数组, 尾部可能有'\0', 也可能没有.
    • c_str()是指返回 C 兼容的字符串, 尾部肯定有'\0'
  5. at() 与[]的区别
    • [] 没有检查越界, 不会抛出异常, 效率高
    • at() 检查越界, 抛出异常, 安全度高
      6.size_t copy (char* s, size_t len, size_t pos = 0) const;
      将 strign 的内容拷贝到外部的一个字符数组中, 而不是将外部的字符串拷贝到 string
      7.size()length()
      size()返回 string 的长度, 是字节数, 而不是字符个数.string 无视内部字符采用的是何种编码方式, 它都当成字节集合来处理.size()length()功能和意义一样, 前者是为了配合 STL 标准加的接口.

C++ string 的那些坑

使用场合:

string 是 C++标准库的一个重要的部分, 主要用于字符串处理. 可以使用输入输出流方式直接进行操作, 也可以通过文件等手段进行操作. 同时 C++的算法库对 string 也有着很好的支持, 而且 string 还和 c 语言的字符串之间有着良好的接口. 虽然也有一些弊端, 但是瑕不掩瑜.
其中使用的代码多数都是来自 cpp 官网, 因为例子非常全.

声明和初始化方法:

想使用 string 首先要在头文件当中加入< string >
声明方式也很简单

声明:

string s;//声明一个 string 对象
string ss[10];//声明一个 string 对象的数组

初始化:
使用等号的初始化叫做拷贝初始化, 不使用等号的初始化叫做直接初始化.

#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    string s;//默认初始化, 一个空字符串
    string s1("ssss");//s1 是字面值"ssss" 的副本
    string s2(s1);//s2 是 s1 的副本
    string s3=s2;//s3 是 s2 的副本
    string s4(10,'c');//把 s4 初始化
    string s5="hiya";//拷贝初始化
    string s6=string(10,'c');//拷贝初始化, 生成一个初始化好的对象, 拷贝给 s6

    //string s(cp, n)
    char cs[]="12345";
    string s7(cs, 3);//复制字符串 cs 的前 3 个字符到 s 当中

    //string s(s2, pos2)
    string s8="asac";
    string s9(s8, 2);//从 s2 的第二个字符开始拷贝, 不能超过 s2 的 size

    //string s(s2, pos2, len2)
    string s10="qweqweqweq";
    string s11(s10, 3, 4);//s4 是 s3 从下标 3 开始 4 个字符的拷贝, 超过 s3.size 出现未定义
    return 0;
}

字符串处理:

substr 操作:

注意 substr 没有迭代器作为参数的操作

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    string s="abcdefg";

    //s.substr(pos1, n) 返回字符串位置为 pos1 后面的 n 个字符组成的串
    string s2=s.substr(1, 5);//bcdef

    //s.substr(pos)//得到一个 pos 到结尾的串
    string s3=s.substr(4);//efg

    return 0;
}

如果输入的位置超过字符的长度, 会抛出一个 out_of_range 的异常

insert 操作:

代码来自 cpp 官网, 经过自己的整理
注意用迭代器当参数和无符号数当参数的区别

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    string str="to be question";
    string str2="the ";
    string str3="or not to be";
    string::iterator it;

    //s.insert(pos, str)//在 s 的 pos 位置插入 str
    str.insert(6, str2);                 // to be the question

    //s.insert(pos, str, a, n) 在 s 的 pos 位置插入 str 中插入位置 a 到后面的 n 个字符
    str.insert(6, str3, 3, 4);             // to be not the question

    //s.insert(pos, cstr, n)//在 pos 位置插入 cstr 字符串从开始到后面的 n 个字符
    str.insert(10,"that is cool", 8);    // to be not that is the question

    //s.insert(pos, cstr) 在 s 的 pos 位置插入 cstr
    str.insert(10,"to be ");            // to be not to be that is the question

    //s.insert(pos, n, ch) 在 s.pos 位置上面插入 n 个 ch
    str.insert(15, 1,':');               // to be not to be: that is the question

    //s.insert(s.it, ch) 在 s 的 it 指向位置前面插入一个字符 ch, 返回新插入的位置的迭代器
    it = str.insert(str.begin()+5,','); // to be, not to be: that is the question

    //s.insert(s.it, n, ch)//在 s 的 it 所指向位置的前面插入 n 个 ch
    str.insert (str.end(), 3,'.');       // to be, not to be: that is the question...

    //s.insert(it, str.ita, str.itb) 在 it 所指向的位置的前面插入[ita, itb) 的字符串
    str.insert (it+2, str3.begin(), str3.begin()+3); // to be, or not to be: that is the question...

    return 0;
}

erase 操作:

用来执行删除操作
删除操作有三种

指定 pos 和 len, 其中 pos 为为起始位置, pos 以及后面 len-1 个字符串都删除
迭代器, 删除迭代器指向的字符
迭代器范围, 删除这一范围的字符串, 范围左闭右开
代码来自 cpp 官网

#include <iostream>
#include <string>

int main ()
{
  std::string str ("This is an example sentence.");
  std::cout << str << '\n';
                          // "This is an example sentence."
  str.erase (10, 8);       //            ^^^^^^^^
  //直接指定删除的字符串位置第十个后面的 8 个字符
  std::cout << str << '\n';
                            // "This is an sentence."
  str.erase (str.begin()+9);//           ^
  //删除迭代器指向的字符
  std::cout << str << '\n';
                            // "This is a sentence."
                            //       ^^^^^
  str.erase (str.begin()+5, str.end()-9);
  //删除迭代器范围的字符
  std::cout << str << '\n';
                            // "This sentence."
  return 0;
}

append 和 replace 操作:

append 函数可以用来在字符串的末尾追加字符和字符串. 由于 string 重载了运算符, 也可以用+=操作实现
repalce 顾名思义, 就是替换的意思, 先删除, 后增加.
代码来自 cpp 官网, 附上自己的解释

#include <iostream>
#include <string>

int main ()
{
    std::string str;
    std::string str2="Writing ";
    std::string str3="print 10 and then 5 more";

    //直接追加一个 str2 的字符串
    str.append(str2);                       // "Writing "
    //后面追加 str3 第 6 个字符开始的 3 个字符串
    str.append(str3, 6, 3);                   // "10 "
    //追加字符串形参的前 5 个字符
    str.append("dots are cool", 5);          // "dots "
    //直接添加
    str.append("here: ");                   // "here: "
    //添加 10 个'.'
    str.append(10u,'.');                    // ".........."
    //添加 str3 迭代器范围的字符串
    str.append(str3.begin()+8, str3.end());  // " and then 5 more"
    //最后这个比较特殊, 意思是添加 5 个'A', 实际上参数里面的 65 对应的 asc 码就是 65
    str.append<int>(5, 65);                // "....."
    //字符串追加也可以用重载运算符实现
    str+="lalala";
    std::cout << str << '\n';
    return 0;
}

replace 的使用方法, replace 支持使用无符号整数寻找位置, 也支持用迭代器寻找位置

#include <iostream>
#include <string>

int main ()
{
    std::string base="this is a test string.";
    std::string str2="n example";
    std::string str3="sample phrase";
    std::string str4="useful.";

    // replace signatures used in the same order as described above:

    // Using positions:                 0123456789*123456789*12345
    std::string str=base;           // "this is a test string."
    //第 9 个字符以及后面的 4 个字符被 str2 代替
    str.replace(9, 5, str2);          // "this is an example string." (1)
    //第 19 个字符串以及后面的 5 个字符用 str 的第 7 个字符以及后面的 5 个字符代替
    str.replace(19, 6, str3, 7, 6);     // "this is an example phrase." (2)
    //第 8 个字符以及后面的 9 个字符用字符串参数代替
    str.replace(8, 10,"just a");     // "this is just a phrase."     (3)
    //第 8 个字符以及后面的 5 个字符用字符串参数的前 7 个字符替换
    str.replace(8, 6,"a shorty", 7);  // "this is a short phrase."    (4)
    //第 22 以及后面的 0 个字符用 3 个叹号替换
    str.replace(22, 1, 3,'!');        // "this is a short phrase!!!"  (5)
    //迭代器的原理同上
    // Using iterators:                                               0123456789*123456789*
    str.replace(str.begin(), str.end()-3, str3);                    // "sample phrase!!!"      (1)
    str.replace(str.begin(), str.begin()+6,"replace");             // "replace phrase!!!"     (3)
    str.replace(str.begin()+8, str.begin()+14,"is coolness", 7);    // "replace is cool!!!"    (4)
    str.replace(str.begin()+12, str.end()-4, 4,'o');                // "replace is cooool!!!"  (5)
    str.replace(str.begin()+11, str.end(), str4.begin(), str4.end());// "replace is useful."    (6)
    std::cout << str << '\n';   
    return 0;
}

以上的 replace 操作可以用 insert 和 erase 的操作组合替换, 但是 replace 操作更加方便.

assign 操作:

assign 操作在一起列容器当中都存在, 比如 vector 等等. 是一个很基本的操作函数, string 使用 assign 可以灵活的对其进行赋值.
代码来自 cpp 官网

#include <iostream>
#include <string>

int main ()
{
    std::string str;
    std::string base="The quick brown fox jumps over a lazy dog.";

    // used in the same order as described above:
    //直接把 base 赋值给 str
    str.assign(base);
    std::cout << str << '\n';
    //把 base 第 10 个字符以及后面的 8 个字符赋给 str
    str.assign(base, 10, 9);
    std::cout << str << '\n';         // "brown fox"
    //把参数中的 0 到 6 个字符串赋给 str
    str.assign("pangrams are cool", 7);
    std::cout << str << '\n';         // "pangram"
    //直接使用参数赋值
    str.assign("c-string");
    std::cout << str << '\n';         // "c-string"
    //给 str 赋值 10 个'*' 字符
    str.assign(10,'*');
    std::cout << str << '\n';         // "**********"
    //赋值是 10 个'-'
    str.assign<int>(10, 0x2D);
    std::cout << str << '\n';         // "----------"
    //指定 base 迭代器范围的字符串
    str.assign(base.begin()+16, base.end()-12);
    std::cout << str << '\n';         // "fox jumps over"

    return 0;
}

string 的搜索操作:

string 类中提供了很多性能优秀, 使用方便的成员方法. 而且在泛型算法当中也有很多实用的技巧.

find 和 rfind 函数:

find 函数主要是查找一个字符串是否在调用的字符串中出现过, 大小写敏感.
代码来自 cpp 官网

#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    std::string str ("There are two needles in this haystack with needles.");
    std::string str2 ("needle");

    // different member versions of find in the same order as above:
    //在 str 当中查找第一个出现的 needle, 找到则返回出现的位置, 否则返回结尾
    std::size_t found = str.find(str2);
    if (found!=std::string::npos)
    std::cout << "first 'needle' found at: " << found << '\n';
    //在 str 当中, 从第 found+1 的位置开始查找参数字符串的前 6 个字符
    found=str.find("needles are small", found+1, 6);
    if (found!=std::string::npos)
    std::cout << "second 'needle' found at: " << found << '\n';
    //在 str 当中查找参数中的字符串
    found=str.find("haystack");
    if (found!=std::string::npos)
    std::cout << "'haystack' also found at: " << found << '\n';
    //查找一个字符
    found=str.find('.');
    if (found!=std::string::npos)
    std::cout << "Period found at: " << found << '\n';
    //组合使用, 把 str2 用参数表中的字符串代替
    // let's replace the first needle:
    str.replace(str.find(str2), str2.length(),"preposition");
    std::cout << str << '\n';
    return 0;
}

rfind 函数就是找最后一个出现的匹配字符串, 返回的位置仍然是从前往后数的.

#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    std::string str ("The sixth sick sheik's sixth sheep's sick.");
    std::string key ("sixth");//                    ^
    //rfind 是找最后一个出现的匹配字符串
    std::size_t found = str.rfind(key);
    if (found!=std::string::npos)
    {
        cout<<found<<endl;//输出 23
        str.replace (found, key.length(),"seventh");//找到的 sixth 替换成 seventh
    }

    std::cout << str << '\n';
    return 0;
}

查找的效率非常高, 我没看过 stl 源码剖析, 但是感觉是用 kmp 实现的. 呵呵, 可以自己写一个.

find_….of 函数:
find_first_of(args) 查找 args 中任何一个字符第一次出现的位置
find_last_of(args) 最后一个出现的位置
find_fist_not_of(args) 查找第一个不在 args 中的字符
find_last_not_of 查找最后一个不在 args 中出现的字符
#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    std::string str1 ("Please, replace the vowels in this sentence by asterisks.");
    std::size_t found1 = str1.find_first_of("aeiou");
    //把所有元音找出来用*代替
    while (found1!=std::string::npos)
    {
        str1[found1]='*';
        found1=str1.find_first_of("aeiou", found1+1);
    }
    std::cout << str1 << '\n';

    //在 str2 中找到第一个不是消协英文字母和空格的字符
    std::string str2 ("look for non-alphabetic characters...");
    std::size_t found2 = str2.find_first_not_of("abcdefghijklmnopqrstuvwxyz ");
    if (found2!=std::string::npos)
    {
        std::cout << "The first non-alphabetic character is " << str2[found2];
        std::cout << " at position " << found2 << '\n';
    }
    return 0;
}

find_last_of 和 find_last_not_of 与 first 基本相同, 就不写例子代码了.

C++ string 中的 find() 函数
1.string 中 find() 返回值是字母在母串中的位置 (下标记录), 如果没有找到, 那么会返回一个特别的标记 npos.(返回值可以看成是一个 int 型的数)

复制代码
1 #include
2 #include
3 #include
4 using namespace std;
5 int main()
6 {
7 ////find 函数返回类型 size_type
8 string s("1a2b3c4d5e6f7jkg8h9i1a2b3c4d5e6f7g8ha9i");
9 string flag;
10 string::size_type position;
11 //find 函数 返回 jk 在 s 中的下标位置
12 position = s.find("jk");
13 if (position != s.npos) //如果没找到, 返回一个特别的标志 c++中用 npos 表示, 我这里 npos 取值是 4294967295,
14 {
15 printf("position is : %d\n" , position);
16 }
17 else
18 {
19 printf("Not found the flag\n");
20 }
21 }
复制代码

  1. 返回子串出现在母串中的首次出现的位置, 和最后一次出现的位置.

1 flag = "c";
2 position = s.find_first_of(flag);
3 printf("s.find_first_of(flag) is :%d\n", position);
4 position = s.find_last_of(flag);
5 printf("s.find_last_of(flag) is :%d\n", position);

  1. 查找某一给定位置后的子串的位置

1 //从字符串 s 下标 5 开始, 查找字符串 b , 返回 b 在 s 中的下标
2 position=s.find("b", 5);
3 cout<<"s.find(b, 5) is : "<<position<<endl;

  1. 查找所有子串在母串中出现的位置

复制代码
//查找 s 中 flag 出现的所有位置.
flag="a";
position=0;
int i=1;
while((position=s.find(flag, position))!=string::npos)
{
cout<<"position "<<i<<" : "<<position<<endl;
position++;
i++;
}
复制代码

  1. 反向查找子串在母串中出现的位置, 通常我们可以这样来使用, 当正向查找与反向查找得到的位置不相同说明子串不唯一.

1 //反向查找, flag 在 s 中最后出现的位置
2 flag="3";
3 position=s.rfind (flag);
4 printf("s.rfind (flag) :%d\n", position);
例题:

  1. 给出一个字符串, 串中会出现有人名, 找到一个只有一个人名的字符串.

复制代码
1 #include <bits/stdc++.h>
2 using namespace std;
3 vector s;
4 int main()
5 {
6 s.push_back("Danil");
7 s.push_back("Olya");
8 s.push_back("Slava");
9 s.push_back("Ann");
10 s.push_back("Nikita");///建立动态数组
11 string a;
12 cin>>a;
13 int res = 0;
14 for(int i = 0; i < 5; i++)
15 {
16 if(a.find(s[i]) != a.npos)
17 {
18 res++;
19 if(a.rfind(s[i]) != a.find(s[i]))///一个字符中出现多个一样的名字
20 {
21 res++;
22 }
23 }
24 }
25 if(res == 1)
26 {
27 cout<<"YES"<<endl;
28 }
29 else
30 {
31 cout<<"NO"<<endl;
32 }
33 return 0;
34 }
复制代码

  1. 你有 n 个字符串. 每个字符串由小写英文字母组成. 重新排序给定的字符串, 使得对于每个字符串, 在它之前的所有字符串都是它的子串.

https://www.cnblogs.com/wkfvawl/p/9229758.html

复制代码
1 #include
2 #include
3 #include
4 #include
5 using namespace std;
6 bool cmp(string a, string b)
7 {
8 if (a.length() == b.length())
9 return a < b;
10 return a.length() < b.length();
11 }
12 int main()
13 {
14 int n;
15 string s[111];
16 scanf("%d", &n);
17 for (int i = 0; i < n; i++)
18 {
19 cin >> s[i];
20 }
21 sort(s, s + n, cmp);
22 int flag = 1;
23 for (int i = 1; i < n; i++)
24 {
25 if (s[i].find(s[i-1]) == string::npos)
26 {
27 flag = 0;
28 break;
29 }
30 }
31 if (flag)
32 {
33 cout << "YES" << endl;
34 for (int i = 0; i < n; i++)
35 {
36 cout << s[i] << endl;
37 }
38 }
39 else
40 {
41 cout << "NO" << endl;
42 }
43 return 0;
44 }
复制代码

  1. 查询区间内子串在母串中的个数.

https://www.cnblogs.com/wkfvawl/p/9452869.html

复制代码
1 #include
2 #include
3 #include
4 #include
5 #include
6 using namespace std;
7 int main()
8 {
9 int n, m, q, i, j,l,r,len;
10 int counts;
11 int vis[10010];
12 string s1, s2;
13 cin>>n>>m>>q;
14 cin>>s1>>s2;
15 len=s2.size();
16 memset(vis, 0, sizeof(vis));
17 string::size_type pos=0;
18 while((pos=s1.find(s2, pos))!=string::npos)
19 {
20 vis[pos+1]=pos+1;
21 pos++;
22 }
23 for(i=1;i<=q;i++)
24 {
25 counts=0;
26 scanf("%d%d",&l,&r);
27 for(j=l;j<=r;j++)
28 {
29 if(vis[j]!=0&&vis[j]+len-1<=r)
30 {
31 counts++;
32 }
33 }
34 printf("%d\n", counts);
35 }
36 return 0;
37 }
复制代码

比较与转换:

类似 c 语言的字符串比较函数 strcmp 函数一样, 支持字符串比较操作, 同时也类似 python, C# 语言中的函数一样, 支持把数字和字符串转换. 有些特性是 C++11 当中才有.
注意编译器 bug:
在 MinGW 编译器当中如果版本低于 3.8, 虽然支持 c++11 但是里面有一个 bug, 就是不支持字符串和数组的转换! 要更新 MinGW 的版本才可以, 或者直接使用 g++.

compare 函数:

和 strcmp 函数一样, 如果两个字符串相等, 那么返回 0, 调用对象大于参数返回 1, 小于返回-1.
在 compare 当中还支持部分比较, 里面有 6 个参数可以设置.

#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    string s1="123", s2="123";
    cout<<s1.compare(s2)<<endl;//0

    s1="123", s2="1234";
    cout<<s1.compare(s2)<<endl;//-1

    s1="1234", s2="123";
    cout<<s1.compare(s2)<<endl;//1

    std::string str1 ("green apple");
    std::string str2 ("red apple");

    if (str1.compare(str2) != 0)
    std::cout << str1 << " is not " << str2 << '\n';
    //str1 的第 6 个字符以及后面的 4 个字符和参数比较
    if (str1.compare(6, 5,"apple") == 0)
    std::cout << "still, " << str1 << " is an apple\n";

    if (str2.compare(str2.size()-5, 5,"apple") == 0)
    std::cout << "and " << str2 << " is also an apple\n";
    //str1 的第 6 个字符以及后面的 4 个字符和 str2 的第 4 个字符以及后面的 4 个字符比较
    if (str1.compare(6, 5, str2, 4, 5) == 0)
    std::cout << "therefore, both are apples\n";
    return 0;
}

由于 string 重载了运算符, 可以直接用>,<,==来进行比较, 也很方便.

数值转换:

在 io 的部分有过数值和字符串相互转换的例子, 使用的是 stringstream 函数, 在 c++11 当中有定义好的现成的函数取调用, 非常方便.

string 和数值转换
to_string(val) 把 val 转换成 string
stoi(s, p, b) 把字符串 s 从 p 开始转换成 b 进制的 int
stol(s, p, b) long
stoul(s, p, b) unsigned long
stoll(s, p, b) long long
stoull(s, p, b) unsigned long long
stof(s, p) float
stod(s, p) double
stold(s, p) long double

注意, 下段代码在 MinGw 中会报错! 即使使用 c++11 编译也一样, 无法识别 to_string!

#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    string s1;
    s1=to_string(100);
    cout<<s1<<endl;
    int a=stoi(s1, 0, 10)+1;
    cout<<a<<endl;

    return 0;
}

C++ string 的用法和例子

rainbow

没什么大用的码农; 贴图怪; bug制造者; 只会电脑开关机的开发;

文章评论