C++【STL】【string类的使用】

发布于:2023-01-24 ⋅ 阅读:(591) ⋅ 点赞:(0)

目录

string类的常用接口说明 

1. string类对象的常见构造

2. string类对象的容量操作 

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

【LeetCode】反转字符串 

string的迭代器

范围for 

反向迭代器 

常量正向/反向迭代器

4. string类对象的修改操作 

find vs rfind

【牛客】字符串最后一个单词的长度 

getline 

5. string类非成员函数  

6. string结构的说明

g++下string的结构 

reserve/resize可以提前扩容好大小

7.插入擦除函数

insert

erase

【LeetCode】字符串相加

8.字符串转换相关的函数 

to_string vs stoi

部分练习 

 【Leetcode】字符串中的第一次出现的唯一一次的字符​​​​​​​

【LeetCode】验证回文串


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

在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。 

1. 字符串是表示字符序列的类
2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)。
4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。
总结:
1. string是表示字符串的字符串类
2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作 

3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator> string;
4. 不能操作多字节或者变长字符的序列。
在使用string类时,必须包含#include头文件以及using namespace std;

string - C++ Reference

打印asc码中的字符 

void test12()
{
    for(unsigned char ch=0;ch<128;++ch)
    {
        cout<<ch<<" ";
    }
}
int main()
{
    test12();
    return 0;
}

asc码表中的部分字符时不可以显式打印出来的 

string类的常用接口说明 

string内部可能就是下面的结构

_str //存储具体的字符串
_size //存储具体字符串的长度
_capacity //存储字符串的最大长度

1. string类对象的常见构造

(constructor)函数名称 功能说明
string 构造空的string类对象,即空字符集
string(const char*s) 用C-string来构造string类对象
string(size_t n,char c) string类对象中包含n个字符c
string(const string&c) 拷贝构造函数
#include<iostream>
#include <string>
using namespace std;
void test()
{
    //构造空参的字符串
    string s1;
    //构造传参的字符串
    string s2("hello world");
    //由于隐式类型转换,将hello world转换成了一个字符串对象然后拷贝给s10。(编译器优化后可能会变成直接构造)
    string s10="hello world";
    s1=s2;
    s1="xxxx";//赋值
    s1='y';
    //输入字符串
    cin>>s1;
    //打印字符串
    cout<<s1<<"  "<<s2<<endl;

    //拷贝构造
    string s3(s2);
    cout<<s3<<endl;

    //拷贝构造一部分
    //如果省略掉最后一个参数,就默认从pos位置拷贝到最后
    string s4(s2,6);
    cout<<s4<<endl;
    //从第六个位置开始拷贝两个。
    //如果拷贝的长度超出了字符串的最大长度,就拷贝到结尾就停止了
    string s5(s2,6,2);
    cout<<s5<<endl;

    //这里utf8的字符集对于中文是三个字节一个中文字符,所以一共会将前7个字符打印出来
    string s6("喝了这碗鸡汤,我肯定得死,你们也白想活着!",21);
    cout<<s6<<endl;

    //将x重复打印10次,但是好像不支持中文字符
    string s7 (10, 'x');
    cout<<s7<<endl;
}

int main()
{
    test();
    return 0;
}

 

2. string类对象的容量操作 

函数名称 功能说明
size 返回字符串的有效字符长度
length 返回字符串有效字符长度
capacity 返回空间的总大小
empty 检测字符串释是否为空串,是返回TRUE,否则返回FALSE
clear 清空有效字符
reverve 为字符串预留空间
resize 将有效字符的个数改成n个,多出的空间用字符c填充

注意:
1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
2. clear()只是将string中有效字符清空,不改变底层空间大小。

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

#include<iostream>
#include <list>

using namespace std;
void Teststring1()
{
    // 注意:string类对象支持直接用cin和cout进行输入和输出
    string s("hello, world!!!");
    //size是我们真实的有效的字符长度,上面s的长度就是15
    cout << s.size() << endl;
    //length主要是为了兼容以前的C风格代码准备的
    cout << s.length() << endl;
    //这里的capacity指的值系统给我们字符串预开辟的空间大小
    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);
    //有效字符为5个的话,原先多出来的字符串就会被截断了
    cout << s.size() << endl;
    //预先开辟的容量大小是不会变得
    cout << s.capacity() << endl;
    cout << s << endl;
}

int main(int argc, char *argv[])
{
    Teststring1();
    return 1;
}

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;
}
int main()
{
    Teststring2();
    return 1;
}

从下面的测试代码中可以看出如果我们后一次开辟的空间大小比前一次小的话,其底层还是保持原先大的空间。 

void Teststring2()
{
    string s;
    // 测试reserve是否会改变string中有效元素个数
    s.reserve(50);
    cout << s.size() << endl;
    cout << s.capacity() << endl;

    // 测试reserve参数小于string的底层空间大小时,是否会将空间缩小
    s.reserve(100);
    cout << s.size() << endl;
    cout << s.capacity() << endl;
}
int main()
{
    Teststring2();
    return 1;
}

从下面的测试结果中可以看出,如果我们原先开辟的空间较小,然后我们又重新开辟了一块较大的空间的时候,我们的底层开辟的空间就会变大。 

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

函数名称

功能说明

operator[] (重点)

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

beginend

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

rbegin rend

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

范围for

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

其operator使我们的string能够像char[]一样随机提取元素,其底层的原理可能如下。

char& operator[](size_t pos)
{
    assert(pos<size);
    return _str[pos];
}

 所以s1[0]='x'就等价于s1.operator[](0);

引用做返回

1、减少拷贝

2、修改返回对象

void test3()
{
    string s1("helloworld");
    for(size_t i=0;i<s1.size();i++)
    {
        s1[i]++;
    }
    cout<<s1<<endl;

    const string s2("helloworld");
    for(size_t i=0;i<s2.length();i++)
    {
        cout<<s2[i]<<" ";
    }
    cout<<" "<<endl;
    //范围越界会报错
//    cout<<s2[100]<<endl;
}
int main()
{
    test3();
    return 0;
}

 

【LeetCode】反转字符串 

力扣

给你一个字符串 s ,根据下述规则反转字符串:

  • 所有非英文字母保留在原有位置。
  • 所有英文字母(小写或大写)位置反转。

对于这道题在我们的c++中就可以采取我们下面的写法

class Solution {
public:
    string reverseOnlyLetters(string s) {
        size_t begin=0,end=s.size()-1;
        while(begin < end)
        {
            while(begin<end&&!isalpha(s[begin]))
            {
                ++begin;
            }
            while(begin<end&&!isalpha(s[end]))
            {
                --end;
            }
            swap(s[begin],s[end]);
            ++begin;
            --end;
        }

        return s;
    }
};

string的迭代器

iterator是一个像指针一样的类型,有可能就是指针,也有可能不是指针,但是它的用法像指针一样。

字符串的迭代器

string::iterator 

容器的迭代器

vector<int>::iterator

list<int>::iterator

void test4()
{
    string s="hello world";
    string::iterator it =s.begin();
//对于string和vector可以将!=改成<但是对于别的不一定
//因为string和vector存储的是连续的数据,并且用的是原生的指针,
//对于别的STL可不一定。
    while(it!=s.end())
    {
        cout<<*it<<" ";
        ++it;
    }
    cout<<endl;

//iterator是所有容器通用访问方式,用法类似的
    list<int> lt(10,1);
    list<int>::iterator lit=lt.begin();
    while(lit!=lt.end())
    {
        cout<<*lit <<" ";
        ++lit;
    }
    cout<<endl;
}

int main()
{
    test4();
    return 0;
}

  

 但是string不喜欢用迭代器,因为[]更好用。

vector不喜欢用iterator,因为[]更好用

list/map/set……只能用迭代器访问

iterator是所有容器通用的访问方式,并且用法是类似的。

范围for 

范围for相较于迭代器,是不可以反向的

void test5()
{
    //范围for -自动迭代,自动判断结束。
    //依次取s中的每个字符,赋值给ch
    string s="hello world";
    for(auto ch :s)
    {
        cout<<ch<<" ";
    }
    cout<<endl;

    //范围for对于容器都能支持
    list<int> lt(10,1);
    for(auto e :lt)
    {
        cout<<e<<" ";
    }
    cout<<endl;
    //范围for的底层其实就是迭代器
}
int main()
{
    test5();
    return 0;
}

反向迭代器 

void test6()
{
    string s("hello");
    string::reverse_iterator  rit=s.rbegin();
    while(rit !=s.rend())
    {
        cout<<*rit<<"  ";
        ++rit;
    }
    cout<<endl;
}
int main()
{
    test6();
    return 0;
}

常量正向/反向迭代器

常量迭代器过程中得到的数据不可修改

void PrintString(const string&str)
{
//常量正向迭代器
    string::const_iterator it=str.begin();
//可以使用自动推断
//    auto it=str.begin();
    while(it!=str.end())
    {
        cout<<*it<<" ";
        ++it;
    }
    cout<<endl;

    //常量反向迭代器
    string::const_reverse_iterator rit=str.rbegin();
//可以使用自动推断
//    auto it=str.begin();
    while(rit!=str.rend())
    {
        cout<<*rit<<" ";
        ++rit;
    }
    cout<<endl;
}

int main()
{
    string s("hello");
    PrintString(s);
    return 0;
}

4. string类对象的修改操作 

函数名称

功能说明

push_back

在字符串后尾插字符c

append

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

operator+= (重点)

在字符串后追加字符串str

c_str(重点)

返回C格式字符串

find npos(重点)

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

rfind

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

substr

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

注意:
1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

// 测试string:
// 1. 插入(拼接)方式:push_back  append  operator+=
// 2. 正向和反向查找:find() + rfind()
// 3. 截取子串:substr()
// 4. 删除:erase
void Teststring5()
{
    string str;
    str.push_back(' ');   // 在str后插入空格
    str.append("ho");  // 在str后追加一个字符"ho"
    str += 'm';           // 在str后追加一个字符'm'
    str += "mo";          // 在str后追加一个字符串"mo"
//这里使用的是流输出
//打印用的是string的重载
    cout << str << endl;
//这里用的是const char*的重载
    cout << str.c_str() << endl;   // 以C语言的方式打印字符串
    //c_str()生成一个const char *指针,指向首元素的地址

    // 获取file的后缀
    string file("string.cpp");
    size_t pos = file.rfind('.');
    cout<<pos<<endl;
    string suffix(file.substr(pos, file.size() - pos));
    cout << suffix << endl;

    // npos是string里面的一个静态成员变量
    // static const size_t npos = -1;

    // 取出url中的域名
    string url("http://www.cplusplus.com/reference/string/string/find/");
    cout << url << endl;
    size_t start = url.find("://");
    //会找到://第一个字符所在的位置4
    cout<<start<<endl;
    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("://");
    //pos+3是将://全部删光
    url.erase(0, pos + 3);
    cout << url << endl;
}

int main()
{
    Teststring5();
    return 1;
}

对于c_str()是为了兼容c语言,就如fopen中读取的字符串只能是c语言的char[]类型,所以我们下面一定要这样写才能兼容fopen

void test10()
{
    string filename("cplustext/main.cpp");
    FILE* fout=fopen(filename.c_str(),"r");
    assert(fout);
    char ch=fgetc(fout);
    while(ch !=EOF)
    {
        cout<<ch;
        ch=fgetc(fout);
    }
}
int main()
{
    test10();
    return 1;
}

 

对于string 和string.c_str()进一步的测试 

void test11()
{
    string filename("test.cpp");
    cout<<filename<<endl;
    cout<<filename.c_str()<<endl;
    filename+='\0';
    filename+="string.cpp";
    //\0不是可以显示的字符,所以打印不出来,会是一个奇怪的字符
    cout<<filename<<endl;//以string对象的size为准输出字符
    cout<<filename.c_str()<<endl;//常量字符串对象,遇到\0就停止打印。

    cout<<filename.size()<<endl;
    string copy=filename;
    cout<<copy<<endl;

}
int main()
{
    test11();
    return 0;
}

 

void test7()
{
    string s("砸");
    //push_back只支持一个一个字符从后面插入
    s.push_back(' ');
    s.push_back('-');
    //append可以append一整个字符串
    s.append("瓦鲁多!!");
    cout<<s<<endl;

    string str1("赛扣泥");
    string str2("嗨忒鸭子大,哈哈");
    //+=的底层就是push_back和append
    s+='@';
    s+=str1;
    s+=str2;
    cout<<s<<endl;

    //可以直接把str1从开头到结尾全部追加到s中
    s.append(str1.begin(),str1.end());
    cout<<s<<endl;
    //可以指定追加的范围
    string copy(s.begin()+3,s.end()-3);
    cout<<copy<<endl;
}
int main()
{
    test7();
    return 0;
}

 ​​​​​​​

find vs rfind

查找函数

void test13()
{
    string filename("kono.dio.da");
    //查找后缀,默认从0位置开始查找
    //返回第一个匹配的位置,如果匹配不到就返回string::npos,也就是整型的最大值
    size_t pos=filename.find('.');
    if(pos!=string::npos)
    {
        string  suff=filename.substr(pos,filename.size()-pos);
        cout<<suff<<endl;
    }
    //返回最后一个匹配到的位置
    size_t pos2=filename.rfind('.');
    if(pos!=string::npos)
    {
        string  suff=filename.substr(pos2,filename.size()-pos);
        cout<<suff<<endl;
    }

}
int main()
{
    test13();
    return 0;
}

 利用上面的函数,编写一个分解url成协议,域名和后面的资源名的函数

void DealUrl(const string&url)
{
    size_t pos1=url.find("://");
    if(pos1==string::npos)
    {
        cout<<"非法的url"<<endl;
        return;
    }
    string protocol=url.substr(0,pos1);
    cout<<protocol<<endl;

    size_t pos2=url.find('/',pos1+3);
    if(pos2==string::npos)
    {
        cout<<"非法的url"<<endl;
        return;
    }
    string domin=url.substr(pos1+3,pos2-pos1-3);
    cout<<domin<<endl;

    string uri=url.substr(pos2+1);
    cout<<uri<<endl;
}
void test14()
{
    string url1="https://www.cplusplus.com/reference/string/string/?kw=string";
    cout<<url1<<endl;
    DealUrl(url1);
}
int main()
{
    test14();
    return 0;
}

【牛客】字符串最后一个单词的长度 

描述

计算字符串最后一个单词的长度,单词以空格隔开,字符串长度小于5000。(注:字符串末尾不以空格为结尾) 

输入描述:

输入一行,代表要计算的字符串,非空,长度小于5000。

输出描述:

输出一个整数,表示输入字符串最后一个单词的长度。

字符串最后一个单词的长度_牛客题霸_牛客网

getline 

如果是的cin>>str的话,在读取到空格或者回车就会停止读取,需要使用getline来读取一行数据 

#include <iostream>
#include <string>
using namespace std;

int main(){
    string str;
//     cin>>str;
    //找到最后一个空格
    getline(cin,str);
    size_t pos=str.rfind(' ');
    if(pos !=string::npos)
    {
        cout<<str.size()-pos-1<<endl;
    }
    else
    {
        cout<<str.size()<<endl;
    }
}

5. string类非成员函数  

函数

功能说明

operator+

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

operator>> (重点)

输入运算符重载

operator<< (重点)

输出运算符重载

getline (重点)

获取一行字符串

relational operators (重点)

大小比较

6. string结构的说明

void test8()
{
    string s1;
    string s2("111111");
    cout<<s1.max_size()<<endl;
    cout<<s2.max_size()<<endl;

    cout<<s1.capacity()<<endl;
    cout<<s2.capacity()<<endl;
}
int main()
{
    test8();
    return 0;
}

 capacity是会动态变化的,但是max_size是确定的

void TestPushBack()
{
    string s;
    //这里的容量是没有计算\0的空间
    size_t sz = s.capacity();
    cout << "capacity changed: " << sz << '\n';
    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';
        }
    }
}

int main()
{
    TestPushBack();
    return 0;
}

可以看到在clion的编译器下,初始开辟是22,

在vs下面可能为15,

在Linux的g++下可能为1->2->4->8…… 

在Linux的g++下面的编译结果是这样的 

g++下string的结构 

G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
空间总大小
字符串有效长度
引用计数 

struct _Rep_base
{
    size_type _M_length;
    size_type _M_capacity;
    _Atomic_word _M_refcount;
};

reserve/resize可以提前扩容好大小

reserve是保留 开空间

resize是开空间+初始化

void TestPushBack()
{
    string s;
    s.reserve(1000);
    //这里的容量是没有计算\0的空间
    size_t sz = s.capacity();
    cout << "capacity changed: " << sz << '\n';
    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';
        }
    }
}

int main()
{
    TestPushBack();
    return 0;
}

 

测试resize 

void TestPushBack()
{
    string s;
    s.resize(1000);
    //这里的容量是没有计算\0的空间
    size_t sz = s.capacity();
    cout << "capacity changed: " << sz << '\n';
    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';
        }
    }
}

int main()
{
    TestPushBack();
    return 0;
}

这里为什么reverse在后面添加数据的时候并不会继续扩容,而resize会继续扩容呢?

这是因为reverse是提前开辟空间,接下来存就是从头开始存了,而resize开辟了空间,接着就是从这个空间往后存了。 

在clion的环境下,当开辟的大小到1008时,就会扩容到2015了

7.插入擦除函数

insert

下面的insert插入,erase擦除或者是replace都会是要大量移动字符串中元素的,所以效率都不高

#include<iostream>
#include <list>

using namespace std;
void test8()
{
    //在每个空格的位置插入百分号
    string str("ko no dio da");
    for(size_t i=0;i<str.size();)
    {
        if(str[i]==' ')
        {
            str.insert(i,"20%");
            //这个4是20%和下一个空格,从而达到下一个单词
            i+=4;
        }
        else
        {
            ++i;
        }
    }
    cout<<str<<endl;
}

int main()
{
    test8();
    return 1;
}

erase

#include<iostream>
#include <list>

using namespace std;
void test8()
{
    //在每个空格的位置插入百分号,并且不保留空格
    string str("ko no dio da");
    for(size_t i=0;i<str.size();)
    {
        if(str[i]==' ')
        {
            str.erase(i,1);
            str.insert(i,"20%");
            I+=3;
        }
        else
        {
            ++i;
        }
    }
    cout<<str<<endl;
}

int main()
{
    test8();
    return 1;
}

 或者采用下面用空间换时间的方式

#include<iostream>
#include <list>

using namespace std;
void test8()
{
    //在每个空格的位置插入百分号
    string str("ko no dio da");
    for(size_t i=0;i<str.size();)
    {
        if(str[i]==' ')
        {
            str.erase(i,1);
            str.insert(i,"20%");
            i+=4;
        }
        else
        {
            ++i;
        }
    }
    cout<<str<<endl;
}
void test9()
{
    string newstr;
    string str("ko no dio da");
    for(size_t i=0;i<str.size();++i)
    {
        if(str[i]!=' ')
        {
            newstr+=str[i];
        }
        else
        {
            newstr+="20%";
        }
    }
    cout<<newstr<<endl;
}
int main()
{
    test9();
    return 1;
}

 

【LeetCode】字符串相加

给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和并同样以字符串形式返回。

你不能使用任何內建的用于处理大整数的库(比如 BigInteger), 也不能直接将输入的字符串转换为整数形式。

来源:力扣(LeetCode)
链接:力扣
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

第一种方法,就是将两个数据从尾部相加,如果有进位,就将进位保存在一个临时的变量中,参与下一次的相加,然后将相加后的数据头插到结果的字符串中,直到两个数据都全部完成相加。 

class Solution {
public:
    string addStrings(string num1, string num2) {
//分别获取两个字符串的数组的最大下标
        int end1=num1.size()-1,end2=num2.size()-1;
//用next来存储进位
        int next=0;
//创建存储结果的字符串
        string strRet;
//只要有一个字符串中还有数据就要继续循环
        while(end1>=0 || end2>=0)
        {
//如果读取到的非空的话,就将字符串转换为数值
            int val1=end1>=0 ?num1[end1]-'0' :0;
            int val2=end2>=0 ?num2[end2]-'0' :0;

            int ret=val1+val2+next;
//进位最多也只是9+9,最大也仅仅是1
            next=ret>9 ? 1:0;
//可以采用insert指定在0号位置,插入一个元素,然后将我们计算过后的结果转换为字符串传入
            // strRet.insert(0,1,(ret%10)+‘0’);
//或者是使用迭代器,将迭代器的开始位置传入,然后将我们计算过后的结果转换为字符串传入
            strRet.insert(strRet.begin(),'0'+(ret%10));
//循环迭代
            --end1;
            --end2;
        }
        //处理1+9之类的情况,如果尾部为0,而十位上的读取都已经没有数据了,
        //但是还有进位的话就需要再头插1
        if(next)
        {
            strRet.insert(strRet.begin(),'1');
        }
        return strRet;
    }
    
};

上面头插的版本由于头插的效率比较低,需要遍历字符串我们可以看到我们的代码运行仅仅打败了7%,所以我们可以采用下面先尾插然后将整个字符串逆置的思路来提升我们的代码运行效率

class Solution {
public:
    string addStrings(string num1, string num2) {
        int end1=num1.size()-1,end2=num2.size()-1;
        int next=0;
        string strRet;
        while(end1>=0 || end2>=0)
        {
            int val1=end1>=0 ?num1[end1]-'0' :0;
            int val2=end2>=0 ?num2[end2]-'0' :0;
            int ret=val1+val2+next;
            next=ret>9 ? 1:0;

            //尾插
            strRet+=('0'+ ret%10);

            --end1;
            --end2;
        }
        //如果next还有进位就需要再尾插1
        if(next==1)
        {
            strRet+='1';
        }

        //逆置
        reverse(strRet.begin(),strRet.end());
        return strRet;
    }
    
};

【Leetcode】【字符串相乘】_桜キャンドル淵的博客-CSDN博客 

8.字符串转换相关的函数 

to_string vs stoi

void test15()
{
    int ival;
    double dval;
    cin>>ival>>dval;
    string istr= to_string(ival);
    //默认保持小数点后6位
    string dstr= to_string(dval);
    cout<<istr<<endl;
    cout<<dstr<<endl;

    istr="9999";
    dstr="9999.99999";
    //string to int
    ival=stoi(istr);
    dval=stoi(dstr);
    cout<<ival<<endl;
    cout<<dval<<endl;
}
int main()
{
    test15();
    return 0;
}

部分练习 

 【Leetcode】字符串中的第一次出现的唯一一次的字符​​​​​​​

给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1 。

力扣

class Solution {
public:
    int firstUniqChar(string s) {
//定义一个计数数组
        int a[26]={0};
        int len=s.size();
        for(int i=0;i<len;i++)
        {
            a[s[i]-'a']++;
        }
        for(int i=0;i<len;i++)
        {
            if(a[s[i]-'a']==1)
            {
                return i;
            }
        }
        return -1;
    }
};

【LeetCode】验证回文串

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

说明:本题中,我们将空字符串定义为有效的回文串。

力扣

class Solution {
public:
    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&&!isalnum(s[begin]) )
            {
                ++begin;
            }
            while(begin<end&&!isalnum(s[end]) )
            {
                --end;
            }
            if(s[begin]==s[end])
            {
                ++begin;
                --end;
            }
            else
            {
                return false;
            }
        }
        return true;
    }
};

本文含有隐藏内容,请 开通VIP 后查看