C++Builder编程技能全面提升指南

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:《C++Builder学习大全》是一本全面覆盖C++Builder集成开发环境的指南,适用于希望深入理解并掌握C++Builder的开发者。该资源详细介绍了C++语言基础、VCL框架、IDE功能、数据库集成、单元测试以及项目管理等内容。通过学习,读者能有效构建高性能应用程序,并掌握从基础到高级的各项技能。

1. C++语言基础语法

1.1 C++语法简介

C++是一种静态类型、编译式、通用的编程语言,它支持多种编程范式,包括过程化、面向对象和泛型编程。作为C语言的扩展,C++引入了类和对象的概念,这使得它在系统软件、游戏开发、嵌入式系统等领域得到广泛应用。在开始深入学习C++前,了解其基础语法是必要的,包括数据类型、变量、运算符、控制语句等。

1.2 变量声明与数据类型

C++中的每个变量都有一个特定的数据类型,这些类型包括基本类型如 int , float , char 等,以及通过 struct , class , enum 等自定义类型。变量声明指定了类型并为变量分配了存储空间。在C++11及更高版本中,还引入了类型推断关键字 auto decltype

#include <iostream>
using namespace std;

int main() {
    int number = 10; // 声明并初始化int类型变量
    float pi = 3.14159; // 声明并初始化float类型变量
    cout << "Number: " << number << ", Pi: " << pi << endl;
    return 0;
}

1.3 控制结构和运算符

控制结构(如条件语句和循环)和运算符(如算术和逻辑)是C++中实现程序逻辑的基本构造。使用这些结构可以使程序根据不同的条件执行不同的代码块,并进行必要的数学或逻辑运算。

#include <iostream>
using namespace std;

int main() {
    int score;
    cout << "Enter your score: ";
    cin >> score;
    if (score >= 60) {
        cout << "Passed!" << endl;
    } else {
        cout << "Failed..." << endl;
    }
    // for 循环示例
    for (int i = 0; i < 5; i++) {
        cout << i << " ";
    }
    cout << endl;
    return 0;
}

小结

本章概述了C++的基础语法知识,包括数据类型、变量、控制结构和运算符等概念,这是构建更复杂编程概念的基石。掌握这些基础知识对于深入理解后续章节中的面向对象编程、组件化编程、事件驱动编程等内容至关重要。

2. 面向对象编程核心概念

2.1 类与对象的创建和使用

2.1.1 类的定义和对象的实例化

在C++中,类是一种自定义的数据类型,它将数据和操作数据的函数封装起来。类的基本定义通常包括成员变量(属性)和成员函数(方法)。创建类的目的在于定义一个抽象的模板,可以使用这个模板来创建具体的实例,这些实例被称为对象。

class Person {
public:
    // 构造函数
    Person(std::string name, int age) : name_(name), age_(age) {}

    // 成员函数
    void introduce() {
        std::cout << "My name is " << name_ << " and I am " << age_ << " years old." << std::endl;
    }

private:
    std::string name_; // 成员变量(属性)
    int age_;          // 成员变量(属性)
};

int main() {
    // 实例化对象
    Person person("Alice", 30);
    person.introduce();
    return 0;
}

在上述示例中,我们定义了一个名为 Person 的类,其中包含一个构造函数用于初始化对象,一个成员函数 introduce 用于输出介绍信息,以及两个私有成员变量 name_ age_ 。在 main 函数中,我们通过构造函数创建了一个 Person 类的实例,并调用了 introduce 方法。

2.1.2 构造函数和析构函数的作用

构造函数是在创建对象时自动调用的特殊成员函数,用于初始化对象。C++中构造函数与类名相同,并且没有返回类型。如果一个类没有显式定义构造函数,编译器会自动生成一个默认的构造函数。析构函数的作用则与构造函数相反,它是在对象生命周期结束时自动调用的特殊成员函数,用于执行清理工作。

class Example {
public:
    Example() {
        // 构造函数代码
        std::cout << "An object is created." << std::endl;
    }

    ~Example() {
        // 析构函数代码
        std::cout << "An object is destroyed." << std::endl;
    }
};

int main() {
    Example obj;
    return 0;
}

在上面的代码中, Example 类包含了一个构造函数和一个析构函数。当 obj 对象在 main 函数中被创建时,构造函数被自动调用;当 main 函数结束时, obj 对象超出作用域,析构函数随之被调用。

2.1.3 访问控制和封装性

封装性是面向对象编程的核心概念之一。通过将数据和操作数据的函数封装在一起,类可以隐藏其内部的实现细节,只向外部暴露必要的接口。访问控制则是实现封装的一种手段,它规定了类成员的访问级别。

C++提供了三个访问控制关键字: public protected private public 成员可以被任何代码访问, protected 成员可以被派生类访问,而 private 成员只能被同一个类的成员函数和友元函数访问。

class EncapsulationDemo {
public:
    void publicMethod() {
        std::cout << "Public method" << std::endl;
    }

protected:
    void protectedMethod() {
        std::cout << "Protected method" << std::endl;
    }

private:
    void privateMethod() {
        std::cout << "Private method" << std::endl;
    }
};

class DerivedClass : public EncapsulationDemo {
public:
    void callMethods() {
        publicMethod();          // 可以访问
        protectedMethod();       // 可以访问,因为是派生类
        // privateMethod();      // 编译错误,不可访问
    }
};

int main() {
    EncapsulationDemo demo;
    demo.publicMethod();       // 可以访问
    // demo.protectedMethod(); // 编译错误,不可访问
    // demo.privateMethod();   // 编译错误,不可访问
    return 0;
}

EncapsulationDemo 类中,我们使用了不同的访问控制关键字来声明成员函数。通过创建一个派生类 DerivedClass 和在 main 函数中进行测试,我们可以验证访问权限的差异。这种机制确保了数据的安全性和类的结构清晰。

2.2 继承与多态的实现

2.2.1 继承的规则和方法

继承是面向对象编程中实现代码复用和建立类之间关系的重要机制。在C++中,继承意味着一个类(子类或派生类)可以继承另一个类(基类或父类)的成员变量和成员函数。通过继承,子类可以扩展或修改基类的行为。

继承的规则包括: - 子类继承父类的所有非私有成员。 - 子类可以添加新的成员变量和成员函数。 - 子类可以重写继承自父类的虚函数。

继承可以通过不同的访问修饰符来实现,常见的有 public protected private 继承。每种继承方式对基类成员的访问权限有不同的影响。

class Animal {
protected:
    std::string name;

public:
    Animal(std::string name) : name(name) {}

    virtual void makeSound() const {
        std::cout << "Animal makes a sound." << std::endl;
    }

    virtual ~Animal() {}
};

class Dog : public Animal {
public:
    Dog(std::string name) : Animal(name) {}

    void makeSound() const override {
        std::cout << name << " says: Woof!" << std::endl;
    }
};

int main() {
    Dog dog("Buddy");
    dog.makeSound(); // 输出 "Buddy says: Woof!"
    return 0;
}

在这个例子中, Dog 类继承自 Animal 类。 Dog 类重写了 Animal 类中的 makeSound 虚函数,并使用 override 关键字表示这是一个重写函数。这样的设计允许我们创建 Animal 类型的指针或引用,它可以指向 Animal 对象,也可以指向 Dog 对象,并且调用 makeSound 时将执行相应对象的实际代码。

2.2.2 虚函数与多态的原理

多态是面向对象编程的一个核心概念,它允许程序在运行时根据对象的具体类型决定其行为。在C++中,多态主要是通过虚函数(Virtual Functions)来实现的。当一个函数被声明为虚函数时,该函数在派生类中可以被重写。如果通过基类的指针或引用调用虚函数,那么实际调用的是对象实际类型的函数版本。

虚函数通过在基类中提供一个函数声明,并在派生类中提供一个匹配的函数实现(重写),使得通过基类指针或引用可以调用到派生类的函数,实现运行时的多态。

class Base {
public:
    virtual void print() const {
        std::cout << "Base class print function" << std::endl;
    }
};

class Derived : public Base {
public:
    void print() const override {
        std::cout << "Derived class print function" << std::endl;
    }
};

void processPrint(const Base& obj) {
    obj.print();
}

int main() {
    Base base;
    Derived derived;
    processPrint(base);     // 输出 "Base class print function"
    processPrint(derived);  // 输出 "Derived class print function"
    return 0;
}

在上面的例子中, Base 类有一个虚函数 print Derived 类重写了这个虚函数。 processPrint 函数接受 Base 类的引用,但传入 Derived 类的实例,多态行为允许 processPrint 调用 Derived 类的 print 函数。这展示了多态性如何在运行时决定调用哪个版本的函数。

2.2.3 抽象类和接口的运用

在C++中,抽象类是指至少包含一个纯虚函数的类。纯虚函数是一种声明了但没有实现的虚函数,通常以 = 0 在函数声明的末尾进行标识。抽象类不能实例化,但可以被继承。抽象类的主要作用是定义接口,强制派生类提供特定的实现。

接口则是一种特殊的抽象类,它通常只包含纯虚函数(即没有成员变量和非纯虚函数)。接口用于定义一系列操作,确保所有派生类都实现这些操作。

class AbstractShape {
public:
    virtual void draw() const = 0; // 纯虚函数
    virtual ~AbstractShape() {}
};

class Circle : public AbstractShape {
public:
    void draw() const override {
        std::cout << "Drawing Circle" << std::endl;
    }
};

class Rectangle : public AbstractShape {
public:
    void draw() const override {
        std::cout << "Drawing Rectangle" << std::endl;
    }
};

int main() {
    AbstractShape* shape1 = new Circle();
    AbstractShape* shape2 = new Rectangle();
    shape1->draw();
    shape2->draw();
    delete shape1;
    delete shape2;
    return 0;
}

在这个例子中, AbstractShape 是一个抽象类,它定义了一个 draw 函数的接口。 Circle Rectangle 类继承了 AbstractShape 并实现了 draw 函数。通过抽象类指针,我们可以在不知道具体对象类型的情况下调用 draw 函数,这展示了接口和抽象类在多态中的应用。

3. RTTI和组件化编程特性

3.1 运行时类型信息(RTTI)的应用

3.1.1 RTTI在程序中的作用

运行时类型信息(Run-Time Type Information,简称RTTI)是C++语言的一个重要特性,它允许程序在运行时确定一个对象的实际类型。RTTI主要用于以下几个方面:

  1. 类型安全的向下转换: dynamic_cast 运算符提供了一种类型安全的方式来将基类指针或引用转换为派生类指针或引用。
  2. 查询对象的实际类型: typeid 运算符允许程序获取表达式的类型信息。

RTTI在多态的应用中尤为关键,因为它能够帮助开发人员在运行时处理不同类型的对象。例如,在处理多态类型对象时,可能会有一个基类的指针或引用指向一个实际为派生类的实例,这时RTTI便能够帮助我们获取该对象的确切类型信息。

3.1.2 dynamic_cast和typeid的使用场景

dynamic_cast typeid 是实现RTTI的两个关键操作符:

  • dynamic_cast 用于将基类类型的指针或引用安全地转换为派生类类型的指针或引用。这种转换在运行时进行检查,如果转换不可能(例如,如果基类实际上并不指向或引用派生类类型的对象), dynamic_cast 将返回 nullptr (指针)或抛出 std::bad_cast 异常(引用)。

```cpp class Base { public: virtual ~Base() {} // 确保基类有虚析构函数 };

class Derived : public Base {};

Base b = new Derived(); Derived d = dynamic_cast (b); // 将Base 转换为Derived if (d) { // 转换成功,d指向一个Derived对象 } else { // 转换失败 } ``` *>

  • typeid 操作符用于获取一个表达式的类型信息。它可以与 std::type_info 类一起使用, std::type_info 类包含了一些函数,允许比较两个类型是否相同。

cpp Base* b = new Base(); std::cout << "The type of b is " << typeid(b).name() << std::endl; std::cout << "The type of *b is " << typeid(*b).name() << std::endl;

在实际编程中,RTTI的使用需要谨慎。过度使用RTTI可能会导致设计上的问题,如违反了面向对象设计的“开闭原则”(软件实体应该对扩展开放,对修改关闭)。因此,应当在确实需要在运行时确定对象类型时才使用RTTI,并尽可能通过设计来避免这种情况。

3.2 组件化编程的优势与实践

3.2.1 组件化编程的基本理念

组件化编程是一种将软件分割成独立、可复用且具有特定功能的组件的编程范式。在组件化编程中,组件是一段代码,它封装了自己的内部状态和行为,并提供了一组接口与外界通信。

组件化编程的基本理念包括:

  • 模块化:组件作为独立的模块,可以独立开发、测试和维护。
  • 重用性:通过组件的重用,可以减少开发工作量和错误,提高开发效率。
  • 解耦合:组件之间仅通过接口进行交互,减少了代码间的依赖和耦合。
  • 易于扩展:在不影响其他部分的情况下,可以方便地添加、替换或更新组件。
  • 易于维护:每个组件只负责一个功能点,因此单个组件的错误不会影响到整个系统的稳定性。

3.2.2 在C++Builder中的组件编程实践

C++Builder是一个支持组件化编程的集成开发环境(IDE),它提供了一个丰富的组件库,允许开发者以可视化的方式拖放组件,并为它们编写事件处理代码。

在C++Builder中实践组件化编程通常包括以下步骤:

  • 识别并创建独立的功能组件。
  • 定义组件的属性、方法和事件。
  • 使用Form Designer来布局组件,并为组件事件编写事件处理代码。
  • 通过继承现有的组件类来定制和扩展组件行为。

下面是一个简单的例子,展示如何在C++Builder中使用组件:

// 假设我们有一个TButton组件,名为Button1
Button1->OnClick = [](TObject* Sender) {
    ShowMessage("Button clicked!");
};

这里, Button1 是一个TButton组件的实例,我们为它的 OnClick 事件注册了一个lambda表达式作为事件处理器。当按钮被点击时,将显示一个消息框。

在C++Builder中进行组件化编程的一个关键优势是能够利用其VCL(Visual Component Library)和FireMonkey框架,这些框架提供了大量的预建组件和功能,简化了复杂功能的实现。

通过组件化编程,C++Builder不仅提升了开发效率,而且使得软件更容易维护和升级。开发者可以专注于单个组件的开发,而整个应用的构建则通过这些组件的组合来完成。这使得C++Builder成为一个强大的工具,尤其适合开发复杂的桌面应用和企业级软件。

4. IDE可视化界面设计工具

4.1 Form Designer的使用方法

4.1.1 Form的布局和控件添加

Form Designer是C++Builder中用于设计可视界面的强大工具。在开始界面设计之前,首先要了解Form的概念,它相当于Windows应用程序中的窗口。Form的设计包括布局和控件的添加,布局主要涉及控件在Form上的位置和大小,而控件添加则指的是将各种组件放置到Form上以实现特定功能。

创建一个新的Form后,可以开始添加控件。首先从工具箱中选择需要的控件,然后在Form上拖拽以放置。为了快速定位控件,可以使用对齐工具,确保控件按照预定的布局排列整齐。控件之间的间隔可以通过调节其 Align 属性来实现,而对齐可以通过 Anchor 属性进行调整,以应对窗口大小改变时控件的自适应。

布局设计中常见的一个操作是使用停靠(Docking)功能,允许控件“停靠”到Form的边缘。这种方式可以节省布局时间,并且使界面看起来更加整洁。此外,通过 TabOrder 属性,可以设置控件的Tab顺序,使用户可以通过Tab键在不同控件间切换。

// 示例代码块,演示如何添加一个按钮控件到Form上
void __fastcall TForm1::FormCreate(TObject *Sender)
{
    // 创建一个新的TButton控件
    TButton *Button = new TButton(this);
    // 设置按钮的标题
    Button->Caption = "Click Me";
    // 设置按钮的位置
    Button->Left = 100;
    Button->Top = 50;
    // 设置按钮的大小
    Button->Width = 100;
    Button->Height = 40;
    // 将按钮添加到Form的控件列表中
    this->Controls->Add(Button);
}

该代码展示了如何在C++Builder中通过编程方式添加一个按钮控件到Form上。通过设置控件的位置和大小,可以实现复杂的布局,同时也可以在运行时动态地创建界面。

4.1.2 事件处理和组件属性设置

在设计好了界面布局和控件添加之后,接下来的核心工作是编写事件处理逻辑,以及对组件属性进行设置,以实现程序的功能。在C++Builder中,事件处理通常是通过事件连接(event sinks)实现的。每个控件都可以响应特定的事件,比如点击按钮、文本变化等。

事件处理首先需要确定事件的来源控件和事件类型,然后在代码中编写事件的响应函数。比如一个按钮被点击,它会触发一个 OnClick 事件。开发者需要在相应的事件处理函数中编写具体的逻辑。

组件属性的设置主要是为了调整控件的行为和外观,使得界面更加友好。在设计时,可以通过属性编辑器直接在IDE中设置。属性可以是字体、颜色、尺寸等,也可以是控件的特定功能行为,如 Enabled 属性表示控件是否可用。

以下是一个按钮点击事件处理函数的示例,它展示了如何在用户点击按钮时改变另一个文本框的内容:

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    // 设置文本框的新内容
    Edit1->Text = "按钮被点击了";
}

通过这种事件驱动的编程模型,开发者可以很直观地为用户界面的每一个动作编写相应的逻辑,增强应用程序的交互性。而组件属性的合理设置,则是提升用户体验的关键一步。

5. 代码编辑器高级功能

5.1 代码自动完成与模板

代码自动完成的原理与应用

代码自动完成(IntelliSense)是现代IDE中一项重要的功能,它能够显著提高开发效率和准确性。在C++的开发中,IntelliSense通过分析代码上下文、类型信息以及符号定义,为用户提供函数、类、变量等符号的提示。它依赖于强大的语言解析器,通常与编译器后端的数据集成,确保能够提供实时和准确的代码建议。

实现IntelliSense的关键在于符号数据库。这个数据库在编译代码时生成,并持续更新以反映代码的最新状态。当开发者在编辑器中编写代码时,这个数据库就会被查询,以提供智能提示。

使用代码自动完成功能时,开发者只需要开始键入代码,例如调用一个函数或创建一个对象,编辑器会自动弹出一个列表,列出所有可能的完成项。如果按下了特定的快捷键,IDE还能够显示当前符号的详细信息,例如参数类型、返回类型、成员变量等。

// 示例代码段
#include <iostream>
#include <vector>

int main() {
    std::vector<int> numbers;
    numbers. // 在此处,IDE将展示成员函数列表进行自动完成功能
}

在上面的代码段中,当点出 numbers. 之后IDE会根据 std::vector<int> 类型,展示出可以调用的所有成员函数列表,如 push_back() size() 等。

代码自动完成不仅限于当前作用域内的符号,它也可以根据包含的头文件、库以及之前的代码编写历史来提供相关的补全建议。

代码模板的创建和使用

代码模板是另一种强大的编辑器功能,允许开发者预定义代码段,并在需要时快速插入到代码中。模板可以用于快速生成常用的代码结构,如循环、条件判断、类定义等,从而减少重复的编码工作。

在大多数现代IDE中,创建自定义代码模板是可能的,一些IDE甚至提供了内置模板。要创建一个新的模板,开发者首先需要定义一个包含变量的代码段。然后在需要使用模板时,通过特定的触发指令(例如快捷键)来插入模板,并替换其中的变量为实际需要的值。

例如,创建一个简单的 for 循环模板,可能包含如下内容:

for (int $iterator$ = 0; $iterator$ < $count$; ++$iterator$) {
    // 循环体内容
}

使用时,可以在编辑器中输入一个触发关键字(比如 forloop ),IDE会自动展开上述模板,并允许用户输入特定的变量值来完成模板的个性化填充。

forloop // 触发模板展开

for (int i = 0; i < 10; ++i) {
    // 循环体内容
}

在上例中, $iterator$ $count$ 会被视为模板变量,编辑器会提示用户输入这些变量的具体值。一旦完成替换,模板就会生成目标代码结构。

代码模板不仅限于简单结构,还可以用于创建复杂的类、函数或整个程序框架。模板库的建立,对于标准化代码风格和加速开发流程具有重要意义。

6. VCL组件库使用和定制

6.1 VCL组件库的核心组件

6.1.1 数据感知组件的使用

VCL(Visual Component Library)是Borland公司开发的一套用于快速开发基于Windows应用程序的组件库。在VCL中,数据感知组件主要是指那些能够直接与数据源交互的组件,它们简化了数据处理流程,并使开发者能够以声明方式执行数据绑定和操作。

数据感知组件如 TDBGrid , TDBText , TDBComboBox , 和 TDBNavigator 等,这些组件在数据库应用程序中扮演着重要的角色。例如, TDBGrid 组件可以用来展示和编辑数据库表格的数据,而 TDBNavigator 提供了直观的导航控制。

使用数据感知组件通常涉及以下步骤:

  1. 配置数据库连接(例如,通过 TADOConnection TDataSource )。
  2. 将数据感知组件的 DataSource 属性指向步骤1中配置的数据源。
  3. 设置其他属性,如 DataField ,来指定组件与数据表中的哪个字段绑定。

6.1.2 常用的非视觉组件介绍

除了数据感知组件外,VCL还包括许多非视觉组件,这些组件在后台为应用程序提供各种支持功能,如计时器、文件操作、系统信息获取等。一些常用的非视觉组件包括:

  • TTimer :可以在指定的间隔执行代码或进行其他操作。
  • TIniFile :提供读写Windows注册表之外的INI文件的能力。
  • TStringList :提供对字符串列表的高级操作功能,包括字符串排序、查找等。

使用非视觉组件通常需要在代码中创建相应的实例,然后通过程序逻辑调用其方法或者获取其属性值。例如,创建一个定时器并设置其 Interval 属性(以毫秒为单位)来定期触发事件。

var
  MyTimer: TTimer;
begin
  MyTimer := TTimer.Create(nil);
  MyTimer.Interval := 1000; // 1 second
  MyTimer.OnTimer := TimerEvent; // Linking the OnTimer event to the TimerEvent method
  MyTimer.Enabled := True;
end;

procedure TimerEvent(Sender: TObject);
begin
  // Code to execute every second
end;

6.2 组件库的扩展和定制

6.2.1 组件的继承与扩展

在VCL中,几乎所有的组件都可以被继承并扩展其功能。开发者可以根据自己的需求创建新的组件类型,并从现有的组件类型派生新类。例如,从 TButton 继承一个自定义的按钮类,添加新的属性或方法。

type
  TMyButton = class(TButton)
  private
    FCustomProperty: string;
  public
    procedure Click; override;
  published
    property CustomProperty: string read FCustomProperty write FCustomProperty;
  end;

procedure TMyButton.Click;
begin
  inherited Click;
  // Additional behavior for custom button
end;

使用 published 关键字来声明自定义属性,意味着这些属性将出现在对象检查器中,便于开发者在设计时设置这些属性。

6.2.2 创建自定义组件的方法

创建自定义组件需要几个关键步骤:

  1. 定义新组件类 :继承一个已存在的VCL组件类。
  2. 添加新属性、方法和事件 :在新类中添加额外的功能。
  3. 注册新组件 :将新组件添加到组件面板中,使其可以拖放到Form上。
type
  TMyComponent = class(TComponent)
  private
    // Private data
  protected
    // Protected methods and properties
  public
    constructor Create(AOwner: TComponent); override;
  published
    // Published properties
  end;

constructor TMyComponent.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  // Initialize component properties
end;

procedure Register;
begin
  RegisterComponents('MyComponents', [TMyComponent]);
end;

在上述代码示例中, RegisterComponents 函数将新组件添加到VCL组件面板上指定的页签中。通过这种方式,开发者可以直观地在设计视图中使用自定义组件。

这些自定义组件不仅可以在当前项目中使用,还可以通过编译为DLL文件,被其他项目重复使用。组件库的扩展和定制是提高开发效率和代码复用性的关键技术之一。

请记住,每个章节的最后一行,不要输出总结性的内容。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:《C++Builder学习大全》是一本全面覆盖C++Builder集成开发环境的指南,适用于希望深入理解并掌握C++Builder的开发者。该资源详细介绍了C++语言基础、VCL框架、IDE功能、数据库集成、单元测试以及项目管理等内容。通过学习,读者能有效构建高性能应用程序,并掌握从基础到高级的各项技能。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值