【南方科技大学“C/C++程序设计”课程】C/C++学习笔记

CPP学习笔记

1 CPP basic

1.1 main function

  • 主函数完整形式

    int main(int argc, char *argv[]){
         
         
        //
    }
    // 或者
    int main(int argc, char **argv){
         
         
        //
    }
    
  • 代码示例

    #include <iostream>
    
    using namespace std;
    
    int main(int argc, char *argv[])
    {
         
         
        for (int i = 0; i < argc; ++i)
        {
         
         
            cout << "第" << i << "个参数是" << argv[i] << " ";
        }
        cout << endl;
        return 0; // 可以不写
    }
    
    // 调用 ./cpp_lab abc def ghi
    // 输出 第0个参数是./cpp_lab 第1个参数是abc 第2个参数是def 第3个参数是ghi
    

1.2 数据的位表示

  • 包含头文件#include <bitset>

  • 指定位数,32位、64位等。

  • 创建 bitset 对象std::bitset<32> bits;

  • 将值赋给bitset对象,int value = -3; bits = value;

    #include <iostream>
    #include <bitset>
    
    int main() {
         
         
        // 假设我们处理一个32位的整数
        const int numBits = 32;
        int value = -3; // 我们要获取二进制表示的值
    
        // 创建一个 bitset 对象,大小为 numBits
        std::bitset<numBits> bits(value);
    
        // 输出二进制位表示
        std::cout << "The binary representation of the value is: " << bits << std::endl;
    
        // 将 bitset 转换为字符串形式并输出
        std::string bitString = bits.to_string();
        std::cout << "The binary string representation is: " << bitString << std::endl;
    
        return 0;
    }
    

1.3 trick

  • 批量更改变量名——refractor->rename

2 Compile and link

2.1 Function prototypes and definitions 函数原型和定义

  • function prototype(.h/.hpp)

    int add(int a, int b);
    
  • function definition(.c/.cpp)

    int add(int a, int b){
         
         
        return a + b;
    }
    

2.2 Compile and link

  • 编译

    g++ -c main.cpp # -c 表示只编译不链接,编译为 main.o
    g++ -c func.cpp # 编译为 add.o
    
  • 链接

    g++ main.o add.o -o add # -o表示输出
    

2.3 Debug

  • compilation errors 编译错误——语法错误
  • link errors 链接错误——undefinded/not found
  • runtime errors 运行时错误——除0异常

3 Preprocessor and macros 预处理器和宏

3.1 Preprocessor 预处理器

  • preprocessing instructions 预处理指令

    • define, undef, include, if, ifd, ef, ifndef, else, endif, line, error, pragma
  • 预处理指令由预处理器执行

  • #pragma once指令表示只include一次,该指令不能写在main函数中

3.2 Macros

  • 使用预处理器 #define 定义宏常数 PI 代替3.14

    #define PI 3.14
    

4 Input and output

4.1 C++ style outpout

  •   #include<iostream>
      int v = 100;
      std::cout << "v is " << v << "." << std::endl; // v is 100.
    

4.2 C++ style input

  •   int a, b;
      std::cin >> a >> b;
    

4.3 C style output

  •   #include<cstdio>
      int v = 100;
      printf("v is %d\n.", v); // v is 100.
    

4.4 C style input

  •   int v;
      scanf("%d", &v);
    

5 Integer type 整型

5.1 Int

  • declare and initialize

    int i; // 声明
    int j = 10; // 声明和初始化
    int k;
    k = 10; // 赋值
    
  • ⭐变量必须初始化

    • 不确定行为,程序崩溃,数据损坏,逻辑错误,安全漏洞等
  • overflow——没有警告,没有错误

    int num1 = 56789;
    int num2 = 56789;
    cout << num1 * num2 << endl; // -1069976775
    

5.2 Variable initialization

  • 整数初始化

    int num = 10;
    int num(10);
    int num{
         
         10}
    

5.3 signed and unsigned(64位系统)

  • 至少16位

    • signed int ∈ [ − 2 31 , 2 31 − 1 ] \in[-2^{31},2^{31}-1] [231,2311]

    • unsigned int ∈ [ 0 , 2 32 − 1 ] \in[0,2^{32}-1] [0,2321]

    • signed short int

    • unsigned short int

  • 至少32位

    • signed long int
    • unsigned long int
  • 至少64位

    • signed long long int
    • unsigned long long int

5.4 sizeof 操作符

  • 类型和变量sizeof 可以用于数据类型或变量。当用于类型时,它返回类型的尺寸;当用于变量时,它返回变量的尺寸。
  • 数组:当用于数组时,返回整个数组占用的内存大小,而不是数组单个元素的大小或指针的大小。
  • 指针:当用于指针时,返回指针本身的大小,而不是指针所指向的数据的大小。
  • 结构体和类sizeof 可以用来确定用户定义的类型,如结构体(struct)和类class的大小。
  • 动态内存sizeof 也可以用来确定动态分配的内存块的大小。
  • 复合字面量:C++11 引入了复合字面量,sizeof 可以直接应用于它们。
  • 类型安全sizeof 是类型安全的。如果操作数的类型是依赖于模板参数的,那么 sizeof 将在模板实例化时计算大小。
  • 宏和常量表达式sizeof 可以用在宏定义和常量表达式中,这使得它在编译时常用于条件编译和大小检查。
  • 内存对齐sizeof 的结果受编译器和平台的内存对齐规则的影响。有些类型可能由于对齐要求而比实际占用的内存稍大。

5.5 typedef

  • 创建别名,可以用来代替复杂的类型名

    unsigned char color[3] = {
         
         255, 0, 0};
    // 使用typedef
    typedef unsigned char vec3b[3];
    vec3b color = {
         
         255, 0, 0};
    

6 More integer types

6.1 Char

  • char:8位整数类型

    • ⭐signed and unsigned
  • 表示一个 ASCII 字符

    char c = 'C';  // 使用单引号包括字符
    char c = 80;   // 使用 ASCII 码数值
    char c = 0x50; // 使用十六进制 ASCII 数值
    
  • 中文字符

    char16_t c = u'中';
    char32_t c = U'中';
    

6.2 Bool

  • bool:C++ 关键字,非0即 true,占8个位

    int a = 1;
    bool b = true;
    int c = 0;
    bool d = false;
    cout << (a == b) << "," << (c == d) << endl; // 1,1
    
  • 在C语言中使用bool类型

    1. 使用typedef

      typedef char bool; // 定义一个新类型 bool,是 char 类型的别名
      #define true 1     // 定义宏 true 替换1
      #define false 0    // 定义宏 false 替换0
      
    2. 使用stdbool.h

      #include <stdbool.h>  //从 C99 开始,包含该库可使用 bool 类型
      bool tag = true;
      

6.3 size_t

  • 无符号整数,用于表示内存大小或数量
  • sizeof运算符的结果的类型
  • 32-bit,or 64-bit

6.4 <cstdint>

  • type
    • int8_t, int16_t, int32_t, int64_t
    • uint8_t, uint16_t, uint32_t, uint64_t
  • macros
    • INT8_MIN, INT16_MIN, INT32_MIN, INT64_MIN
    • INT8_MAX, INT16_MAX, INT32_MAX, INT64_MAX

7 Floating point number

7.1 Floating-point types

  • float-32位

  • double-64位

  • long double

    • 128位
    • 64位
  • half-float-16位

7.2 浮点型 VS 整型

  • 存储

    • 整数:通常使用二进制补码进行存储。
    • 浮点数:遵循 IEEE 754 标准,使用科学计数法的二进制形式存储。
  • 优缺点

    • 整数:运算精确,没有舍入误差;整数运算比浮点数运算更快;存储和传输内存使用更高效。
    • 浮点数:表示范围比整数更大,能够表示分数和小数;在高精度和大范围数值的科学计算中非常有用。
  • 应用场景

    • 整数:适用于计数、索引、数组大小、循环计数器等场景,以及任何需要精确数值计算的场合。
    • 浮点数:适用于需要表示小数或进行科学计算的场合,如物理模拟、图形渲染、金融计算等。

7.3 Precision

  • float 类型通常有 32 位的存储大小,包括 1 位符号位、8 位指数位和 23 位尾数位(小数位)。

  • [ 0 , 1 ] [0,1] [0,1]之间有无穷个数,用32位/64位不能表示所有小数

  • 浮点数运算操作永远会带来微小误差,这些误差不能被消除

  • 近似精度损失:比较浮点数不建议使用==

    float f1= 23456789345.0f; // 转化为可以接受的精度范围
    float f2= f1 + 10;
    cout << (f1 == f2) << endl; // 输出 1
    
  • 设置容差:在比较浮点数时,使用一个足够小的容差值来检查两个数是否“足够接近”。

    bool areAlmostEqual(float a, float b, float tolerance) {
         
         
    return fabs(a - b) <= tolerance;
    }
    

7.4 inf or nan

  • 无穷大(Infinity):当一个数除以零时,结果趋向于无限大。在浮点数表示中,这通常用一个特殊值来表示,即inf。对于正数除以零,结果是+inf;对于负数除以零,结果是-inf

  • NaN:NaN 是一个特殊的浮点数值,表示不是一个数字。它通常在不合法的运算中产生,比如零除零或者无穷大减去无穷大。

  • 检查inf和nan

    float f1 = 2.0f / 0.0f;
    float f2 = 0.0f / 0.0f;
    cout << f1 << "," << f2 << endl;                         // 输出inf,-nan
    cout << std::isinf(f1) << "," << std::isnan(f2) << endl; // 输出 1,1
    

8 Arithmetic operators 算数运算符

8.1 Constant numbers 常量

  • 注意事项

    1. 常量定义后必须初始化。
    2. 常量一旦定义,其值在程序的整个生命周期内不能被改变。
    3. 使用 const 定义的常量类型安全,而宏常数(使用 #define)不具备类型安全。
    4. 宏常数在预处理阶段展开,不进行类型检查,而 const 定义的常量在编译时检查。
  • 字面量常量:直接在代码中使用的固定值,如数字、字符或字符串。

    int maxInt = 2147483647;        // 整型字面量
    double pi = 3.14159;            // 双精度字面量
    char newlineChar = '\n';        // 字符字面量
    const char* greeting = "Hello"; // 字符串字面量
    
  • 常量指针和指针常量

    const int* ptr = &a;            // 指针常量,指向常量的指针
    int* const ptr2 = &a;           // 指针常量,指针本身的值不可变
    const int* const ptr3 = &a;     // 同时是指针常量和常量指针
    

8.2 const 类型限定符

  • 如果一个变量/对象是const类型,在声明时必须初始化,其值不能被改变

    const float PI = 3.1415926f;
    PI += 1; // error!
    

8.3 auto (since C++11)

  • ⭐注意事项

    1. auto 不是一个类型,而是让编译器根据上下文推断类型的一种方式。
    2. auto 必须被初始化,编译器不能在没有初始化的情况下推断其类型。
    3. 在C++11及以后的版本中,auto 的使用变得更加广泛和强大。
  • 自动类型推断:使用 auto 可以让编译器根据初始化表达式来推断变量的类型。

    auto a = 2;   		// a 的类型被推断为 int
    auto b = 2.3;  		// a 的类型被推断为 double
    auto c = "string";  // c 的类型被推断为 const char[]
    
  • 通用编程auto 在模板编程中非常有用,特别是与泛型算法和容器一起使用时,可以避免冗长的类型声明。

    std::vector<std::string> strings = {
         
         "Hello", "World"};
    for (auto it = strings.begin(); it != strings.end(); ++it) {
         
         
        std::cout << *it << std::endl;
    }
    
  • 范围 for 循环auto 在范围for循环中用于简化遍历容器元素的语法。

    for (auto str : strings) {
         
         
        std::cout << str << std::endl;
    }
    
  • lambda 表达式auto 在定义lambda表达式时用于声明捕获的变量。

    auto lambda = [capture](args) -> return_type {
         
          /* 函数体 */ };
    
  • 函数返回类型(since C++ 14):从 C++14 开始,auto 也可以用于函数返回类型,使函数能够返回与返回语句中表达式的类型相同的值。

    auto add(int a, int b) {
         
          // 返回类型自动推断为 int
        return a + b;
    }
    

8.4 算数运算符

  • 运算符
    在这里插入图片描述

8.5 ~ 取反运算符

  • 对于正数,~ 运算符会得到一个负数。这是因为正数的二进制补码表示取反后,变成了其对应的负数的补码表示。

  • 对于负数,~ 运算符的效果稍微复杂。由于负数在计算机中通常使用补码表示,取反操作会得到一个更大的负数。

  • 对于 0~ 运算符的结果取决于整数类型的大小。例如,在32位整数中,~0 的结果是 -1,因为所有位都是 1

  • ⭐注意事项

    1. ~ 运算符只应用于整数类型,包括有符号和无符号整数。
    2. 结果的符号取决于操作数的类型。对于有符号整数,取反可能会得到一个负数;对于无符号整数,结果将是一个正数,但表示为该类型的可能值之一。
    3. 在有符号整数的上下文中使用 ~ 运算符时,结果应该被解释为补码。这意味着取反操作实际上会得到该数的加法逆元(additive inverse)。
    4. ~ 运算符不适用于浮点数。
  • 对32位整数2取反

    2 的原码:0000 0000 0000 0000 0000 0000 0000 0010

    2 的反码:1111 1111 1111 1111 1111 1111 1111 1101

    -3 的原码:1000 0000 0000 0000 0000 0000 0000 0011

    -3 的反码:1111 1111 1111 1111 1111 1111 1111 1100

    -3 的补码:1111 1111 1111 1111 1111 1111 1111 1101

8.6 数据类型转换

  • 隐式类型转换(自动类型转换):C++编译器在需要时会自动进行类型转换,通常发生在赋值或函数参数传递时。

    • 小范围转换:从范围较小的类型到范围较大的类型,如 charint
  • 标准转换:如从整数类型到浮点类型。
```cpp
int i = 10;
double d = i; // 隐式转换 int 到 double
```
  • 显式类型转换:通过强制类型转换(cast)操作符显式地将一个类型转换为另一个类型。

    1. static_cast(expression):用于基本的非多态类型转换。
  1. dynamic_cast(expression):用于类层次结构中的安全向下转型。

  2. reinterpret_cast(expression):用于低级别的重新解释转换。

  3. const_cast(expression):用于移除或添加 const 属性。

```cpp
double d = 3.14;
int i = static_cast<int>(d); // 显式转换 double 到 int
```
  • 四舍五入转换:在转换整数类型时,可能会发生四舍五入。

    • 截断:丢弃小数部分,只保留整数部分。
    float f = 3.99f;
    int i = f; // i 将被赋值为 3
    
  • 整数提升:较小的整数类型(如 charshort)在参与运算时可能会被提升为 int 类型。

    char c = 5;
    int result = c + 10; // c 被提升为 int 类型
    
  • 浮点数转换:在将浮点数转换为整数时,小数部分会被丢弃。

    float f = 3.99f;
    int i = f; // i 将被赋值为 3
    
  • 字符串到数字的转换:可以使用 std::stoistd::stolstd::stollstd::stofstd::stodstd::stold 等函数将字符串转换为数字。

    string str = "123";
    int i = std::stoi(str);
    
  • 数字到字符串的转换:可以使用 std::to_string 函数将数字转换为字符串。

    int i = 123;
    std::string str = std::to_string(i);
    

9 Branch

9.1 if and if-else

  • else和最近的未匹配的if进行匹配

9.2 ? : operator

  • 三元运算符语法:condition ? expression1 : expression2;

    int x = 0;
    bool condition = true;
    if (condition) {
         
         
        x = 1;
    }
    else{
         
         
        x = -1;
    }
    // 使用三元运算符
    x = condition ? 1 : -1;
    // 优化跳转,保留计算
    x = (condition) * 2 -1
    

9.3 指针表达式

  • 空指针是0(false)

    int * p = new int[1024];
    if (!p) // if(p == NULL)
        cout << "分配内存失败!" << endl;
    

10 Loop

10.1 基础循环

  • for 循环:是一种基本的计数器循环,通常用于已知迭代次数的情况。

  • while循环:在给定条件为真时重复执行代码块,适用于迭代次数未知的情况。

  • do-while循环:与 while 循环类似,但它至少执行一次,因为条件判断在循环体之后。

10.2 范围 for 循环(since C++11)

  • 范围 for 循环允许遍历容器中的所有元素,而不需要使用索引

    #include <vector>
    #include <iostream>
    
    std::vector<int> numbers = {
         
         1, 2, 3, 4, 5};
    for (const auto& number : numbers) {
         
         
        std::cout << number << " ";
    }
    // 输出: 1 2 3 4 5
    

10.3 for 循环与迭代器(STL 容器)

  • 使用迭代器遍历标准模板库(STL)容器,如 std::vectorstd::list 等。

    #include <vector>
    #include <iostream>
    
    std::vector<int> numbers = {
         
         1, 2, 3, 4, 5};
    for (std::vector<int>::iterator it = numbers.begin(); it != numbers.end(); ++it) {
         
         
        std::cout << *it << " ";
    }
    // 输出: 1 2 3 4 5
    

10.4 for 循环与反向迭代器(STL 容器)

  • 反向迭代器允许你从容器的末尾开始向前遍历。

    #include <vector>
    #include <iostream>
    
    std::vector<int> numbers = {
         
         1, 2, 3, 4, 5};
    for (std::vector<int>::reverse_iterator rit = numbers.rbegin(); rit != numbers.rend(); ++rit) {
         
         
        std::cout << *rit << " ";
    }
    // 输出: 5 4 3 2 1
    

10.5 goto 语句

  • 虽然不推荐使用 goto 语句,但它们可以用于跳出多层循环。

    loop: // 标签
        for (int i = 0; i < 10; ++i) {
         
         
            for (int j = 0; j < 10; ++j) {
         
         
                if (i * j > 50) {
         
         
                    goto end; // 跳出两层循环
                }
            }
        }
    end:  // 标签
        std::cout << "Loop exited." << std::endl;
    

11 Array

11.1 静态array

  • 元素类型可以是任何基础类型(int, float, bool,等), structure, class, pointer, enumeration
  • 数组的大小必须是常量表达式,这意味着在编译时必须已知其大小。
  • 使用未初始化的局部数组(非静态)可能导致不确定的值。
  • 对于静态或全局数组,如果不显式初始化,将自动初始化为0。
  • 数组初始化时,元素的构造函数(如果存在)将被调用。

11.2 数组初始化

  • 静态初始化(使用初始化列表):在声明数组时,可以使用花括号 {} 包围的初始化列表来初始化数组。

    int arr1[] = {
         
         1, 2, 3, 4, 5}; // 声明并初始化一个整型数组
    std::string names[] = {
         
         "Alice", "Bob", "Charlie"}; // 声明并初始化一个字符串数组
    
  • 指定元素初始化:如果初始化列表中的元素少于数组的大小,剩余的元素将被初始化为默认值(对于类类型是默认构造函数的结果,对于基本类型通常是0)。

    int arr2[5] = {
         
         0, 1}; // arr2 = {0, 1, 0, 0, 0}
    
  • 复制初始化:如果数组的初始化列表只有一个元素,该元素的值将被复制到数组的所有元素中。

    int arr3[5] = {
         
         1}; // arr3 = {1, 1, 1, 1, 1}
    
  • 列表初始化(since C++ 11):列表初始化允许使用花括号初始化基本数组类型,而不需要显式指定类型。

    auto arr4 = {
         
         1, 2, 3, 4, 5}; // auto 推断为 int[]
    
  • 标准数组初始化(C++17引入):C++17允许使用 {} 直接初始化具有静态存储期或线程存储期的数组。

    int arr5[]{
         
         1, 2, 3, 4, 5}; // 从 C++17 开始,可以省略类型前的空格
    
  • 填充初始化:可以使用标准库函数 std::fillstd::fill_n 来初始化数组。

    int arr6[5];
    std::fill(arr6, arr6 + 5, 10); // 将 arr6 的所有元素初始化为 10
    

11.3 Variable-Length Arrays 变长数组(C99数组)

  • 变长数组在运行时确定数组的大小。

  • 其中 type 是数组元素的类型,array_name 是数组的名称,n 是数组的大小,必须是一个整型表达式,其值在编译时不必是常量。

    int size = 0;  // 声明一个int变量并初始化位0
    cin >> size;   // 运行时获取数组长度
    int arr[size]; // 声明一个变长数组
    
  • 变长数组不能在声明时初始化,必须在声明后逐个初始化。

  • 变长数组只能在函数内部使用,不能在全局或命名空间作用域内使用。

  • ⭐在C++11中,变长数组被标记为已弃用(deprecated),建议使用动态分配数组或标准容器库。

11.4 Arrays of unknown size 未知长度数组

  • 在声明时不指定数组长度

    int arr[] = {
         
         1,2,3,4,5}; // 由初始化列表长度推断,arr长度位5
    
  • 函数的参数列表

    int array_sum(int values[], size_t length); // 传入数组
    int array_sum(int * values, size_t length); // 传入指针
    

11.5 数组元素读写

  • ⭐注意事项

    1. 在读取或写入之前,应确保索引在有效范围内。
    2. 尝试访问数组之外的索引将导致未定义行为,可能触发运行时错误。
    3. 数组元素的类型应与数组声明的类型一致
    4. 写入操作会修改数组的内容。如果数组是 const 修饰的,那么它的元素就不能被修改。
    5. 数组在声明时分配内存,直到数组离开作用域或被显式释放前,内存才会被回收。
  • 使用指针遍历数组:数组名本身就是一个指向数组首元素的指针。

    int* ptr = arr; 										 	// 获取数组首地址
    while (ptr < arr + sizeof(arr) / sizeof(arr[0])) {
         
         
        std::cout << *ptr << " "; 								// 读取当前元素
        ++ptr; 													// 移动到下一个元素
    }
    

11.6 Multidimensional arrays 多维数组

  • 多维数组在科学计算、图像处理、游戏开发等领域中非常有用。

  • 由于多维数组的一些限制(如在函数间传递时的退化问题),有时会使用其他数据结构来替代,例如:

    • std::vector 的嵌套:使用 std::vector 可以创建动态的多维数组,并且可以更灵活地处理数组的大小和内存。
    • std::array 的嵌套:对于编译时常量大小的多维数组,可以使用 std::array
  • 二维数组的偏移量要手动确定,即数据第二维要手动赋值

    void init_2d_array(int matrix[][10], size_t rows, size_t cols);
    

11.7 数组拷贝

  • ⭐注意事项

    1. 当拷贝数组时,通常只拷贝数组的内容,而不是数组对象本身。
    2. 对于自定义类型或包含动态分配内存的数组,需要确保正确实现了拷贝构造函数和赋值操作符。
    3. 使用 memcpy 时要小心,因为它不会调用构造函数或析构函数,也不会处理指针或动态分配的内存。
    4. 对于C++标准库容器(如 std::vectorstd::array),可以直接使用赋值操作符来拷贝数组。
  • 手动拷贝:遍历数组,逐个元素赋值。

  • 使用std::copy函数(STL算法)

    #include <algorithm>                       // 包含 std::copy
    
    int source[] = {
         
         1, 2, 3, 4, 5};
    int target[4];
    std::copy(source, source + 4, target);
    
  • 使用数组的默认构造函数

    class MyArray {
         
         
    public:
        MyArray(const int arr[], size_t size) : data(arr, size) {
         
         }
    
    private:
        std::vector<int> data;
    };
    
    int main() {
         
         
        int source[] = {
         
         1, 2, 3, 4};
        MyArray myArray(source, 4);
        return 0;
    }
    
  • 使用memcpy函数

    #include <cstring>                          // 包含 memcpy
    
    int source[] = {
         
         1, 2, 3, 4, 5};
    int target[4];
    std::copy(target, source, sizeof(source));
    
  • 使用std::fill函数

    #include <algorithm>                         // 包含 std::fill
    
    int destination[4];
    std::fill(destination, destination + 4, 10); // 用10填充数组
    
  • 拷贝构造函数

    #include <iostream>
    #include <vector>
    
    // MyArray 类的定义
    class MyArray
    {
         
         
    public:
        // 默认构造函数
        MyArray() : data() {
         
         }
    
        // 通过 vector 初始化的构造函数
        explicit MyArray(const std::vector<int> &vec) : data(vec) {
         
         }
    
        // 拷贝构造函数
        MyArray(const MyArray &other) : data(other.data)
        {
         
         
            std::cout << "Copy constructor is called." << std::endl;
        }
    
        // 成员函数,打印数组内容
        void print() const
        {
         
         
            std::cout << "Array contains:";
            for (int num : data)
            {
         
         
                std::cout << " " << num;
            
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值