C++:STL::String模拟实现
创始人
2024-02-19 05:40:57

前言:

浅拷贝和深拷贝

  实现string需要知道深浅拷贝问题。观察如下自命名空间中实现的string,不自写string的string类型参数的构造函数,编译器会默认生成,做浅拷贝。对于自定义类型使用自定义类型的构造函数,如果是默认类型,会做浅拷贝。这里创建s2用string s1来构造,string自定义类型,但是没有默认构造函数,系统自动生成,内部的_str指针类型,会做浅拷贝或叫值拷贝,把s1的_str地址给s2的_str,最后s1是临时变量,空间被释放后,因为s2中_str指向同一片空间,连续两次析构函数使用:delete [],同一片空间用两次,造成如下错误。

#pragma once#include
#include// 想定义一个和库中一样名字的类 
// 定义命名空间做隔离 防止冲突
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”<

}

else cout<<“false”<

string c=b;

c=“”;

if (a.c_str()==b.c_str())

{

cout<<“true”<

}

else cout<<“false”<

a=“”;

if (a.c_str()==b.c_str())

{

cout<<“true”<

}

else cout<<“false”<

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<

return 0;
}

string模拟实现

数据成员

  1. string是连续的字符串,所以底层一般指针指向连续的一片空间。使用char* _str。
  2. 还要字符串长度、字符串容量,此外还看到了npos,这是个标志位,默认值-1,标记寻找不到或到末尾。
  3. 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()比较两个字符串以字典序。

遇到的错误

  1. 类外写重载报错了:原因是除了返回值地方要加string::,在operator前面也要加string::。
    请添加图片描述

重载发现已经声明,找了半天发现:一个加个const,类外一个没有加 请添加图片描述

原始模拟全部代码

#define _CRT_SECURE_NO_WARNINGS#include 
#include 
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 析构:分析:释放申请的连续空间	变量置0delete[]	这里数组是连续空间,用delete[]释放 _str 置null_size、_capacity置01.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+1strcpy()拷贝内容= :现代写法:先释放原指针指的连续空间 再创建tmp 做交换注意:1. 还是得判断 this != &s2. 返回类型是 string& 不需要再次拷贝构造3. return *this 得在判断之外1.7 容量reserve:只改变容量capacity 不改变size1.8 CRUDfind:字符串我好像不会resize:n小可以缩	不改变capacity1.x 工具函数swap():内部交换全部成员变量	必要性:它和全局swap()不一样,全局的std::swap()可以针对任何类型	如果我直接用std::swap()做交换行不行,不行,它内部用模板类型 a = b,使得一直调用 重载= ,而我们重载=里面用的就是用的这个,就不断重复做,而且每个里面都有string tmp创建临时对象,都开辟空间,这样最后就会栈溢出。size():直接_size 注意 size_t类型和参数类型是const2. 回忆在类外部定义成员函数,在一个命名空间内,只加类::即可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;}// 访问 accesschar& 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 ,容量不算\0void 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;}// 作差返回 possize_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 <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<

out:
重载后发现,如果插入‘\0’,cout< 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;elsereturn 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 之间,夹住一个单词。
  1. 注意点(犯错点):不要用for,直接while,因为i++在内部即可。每次找到一个空格位置,翻转完一定要i++。到了下一个单词的开头,j = i。因为j每次需要靠i的旧值得走到单词首位。而我用了for,i++两次,且在j = i之后。忘了i++。且不用for,不然i++多了一个。
  2. 注意这个题,最后不需要整体翻转过来。它就是要反转一个句子里面的每个单词。
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:看有几个2kint 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;}
};

相关内容

热门资讯

北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...