C++模拟实现string类

本文详细探讨了C++中string类的设计,包括const修饰、内存分配、拷贝构造、浅拷贝与深拷贝的区别,流重载优化,以及迭代器的使用。重点介绍了如何处理字符串存储、容量管理以及避免无限递归等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

注:写本博客的目的接在记录学习过程,学习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对象。

思考:

还请区别浅拷贝与深拷贝

在这篇文章中我有详细介绍,需要的看这边:

c++中class类中的四大常用默认函数-优快云博客

一、我们将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进行拷贝,从而一直进行对拷贝构造的无限递归。

关于拷贝构造的无限递归,这篇文章中也有介绍,需要的还可以参考如下文章:

c++中class类中的四大常用默认函数-优快云博客

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的位置

可以记为左闭右开

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蠢 愚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值