【C++】C++STL 揭秘:Strng背后的底层逻辑

是店小二呀 2024-09-05 13:35:01 阅读 55

在这里插入图片描述

C++语法 相关知识点 可以通过点击 以下链接进行学习 一起加油!
命名空间 缺省参数与函数重载 C++相关特性 类和对象-上篇 类和对象-中篇
类和对象-下篇 日期类 C/C++内存管理 模板初阶 String使用

在上篇介绍string类的使用与理解,本篇将为大家来带关于string的底层实现逻辑,当然这不是一定库里面的实现逻辑。我们设计一个string类是为了在使用string类相关接口,是为了我们更好的使用string类相关接口,在使用过程中知道该接口效率高还是低,可以更好地去选择

请添加图片描述

Alt

🌈个人主页:是店小二呀

🌈C语言专栏:C语言

🌈C++专栏: C++

🌈初阶数据结构专栏: 初阶数据结构

🌈高阶数据结构专栏: 高阶数据结构

🌈Linux专栏: Linux

🌈喜欢的诗句:无人扶我青云志 我自踏雪至山巅

请添加图片描述

文章目录

一、模拟现实string准备工作二、构造函数2.1 无参构造函数2.2 有参构造函数2.3 全缺省构造函数

三、析构函数四、交换函数Swap五、拷贝构造函数5.1 传统写法5.2 现代写法

六、operator[] (下标方括号)七、operator=(赋值运算符重载)7.1 传统写法7.2 现代写法7.3 现代写法优化

八、Size(获得字符串长度)九、Capacity(获得容量大小)十、resever十一、resize十二、insert12.1 任意位置插入字符12.2 任意位置插入字符串

十三、push_back十四、append十五、operator +=十六、find16.1 寻找字符16.2 寻找字符串

十七、substr十八、clear十九、erase二十、运算符重载二十一、实现迭代器21.1 可读可修改22.2 可读不可修改

二十三、模拟实现流插入和流提取23.1 operator<<(流插入)23.2 operator>>(流提取)

头文件:string.h

一、模拟现实string准备工作

在模拟实现string过程中,为了避免跟库中string发生冲突,需要创建个命名空间,在命名空间中实现string。

<code>namespace str

{ -- -->

class string

{

};

}

string底层是动态字符串顺序表,对此string中需要这个四个成员变量作为支架。

_str:指向动态开辟的空间_size:有效数据个数_capacity:容量空间的大小(不包括\0,实际空间包括)npos:静态成员变量,表示最大值(属于整个类)

namespace str

{

class string

{

private:

char* _str;

size_t _size;

size_t _capacity;

static const size_t npos;

};

//在类外初始化

const size_t string:: npos = -1;

}

二、构造函数

2.1 无参构造函数

string()

:_str(new char[1])//为'\0'开一块空间

:_size(0)

:_capacity(0)

{

_str[_size] = '\0';

}

2.2 有参构造函数

string(const char* str)

:_size(strlen(str))

,_capacity(strlen(str))

{

_str = new char[_capacity + 1];

strcpy(_str, str);

}

先确定str有效字符串长度,再决定申请多大空间,最后将str中内容拷贝给该对象

2.3 全缺省构造函数

【瑕疵版本】

string(const char *str = nullptr)

:_size(strlen(str))

,_capacity(strlen(str))

{

_str = new char[_capacity + 1];

strcpy(_str, str);

}

不足在于:当缺省值为nullptr时,strlen计算大小会报错,strlen会对参数部分解引用操作。同时这里strlen函数调用多次,效率降低。

【完善版本】

string(const char *str="")code>

:_size(strlen(str))

{ -- -->

_capacity = _size;

_str = new char[_capacity + 1];

strcpy(_str, str);

}

缺省值给""空字符串,编译器会自动填充\0,达到了无参的要求。无参调用时,调用strlen()函数计算大小为0。虽然缺省值可以直接给\0,但是没有必要,会导致有两个\0存在。

小结:在实践中推荐只保留一个全缺省构造函数

三、析构函数

~string()

{

delete[]_str;

_size = _capacity = 0;

}

清空_str指向动态空间中的资源,delete[] _str会调用析构函数和free释放空间。

四、交换函数Swap

//string类中

void swap(string& s)

{

std::swap(_str, s._str);

std::swap(_size, s._size);

std::swap(_capacity, s._capacity);

}

//string类外

void swap(string& x,string &y)

{

x.swap(y);

}

这里实现了在string类外和string类中的交换函数swap,但是类外的本质还是调用类中的swap。虽然算法库有swap函数模板,但是需要走深拷贝,代价很大,这里需要重现实现swap函数。

实现一个成员函数,通过交换属性(值拷贝),代价较少。虽然本质还是调用算法库中库函数,但是使用方式不同,付出的代价也不同。

小结:在使用swap函数时,需要根据自己的需求来使用,不然会弄巧成拙的

五、拷贝构造函数

5.1 传统写法

string(const string& s)

{

_str = new char[s._capacity + 1];

strcpy(_str, s._str);

_size = s._size;

_capacity = s._capacity;

}

在这里插入图片描述

具体流程:_str指向空,该对象没有被实例化。先开辟一块跟被拷贝对象等大的空间,再将数据拷贝一份。拷贝构造是将其他对象数据,拷贝到新的对象中。这本身是不影响被拷贝对象。

5.2 现代写法

<code>string(const string& str)

{ -- -->

string ss(str._str);

swap(ss);

}

在这里插入图片描述

具体流程:_ str指向空,该对象没有被实例化。先调用<code>string ss(str. _str)构造函数,得到一份str._str相同数据跟_str交换。相当于就是 _str和打工仔ss进行交互,完成拷贝操作。

这不采用string ss(s)拷贝构造,在拷贝构造中调用拷贝构造,本身就是闭环。

小结

无论是现代写法还是传统写法,在效率上是一样的,主要在于书写行数的关系现代写法和传统写法参数相同,不能构成函数重载,只能选择一个使用

六、operator[] (下标方括号)

char& operator[](size_t pos)

{ -- -->

assert(pos < _size);

return _str[pos];

}

const char& operator[](size_t pos) const

{

assert(pos < _size);

return _str[pos];

}

具体分析:

首先operator[]是调用函数,并且有两个函数重载其次operator[]是个大进步,对于检查越界的情况如果是数组的话,很难检查是否有越界情况发生(越界检查是一种抽查,在容易越界的地方,放几个值,看这个值是否被修改)。越界读的话,很难发现越界,是很危险的operator[]实现中,有assert判断是否出现有越界的情况。关于const char& operator[](size_t pos) const情况,是为了对于被const修饰对象,也可以访问其元素,并且仅限于读权限

七、operator=(赋值运算符重载)

7.1 传统写法

string& operator=(const string& str)

{

char* tmp = new char[str._capacity + 1];

strcpy(tmp, str._str);

delete[]_str;

_str = tmp;

_size = str._size;

_capacity = str._capacity;

return *this

}

在这里插入图片描述

具体说明

赋值操作是发生于两个对象已经创建完毕,对此被赋值对象自然存在一块空间,需要等着被清除资源。这里赋值拷贝不能影响到赋值对象,可以采用使用一个临时变量进行中间过程交换,开辟跟被拷贝对象等大的空间,将数据拷贝,将_str指向旧空间释放,指向tmp指向空间完成赋值。

tmp是函数内部声明的自动变量(局部变量),出作用域会自动销毁,但是指向堆上分配的内存不会因为tmp的销毁而释放,导致_str指向那块空间不会被回收,顺便完成了tmp指向空间为空操作。

7.2 现代写法

<code>string& operator=(const string& s)

{ -- -->

string ss(s);

swap(ss);

return *this;

}

在这里插入图片描述

这里不用担心ss对象指向空间没有得到释放,ss对象属于局部对象,出了函数作用域就会调用析构函数和销毁。

7.3 现代写法优化

<code>string& operator=(string ss)

{ -- -->

swap(ss);

return *this;

}

相对于上面的现代写法,这里在传参的过程中完成了拷贝构造。

小结:

无论是现代写法还是传统写法,在效率上是一样的,主要在于书写行数的关系现代写法和传统写法参数相同,不能构成函数重载,只能选择一个使用

八、Size(获得字符串长度)

size_t size(const string& s) const

{

return s._size;

}

九、Capacity(获得容量大小)

size_t capacity(const string& s) const

{

return s._capacity;

}

十、resever

void reserve(size_t n)

{

if (n > _capacity)

{

char* tmp = new char[n + 1];

strcpy(tmp, _str);

delete[]_str;

_str = tmp;

_capacity = n;

}

}

说明:

reserve只有在所需空间超过当前容量才起到扩容作用让打工仔tmp开辟满足条件的空间,_str在进行拷贝、销毁、转化指向这里需要改动_capacity就行, _size代表是有效个数

十一、resize

void resize(size_t n, char ch = '\0')

{

if (n <= _size)

{

_str[n] = '\0';

_size = n;

}

else

{

//提前开辟好空间

reserve(n);

for (size_t i = _size; i < n; i++)

{

_str[i] = ch;

}

_str[n] = '\0';

_size = n;

}

}

说明:

使用resize需要考虑三种情况,但是模拟实现可以只考虑两种情况提前使用reserve开辟好空间,避免扩容操作,将两种情况处理成一种情况当n小于等于当前大小,需要使用\0的形式截断字符串当n大于等当前容量,就是按照ch完成填充新开辟空间

十二、insert

12.1 任意位置插入字符

【瑕疵版本】

void insert(size_t pos, char ch)

{

assert(pos <= _size);

// 扩容2倍

if (_size == _capacity)

{

reserve(_capacity == 0 ? 4 : 2 * _capacity);

}

size_t end = _size;

while (end >= pos)

{

_str[end + 1] = _str[end];

--end;

}

_str[pos] = ch;

++_size;

}

不足之处:从代码的逻辑来看,感觉是没啥问题。如果选择在首位置插入数据,end进行–操作变成负数。由于end类型为size_t 无符号整数会导致end为最大值,循环不会停下。

【尝试调正】:那么将end设置为符号整型,就可以保证它可以为负数

int end = _size;

while (end >= pos)

{

_str[end + 1] = _str[end];

--end;

}

但是end和pos是不同类型的变量,在进行比较时,编译器会执行整型提升,size__t隐式转化为int类型再比较。

【最终方案】:

size_t end = _size + 1;

while (end > pos)

{

_str[end] = _str[end - 1];

--end;

}

让end指向结尾下一个位置。在数据移动的时候,解决了首位置插入end等于pos移动导致死循环的问题。

12.2 任意位置插入字符串

void insert(size_t pos, const char* str)

{

assert(pos <= _size);

size_t len = strlen(str);

if (len + _size > _capacity)

{

reserve(_size + len);

}

size_t end = _size + len;

while (end > pos + len)

{

_str[end] = _str[end - len];

--end;

}

//通过strncpy将需要插入字符串覆盖,完成插入

strncpy(_str + pos, str, len);

_size += len;

}

在这里插入图片描述

具体流程:先提前判断插入字符串长度是否会超过当前容量,这里同上面一致将元素移动备份,将待插入元素进行覆盖操作。需要注意插入在首位置死循环的问题,同样采取上面的解决办法:向后移动N位预留空间。代码逻辑可以参考中两张图片理解。

十三、push_back

<code>void push_back(char ch)

{ -- -->

// 扩容2倍

if (_size == _capacity)

{

reserve(_capacity == 0 ? 4 : 2 * _capacity);

}

_str[_size] = ch;

++_size;

_str[_size] = '\0';

//insert(_size, ch);

}

说明:

插入单个字符时,需要判断空间是否充足跟顺序表的尾插差不多实现任意位置插入,直接内部调用insert也是可以的

十四、append

void append(const char* str)

{

// 扩容

size_t len = strlen(str);

if (_size + len > _capacity)

{

reserve(_size + len);

}

strcpy(_str + _size, str);//在尾部拷贝完成添加操作

_size += len;

//insert(_size, str);

}

说明:

当然可以选择一开始直接扩容插入字符串时,需要判断空间是否充足跟顺序表的尾插差不多实现任意位置插入,直接内部调用insert也是可以的

十五、operator +=

string& operator+=(char ch)

{

push_back(ch);

return *this;

}

string& operator+=(const char* str)

{

append(str);

return *this;

}

十六、find

16.1 寻找字符

size_t find(char ch, size_t pos = 0) const

{

assert(pos < _size);

for (size_t i = pos; i < _size; i++)

{

if (_str[i] == ch)

return i;

}

return npos;

}

说明:

从任意位置遍历,找到返回该位置索引没有找到就返回npos(-1)const修饰函数,仅限于读权限

16.2 寻找字符串

size_t find(const char* sub, size_t pos = 0) const

{

assert(pos < _size);

const char* p = strstr(_str + pos, sub);

if (p)

{

return p - _str;

}

else

{

return npos;

}

}

说明:

strstr匹配所需字符串,返回匹配到字符串第一个位置如果为空,表示不存在如果找到了,利用指针-指针,得到下标位置索引

十七、substr

string substr(size_t pos = 0, size_t len = npos)

{

string sub;

//if (len == npos || len >= _size-pos)

if (len >= _size - pos)

{

for (size_t i = pos; i < _size; i++)

{

sub += _str[i];

}

}

else

{

for (size_t i = pos; i < pos + len; i++)

{

sub += _str[i];

}

}

return sub;

}

说明:

substr功能是返回从指定位置开始len长度的字符串先创建string空对象用于接收截取字符串当len == nposlen >= _size - pos,代表了从pos位置到尾的字符串截取。尽量书写len >= _size - pos,而不是len + pos >= _size这种,是为了防止len + pos超过类型最大值范围

十八、clear

void clear()

{

_size = 0;

_str[_size] = '\0';

}

清空资源,不释放空间

十九、erase

void erase(size_t pos = 0, size_t len = npos)

{

assert(pos <= _size);

if (pos > _size - len || len==npos)

{

_str[pos] = '\0';

_size = pos;

}

else

{

strcpy(_str + pos, _str + pos + len);

_size -= len;

}

}

在这里插入图片描述

具体说明:如果长度大于从指定位置到结束位置的长度,只需要在pos位置设置结束标识符。不然的话,就是将需要清空位置被后面的内容覆盖就行。如果对于strcpy这块逻辑不明白,可以回顾下strcpy使用方法。

二十、运算符重载

<code>bool operator==(const string& s1, const string& s2)

{ -- -->

int ret = strcmp(s1.c_str(), s2.c_str());

return ret == 0;

}

bool operator<(const string& s1, const string& s2)

{

int ret = strcmp(s1.c_str(), s2.c_str());

return ret < 0;

}

bool operator<=(const string& s1, const string& s2)

{

return s1 < s2 || s1 == s2;

}

bool operator>(const string& s1, const string& s2)

{

return !(s1 <= s2);

}

bool operator>=(const string& s1, const string& s2)

{

return !(s1 < s2);

}

bool operator!=(const string& s1, const string& s2)

{

return !(s1 == s2);

}

说明:跟类和对象实现日期Date是一样的,具体有需要点击该连接日期类

二十一、实现迭代器

21.1 可读可修改

typedef char* iterator;

iterator begin()

{

return _str;

}

iterator end()

{

return _str + _size;

}

22.2 可读不可修改

typedef const char* iterator;

const_iterator begin() const

{

return _str;

}

const_iterator end() const

{

return _str + _size;

}

说明:

迭代器是一个像指针的东西,可以是指针,也可以不是指针,具体还需要看底层实现这里使用指针实现迭代器,返回第一个位置和最后一个位置的下一个位置。

二十三、模拟实现流插入和流提取

23.1 operator<<(流插入)

#include<iostream>

using namespace std;

ostream& operator<<(ostream& out, const string& s)

{

for (auto ch : s)

{

out << ch;

}

return out;

}

说明:

operator是C++标准库中的一个类,用于输出数据流,位于标准命名空间std中。对此需要引用std命名空间访问。out << ch;重载的<<运算符用于将字符ch写入输出流out是C++标准库中重载

23.2 operator>>(流提取)

istream& operator>>(istream& in, string& s)

{

s.clear();

char ch;

//in >> ch;

ch = in.get();

char buff[128];

size_t i = 0;

while (ch != ' ' && ch != '\n')

{

buff[i++] = ch;

// [0,126]

if (i == 127)

{

buff[127] = '\0';

s += buff;

i = 0;

}

ch = in.get();

}

if (i > 0)

{

buff[i] = '\0';

s += buff;

}

return in;

}

具体流程:首先clear先清空对象中的资源,这里需要考虑char类型的最大值为128,创建个数组用于输入到类中,使用operator+=将得到的字符串输入到对象中。

流提取是将从键盘中读取的数据存储到s对象中,至于返回类型为istream&是为支持连续流提取操作cin >> s >> s2。string中getline接口模拟实现是一样的就不展开.

头文件:string.h

#pragma once

#include <assert.h>

#include <iostream>

using namespace std;

//模拟实现string 4.4

//设置命名空间,防止跟库中string有冲突

namespace bit

{

class string

{

public:

typedef char* iterator;

typedef const char* const_iterator;

iterator begin()

{

return _str;

}

const_iterator begin() const

{

return _str;

}

iterator end()

{

return _str + _size;

}

const_iterator end() const

{

return _str + _size;

}

//\0会占用一块空间

//无参构造

//string()

//:_str(new char[1])

//,_size(0)

//,_capacity(0)

//{

//_str[_size] = '\0';

//}

//有参构造

//string(const char* str)

//:_size(strlen(str))

//,_capacity(strlen(str))

//{

//_str = new char[_capacity + 1];

//strcpy(_str, str);

//}

//全缺省值构造

string(const char *str="")code>

:_size(strlen(str))

{ -- -->

_capacity = _size;

_str = new char[_capacity + 1];

strcpy(_str, str);

}

析构构造

//~string()

//{

//delete[]_str;

//_size = _capacity = 0;

//}

//构造函数s1(s2)

//传统写法--c_str作用还是不明白

//string(const string& s)

//{

//_str = new char[s._capacity + 1];

// strcpy(_str, s._str);

//_size = s._size;

//_capacity = s._capacity;

//}

//现代写法s1(s2)

//string(const string& s)

//{

////string ss(s);//为什么需要做一份拷贝呢?不会递归死循环吗?

//

////不能在拷贝构造里面,调用拷贝构造,应该调用构造,大小和容量可以根据字符串来计算

//string ss(s._str);

//swap(ss);//这里完成的是交换,是s1拷贝s2一份,s2本身不跟s1交换

//}

//意思是在传参的时候,进行了拷贝构造,但是拷贝构造还没有实现

//string(string ss)

//{

//swap(ss);

//}

//修改string的接口

//赋值operator=

//传统写法 s1=s2;

string& operator=(const string& s)

{

char* tmp = new char[s._capacity + 1];

strcpy(tmp, s._str);

delete[]_str;

_str = tmp;

_size = s._size;

_capacity = s._capacity;

return *this;

}

//现代写法

string& operator=(const string& s)

{

//可以使用构造或者使用拷贝构造

string ss(s._str);

swap(ss);

return *this;

}

//优化 有拷贝构造基础上

string& operator=(string ss)

{

swap(ss);

return *this;

}

//遍历

size_t size(const string& s) const

{

return s._size;

}

size_t capacity(const string& s) const

{

return s._capacity;

}

char& operator[](size_t pos)

{

assert(pos < _size);

return _str[pos];

}

const char& operator[](size_t pos) const

{

assert(pos < _size);

return _str[pos];

}

const char& operator[](size_t pos) const

{

assert(pos < _size);

return _str[pos];

}

//扩容操作

void reserve(size_t n)

{

if (n > _capacity)

{

char* tmp = new char[n + 1];

strcpy(tmp, _str);

delete[]_str;

_str = tmp;

_capacity = n;

}

}

void resize(size_t n, char ch = '\0')

{

//如果小于该容量

if (n <= _capacity)

{

_str[n] = '\0';

_size = n;

}

else

{

//提前开辟好空间

reserve(n);

for (size_t i = _size; i < n; i++)

{

_str[i] = ch;

}

_str[n] = '\0';

_size = n;

}

}

//插入操作

void push_back(char ch)

{

//判断容量是否满了

if (_size == _capacity)

{

//是否为一开始的状态,就是为空待插入

reserve(_capacity == 0 ? 2 : 2 * _capacity);

}

_str[_size] = ch;

_size++;

_str[_size] = '\0';

}

void append(const char* str)

{

//不用想这么多,先扩容先

size_t len = strlen(str);

//里面会给\0开辟一块空间

reserve(_capacity+len);

strncpy(_str + _size, str, len);

_size = _size + len;

}

void swap(string& s)//做值拷贝,属性交换,避免深拷贝

{

std::swap(_str, s._str);

std::swap(_size, s._size);

std::swap(_capacity, s._capacity);

}

//实现更加轻松的+=

string& operator+=(const char ch)

{

push_back(ch);

return *this;

}

string& operator+=(const char* str)

{

append(str);

return *this;

}

void insert(size_t pos, char ch)

{

assert(pos <= _size);

if (_size == _capacity)

{

reserve(_capacity == 0 ? 2 : 2 * _capacity);

}

//size_t end = _size;

0<end end>=0

//while (end > pos)

//{

//_str[end] = _str[end - 1];

//end--;

//}

//_str[pos] = ch;

//_size++;

//_str[_size] = '\0';

//从\0开始移动

size_t end = _size + 1;

while (end > pos)

{

_str[end] = _str[end - 1];

--end;

}

_str[pos] = ch;

++_size;

}

void insert(size_t pos, const char* str)

{

assert(pos <= _size);

size_t len = strlen(str);

if (len + _size > _capacity)

{

reserve(_size + len);

}

size_t end = _size + len;

while (end > pos + len)

{

_str[end] = _str[end - len];

--end;

}

strncpy(_str + pos, str, len);

_size += len;

}

void erase(size_t pos, size_t len = npos)

{

assert(pos <= _size);

if (pos + len > _size || len==npos)

{

_str[pos] = '\0';

_size = pos;

}

else

{

strcpy(_str + pos, _str + pos + len);

_size -= len;

}

}

size_t find(char ch, size_t pos = 0) const

{

assert(pos <= _size);

for (size_t i = 0; i < pos; i++)

{

if (_str[i] == ch)

return i;

}

return npos;

}

size_t find(char* sub, size_t pos = 0) const

{

assert(pos <= _size);

const char* p = strstr(_str + pos, sub);

if (p == nullptr)

return npos;

else

return p - _str;

}

//功能是从某个位置打印len长度的字符串

string substr(size_t pos = 0, size_t len = npos)

{

assert(pos <= _size);

string sub;

//这里怕pos+len会太大

//打印拿出来

if (pos > _capacity - len || len==npos)

{

for (size_t i = pos; i <_size; i++)

{

sub += _str[i];//i=size-1

}

}

//没有超过

else

{

for (size_t i = pos; i < pos + len; i++)

{

sub += _str[i];

}

}

return sub;

}

//当你将 sub 返回时,std::string 类会自动确保字符串以 null 结尾。不需要手动添加 \0。

const char* c_str() const

{

return _str;

}

void clear()

{

_size = 0;

_str[_size] = '\0';

}

private:

char* _str;

size_t _size;

size_t _capacity;

static const size_t npos;

};

static const size_t nops = -1;

void swap(string& x,string &y)//实现两个swap 外面的swap实际还是调用里面的swap

{

x.swap(y);

}

bool operator==(const string& s1, const string& s2)

{

int ret = strcmp(s1.c_str(), s2.c_str());

return ret == 0;

}

bool operator<(const string& s1, const string& s2)

{

int ret = strcmp(s1.c_str(), s2.c_str());

return ret < 0;

}

bool operator<=(const string& s1, const string& s2)

{

return s1 < s2 || s1 == s2;

}

bool operator>(const string& s1, const string& s2)

{

return !(s1 <= s2);

}

bool operator>=(const string& s1, const string& s2)

{

return !(s1 < s2);

}

bool operator!=(const string& s1, const string& s2)

{

return !(s1 == s2);

}

//需要实现迭代器

ostream& operator<<(ostream& out, const string& s)

{

for (auto ch : s)

{

out << ch;

}

return out;

}

iostream& operator>>(iostream& in, string& s)

{

//先对资源清空

s.clear();

char buff[128];

char ch;

ch=in.get();

size_t i = 0;

while (ch != '\n' && ch != ' ')

{

buff[i++] = ch;

if (i == 127)

{

buff[127] = '\0';

s += buff;

i = 0;

}

ch = in.get();

}

if (i > 0)

{

buff[i] = '\0';

s += buff;

}

//已经手动输入过了

return in;

}

istream& getline(istream in, string& s)

{

//先对资源清空

s.clear();

char buff[128];

char ch;

ch = in.get();

size_t i = 0;

while (ch != '\n' && ch != ' ')

{

buff[i++] = ch;

if (i == 127)

{

buff[127] = '\0';

s += buff;

i = 0;

}

ch = in.get();

}

if (i > 0)

{

buff[i] = '\0';

s += buff;

}

//已经手动输入过了

return in;

}

}


以上就是本篇文章的所有内容,在此感谢大家的观看!这里是店小二呀C++笔记,希望对你在学习C++语言旅途中有所帮助!

请添加图片描述



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。