自学内容网 自学内容网

C++之spring

C++之spring

在这里插入图片描述

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

operator[]

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

在这里插入图片描述

这是一个既可以写也可以读的库函数,const修饰的内容是不可以更改的,所以是读

C++类与对象里要想普通对象和const修饰的对象同时重载

第二种访问及遍历操作就是:下标+[]

代码如下:

#include <iostream>
#include <string>
using namespace std;
int main()
{
    string s1("hello world");
    for (size_t i = 0; i < s1.size(); i++)
    {
        cout << s1[i] << " ";
    }
    cout << endl;
}

还有种写法:

#include <iostream>
#include <string>
using namespace std;
int main()
{
    string s1("hello world");
    for (size_t i = 0; i < s1.size(); i++)
    {
        s1[i]++;
    }
   
}

这种写法使自定义类型像数组一样访问及遍历,也能得到相同的结果:

在这里插入图片描述

接下来再来引入一个概念:迭代器

可以理解为像指针一样的东西,它在堆上面开辟空间,因为可以动态调整大小

代码示例如下:

    //迭代器
    string::iterator it1 = s1.begin();
    //这么写是因为定义在string的类域里面
    while (it1 != s1.end())
    {
        cout << *it1 << " ";
        ++it1;
    }
    cout << endl;
}

这里需要特别强调的是:end是最后一个数据的下一个位置,不是最后一个数据

运行结果如下:

在这里插入图片描述

可能string这里会显得迭代器有点繁琐,但是它是适用于所有容器通用的方法,下标+[]只适用于string,vector这样的底层是连续的物理结构,但是像链表就不太适合,运算符重载的效率就会大大降低,因为链表的地址没有大小关系

顺便说一句,begin和end相当于数学里面左闭右开的逻辑关系

可能会觉得while(it1!=s1.end())是否可以变成<,不建议这样,<只适合地址有大小关系的情况,!=更通用些

迭代器使用的例子:

#include <iostream>
#include <list>
using namespace std;
list <int> lt;
 //尾插
 lt.push_back(1);
 lt.push_back(2);
 lt.push_back(3);
 lt.push_back(4);
 //迭代器的使用
 list<int>::iterator it = lt.begin();
 while (it != lt.end())
 {
     cout << *it << " ";
     //用法类似于指针的解引用
     //但是并不是指针,底层是运算符重载
 }
 cout << endl;

迭代器的逆置访问

例子如下:

#include <iostream>
#include <string>
using namespace std;
int main()
{
    string s1("hello world");
string::reverse_iterator rit = s1.rbegin();
while (rit != s1.rend())
{
    cout << *rit << " ";
    ++rit;
}
cout << endl;
}

打印结果:

在这里插入图片描述

反向,因为方向也是相反的,所以不是–而是++

const迭代器

const string s2(s1);
//string::const_iterator it1 = s2.begin();  加上const,因为这是不能被修改的
auto it1 = s2.begin();//换成auto关键字,提高效率,毕竟是块语法糖
while (it1 != s2.end())
{
//*it1 += 1;    常量不能被修改
cout << *it1 << " ";
++it1;
}
cout << endl;
//反向
//string::const_reverse_iterator rit1 = s2.rbegin();
auto rit1 = s2.rbegin();
while (rit1 != s2.rend())
{
cout << *rit1 << " ";
++rit1;
}
cout << endl;

auto关键字

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个 不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型 指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期 推导而得

**用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加& **

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际 只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

**auto不能作为函数的参数,可以做返回值,但是建议谨慎使用 **

auto不能直接用来声明数组

示例如下:

int i=0;
int j=i;
//自动推导类型
auto z=i;
auto x=1.1;//double
auto p=&i;//int*
int &r1=i;
auto r2=r1;//int
auto &r3=r1;//int&

应用场景:

以后会学到map:它的底层涉及到红黑树,代码非常地长

std::map<std::string, std::string> dict;
std::map<std::string, std::string>::iterator dit = dict.begin();
auto dit = dict.begin();
//会简便非常多

这是一种语法糖,可以简化代码,是懒人的福音

auto能否做函数的形参?

C++20才支持

auto能否作返回值?

可以,但是不是特别好,因为万一嵌套不写注释,就会很麻烦:

.....
auto func3()
{
auto y=func4();
}
auto func2()
{
auto x=func3();
}

范围for

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此 C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围 内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。

范围for可以作用到数组和容器对象上进行遍历

范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。

例子1:

#include <iostream>
#include <string>
using namespace std;
int main()
{
    string s1("hello world");
//范围for
 for (char ch : s1)
 {
     cout << ch << " ";
 }
 cout << endl;
}

运行结果:
在这里插入图片描述

但其实日常中我们会更喜欢前面的类型是auto,会更加地方便,让编译器在编译的时期进行推导

和引用结合的情况:

1)修改内容:

#include <iostream>
#include <string>
using namespace std;
int main()
{
    string s1("hello world");
//范围for
 for (auto& ch : s1)
 {
     ch++;
     cout << ch << " ";
 }
 cout << endl;
}

运行结果:
在这里插入图片描述

不加上引用,就不会有影响,因为这是赋值拷贝,并没有改变内容本身

2)容器比较大的情况

例如上面提到的map容器

并不是所有情况都适合使用范围for

因为它的底层是迭代器,迭代器只支持容器,范围for有个例外,支持数组

例子如下:

int a[] = { 1,2,3,4,5,6 };
for (size_t i = 0; i < sizeof(a) / sizeof(int); i++)
{
    cout << a[i] << " ";
}
cout << endl;
for (auto e : a)
{
    cout << e << " ";
}
cout << endl;

运行结果:

在这里插入图片描述

也就是说范围for适用于容器和数组

string类对象的容量操作

size和length

功能都是返回字符串有效字符长度

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

capacity

主要作用是扩容

样例代码如下:

 string s1;
 size_t old = s1.capacity();
 cout << "capacity:" << old << endl;
 for (size_t i = 0; i < 100; i++)
 {
     s1 += 'x';
     if (s1.capacity() != old)
     {
         cout << "capacity:" << s1.capacity() << endl;
         old = s1.capacity();
     }
 }

运行结果:

在这里插入图片描述

可以发现,除了第一次是两倍大小的关系,其他情况下都是1.5倍左右,因为VS会预留个buffer,大小为16个字节大小,如果刚开始的string字符串大小小于16个字节,放到buffer里面,大于16个就浪费这个buffer,加上这个buffer是为了防止频繁地调用堆里面的存储

具体来说,原因如下:

vs和g++下string结构的说明

注意:

下述结构是在32位平台下进行验证,32位平台下指针占4个字节。

  • **vs下string的结构 **

string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义 string中字符串的存储空间

**当字符串长度小于16时,使用内部固定的字符数组来存放 **

当字符串长度大于等于16时,从堆上开辟空间

union _Bxty
{ // storage for small buffer or pointer to larger one
value_type _Buf[_BUF_SIZE];
pointer _Ptr;
char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;

这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建 好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。

其次:还有**一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的 容量 **

最后:还有一个指针做一些其他事情。

故总共占16+4+4+4=28个字节。

  • g++下string的结构

G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个 指针,该指针将来指向一块堆空间,内部包含了如下字段:

空间总大小

字符串有效长度

引用计数

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

指向堆空间的指针,用来存储字符串。

reserve

功能:为字符串预留空间

在VS中,reserve预留空间会比实际的要大

代码如下:

string s1;
s1.reserve(200);
size_t old = s1.capacity();
cout << "capacity:" << old << endl;
for (size_t i = 0; i < 100; i++)
{
    s1 += 'x';
    if (s1.capacity() != old)
    {
        cout << "capacity:" << s1.capacity() << endl;
        old = s1.capacity();
    }
}

运行结果如下:

在这里插入图片描述

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

string类对象的修改操作

push_back

在字符串后尾插字符

append

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

operator+=(重点,比上面两个库函数好)

在字符串后追加字符串str

int main()
{
string s1("hello");
s1.push_back(',');
s1.push_back('w');
cout << s1 << endl;

s1.append("orld");
cout << s1 << endl;

s1.append(10, '!');
cout << s1 << endl;

string s2("hello smile hello world");

s1.append(s2.begin()+6, s2.end());
cout << s1 << endl;

string s3("hello");
s3 += ',';
s3 += "world";
cout << s3 << endl;

return 0;
}

运行结果:

在这里插入图片描述

一道OJ题:仅仅反转字母

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

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

返回反转后的 s

示例 1:

输入:s = "ab-cd"
输出:"dc-ba"

示例 2:

输入:s = "a-bC-dEf-ghIj"
输出:"j-Ih-gfE-dCba"

示例 3:

输入:s = "Test1ng-Leet=code-Q!"
输出:"Qedo1ct-eeLg=ntse-T!"

提示

  • 1 <= s.length <= 100
  • s 仅由 ASCII 值在范围 [33, 122] 的字符组成
  • s 不含 '\"''\\'

代码:

class Solution {
public:
    string reverseOnlyLetters(string s) {
        int n = s.size();
        int left = 0, right = n - 1;
        while (true) {
            while (left < right && !isalpha(s[left])) { // 判断左边是否扫描到字母
                left++;
            }
            while (right > left && !isalpha(s[right])) { // 判断右边是否扫描到字母
                right--;
            }
            if (left >= right) {
                break;
            }
            swap(s[left], s[right]);
            left++;
            right--;
        }
        return s;
    }
};


双指针法,看下标

一道OJ题:字符串相加

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

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

示例 1:

输入:num1 = "11", num2 = "123"
输出:"134"

示例 2:

输入:num1 = "456", num2 = "77"
输出:"533"

示例 3:

输入:num1 = "0", num2 = "0"
输出:"0"

提示:

  • 1 <= num1.length, num2.length <= 104
  • num1num2 都只包含数字 0-9
  • num1num2 都不包含任何前导零

代码如下:

class Solution {
public:
    string addStrings(string num1, string num2) {
        //这是我们需要返回的字符串,要接受的对象
        string str;
        //因为扩容会浪费一定的空间,会导致效率降低,所以用了库函数里面的函数,更加合理地使用空间
        //+1是为了防止越界
        str.reserve(max(num1.size(),num2.size())+1);
        //end是有效字符串最后一位的下一位,所以要-1防止越界
        int end1=num1.size()-1,end2=num2.size()-1;
        //进位
        int next=0;
        //循环的条件是要继续下去,平常可能会想着结束的条件不一样,是反的
        while(end1>=0||end2>=0||next)
        {
            //这里是字符串接收,end计算完了还要记得--,字符串‘0’是拿ASCLL码表,0是48
            int x1=end1>=0?num1[end1--]-'0':0;
            int x2=end2>=0?num2[end2--]-'0':0;
            //ret是要接收的数
            int ret=x1+x2+next;
            //进位将数取整
            next=ret/10;
            //这个是个位,例如9+4,要余3
            ret=ret%10;
            //库函数,头插,尾插会错误,如11,231,会变成242
            str.insert(0,1,'0'+ret);
        }
        //这是为了处理1+9,没有进位的情况
        if(next==1)
        {
            str+='1';
            //这里引入了一个库函数,进行逆置,是为了降低时间复杂度,使其为O(N)
            reverse(str.begin(),str.end());
            //str.insert(str.begin(),'1');,时间复杂度为O(N^2)
        }
        return str;
    }
};
       int x1=end1>=0?num1[end1--]-'0':0;
        int x2=end2>=0?num2[end2--]-'0':0;
        //ret是要接收的数
        int ret=x1+x2+next;
        //进位将数取整
        next=ret/10;
        //这个是个位,例如9+4,要余3
        ret=ret%10;
        //库函数,头插,尾插会错误,如11,231,会变成242
        str.insert(0,1,'0'+ret);
    }
    //这是为了处理1+9,没有进位的情况
    if(next==1)
    {
        str+='1';
        //这里引入了一个库函数,进行逆置,是为了降低时间复杂度,使其为O(N)
        reverse(str.begin(),str.end());
        //str.insert(str.begin(),'1');,时间复杂度为O(N^2)
    }
    return str;
}

原文地址:https://blog.csdn.net/Mr_Xuhhh/article/details/142424895

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!