前言:
浅拷贝和深拷贝
实现string需要知道深浅拷贝问题。观察如下自命名空间中实现的string,不自写string的string类型参数的构造函数,编译器会默认生成,做浅拷贝。对于自定义类型使用自定义类型的构造函数,如果是默认类型,会做浅拷贝。这里创建s2用string s1来构造,string自定义类型,但是没有默认构造函数,系统自动生成,内部的_str指针类型,会做浅拷贝或叫值拷贝,把s1的_str地址给s2的_str,最后s1是临时变量,空间被释放后,因为s2中_str指向同一片空间,连续两次析构函数使用:delete [],同一片空间用两次,造成如下错误。
#pragma once
#include<string>
#include<iostream>
// 想定义一个和库中一样名字的类
// 定义命名空间做隔离 防止冲突
namespace bit {
class string
{
public:
// :_str(str):这样是浅拷贝,把str的值给了_str,str右值是字符串常量,str值是地址,str是指针
// str值在常量区,不是在堆上,扩容不方便。 用所以建议用new做深拷贝。这样方便扩容。
// 以下这种方式只是拷贝值
string(const char* str)
:_str(new char[strlen(str)+1])
{
strcpy(_str, str);
}
~string()
{
delete[] _str;
_str = nullptr;
}
private:
char* _str;
};
void test_string1()
{
string s1("hello world");
// 如果是以下的方式,使用系统默认的构造函数做浅拷贝,s2的_str值会和s1的_str相等
// s1是临时变量,s2以s1为模板,s1的没了,s2的_str也没了。
// s2以s1为模板 s1是临时变量的栈空间会做释放,s2指向的内存栈空间也会释放,
// 释放两次,肯定会报错。
// !!!程序报错是因为创建s2时,编译器调用默认的构造函数 s1后面释放了,s2的以s1作浅拷贝。
string s2(s1);
}
}
错误如下
这个报错就是说明:delete出错,可能连续释放了。
-
深拷贝:
先申请一片一样大的空间,再做复制。
这里string类使用Strcpy()做值复制。 -
string s3(s1) 和 string s3 (“aaa”); s3 = s1 的区别是什么?
-
对于赋值运算符=的重载注意点:引用做参数、比较是不是自己等知识点
-
Find_first(const string& str. size_t pos=0, size_t n):了解它常用的三个参
str:子串。
pos:查找的起始位置
n:找n个字符。不给就找到末尾。 -
str.c_str()
答:返回字符串,但是其实是得到指向内容的指针,所以直接比较两个string变量的c_str() ,==判断的是指针相等否,必然不等。如下第一个是F、此外,都做深拷贝,所以都是F。
关于代码输出正确的结果是( )(vs2013 环境下编译运行)
int main(int argc, char *argv[])
{
string a=“hello world”;
string b=a;
if (a.c_str()==b.c_str())
{
cout<<“true”<<endl;
}
else cout<<“false”<<endl;
string c=b;
c=“”;
if (a.c_str()==b.c_str())
{
cout<<“true”<<endl;
}
else cout<<“false”<<endl;
a=“”;
if (a.c_str()==b.c_str())
{
cout<<“true”<<endl;
}
else cout<<“false”<<endl;
return 0;
}
- resize()和reserve()
resize()调整的是size(),其中的字符数量,reserve()是扩大容量。resize()允许size变小,但是reserve(n)中n< capacity(),在变小了,就不起作用。
下面程序的输出结果正确的是( )
int main()
{
string str(“Hello Bit.”);
str.reserve(111);
str.resize(5);
str.reserve(50);
cout<<str.size()<<“:”<<str.capacity()<<endl;
return 0;
}
string模拟实现
数据成员
- string是连续的字符串,所以底层一般指针指向连续的一片空间。使用char* _str。
- 还要字符串长度、字符串容量,此外还看到了npos,这是个标志位,默认值-1,标记寻找不到或到末尾。
- npos是静态成员变量,需要在类外部设置。
class string
{
private:
char* _str;
size_t _size;
size_t _capacity;
static size_t npos;
};
构造函数和析构函数
普通构造函数一有两种,无参和有参。但我们常用有参的,但是如果要写有参,编译器就不会实现无参,这样如果不初始化只申请会出错。
string(const char* str = “” ):
1.构造函数设置为缺省参数,不传入参数,则构造""空字符串,不然你只写const char* str,不写别的,编译器发现你有构造函数,就不会自己生成。不然 string s; 不能通过,因为这是无参的。定义 string s;不做初始化,就会报错。此外:需要设置:capacity、size、str。 能用构造参数列表尽量用 效率高,但是初始化列表中的顺便按private顺序。
2. 申请空间,其实多申请一个,比如我要构造长度为n的字符串,其实我new char[n+1],因为n+1个位置要放‘\0’。
关于拷贝构造,常有现代写法和普通写法,现代写法和普通写法的区别是现代写法更加简介,通过调用工具函数(自己实现的swap交换拷贝构造得到的临时对象的string的成员指针免去自己写大量重复代码)。
关于析构,我们注意有非默认类型(默认类型是:int、double等那些)如指针或包含了指针的类对象成员需要自己实现析构函数,编译器生成的不行。
析构需要delete[]释放连续空间。
迭代器
本质其实是typedef char*。一般有普通迭代器和反向迭代器。因为不能只因为返回值构成重载,所以我们再typedefconst char* const迭代器。此外,()const是对this加const,根本上保证了成员变量权限缩小。
扩容
插入时需要判断容量是否满。
- 注意reserve()和resize()前面只开辟空间而不做初始化,resize()做初始化,且n小于当前时,只改变size而不变capacity。
增删改查
- append():只实现插入一个长串即可,它底层用insert()可实现。插入单个字符通过push_back()即可。
- push_back():用insert()插入末尾即可。
- insert():挪动旧的,还要判断位置是否合法。
工具函数
swap:自己内部交换掉所有成员变量。
重载
实现两个就可以实现一个,比如>,底层用了字符串的strcmp函数做比较。
重载出两个就可以根据这两个去简写其它了。
重载:[],我们需要返回引用类型,所以用string&接收。
使用到的C语言函数:
重载比较、构造函数中等地方,多用strlen()计算参数长度,用strcpy()拷贝两个字符串,strcat()不建议使用,内部会一直查找到末尾,效率非常低。strcmp()比较两个字符串以字典序。
遇到的错误
- 类外写重载报错了:原因是除了返回值地方要加string::,在operator前面也要加string::。
重载发现已经声明,找了半天发现:一个加个const,类外一个没有加
原始模拟全部代码
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <assert.h>
using namespace std;
/*
补:流重载的知识:
string:
1。 string的默认成员函数:
private 私有成员:
char* str : string的底层一定是指向字符串的指针,char*即可。
size_t _size :有效长度
size_t _capacity:容量
static const size_t npos; :查找结果的返回,常设置为-1,默认表示找不到
静态成员变量,需要在类外部设置
public 公有成员:
构造函数:
string str = "abc"; 需要实现:string(const char* str = "" ) 这样的话,免去了写无参
string s = str; 需要实现拷贝构造: string(const string& s);
string s(str); 需要实现拷贝构造 同上
string& operator=(const string& s)
~string()
1.1 string(const char* str = "" ):
: 构造函数设置为缺省参数,不传入参数,则构造""空字符串,不然你只写const char* str,不写别的,编译器发现你有构造函数,就不会自己生成,
此外 string s; 不能通过,因为这是无参的。
定义 string s;不做初始化,就会报错。
此外:需要设置:capacity、size、str。
能用构造参数列表尽量用 效率高
分析:
1. size:看你传入的str长度:用strlen(),C语言的接口
2. 初始容量和 _size相等,为了节省空间
3. strcpy:拷贝:给前面拷贝后面,最后一个位置会给'\0'
4. char* str要多一个空间,因为最后存'\0' 但 '\0'不计入sizes 直接new char【】
5. 构造参数列表:能用多用,效率高。初始化变量在构造参数列表里面用,而使用函数比如做赋值等在函数体里面。
遇到的错误:
1. 类内声明 外定义 缺省值 const char* str = "" 必须删一个,建议删下面,但其实上下删哪个都行
2. 初始化参数列表里的执行顺序是按你的private来写,所以_str(new char[capacity+1])时,是错的,建议用函数体 就不会考虑
注意:
!!可以调用同类型对象的私有成员。
1.2 析构:
分析:
释放申请的连续空间 变量置0
delete[] 这里数组是连续空间,用delete[]释放
_str 置null
_size、_capacity置0
1.3 拷贝构造函数
传统写法分析:
s._str,怎么可以的 在类内部就可以
现代写法分析:
利用swap()交换两个对象内部的全部成员。所以下面需要实现swap()
错误点:
1. 当:s1 = s1
自己给自己赋值,就防止出麻烦,避免掉因为释放掉自己,也相当于释放掉参数的内容了
判断用的地址比较: this != &s
注意:
这里无非是考虑深浅拷贝问题,最简单判定方法就是看看是否成功析构
1.4 access
[] : 要开放对它内容修改,所以用&
const operator[](size_t pss)const:
!!! :必须加()后const,加上const变了参数类型,不加只是变返回值类型不能构成重载
1.51 迭代器:本质是字符串指针,所以现在public中做typedef
类型必须公有,因为外面需要调用
begin(): 类型是迭代器,其实就是个指针
返回_str即可 是首地址
end():
1.52 const迭代器:
分析:
重命名类型 所以就返回str 或 str 的加法即可
()后面加const 否则只是类型不同不能构成重载,()后面加const改变的是this指针,相当于变了参数
1.6 重载
= :
类型:我们肯定要得到一个一直要存在的值,所以函数返回类型必须是&。
传统写法:
释放原来指针指向连续空间
_str = new char[] 申请新的 capacity+1
strcpy()拷贝内容
= :现代写法:
先释放原指针指的连续空间 再创建tmp 做交换
注意:
1. 还是得判断 this != &s
2. 返回类型是 string& 不需要再次拷贝构造
3. return *this 得在判断之外
1.7 容量
reserve:只改变容量capacity 不改变size
1.8 CRUD
find:字符串我好像不会
resize:n小可以缩 不改变capacity
1.x 工具函数
swap():内部交换全部成员变量
必要性:
它和全局swap()不一样,全局的std::swap()可以针对任何类型
如果我直接用std::swap()做交换行不行,不行,它内部用模板类型 a = b,使得一直调用 重载= ,而我们重载=里面用的就是用的这个,就不断重复做,
而且每个里面都有string tmp创建临时对象,都开辟空间,这样最后就会栈溢出。
size():直接_size 注意 size_t类型和参数类型是const
2. 回忆在类外部定义成员函数,在一个命名空间内,只加类::即可
3.
*/
namespace lz
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
// 默认成员函数
string(const char* str ="");
string(const string& s);
~string();
// 重载 = 传统写法
/*string& operator=(const string& s)
{
if (&s != this)
{
delete[] _str;
_str = new char[s._capacity + 1];
_capacity = s._capacity;
_size = s._size;
strcpy(_str, s._str);
}
return *this;
}*/
// 重载 = 现代写法
/*string& operator=(const string& s)
{
if (&s != this)
{
delete[] _str;
string tmp(s);
swap(tmp);
}
return *this;
}*/
// 重载 = 现代写法之更简洁
string& operator=(string s)
{
swap(s);
return *this;
}
iterator begin()
{
return _str;
}
iterator end()
{
return _str+_size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
// 访问 access
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
// 容量:
// 扩容:多申请一个空间 n+1,未来放 '\0' ,但是capacity = n ,容量不算\0
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 > _size)
{
// 插入数据
reserve(n); // 里面会判断
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_str[n] = '\0';
_size = n; // resize后_size肯定变了
}
else
{
// 删除数据
_str[n] = '\0'; // 因为从0开始
_size = n;
}
}
// =======================CRUD
// append要接字符串,所以不适合直接扩容2倍,可能太多了
// 计算插入串长度
//void append(const char* str)
//{
// size_t len = strlen(str);
// // 判容量够不够
// if (_size + len > _capacity)
// reserve(_size+len); // 容量不够至少扩这么多
// strcpy(_str + _size, str); // C语言的函数可以直接用 +_size直接到末尾'\0'位置,这里可以直接拷贝过去 strcpy:拷贝到'\0' 包括'\0'
// _size += len; // capacity不动,只有reverse才动
//}
// 以insert实现 追加一串
void append(const char* str)
{
insert(_size, str);
}
// 重载的append 放n个一样的字符 多次pushback效率低,n很大,可能会扩容多次,所以直接reverse(n)
void append(size_t n, char ch) //
{
reserve(_size + n);
for (size_t i = 0; i < n; i++)
{
push_back(ch);
}
}
/*void append(size_t n, char ch)
{
insert(_size, ch);
}*/
// pos位置合法 满了就扩容:小心0问题,如果pos == 0 到insert0,会因为size_t无符号,0--,导致从0到int最大值,死循环
string& insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
for (size_t i = _size+1; i > pos; i--)
{
_str[i] = _str[i-1]; // 从_size+1开始,其实_size处是'\0',所以连 '\0'也拿走
}
_str[pos] = ch; // 总之还是给pos位置
++_size;
return *this;
}
// 插入串,先挪动开旧的
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len); // 实际扩容会比这个多一个
}
size_t end = _size + len; // 现在在'\0'处 pos处后面的得挪动len个。且从_size开始的,'\0'也挪动了
while (end >= pos + len) // end 不会是0 ,不会发生0再到大的事
{
_str[end] = _str[end-len];
end--;
}
strncpy(_str + pos, str, len); // len个长度
//strcpy(_str+pos, str);
_size += len;
return *this;
}
// 满了要扩容,且别忘'\0'
/*void push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0?4: 2*_capacity );
}
_str[_size] = ch;
_str[_size + 1] = '\0';
++_size;
}*/
// push_back用insert实现
void push_back(char ch)
{
insert(_size, ch);
}
// erase:删除npos之后全部
void erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos+len>=_size) // len == npos意思是len没有给值是默认就删完,或 该位置后删的长度多于pos后本有长度就删完
{
_str[pos] = '\0';
_size = pos;
}
else // pos后删len个,就跳过len个,都挪前面来,包括了'\0'
{
strcpy(_str + pos, _str + pos+len);
_size -= len;
}
}
// ==================================重载
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
/*bool operator >(const string& s)
{
return strcmp(_str, s._str) > 0;
}*/
bool operator >(const string& s)const;
bool operator ==(const string& s)
{
return strcmp(_str, s._str) == 0;
}
bool operator >=(const string& s)
{
return *this > s || *this == s;
}
bool operator <(const string& s)
{
return !( * this >= s);
}
bool operator <=(const string& s)
{
return !(*this > s);
}
bool operator !=(const string& s)
{
return !(* this == s);
}
// 其余工具函数
void swap(string& s);
// c_str 早期模拟实现流重载先用它测试输出
const char* c_str() const
{
return _str;
}
// clear()
void clear();
size_t size() const
{
return _size;
}
size_t find(char ch, size_t pos = 0) const
{
for (size_t i = 0; i < _size; i++)
{
if (ch == _str[i])
{
return i;
}
}
return npos;
}
// 作差返回 pos
size_t find(const char* sub, size_t pos = 0) const
{
// strstr返回的是地址
const char* ptr = strstr(_str + pos, sub);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
// 取子串
string substr(size_t pos, size_t len = npos)const
{
assert(pos < _size);
size_t realLen = len;
// == _size:刚刚好,需要末'\0' 要不要等于 等于也是到末尾,但是不用加加上也没事
if (len == npos || pos+len > _size)
{
realLen = _size - pos;
}
string sub; // 全取上
for (size_t i = 0; i < realLen; i++)
{
sub += _str[pos + i];
}
return sub;
}
private:
char* _str;
size_t _size;
size_t _capacity;
static size_t npos;
};
size_t string::npos = -1;
//=================================================================================================================================================================
// 缺省普通构造 传统写法
/*string::string(const char* str)
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}*/
// 缺省普通构造 现代写法
string::string(const char* str)
:_size(strlen(str))
,_capacity(_size)
,_str(new char[strlen(str)+1])
{
strcpy(_str, str);
}
// 流重载 好像用问题,用'\0'会停止输出
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); i++)
{
out << s[i];
}
return out;
}
// 如何提取:字符串默认以 string之间默认以'\n'间隔 搜 in.get()
// 用 in.get(),发现不是 ' ' 和 '\n'即
istream& operator>>(istream& in, string& s)
{
//char ch;
//in >> ch; // :in >> ch:拿不到空格和换行,字符串默认以 string之间默认以 '\n' 和 ' ' 间隔
//while (ch != ' ' && ch != '\0')
//{
// s += ch;
//}
// 上述方法不行
s.clear(); // 你重复cin>>s,s会不断拼接。所以需要它
char ch;
ch = in.get();
const size_t N = 32;
char buff[N];
size_t i = 0;
while (ch != ' ' &&ch != '\n')
{
buff[i++] = ch;
if (i == N - 1)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
// 出来以后 buff中还有剩余
buff[i] = '\0';
s += buff;
return in;
}
// 拷贝构造 传统写法:用错误
/*string::string(const string& s)
:_str(new char[s._capacity+1])
, _size(s._size)
, _capacity(s._capacity)
{
strcpy(_str, s._str);
}
*/
// 现代写法:
string::string(const string& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
lz::string tmp(s._str);
swap(tmp);
}
// 赋值运算符重载
/*string::string& operator=(string& s);
{
return this;
}*/
/*string::string& operator=(string s)
{
string::swap(s);
return *this;
}*/
//bool string::operator > (const string& s)const
//{
// return false;
/*
bool string::operator == (const string& s)
{
}
bool string::operator >= (const string& s)
{
}
bool string::operator < (const string& s)
{
}
bool string::operator <= (const string& s)
{
}
bool string::operator !=(const string& s)
{
}*/
string::~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
// 工具
void string::swap(string& s)
{
std::swap(s._str, _str);
std::swap(s._size, _size);
std::swap(s._capacity, _capacity);
}
void string::clear()
{
_str[0] = '\0';
_size = 0;
}
}
测试代码
#include "L1_string.h"
void string1()
{
lz::string s1 = "come the way abc";
lz::string s2;
//lz::string s2 = s1;
cout << "临时测试s1能不能初始化成功,s2空字符串" << endl;
cout << s1.c_str() << endl;
cout << "s2.c_str() : " << s2.c_str() << endl;
cout << "下面测试 []访问" << endl;
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i] << " ";
}
cout << endl;
cout << "通过[] 修改" << endl;
for (size_t i = 0; i < s1.size(); i++)
{
cout << ++s1[i]<< " ";
}
cout << endl <<"下面测试迭代器访问" << endl;
lz::string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it << " ";
it++;
}
cout << endl << "测试范围for"<< endl;
for (auto ch : s1)
{
cout << ch << " ";
}
cout << endl;
}
/*
如果没有拷贝构造,会崩溃,因为编译器默认生成的浅拷贝构造使得s2和s1指向一样,
最后会重复释放,但是对于日期这种,没有向内存申请空间的对象,就可以做浅拷贝。
2. 对初始化后的赋值,会报错,因为析构了两次,所以得重写operator
*/
void testCopy()
{
lz::string s1("hello gan");
lz::string s2(s1);
s2[0] = 'w';
cout << s1.c_str() << endl;
cout << s2.c_str() << endl;
cout << "修改了sw[0]也一样" << endl;
cout << "下面测试赋值 s3 = s1 ," << endl;
lz::string s3 = "aaa";
cout << "s3 = " << s3.c_str() << endl;
cout << "自己给自己赋值 出现乱码 需要在拷贝构造中加判断,这算个思想BUG 要防范" << endl;
s3 = s3;
cout << s3.c_str() << endl;
}
void test_push_back()
{
lz::string s1("hello back");
s1.push_back('6');
cout << s1.c_str() << endl;
}
void test_append()
{
lz::string s1("hello back");
s1 += ' ';
s1.append("sir");
cout << s1.c_str() << endl;
cout << "下面是+=字符串" << endl;
s1 += " o haha";
cout <<s1.c_str() << endl;
}
void test_insert()
{
lz::string s1("hello back");
s1.insert(s1.size(), 'a');
cout << s1.c_str() << endl;
}
void test_insertSring()
{
lz::string s1("hello www");
s1.insert(6, "orld hh");
cout << s1.c_str() << endl;
}
void test_append_ByInsert()
{
lz::string s1("hello world");
s1.append(" ahh");
cout << s1.c_str() << endl;
cout << "下面append不加一个字符,加一个字符第一个都能匹配上,且编译通不过" << endl;
s1.append("abcd");
}
void test_PB_ByInsert()
{
lz::string s1("hello world");
s1.push_back('!');
cout << s1.c_str() << endl;
}
void test_Erase()
{
string s1("hello");
cout << "对h1 = hello 删1, 10, 理论只留下一个h" << endl;
s1.erase(1, 10);
cout << s1.c_str() << endl;
string s2("hello");
s2.erase(1);
cout << s2.c_str() << endl;
string s3("hello");
s3.erase();
cout << s3.c_str() << endl;
}
//
void test_cout()
{
lz::string s("hello world");
cout << s.c_str() << endl;
cout << s << endl;
cout << "=== , +='\0' 和 +=world == " << endl;
s += '\0';
s += " world";
cout << s.c_str() << endl;
cout << s << endl;
}
void test_in()
{
lz::string s;
cin >> s;
cout << s << endl;
cout << "测试连续输入" << endl;
cin >> s;
cout << s;
}
void test_sub()
{
lz::string s("hekki eptka");
lz::string a = s.substr(0, 45);
cout << a << endl;
}
void test_resize()
{
lz::string s("hekki eptka");
s.resize(20, 'x');
cout << s << endl;
s.resize(3);
cout << s << endl;
}
int main()
{
// lz::string s1 = "abc";
//string1();
//test_push_back();
// test_append();
//test_insert();
//test_insertSring();
//test_PB_ByInsert();
// test_insert();
//test_PB_ByInsert();
//test_append_ByInsert();
//test_Erase();
//test_cout();
//test_cout();
//test_in();
//test_sub();
test_resize();
return 0;
}
零碎笔记
reverse: pb需要借助,满了要扩容,满了的判断
push_back:记得’\0’
string& 引用需要对象,所以return *this;
+=:注意返回值返回谁、类型是什么
append(): 它用来加一串,最后可以用insert加一串简化,
它不实现append()加1个,因为用push_back()
strcpy为什么不用strcat:它是个失败的设计,找’\0’,太费劲。
借助append() += 字符串也实现了
insert()某个位置插字符:size_t pos 0问题,end >pos,即可,不要end>=pos,
end = _size+1
用 str[i] = str[i-1],使得前一个比如0号位的也拿到了,i就不用在0时–了。
原来 end >= pos :pos==0
end从 _size~0,
改为:end>=_size+1, pos == 0
s[i+1] =s[i] ,这样 i == 0, s[0+1] = s[0],但是 i–后成了最大正数,死循环
初始:end =_size+1, end > pos
s[i] = s[i-1] 这样pos 不用到0,不存在–到最大值的情况
总之,就是想办法让i不能为0
insert()插串:插入方式特别用end
调用strncpy
erase():借助npos,类中声明,类外定义,静态成员变量必须在类外
len == npos意思是len没有给值是默认就删完,或 该位置后删的长度多于pos后本有长度就删完
注意删完,我们也是给了’\0’的。
巧妙的npos+len <_size写法:
pos后删len个,就跳过len个,都挪前面来,包括了’\0’
流重载:不一定要友元
把每个字符给out : out<<s[i];
out:
重载后发现,如果插入‘\0’,cout<<s还有,而
s.c_str()就没有。
in: 流提取
1、输入和输出不一样,输入要考虑扩容问题,因为你在造一个string,
而且不断输入过程中,可能面临输入很长,但是你会不断扩容的过程,
扩容速度较慢,应该让它少做。用一个数组缓冲区。每次够一定程度,
做一次 +=
2、in >> ch,不行,遇到空格和换行会停止读取。
用in.get()、不会中断
istream读取效率不低,用缓冲区
3. 有BUG,重复对s输出,先应该对s清理。clear()。不然上一次字符接收有剩余
clear:_size清0
0位置置’\0’;
find:
找字符:从头到尾找,找不到返回npos,默认-1
找子串:strstr:原理是暴力查找,
resize:除了开空间,还初始化
比现在小,不会变capacity,变size,保留n之前。
判断 n 和 _size分if else
vs下string被做了特殊处理
空串的:容量开了十几,因为加了优化,向堆申请小空间,会有内存碎片,
所以多开了16字节数组,给string里面加了char _buff[16],一开始给buff放
优点:效率会高,空间换时间。缺点显然就是:空间
但是Linux中又不一样。
string使用练习题
string s = “hello world”;
- 题:
1 . 字符串转int:
判断是否越界:使用int接收,如果某个时刻res/10 > int_max/10,就说明越界了。
合理利用条件:因为说了+ -只能在第一位出现,其它位置只是可能出现字母,但凡有字母,就返回0.
所以一个while加3个if简单判定就可以搞定。且这个题,给了返回值是int,不用考虑越界。
思路和做法:
巧妙点: 1. 充分利用条件:+、-只在1位,碰到字母就返回0。且判断+ - ,如果是数字isdigit()才做计算
巧妙点: 2 注意进位:直接ans = ans*10 + ch - ‘0’ 更是巧妙不用进位计算。
注意点:函数使用:isdigit()和 isalpha :啊了佛
class Solution {
public:
int StrToInt(string str) {
int ans = 0;
int isplus = 1; // 默认必须给1 :因为有时候首位没有+
// 从尾到头计算:每次 ans = ans * 10 + ch - '0' 更nb 进位也不用 迭代器也可
for(int i = 0; i < str.size(); i++)
{
if(isalpha(str[i]))
return 0;
else if(str[i] == '+' || str[i] == '-')
isplus = (str[i] == '+')?1:-1;
else if(isdigit(str[i]))
ans = ans * 10 + str[i] - '0';
}
return ans*isplus;
}
};
- 字符串相加:
思路:相加共有部分,再看进位剩余,再看谁剩余逐个位相加。
注意: 1. i、j别给错
2. 谁空就给另外一个,开头的出口条件
3. 字符串反转: Reverse() 、Reverse()、Reverse() 不是 sort() 不是sort() 。
class Solution {
public:
string addStrings(string num1, string num2) {
if(num1.size() == 0)
return num2;
if(num2.size() == 0)
return num1;
// 先加共有部分
int i = num1.size()-1;
int j = num2.size()-1;
string res= “”;
int flag = 0;
while(i>=0 && j>=0 )
{
int t = num1[i] - ‘0’ + num2[j] - ‘0’ + flag;
if(t > 9)
{
t -= 10;
flag = 1;
}
else
{
flag = 0;
}
res += char(t + ‘0’);
i–;
j–;
}
// 进位有剩余 res 一直走 :但是res完了 又得和剩余的num1和num2,但是可以直接做 ,不用单纯加进位
while(i>=0)
{
int t = num1[i] - ‘0’ + flag;
if(t > 9)
{
t -= 10;
flag = 1;
}
else
{
flag = 0;
}
res += char(t + ‘0’);
i–;
}
// 如果是num2剩余
while(j>=0)
{
int t = num2[j] - ‘0’ + flag;
if(t > 9)
{
t -= 10;
flag = 1;
}
else
{
flag = 0;
}
res += char(t+ ‘0’);
j–;
}
// 如果flag剩余 其实也就一个了 直接插入即可
if(flag)
{
res += ‘1’;
}
// 逆置
std::reverse(res.begin(), res.end());
return res;
}
};
- 验证回文串
思路:首先,它里面都是字母、空格、符号,我们需要比较每个字母即可。所以大体思路就是双指针正序和逆序去比较。
注意点:1 。 为了跳过空格和其它标点,借助判断数字或字母的的函数。
2. 发现当前不是数字或字母,直接跳过,用while更猛。
3. 大小写不影响:都化小写:记住转小写:tolower(),此外是!isNumOrLetter
4.
class Solution {
public:
bool isNumOrLetter(char a)
{
if(a >= '0' && a <= '9')
return true;
else if((a <= 'z' && a >= 'a') || ('A' <= a && a <= 'Z'))
return true;
else
return false;
}
bool isPalindrome(string s) {
int start = 0;
int end = s.size()-1;
while(start < end)
{
// 跳过非字母数字
while(start < end && !isNumOrLetter(s[start]))
start++;
while(start < end && !isNumOrLetter(s[end]))
end--;
// 用小写比较
if(tolower(s[start]) != tolower(s[end]))
return false;
else
{
start++;
end--;
}
}
return true;
}
};
// raceacar
// racaecar
- 反转字符串中的单词:III
答:每次逆转一个单词。双指针:i找空格或最后一个,而j标记到i。j 和 i 之间,夹住一个单词。
- 注意点(犯错点):不要用for,直接while,因为i++在内部即可。每次找到一个空格位置,翻转完一定要i++。到了下一个单词的开头,j = i。因为j每次需要靠i的旧值得走到单词首位。而我用了for,i++两次,且在j = i之后。忘了i++。且不用for,不然i++多了一个。
- 注意这个题,最后不需要整体翻转过来。它就是要反转一个句子里面的每个单词。
class Solution {
public:
void Reverse(string& str, int start , int end)
{
while(start < end)
{
char t = str[start];
str[start] = str[end];
str[end] = t;
start++;
end--;
}
}
string reverseWords(string s) {
int i = 0;
int j = 0;
// 9一次转一个词
while(i < s.size())
{
while(i < s.size() && (s[i]!= ' ' || i == s.size()-1))
i++;
Reverse(s, j, i-1);
// 翻转完一定得i++到下一个单词开头
i++;
j = i;
}
//Reverse(s, 0, s.size()-1);
return s;
}
};
- 反转字符 II:
思路:每2k个,转k个,我求了一下loop。然后看剩余几个。
妙点: i += 2* k
每个loop里面: 翻转: ( i , i + k - 1 )
class Solution {
public:
void Reverse(string& s, int start, int end)
{
while(start < end)
{
char t = s[start];
s[start] = s[end];
s[end] = t;
start++;
end--;
}
}
string reverseStr(string s, int k) {
// 每2k,就翻转2k的前k:看有几个2k
int loop = s.size() / (2 * k);
int i = 0;
while(loop)
{
loop--;
Reverse(s, i, i + k-1);
i += 2*k;
}
loop = s.size() / (2 * k);
int surplus = s.size() - 2 * k * loop;
if( 0 < surplus && surplus < k)
{
Reverse(s, i, s.size()-1);
}
else if(k <= surplus &&surplus < 2*k)
{
int end = i + k - 1;
Reverse(s, i, end);
}
return s;
}
};