Cpp Chapter 16: The string Class and the Standard Template Library Part1

(这已经是第二次博客园吞我东西了,有点心态爆炸)

16.1 The string Class

) Constructing a string
Here is a table which shows the seven form of constructors that the string class has:

ConstructorDescription
string(const char * s)initializes a string object to the C-style string pointed to by s
string(size_type n, char c)initializes a string object of n elements, each initialized to c
string(const string & str)initializes a string object to the string object str(copy constructor)
string()creates a default string object of size 0
string(const char * s, size_type n)initializes a string object to the C-style string pointed to by s, continuing for n characters even it exceeds s
template string(Iter begin, Iter end)initializes a string to the values in the range [begin, end)
string(const string & str, size_type pos, size_type n = npos)initializes a string object to str, starting from pos and going to end or terminate with n characters

) The string class input
Typically, you have two ways of string input:
1

string stuff;
cin >> stuff;

In this way you read a word.
2

string stuff;
getline(cin, stuff);

In this way you read a line of characters.
An important feature of string class is that it automatically size the string object to hold the input characters.
The getline() function for the string class reads characters from input unless:
1 the end-of-file is encountered
2 the delimiting character(\n) is reached
3 the maximum possible number of characters is read, which is represented by string::npos
The operator>>() function reads up to the whitespace, which is typically ' ' and any characters that makes isspace() return true

Next comes a program that illustrates the features of file output:

// strfile.cpp -- read strings from a file
#include <iostream>
#include <string>
#include <cstdlib>
#include <fstream>

int main()
{
    using namespace std;
    ifstream fin;
    fin.open("tobuy.txt");
    if (fin.is_open() == false)
    {
        cerr << "Can't open file. Bye.\n";
        exit(EXIT_FAILURE);
    }
    string item;
    int count = 0;
    getline(fin, item, ':');
    while (fin)
    {
        ++count;
        cout << count << ": " << item << endl;
        getline(fin, item, ':');
    }
    cout << "Done\n";
    fin.close();
    return 0;
}

Noteworthy:
1 Typically, the file you tried to read should be in the same directory with the program. If not so, you can provide a full path name:

fin.open("C:\\CPP\\tobuy.txt");

The \\ here is an escape sequence, which represents one ''
2 You open a file with fin.open(path), and fin is an ifstream object. To get contents of the file, you use getline(fin, str, ':') where ':' stands for the delimiter here, which means that the input terminates when it reaches ':'
3 while(fin) loops as long as the input is not EOF and is good.

) Working with strings
1 You can compare strings with the overloaded relational operators, with one object being considered less if it occurs earlier in the machine collating sequence(ASCII).
2 You can determine the size of a string by length() or size():

if (snake1.length() == snake2.size())
    cout << "Both strings have the same length";

3 You can search for a substring or a character in a string"
size_type find(const string & str, size_type pos = 0) const finds the first occurence of str, starting from pos, and returning the position found or pos otherwise.
Next comes a program using the features of the string class, which is a game that lets you guess a word with a given length:

// hangman.cpp -- some string methods
#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
#include <cctype>
using std::string;
const int NUM = 26;
const string wordlist[NUM] = {"apiary", "beetle", "cereal", "danger", "ensign", "florid", "garage", "health", "insult",
    "jackal", "keeper", "loaner", "manage", "nonce", "onset", "plaid", "quilt", "remote", "stolid", "train", "useful", "valid", "whence", "exnon", "yearn", "zipppy"};

int main()
{
    using std::cout;
    using std::cin;
    using std::tolower;
    using std::endl;
    std::srand(std::time(0));
    char play;
    cout << "Will you play a word game? <y/n>? ";
    cin >> play;
    play = tolower(play);
    while (play == 'y')
    {
        string target = wordlist[std::rand() % NUM];
        int length = target.length();
        string attempt(length, '-');
        string badchars;
        int guesses = 6;
        cout << "Guess my secret word. It has " << length << " letters, and you guess\none letter at a time. You get " << guesses << " wrong guesses.\n";
        cout << "Your word: " << attempt << endl;
        while (guesses > 0 && attempt != target)
        {
            char letter;
            cout << "Guess a letter: ";
            cin >> letter;
            if (badchars.find(letter) != string::npos || attempt.find(letter) != string::npos)
            {
                cout << "You already guessed that. Try again.\n";
                continue;
            }
            int loc = target.find(letter);
            if (loc == string::npos)
            {
                cout << "Oh, bad guess!\n";
                --guesses;
                badchars += letter;
            }
            else
            {
                cout << "Good guess!\n";
                attempt[loc] = letter;
                loc = target.find(letter, loc + 1); // start searching from loc+1
                while (loc != string::npos)
                {
                    attempt[loc] = letter;
                    loc = target.find(letter, loc + 1);
                };
            }
            cout << "Your word: " << attempt << endl;
            if (attempt != target)
            {
                if (badchars.length() > 0)
                    cout << "Bad choices: " << badchars << endl;
                cout << guesses << " bad guesses left\n";
            }
        }
        if (guesses > 0)
            cout << "That's right!\n";
        else
            cout << "Sorry, the word is " << target << ".\n";
        cout << "Will you play another? <y/n> ";
        cin >> play;
        play = tolower(play);
    }

    cout << "Bye\n";
    return 0;
}

Noteworthy:
1 Recall that string.find(letter) returns string::npos if it doesn't find the required letter in it. So the program uses this sentence to test if the user's input is already guessed before:

if (badchars.find(letter) != string::npos || attempt.find(letter) != string::npos)

npos is a static member of string class, it marks the greatest amount of characters that a string could have, thus used in find() to indicate that the letter required isn't found in the string.
2 The program also used + for string concatenation, > for comparison of string and the features provided by string class.

) What else does the string class offer
The string class objects will automatically adjust its size, so it initially will allocate a larger memory for a string object, letting it to grow in size. When it grows to exceed the memory, the compiler allocates a block of memory that is twice the size of the previous one and copy the object contents to the new place.
The capacity() method returns the size of the current block, and the reserve() method allows you to request a minimum size for the block.
Here is a program illustrating the mentioned features about memory allocation:

// str2.cpp -- capacity() and reserve()
#include <iostream>
#include <string>
int main()
{
    using namespace std;
    string empty;
    string small = "bit";
    string larger = "Elephants are a girl's best friend";
    cout << "Sizes\n";
    cout << "\tempty: " << empty.size() << endl;
    cout << "\tsmall: " << small.size() << endl;
    cout << "\tlarger: " << larger.size() << endl;
    cout << "Capacities:\n";
    cout << "\tempty: " << empty.capacity() << endl;
    cout << "\tsmall: " << small.capacity() << endl;
    cout << "\tlarger: " << larger.capacity() << endl;
    empty.reserve(50);
    cout << "Capacity after empty.reserve(50): " << empty.capacity() << endl;
    return 0;
}

) Conversion between C-style string and string objects
In order to convert from C-style string to string objects, use the string constructor:

char a[2] = "ab";
string b = string(a);

In order to convert from string objects to C-style strings(e.g., opening file), use string.c_str():

string b = "haha.txt";
fout.open(b.c_str());

16.2 Smart Pointer Template Classes

) Why smart pointer?
Sometimes you might forget to append delete ptr after you declare a block of memory which is pointed to by ptr, or a function would terminate by throwing exception and skip the delete code. In both cases, it causes a memory leak. In order to solve this problem one for all, you may design a pointer which has a destructor that automatically free the memory it points to. That is just the idea of smart pointers, to be more specific, auto_ptr, unique_ptr and shared_ptr.

) Using smart pointers
In order to use smart pointers, you include memory header file and use the syntax of ordinary templates:

auto_ptr<double> pd(new double);
auto_ptr<string> pd(new string);

Note that smart pointers belong to the std namespace. Next comes code that uses smart pointers:

// smrtptrs.cpp -- using three kinds of smart pointers
// requires support of C++11 shared_ptr and unique_ptr
#include <iostream>
#include <string>
#include <memory>

class Report
{
private:
    std::string str;
public:
    Report(const std::string s) : str(s) {std::cout << "Object created!\n";}
    ~Report() {std::cout << "Object deleted!\n";}
    void comment() const {std::cout << str << "\n";}
};

int main()
{
    {
        std::auto_ptr<Report> ps (new Report("using auto_ptr"));
        ps->comment();
    }
    {
        std::shared_ptr<Report> ps (new Report("using shared_ptr"));
        ps->comment();
    }
    {
        std::unique_ptr<Report> ps (new Report("using unique_ptr"));
        ps->comment();
    }
    return 0;
}

Noteworthy:
1 You could just use smart pointer as ordinary pointers. It supports derefrencing*, call members'->', and assign to other pointers.
2 Avoid letting smart pointers point to non-heap memory:

string vacation("oh");
shared_ptr<string> pvac(vacation);

When pvac expires, it would delete vacation, which is not right.

) Smart pointer consideration
Consider:

vocation = ps;

When you let two smart pointers point to a same chunk of memory, it will free it twice when they both expired, which would raise an error. There are strategies that solve this:
1 Design the assignment to do deep copy
2 Take the concept of ownership, meaning that only the pointer that owns the memory could delete it with its destructor. This is the strategy used for auto_ptr and unique_ptr.
3 Create an even smarter pointer that keeps track of how many pointer point to a particular object. This is called reference counting. shared_ptr uses this strategy.
Next comes an example that auto_ptr works poorly:

// fowl.cpp -- auto_ptr a poor choice
#include <iostream>
#include <string>
#include <memory>

int main()
{
    using namespace std;
    auto_ptr<string> films[5] = {auto_ptr<string> (new string("A")),
                                auto_ptr<string> (new string("B")),
                                auto_ptr<string> (new string("C")),
                                auto_ptr<string> (new string("D")),
                                auto_ptr<string> (new string("E"))};
    auto_ptr<string> pwin;
    pwin = films[2]; // films[2] loses ownership

    cout << "The nominees for best avian baseball film are\n";
    for (int i = 0; i < 5; i++)
        cout << *films[i] << endl;
    cout << "The winner is " << *pwin << "!\n";
    cin.get();
    return 0;
}

This program will crash due to the sentence:

pwin = films[2];

As auto_ptr incorporates the idea of ownership, this assignment passes ownership from films[2] to pwin. After an auto_ptr gives up the ownership of an object, it no longer provides access to it. So when you later do cout << *films[2]; it will find a null-pointer and leads to a crash.
But if you use shared_ptr here, the program runs properly because both pointers retain access to the object.

---恢复内容结束---

## 16.1 The string Class
) Constructing a string
Here is a table which shows the seven form of constructors that the string class has:
ConstructorDescription
string(const char * s)initializes a string object to the C-style string pointed to by s
string(size_type n, char c)initializes a string object of n elements, each initialized to c
string(const string & str)initializes a string object to the string object str(copy constructor)
string()creates a default string object of size 0
string(const char * s, size_type n)initializes a string object to the C-style string pointed to by s, continuing for n characters even it exceeds s
template string(Iter begin, Iter end)initializes a string to the values in the range [begin, end)
string(const string & str, size_type pos, size_type n = npos)initializes a string object to str, starting from pos and going to end or terminate with n characters

) The string class input
Typically, you have two ways of string input:
1

string stuff;
cin >> stuff;

In this way you read a word.
2

string stuff;
getline(cin, stuff);

In this way you read a line of characters.
An important feature of string class is that it automatically size the string object to hold the input characters.
The getline() function for the string class reads characters from input unless:
1 the end-of-file is encountered
2 the delimiting character(\n) is reached
3 the maximum possible number of characters is read, which is represented by string::npos
The operator>>() function reads up to the whitespace, which is typically ' ' and any characters that makes isspace() return true

Next comes a program that illustrates the features of file output:

// strfile.cpp -- read strings from a file
#include <iostream>
#include <string>
#include <cstdlib>
#include <fstream>

int main()
{
    using namespace std;
    ifstream fin;
    fin.open("tobuy.txt");
    if (fin.is_open() == false)
    {
        cerr << "Can't open file. Bye.\n";
        exit(EXIT_FAILURE);
    }
    string item;
    int count = 0;
    getline(fin, item, ':');
    while (fin)
    {
        ++count;
        cout << count << ": " << item << endl;
        getline(fin, item, ':');
    }
    cout << "Done\n";
    fin.close();
    return 0;
}

Noteworthy:
1 Typically, the file you tried to read should be in the same directory with the program. If not so, you can provide a full path name:

fin.open("C:\\CPP\\tobuy.txt");

The \\ here is an escape sequence, which represents one ''
2 You open a file with fin.open(path), and fin is an ifstream object. To get contents of the file, you use getline(fin, str, ':') where ':' stands for the delimiter here, which means that the input terminates when it reaches ':'
3 while(fin) loops as long as the input is not EOF and is good.

) Working with strings
1 You can compare strings with the overloaded relational operators, with one object being considered less if it occurs earlier in the machine collating sequence(ASCII).
2 You can determine the size of a string by length() or size():

if (snake1.length() == snake2.size())
    cout << "Both strings have the same length";

3 You can search for a substring or a character in a string"
size_type find(const string & str, size_type pos = 0) const finds the first occurence of str, starting from pos, and returning the position found or pos otherwise.
Next comes a program using the features of the string class, which is a game that lets you guess a word with a given length:

// hangman.cpp -- some string methods
#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
#include <cctype>
using std::string;
const int NUM = 26;
const string wordlist[NUM] = {"apiary", "beetle", "cereal", "danger", "ensign", "florid", "garage", "health", "insult",
    "jackal", "keeper", "loaner", "manage", "nonce", "onset", "plaid", "quilt", "remote", "stolid", "train", "useful", "valid", "whence", "exnon", "yearn", "zipppy"};

int main()
{
    using std::cout;
    using std::cin;
    using std::tolower;
    using std::endl;
    std::srand(std::time(0));
    char play;
    cout << "Will you play a word game? <y/n>? ";
    cin >> play;
    play = tolower(play);
    while (play == 'y')
    {
        string target = wordlist[std::rand() % NUM];
        int length = target.length();
        string attempt(length, '-');
        string badchars;
        int guesses = 6;
        cout << "Guess my secret word. It has " << length << " letters, and you guess\none letter at a time. You get " << guesses << " wrong guesses.\n";
        cout << "Your word: " << attempt << endl;
        while (guesses > 0 && attempt != target)
        {
            char letter;
            cout << "Guess a letter: ";
            cin >> letter;
            if (badchars.find(letter) != string::npos || attempt.find(letter) != string::npos)
            {
                cout << "You already guessed that. Try again.\n";
                continue;
            }
            int loc = target.find(letter);
            if (loc == string::npos)
            {
                cout << "Oh, bad guess!\n";
                --guesses;
                badchars += letter;
            }
            else
            {
                cout << "Good guess!\n";
                attempt[loc] = letter;
                loc = target.find(letter, loc + 1); // start searching from loc+1
                while (loc != string::npos)
                {
                    attempt[loc] = letter;
                    loc = target.find(letter, loc + 1);
                };
            }
            cout << "Your word: " << attempt << endl;
            if (attempt != target)
            {
                if (badchars.length() > 0)
                    cout << "Bad choices: " << badchars << endl;
                cout << guesses << " bad guesses left\n";
            }
        }
        if (guesses > 0)
            cout << "That's right!\n";
        else
            cout << "Sorry, the word is " << target << ".\n";
        cout << "Will you play another? <y/n> ";
        cin >> play;
        play = tolower(play);
    }

    cout << "Bye\n";
    return 0;
}

Noteworthy:
1 Recall that string.find(letter) returns string::npos if it doesn't find the required letter in it. So the program uses this sentence to test if the user's input is already guessed before:

if (badchars.find(letter) != string::npos || attempt.find(letter) != string::npos)

npos is a static member of string class, it marks the greatest amount of characters that a string could have, thus used in find() to indicate that the letter required isn't found in the string.
2 The program also used + for string concatenation, > for comparison of string and the features provided by string class.

) What else does the string class offer
The string class objects will automatically adjust its size, so it initially will allocate a larger memory for a string object, letting it to grow in size. When it grows to exceed the memory, the compiler allocates a block of memory that is twice the size of the previous one and copy the object contents to the new place.
The capacity() method returns the size of the current block, and the reserve() method allows you to request a minimum size for the block.
Here is a program illustrating the mentioned features about memory allocation:

// str2.cpp -- capacity() and reserve()
#include <iostream>
#include <string>
int main()
{
    using namespace std;
    string empty;
    string small = "bit";
    string larger = "Elephants are a girl's best friend";
    cout << "Sizes\n";
    cout << "\tempty: " << empty.size() << endl;
    cout << "\tsmall: " << small.size() << endl;
    cout << "\tlarger: " << larger.size() << endl;
    cout << "Capacities:\n";
    cout << "\tempty: " << empty.capacity() << endl;
    cout << "\tsmall: " << small.capacity() << endl;
    cout << "\tlarger: " << larger.capacity() << endl;
    empty.reserve(50);
    cout << "Capacity after empty.reserve(50): " << empty.capacity() << endl;
    return 0;
}

) Conversion between C-style string and string objects
In order to convert from C-style string to string objects, use the string constructor:

char a[2] = "ab";
string b = string(a);

In order to convert from string objects to C-style strings(e.g., opening file), use string.c_str():

string b = "haha.txt";
fout.open(b.c_str());

16.2 Smart Pointer Template Classes

) Why smart pointer?
Sometimes you might forget to append delete ptr after you declare a block of memory which is pointed to by ptr, or a function would terminate by throwing exception and skip the delete code. In both cases, it causes a memory leak. In order to solve this problem one for all, you may design a pointer which has a destructor that automatically free the memory it points to. That is just the idea of smart pointers, to be more specific, auto_ptr, unique_ptr and shared_ptr.

) Using smart pointers
In order to use smart pointers, you include memory header file and use the syntax of ordinary templates:

auto_ptr<double> pd(new double);
auto_ptr<string> pd(new string);

Note that smart pointers belong to the std namespace. Next comes code that uses smart pointers:

// smrtptrs.cpp -- using three kinds of smart pointers
// requires support of C++11 shared_ptr and unique_ptr
#include <iostream>
#include <string>
#include <memory>

class Report
{
private:
    std::string str;
public:
    Report(const std::string s) : str(s) {std::cout << "Object created!\n";}
    ~Report() {std::cout << "Object deleted!\n";}
    void comment() const {std::cout << str << "\n";}
};

int main()
{
    {
        std::auto_ptr<Report> ps (new Report("using auto_ptr"));
        ps->comment();
    }
    {
        std::shared_ptr<Report> ps (new Report("using shared_ptr"));
        ps->comment();
    }
    {
        std::unique_ptr<Report> ps (new Report("using unique_ptr"));
        ps->comment();
    }
    return 0;
}

Noteworthy:
1 You could just use smart pointer as ordinary pointers. It supports derefrencing*, call members'->', and assign to other pointers.
2 Avoid letting smart pointers point to non-heap memory:

string vacation("oh");
shared_ptr<string> pvac(vacation);

When pvac expires, it would delete vacation, which is not right.

) Smart pointer consideration
Consider:

vocation = ps;

When you let two smart pointers point to a same chunk of memory, it will free it twice when they both expired, which would raise an error. There are strategies that solve this:
1 Design the assignment to do deep copy
2 Take the concept of ownership, meaning that only the pointer that owns the memory could delete it with its destructor. This is the strategy used for auto_ptr and unique_ptr.
3 Create an even smarter pointer that keeps track of how many pointer point to a particular object. This is called reference counting. shared_ptr uses this strategy.
Next comes an example that auto_ptr works poorly:

// fowl.cpp -- auto_ptr a poor choice
#include <iostream>
#include <string>
#include <memory>

int main()
{
    using namespace std;
    auto_ptr<string> films[5] = {auto_ptr<string> (new string("A")),
                                auto_ptr<string> (new string("B")),
                                auto_ptr<string> (new string("C")),
                                auto_ptr<string> (new string("D")),
                                auto_ptr<string> (new string("E"))};
    auto_ptr<string> pwin;
    pwin = films[2]; // films[2] loses ownership

    cout << "The nominees for best avian baseball film are\n";
    for (int i = 0; i < 5; i++)
        cout << *films[i] << endl;
    cout << "The winner is " << *pwin << "!\n";
    cin.get();
    return 0;
}

This program will crash due to the sentence:

pwin = films[2];

As auto_ptr incorporates the idea of ownership, this assignment passes ownership from films[2] to pwin. After an auto_ptr gives up the ownership of an object, it no longer provides access to it. So when you later do cout << *films[2]; it will find a null-pointer and leads to a crash.
But if you use shared_ptr here, the program runs properly because both pointers retain access to the object.

转载于:https://www.cnblogs.com/fsbblogs/p/9986964.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
std::string_view是C++17中的新类型,它是一个用来表示字符串的轻量级视图,不拥有字符串的所有权,而是指向一个已有的字符串对象。它类似于指向字符串的指针,但是提供了更多的安全性和便利性。 std::string_view的定义如下: ``` namespace std { template<class charT, class traits = std::char_traits<charT>> class basic_string_view { public: using value_type = charT; using traits_type = traits; using pointer = value_type*; using const_pointer = const value_type*; using reference = value_type&; using const_reference = const value_type&; using const_iterator = const_pointer; using iterator = const_iterator; using size_type = std::size_t; using difference_type = std::ptrdiff_t; // 构造函数 constexpr basic_string_view() noexcept; constexpr basic_string_view(const basic_string_view&) noexcept = default; constexpr basic_string_view(const charT* str); constexpr basic_string_view(const charT* str, size_type len); // 迭代器 constexpr const_iterator begin() const noexcept; constexpr const_iterator end() const noexcept; constexpr const_iterator cbegin() const noexcept; constexpr const_iterator cend() const noexcept; // 容量 constexpr size_type size() const noexcept; constexpr size_type length() const noexcept; constexpr size_type max_size() const noexcept; constexpr bool empty() const noexcept; // 元素访问 constexpr const_reference operator[](size_type pos) const noexcept; constexpr const_reference at(size_type pos) const; constexpr const_reference front() const noexcept; constexpr const_reference back() const noexcept; constexpr const_pointer data() const noexcept; // 子串 constexpr basic_string_view substr(size_type pos, size_type n = npos) const; // 查找 constexpr size_type find(basic_string_view v, size_type pos = 0) const noexcept; constexpr size_type find(charT c, size_type pos = 0) const noexcept; constexpr size_type rfind(basic_string_view v, size_type pos = npos) const noexcept; constexpr size_type rfind(charT c, size_type pos = npos) const noexcept; constexpr size_type find_first_of(basic_string_view v, size_type pos = 0) const noexcept; constexpr size_type find_first_of(charT c, size_type pos = 0) const noexcept; constexpr size_type find_last_of(basic_string_view v, size_type pos = npos) const noexcept; constexpr size_type find_last_of(charT c, size_type pos = npos) const noexcept; // 操作符 friend bool operator==(basic_string_view lhs, basic_string_view rhs) noexcept; friend bool operator!=(basic_string_view lhs, basic_string_view rhs) noexcept; friend bool operator<(basic_string_view lhs, basic_string_view rhs) noexcept; friend bool operator<=(basic_string_view lhs, basic_string_view rhs) noexcept; friend bool operator>(basic_string_view lhs, basic_string_view rhs) noexcept; friend bool operator>=(basic_string_view lhs, basic_string_view rhs) noexcept; // 常量 static constexpr size_type npos = size_type(-1); }; // 类型别名 using string_view = basic_string_view<char>; using wstring_view = basic_string_view<wchar_t>; using u16string_view = basic_string_view<char16_t>; using u32string_view = basic_string_view<char32_t>; } ``` std::string_view的使用非常简单,可以通过构造函数传入一个已有的字符串对象,也可以传入一个C风格字符串(以'\0'结尾)。然后就可以像操作字符串一样使用std::string_view了,例如获取字符串的长度、访问字符串中的字符、查找字符串等等。和std::string不同的是,std::string_view没有修改字符串的操作。 std::string_view的好处在于它比std::string更轻量级,不需要额外的内存分配和复制操作,因此在一些需要高效处理字符串的场景下,使用std::string_view可以减少不必要的开销。例如,在函数参数中传递字符串时,可以使用std::string_view来避免不必要的内存分配和复制,提高程序的性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值