C++ string底层完成逻辑(与类知识点结合)string——下

string初始工作(默认构造、析构、初始化……)

全文侧重于,string底层实现原理、并结合class类知识点重合混杂之后可能出现的问题、以及修正方法。

1.string篇

string.h

#pragma once
//头文件的包含{string、iostream、strcpy}
#define _CRT_SECURE_NO_WARNINGS
#include<string>
  #include<iostream>
    using namespace std;
    namespace bit
    {
    //定义类string
    class string
    {
    public:
    //构造函数——无参
    string()
    :_str(nullptr)
    , _size(0)
    , _capacity(0)
    {}
    //构造函数——有参
    string(const char* str)
    :_str(new char[strlen(str) + 1])
    , _size(strlen(str))
    , _capacity(strlen(str))
    /*
    Q2::
    1.string3个初始化列表初始化 进行了三次strlen接口调用,极大浪费效率
    2.这里初始化列表的顺序和私有成员函数定义的顺序保持一致,错误的写法为
    string(const char* str)
    :_size(strlen(str))
    ,_str(new char[_size + 1])
    , _capacity(_size)
    修改:
    1.只将_size 走初始化列表,_str 和 _capacity 走内部函数默认初始化
    */
    {}
    //构造c_str
    char* c_str()
    {
    return _str;
    }
    //析构函数
    ~string()
    {
    delete[]_str;
    _str = nullptr;
    _size = 0;
    _capacity = 0;
    }
    //private私有成员
    private:
    char* _str;
    size_t _size;
    size_t _capacity;
    };
    }

test.cpp

//头文件包含
#include"string.h"
//测试样例1
namespace bit
{
void test_string1()
{
string s1;
cout << s1.c_str() << endl;
/*Q1::
1.这里输出的s1.c_str程序会发生崩溃
c_str是C语言中的语法,其侧重的是传值返回,
而原始的_str内存空间为空,传值返回必然伴随解引用,因此会发生越界访问
修改:
1.将原始空间设置缺省值 空间指定为1 存入\0
*/
//string s2("hello world");
//cout << s2.c_str() << endl;
}
}
//main函数
int main()
{
bit::test_string1();
return 0;
};

string.cpp

//无

2.string修正篇

string.h

#pragma once
//头文件的包含{string、iostream、strcpy}
#define _CRT_SECURE_NO_WARNINGS
#include<string>
  #include<iostream>
    using namespace std;
    namespace bit
    {
    //定义类string
    class string
    {
    public:
    //构造函数——无参
    string()
    :_str(new char[1] {'\0'})
    , _size(0)
    , _capacity(0)
    {}
    //构造函数——有参
    string(const char* str)
    : _size(strlen(str))
    {
    _str = new char[_size + 1];
    _capacity = _size;
    strcpy(_str, str);
    }
    //构造c_str
    char* c_str()
    {
    return _str;
    }
    //析构函数
    ~string()
    {
    delete[]_str;
    _str = nullptr;
    _size = 0;
    _capacity = 0;
    }
    //private私有成员
    private:
    char* _str;
    size_t _size;
    size_t _capacity;
    };
    }

test.cpp

//包含头文件
#include "string.h"
//定义命名空间
namespace bit
{
//特使样例1
void test_string1()
{
std::string s1;
cout << s1.c_str() << endl;
string s2("hello world");
cout << s2.c_str() << endl;
}
}
//mian
int main()
{
bit::test_string1();
return 0;
}

string.cpp

//无

3.修正string 添加析构、声明定义分离

string.h

#pragma once
//头文件的包含{string、iostream、strcpy}
#define _CRT_SECURE_NO_WARNINGS
#include<string>
  #include<iostream>
    using namespace std;
    namespace bit
    {
    //定义类string
    class string
    {
    public:
    /*
    //构造函数——无参
    string()
    :_str(new char[1] {'\0'})
    , _size(0)
    , _capacity(0)
    {}
    */
    //Q1:修正一、
    //构造函数——有参、无参混合到一起
    //设置缺省值、若为空则使用缺省值、若不为空贼使用str传入的string
    string(const char* str = "");
    /* : _size(strlen(str))
    {
    _str = new char[_size + 1];
    _capacity = _size;
    strcpy(_str, str);
    }
    */
    //构造c_str
    char* c_str();
    /*
    {
    return _str;
    }
    */
    //Q2:修正二、
    //析构函数
    ~string();
    /*
    {
    delete[]_str;
    _str = nullptr;
    _size = 0;
    _capacity = 0;
    }
    */
    //private私有成员
    private:
    char* _str;
    size_t _size;
    size_t _capacity;
    };
    }

test.cpp

//头文件包含
#include"string.h"
//测试样例1
namespace bit
{
void test_string1()
{
string s1;
cout << s1.c_str() << endl;
string s2("hello world");
cout << s2.c_str() << endl;
}
}
//main函数
int main()
{
bit::test_string1();
return 0;
};

string.cpp

//Q3:修正三
//声明定义分离
#include"string.h"
namespace bit
{
string::string(const char* str)  //声明定义分离不能同时设置缺省值
: _size(strlen(str))
{
_str = new char[_size + 1];
_capacity = _size;
strcpy(_str, str);
}
//构造c_str
char* string::c_str()
{
return _str;
}
//析构
string::~string()
{
delete[]_str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
}

这里面有一个需要注意的知识点 就是声明和定义分离时不能同时设置缺省值
定义的时候都需要指名属于string::域 相对来说比较麻烦

4.string修正后——模板

后续类功能都是在此模板上添加,修改

string.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS //strcpy
#include<string>
  #include<iostream>
    using namespace std;
    namespace bit
    {
    //定义类string
    class string
    {
    public:
    //构造函数——有参、无参混合到一起
    string(const char* str = "");//设置缺省值、若为空则使用缺省值、若不为空贼使用str传入的string
    //构造c_str
    char* c_str();
    //析构函数
    ~string();
    //private私有成员
    private:
    char* _str;size_t _size;size_t _capacity;
    };
    }

test.cpp

//头文件包含
#include"string.h"
//测试样例1
namespace bit
{
void test_string1()
{
string s1;
cout << s1.c_str() << endl;
string s2("hello world");
cout << s2.c_str() << endl;
}
}
//main函数
int main()
{
bit::test_string1();
return 0;
};

string.cpp

//声明定义分离
#include"string.h"
namespace bit
{
//默认构造
string::string(const char* str)  //声明定义分离不能同时设置缺省值
: _size(strlen(str))
{
_str = new char[_size + 1];
_capacity = _size;
strcpy(_str, str);
}
//构造c_str
char* string::c_str()
{
return _str;
}
//析构
string::~string()
{
delete[]_str;_str = nullptr;_size = 0;_capacity = 0;
}
}

string库功能实现,底层代码(非万能)

一、核心实现——operator[]重载、范围for(包括const修饰和非const修饰)

string.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS //strcpy
#include<string>
  #include<iostream>
    #include<assert.h>
      using namespace std;
      namespace bit
      {
      //定义类string
      class string
      {
      public:
      //构造函数——有参、无参混合到一起
      string(const char* str = "");//设置缺省值、若为空则使用缺省值、若不为空贼使用str传入的string
      //构造c_str
      char* c_str()const;
      //析构函数
      ~string();
      //operator[]的实现 
      char& operator[](int pos)const;
      //实现auto
      typedef char* iterator;
      iterator begin()
      {
      return _str;
      }
      iterator end()
      {
      return _str + _size;
      }
      //实现const auto
      typedef const char* const_iterator;
      const_iterator begin()const
      {
      return _str;
      }
      const_iterator end()const
      {
      return _str + _size;
      }
      //private私有成员
      private:
      char* _str; size_t _size; size_t _capacity;
      };
      }

string.cpp

//声明定义分离
#include"string.h"
namespace bit
{
//默认构造
string::string(const char* str)  //声明定义分离不能同时设置缺省值
: _size(strlen(str))
{
_str = new char[_size + 1];
_capacity = _size;
strcpy(_str, str);
}
//构造c_str
char* string::c_str()const
{
return _str;
}
//operator[]
char& string::operator[](int pos)const
{
assert(pos < _size);
return _str[pos];
}
//析构
string::~string()
{
delete[]_str; _str = nullptr; _size = 0; _capacity = 0;
}
}

test.cpp

//头文件包含
#include"string.h"
//测试样例1
namespace bit
{
void test_string1()
{
string s1;
cout << s1.c_str() << endl;
string s2("hello world");
cout << s2.c_str() << endl;
s2[0] = 'x';
cout << s2.c_str() << endl;
//范围for实现
string s3("My name is ljy.");
for (auto ch:s3)
{
cout << ch << "-";
}
cout << endl;
const string s4("What's your name?");
for (auto c_ch : s4)
{
cout << c_ch << "-";
}
cout << endl;
}
}
//main函数
int main()
{
bit::test_string1();
return 0;
};

二、核心实现 string,reserve、push_back、append、+=、insert插入(单个字符)

string.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS //strcpy
#include<string>
  #include<iostream>
    #include<assert.h>
      #include<algorithm>
        using namespace std;
        namespace bit
        {
        //定义类string
        class string
        {
        public:
        //构造函数——有参、无参混合到一起
        string(const char* str = "");//设置缺省值、若为空则使用缺省值、若不为空贼使用str传入的string
        //构造c_str
        char* c_str()const;
        //析构函数
        ~string();
        //operator[]的实现 
        char& operator[](int pos)const;
        //实现auto
        typedef char* iterator;
        iterator begin();
        iterator end();
        //实现const auto
        typedef const char* const_iterator;
        const_iterator begin()const;
        const_iterator end()const;
        //push_back 的实现
        void push_back(char ch);
        //reserve 的实现
        void reserve(size_t n);
        //append的实现
        void append(const char* str);
        //+=字符串的实现
        string& operator+=(const char* str);
        //+=单个字符的实现
        string& operator+=(char ch);
        //private私有成员
        //insert函数
        void insert(size_t pos, char ch);
        private:
        char* _str; size_t _size; size_t _capacity;
        //这里少一个npos的定义
        };
        }

string.cpp

//声明定义分离
#include"string.h"
namespace bit
{
//默认构造
string::string(const char* str)  //声明定义分离不能同时设置缺省值
: _size(strlen(str))
{
_str = new char[_size + 1];
_capacity = _size;
strcpy(_str, str);
}
//构造c_str
char* string::c_str()const
{
return _str;
}
//operator[]
char& string::operator[](int pos)const
{
assert(pos < _size);
return _str[pos];
}
//析构
string::~string()
{
delete[]_str; _str = nullptr; _size = 0; _capacity = 0;
}
//auto
string::iterator string::begin()
{
return _str;
}
string::iterator string::end()
{
return _str + _size;
}
//const auto
string::const_iterator string::begin()const
{
return _str;
}
string::const_iterator string::end()const
{
return _str + _size;
}
//reserve
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* new_str = new char[n + 1];
strcpy(new_str, _str);
delete[]_str;
_str = new_str;
_capacity = n;
}
}
//push_back
void string::push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
//append
void string::append(const char* str)
{
int len = strlen(str);
int new_capacity = max(_size + len, _capacity * 2);
reserve(new_capacity);
strcpy(_str + _size, str);
_size = _size + len;
}
//+=字符串
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
//+字符
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
//insert
void string::insert(size_t pos, char ch)
{
assert(pos < _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = _size;
while (end >= (int)pos)
{
_str[end + 1] = _str[end];
end--;
}
_str[pos] = ch;
}
}

test.cpp

//头文件包含
#include"string.h"
//测试样例1
namespace bit
{
void test_string1()
{
string s1;
cout << s1.c_str() << endl;
string s2("hello world");
cout << s2.c_str() << endl;
s2[0] = 'x';
cout << s2.c_str() << endl;
//范围for实现
string s3("My name is ljy.");
for (auto ch:s3)
{
cout << ch << "-";
}
cout << endl;
const string s4("What's your name?");
for (auto c_ch : s4)
{
cout << c_ch << "-";
}
cout << endl;
string s5("My name is ljy");
s5.push_back('.');
cout << s5.c_str() << endl;
s5.append(" What's your name?");
cout << s5.c_str() << endl;
string s6("hello");
s6 += " lijiaye";
s6 += '.';
cout << s6.c_str() << endl;
string s7("What' your name?");
s7.insert(5, 's');
cout << s7.c_str() << endl;
}
}
//main函数
int main()
{
bit::test_string1();
return 0;
};

核心实现insert(插入一段数据),erase删除,string拷贝构造,operator=运算符重载。

string.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS //strcpy
#include<string>
  #include<iostream>
    #include<assert.h>
      using namespace std;
      namespace bit
      {
      //定义类string
      class string
      {
      public:
      //构造函数——有参、无参混合到一起
      string(const char* str = "");//设置缺省值、若为空则使用缺省值、若不为空贼使用str传入的string
      //构造c_str
      char* c_str()const;
      //operator[] 访问
      char& operator[](size_t pos)const;
      //析构函数
      ~string();
      //实现auto
      typedef char* iterator;
      typedef const char* const_iterator;
      iterator begin();
      iterator end();
      const_iterator begin()const;
      const_iterator end()const;
      //reserve实现
      void reserve(size_t n);
      //push_back 实现
      void push_back(char ch);
      //append的实现
      void append(const char* str);
      //insert的实现
      void insert(size_t pos, char ch);
      void insert(size_t pos, const char* s);
      //erase的实现
      void erase(size_t pos, size_t len);
      //拷贝构造的实现string
      string(const string& str);
      string& operator=(const string& str);
      //private私有成员
      private:
      char* _str;size_t _size;size_t _capacity;
      };
      }

string.cpp

//声明定义分离
#include"string.h"
namespace bit
{
//默认构造
string::string(const char* str)  //声明定义分离不能同时设置缺省值
: _size(strlen(str))
{
_str = new char[_size + 1];
_capacity = _size;
strcpy(_str, str);
}
//构造c_str
char* string::c_str()const
{
return _str;
}
//operator[]
char& string::operator[](size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
//析构
string::~string()
{
delete[]_str; _str = nullptr; _size = 0; _capacity = 0;
}
//auto 实现
string::iterator string::begin()
{
return _str;
}
string::iterator string::end()
{
return _str + _size;
}
string::const_iterator string::begin()const
{
return _str;
}
string::const_iterator string::end()const
{
return _str + _size;
}
void string::reserve(size_t n)
{
//单反设计空间的开辟,那么就必须引入新的指针,拷贝旧指针,清空旧的指针,旧指针指向新指针,更新容量
if (n > _capacity)
{
char* tmp = new char[n + 1];//CP2
strcpy(tmp, _str);
delete[]_str;
_str = tmp;
_capacity = n; //CP2
}
}
//push_back 实现
void string::push_back(char ch)
{
char* tmp = new char[_size + 2];
strcpy(tmp, _str);
delete[]_str;
_str = tmp;
_str[_size] = ch;
_size = _size + 1;
_str[_size] = '\0';
//CP1new char +2 的原因  和最后是垃圾值的原因
}
//append的实现
void string::append(const char* str)
{
int len = strlen(str);
reserve(max(_size + len, _capacity * 2));
//strcpy(_str + _size, str);
//_size = _size + len;
//Q3  为什么用for 就不行,而且为什么还需要后面加\0
//核心是 strcpy与\0有着密切关系
for (int i = 0; i < len; i++)
{
_str[_size] = str[i];
_size++;
}
_str[_size] = '\0';
}
//insert的实现
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
int end = _size;
while (end >= (int)pos)
{
_str[end + 1] = _str[end];
end--;
}
_str[pos] = ch;
}
//insert实现——传字符串
void string::insert(size_t pos, const char* s)
{
int len = strlen(s);
reserve(max(_size + len, _capacity * 2));
int end = _size;
while (end >= (int)pos)
{
_str[end + len] = _str[end];
end--;
}
strncpy(_str + pos, s, len); //Q25这里为什么写成_str[pos]就不对呢?   为什么要写成strncpy呢?  strcpy为什么就不对呢?
}
//erase删除字符串
void string::erase(size_t pos, size_t len)
{
if (pos + len >= _size - 1)
{
_str[pos] = '\0';
_size = pos; //Q25调整空间大小
}
else
{
strcpy(_str + pos, _str + pos + len); //这里会把\0也拷贝过去
_size = _size - len;
}
}
//深拷贝构造
string::string(const string& str)
{
_str = new char[str._capacity + 1];  //开辟空间 开辟_capacity
strcpy(_str, str._str);
_size = str._size;
_capacity = str._capacity;
}
//运算符重载=
string& string::operator=(const string& str)
{
char* tmp = new char[str._capacity + 1];
strcpy(tmp, str._str);
delete[]_str;
_str = tmp;  //Q25这里为什么不用拷贝构造?
_size = str._size;
_capacity = str._capacity;
return *this;
}
}

test.cpp

//头文件包含
#include"string.h"
//测试样例1
namespace bit
{
void test_string1()
{
string s1;
cout << s1.c_str() << endl;
string s2("hello world");
cout << s2.c_str() << endl;
s2[2] = 'x';
cout << s2.c_str()<<endl;
string s3("My name is ljy.");
for (auto ch : s3)
{
cout << ch;
}
cout << endl;
string s4("lj");
s4.push_back('y');
cout << s4.c_str() << endl;
string s5("My name is ljy.");
s5.append(" What's your name?");
cout << s5.c_str() << endl;
string s6("My ame is ljy.");
s6.insert(3, 'n');
cout << s6.c_str() << endl;
string s7("My name is");
s7.insert(10, " ljy.");
cout << s7.c_str() << endl;
string s8("Hello world");
s8.erase(6, 5);
cout << s8.c_str() << endl;
string s9("My namename is ljy.");
s9.erase(3, 4);
cout << s9.c_str() << endl;
}
}
//main函数
int main()
{
bit::test_string1();
return 0;
};

三、核心实现 深拷贝、substr、find、>> << 各种运算符重载、getline

string.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS //strcpy
#include<string>
  #include<iostream>
    #include<assert.h>
      using namespace std;
      namespace bit
      {
      //定义类string
      class string
      {
      public:
      friend std::ostream& operator<<(std::ostream& out, const string& str);
      friend istream& operator>>(istream& in, const string& str);
      friend istream& getline(istream& in, string& s, char delim);
      //构造函数——有参、无参混合到一起
      string(const char* str = "");//设置缺省值、若为空则使用缺省值、若不为空贼使用str传入的string
      //构造c_str
      char* c_str()const;
      //operator[] 访问
      char& operator[](size_t pos)const;
      //析构函数
      ~string();
      //实现auto
      typedef char* iterator;
      typedef const char* const_iterator;
      iterator begin();
      iterator end();
      const_iterator begin()const;
      const_iterator end()const;
      //reserve实现
      void reserve(size_t n);
      //push_back 实现
      void push_back(char ch);
      //+=字符串的实现
      string& operator+=(const char* str);
      //+=单个字符的实现
      string& operator+=(char ch);
      //append的实现
      void append(const char* str);
      //insert的实现
      void insert(size_t pos, char ch);
      void insert(size_t pos, const char* s);
      //erase的实现
      void erase(size_t pos, size_t len);
      //深拷贝构造的实现string
      string(const string& str);
      //operator重载=
      string& operator=(const string& str);
      //find-单个字符
      size_t find(char ch, size_t pos);
      //find—字符串
      size_t find(const char* str, size_t pos);
      //clear
      void clear()
      {
      _str[0] = '\0';
      _size = 0;
      }
      //substr
      string substr(size_t pos, size_t len);
      //private私有成员
      private:
      char* _str;size_t _size;size_t _capacity;
      const static int npos = -1;
      };
      }
      //运算符<<重载
      ostream& operator<<(ostream& out, const string& str);
      //运算符>>重载
      istream& operator>>(istream& in, const string& str);
      //getline
      istream& getline(istream& in, string& s, char delim);

string.cpp

//声明定义分离
#include"string.h"
namespace bit
{
//默认构造
string::string(const char* str)  //声明定义分离不能同时设置缺省值
: _size(strlen(str))
{
_str = new char[_size + 1];
_capacity = _size;
strcpy(_str, str);
}
//构造c_str
char* string::c_str()const
{
return _str;
}
//operator[]
char& string::operator[](size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
//析构
string::~string()
{
delete[]_str; _str = nullptr; _size = 0; _capacity = 0;
}
//auto 实现
string::iterator string::begin()
{
return _str;
}
string::iterator string::end()
{
return _str + _size;
}
string::const_iterator string::begin()const
{
return _str;
}
string::const_iterator string::end()const
{
return _str + _size;
}
void string::reserve(size_t n)
{
//单反设计空间的开辟,那么就必须引入新的指针,拷贝旧指针,清空旧的指针,旧指针指向新指针,更新容量
if (n > _capacity)
{
char* tmp = new char[n + 1];//CP2
strcpy(tmp, _str);
delete[]_str;
_str = tmp;
_capacity = n; //CP2
}
}
//push_back 实现
void string::push_back(char ch)
{
char* tmp = new char[_size + 2];
strcpy(tmp, _str);
delete[]_str;
_str = tmp;
_str[_size] = ch;
_size = _size + 1;
_str[_size] = '\0';
//CP1new char +2 的原因  和最后是垃圾值的原因
}
//+=字符串
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
//+=字符
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
//append的实现
void string::append(const char* str)
{
int len = strlen(str);
reserve(max(_size + len, _capacity * 2));
//strcpy(_str + _size, str);
//_size = _size + len;
//Q3  为什么用for 就不行,而且为什么还需要后面加\0
//核心是 strcpy与\0有着密切关系
for (int i = 0; i < len; i++)
{
_str[_size] = str[i];
_size++;
}
_str[_size] = '\0';
}
//insert的实现
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
int end = _size;
while (end >= (int)pos)
{
_str[end + 1] = _str[end];
end--;
}
_str[pos] = ch;
}
//insert实现——传字符串
void string::insert(size_t pos, const char* s)
{
int len = strlen(s);
reserve(max(_size + len, _capacity * 2));
int end = _size;
while (end >= (int)pos)
{
_str[end + len] = _str[end];
end--;
}
strncpy(_str + pos, s, len); //Q25这里为什么写成_str[pos]就不对呢?   为什么要写成strncpy呢?  strcpy为什么就不对呢?
}
//erase删除字符串
void string::erase(size_t pos, size_t len)
{
if (pos + len >= _size - 1)
{
_str[pos] = '\0';
_size = pos; //Q25调整空间大小
}
else
{
strcpy(_str + pos, _str + pos + len); //这里会把\0也拷贝过去
_size = _size - len;
}
}
//深拷贝构造
string::string(const string& str)
{
_str = new char[str._capacity + 1];  //开辟空间 开辟_capacity
strcpy(_str, str._str);
_size = str._size;
_capacity = str._capacity;
}
//运算符重载=
string& string::operator=(const string& str)
{
char* tmp = new char[str._capacity + 1];
strcpy(tmp, str._str);
delete[]_str;
_str = tmp;  //Q25这里为什么不用拷贝构造?
_size = str._size;
_capacity = str._capacity;
return *this;
}
//运算符重载<<
ostream& operator<<(ostream& out, const string& str)
{
for (auto ch : str)
{
cout << ch << "";
}
return out;
}
//运算符重载>>  
istream& operator>>(istream& in, string& str) //Q9/28 >>ch  和scanf  和in.get的区别
{
str.clear();
char buff[256];
int i = 0;
char ch;
//in>>ch;
ch = in.get();
while (ch != '\n' && ch != ' ')
{
buff[i++] = ch;
if (i == 255)
{
buff[i] = '\0';
str += buff;
i = 0;
}
//str += ch;
//in>>ch;
ch = in.get();
}
if (i > 0)
{
buff[i] = '\0';
str += buff;
}
return in;
}
//find 查找单个字符
size_t string::find(char ch, size_t pos)
{
assert(pos < _size);
int i = pos;
for (i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
//find查找字符串
size_t string::find(const char* str, size_t pos)//Q9/28 char ch 、const char* s 、string&str
{
assert(pos < _size);
char* ptr = strstr(_str+pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr-_str;
}
}
//substr
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
if (len == npos || len > _size - pos)
{
len = _size - pos;
}
string sub;
sub.reserve(len);
for (size_t i= 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;
}
istream& getline(std::istream& in, string& s, char delim)
{
s.clear();
char buff[256];
int i = 0;
char ch;
//in >> ch;
ch = in.get();
while (ch != delim)
{
buff[i++] = ch;
if (i == 255)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
//const size_t string::npos = -1;
}
}

test.cpp

//头文件包含
#include"string.h"
//测试样例1
namespace bit
{
void test_string1()
{
string s1;
cout << s1.c_str() << endl;
string s2("hello world");
cout << s2.c_str() << endl;
s2[2] = 'x';
cout << s2.c_str()<<endl;
string s3("My name is ljy.");
for (auto ch : s3)
{
cout << ch;
}
cout << endl;
string s4("lj");
s4.push_back('y');
cout << s4.c_str() << endl;
string s5("My name is ljy.");
s5.append(" What's your name?");
cout << s5.c_str() << endl;
string s6("My ame is ljy.");
s6.insert(3, 'n');
cout << s6.c_str() << endl;
string s7("My name is");
s7.insert(10, " ljy.");
cout << s7.c_str() << endl;
string s8("Hello world");
s8.erase(6, 5);
cout << s8.c_str() << endl;
string s9("My namename is ljy.");
s9.erase(3, 4);
cout << s9.c_str() << endl;
char ch = 'l';
string s10("My name is ljy.");
size_t insert1 = s10.find(ch, 5);
cout << insert1 << endl;
string s11("My name is ljy.");
size_t insert2 = s11.find("ljy", 5);
cout << insert2 << endl;
s11.clear();
cout << s11 << endl;
string s12("My name is ljy.");
cout << s12 << endl;
string s13;
s13 = s12.substr(3, 4);
cout << s13 << endl;
string s1, s2("xxxxxx");
cin >> s1 >> s2;
cout << s1 << endl;
cout << s2 << endl;
}
}
//main函数
int main()
{
bit::test_string1();
return 0;
};

补充——深拷贝实现、swap和strcpy的踩坑点

#include<iostream>
  #include<string>
    #include<assert.h>
      using namespace std;
      //命名空间
      namespace bit
      {
      class string
      {
      public:
      //一、分析参数传入的是一个地址还是实参本身、这里是地址
      string(const char* str = "")
      {
      if (str == nullptr)
      {
      assert(false);
      return;
      }
      //二、分析new char 需要的是一个具体的数值大小,这个大小需要strlen来实现,strlen传入的是地址而非实参,因此这里是strlen(str)
      _str = new char(strlen(str) + 1);  //这里传入的是指针 ,而下面传入的是实参本身,因此两者不一样
      strcpy(_str, str);
      }
      //_str new char 空间
      //strcpy直接拷贝
      //	:_size(strlen(str))
      //{
      //	_str = new char[_size + 1];
      //	_capacity = _size + 1;
      //	strcpy(_str, str);
      //}
      string(const string& s) //传入的是s本身 而非指针,且不可变
      //初始化
      //三、分析分析参数传入的是一个地址还是实参本身、这里是实参本身,因此strlen要访问私有成员变量
      :_str(new char(strlen(s._str) + 1)) //要想再类里面使用strlen、_size _capacity注意成员变量是个很重要的角色
      //strcpy直接拷贝
      {
      strcpy(_str, s._str);
      }
      //{
      //	char* tmp = new char[s._size + 1];  //这里不能加括号,加上括号就是函数
      //	memcpy(tmp, s._str, _size + 1);
      //	delete[]s;
      //	_str = tmp;
      //	_size = tmp._size;
      //	_capacity = _size + 1;
      //}
      //四、分析重载运算符里面都有一个this指针,这里我们一定要传出this指针,运算符重载的共性
      string& operator=(const string& s)
      {
      if (this != &s)
      {
      delete[]_str;
      _str = new char(strlen(s._str) + 1);
      strcpy(_str, s._str);
      return *this;
      }
      }
      //这里默认有一个*this
      //先删除_str
      //_str new char新空间
      //strcpy拷贝
      //return *this
      ~string()
      {
      delete[]_str;
      _str = nullptr;
      }
      //{
      //	//delete[]
      //	//_str指向新空间
      //	delete[]_str;
      //	_size = 0;
      //	_capacity = 0;
      //}
      private:
      char* _str;
      };
      }
      int main()
      {
      return 0;
      }

通过swap实现

#include<string>
  #include<iostream>
    using namespace std;
    namespace ljy
    {
    class string
    {
    public:
    string(const char* str = "")//有参构造 无参构造
    : _str(new char(strlen(str) + 1))
    {
    strcpy(_str, str);
    }
    string(string& s)
    {
    //char* tmp = new char(strlen(s._str) + 1);
    string tmp(s._str);
    swap(_str, tmp._str);
    }
    string& operator=(string& s)
    {
    string tmp(s);
    swap(_str, tmp._str);
    }
    ~string()
    {
    delete[]_str;
    _str = nullptr;
    }
    private:
    char* _str;
    };
    }
    int main()
    {
    return 0;
    }

详解,swap的好处
在这里插入图片描述

swap应注意的点
swap适用场景:

1.swap适用于2个类之间的交换,(swap不仅交换值,还会交换成员地址)
2.swap适用于 2个已有成员,而非初始化(构造函数不适用swap的原因)
3.当我们交换完之后 还需要析构,还需要判断是否自引用 这时候用swap会非常非常方便。

因为swap 不仅不会进行深拷贝那么复杂的运算,他还会调用完之后自动析构,(采用创建第三变量的方法),自引用的问题也可以很好的解决。

总结
在这里插入图片描述

变式

在这里插入图片描述

#define _CRT_SECURE_NO_WARNINGS
//头文件
#include<string>
  #include<cctype>
    #include<iostream>
      #include<assert.h>
        using namespace std;
        class Student
        {
        public:
        // 1. 构造函数:初始化姓名和年龄
        Student(const char* name = "", int age = 0)
        {
        _age = age;
        _name = new char[strlen(name) + 1];
        // 给姓名分配内存并拷贝
        strcpy(_name, name);
        }
        // 2. 析构函数:释放姓名的内存
        ~Student()
        {
        delete[]_name;
        _name = nullptr;
        int age = 0;
        }
        // 3. 拷贝构造函数:用已有学生复制新学生
        Student(const Student& stu)
        {
        // 新学生的姓名也需要独立内存
        _age = stu._age;
        // 为新对象分配自己的内存
        _name = new char[strlen(stu._name) + 1];
        // 复制内容
        strcpy(_name, stu._name);
        }
        // 4. 赋值运算符:把s的信息赋给当前对象
        Student& operator=(Student& stu)
        {
        // 防止自我赋值(比如 s1 = s1;)
        if (this != &stu)
        {
        // 复制年龄和姓名
        delete[]_name;
        _age = stu._age;
        _name = new char[strlen(stu._name) + 1];
        strcpy(_name, stu._name);
        return *this;
        }
        }
        // 5. 打印函数:输出学生信息
        void print() const
        {
        std::cout << "姓名:" << _name << ",年龄:" << _age << std::endl;
        }
        private:
        char* _name; // 姓名(动态分配内存)
        int _age;    // 年龄
        // 测试函数
        };
        void testStudent() {
        std::cout << "===== 创建 s1 =====" << std::endl;
        Student s1("张三", 18);
        s1.print();
        std::cout << "\n===== 拷贝构造创建 s2 =====" << std::endl;
        Student s2(s1);
        s2.print();
        std::cout << "\n===== 赋值创建 s3 =====" << std::endl;
        Student s3;
        s3 = s2;
        s3.print();
        std::cout << "\n===== 测试结束,对象即将按顺序释放 =====" << std::endl;
        }
        int main() {
        testStudent();
        return 0;
        }

知识点总结

一、&既然修改原始值,那么是否还会调用默认构造?

问题:
针对于我们上述的代码,既然我们定义了

char& string::operator[](size_t pos)

但是原构造函数 成员内部有

strcpy(_str,str);

既然都有引用了,是否还调用strcpy重新拷贝,如果拷贝那不就是纯粹浪费空间和时间?如果不拷贝,为什么,难道初始化不走构造函数嘛?

先下结论,operator[] 的调用不会触发构造函数,strcpy 也不会被再次执行
原因是 我们只有在定义 string s1(“hello world”)这个时候他才会调用构造函数,而调用完构造函数之后,他就会存储起来。当我们operator[]时候,就会引用那么我们存储起来的函数。

解释:

对象的生命周期:从生到死一个对象的生命周期可以分为三个阶段:
1.诞生 (Construction):当你创建一个对象时(比如 bit::string s(“hello world”);),构造函数被唯一一>次调用。此时,对象的内存被分配,成员变量被初始化。
2.存活 (Lifetime):对象被创建后,就进入了它的 “存活期”。在这个期间,你可以调用它的各种成员函数来使用或修改它,比如 s.c_str()、s[0] = ‘H’ 等。这个阶段不会再调用构造函数。
3.销毁 (Destruction):当对象即将被销毁时(比如离开它的作用域),它的析构函数被唯一一次调用。此时,对象会清理它所拥有的资源(比如 delete[] _str;)。

二、构造函数就一个 但是却能string 也能const string?

我们的构造函数只写了一个 string(const char* str=“”)
但是我们可以用它来创建两种对象
1.bit::string s1(“hello”)//非const 对象
2.const bit::string s2 (“world”)//const对象

这是因为 构造函数它的作用只是限定而非真正的定义就像string(const char* str=“”),如果传入的有参 那么他就不会取缺省值。如果传入的没参 那它就会自动取缺省值。 const 同理,如果定义的为非const那么他就是一个非const对象。如果定义的为const 那么这里的const 就起到作用。

因此这里可以看出另一个问题就是我上述代码在函数定义和声明时,
c_str() 和 operator[]都没有添加后缀const 也就是他们都是非const 成员函数。那么倘若我们这样定义就会报错,原因就是c_str 和operator都是非const成员函数

const bit::string cs("hello");
cout << cs.c_str() << endl; // 编译错误!'c_str' is not a const member function
cs[0] = 'H';                // 编译错误!'operator[]' is not a const member function

最好的做法就是都加上const,但是前提是你不修改原函数
这里加上const 还有一点好处就是 const函数可以访问非const 函数和const函数。
但是如果不加const,那么只能访问非const函数
总结:

1.构造不分const 和非const 因此在设置缺省值建议加上const
2.1非const 函数无法访问const修饰的函数 但是可以修改
2.2const函数可以访问const修饰的函数,和非const修饰的函数。我们在书写时往往都加上const
3.const书写是C++编程中一个非常重要的规范

三、.1_capcity、_size书写与扩容问题、具体如何区分什么时候该括_capacity_size 以及扩多少?

首先我们来聊聊,他们与/0的关系

_capacity、_size 以及strlen都不包括\0,那么什么时候才会涉及\0?
答案就是在在用new 创建空间时,这个时候如果创建空间必须要 创建 capacity+1的空间,因为需要储存\0。所以需要注意 在涉及insert插入一个字符或者push_back 时候 _capacity 往往需要+=2

_capacity _size strlen 什么时候需要扩容 _capacity? 什么时候需要更新 _size? strlen有什么用?

strlen 永远不需要更新 扩容,并且 strlen没有_capacity 和 _size 使用的相对频繁。 strlen的功能完全可以被_size 替代。
补充:

strlen 是一个库函数,用于计算C 风格字符串的有效长度(即从首字符到 ‘\0’ 前的字符数)。
它是一个 “临时计算” 的函数,不是类的成员变量,因此不存在 “更新” 的说法。
_size 本质上就是对 strlen 结果的缓存(因为每次字符串修改后,_size 都会同步更新),这样可以避免频繁调用 strlen 带来的性能开销。
小总结:
在插入,删除,扩容,以及增删改除后 _size 和_capacity 都需要我们重新更新
扩容往往不需要对_size 进行更新 只需要对_capacity 进行更新
在插入 增加指定n个字符时 _size 往往需要更新 _capacity 也需要更新

.2 new char[_capacity+1] 会不会扩容_capacity? strcpy(_str,str)之后会不会自动改变_size?

结论 new char之后不会改变_capacity容量,strcpy之后也不会改变_size的大小
new char 只负责内存的创建,它完全不认识你参数里面的_capacity 是什么东西
strcpy也同理 它只负责拷贝,它根本就不管你里面有什么_size _capacity strlen 还是其他什么东西
因此他们不会对_capacity 和 _size 私有成员进行改变,在最后我们需要更新_capacity _size的时候还得手动更新

3. delete[]_str 之后能否进行 strcpy(_str,str)?

结论是不能
delete[]_str 之后,_str指向的资源被释放,那么现在_str只剩一个空地址,它的_capacity 和_size都已经成为随机值(由于释放),而strcpy是需要对地址进行解引用,直到遇到\0位置。这里如果strcpy的话就会出现随机值访问的错误

四、C语言的for循环在更新数据的弊端

在没有\0、或者有空间限制的情况下 需要特别注意\0的添加
否则会发生下列问题
在这里插入图片描述
这种后面是垃圾值,原因是结尾没有/0,但是也没有发生越界

还有一种情况是烫烫烫,这种是访问到了随机值,就是这个空间没有进行赋值,是空的。那么访问时就会出现烫烫烫。 这种也没有发生越界。但都是两种错误的典范。

posted @ 2025-10-29 19:26  gccbuaa  阅读(4)  评论(0)    收藏  举报