注:写本博客的目的接在记录学习过程,学习c++的新知识点,以及巩固以前学过的知识!!!
/*
1.const类型调用需要const修饰函数
2.拷贝构造只能用引用接收,如果不适用引用,会导致无限递归
3.浅拷贝与深拷贝
4.为了接收参数,必须使用const char* str ,必须使用const修饰,否则会导致无法匹配
5.size_t 类型与 int类型 进行比较时,有个隐式类型的提升
6.流重载--之前不会,现在也不熟悉
7.流重载中的流提取的buff妙用
8.cin 与 .get的区别
9.cout打印遇到\0会终止
*/2023/11/12
思考:string类要实现的功能是什么,以及应该怎么样去设计string?
答:对字符串的存储以及进行相关的处理,既然string类要存储字符串,需要一个字符串指针,为了能够动态开辟空间,还需要size,用于记录string的字符串有效字符个数,以及一个capacity,用于记录string中总共有多大的空间用来存储字符串,并且可以通过判断size与capacity的大小来判断插入字符串、字符是否需要扩容。
那么它的基础框架就出来了:
namespace zxh {
class string {
public:
//code...
private:
char* _str;
size_t _size;
size_t _capacity;
};
我们要实例话string对象,那么就需要在初始化时对_str,_size,_capacity进行处理:
//构造函数
string(const char* str = "\0") : //question1
_size(strlen(str))
{
_str = new char[_size+1]; //question2
_capacity = _size;
strcpy(_str,str);
}
思考:
一丶为什么question1处,要使用const char *str="\0"?更换为其他的写法是否正确?
答: 必须使用const修饰
1.1使用const修饰str,可以防止str的内容被修改
1.2使用const修饰str,还可以进行如下操作: string s1("abcd");
这里直接传入的abcd默认是const类型
在c语言中,为
char *p="abcd";
*p的内容不可以被更改。
既然传递给string的为const类型的字符串,如果我们在接收的地方不使用const类型进行接收,会进行报错,属于典型的权限放大:const类型被放大为非const类型。
1.3这里的"\0"就是const类型,所以需要const修饰
2.1是否可以更换为:
string(const char *='\0');
不可以。'\0'是单个字符,不能用来初始化str这个字符串
二丶 为什么_str=new char[_size+1] ,使用_size+1?
答:在这里设计的_size是记录字符串的有效个数,假如字符串中有"abcd"
那么_size为4,但需要开辟的空间为5个,最后一个位置需要存放\0,来代表abcd的结尾。
为了能够拷贝string本身,所以还需要使用拷贝构造,用来拷贝string本身
//拷贝构造
string(const string &s) {//question1
_size = s._size;
_capacity = s._capacity;
_str = new char[_size + 1];
memcpy(_str,s._str,_size*sizeof(char));
}
string对象中有三个成员变量,需要将这三个成员变量全部copy给另一个string对象。
思考:
还请区别浅拷贝与深拷贝
在这篇文章中我有详细介绍,需要的看这边:
一、我们将string &s换为string s,是否正确?
答:是万万不可的。
我们来梳理一下知识:
void fun1(string s)与void fun2(string &s)这两个的区别?
fun1中,s是一份拷贝,fun2中s是对传过来参数的引用
既然string s是一份拷贝,那么拷贝需要怎么办?
拷贝是不是会去调用拷贝构造函数?
回到这里的string(const string s)拷贝构造函数
我们用s去接收参数,那么s就需要拷贝接收到的参数,拷贝接收到的参数,就又需要调用拷贝构造函数,而拷贝构造函数里面,又要接收s进行拷贝,从而一直进行对拷贝构造的无限递归。
关于拷贝构造的无限递归,这篇文章中也有介绍,需要的还可以参考如下文章:
3.复制重载:
//复制重载
string& operator=(const string &s) {
if (this != &s) { //question1
char* tmp = new char[s._capacity + 1];
delete[] _str;
_str = tmp;
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
思考:
这里为什么要判断if(this!=&s)?
答:是为了防止自己复制给自己,本身没有意义,在这个代码逻辑中,还会产生会导致strcpy的内容为随机值,其中-------this 为指针 &s取s的地址,&s也为指针
有个小窍门,可以先试着开辟空间,如果空间开辟失败,那么也不会导致其字符串丢失。
4.常规函数
void reserve(size_t n) {
//reserve会无视缩容要求
if(n>_size){
char* tmp = new char[n+1];
strcpy(tmp,_str);
_size = _size;
_capacity = n;
delete[]_str;
_str = tmp;
}
}
void resize(size_t n,char ch='\0') {
if (n>_size) {
if (n>_capacity) {
reserve(n);
}
while (_size!=n) {
_str[_size++] = ch;
}
}
_str[n] = '\0';
_size = n;
}
void push_back(char ch) {
if (_size==_capacity) {
//reserve(_capacity*=2);// 这样写对吗?--考虑_capacity=0的情况
reserve(_capacity=_capacity==0?10:_capacity*2);
}
_str[_size++] = ch;
//最后还要加上\0
_str[_size] = '\0';
}
void append(const char *str) {
size_t str_size = strlen(str);
if (_size+str_size>_capacity) {
reserve(_size+str_size);
}
strcpy(_str+_size,str);
_size += str_size;
}
void insert(size_t pos,char ch) {
assert(pos<=_size);
if (_size==_capacity) {
reserve(_capacity = _capacity == 0 ? 10 : _capacity * 2);
}
int end = _size + 1; //question1
while (end>pos) {
_str[end] = _str[end-1];
end--;
}
_str[pos] = ch;
_size++;
}
void insert(size_t pos,const char *str) {
size_t str_size = strlen(str);
if (str_size+_size>_capacity) {
reserve(str_size+_size);
}
int end = _size +str_size;
while (end > pos+str_size-1) {
_str[end] = _str[end - str_size];
end--;
}
strncpy(_str+pos,str,str_size);
_size = str_size + _size;
_str[_size] = '\0';
}
void clear() {
_str[0] = '\0';
_size = 0;
}
size_t size() const{
return _size;
}
void print() const{
cout << "capacity:" << _capacity << " size:" << _size << endl;
for (auto ch:*this) {
cout << ch << " ";
}
cout << endl;
}
此处仅仅考虑一个问题:
问题一处,为什么要定义end=_size+1?
答:
int end = _size;
这里pos为 size_t end 为int类型
while (end>=pos)
进行比较时,end 会提升为size_t ,如果pos为0,会导致end一直满足>=0
会出现错误,所以解决办法为:while (end!=pos)第二个办法便是上文中的办法。
所以size_t 与 int 进行比较时,要注意隐形的整型提升。
流重载
std::istream& operator>>(std::istream& in, zxh::string& s) {
s.clear(); //进行流插入时,应该先把string里面的内容清除
/*
如果我们插入的字符串比较长,那+=会频繁的扩容,我们应该怎么解决这个问题?
预留一个buff,每次写入到buff中,等buff满了之后,将buff写入
*/
char buff[128];
int buff_i = 0;
char ch = in.get();
while (ch != ' ' && ch != '\n') {
if (buff_i==127) {//此时里面有128个数
buff[127] = '\0';
s.append(buff);
buff_i = 0;
}
buff[buff_i++] = ch;
ch = in.get();
}
if (buff_i!=0) {
buff[buff_i] = '\0';
s.append(buff);
}
return in;
}
std::ostream& operator<<(std::ostream& out,string s) {
size_t size = s.size();
for (size_t i = 0; i <= size - 1; i++) {
cout << s[i];
}
return out;
}
std::istream 是 C++ 中输入流的基类,用于从标准输入或其他输入源中读取数据。
而在这段代码中,这个输入流类被用作输入运算符 >> 的一个参数,
该运算符被用于从输入流中读取数据并填充到指定的 zxh::string 对象中。char ch;
in >> ch;
while (ch!=' '&&ch!='\n') {
s.push_back(ch);
in >> ch;
}
这样写对吗?--不对,原因如下:in >> ch 会忽略空格,并读取下一个非空格字符,而在 zxh::string 类的实现中,
空格也应该被当作字符串的一部分来处理。
----------------------------------------------------------------------以下为修改后的代码:
在输入运算符 >> 的实现中,所以需要使用 in.get() 方法来读取字符
char ch = in.get();
while (ch!=' '&&ch!='\n') {
s.push_back(ch);
ch = in.get();
}但是这样写存在一个问题,假如我们要插入的字符很多,每次都要进行push_back频繁的扩容,于是我们将此代码进行修改,增加一个缓冲区,将输入的字符放到缓冲区里面,等缓冲区满了或者输入结束了,将缓冲区一并写入string里面,只扩一次容,可以提升效率。
使用案例:
/*
当你使用 std::cin >> s1 进行输入操作时,
编译器会自动将 std::cin 作为第一个参数传递给 operator>> 函数,
并将 s1 作为第二个参数传递给该函数来进行处理。
*/
std::cin >> s1;
s1.print();
字符串输出:
const char* c_str() const{
return _str;
}
有如下代码:
std::string s1("abcd");
s1 += '\0';
s1 += "efgh";
这俩的区别是什么?
cout << s1 << endl;
cout << s1.c_str() << endl;
* 打印结果为
abcdefgh
abcd
*
原因是c_str的实现为:
const char* c_str() const{
return _str;
}
在打印这个的时候,由于用到cout打印,遇到\0会结束
迭代器:
//迭代器--左闭右开
typedef char* iterator;
typedef const char* const_iterator;//这里的const修饰的是char *所指向的内容
iterator begin() {
return _str;
}
iterator end() {
return _str + _size ;
}
const_iterator begin() const{
return _str;
}
const_iterator end()const {
return _str + _size;
}
从这里可以看出迭代器底层为地址
begin指向字符串的首位置
end指向字符串的最后一个位置,即\0的位置
可以记为左闭右开