实现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; } str.reserve(111); str.resize(5); str.reserve(50); cout< return 0; 普通构造函数一有两种,无参和有参。但我们常用有参的,但是如果要写有参,编译器就不会实现无参,这样如果不初始化只申请会出错。 string(const char* str = “” ): 关于拷贝构造,常有现代写法和普通写法,现代写法和普通写法的区别是现代写法更加简介,通过调用工具函数(自己实现的swap交换拷贝构造得到的临时对象的string的成员指针免去自己写大量重复代码)。 析构需要delete[]释放连续空间。 本质其实是typedef char*。一般有普通迭代器和反向迭代器。因为不能只因为返回值构成重载,所以我们再typedefconst char* const迭代器。此外,()const是对this加const,根本上保证了成员变量权限缩小。 插入时需要判断容量是否满。 swap:自己内部交换掉所有成员变量。 实现两个就可以实现一个,比如>,底层用了字符串的strcmp函数做比较。 重载比较、构造函数中等地方,多用strlen()计算参数长度,用strcpy()拷贝两个字符串,strcat()不建议使用,内部会一直查找到末尾,效率非常低。strcmp()比较两个字符串以字典序。 重载发现已经声明,找了半天发现:一个加个const,类外一个没有加 reverse: pb需要借助,满了要扩容,满了的判断 insert()插串:插入方式特别用end 流重载:不一定要友元 out: in: 流提取 clear:_size清0 find: vs下string被做了特殊处理 string s = “hello world”; 思路和做法: 巧妙点: 1. 充分利用条件:+、-只在1位,碰到字母就返回0。且判断+ - ,如果是数字isdigit()才做计算 注意: 1. i、j别给错 class Solution { 注意点:1 。 为了跳过空格和其它标点,借助判断数字或字母的的函数。 妙点: i += 2* k
resize()调整的是size(),其中的字符数量,reserve()是扩大容量。resize()允许size变小,但是reserve(n)中n< capacity(),在变小了,就不起作用。
下面程序的输出结果正确的是( )
int main()
{
string str(“Hello Bit.”);
}string模拟实现
数据成员
class string
{
private:
char* _str;
size_t _size;
size_t _capacity;
static size_t npos;
};
构造函数和析构函数
1.构造函数设置为缺省参数,不传入参数,则构造""空字符串,不然你只写const char* str,不写别的,编译器发现你有构造函数,就不会自己生成。不然 string s; 不能通过,因为这是无参的。定义 string s;不做初始化,就会报错。此外:需要设置:capacity、size、str。 能用构造参数列表尽量用 效率高,但是初始化列表中的顺便按private顺序。
2. 申请空间,其实多申请一个,比如我要构造长度为n的字符串,其实我new char[n+1],因为n+1个位置要放‘\0’。
关于析构,我们注意有非默认类型(默认类型是:int、double等那些)如指针或包含了指针的类对象成员需要自己实现析构函数,编译器生成的不行。
迭代器
扩容
增删改查
工具函数
重载
重载出两个就可以根据这两个去简写其它了。
重载:[],我们需要返回引用类型,所以用string&接收。使用到的C语言函数:
遇到的错误
原始模拟全部代码
#define _CRT_SECURE_NO_WARNINGS
#include
测试代码
#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;
}
零碎笔记
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
调用strncpy
erase():借助npos,类中声明,类外定义,静态成员变量必须在类外
len == npos意思是len没有给值是默认就删完,或 该位置后删的长度多于pos后本有长度就删完
注意删完,我们也是给了’\0’的。
巧妙的npos+len <_size写法:
pos后删len个,就跳过len个,都挪前面来,包括了’\0’
把每个字符给out : out<
重载后发现,如果插入‘\0’,cout< s.c_str()就没有。
1、输入和输出不一样,输入要考虑扩容问题,因为你在造一个string,
而且不断输入过程中,可能面临输入很长,但是你会不断扩容的过程,
扩容速度较慢,应该让它少做。用一个数组缓冲区。每次够一定程度,
做一次 +=
2、in >> ch,不行,遇到空格和换行会停止读取。
用in.get()、不会中断
istream读取效率不低,用缓冲区
3. 有BUG,重复对s输出,先应该对s清理。clear()。不然上一次字符接收有剩余
0位置置’\0’;
找字符:从头到尾找,找不到返回npos,默认-1
找子串:strstr:原理是暴力查找,
resize:除了开空间,还初始化
比现在小,不会变capacity,变size,保留n之前。
判断 n 和 _size分if else
空串的:容量开了十几,因为加了优化,向堆申请小空间,会有内存碎片,
所以多开了16字节数组,给string里面加了char _buff[16],一开始给buff放
优点:效率会高,空间换时间。缺点显然就是:空间
但是Linux中又不一样。string使用练习题
1 . 字符串转int:
判断是否越界:使用int接收,如果某个时刻res/10 > int_max/10,就说明越界了。
合理利用条件:因为说了+ -只能在第一位出现,其它位置只是可能出现字母,但凡有字母,就返回0.
所以一个while加3个if简单判定就可以搞定。且这个题,给了返回值是int,不用考虑越界。
巧妙点: 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;
}
};
思路:相加共有部分,再看进位剩余,再看谁剩余逐个位相加。
2. 谁空就给另外一个,开头的出口条件
3. 字符串反转: Reverse() 、Reverse()、Reverse() 不是 sort() 不是sort() 。
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;
}
};
思路:首先,它里面都是字母、空格、符号,我们需要比较每个字母即可。所以大体思路就是双指针正序和逆序去比较。
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
答:每次逆转一个单词。双指针:i找空格或最后一个,而j标记到i。j 和 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;
}
};
思路:每2k个,转k个,我求了一下loop。然后看剩余几个。
每个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;
}
};