学完c以后刚开始学c++,然后遇见了string,就有点好奇它的内部实现,在c里面都是用char型的数组来表示字符串的,而c++有了string来表示字符串, 我就想在c里面字符串以’\0’结尾,那么我是不是可以用’\0’给string的对象初始化,结果编译通过了,但是执行的时候出问题了
然后我产生了一个邪恶的想法,既然’\0’编译通过了,那我直接赋’A’这样的单字符看编译器会怎么样,结果是我傻了,编译器直接告诉我’A’不是char*类型不匹配
接着我就纳闷了,遇事不决就百度,然后复制以上错误找了半天都没有找到满意的答案,就决定来模拟实现一下string
于是我就想,这个string内部应该是动态分配空间来的
然后我就定义了一个类
#include <iostream>
#include <cstring>
#include <cstdlib> //为了使用 exit函数 以及system函数
using namespace std;
class HString
{
public:
HString();
HString(const char *);
/** 打印字符串 */
int StrPrint();
/** 返回字符串长度 */
int GetLength()
{
return m_length;
}
/** 重新赋值 */
int ReSet(const char *);
/** 重载=赋值 防止内存泄漏*/
const HString &operator=(const HString &);
/** 重载运算符= 使可以用字符串直接赋值 */
const HString &operator=(const char *r_str)
{
this->ReSet(r_str);
return *this;
}
virtual ~HString();
protected:
private:
char *m_str; //字符串 动态分配
int m_length; //字符串长度
int m_size; //当前申请空间的大小
void InitStr(const char *);//初始化字符串
};
以下是类的实现
#include "HString.h"
HString::HString()
{
m_str = nullptr;
m_length = 0;
m_size = 0;
}
void HString::InitStr(const char *initStr)
{
if(nullptr == initStr)
{
cout << "HString::InitStr(const char *): initialization failed by " << __FILE__ << __LINE__ << endl;
exit(4);
}
int len = strlen(initStr);
m_length = 0;
m_size = 0;
m_str = new char[len + 1];
if(nullptr != m_str)
{
m_length = len;
m_size = len + 1;
memset(m_str, 0, m_size);
memcpy(m_str, initStr, m_size);
}
}
HString::HString(const char *initStr)
{
InitStr(initStr);
}
/** 重新赋值 */
int HString::ReSet(const char *p_str)
{
if(nullptr == p_str)
{
cout << "HString::ReSet :The parameter is incorrect" << endl;
return 1;
}
int len = strlen(p_str);
if(len < 0)
{
cout << "HString::ReSet :get parameter is invailed" << endl;
return 2;
}
if(len < m_size - 1) //如果在分配的字符串小于当前分配空间长度 就直接赋值
{
memset(m_str, 0, m_size);
memcpy(m_str, p_str, len + 1);
m_length = len;
}
else //否则 空间再分配
{
if(nullptr == m_str) //如果对象字符串没有空间
{
InitStr(p_str); //进行初始化
}
else
{
char *p_temp = new char[len + 1];
if(nullptr == p_temp)
{
cout << "relloc failed!" << endl;
return 3;
}
else
{
memset(p_temp, 0, m_size);
delete m_str;
m_str = p_temp;
p_temp = nullptr;
m_size = len + 1;
m_length = len;
memcpy(m_str, p_str, m_size);
}
}
}
return 0;
}
/** 重载=赋值 防止内存泄漏*/
const HString & HString::operator=(const HString &other)
{
if(this != &other) //如果传入的不是自身
{
if(other.m_size <= this->m_size)
{
memset(this->m_str, 0, this->m_size);
memcpy(this->m_str, other.m_str, other.m_size);
this->m_length = other.m_length;
}
else
{
if(nullptr != this->m_str)
{
delete this->m_str;
}
this->m_str = new char[other.m_size];
memcpy(this->m_str, other.m_str, other.m_size);
this->m_length = other.m_length;
this->m_size = other.m_size;
}
}
return *this;
}
int HString::StrPrint()
{
if(0 == m_length || nullptr == m_str)
{
cout << "NULL" << endl;
return 1;
}
cout << m_str << endl;
return 0;
}
HString::~HString()
{
if(nullptr != m_str)
{
delete m_str;
m_str = nullptr;
}
}
在尽我所能的模拟string的过程中,我发现了问题的所在,由于要实现重新赋值,所以一定要记录字符串长度,为了不内存泄漏以及空间的合法使用,就要记录当前申请空间的大小,量字符串长度要用到strlen函数,我在调试时发现程序死在strlen
然后我就想如果一个指针指向’\0’,那么它的地址是什么,因为我在初始对象传入的是char型的指针嘛,而我如果传’A’这样的单字符,编译器是会直接报错的,因为类型不匹配,而传’\0’却可以,
于是
我终于发现了问题的所在,原来’\0’的地址就是nullptr
对strlen传入一个NULL指针,会由于NULL指向的空间不可被访问,而导致程序死掉,于是我发现我的InitStr方法没有对参数进行判断,然后马上改正,修改成了以上代码那样。
然后在给我自己写的类的对象赋值成’\0’
然后
发现在代码
14行处跳出
然后我就明白了
terminate called after throwing an instance of ‘std::logic_error’
what(): basic_string::_M_construct null not valid
错误的意思
可喜可贺~我的问题就那么解决了
这个类在申请堆区的空间的时候应该与string不一样,由此可能存在一些效率问题,因为我只是想探究一下这个错误,所以还有一些功能没有实现,请大佬轻喷ヾ(≧▽≦)o*
printf(“谢谢观看233!”);