实现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 <零碎笔记
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;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
答:每次逆转一个单词。双指针: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:看有几个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;}
};
上一篇:BT - Unet:生物医学图像分割的自监督学习框架
下一篇:国家资助政策宣传标语