C++ FAQ Lite[16]--自由存储(FreeStore)管理(上)

[C++深入] --- malloc/free和new/delete 在C++中,内存区分为5个区,分别是堆、栈、自由存储区、全局/静态存储区、常量存储区。自由存储区是C++基于new操作符的一个抽象概念,。new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。那么自由存储区是否能等于堆(问题等价于new是否能在堆上动态分配内存),这取决于operator new 的实现细节。自由存储区不仅可以是堆,还可以是静态存储区,这都看operator new在哪里为对象分配内存。 阅读详情

 

 

[16] 自由存储(Freestore)管理(上)
(Part of C++ FAQ Lite, Copyright ? 1991-2001, Marshall Cline, cline@parashift.com)

简体中文版翻译:申旻,nicrosoft@sunistudio.com(东日制作室,东日文档)


FAQs in section [16]:


[16.1] delete p 删除指针 p,还是删除指针所指向的数据 *p?

指针指向的数据。

关键字应该是 delete_the_thing_pointed_to_by。同样的情况也发生在 C中释放指针所指的内存: free(p)实际上是指free_the_stuff_pointed_to_by(p)。

[ Top | Bottom | Previous section | Next section ]


[16.2] 可以 free() 一个由 new 分配的指针吗?可以 delete 一个由 malloc() 分配的指针吗?

不!

在一个程序中同时使用 malloc() 和 delete 或者同时使用 new 和 free() 是合情合理合法的。但是,对由 new 分配的指针调用 free(),或对由 malloc() 分配的指针调用 delete,是无理的、非法的、卑劣的。

当心!我偶尔收到一些人的e-mail,他们告诉我在他们的机器 X 上和编译器 Y 上工作正常。但这并不能使得它成为正确的!有时他们说:“但我只是用一下字符数组而已”。即便虽然如此,也不要在同一个指针上混合malloc() 和 delete,或在同一个指针上混合new 和 free()。如果通过p = new char[n]分配,则必须使用delete[] p;不可以使用free(p)。如果通过分配p = malloc(n),则必须使用free(p);不可以使用delete[] p 或 delete p!将它们混合,如果将代码放到新的机器上,新的编译器上,或只是同样编译器的新版本上,都可能导致运行时灾难性的失败。

记住这个警告。

[ Top | Bottom | Previous section | Next section ]


[16.3] 为什么要用 new 取代原来的值得信赖的 malloc()?

构造函数/析构函数,类型安全,可覆盖性(Overridability)。

  • 构造函数/析构函数:与 malloc(sizeof(Fred))不一样,new Fred() 调用 Fred 的构造函数。同样,delete p 调用 *p 的析构函数。
  • 类型安全:malloc() 返回一个没有类型安全的 void* 。new Fred() 返回一个正确类型(一个 Fred*)的指针。
  • 可覆盖性:new 是一个可被类重写/覆盖的算符(operator),而 malloc() 在类上没有可覆盖性。

[ Top | Bottom | Previous section | Next section ]


[16.4] 可以在一个由 new 分配的指针上使用 realloc() 吗?

不可!

realloc() 拷贝时,使用的是位拷贝(bitwise copy )算符,这会打碎许多 C++ 对象。C++对象应该被允许拷贝它们自己。它们使用自己的拷贝构造函数或者赋值算符。

除此之外,new 使用的堆可能和 malloc() 和 realloc() 使用的堆不同!

[ Top | Bottom | Previous section | Next section ]


[16.5] 需要在 p = new Fred()之后检查NULL吗?UPDATED!

[Recently changed so it uses new-style headers and the std:: syntax (on 7/00). Click here to go to the next FAQ in the "chain" of recent changes<!--rawtext:[16.6]:rawtext-->.]

不!(但如果你只有旧的编译器,你可能不得不强制 new 算符在内存溢出时抛出一个异常。)

总是在每一个new 调用之后写显式的 NULL 测试实在是非常痛苦的.如下的代码是非常单调乏味的:

 Fred* p = new Fred();
 if (p == NULL)
   throw std::bad_alloc();

如果你的编译器不支持(或如果你拒绝使用)异常, 你的代码可能会更单调乏味:

 Fred* p = new Fred();
 if (p == NULL) {
   std::cerr << "Couldn't allocate memory for a Fred" << endl;
   abort();
 }

振作一下。在 C++中,如果运行时系统无法为p = new Fred()分配 sizeof(Fred) 字节的内存,会抛出一个 std::bad_alloc 异常。与 malloc()不同,new 永远不会返回 NULL!

因此你只要简单地写:

 Fred* p = new Fred();   // 不需要检查 p 是否为 NULL

然而,如果你的编译器很古老,它可能还不支持这个。查阅你的编译器的文档找到“new”。如果你只有古老的编译器,就必须强制编译器拥有这种行为。

[ Top | Bottom | Previous section | Next section ]


[16.6] 我如何确信我的(古老的)编译器会自动检查 new 是否返回 NULL ? UPDATED!

[Recently changed the example to use throw rather than abort() thanks to Stan Brown; changed to use new-style headers and the std:: syntax (on 7/00). Click here to go to the next FAQ in the "chain" of recent changes<!--rawtext:[16.13]:rawtext-->.]

最终你的编译器会支持的。

如果你只有古老的不自动执行NULL 测试<!--rawtext:[16.5]:rawtext-->的编译器的话,你可以安装一个“new handler”函数来强制运行时系统来测试。你的“new handler”函数可以作任何你想做的事情,诸如抛出一个异常, delete 一些对象并返回(在operator new会试图再分配的情况下),打印一个消息或者从程序中 abort() 等等。

这里有一个“new handler”的例子,它打印消息并抛出一个异常。它使用 std::set_new_handler() 被安装:

 #include <new>       // 得到 std::set_new_handler
 #include <cstdlib>   
// 得到 abort()
 #include <iostream>  
// 得到 std::cerr
 
 class alloc_error : public std::exception {
 public:
   alloc_error() : exception() { }
 };
 
 void myNewHandler()
 {
   
// 这是你自己的 handler。它可以做任何你想要做的事情。
   throw alloc_error();
 }
 
 int main()
 {
   std::set_new_handler(myNewHandler);   
// 安装你的 "new handler"
   
// ...
 }

在std::set_new_handler()被执行后,如果/当内存不足时,operator new将调用你的myNewHandler()。这意味着new 不会返回NULL:

 Fred* p = new Fred();   // 不需要检查 p 是否为 NULL

注意:如果你的编译器不支持异常处理,作为最后的诉求,你可以将 throw ...; 这一行改为:

   std::cerr << "Attempt to allocate memory failed!" << std::endl;
 abort();

注意:如果某些全局的/静态的对象的构造函数使用了new,由于它们的构造函数在main()开始之前被调用,因此它不会使用myNewHandler()函数。不幸的是,没有简便的方法确保std::set_new_handler() 在第一次使用 new 之前被调用。例如,即使你将std::set_new_handler()的调用放在全局对象的构造函数中,你仍然无法知道包含该全局对象的模块(“编译单元”)被首先还是最后还是还是中间某个位置被解释。因此,你仍然无法保证std::set_new_handler() 的调用会在任何其他全局对象的构造函数调用之前。

[ Top | Bottom | Previous section | Next section ]


[16.7] 在delete p之前需要检查NULL吗?

不需要!

C++语言担保,如果p等于NULL,则delete p不作任何事情。由于之后可以得到测试,并且大多数的测试方法论都强制显式测试每个分支点,因此你不应该加上多余的 if 测试。

错误的:

   if (p != NULL)
   delete p;

正确的:

 delete p;

[ Top | Bottom | Previous section | Next section ]


[16.8] delete p 执行了哪两个步骤?

delete p 是一个两步的过程:调用析构函数,然后释放内存。delete p产生的代码看上去是这样的(假设是Fred*类型的):

   // 原始码:delete p;
 if (p != NULL) {
   p->~Fred();
   operator delete(p);
 }

p->~Fred() 语句调用 p 指向的Fred 对象的析构函数。

operator delete(p) 语句调用内存释放原语 void operator delete(void* p)。该原语类似free(void* p)。(然而注意,它们两个不能互换;举例来说,没有谁担保这两个内存释放原语会使用同一个堆!)。

[ Top | Bottom | Previous section | Next section ]


[16.9] 在 p = new Fred() 中,如果Fred 构造函数抛出异常,是否会内存“泄漏”?

不会。

如果异常发生在p = new Fred()的 Fred 构造函数中, C++语言确保已分配的 sizeof(Fred)字节的内存会自动从堆中回收。

这里有两个细节:new Fred()是一个两步的过程:

  1. sizeof(Fred) 字节的内存使用void* operator new(size_t nbytes)原语被分配。该原语类似于malloc(size_t nbytes)。(然而注意,他们两个不能互换;举例来说,没有谁担保这两个内存分配原语会使用同一个堆!)。
  2. 它通过调用Fred构造函数在内存中建立对象。第一步返回的指针被作为 this 参数传递给构造函数。这一步被包裹在一个块中以处理这步中抛出异常的情况。

因此实际产生的代码可能是象这样的:

 // 原始代码:Fred* p = new Fred();
 Fred* p = (Fred*) operator new(sizeof(Fred));
 try {
   new(p) Fred();       
// Placement new<!--rawtext:[11.10]:rawtext-->
 } catch (...) {
   operator delete(p);  
// 释放内存
   throw;               
// 重新抛出异常
 }

标记为“Placement new”的这句语句调用了 Fred 构造函数。指针 p 成了构占函数 Fred::Fred()内部的this指针。

[ Top | Bottom | Previous section | Next section ]


[16.10] 如何分配/释放一个对象的数组?

使用 p = new T[n] 和 delete[] p:

 Fred* p = new Fred[100];
 
// ...
 delete[] p;

任何时候你通过new 来分配一个对象的数组(通常在表达式中有[n]),则在 delete 语句中必须使用[]。该语法是必须的,因为没有什么语法可以区分指向一个对象的指针和指向一个对象数组的指针(从 C 派生出的某些东西)。

[ Top | Bottom | Previous section | Next section ]


[16.11] 如果 delete 一个由new T[n]分配的数组,漏了[]会如何?

所有生命毁灭性地终止。

正确地连接new T[n]和delete[] p是程序员的——不是编译器的——责任。如果你弄错了,编译器会在编译时或运行时给出错误消息。堆(Heap)被破坏是可能的结果,或者更糟糕,你的程序可能会死亡。

[ Top | Bottom | Previous section | Next section ]


[16.12] 当delete一个内建类型 (char, int, 等)的数组时,能去掉 [] 吗?

不行!

有时程序员会认为在delete[] p 中存在[] 仅仅是为了编译器为数组中的每个元素调用适当的析构函数。由于这个原因,他们认为一些内建类型的数组,如 char或int可以不需要[]。举例来说,他们认为以下是合法的代码:

   void userCode(int n)
 {
   char* p = new char[n];
   
// ...
   delete p;     
// <— 错!应该是 delete[] p !
 }

但以上代码是错误的,并且会导致一个运行时的灾难。更详细地来说,delete p调用的是operator delete(void*),而delete[] p调用的是operator delete[](void*)。虽然后者的默认行为是调用前者,但将后者用不同的行为取代是被允许的(这种情况下通常也会将相应的operator new[](size_t)中的 new 取代)。如果被取代的delete[]  代码与delete 代码不兼容,并且调用错误的那个(例如,你写了delete p而不是delete[] p),在运行时可能完蛋。

[ Top | Bottom | Previous section | Next section ]


[16.13] p = new Fred[n]之后,编译器在delete[] p的时候如何知道有个对象被析构?UPDATED!

[Recently changed "popluar" to "popular" thanks to Fabrice Clerc (on 7/00). Click here to go to the next FAQ in the "chain" of recent changes<!--rawtext:[16.15]:rawtext-->.]

精简的回答:魔法。

详细的回答:运行时系统将对象的数量 n 保存在某个通过指针 p 可以获取的地方。有两种普遍的技术来实现。这些技术都在商业编译器中使用,各有权衡,都不完美。这些技术是:

[ Top | Bottom | Previous section | Next section ]


[16.14] 成员函数调用delete this合法吗?

只要你小心,一个对象请求自杀(delete this).是可以的。

以下是我对“小心”的定义:

  1. 你必须100%的确定,this对象是用 new分配的(不是用new[],也不是用定位放置 new<!--rawtext:[11.10]:rawtext-->,也不是一个栈上的局部对象,也不是全局的,也不是另一个对象的成员,而是明白的普通的new)。
  2. 你必须100%的确定,该成员函数是this对象最后调用的的成员函数。
  3. 你必须100%的确定,剩下的成员函数(delete this之后的)不接触到 this对象任何一块(包括调用任何其他成员函数或访问任何数据成员)。
  4. 你必须 100%的确定,在delete this之后不再去访问this指针。换句话说,你不能去检查它,将它和其他指针比较,和 NULL比较,打印它,转换它,对它做任何事。

自然,对于这种情况还要习惯性地告诫:当你的指针是一个指向基类类型的指针,而没有虚析构函数时(也不可以 delete this)。

[ Top | Bottom | Previous section | Next section ]


[16.15] 如何用new分配多维数组? UPDATED!

[Recently fixed a leak in the third manipulateArray() by moving another for loop into the try block (on 7/00). Click here to go to the next FAQ in the "chain" of recent changes<!--rawtext:[16.16]:rawtext-->.]

有许多方法,取决于你想要让数组有多大的灵活性。一个极端是,如果你在编译时就知道数组的所有的维数,则可以静态地(就如同在C中)分配多维数组:

   class Fred { /*...*/ };
 void someFunction(Fred& fred);
 
 void manipulateArray()
 {
   const unsigned nrows = 10;  
// 行数是编译期常量
   const unsigned ncols = 20;  
// 列数是编译期常量
   Fred matrix[nrows][ncols];
 
   for (unsigned i = 0; i < nrows; ++i) {
     for (unsigned j = 0; j < ncols; ++j) {
       
// 访问(i,j)元素的方法:
       someFunction( matrix[i][j] );
 
       
// 可以安全地“返回”,不需要特别的delete代码:
       if (today == "Tuesday" && moon.isFull())
         return;     
// 月圆的星期二赶紧退出
     }
   }
 
   
// 在函数末尾也没有显式的delete代码
 }

更一般的,矩阵的大小只有到运行时才知道,但确定它是一个矩形。这种情况下,你需要使用堆(“自由存储”)(heap,freestore),但至少你可以把所有元素非胚在自由存储块中。

 void manipulateArray(unsigned nrows, unsigned ncols)
 {
   Fred* matrix = new Fred[nrows * ncols];
 
   
// 由于我们上面使用了简单的指针,因此我们需要非常
   
// 小心避免漏过 delete 代码。
   
// 这就是为什么要捕获所有异常:
   try {
 
     
// 访问(i,j) 元素的方法:
     for (unsigned i = 0; i < nrows; ++i) {
       for (unsigned j = 0; j < ncols; ++j) {
         someFunction( matrix[i*ncols + j] );
       }
     }
 
     
// 如果你想在月圆的星期二早点退出,
     
// 就要确保在返回的所有途径上做 delete :
     if (today == "Tuesday" && moon.isFull()) {
       delete[] matrix;
       return;
     }
 
     
// ...
 
   }
   catch (...) {
     
// 确保在异常抛出后delete :
     delete[] matrix;
     throw;    
// 重新抛出当前异常
   }
 
   
// 确保在函数末尾也做了 delete :
   delete[] matrix;
 }

最后是另一个极端,你可能甚至不确定矩阵是矩形的。例如,如果每行可以有不同的长度,你就需要为个别地分配每一行。在如下的函数中,ncols[i] 是第 i 行的列数,i 的可变范围是 0 到 nrows-1。

 void manipulateArray(unsigned nrows, unsigned ncols[])
 {
   typedef Fred* FredPtr;
 
   
// 如果后面抛出异常,不要成为漏洞:
   FredPtr* matrix = new FredPtr[nrows];
 
   
// 以防万一稍后会有异常,将每个元素设置为 NULL:
   
// (见 try 块顶端的注释。)
   for (unsigned i = 0; i < nrows; ++i)
     matrix[i] = NULL;
 
   
// 由于我们上面使用了简单的指针,我们需要
   
// 非常小心地避免漏过delete 代码。
   
// 这就是为什么我们要捕获所有的异常:
   try {
 
     
// 接着我们组装数组。如果其中之一抛出异常,所有的
     
// 已分配的元素都会被释放 (见如下的 catch )。
     for (unsigned i = 0; i < nrows; ++i)
       matrix[i] = new Fred[ ncols[i] ];
 
     
// 访问(i,j) 元素的方法:
     for (unsigned i = 0; i < nrows; ++i) {
       for (unsigned j = 0; j < ncols[i]; ++j) {
         someFunction( matrix[i][j] );
       }
     }
 
     
// 如果你想在月圆的星期二早些退出,
     
// 确保在返回的所有途径上做 delete:
     if (today == "Tuesday" && moon.isFull()) {
       for (unsigned i = nrows; i > 0; --i)
         delete[] matrix[i-1];
       delete[] matrix;
       return;
     }
 
     
// ...
 
   }
   catch (...) {
     
// 确保当有异常抛出时做 delete :
     
// 注意 matrix[...] 中的一些指针可能是
     
// NULL, 但由于delete NULL是合法的,所以没问题。
     for (unsigned i = nrows; i > 0; --i)
       delete[] matrix[i-1];
     delete[] matrix;
     throw;    
// 重新抛出当前异常
   }
 
   
// 确保在函数末尾也做 delete :
   
// 注意释放与分配反向:
   for (unsigned i = nrows; i > 0; --i)
     delete[] matrix[i-1];
   delete[] matrix;
 }

注意释放过程中 matrix[i-1]的使用。这样可以防止无符号值 i 的步进为小于0 的回绕。

最后,注意指针和数组是会带来麻烦的。通常,最好将你的指针封装在一个有着安全的和简单的接口的类中。下一个FAQ告诉你如何这样做。

[ Top | Bottom | Previous section | Next section ]


[16.16] 但前一个FAQ的代码太技巧容易出错!有更简单的方法吗? UPDATED!

[Recently clarified the last paragraph (on 7/00) and fixed the Star Trek movie number thanks to Chris Sheppard (on 4/01) and wordsmithed last paragraph at the suggestion of prapp (on 4/01). Click here to go to the next FAQ in the "chain" of recent changes<!--rawtext:[16.17]:rawtext-->.]

有。

前一个FAQ之所以太过技巧而容易出错是因为它使用了指针,我们知道指针和数组会带来麻烦。解决办法是将指针封装到一个有着安全的和简单的接口的类中。例如,我们可以定义一个 Matrix 类来处理矩形的矩阵,用户代码将比前一个FAQ中的矩形矩阵的代码简单得多:

 // Matrix 类的代码在下面显示...
 void someFunction(Fred& fred);
 
 void manipulateArray(unsigned nrows, unsigned ncols)
 {
   Matrix matrix(nrows, ncols);   
// 构造一个 matrix
 
   for (unsigned i = 0; i < nrows; ++i) {
     for (unsigned j = 0; j < ncols; ++j) {
       
// 访问(i,j) 元素的方法:
       someFunction( matrix(i,j) );
 
       
// 你可以不用写任何的 delete 代码安全地“返回”:
       if (today == "Tuesday" && moon.isFull())
         return;     
// 月圆的星期二早些退出
     }
   }
 
   
// 在函数末尾也没有显式的delete代码
 }

需要注意的主要是整理后的代码的短小。例如,再如上的代码中没有任何 delete 语句,也不会有内存泄漏,这个假设仅仅是基于析构函数正确地完成它的工作。

以下就是使得以上成为可能的Matrix的代码:

 class Matrix {
 public:
   Matrix(unsigned nrows, unsigned ncols);
   
// 如果任何一个尺寸为 0,则抛出 BadSize 对象的异常:
   class BadSize { };
 
   
// 基于大三法则(译注:即三者须同时存在):
  ~Matrix();
   Matrix(const Matrix& m);
   Matrix& operator= (const Matrix& m);
 
   
// 取得 (i,j) 元素的访问方法:
   Fred&       operator() (unsigned i, unsigned j);
   const Fred& operator() (unsigned i, unsigned j) const;
   
// 如果i 或j 太大,抛出BoundsViolation 对象
   class BoundsViolation { };
 
 private:
   Fred* data_;
   unsigned nrows_, ncols_;
 };
 
 inline Fred& Matrix::operator() (unsigned row, unsigned col)
 {
   if (row >= nrows_ || col >= ncols_) throw BoundsViolation();
   return data_[row*ncols_ + col];
 }
 
 inline const Fred& Matrix::operator() (unsigned row, unsigned col) const
 {
   if (row >= nrows_ || col >= ncols_) throw BoundsViolation();
   return data_[row*ncols_ + col];
 }
 
 Matrix::Matrix(unsigned nrows, unsigned ncols)
   : data_  (new Fred[nrows * ncols]),
     nrows_ (nrows),
     ncols_ (ncols)
 {
   if (nrows == 0 || ncols == 0)
     throw BadSize();
 }
 
 Matrix::~Matrix()
 {
   delete[] data_;
 }

注意以上的Matrix类完成两件事:将技巧性的内存管理代码从客户代码(例如,main())移到类中,并且总体上减少了编程。这第二点很重要。例如,假设 Matrix有略微的可重用性,将复杂性从Matrix的用户们[复数]处移到了Matrix自身[单数]就等于将复杂性从多的方面移到少的方面。任何看过星际旅行2的人都知道多数的利益高于少数或者个体的利益。

[ Top | Bottom | Previous section | Next section ]


[16.17] 但上面的Matrix类是针对Fred的!有办法使它通用吗? UPDATED!

[Recently rewrote (on 7/00). Click here to go to the next FAQ in the "chain" of recent changes<!--rawtext:[16.18]:rawtext-->.]

有;那就是使用模板:

以下就是如何能用模板:

 #include "Fred.hpp"     // 得到Fred类的定义
 
 
// Matrix<T> 的代码在后面显示...
 void someFunction(Fred& fred);
 
 void manipulateArray(unsigned nrows, unsigned ncols)
 {
   Matrix<Fred> matrix(nrows, ncols);   
// 构造一个称为matrix的 Matrix<Fred> 
 
   for (unsigned i = 0; i < nrows; ++i) {
     for (unsigned j = 0; j < ncols; ++j) {
       
// 访问 (i,j) 元素的方法:
       someFunction( matrix(i,j) );
 
       
// 你可以不用任何的delete 的代码安全地“返回”:
       if (today == "Tuesday" && moon.isFull())
         return;     
// 月圆的星期二早些退出
     }
   }
 
   
// 函数末尾也没有显式的delete代码

 }

现在很容易为非 Fred 的类使用 Matrix<T>。例如,以下为std::string 使用一个 Matrix (std::string 是标准字符串类):

   #include <string>
 
 void someFunction(std::string& s);
 
 void manipulateArray(unsigned nrows, unsigned ncols)
 {
   Matrix<std::string> matrix(nrows, ncols);   
// 构造一个 Matrix<std::string>
 
   for (unsigned i = 0; i < nrows; ++i) {
     for (unsigned j = 0; j < ncols; ++j) {
       
// 访问 (i,j) 元素的方法:
       someFunction( matrix(i,j) );
 
       
// 你可以不用任何的delete 的代码安全地“返回”:
       if (today == "Tuesday" && moon.isFull())
         return;     
// 月圆的星期二早些退出
     }
   }
 
   
// 函数末尾也没有显式的delete代码
 }

因此,你可以从模板得到类的完整家族。例如, Matrix<Fred>, Matrix<std::string>, Matrix< Matrix<std::string> >等等。

以下是实现该模板的一种方法:

 template<class T>  // 详见 模板一节
 class Matrix {
 public:
   Matrix(unsigned nrows, unsigned ncols);
   
// 如果任何一个尺寸为 0,则抛出 BadSize 对象
   class BadSize { };
 
   
// 基于大三法则(译注:即三者须同时存在):
  ~Matrix();
   Matrix(const Matrix<T>& m);
   Matrix<T>& operator= (const Matrix<T>& m);
 
   
// 获取 (i,j) 元素的访问方法:
   T&       operator() (unsigned i, unsigned j);
   const T& operator() (unsigned i, unsigned j) const;
   
// 如果 i 或 j 太大,则抛出 BoundsViolation 对象
   class BoundsViolation { };
 
 private:
   T* data_;
   unsigned nrows_, ncols_;
 };
 
 template<class T>
 inline T& Matrix<T>::operator() (unsigned row, unsigned col)
 {
   if (row >= nrows_ || col >= ncols_) throw BoundsViolation();
   return data_[row*ncols_ + col];
 }
 
 template<class T>
 inline const T& Matrix<T>::operator() (unsigned row, unsigned col) const
 {
   if (row >= nrows_ || col >= ncols_) throw BoundsViolation();
   return data_[row*ncols_ + col];
 }
 
 template<class T>
 inline Matrix<T>::Matrix(unsigned nrows, unsigned ncols)
   : data_  (new T[nrows * ncols])
   , nrows_ (nrows)
   , ncols_ (ncols)
 {
   if (nrows == 0 || ncols == 0)
     throw BadSize();
 }
 
 template<class T>
 inline Matrix<T>::~Matrix()
 {
   delete[] data_;
 }

[ Top | Bottom | Previous section | Next section ]


[16.18] 还有其他方法建立 Matrix 模板吗? NEW!

[Recently created thanks to Jesper Rasmussen (on 4/01). Click here to go to the next FAQ in the "chain" of recent changes<!--rawtext:[16.19]:rawtext-->.]

用标准的vector 模板,制作一个向量的向量。

以下代码使用了一个vector<vector<T> >(注意两个 > 符号之间的空格)。

 #include <vector>
 
 template<class T>  
// 详见模板一节
 class Matrix {
 public:
   Matrix(unsigned nrows, unsigned ncols);
   
// 如果任何的尺寸为 0,抛出 BadSize 对象
   class BadSize { };
 
   
// 不需要大三法则!
 
   
// 得到 (i,j) 元素的访问方法:
   T&       operator() (unsigned i, unsigned j);
   const T& operator() (unsigned i, unsigned j) const;
   
// 如果 i 或 j 太大,则抛出 BoundsViolation 对象
   class BoundsViolation { };
 
 private:
   vector<vector<T> > data_;
 };
 
 template<class T>
 inline T& Matrix<T>::operator() (unsigned row, unsigned col)
 {
   if (row >= nrows_ || col >= ncols_) throw BoundsViolation();
   return data_[row][col];
 }
 
 template<class T>
 inline const T& Matrix<T>::operator() (unsigned row, unsigned col) const
 {
   if (row >= nrows_ || col >= ncols_) throw BoundsViolation();
   return data_[row][col];
 }
 
 template<class T>
 Matrix<T>::Matrix(unsigned nrows, unsigned ncols)
   : data_ (nrows)
 {
   if (nrows == 0 || ncols == 0)
     throw BadSize();
   for (unsigned i = 0; i < nrows; ++i)
     data_[i].resize(ncols);
 }

[ Top | Bottom | Previous section | Next section ]


E-Mail E-mail the author
[ C++ FAQ Lite | Table of contents | Subject index | About the author | ? | Download your own copy ]
Revised Apr 8, 2001

C++ FAQ Lite CFAQ的C++版本,相信CFAQ对C语言学习很有帮助,C++FAQ应该对C++学习者也很有帮助 立即下载

相关推荐

【FPGA】Robei EDA 的使用(7)—— Robei文件上板试验

#沐朝露兮以自华,觅大道兮以求成 我又滚过来了,研究了20多天RISCV,结果最后因为硬件问题导致无法在比赛中使用RISCV CPU,虽然已经临近比赛后期了,非常的崩溃,但是也没有什么办法。唉,一切不过是从头再来罢了。(写写博客舒缓我心中苦闷) ————————————————进入正文的分割线———————————————— 今天介绍一下如何利用 Robei EDA 生成文件,导入vivado中,综合下载到开发板里。拿我一个PWM控制舵机的代码为例。 Robei EDA设置 假设代码模块(model文件)经

Ninquelote的博客 2564

TensorFlow Lite Micro简介与使用

一文高效搞懂TensorFlow Lite Micro的简介与使用!

全栈O-Jay的博客 7872

MIT6.824-lab2D-2022(日志压缩详细讲解)

从paper中去讲解,并实现关于lab2D的代码。

幸平的博客 6037

C++ (动态)内存分配区域:自由存储区(Free Store)&堆区(Heap)

Heap: The heap is the other dynamic memory area, allocated/freed by malloc/free and their variants. Note that while the default global new and delete might be implemented in terms of malloc and ...

jxluoix的专栏 747

基于JavaFX的Http Server模拟器

源码地址 GitHub - DayRain/http-server-simulator: http server 模拟器 功能描述 HttpServer模拟器,通过简单的配置即可模拟第三方服务端,用于接收和响应HTTP请求。 支持同时模拟多个服务、动态添加接口、动态配置返回值。 使用说明 运行 exe运行 编译代码后,点击exe直接运行 代码运行 下载好代码后,运行主类ApplicationStarter即可 新建服务 主界面如下 点击 文件-新建 创建新的服务 ...

小楼夜听雨的博客 2535

从战略到执行:业务领先模型 BLM 的第一把钥匙「差距分析」

业务领先模型「BLM」(Business Leadership Model),俗称“别乱摸”,起源于哈佛商学院的战略理论研究,是一套完整的战略规划方法论。 后来被 IBM 发扬光大,IBM 在转型关键时期完成了对 BLM 模型的导入和创新,从此这个模型成为 IBM 中高层用于战略制定与执行联接的方法与平台。 再到后来华为花巨资引入了 BLM 模型,经过多年的应用,将其演化成了公司战略规划和落地的主要方法与流程,也因此被中国科技企业所认识和接受。 BLM 模型可以解决哪些问题? 在企业内,

产品线负责人 2400

【STM32开发笔记】移植AI框架TensorFlow到STM32单片机【下篇】

本文将会继续介绍——如何为STM32H7S78-DK开发板准备CMake项目、如何将TFLM集成到基于CMake的STM32项目中、如何在STM32H7S78-DK开发板上运行TFLM基准测试,具体包括如何支持计时和printf输出、如何集成TFLM到基于CMake的STM32项目,以及解决过程中遇到的一些问题。

码工许师傅 5431

c++中,部署Ethercat IGH + PREEMPT_RT 环境,跑DC模式,如何实现?

🏆本文收录于 《全栈 Bug 调优(实战版)》 专栏。专栏聚焦真实项目中的各类疑难 Bug,从成因剖析 → 排查路径 → 解决方案 → 预防优化全链路拆解,形成一套可复用、可沉淀的实战知识体系。无论你是初入职场的开发者,还是负责复杂项目的资深工程师,都可以在这里构建一套属于自己的「问题诊断与性能调优」方法论,助你稳步进阶、放大技术价值 。

**My Coding Family** 1358

C++自由存储区及原理

C++自由存储区

Funning的博客 805

【NLP】第10章 使用基于 BERT 的 Transformer 进行语义角色标记

Transformers 在过去几年里取得的进步比上一代 NLP 还要多。标准 NLU 方法首先学习句法和词汇特征来解释句子的结构。在运行语义角色标签( SRL )之前,将训练以前的 NLP 模型理解语言的基本语法。Shi和Lin(2019)在他们的论文开始时询问是否可以跳过初步的句法和词汇训练。基于 BERT 的模型可以在不经过那些经典训练阶段的情况下执行 SRL 吗?答案是肯定的!Shi和Lin (2019) 建议 SRL 可以被视为序列标记并提供标准化的输入格式。他们基于 BERT 的模型产生了令人惊

sikh_0529的博客 7375

使用 MATLAB/Simulink 构建一个基于FOC控制的PMSM矢量控制仿真模型

本教程详细介绍了基于MATLAB/Simulink的永磁同步电机(PMSM)磁场定向控制(FOC)系统建模与仿真方法。主要内容包括:PMSM电机模型参数设置、Clarke/Park变换模块设计、电流环PID控制器配置、SVPWM调制实现以及系统仿真参数优化。文章提供了模块化设计思路和具体参数设置示例,并给出了PID整定建议和进阶优化方向。通过该仿真模型,可深入理解PMSM矢量控制原理,掌握高性能电机控制系统的建模技巧,适用于伺服系统、电动汽车驱动等高端控制场景。教程还推荐了实时仿真和高级控制策略等扩展应用。

xiaoheshang_123的博客 530

C++ "free store" VS "heap"

当我问你C++的内存布局时,你大概会回答: “在C++中,内存区分为5个区,分别是堆、栈、自由存储区、全局/静态存储区、常量存储区”。 如果我接着问你自由存储区与堆有什么区别,你或许这样回答: “malloc在堆上分配的内存块,使用free释放内存,而new所申请的内存则是在自由存储区上,使用delete来释放。” 这样听起来似乎也没错,但如果我接着问: 自由存储区与堆是两块不同的内存区域吗?它们...

莫问收获,但问耕耘 844

使用ClickHouse JDBC官方驱动,踩坑无数

这个问题我在网上查原因说是由于Zookeeper压力过大,表变成只读状态,导致ClickHouse插入数据失败。写入数据频率过高。Zookeeper中的集群节点挂掉。而我们项目出现这个问题的原因是第一个:写入数据频率过高。但是在网上搜资料的过程中,我又发现了另外一个问题:我们项目用了JDBC驱动,但ClickHouse官方并不推荐。。证实了官网确实不建议使用驱动:而应该改成驱动,并且推荐使用0.3.2以上的版本:于是,后面几天开始了ClickHouse的JDBC驱动升级之旅。

2万+

C++ FAQ Lite[16]--自由存储(Freestore)管理(下)

 [16] 自由存储(Freestore)管理(Part of C++ FAQ Lite, Copyright ? 1991-2001, Marshall Cline, cline@parashift.com)简体中文版翻译:申旻,nicrosoft@sunistudio.com(东日制作室,东日文档)FAQs in section [16]:[16.19] C++ 有

Nicrosoft的专栏 1763

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

最简单体验TinyML、TensorFlow Lite——ESP32跑机器学习(全代码)

最详细、通俗、简单地带你从0到1实现TinyML第一个项目。基于tensorflowlite、python、esp32的嵌入式机器学习。

全栈O-Jay的博客 2万+

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

实际编译运行tensorflow Lite Microcontroller的例子《MCU嵌入式AI开发笔记》(第8集)

**MCU嵌入式AI开发笔记** 实际编译运行tensorflow Lite Microcontroller的例子《MCU嵌入式AI开发笔记》(第8集)。抖音、B站、视频号等站点搜索柔贝特三哥,《MCU嵌入式AI开发笔记》视频同步更新,视频详细讲解。

跨界交流 1154

【STM32开发笔记】移植AI框架TensorFlow到STM32单片机【上篇】

本系列将介绍如何将TensorFlow Lite for Microcontrollers一直到STM32H7S78-DK上。由于整个过程较为繁琐,本系列将分为上下两篇进行介绍。本文为系列内容的上篇,主要分为TFLM是什么、TFLM初步体验、TFLM源码浅析、TFLM主体移植几个部分。其中,TFLM初步体验部分将会介绍如何在PC上运行TFLM基准测试,TFLM源码浅析部分主要介绍TFLM源码是如何进行构建的,TFLM主体移植主要介绍如何在基于CMake的STM32项目中构建TFLM库和基准测试。

码工许师傅 1万+

STM32Cube.AI实战:TensorFlow Lite手势识别模型部署到MCU全流程

本文详细介绍了从TensorFlow模型训练到STM32微控制器部署手势识别的全流程。主要内容包括:使用TensorFlow训练CNN手势识别模型并进行TFLite量化;通过STM32Cube.AI工具链将模型部署到STM32F407VET6微控制器;开发传感器驱动和实时推理优化,最终实现低功耗(平均电流9mA)的手势识别系统,准确率达89-97%,推理延迟仅7.8-8.1ms。整个流程涵盖了数据采集、模型转换、CubeMX工程配置、代码生成及性能优化等关键技术环节。

嵌入式毕设/课设资料清单(速查手册·文档模板·题库,持续更新)→ 见最新文章 1257

TensorFlow-Lite-Micro MCU部署

本文深入分析TensorFlow Lite for Microcontrollers在MCU上的实现机制,涵盖静态内存管理、算子裁剪与量化压缩技术,并提供从模型转换到嵌入式部署的完整流程,适用于STM32、ESP32等资源受限设备的关键词检测、异常识别等TinyML应用。

weixin_32312889的博客 1056

C++ Top50 面试问题

C++是一种面向对象的编程语言,它的引入是为了克服C的局限性。所谓面向对象,我们指的是它多态、继承、抽象、封装、对象和类的概念。c++是一种面向对象的语言,这意味着数据被视为对象。c++是一种多范式语言;简而言之,这意味着我们可以对程序的逻辑、结构和过程进行编程。内存管理是c++中的一个关键特性,因为它支持动态内存分配它是一种中级编程语言,这意味着它可以开发游戏、桌面应用程序、驱动程序和内核std当一个变量被描述为引用时,它就变成了现有变量的别名。

明朗晨光的专栏 2027

使用S32DS部署Tensorflow lite到S32K3

右击工程“Build configurations Explorer”

湘淮子的专栏 1419

c++ 动态内存

c++ 动态内存

weixin_40378209的博客 398

11集在Docker上编译tensorFlow Lite MCU例子工程-《MCU嵌入式AI开发笔记》

11集在Docker上编译tensorFlow Lite MCU例子工程-《MCU嵌入式AI开发笔记》

跨界交流 1397

C/C++ 内存分区以及自由存储区和堆的区别

作者:骇客HK 出处:https://www.cnblogs.com/ruanraun/p/Heap.html C/C++编译的程序占用的内存分区 1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数名,局部变量的名等。其操作方式类似于数据结构中的栈。 2、堆区(heap)— 由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。 3、全局/静态存储区 —全局变量和局部静态变量的存储是放在一块的(在以前的C语言中,..

马小超 2153

C++ FAQ Lite[23]--继承(你所不知道的)(更新)

[23] 继承 — 你所不知道的(Part of C++ FAQ Lite, Copyright © 1991-2001, Marshall Cline, cline@parashift.com)简体中文版翻译:申旻,nicrosoft@sunistudio.com(东日制作室,东日文档)FAQs in section [23]:[23.1] 基类的非虚函数调用虚函数可以吗

Nicrosoft的专栏 1409

free store 和 heap

free store (自由存储区)和 heap (堆),在C/C++中经常会遇到。他们是否有区别呢?偶最早发现这两个概念性问题是在《Exceptional C++》一书中。其中提到C++中使用new分配所得的内存是分配在 freestore 上,而C 风格的内存分配 malloc 分配所得的内存是在 heap 上。额。这个有什么区别呢?通过在 Google 的搜索,所得的中文资料

Stone X 档案 4469
上一篇: 再谈多态——向上映射及VMT/DMT
下一篇: C++ FAQ Lite[16]--自由存储(Freestore)管理(下)
Nicrosoft
博客等级 码龄26年 9粉丝 45原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值