指针变量和引用变量有什么区别?

问:

指针变量和引用变量有什么区别?

答1:

huntsbot.com洞察每一个产品背后的需求与收益,从而捕获灵感

可以重新分配指针: int x = 5;整数 y = 6;诠释* p; p = &x; p = &y; *p = 10;断言(x == 5);断言(y == 10);引用不能重新绑定,必须在初始化时绑定:int x = 5;整数 y = 6;诠释&q; // 错误 int &r = x;指针变量有它自己的标识:可以用一元 & 运算符获取的独特的、可见的内存地址和可以用 sizeof 运算符测量的一定量的空间。在引用上使用这些运算符会返回一个与引用绑定的值相对应的值;引用自己的地址和大小是不可见的。由于引用以这种方式假定原始变量的身份,因此可以方便地将引用视为同一变量的另一个名称。诠释 x = 0; int &r = x; int *p = &x; int *p2 = &r;断言(p == p2); // &x == &r 断言(&p != &p2);您可以将任意嵌套的指针指向提供额外间接级别的指针。引用仅提供一级间接。诠释 x = 0;整数 y = 0; int *p = &x;整数 *q = &y; int **pp = &p; **pp = 2; pp = &q; // *pp 现在是 q *pp = 4;断言(y == 4);断言(x == 2);可以为指针分配 nullptr,而引用必须绑定到现有对象。如果你足够努力,你可以绑定一个对 nullptr 的引用,但这是未定义的,并且不会表现得一致。 / 下面的代码是未定义的;你的编译器可能会以不同的方式优化它,发出警告,或者完全拒绝编译它 */ int &r = *static_cast(nullptr); // 在 GCC 10 下打印 “null” std::cout << (&r != nullptr ? “not null” : “null”) << std::endl; bool f(int &r) { return &r != nullptr; } // 在 GCC 10 下打印 “not null” std::cout << (f(*static_cast(nullptr)) ? “not null” : “null”) << std::endl;但是,您可以引用值为 nullptr 的指针。指针可以遍历数组;您可以使用 ++ 转到指针指向的下一个项目,使用 + 4 转到第 5 个元素。这与指针指向的对象大小无关。指针需要用 * 取消引用才能访问它指向的内存位置,而引用可以直接使用。指向类/结构的指针使用 -> 访问其成员,而引用使用 … 引用不能放入数组中,而指针可以(用户 @litb 提到)常量引用可以绑定到临时对象。指针不能(不是没有间接性): const int &x = int(12); // 合法的 C++ int *y = &int(12); //非法获取临时地址。这使得 const & 在参数列表等中使用更方便。

...但是取消引用 NULL 是未定义的。例如,您无法测试引用是否为 NULL(例如,&ref == NULL)。

数字 2 不正确。引用不仅仅是“同一变量的另一个名称”。引用可以以与指针非常相似的方式传递给函数、存储在类中等。它们独立于它们指向的变量而存在。

布赖恩,堆栈不相关。引用和指针不必占用堆栈空间。它们都可以在堆上分配。

Brian,变量(在本例中为指针或引用)需要空间这一事实并不意味着它需要堆栈上的空间。指针和引用可能不仅指向堆,它们实际上可能分配在堆上。

另一个重要的区别:引用不能填充到数组中

答2:

huntsbot.com聚合了超过10+全球外包任务平台的外包需求,寻找外包任务与机会变的简单与高效。

什么是 C++ 参考(针对 C 程序员)

reference 可以被认为是具有自动间接寻址的 常量指针(不要与指向常量值的指针混淆!),即编译器将应用 {1 } 运算符为您服务。

所有引用都必须用非空值初始化,否则编译将失败。既不可能获得引用的地址——地址运算符将返回被引用值的地址——也不可能对引用进行算术运算。

C 程序员可能不喜欢 C++ 引用,因为当间接发生或如果参数通过值或指针传递而不查看函数签名时,它将不再明显。

C++ 程序员可能不喜欢使用指针,因为它们被认为是不安全的——尽管引用实际上并不比常量指针更安全,除非在最微不足道的情况下——缺乏自动间接的便利性并带有不同的语义内涵。

考虑 C++ FAQ 中的以下语句:

尽管引用通常使用底层汇编语言中的地址来实现,但请不要将引用视为指向对象的有趣指针。引用是对象。它不是指向对象的指针,也不是对象的副本。它是对象。

但如果引用真的是对象,怎么会有悬空引用呢?在非托管语言中,引用不可能比指针更“安全”——通常没有一种方法可以跨范围边界可靠地对值进行别名!

为什么我认为 C++ 参考很有用

来自 C 背景,C++ 引用可能看起来有点愚蠢,但仍应尽可能使用它们而不是指针:自动间接 方便,并且引用在处理 {1 } - 但不是因为任何感知到的安全优势,而是因为它们使编写惯用代码变得不那么尴尬。

RAII 是 C++ 的核心概念之一,但它与复制语义的交互非常重要。通过引用传递对象避免了这些问题,因为不涉及复制。如果语言中不存在引用,则必须改用指针,这使用起来更麻烦,从而违反了最佳实践解决方案应该比替代方案更容易的语言设计原则。

@kriss:不,您还可以通过按引用返回自动变量来获得悬空引用。

@kriss:在一般情况下,编译器几乎不可能检测到。考虑一个返回对类成员变量的引用的成员函数:这是安全的,编译器不应禁止。然后具有该类的自动实例的调用者调用该成员函数并返回引用。 Presto:悬空参考。是的,这会造成麻烦,@kriss:这就是我的观点。许多人声称引用优于指针的一个优点是引用总是有效的,但事实并非如此。

@kriss:不,对自动存储持续时间对象的引用与临时对象非常不同。无论如何,我只是为您的陈述提供一个反例,即您只能通过取消引用无效指针来获得无效引用。 Christoph 是正确的——引用并不比指针更安全,只使用引用的程序仍然会破坏类型安全。

引用不是一种指针。它们是现有对象的新名称。

@catphive:如果您按照语言语义,则为 true,如果您实际查看实现,则不是 true; C++ 是一种比 C 更“神奇”的语言,如果你从引用中移除魔法,你最终会得到一个指针

答3:

huntsbot.com – 高效赚钱,自由工作

如果你想真正迂腐,你可以用引用做一件你不能用指针做的事情:延长临时对象的生命周期。在 C++ 中,如果将 const 引用绑定到临时对象,则该对象的生命周期将变为引用的生命周期。

std::string s1 = "123";
std::string s2 = "456";

std::string s3_copy = s1 + s2;
const std::string& s3_reference = s1 + s2;

在此示例中,s3_copy 复制作为连接结果的临时对象。而 s3_reference 本质上成为临时对象。它实际上是对临时对象的引用,该对象现在与引用具有相同的生命周期。

如果您在没有 const 的情况下尝试此操作,它应该无法编译。您不能将非常量引用绑定到临时对象,也不能为此获取其地址。

但是这个用例是什么?

好吧,s3_copy 将创建一个临时文件,然后将其复制到 s3_copy 中,而 s3_reference 直接使用临时文件。然后要真正迂腐,您需要查看返回值优化,允许编译器在第一种情况下省略复制构造。

@digitalSurgeon:那里的魔法非常强大。 const & 绑定的事实延长了对象的生命周期,并且仅当引用超出范围时,实际引用类型的析构函数(与引用类型相比,这可能是base) 被调用。由于它是参考,因此不会在两者之间进行切片。

C++11 更新:最后一句应为“您不能将非常量左值引用绑定到临时对象”,因为您可以将非常量右值引用绑定到临时对象,并且它具有相同的生命周期延长行为。

@AhmadMushtaq:它的主要用途是派生类。如果不涉及继承,您不妨使用值语义,由于 RVO/move 构造,这将是便宜或免费的。但是如果您有 Animal x = fast ? getHare() : getTortoise(),那么 x 将面临经典的切片问题,而 Animal& x = ... 将正常工作。

答4:

huntsbot.com – 程序员副业首选,一站式外包任务、远程工作、创意产品分享订阅平台。

除了语法糖,引用是一个 const 指针(not 指向 const 的指针)。您必须在声明引用变量时确定它所引用的内容,并且以后不能更改它。

更新:现在我想多了,有一个重要的区别。

const 指针的目标可以通过获取其地址并使用 const 强制转换来替换。

引用的目标不能以任何方式替换 UB。

这应该允许编译器对引用进行更多优化。

我认为这是迄今为止最好的答案。其他人谈论引用和指针就像它们是不同的野兽,然后列出它们在行为上的不同之处。恕我直言,这并没有让事情变得更容易。我一直将引用理解为具有不同语法糖的 T* const(这恰好从您的代码中消除了很多 * 和 & )。

“一个常量指针的目标可以通过获取它的地址并使用常量转换来替换。”这样做是未定义的行为。有关详细信息,请参阅 stackoverflow.com/questions/25209838/…。

尝试更改引用的所指对象或 const 指针(或任何 const 标量)的值都是非法的。您可以做什么:删除由隐式转换添加的 const 限定条件:int i; int const *pci = &i; /* implicit conv to const int* */ int *pi = const_cast(pci); 可以。

这里的区别是UB与字面上不可能。 C++ 中没有可以让您更改参考点的语法。

并非不可能,更难的是,您可以访问正在建模该引用并更改其内容的指针的内存区域。这当然可以做到。

答5:

一个优秀的自由职业者,应该有对需求敏感和精准需求捕获的能力,而huntsbot.com提供了这个机会

与流行的观点相反,可以有一个为 NULL 的引用。

int * p = NULL;
int & r = *p;
r = 1;  // crash! (if you're lucky)

诚然,参考文献要困难得多——但如果你能做到,你会为了找到它而绞尽脑汁。引用在 C++ 中并不是天生安全的!

从技术上讲,这是一个无效的引用,而不是一个空引用。 C++ 不支持空引用作为您在其他语言中可能发现的概念。还有其他类型的无效引用。任何无效引用都会引发未定义行为的幽灵,就像使用无效指针一样。

实际错误在于在分配给引用之前取消引用 NULL 指针。但是我不知道有任何编译器会在这种情况下产生任何错误——错误会传播到代码中的某个点。这就是使这个问题如此阴险的原因。大多数情况下,如果您取消引用 NULL 指针,您就会在该位置崩溃,并且不需要太多调试即可解决。

我上面的例子很短而且做作。这是一个更真实的例子。

class MyClass
{
    ...
    virtual void DoSomething(int,int,int,int,int);
};

void Foo(const MyClass & bar)
{
    ...
    bar.DoSomething(i1,i2,i3,i4,i5);  // crash occurs here due to memory access violation - obvious why?
}

MyClass * GetInstance()
{
    if (somecondition)
        return NULL;
    ...
}

MyClass * p = GetInstance();
Foo(*p);

我想重申,获得空引用的唯一方法是通过格式错误的代码,一旦你拥有它,你就会得到未定义的行为。 从不检查空引用是有意义的;例如,您可以尝试 if(&bar==NULL)… 但编译器可能会优化该语句不存在!有效的引用永远不能为 NULL,因此从编译器的角度来看,比较总是错误的,并且可以自由地将 if 子句作为死代码消除 - 这是未定义行为的本质。

避免麻烦的正确方法是避免取消引用 NULL 指针来创建引用。这是实现此目的的自动化方法。

template
T& deref(T* p)
{
    if (p == NULL)
        throw std::invalid_argument(std::string("NULL reference"));
    return *p;
}

MyClass * p = GetInstance();
Foo(deref(p));

有关具有更好写作技巧的人对这个问题的较早看法,请参阅 Jim Hyslop 和 Herb Sutter 的Null References。

有关取消引用空指针的危险的另一个示例,请参见 Raymond Chen 的 Exposing undefined behavior when trying to port code to another platform。

有问题的代码包含未定义的行为。从技术上讲,你不能对空指针做任何事情,除了设置它并比较它。一旦你的程序调用了未定义的行为,它就可以做任何事情,包括在你给大老板演示之前看起来正常工作。

mark 有一个有效的参数。指针可能为 NULL 并且因此您必须检查的参数也不是真实的:如果您说函数需要非 NULL,那么调用者必须这样做。因此,如果调用者不这样做,他将调用未定义的行为。就像标记对坏参考所做的那样

描述是错误的。此代码可能会或可能不会创建 NULL 引用。它的行为是未定义的。它可能会创建一个完全有效的参考。它可能根本无法创建任何引用。

@David Schwartz,如果我在谈论事情必须按照标准工作的方式,那么你是对的。但这不是我在说的——我说的是使用非常流行的编译器实际观察到的行为,并根据我对典型编译器和 CPU 架构的了解推断可能会发生的事情。如果您认为引用优于指针,因为它们更安全并且不认为引用可能是坏事,那么有一天您会像我一样被一个简单的问题难住。

取消引用空指针是错误的。任何这样做的程序,即使是初始化引用也是错误的。如果您从指针初始化引用,则应始终检查指针是否有效。即使这成功了,底层对象也可能随时被删除,留下引用来引用不存在的对象,对吧?你说的是好东西。我认为这里真正的问题是,当您看到引用时,不需要检查“nullness”,并且至少应该断言指针。

答6:

huntsbot.com提供全网独家一站式外包任务、远程工作、创意产品分享与订阅服务!

你忘记了最重要的部分:

带有指针的成员访问使用 -> 带有引用的成员访问使用 .

foo.bar 明显优于 foo->bar,就像 vi 明显优于 Emacs 😃

@Orion Edwards >member-access with pointers uses -> >member-access with references uses 。这不是 100% 正确的。您可以引用指针。在这种情况下,您将使用 -> struct Node { Node *next; 访问取消引用指针的成员。 };节点 *first; // p 是对指针的引用 void foo(Node*&p) { p->next = first; } 节点 *bar = 新节点;富(酒吧); -- OP:你熟悉右值和左值的概念吗?

智能指针两者都有。 (智能指针类的方法)和 -> (基础类型的方法)。

@user6105 Orion Edwards 声明实际上是 100% 正确的。 “访问 [the] de-referenced pointer 的成员” 指针没有任何成员。指针所指的对象有成员,而对这些成员的访问正是 -> 为指针引用所提供的,就像指针本身一样。

为什么 . 和 -> 与 vi 和 emacs 有关系 :)

@artM - 这是个玩笑,对非英语母语人士来说可能没有意义。我很抱歉。解释一下,vi 是否优于 emacs 完全是主观的。有些人认为 vi 要好得多,而另一些人则认为恰恰相反。同样,我认为使用 . 比使用 -> 更好,但就像 vi 与 emacs 一样,它完全是主观的,你无法证明任何事情

答7:

一个优秀的自由职业者,应该有对需求敏感和精准需求捕获的能力,而huntsbot.com提供了这个机会

引用与指针非常相似,但它们是专门为优化编译器而设计的。

引用的设计使得编译器更容易跟踪哪些引用别名哪些变量。两个主要特性非常重要:没有“参考算术”和没有重新分配参考。这些允许编译器在编译时找出哪些引用别名哪些变量。

允许引用引用没有内存地址的变量,例如编译器选择放入寄存器的变量。如果您获取局部变量的地址,编译器很难将其放入寄存器中。

举个例子:

void maybeModify(int& x); // may modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // This function is designed to do something particularly troublesome
    // for optimizers. It will constantly call maybeModify on array[0] while
    // adding array[1] to array[2]..array[size-1]. There's no real reason to
    // do this, other than to demonstrate the power of references.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(array[0]);
        array[i] += array[1];
    }
}

一个优化编译器可能会意识到我们正在访问大量的 a[0] 和 a[1]。它希望将算法优化为:

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Do the same thing as above, but instead of accessing array[1]
    // all the time, access it once and store the result in a register,
    // which is much faster to do arithmetic with.
    register int a0 = a[0];
    register int a1 = a[1]; // access a[1] once
    for (int i = 2; i < (int)size; i++) {
        maybeModify(a0); // Give maybeModify a reference to a register
        array[i] += a1;  // Use the saved register value over and over
    }
    a[0] = a0; // Store the modified a[0] back into the array
}

要进行这样的优化,需要证明在调用过程中没有任何东西可以改变array[1]。这很容易做到。 i 永远不会小于 2,因此 array[i] 永远不能引用 array[1]。 MaybeModify() 被赋予 a0 作为参考(别名数组 [0])。因为没有“参考”算术,编译器只需要证明maybeModify 永远不会得到x 的地址,并且已经证明没有任何东西改变array[1]。

它还必须证明,当我们在 a0 中有一个临时寄存器副本时,未来的调用无法读取/写入 a[0]。这通常很容易证明,因为在许多情况下,很明显引用永远不会像类实例那样存储在永久结构中。

现在用指针做同样的事情

void maybeModify(int* x); // May modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Same operation, only now with pointers, making the
    // optimization trickier.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(&(array[0]));
        array[i] += array[1];
    }
}

行为是一样的;只是现在要证明 MaybeModify 不会修改 array[1] 变得更加困难,因为我们已经给了它一个指针;猫从袋子里出来了。现在它必须做更困难的证明:对maybeModify 进行静态分析以证明它永远不会写入&x + 1。它还必须证明它永远不会保存可以引用array[0] 的指针,这只是一样棘手。

现代编译器在静态分析方面越来越好,但帮助他们并使用引用总是很好的。

当然,除非进行如此巧妙的优化,编译器确实会在需要时将引用转换为指针。

编辑:发布此答案五年后,我发现了一个实际的技术差异,其中引用不同于查看相同寻址概念的不同方式。引用可以以指针无法修改的方式修改临时对象的生命周期。

F createF(int argument);

void extending()
{
    const F& ref = createF(5);
    std::cout << ref.getArgument() << std::endl;
};

通常临时对象(例如由调用 createF(5) 创建的对象)在表达式结束时被销毁。但是,通过将该对象绑定到引用 ref,C++ 将延长该临时对象的生命周期,直到 ref 超出范围。

诚然,身体必须是可见的。但是,确定 maybeModify 不获取与 x 相关的任何地址比证明一堆指针算术不发生要容易得多。

我相信优化器已经做了“一堆指针算术不会发生”检查其他一些原因。

“引用与指针非常相似” - 在语义上,在适当的上下文中 - 但就生成的代码而言,仅在某些实现中而不是通过任何定义/要求。我知道你已经指出了这一点,我不同意你的任何帖子在实践中,但我们已经有太多的问题,人们阅读了太多的速记描述,比如“引用就像/通常实现为指针” .

我感觉有人错误地将 void maybeModify(int& x) { 1[&x]++; } 的评论标记为过时,上面的其他评论正在讨论

答8:

huntsbot.com高效搞钱,一站式跟进超10+任务平台外包需求

实际上,引用并不像指针。

编译器保持对变量的“引用”,将名称与内存地址相关联;这就是编译时将任何变量名转换为内存地址的工作。

创建引用时,您只需告诉编译器您为指针变量分配了另一个名称;这就是为什么引用不能“指向 null”,因为变量不能是,也不是。

指针是变量;它们包含一些其他变量的地址,或者可以为空。重要的是指针有一个值,而引用只有一个它正在引用的变量。

现在对实际代码进行一些解释:

int a = 0;
int& b = a;

在这里,您没有创建另一个指向 a 的变量;您只是在保存 a 值的内存内容中添加另一个名称。该内存现在有两个名称,a 和 b,可以使用任一名称对其进行寻址。

void increment(int& n)
{
    n = n + 1;
}

int a;
increment(a);

调用函数时,编译器通常会为要复制到的参数生成内存空间。函数签名定义了应该创建的空间,并给出了这些空间应该使用的名称。将参数声明为引用只是告诉编译器使用输入变量内存空间,而不是在方法调用期间分配新的内存空间。说您的函数将直接操作在调用范围内声明的变量可能看起来很奇怪,但请记住,在执行编译后的代码时,没有更多的范围;只有普通的平坦内存,您的函数代码可以操纵任何变量。

现在可能有一些情况,您的编译器在编译时可能无法知道引用,例如使用 extern 变量时。因此,引用可能会也可能不会被实现为底层代码中的指针。但是在我给你的例子中,它很可能不会用指针来实现。

引用是对左值的引用,不一定是对变量的引用。因此,它更接近指针而不是真正的别名(编译时构造)。可以引用的表达式示例有 *p 甚至 *p++

对,我只是指出这样一个事实,即引用可能并不总是像新指针那样将新变量压入堆栈。

@VincentRobert:它的作用与指针相同……如果函数是内联的,则引用和指针都将被优化掉。如果有函数调用,则需要将对象的地址传递给函数。

int *p = NULL; int &r=*p;指向 NULL 的引用; if(r){} -> 轰隆隆 ;)

这种对编译阶段的关注似乎很好,直到您记住可以在运行时传递引用,此时静态别名就消失了。 (然后,引用通常实现为指针,但标准不需要这种方法。)

答9:

huntsbot.com – 高效赚钱,自由工作

引用永远不能是 NULL。

有关反例,请参阅 Mark Ransom 的答案。这是关于引用的最常被断言的神话,但它是一个神话。根据标准,您拥有的唯一保证是,当您有一个 NULL 引用时,您立即拥有 UB。但这类似于说“这辆车很安全,它永远不会离开道路。(如果你把它开到路上可能发生的事情,我们不承担任何责任。它可能会爆炸。)”

@cmaster:在有效程序中,引用不能为空。但是指针可以。这不是神话,这是事实。

@Mehrdad 是的,有效的程序还在路上。但是没有流量障碍可以强制您的程序实际执行。大部分道路实际上缺少标记。所以晚上下车非常容易。并且对于调试您知道可能发生的此类错误至关重要:空引用可以在程序崩溃之前传播,就像空指针一样。当它出现时,您有像 void Foo::bar() { virtual_baz(); } 这样的段错误代码。如果您不知道引用可能为 null,则无法将 null 追溯到其来源。

int *p = NULL; int &r=*p;指向 NULL 的引用; if(r){} -> boOm ;) –

@sree int &r=*p; 是未定义的行为。那时,您没有“指向 NULL 的引用”,您有一个无法再对完全进行推理的程序。

答10:

与HuntsBot一起,探索全球自由职业机会–huntsbot.com

如果您不熟悉以抽象甚至学术方式学习计算机语言,那么语义差异可能会显得深奥。

在最高级别,引用的概念是它们是透明的“别名”。您的计算机可能使用地址使它们工作,但您不应该担心这一点:您应该将它们视为现有对象的“只是另一个名称”,并且语法反映了这一点。它们比指针更严格,因此您的编译器可以在您即将创建悬空引用时比在您即将创建悬空指针时更可靠地警告您。

除此之外,指针和引用之间当然存在一些实际差异。使用它们的语法显然不同,您不能“重新定位”引用、引用虚无或引用指针。

答11:

huntsbot.com聚合了超过10+全球外包任务平台的外包需求,寻找外包任务与机会变的简单与高效。

虽然引用和指针都用于间接访问另一个值,但引用和指针之间有两个重要区别。第一个是引用总是引用一个对象:定义一个引用而不初始化它是错误的。赋值的行为是第二个重要区别:赋值给引用会改变引用绑定的对象;它不会重新绑定对另一个对象的引用。一旦初始化,一个引用总是指向同一个底层对象。

考虑这两个程序片段。首先,我们将一个指针分配给另一个:

int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2;    // pi now points to ival2

在分配 ival 之后,由 pi 寻址的对象保持不变。赋值改变了 pi 的值,使其指向不同的对象。现在考虑一个分配两个引用的类似程序:

int &ri = ival, &ri2 = ival2;
ri = ri2;    // assigns ival2 to ival

这个赋值改变了 ival,ri 引用的值,而不是引用本身。赋值后,这两个引用仍然引用它们原来的对象,这些对象的值现在也一样了。

“引用总是指向一个对象”是完全错误的

原文链接:https://www.huntsbot.com/qa/4ln9/what-are-the-differences-between-a-pointer-variable-and-a-reference-variable?lang=zh_CN&from=csdn

一个优秀的自由职业者,应该有对需求敏感和精准需求捕获的能力,而huntsbot.com提供了这个机会

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值