C++ Primer Plus(第五版)完整习题与代码示例

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

简介:《C++ Primer Plus(第五版)》是C++编程的经典入门教材,提供了详尽的C++基础讲解。本资源集包括书中的例题代码和习题解答,对初学者和希望加强基础的开发者极具价值。介绍了C++的核心概念,如基础语法、指针与引用、类与对象、标准模板库(STL)、异常处理、文件I/O、模板、命名空间和预处理器宏,并通过实践项目加深理解。代码示例和习题解答帮助学习者通过实践加深理论理解,并提高编程技能。 C++ Primer Plus(第五版)习题解答和例题代码

1. C++基础语法回顾

1.1 C++程序的基本结构

C++程序由一个或多个源文件组成,每个文件包含一系列的函数定义和变量声明。程序的入口点是 main 函数。C++支持过程式编程,同时也提供了面向对象和泛型编程的特性。

#include <iostream>

// 声明全局变量
int g_count = 0;

// 定义函数
void increment() {
    ++g_count;
}

int main() {
    increment();
    std::cout << "Count is: " << g_count << std::endl;
    return 0;
}

1.2 数据类型和变量

C++定义了几种基本数据类型,包括整型、浮点型、字符型和布尔型。变量是存储数据的实体,必须声明类型才能使用。

int integerVar = 10;  // 整型变量
float floatVar = 3.14f;  // 浮点型变量
char charVar = 'A';  // 字符型变量
bool boolVar = true;  // 布尔型变量

1.3 控制结构

控制结构用于决定程序执行的流程。C++支持条件语句( if , switch )和循环语句( for , while , do-while )。

if (boolVar) {
    // 条件为真时执行
} else {
    // 条件为假时执行
}

for (int i = 0; i < 10; ++i) {
    // 循环执行10次
}

以上代码块展示了C++程序的基本结构、数据类型以及控制结构的基础用法,这些是学习C++时必须掌握的入门知识。接下来我们将探讨更复杂的主题,如指针、引用、面向对象编程等。

2. 深入指针与引用

2.1 指针的核心概念和应用

2.1.1 指针的定义和初始化

在C++中,指针是一个变量,其值为另一个变量的地址,或者说是指针指向另一个变量。指针是C++编程中一个非常基础且强大的概念,它允许程序操作内存地址中的数据,从而可以实现动态内存分配和管理。

定义指针的基本语法是:

数据类型 *指针变量名;

例如,定义一个指向整数的指针:

int *ptr;

初始化指针意味着给它赋予一个特定的内存地址。通常,我们会将指针初始化为 nullptr ,表示该指针当前不指向任何东西:

int *ptr = nullptr;

指针也可以直接初始化为变量的地址,例如:

int var = 10;
int *ptr = &var; // ptr现在指向var的地址

指针的初始化应该非常小心,因为一个未初始化的指针可能包含任意值,这可能会导致运行时错误。

2.1.2 指针与数组

指针和数组在C++中有着非常密切的关系。在大多数情况下,数组名可以被视为指向数组首元素的指针。例如:

int arr[] = {1, 2, 3};
int *ptr = arr; // ptr指向数组的第一个元素

通过指针来访问数组元素的方式与使用数组索引类似:

int firstElement = *ptr; // 通过解引用指针获取第一个元素的值

指针在处理数组时非常有用,特别是在需要动态处理数组大小或者需要将数组数据传递给函数时。

2.1.3 指针与动态内存管理

指针使得我们可以直接访问和操作内存,这对于动态内存分配尤其重要。在C++中,动态内存分配通常使用 new delete 操作符进行:

int *ptr = new int; // 动态分配一个int类型的内存
*ptr = 10; // 为分配的内存赋予一个值

delete ptr; // 释放之前分配的内存

动态内存管理允许程序在运行时分配内存,并在不再需要时释放它。这提供了极大的灵活性,但同时也要注意防止内存泄漏和野指针问题。

2.2 引用的定义和使用场景

2.2.1 引用的基本语法

引用可以看作是变量的别名,一旦一个引用被初始化为指向一个变量,它就始终指向这个变量。引用的声明语法如下:

数据类型 &引用名 = 被引用的变量名;

例如:

int var = 10;
int &ref = var; // ref是var的引用

通过引用,你可以直接通过别名访问变量的值或对其进行修改。

2.2.2 引用与函数参数

引用在函数参数传递中非常有用,特别是当我们想要修改传递给函数的参数时。通过使用引用,我们可以避免复制整个对象,这在传递大型对象时尤其重要。例如:

void increment(int &ref) {
    ref++;
}

int main() {
    int num = 5;
    increment(num); // 传递num的引用
    return 0;
}

在这个例子中, increment 函数接收一个引用参数,因此它能够直接修改 main 函数中的 num 变量。

2.2.3 引用与返回值

引用也可以作为函数的返回值,允许函数返回一个对象的引用而不是其副本。这在返回大型对象或者动态分配的对象时非常有用,因为它避免了对象的复制:

int& getValue() {
    static int x = 10;
    return x;
}

需要注意的是,返回局部变量的引用是不安全的,因为局部变量在函数返回后会被销毁。上述例子中使用 static 关键字是为了保持变量 x 的生命周期。

在使用引用时,必须注意确保引用始终指向有效的内存区域,避免悬挂引用的问题。

3. 面向对象编程的探索

面向对象编程(OOP)是现代编程范式的核心之一,它通过模拟现实世界中的实体和它们之间的关系,使软件设计更加模块化、易于理解和维护。在C++中,面向对象编程的实现尤为突出,因为它支持类、继承、多态和封装等面向对象的特性。本章将深入探索类和对象的基础知识以及面向对象编程的高级特性。

3.1 类和对象的基础

3.1.1 类的定义和对象的创建

类是C++中定义对象属性和行为的蓝图。类中的属性通常称为成员变量,而行为则是成员函数。类的定义以关键字 class 开始,后跟类名和一对花括号,里面包含成员变量和成员函数的声明。以下是类定义的一个基本示例:

class Rectangle {
private:
    int width, height;

public:
    // 构造函数
    Rectangle(int w, int h) : width(w), height(h) {}

    // 成员函数
    int area() {
        return width * height;
    }
};

在上面的代码中, Rectangle 类有两个私有成员变量 width height ,表示矩形的宽度和高度。还有一个构造函数用于创建对象时初始化这些变量,以及一个 area() 成员函数用于计算矩形的面积。

创建对象的过程非常直接。在C++中,你可以像声明基本类型变量一样声明类的实例:

Rectangle rect(10, 5);

这里, rect Rectangle 类的一个对象,使用构造函数进行初始化。

3.1.2 类的构造函数和析构函数

构造函数是一种特殊的成员函数,当创建类的新对象时自动调用。构造函数的任务是初始化新创建的对象。一个类可以有多个构造函数,这称为构造函数重载。默认构造函数是没有参数的构造函数。

析构函数在对象生命周期结束时被调用。析构函数用于释放对象占用的资源,并执行一些清理工作。析构函数不能重载,并且在类中只能有一个。析构函数的名字是在类名前加上一个波浪号( ~ )。

class Example {
public:
    Example() { /* 默认构造函数 */ }
    Example(int value) { /* 带参数的构造函数 */ }
    ~Example() { /* 析构函数 */ }
};

3.1.3 类的访问控制

C++提供了三种访问控制修饰符: public private protected ,用于控制对类成员的访问。

  • public 成员在类的外部是可访问的。
  • private 成员只能被类的成员函数、友元函数或者友元类访问。
  • protected 成员的行为类似于 private ,但它们在派生类中是可访问的。

默认情况下,类的成员都是 private 的,而结构体的成员默认是 public 的。访问控制对于封装和数据隐藏非常重要,因为它允许开发者控制对象的状态。

class MyClass {
private:
    int privateVar;

protected:
    int protectedVar;

public:
    void setPrivateVar(int value) {
        privateVar = value;
    }
};

在上面的例子中, privateVar 只能在 MyClass 内部访问, protectedVar 可以在 MyClass 及其派生类中访问,而 setPrivateVar 是一个 public 成员函数,允许外部代码设置私有变量的值。

3.2 面向对象高级特性

3.2.1 继承与多态

继承是面向对象编程中的一种机制,它允许新创建的类(称为派生类或子类)继承一个或多个已存在的类(称为基类或父类)的属性和方法。继承的主要目的是代码复用和增加新功能。

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

class Derived : public Base {
    // Derived class inherits all public members of Base
};

在上面的例子中, Derived 类继承了 Base 类。因此, Derived 类的对象可以使用从 Base 继承来的 print() 函数。

多态指的是允许不同类的对象对同一消息做出响应的能力。在C++中,多态是通过虚函数实现的。当函数声明为 virtual 时,C++会确保在派生类中适当地重写该函数。

class Base {
public:
    virtual void show() {
        std::cout << "Base class implementation" << std::endl;
    }
};

class Derived : public Base {
public:
    void show() override {
        std::cout << "Derived class implementation" << std::endl;
    }
};

int main() {
    Base* basePtr;
    Derived obj;
    basePtr = &obj;
    basePtr->show(); // Calls Derived class implementation
    return 0;
}

在这个例子中, Base 类有一个虚函数 show() ,而 Derived 类重写了这个函数。通过基类指针调用 show() 时,将调用 Derived 类的版本,展示了运行时多态。

3.2.2 抽象类和纯虚函数

抽象类是不能实例化的类,通常包含一个或多个纯虚函数。纯虚函数是一种特殊的虚函数,它没有实现,并且必须在派生类中被重写。

class Shape {
public:
    virtual void draw() = 0; // Pure virtual function
};

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

在上述代码中, Shape 是一个抽象类,因为它有一个纯虚函数 draw() Circle 继承自 Shape 并提供了 draw() 的实现。

3.2.3 模板类的使用

模板类是用模板参数化定义的类。模板允许为类定义成员函数和数据成员而不需要指定数据类型,提高了代码的复用性。

template <typename T>
class Stack {
private:
    std::vector<T> v;

public:
    void push(T item) { v.push_back(item); }
    void pop() { v.pop_back(); }
    T top() const { return v.back(); }
};

以上代码定义了一个通用的栈类 Stack ,它使用模板参数 T 来存储任意类型的数据。

本章的介绍让我们对面向对象编程有了初步的认识。接下来的章节将深入讨论更多关于继承、多态、模板类的高级用法等面向对象的高级特性,并通过实例加深理解。

4. 标准模板库(STL)实战演练

4.1 STL组件概览

4.1.1 容器的种类和选择

STL(Standard Template Library)为C++提供了一系列的模板类,这些模板类封装了数据结构和算法。在使用STL之前,了解不同容器的用途和特点至关重要。

STL容器可以分为序列式容器和关联式容器两大类:

  • 序列式容器 :存储的元素保持特定的顺序,允许重复值,包括 vector , deque , list , forward_list
  • 关联式容器 :存储的元素遵循特定的排序规则,不允许重复值,包括 set , multiset , map , multimap , unordered_set , unordered_multiset , unordered_map , unordered_multimap

选择合适的容器类型需要考虑以下因素:

  • 是否需要元素排序,是否允许重复值。
  • 需要频繁的插入和删除操作还是随机访问元素。
  • 需要的是双端队列(两端都可以进行插入和删除操作)还是单端队列(只能在一端进行插入和删除操作)。
  • 是否需要顺序访问。

例如,当需要快速随机访问时, vector 是更好的选择;当需要在两端插入或删除元素时, deque 更为合适。

4.1.2 迭代器的使用和特性

迭代器是STL中的核心概念,它们提供了一种统一的方法来访问不同类型的容器中的元素。

迭代器具有类似于指针的行为,通过使用迭代器,开发者可以遍历容器、读取和修改容器元素。不同的容器类型支持不同种类的迭代器,包括输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机访问迭代器。

在使用迭代器时,需要特别注意迭代器失效的情况,即在容器元素被删除或添加时,迭代器可能不再有效。如 list erase 方法会使得被删除元素的迭代器失效。

std::vector<int> numbers = {1, 2, 3, 4, 5};
std::vector<int>::iterator it = numbers.begin();
for (; it != numbers.end(); ++it) {
    std::cout << *it << ' ';
}

代码块中的迭代器 it 被用来遍历 numbers 容器中的所有元素,从头迭代至尾。

4.1.3 算法的基本概念和分类

STL算法库提供了超过100个预定义的函数模板,用于操作容器中的元素。这些算法可以分为以下几类:

  • 非修改式算法 :在不改变容器内容的情况下对元素进行操作。
  • 修改式算法 :修改容器中的元素。
  • 排序算法 :对容器中的元素进行排序。
  • 数值算法 :执行数学计算。

使用算法时,通常需要指定容器范围以及相关操作。迭代器在这里起了关键的作用,因为算法都是通过迭代器来访问容器的。

#include <algorithm> // 引入算法库
#include <vector>

std::vector<int> data = {3, 1, 4, 1, 5, 9};
std::sort(data.begin(), data.end()); // 对data中的元素进行排序

在这个例子中, std::sort 函数用于对 data 容器中的元素进行升序排序。

4.2 STL高级用法

4.2.1 函数对象和Lambda表达式

函数对象和Lambda表达式是C++11引入的特性,它们允许将函数作为参数传递给其他函数,为STL算法提供了更大的灵活性。

函数对象 是可以调用的对象,即重载了 operator() 的类实例。Lambda表达式则是一种定义匿名函数对象的简洁方式。

#include <algorithm>
#include <vector>

int main() {
    std::vector<int> data = {1, 2, 3, 4, 5};
    auto lambda = [](int x) { return x % 2 == 0; };
    std::remove_if(data.begin(), data.end(), lambda);
}

在这个例子中, lambda 是一个返回布尔值的匿名函数对象,它被用来移除 data 中的所有奇数元素。

4.2.2 线性容器与关联容器的比较

线性容器(如 vector , list 等)和关联容器(如 set , map 等)在内部实现和性能特征上有很大差别。

  • 线性容器 :元素是连续存储的,因此可以提供高效的随机访问,但在中间插入和删除元素时会导致元素的复制或移动,开销较大。
  • 关联容器 :元素根据键值排序存储,提供了对数时间复杂度的查找性能,支持快速的元素插入和删除操作。

关联容器通常实现为红黑树,而线性容器中的 vector 实现为动态数组, list 实现为双向链表。

#include <set>
#include <list>
#include <iostream>

int main() {
    std::set<int> set_data = {1, 2, 3};
    std::list<int> list_data = {4, 5, 6};
    set_data.insert(4); // O(log n) 插入操作
    list_data.insert(list_data.begin(), 4); // O(1) 插入操作
}

代码示例中, set_data 使用 set 容器添加一个元素,这需要对树进行重新平衡,其时间复杂度是 O(log n)。而 list_data 使用 list 容器在头位置插入元素,时间复杂度为 O(1)。

4.2.3 STL在实际项目中的应用案例

实际项目中,STL被广泛用于处理各种数据结构的管理,如集合运算、排序和搜索等。

例如,一个网站的用户管理模块可能会用到 set 容器来维护用户的唯一ID集合;一个日志管理系统可能会使用 multimap 来根据时间戳快速查找日志条目。

#include <map>
#include <iostream>

int main() {
    std::map<std::string, int> user_scores;
    user_scores["Alice"] = 100;
    user_scores["Bob"] = 95;
    user_scores["Charlie"] = 88;

    for (const auto& pair : user_scores) {
        std::cout << pair.first << " : " << pair.second << std::endl;
    }
}

上面的代码展示了如何使用 map 容器存储用户的名字和分数,并进行遍历输出。

通过STL的使用,可以大大减少代码量,提高代码的可读性和可维护性。同时,STL内部高度优化的实现能够保证良好的性能表现。

5. 掌握C++异常处理

异常处理是C++语言提供的一种机制,用于处理程序运行时可能出现的异常情况。C++中异常处理的引入,主要目的是为了提高程序的健壮性和可读性。异常处理允许程序在检测到错误或异常情况时,将错误处理代码与正常代码分离,使得程序的逻辑更加清晰。

5.1 异常处理的基本原理

异常处理机制主要包括以下三个部分:抛出异常、捕获异常和异常处理类。

5.1.1 抛出异常

抛出异常是指当程序运行到某些特定的错误条件时,通过 throw 语句显式地抛出一个异常对象。异常对象可以是任何类型,但通常是一个派生自 std::exception 的类对象。异常对象被抛出后,程序将停止当前的执行流程,转而寻找能够处理该异常的 catch 块。

#include <stdexcept>

void functionThatThrows() {
    // Some logic that may throw an exception
    throw std::runtime_error("A runtime error occurred");
}

在上述代码中,函数 functionThatThrows 中如果出现运行时错误,会抛出一个 std::runtime_error 异常对象。

5.1.2 捕获异常

捕获异常使用 try catch 关键字。 try 块包围的代码是可能抛出异常的代码部分,而 catch 块则用于捕获和处理特定类型的异常。

try {
    // Code that may throw an exception
    functionThatThrows();
} catch (const std::exception& e) {
    // Handle the exception
    std::cerr << "Caught an exception: " << e.what() << std::endl;
}

catch 语句可以有多个,每个语句捕获不同类型的异常。在捕获异常时,应当尽量捕获派生类型异常,避免捕获基类异常导致多条 catch 语句都能匹配到异常类型。

5.1.3 标准异常类的使用

C++标准库提供了一系列标准异常类,它们大多定义在 <stdexcept> 头文件中,可以用于在特定情况下抛出。这些类包括但不限于:

  • std::exception - 所有标准异常的基类。
  • std::runtime_error - 表示运行时错误。
  • std::logic_error - 表示逻辑错误。
  • std::out_of_range - 当参数或运算对象的值超出了有效范围时抛出。
try {
    // Some operation that could fail
    throw std::out_of_range("Index out of range");
} catch (const std::out_of_range& e) {
    // Handle the out of range exception
    std::cerr << "Out of range error: " << e.what() << std::endl;
}

在上述代码中,如果执行的索引超出了范围,会抛出一个 std::out_of_range 异常。

5.2 异常处理的最佳实践

在编写异常安全的代码时,需要遵循一些最佳实践来确保程序的鲁棒性。

5.2.1 设计异常安全的代码

异常安全代码指的是代码在抛出异常时,资源能够正确释放,对象保持一致的状态,没有资源泄露或数据损坏。

异常安全通常可以分为三个级别:

  • 基本保证 :异常发生后,程序不会泄露资源,且对象的状态是确定的。
  • 强保证 :异常发生后,程序状态回滚到异常发生之前的状态。
  • 无抛出保证 :异常安全函数保证不抛出异常。

要实现异常安全的代码,通常要使用RAII(Resource Acquisition Is Initialization)原则,它是一种利用对象生命周期来管理资源的技术。

5.2.2 异常与资源管理

在C++中,处理资源管理最常用的模式是RAII模式,即资源获取即初始化。这种模式通过构造函数获取资源,在对象生命周期结束时通过析构函数自动释放资源。

class FileGuard {
public:
    FileGuard(const std::string& filename) : file(filename, std::ios::out | std::ios::in) {
        if (!file.is_open()) {
            throw std::runtime_error("Could not open file " + filename);
        }
    }

    ~FileGuard() {
        if (file.is_open()) {
            file.close();
        }
    }

    std::fstream& operator()() {
        return file;
    }

private:
    std::fstream file;
};

void functionThatMightThrow() {
    FileGuard fileGuard("example.txt");
    std::fstream& file = fileGuard();
    // Work with the file...
}

// No need to explicitly close the file or handle exceptions for opening the file

在上述代码中, FileGuard 类负责文件的打开和关闭,确保了文件资源在异常发生时不会泄露。

5.2.3 异常处理在项目中的应用分析

在实际项目中,合理使用异常处理能够提高代码的健壮性。以下是一些实际项目中异常处理的应用分析:

  • 错误处理 :对于可能因各种原因失败的操作,如文件I/O、网络通信等,使用异常处理进行错误报告和管理。
  • 资源管理 :确保所有资源(如内存、文件句柄、数据库连接等)在异常发生时能够被正确释放。
  • 事务和回滚 :在需要事务处理的地方,如数据库操作,异常可以用来触发事务的回滚。
  • 测试和调试 :异常可以用于非正常的执行路径测试,帮助开发者定位和修复潜在的问题。

在实际开发中,对异常处理的使用需要平衡考虑代码的可读性、性能开销以及程序的健壮性。理想情况下,异常只用于处理异常情况,而不应该用于正常的控制流程。

6. 文件输入输出(I/O)操作详解

6.1 C++ I/O流基础

C++提供了强大的I/O流类库来处理文件的读写操作。通过I/O流类库,可以方便地对文件进行输入输出,而且可以轻松扩展到对内存对象的处理。

6.1.1 I/O流类库概述

C++ I/O流类库主要包含以下几个部分:

  • iostream :定义了用于输入输出的类,如 istream ostream iostream 等。
  • fstream :定义了用于文件操作的类,如 ifstream ofstream fstream 等。
  • sstream :定义了用于字符串流的类,如 istringstream ostringstream stringstream 等。

6.1.2 文件流的打开和关闭

文件流对象在使用前必须打开,使用完毕后需要关闭。

#include <fstream>
using namespace std;

int main() {
    // 打开文件
    ifstream infile("input.txt");
    if (!infile.is_open()) {
        cerr << "无法打开文件" << endl;
        return -1;
    }

    // 执行文件读取操作...

    // 关闭文件
    infile.close();
    return 0;
}

6.1.3 基本的文件读写操作

读写文件的基本操作包括读取单个字符、读取字符串、读取数据到变量,以及将数据写入文件。

#include <fstream>
#include <string>
using namespace std;

int main() {
    ifstream infile("input.txt");
    ofstream outfile("output.txt");

    if (!infile.is_open() || !outfile.is_open()) {
        cerr << "无法打开文件" << endl;
        return -1;
    }

    string str;
    while (infile >> str) {
        outfile << str << endl;
    }

    infile.close();
    outfile.close();
    return 0;
}

6.2 高级I/O操作技巧

除了基本的文件读写操作之外,C++ I/O流还提供了一系列的高级特性,以满足更复杂的文件处理需求。

6.2.1 文件指针和定位操作

通过文件指针可以控制文件读写位置,进行随机访问。

#include <fstream>
using namespace std;

int main() {
    fstream file("example.txt", ios::in | ios::out | ios::binary);

    if (!file.is_open()) {
        cerr << "无法打开文件" << endl;
        return -1;
    }

    // 移动文件指针到第10个字节位置
    file.seekp(10);
    file.write("test", 4);

    // 读取文件指针当前位置
    long pos = file.tellp();
    cout << "当前文件指针位置:" << pos << endl;

    file.close();
    return 0;
}

6.2.2 字符串流的使用

字符串流允许你在内存中进行I/O操作,非常适合处理需要临时存储数据的场景。

#include <sstream>
#include <iostream>
using namespace std;

int main() {
    ostringstream oss;
    oss << "这是字符串流测试" << 123;

    // 输出到控制台
    cout << oss.str() << endl;

    // 使用字符串流中的数据
    string str = oss.str();
    cout << "读取字符串:" << str << endl;
    return 0;
}

6.2.3 格式化输入输出控制

可以使用I/O流的格式化功能来控制数据的显示方式,比如设置宽度、填充字符、对齐方式等。

#include <iostream>
#include <iomanip>
using namespace std;

int main() {
    int num = 123;
    cout << "默认格式输出:" << num << endl;

    // 设置为16进制格式输出
    cout << "16进制格式输出:" << hex << num << endl;

    // 设置输出宽度为10,左对齐,填充字符为'*'
    cout << left << setfill('*') << setw(10) << num << endl;

    return 0;
}

以上章节详细介绍了C++中文件输入输出的基础知识和一些高级技巧,通过这些内容的学习和应用,可以更加高效地在C++中进行文件操作。

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

简介:《C++ Primer Plus(第五版)》是C++编程的经典入门教材,提供了详尽的C++基础讲解。本资源集包括书中的例题代码和习题解答,对初学者和希望加强基础的开发者极具价值。介绍了C++的核心概念,如基础语法、指针与引用、类与对象、标准模板库(STL)、异常处理、文件I/O、模板、命名空间和预处理器宏,并通过实践项目加深理解。代码示例和习题解答帮助学习者通过实践加深理论理解,并提高编程技能。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值