C++:string类模拟实现

3 篇文章 0 订阅
3 篇文章 0 订阅


前言

今天我们来模拟实现一下string类~

总结实现源码附上~

在这里插入图片描述


一、构造相关

1. 类的定义

为了区分和库里面的string我们需要包个自己的命名空间,里面写我们的string类

namespace jyf
{
	class string
	{
	private:
		char* _str;        //数组
		size_t _size;      //大小
		size_t _capacity;  //容量

    public:
		static const size_t npos;  //之后要用的静态成员变量,npos = -1
    };
}

2. 构造,拷贝构造,赋值重载,析构

//string();构造相关
string(const char* str = "");
~string();
string(const string& s);

//传统写法
//string& operator=(const string& s);

//现代写法
string& operator=(string s);
//string();构造相关

//构造函数
string::string(const char* str)
	:_size(strlen(str))
{
	_str = new char[_size + 1];
	strcpy(_str, str);
	_capacity = _size;
}

//析构函数
string::~string()
{
	delete[] _str;
	_str = nullptr;
	_size = 0;
	_capacity = 0;
}

//拷贝构造
//传统写法
string::string(const string& s)
{
	_str = new char[s._capacity + 1];
	strcpy(_str, s._str);
	_size = s._size;
	_capacity = s._capacity;
}

//赋值运算符重载
//传统写法
string& string::operator=(const string& s)
{
	if (this != &s)
	{
		delete[] _str;
		_str = new char[s._capacity + 1];
		strcpy(_str, s._str);

		_size = s._size;
		_capacity = s._capacity;
	}

	return *this;
}


3. 注意的点

1)构造函数初始化列表顺序问题

在这里插入图片描述


2)赋值运算符重载判断自我赋值

在这里插入图片描述


4. 拷贝构造和赋值运算符重载现代写法

1)拷贝构造

//现代写法
string::string(const string& s)
{
	string tmp(s._str);
	swap(tmp);
}

在这里插入图片描述

相当于资本主义,疯狂压榨构造函数和析构的剩余价值~


2)赋值运算符重载

//新版本
string& string::operator=(string s)
{
	swap(s);
	return *this;
}

在这里插入图片描述
构造函数不能只传值,不然会引发无穷递归~


二、遍历相关

1. operator[ ]

char& string::operator[](size_t index)
{
	assert(index < _size);
	return _str[index];
}
const char& string::operator[](size_t index)const
{
	assert(index < _size);
	return _str[index];
}

这两个版本是一样的,一个正常调用,一个const修饰的来调用。


2. 迭代器

注意:使用之前要进行typedef
//typedef char* iterator;
using iterator = char*;
using 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;
}

这里的参数名字要写对,不然类似范围for这样的会出问题


三、容量相关

在这里插入图片描述

1. size

size_t string::size()const
{
	return _size;
}

2. capacity

size_t string::capacity()const
{
	return _capacity;
}

3. empty

bool string::empty()const
{
	return _size == 0;
}

4. 改变大小resize

这里memset是这样的
在这里插入图片描述

//改size,赋值c
void string::resize(size_t newSize, char c)
{
	if (newSize > _size)
	{
		// 如果newSize大于底层空间大小,则需要重新开辟空间
		if (newSize > _capacity)
		{
			reserve(newSize);
		}
		memset(_str + _size, c, newSize - _size);
	}
	_size = newSize;
	_str[newSize] = '\0';
}

5. 预设空间reserve

这里只有n比_capcacity大才开空间

//预开空间
void string::reserve(size_t n)
{
	// n比_capcacity小就不开了
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;

		_capacity = n;
	}
}

四、插入与清除

1. 任意位置插入insert

1)插一个字符

  1. 断言pos位置pos <= _size
  2. 判断是否需要扩容
  3. 循环pos及以后数据后移
    注意这里后移逻辑,size_t end = _size + 1;
    也就是说要从_size + 1的位置开始把_str[end] = _str[end - 1],如果不这样干,一旦让end变为负数,又因为end为_size_t类型,他就会无限循环

size_t -1可以简单理解为最大的整形数据,因此会无限循环
而且只改end为int类型也不行,因为pos是size_t,end会引发整型提升,提升为无符号整型,除非强转了pos为int

  1. 插入
//任意位置插入
void string::insert(size_t pos, char ch)
{
	assert(pos <= _size);

	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : 2 * _capacity);
	}
	
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		end--;
	}
	_str[pos] = ch;

	_size++;
}

2)插字符串

  1. 断言pos位置pos <= _size
  2. 判断是否需要扩容,不一样的是判断_size + len > _capacity,扩容也需要它和2 * _capacity比
  3. 循环pos及以后数据后移,同样注意end不要小于零
  4. 插入
void string::insert(size_t pos, const char* str)
{
	assert(pos <= _size);

	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
	}

	size_t end = _size + len;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		end--;
	}

	for (int i = 0; i < len; i++)
	{
		_str[pos + i] = str[i];
	}

	_size += len;
}

2. 尾插字符push_back

可以考虑复用insert,或者也可以像注释这样写

//尾插字符
void string::push_back(char ch)
{
	/*if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : 2 * _capacity);
	}

	_str[_size] = ch;
	_size++;*/

	insert(_size, ch);
}

3. 尾插字符串append

可以考虑复用insert,或者也可以像注释这样写

//尾插字符串
void string::append(const char* str)
{
	/*size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len > _capacity ? _size + len : 2 * _capacity);
	}
	
	strcpy(_str + _size, str);
	_size += len;*/

	insert(_size, str);
}

4. operator+=

可以考虑复用push_back 和 append

//重载加等
string& string::operator+=(char ch)
{
	push_back(ch);
	return *this;
}
string& string::operator+=(const char* str)
{
	append(str);
	return *this;
}

5. 清除数据clear

不动空间,直接让_str[0]的位置变为’\0’就好

void string::clear()
{
	_str[0] = '\0';
	_size = 0;
}

6. 删除pos后的n个数据erase

n太大,str太小pos后面全删完
n太小,只删除pos后一部分,要挪数据

//删除
void string::earse(size_t pos, size_t len)
{
	assert(pos < _size);

	// n太大, str太小pos后面全删完
	if (pos + len >= _size)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else//只删除pos后一部分,要挪数据
	{
		size_t end = pos + len;
		while (end <= _size)
		{
			_str[end - len] = _str[end];
			end++;
		}

		_size -= len;
	}
}

7. 转C风格

char* c_str() const
{
	return _str;
}

8. 类内交换

直接调用算法库的swap交换_str, _size, _capacity;

//类内的交换
void string::swap(string& str)
{
	std::swap(_str, str._str);
	std::swap(_size, str._size);
	std::swap(_capacity, str._capacity);
}

五、查找相关

1. find

1)返回c在string中第一次出现的位置

同样这里的npos需要定义在类内public作用于下,定义成静态成员变量,类内定义,类外声明

但这里对于size_t npos 编译器做了特殊处理,直接在类内定义都可以,但最好还是按照规则来
在这里插入图片描述
在这里插入图片描述

size_t string::find(char ch, size_t pos)
{
	assert(pos < _size);
	
	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == ch)
		{
			return i;
		}
	}

	return npos;
}

2)返回子串s在string中第一次出现的位置

这里借助了C语言找字串的函数strstr,左值是从哪个位置开始找,右值是找到的字串
在这里插入图片描述
它的返回值是第一次找到的位置,因此我们这个函数要返回的下标就是两个指针相减,得出下标。

size_t string::find(const char* str, size_t pos)
{
	assert(pos < _size);

	const char* ptr = strstr(_str + pos, str);

	if (ptr == nullptr)
	{
		return npos;
	}
	else
	{
		return ptr - _str;
	}
}

六、重载比较运算符

借助在日期类那里学到的思路,只需重载 == 和 < ,其他复用这两个就好

1. operator==

bool operator== (const string& lhs, const string& rhs)
{
	return strcmp(lhs.c_str(), rhs.c_str()) == 0;
}

2. operator<

bool operator< (const string& lhs, const string& rhs)
{
	return strcmp(lhs.c_str(), rhs.c_str()) < 0;
}

3. 复用 == 与 <

bool operator!= (const string& lhs, const string& rhs)
{
	return !(lhs == rhs);
}

bool operator> (const string& lhs, const string& rhs)
{
	return !(lhs <= rhs);
}

bool operator>= (const string& lhs, const string& rhs)
{
	return !(lhs < rhs);
}

bool operator<= (const string& lhs, const string& rhs)
{
	return lhs < rhs || lhs == rhs;
}

七、重载流插入,流输出

1. operator<<

ostream& operator<< (ostream& os, const string& str)
{
	for (size_t i = 0; i < str.size(); i++)
	{
		os << str[i];
	}
	return os;
}

2. operator>>

  1. 赋值要先清理干净原来的内容
  2. 这里用get()是因为要获取到’ ‘(空格) 和’ \n ',将他们作为判断结束的标志

在这里插入图片描述

  1. 不断地循环 += ch
istream& operator>> (istream& is, string& str)
{
	str.clear();

	char ch = is.get();

	while (ch != ' ' && ch != '\n')
	{
		str += ch;
		ch = is.get();
	}

	return is;
}

八、getline

getline 和 operator>> 的区别就是,getline默认遇到’ ‘不结束,遇到’ \n’才结束,而且可以自定义结束终止符。

  1. 赋值就要先清理str
  2. 这里没有直接 += ,做了一个优化

定义一个 char buff[256] ,假定可以存256个值,每次获取到的 ch 都先放到 buff 里,等到 buff 存到255个数据用 i 来记录,就添加一个’\0’,然后 += 到str上面,再将 i 重新置 0 ,继续循环
这样减少了每次的 += 效率的损失,提高了效率

  1. 出循环的时候 i 不为零,补一个’\0’,再 += 到 str 上就好
	//getline获取字符串
	istream& getline(istream& is, string& str, char delim)
	{
		str.clear();

		int i = 0;
		char buff[256];

		char ch;
		ch = is.get();
		while (ch != delim)
		{
			buff[i++] = ch;

			if (i == 255)
			{
				buff[i] = '\0';
				str += buff;
				i = 0;
			}

			ch = is.get();
		}

		if (i > 0)
		{
			buff[i] = '\0';
			str += buff;
		}

		return is;
	}
}

总结

string模拟实现源码:

//string.h
#define _CRT_SECURE_NO_WARNINGS 1

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

namespace jyf
{
	class string
	{

	public:
		//typedef char* iterator;
		using iterator = char*;
		using const_iterator = const char*;

		//string();构造相关
		string(const char* str = "");
		~string();
		string(const string& s);

		//传统写法
		//string& operator=(const string& s);

		//现代写法
		string& operator=(string s);

		char operator[](size_t i);
		const char operator[](size_t i) const;

		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		const_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			return _str + _size;
		}

		size_t size() const
		{
			return _size;
		}

		char* c_str() const
		{
			return _str;
		}

		bool empty()const

		{
			return _size == 0;
		}

		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}

		void resize(size_t newSize, char c = '\0');
		void reserve(size_t n);

		void push_back(char ch);

		void append(const char* str);

		string& operator+=(char ch);
		string& operator+=(const char* str);

		void insert(size_t pos, char ch);
		void insert(size_t pos, const char* str);
		void earse(size_t pos, size_t len = npos);

		size_t find(char ch, size_t pos);
		size_t find(const char* str, size_t pos);

		void swap(string& str);

		string substr(size_t pos, size_t len);

	private:
		char* _str;
		size_t _size;
		size_t _capacity;

	public:
		static const size_t npos;
	};

	//类外交换
	void swap(string& s1, string& s2);

	//类外比较的重载
	bool operator== (const string& lhs, const string& rhs);
	bool operator!= (const string& lhs, const string& rhs);
	bool operator> (const string & lhs, const string & rhs);
	bool operator>= (const string & lhs, const string & rhs);
	bool operator< (const string& lhs, const string& rhs);
	bool operator<= (const string& lhs, const string& rhs);

	//类外重载流插入,流提取
	ostream& operator<< (ostream& os, const string& str);
	istream& operator>> (istream& is, string& str);

	//getline获取字符串
	istream& getline(istream& is, string& str, char delim = '\n');
}
//string.cpp
#include"string.h"

namespace jyf
{
	const size_t string::npos = -1;

	//string();构造相关

	//构造函数
	string::string(const char* str)
		:_size(strlen(str))
	{
		_str = new char[_size + 1];
		strcpy(_str, str);
		_capacity = _size;
	}

	//析构函数
	string::~string()
	{
		delete[] _str;
		_str = nullptr;
		_size = 0;
		_capacity = 0;
	}
	
	//拷贝构造
	//传统写法
	/*string::string(const string& s)
	{
		_str = new char[s._capacity + 1];
		strcpy(_str, s._str);
		_size = s._size;
		_capacity = s._capacity;
	}*/

	//现代写法
	string::string(const string& s)
	{
		string tmp(s._str);
		swap(tmp);
	}

	//赋值运算符重载
	//传统写法
	/*string& string::operator=(const string& s)
	{
		if (this != &s)
		{
			delete[] _str;
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);

			_size = s._size;
			_capacity = s._capacity;
		}

		return *this;
	}*/
	
	//新版本
	string& string::operator=(string s)
	{
		swap(s);
		return *this;
	}

	//改size,赋值c
	void string::resize(size_t newSize, char c)
	{
		if (newSize > _size)
		{
			// 如果newSize大于底层空间大小,则需要重新开辟空间
			if (newSize > _capacity)
			{
				reserve(newSize);
			}
			memset(_str + _size, c, newSize - _size);
		}
		_size = newSize;
		_str[newSize] = '\0';
	}

	//预开空间
	void string::reserve(size_t n)
	{
		// n比_capcacity小就不开了
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;

			_capacity = n;
		}
	}

	//[]遍历
	char string::operator[](size_t i)
	{
		assert(i < _size);
		return _str[i];
	}

	const char string::operator[](size_t i) const
	{
		assert(i < _size);
		return _str[i];
	}

	//尾插字符
	void string::push_back(char ch)
	{
		/*if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : 2 * _capacity);
		}

		_str[_size] = ch;
		_size++;*/

		insert(_size, ch);
	}

	//尾插字符串
	void string::append(const char* str)
	{
		/*size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			reserve(_size + len > _capacity ? _size + len : 2 * _capacity);
		}
		
		strcpy(_str + _size, str);
		_size += len;*/

		insert(_size, str);
	}

	//重载加等
	string& string::operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}
	string& string::operator+=(const char* str)
	{
		append(str);
		return *this;
	}

	//任意位置插入
	void string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);

		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : 2 * _capacity);
		}
		
		size_t end = _size + 1;
		while (end > pos)
		{
			_str[end] = _str[end - 1];
			end--;
		}
		_str[pos] = ch;

		_size++;
	}

	void string::insert(size_t pos, const char* str)
	{
		assert(pos <= _size);

		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}

		size_t end = _size + len;
		while (end > pos)
		{
			_str[end] = _str[end - 1];
			end--;
		}

		for (int i = 0; i < len; i++)
		{
			_str[pos + i] = str[i];
		}

		_size += len;
	}

	//删除
	void string::earse(size_t pos, size_t len)
	{
		assert(pos < _size);

		// n太大, str太小pos后面全删完
		if (pos + len >= _size)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else//只删除pos后一部分,要挪数据
		{
			size_t end = pos + len;
			while (end <= _size)
			{
				_str[end - len] = _str[end];
				end++;
			}

			_size -= len;
		}
	}

	//查找
	size_t string::find(char ch, size_t pos)
	{
		assert(pos < _size);
		
		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
			{
				return i;
			}
		}

		return npos;
	}

	size_t string::find(const char* str, size_t pos)
	{
		assert(pos < _size);

		const char* ptr = strstr(_str + pos, str);

		if (ptr == nullptr)
		{
			return npos;
		}
		else
		{
			return ptr - _str;
		}
	}

	//类内的交换
	void string::swap(string& str)
	{
		std::swap(_str, str._str);
		std::swap(_size, str._size);
		std::swap(_capacity, str._capacity);
	}

	//获取字串
	string string::substr(size_t pos, size_t len)
	{
		assert(pos < _size);

		//如果,len的长度大于pos位置开始之后的长度,就从pos取到结尾
		if (len > _size - pos)
		{
			len = _size - pos;
		}

		jyf::string sub;
		sub.reserve(len);
		
		for (int i = 0; i < len; i++)
		{
			sub += _str[pos + i];
		}

		return sub;
	}
	

	//类外交换
	void swap(string& s1, string& s2)
	{
		s1.swap(s2);
	}

	//类外比较的重载
	bool operator== (const string& lhs, const string& rhs)
	{
		return strcmp(lhs.c_str(), rhs.c_str()) == 0;
	}

	bool operator!= (const string& lhs, const string& rhs)
	{
		return !(lhs == rhs);
	}

	bool operator> (const string& lhs, const string& rhs)
	{
		return !(lhs <= rhs);
	}

	bool operator>= (const string& lhs, const string& rhs)
	{
		return !(lhs < rhs);
	}

	bool operator< (const string& lhs, const string& rhs) 
	{
		return strcmp(lhs.c_str(), rhs.c_str()) < 0;
	}

	bool operator<= (const string& lhs, const string& rhs)
	{
		return lhs < rhs || lhs == rhs;
	}

	//类外重载流插入,流提取
	ostream& operator<< (ostream& os, const string& str)
	{
		for (size_t i = 0; i < str.size(); i++)
		{
			os << str[i];
		}
		return os;
	}

	istream& operator>> (istream& is, string& str)
	{
		str.clear();

		char ch = is.get();

		while (ch != ' ' && ch != '\n')
		{
			str += ch;
			ch = is.get();
		}

		return is;
	}

	//getline获取字符串
	istream& getline(istream& is, string& str, char delim)
	{
		str.clear();

		int i = 0;
		char buff[256];

		char ch;
		ch = is.get();
		while (ch != delim)
		{
			buff[i++] = ch;

			if (i == 255)
			{
				buff[i] = '\0';
				str += buff;
				i = 0;
			}

			ch = is.get();
		}

		if (i > 0)
		{
			buff[i] = '\0';
			str += buff;
		}

		return is;
	}
}
//test.cpp
#include "string.h"
#include <iostream>
using namespace std;

void TestString()
{
    // 1. 构造函数测试
    cout << "=== 构造函数测试 ===" << endl;
    jyf::string s1;
    cout << "默认构造: " << s1.c_str() << " (size: " << s1.size() << ")" << endl;

    jyf::string s2("hello");
    cout << "带参数构造: " << s2.c_str() << " (size: " << s2.size() << ")" << endl;

    jyf::string s3(s2);
    cout << "拷贝构造: " << s3.c_str() << " (size: " << s3.size() << ")" << endl;

    // 2. 赋值运算符测试
    cout << "\n=== 赋值运算符测试 ===" << endl;
    s2 = "world";
    cout << "赋值: " << s2.c_str() << " (size: " << s2.size() << ")" << endl;

    // 3. 运算符[] 测试
    cout << "\n=== 运算符[] 测试 ===" << endl;
    cout << "s2[0]: " << s2[0] << ", s2[4]: " << s2[4] << endl;

    // 4. push_back 测试
    cout << "\n=== push_back 测试 ===" << endl;
    s2.push_back('!');
    cout << "push_back: " << s2.c_str() << " (size: " << s2.size() << ")" << endl;

    // 5. append 测试
    cout << "\n=== append 测试 ===" << endl;
    s2.append(" test");
    cout << "append: " << s2.c_str() << " (size: " << s2.size() << ")" << endl;

    // 6. insert 测试
    cout << "\n=== insert 测试 ===" << endl;
    s2.insert(6, ' ');
    cout << "insert char: " << s2.c_str() << endl;

    s2.insert(12, " case");
    cout << "insert string: " << s2.c_str() << endl;

    // 7. earse 测试
    cout << "\n=== earse 测试 ===" << endl;
    s2.earse(6, 5);
    cout << "earse部分删除: " << s2.c_str() << endl;

    s2.earse(6); // 删除从位置6开始到结尾的所有字符
    cout << "earse到末尾: " << s2.c_str() << endl;

    // 8. find 测试
    cout << "\n=== find 测试 ===" << endl;
    size_t pos = s2.find('!', 0);
    cout << "find '!': " << pos << endl;
    pos = s2.find("ld", 0);
    cout << "find 'ld': " << pos << endl;

    // 9. substr 测试
    cout << "\n=== substr 测试 ===" << endl;
    jyf::string s5 = s2.substr(0, 5);
    cout << "substr(0, 5): " << s5.c_str() << endl;

    // 10. 类外比较运算符测试
    cout << "\n=== 类外比较运算符测试 ===" << endl;
    cout << "s2 == s2: " << (s2 == s2) << endl;
    cout << "s2 != s3: " << (s2 != s3) << endl;
    cout << "s3 < s2: " << (s3 < s2) << endl;
    cout << "s2 > s3: " << (s2 > s3) << endl;

    // 11. 类内 swap 测试
    cout << "\n=== swap 测试 ===" << endl;
    s2.swap(s3);
    cout << "swap后 s2: " << s2.c_str() << ", s3: " << s3.c_str() << endl;

    // 12. clear 测试
    cout << "\n=== clear 测试 ===" << endl;
    s2.clear();
    cout << "clear后 s2: " << s2.c_str() << " (size: " << s2.size() << ")" << endl;

    // 13. 类外 swap 测试
    cout << "\n=== 类外 swap 测试 ===" << endl;
    jyf::swap(s2, s3);
    cout << "类外swap后 s2: " << s2.c_str() << ", s3: " << s3.c_str() << endl;

    // 14. 类外流插入、提取运算符测试
    cout << "\n=== 流插入与提取测试 ===" << endl;
    jyf::string s6("stream test");
    cout << "输出运算符: " << s6 << endl;

    jyf::string s7;
    cout << "输入一段字符串: ";
    cin >> s7;
    cout << "输入的字符串是: " << s7 << endl;

    // 15. getline 测试
    cout << "\n=== getline 测试 ===" << endl;
    jyf::string s8;
    cout << "输入一行文字 (以逗号结束): ";
    jyf::getline(cin, s8, ',');
    cout << "输入的内容是: " << s8 << endl;
}

int main()
{
    TestString();
    return 0;
}

到这里就结束啦~
谢谢大家!~

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值