本章目的主要是实现四个默认成员函数(加深理解深浅拷贝)
我将代码分成了两个文件 头文件和一个test.c文件
头文件:
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;//写了这个 下文就不需要像我一样给每一个cout和cin标明命名空间了
namespace greenbyte//因为标准库里面也有string,所以我们需要自己弄一个命名空间,可以随便取名
{
class string
{
public:
string(const char* str="\0")//这里缺省值不是nullptr,因为strlen函数会直接对字符串解引用,我们需要防止对空指针解引用问题
:_str(new char[strlen(str)+1])//这里把空间开在堆上是为了方便扩容; 因为strlen函数遇到\0停止,所以需要+1
{
strcpy(_str, str);//开辟空间之后把数据拷贝过来 strcpy会拷贝反斜杠0
}
~string()
{
delete[]_str;
_str = nullptr;
}
string(const string& s)
:_str ( new char[strlen(s._str) + 1])//拷贝构造函数也是构造函数,同样建议使用初始化列表//这里是深拷贝,如有疑问请看最后一行
{
strcpy(_str, s._str);
}
string& operator=(const string& s)
{
if (this != &s)//这里需要讨论是不是s1=s1的调用情况,且比较‘=’左右是否是同一个对象必须比较地址,比较值是错误的
{
delete[]_str;//这里先释放_str原本指向的空间,否则这一块空间我们再也找不到了,会导致内存泄漏
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
return *this;//这里返回对象,是为了实现s2=s1=s3的连等于情况,想象一下,如果该函数没有返回值,那么s1=s3实现之后,s2=? 问号处什么也没有
}
}
const char* c_str()//该函数在string类里也有,可以返回指向_str的指针,此处可供我们打印
{
return _str;
}
int size()
{
return strlen(_str) ;
}
char& operator[](size_t pos)
{
return _str[pos];//这里return字符的引用,是为了达到可以通过'[]'修改字符的目的
}
private:
char* _str;
};
void test1()
{
string s1;//无参
std::cout << s1.c_str()<<std::endl;
string s2("hello");//传参
std::cout << s2.c_str()<<std::endl;
string s3;//拷贝构造
s3 = s2;
std::cout << s3.c_str()<<std::endl;
for (size_t i = 0; i < s3.size(); i++)//遍历-只读取数据
{
std::cout << s3[i] << " ";
}
cout << endl;
for (size_t i = 0; i < s3.size(); i++)//遍历-修改数据
{
s3[i] += 1;
std::cout << s3[i] << " ";
}
}
}
//默认拷贝函数和赋值运算符重载函数都是浅拷贝,也叫值拷贝,即一个字节一个字节地拷贝,会导致只把对象的_str这一指针拷贝了,如全是0x22ff4433,却没有重新开辟空间,导致两个对象的_str指向同一块空间,一动全动,这不是我们想要的
// 且最后两个对象同时调用析构函数,对同一块地址空间释放两次也会导致程序崩溃。解决方法即重新为左侧对象开辟空间
关于最后一行注释中的“同时”,这里修改一下,并不是同时,对象建立在栈上,符合先创建的后析构,我在这里想说的意思是,两个对象都会调用析构函数~
test.c文件:
#include"string.h"
int main()
{
greenbyte::test1();
return 0;
}
我将知识点写成了注释,欢迎初学者复制代码在本地ide里面查看(注释部分会更清晰)
如果有疑问或者想要与我讨论,欢迎评论~
下一章实现增删查改