c++ 拾遗

18 char a[](字符串数组)和char *a(字符串指针)区别

https://blog.csdn.net/Wjy2016/article/details/53469557

原因在于两种方式对字符数组操作的机制不同。使用char *p="C++"语句后,编译器在内存的文字常量区分配一块内存,保存”C++“这一字符串字面值,然后在栈上分配内存保存p,p的内容为"C++"的地址。p[0]='c'试图修改常量”C++“,程序当然就会崩溃了。而char ss[]="C++"语句,定义了一个数组,编译器为其在栈上分配了内存空间,因而可以进行修改操作。

因此,可以总结如下:

(1)char ss[]定义了一个数组,ss可认为是一个常指针,ss不可改变,但ss指向的内容可以发生改变。

(2)char *p定义了一个可变指针,p可以指向其它对象。但对于char *p=”abc“这样的情况,p指向的是常量,故内容不能改变。

17 C++ lambda表达式总结

https://www.cnblogs.com/gqtcgq/p/9939651.html

16 C++的string类型中关于append函数、push_back函数和+=运算符的区别

https://blog.csdn.net/weixin_44556968/article/details/109112512

  • += 运算符:追加单个参数值。一个char 一个string 
  • append 函数:允许追加多个参数值。 一个string
  • push_back 函数:只能追加单个字符。 一个char

-15 decltype关键字

decltype被称作类型说明符,它的作用是选择并返回操作数的数据类型。

https://blog.csdn.net/u014609638/article/details/106987131

-14 图解快速排序

https://blog.csdn.net/pengzonglu7292/article/details/84938910

-13 emplace 与 push

emplace_back() 函数在原理上比 push_back() 有了一定的改进,包括在内存优化方面和运行效率方面。内存优化主要体现在使用了就地构造(直接在容器内构造对象,不用拷贝一个复制品再使用)+强制类型转换的方法来实现,在运行效率方面,由于省去了拷贝构造过程,因此也有一定的提升。

https://blog.csdn.net/p942005405/article/details/84764104

https://zhuanlan.zhihu.com/p/213853588

-12 string 函数 

C++ isalpha、isalnum、islower、isupper用法

isdigit(): 功能:如果参数是0到9之间的数字字符,函数返回非零值,否则返回零值。

               注意:判断的字符是char类型的。

isalpha ()是否字母  

isalnum() 是否为数字或者字母

islower() 是否小写字母

isupper() 是否大写字母

tolower() 转换小写

toupper()转换大写

n) reserve() //保留一定量内存以容纳一定数量的字符
o) [ ], at() //存取单一字符
p) >>,getline() //从stream读取某值
q) <<    //将谋值写入stream
r) copy() //将某值赋值为一个C_string
s) c_str() //将内容以C_string返回
t) data() //将内容以字符数组形式返回
u) substr() //返回某个子字符串

https://blog.csdn.net/weixin_41162823/article/details/80172379

stl 教程

http://c.biancheng.net/view/7457.html

-11 关于二叉树

结点拥有的子树数目称为结点的 

同一个双亲结点的孩子结点之间互称兄弟结点

在二叉树的第i层上最多有2^i-1 个节点

二叉树中如果深度为k,那么最多有2^k-1个节点。

n0=n2+1, n0表示度数为0的节点数,n2表示度数为2的节点数。

在完全二叉树中,具有n个节点的完全二叉树的深度为[log2n]+1,其中[log2n]是向下取整。

斜树:所有的结点都只有左子树的二叉树叫左斜树。所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树。

满二叉树:在一棵二叉树中。如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。
满二叉树的特点有:
1)叶子只能出现在最下一层。出现在其它层就不可能达成平衡。
2)非叶子结点的度一定是2。
3)在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多。

完全二叉树:对一颗具有n个结点的二叉树按层编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。

特点
1)叶子结点只能出现在最下层和次下层。
2)最下层的叶子结点集中在树的左部。
3)倒数第二层若存在叶子结点,一定在右部连续位置。
4)如果结点度为1,则该结点只有左孩子,即没有右子树。
5)同样结点数目的二叉树,完全二叉树深度最小。
:满二叉树一定是完全二叉树,但反过来不一定成立。

平衡二叉树: 是一棵空树或它的任意节点的左右两个子树的高度差的绝对值不超过1

二叉排序树,又称二叉查找树、二叉搜索树。
性质如下

若左子树不空,则左子树上所有结点的值均小于它的根结点的值;

若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;

左、右子树也分别为二叉排序树。

红黑树

    **红黑树本质上是一种二叉查找树,但它在二叉查找树的基础上额外添加了一个标记(颜色),同时具有一定的规则。**这些规则使红黑树保证了一种平衡,插入、删除、查找的最坏时间复杂度都为 O(logn)。
    从根节点到叶节点的路径上黑色节点的个数,叫做树的黑色高度。
    每个节点要么是红色,要么是黑色;
    根节点永远是黑色的;
    所有的叶节点都是是黑色的(注意这里说叶子节点其实是上图中的 NIL 节点);
    每个红色节点的两个子节点一定都是黑色;
    从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点;
    各种操作的时间复杂度,能保证在最坏情况下,基本的动态几何操作的时间均为O(lgn)
    相对于BST和AVL树,红黑树是牺牲了严格的高度平衡的优越条件为代价,能够以O(log2n)的时间复杂度进行搜索、插入、删除操作。最坏情况下,查找(O(lgn))比二叉排序树快O(n)。较于AVL树,红黑树在数据较乱时查找要更快。
    与哈希表对比,哈希的内存需求更大,map查找log(n)级别。当数据是静态的时候使用哈希,数据需要动态维护则使用红黑树比较好。比如Linux内核系统使用红黑树维护内存块。

红黑树的旋转

    左旋:把右子树里的一个节点移动到了左子树。
    右旋:把左子树里的一个节点移动到了右子树。

线段树

视频参考

    线段树的提出是为了以log(n)复杂度快速的求出数组中所有树的和所提出的。

    1.线段树的每个节点代表着一个区间

    2.线段树具有唯一的根节点,统计的范围为:[1,N]

    3.对于每个内部节点[l,r]。左子节点是[l,mid],右边子节点是[mid+1,r],mid = (l+r)/2(向下取整)
————————————————
版权声明:本文为CSDN博主「陆标」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/hellowd123/article/details/99692395

-10

数据结构:堆(Heap)

https://www.jianshu.com/p/6b526aa481b1

C++:浅析STL之priority_queue构建大根堆与小根堆 (二叉堆: 完全二叉树)

https://blog.csdn.net/qq_43152052/article/details/103206628

自定义priority_queue 

priority_queue

#include<iostream>
#include<queue>
using namespace std;
//函数对象类
template <typename T>
class cmp
{
public:
    //重载 () 运算符
    bool operator()(T a, T b)
    {
        return a > b;
    }
};

int main()
{
    int a[] = { 4,2,3,5,6 };
    priority_queue<int,vector<int>,cmp<int> > pq(a,a+5);
    while (!pq.empty())
    {
        cout << pq.top() << " ";
        pq.pop();
    }
    return 0;
}

http://c.biancheng.net/view/6996.html

力扣347

class Solution {
public:
    static bool cmp(pair<int, int>& m, pair<int,int>& n){
        return m.second > n.second;
    }
    vector<int> topKFrequent(vector<int>& nums, int k) {
        map<int, int> m;
        for(int& i: nums){
            m[i]++;
        } 
        //小顶堆 decltype(&cmp) 决定
        priority_queue<pair<int,int>, vector<pair<int,int>>, decltype(&cmp)> q(cmp);
        for(auto& [num, fre] : m){
            if(q.size()<k){
                q.push({num, fre});
            }
            else{
                if(fre> q.top().second){
                    q.pop();
                    q.push({num,fre});
                }
            }
        }
        vector<int> res;
        while(!q.empty()){
            res.push_back(q.top().first);
            q.pop();
        }
        return res;
    }
};

-9 STL--set介绍及set的使用

set与数组不同的是,在set中每个元素的值都是 唯一 的。
而且set插入数据时,能够根据元素的值 自动进行排序 。

set中数元素的值并不能直接被改变。

https://blog.csdn.net/xu1105775448/article/details/81977960

-8 关于移位

在C/C++中,整数分为有符号整数和无符号整数两种
- 1 - 对于无符号数,采用逻辑移位,不论左移右移都用“0”填充;
- 2 - 对于有符号数,分左移和右移两种情况
  - 2a - 若为左移,则属于逻辑运算,补“0”
  - 2b - 若为右移,则属于算术运算,补“符号位”
 

根据上述的描述,可以归纳出(均为补码操作):

  1. 当一个【有效的】左移最高位和数据最高位一致时,算术左移和逻辑左移一样,均为右补0,不一致时,算术左移溢出,无意义!

  2. 逻辑右移很简单,只要将二进制数整体右移,左边补0即可 ,算术右移,左侧均补符号位,即正数补0,负数补1。

  3. 逻辑移位最简单,不管左移右移,移出来的空位补0即可

【C语言/C++】算术移位和逻辑移位以及一道移位的题目

https://blog.csdn.net/qq_31828515/article/details/53286554

算术移位和逻辑移位详解

https://blog.csdn.net/cg2258911936/article/details/103574604

-7. int与unsigned int 隐式类型转换

https://blog.csdn.net/yinzewen123/article/details/80547836

C在以下四种情况下会进行隐式转换:
       1、算术运算式中,低类型能够转换为高类型。
       2、赋值表达式中,右边表达式的值自动隐式转换为左边变量的类型,并赋值给他。
       3、函数调用中参数传递时,系统隐式地将实参转换为形参的类型后,赋给形参。

       4、函数有返回值时,系统将隐式地将返回表达式类型转换为返回值类型,赋值给调用函数。

c++ 中的类型转换(强制转换和隐式类型转换)

https://blog.csdn.net/GangStudyIT/article/details/80743370

C语言的隐式类型转换和显示类型转换

https://zhuanlan.zhihu.com/p/166024562

-6 c++优先队列(priority_queue)用法详解

https://blog.csdn.net/weixin_36888577/article/details/79937886

-5. cpp中堆、栈区别:区别 数据结构堆栈 和 内存中的 堆(区)栈(区)

http://blog.sina.com.cn/s/blog_97fcf3c40101fuxt.html

-4. unordered_map 的拷贝构造(深拷贝)

// 构造方法
unordered_map<std::string,std::string> second ( {{"apple","red"},{"lemon","yellow"}} );       // init list
unordered_map<std::string,std::string> fourth (second); //copy
//insert 方法
unordered_map<std::string,std::string> temp;
temp.insert(b.begin(),b.end());

-3. mutex与浅拷贝的权衡

// mMutexKeyFrame锁 在这个函数内生效,阻塞另一个赋值的线程,
// 随着函数退出 lock析构, mMutexKeyFrame解锁
unordered_map<unsigned long, myslam::Frame::Ptr> Viewer::GetKeyFrames()
{
    std::unique_lock<mutex> lock(vo_->mMutexKeyFrame);
    return vo_->mCurrentKF;
}

// 这里 vpKFs是的 vo_->mCurrentKF浅拷贝,mCurrentKF如果改变,就比较危险
unordered_map<unsigned long, myslam::Frame::Ptr> vpKFs = GetKeyFrames();

//可 深拷贝 vo_->mCurrentKF 得到vpKFs, 或者保证  vpKFs不变,那么可以加 const
const unordered_map<unsigned long, myslam::Frame::Ptr> vpKFs = GetKeyFrames();

//but const 出错如下
error: passing ‘const std::unordered_map<long unsigned int, std::shared_ptr<myslam::Frame> >’ as ‘this’ argument discards qualifiers [-fpermissive]
  234 |          pKF = vpKFs[i];

//也就是 const的map无法 用[]这样的 this访问方法, 那就改迭代器或者 auto 访问呗。

//C++ unordered_map容器在const修饰下将无法使用"[]"来获取键值

const的对象使用了非const的成员函数:std::map::[]本身不是const成员函数(操作符),对于不在map中的关键字,使用下标操作符会创建新的条目,改变了map。

解决办法可用如下:

  • 去掉const,这样有一定的安全风险
  • 拷贝map,有一定的性能开销
  • 对于C++11,可以使用map::at。它有const和non-const两个版本,对于找不到匹配关键字的情况,会抛出out_of_range。由于下标检查,也带来了性能代价。

结论:许多成员函数都设置了const和non-const两个版本,在这样的情况下就发挥了它的意义。今后使用时也应当注意功能相同或相似的函数之间细微的区别。

-2.Static与Const的区别

static

  1. static局部变量 将一个变量声明为函数的局部变量,那么这个局部变量在函数执行完成之后不会被释放,而是继续保留在内存中
  2. static 全局变量 表示一个变量在当前文件的全局内可访问
  3. static 函数 表示一个函数只能在当前文件中被访问
  4. static 类成员变量 表示这个成员为全类所共有
  5. static 类成员函数 表示这个函数为全类所共有,而且只能访问静态成员变量

const

  1. const 常量:定义时就初始化,以后不能更改。
  2. const 形参:func(const int a){};该形参在函数里不能改变
  3. const修饰类成员函数:该函数对成员变量只能进行只读操作

static关键字的作用:

(1)函数体内static变量的作用范围为该函数体,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
(2)在模块内的static全局变量和函数可以被模块内的函数访问,但不能被模块外其它函数访问;
(3)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(4)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。

const关键字的作用:

(1)阻止一个变量被改变
(2)声明常量指针和指针常量
(3)const修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
(4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量;
(5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为”左值”。

-1. stl size()返回值

比如哈希表 std::unordered_map::size 定义如下

size_type size() const noexcept;

返回的size_type:


size_t
Unsigned integral type
Alias of one of the fundamental unsigned integer types.

It is a type able to represent the size of any object in bytes: size_t is the type returned by the sizeof operator and is widely used in the standard library to represent sizes and counts.

size_t不是容器概念。
size_type是容器概念,没有容器不能使用。

size()这个函数返回的类型到底是什么呢?一定要记住,绝对不是整形。不要把size的返回值赋给一个int变量。

那么size_type到底是一种什么样的类型呢?

string类类型和许多其他库类型都定义了一些配套类型(companion type)。通过这些配套类型,库类型的使用就能与机器无关。size_type就是这些配套类型中的一种。

size_type被定义为与unsigned型(unsigned int, unsigned long)具有相同的含义,而且可以保证足够大能够存储任意string对象的长度。为而来使用由string类型定义的size_type类型。程序员必须加上作用于操作符来说明所使用的size_type类型是由string类定义的。

我们为什么不适用int变量来保存string的size呢?

使用int变量的问题是:有些机器上的int变量的表示范围太小,甚至无法存储实际并不长的string对象。如在有16位int型的机器上,int类型变量最大只能表示32767个字符的string对象。而能容纳一个文件内容的string对象轻易就能超过这个数字,因此,为了避免溢出,保存一个string对象的size的最安全的方法就是使用标准库类型string::size_type().

一点注意:虽然是在学习标准库string的时候巧遇了size_type类型,但是,其实vector库也可以定义size_type类型,在vector库中还有一个difference_type类型,该类型用来存储任何两个迭代器对象间的距离,所以是signed类型的。

什么是size_t类型呢?其实本质上和size_type没有多大区别

其实size_t和size_type类似,size_t 类型定义在cstddef头文件中,该文件是C标准库的头文件stddef.h的C++版本.它是一个与机器相关的unsigned类型,其大小足以保证存储内存中对象的大小。用法如下:

bitset<32> bitvec;

size_t sz=bitvec.size();

 另外sizeof操作符的返回值的类型也为size_t

1. shared_ptr<>

shared_ptr 是c++为了提高安全性而添加的智能指针,方便了内存管理。

特点

shared_ptr 是通过指针保持对象共享所有权的智能指针。多个 shared_ptr 对象可占有同一对象。这便是所谓的引用计数(reference counting)。一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。这在非环形数据结构中防止资源泄露很有帮助。使得指针可以共享对象,并且不用考虑内存泄漏问题

shared_ptr 可以支持普通指针的所有操作,完全可以像操作普通指针一样操作智能指针。
shared_ptr 可以通过三种方式得到(拷贝初始化,定义delete操作的方式不在罗列,只讨论初始化指针所指对象来源):
1.通过一个指向堆上申请的空间的指针初始化(切记不要用栈上的指针,否则,当智能指针全部释放控制权(栈中的对象离开作用域本身就会析构一次),将会析构对象,导致出错)
2.通过make_shared函数得到
3.通过另外一个智能指针初始化

https://blog.csdn.net/INGNIGHT/article/details/99881762?utm_medium=distribute.pc_relevant.none-task-blog-title-1&spm=1001.2101.3001.4242

https://blog.csdn.net/janeqi1987/article/details/102820048?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

2.深拷贝和浅拷贝

https://www.cnblogs.com/ay-a/p/10428526.html

// 变量均为 shared_ptr<class>
//浅拷贝
ref_= curr_; 
track_ref_= ref_;
car_ref_= car_curr_;
track_car_ref_= car_ref_;
//导致:
track_ref_= ref_= curr_;
track_car_ref_= car_ref_= car_curr_;
ref_= make_shared<Frame>(*curr_);  //重构造指针并且 深拷贝
track_ref_= make_shared<Frame>(*ref_);
car_ref_= make_shared<Mask_RCNN_Car>(*car_curr_);
track_car_ref_= make_shared<Mask_RCNN_Car>(*car_ref_);

3. vector对象的拷贝:assign()

【C++】vector拷贝使用总结(深复制、浅复制)https://blog.csdn.net/vict_wang/article/details/88812389

4.new 和 delete配套使用,避免内存泄漏

char* a= new char[size];

delete [] a;

5.INT_MAX,INT_MIN

在C++中,

如果是int,用INT_MAX表示正无穷,INT_MIN表示负无穷,需要包含头文件limits.h;

以及 LONG_MAX、LONG_MIN

如果是double,用DBL_MAX表示正无穷,DBL_MIN表示负无穷,需要包含文件float.h。

6. 关于方括号

1.  声明变量时使用“[]”,表示数组。如下所示:
  int a[5];  //定义了一个大小为5的整型数组
  int a[]={1,2,3,4,5};  //定义一个整型数组并初始化
  int *p=new int[size];  //申请一个动态整型数组,数组长度由变量size决定

2.  表示下标运算。如下所示:
  int *p;
  p[1]=2;
  上面的语句不难理解:定义了一个指向整型变量的指针p,由于指针变量中保存着地址,通过“地址+[]”的运算即可给下一地址赋值2.
  STL中的一些模板,也重载了[]运算符,如vector和map。

3.  指示指针。这种用法一般是在数组作为函数参数时会用到。如下所示:
  void fun(int a[]);  //[]说明a是一个指针变量
  void fun(int a[][10]);   //前一个“[]”说明a是指针类型,后一个“[]”则说明指针数组的大小为10.这里的“数组”指的是由二维数组行向量构成的一维数组。这种书写方式一般是在二维数组作为函数参数调用时会用到,此处的10即代表的是数组第二维的大小。需要注意的是,第二维的大小必须明确。
  void fun(int (*a)[10]);  //这种写法和上面是等价的。同样,必须规定第二维的大小。第一维的大小则不受限制,甚至会被编译器“忽略”。例如,你定义了一个函数void func(int a[3][10]),如果此时实参是b[5][10],也可以通过编译,即使它的第一维长度5超过了形参的一维长度3;而如果实参是b[3][8],则不会编译通过,因为它们的第二维长度不一致。 
  值得注意的是,void fun(int **p)与上式的含义是不一样的。这里的p表示的是指针的指针,是“二重”的指针,与int *a[10]中的a是一个意思;而void fun(int a[][10])中的a是二维数组的行向量构成的一维数组的指针,归根结底是“一重”的指针。

4.delete p与delete[] p到底有什么区别?查阅资料可知,这两种用法分别是operator delete函数的两个重载版本。
  通常来说,如果p指向的是单个对象的话,应当调用delete p,以释放内存;如果p指向的是一组对象的内存地址的话,则应当调用delete[] p,可以达到逐个调用每个对象的析构函数,再释放内存的效果。在第二种情况下,如果还是使用delete p语句,则除p[0]之外对象的内存没有得到释放,会造成内存泄漏。
  需要注意的是,如果p指向的一组对象都是像int/long/float一类的简单数据类型,由于它们没有析构函数,因此此时使用delete p与使用delete[] p的效果是一致的。 

5.C++17:结构化绑定声明(Structured Binding Declaration)

auto [a, b] = pair(2, "3"s);
#采用结构化绑定声明了 int 类型的 a 和 string 类型的 b,分别绑定了初始化表达式中 pair 对象的 first 和 second。

 解释:https://blog.csdn.net/zwvista/article/details/78111346

结构化绑定声明,是指在一次声明中同时引入多个变量,同时绑定初始化表达式的各个子对象的语法形式。
结构化绑定声明使用auto来声明多个变量,所有变量都必须用中括号括起来。

cv-auto+引用 [变量1, 变量2, ... 变量n ] = 初始化表达式;
cv-auto+引用 [变量1, 变量2, ... 变量n ] (初始化表达式);
cv-auto+引用 [变量1, 变量2, ... 变量n ] {初始化表达式};
// 这里 cv-auto+引用 包含 auto, auto const, auto &, auto&& 等等形式

结构化绑定所声明的变量有两种形式:
1. 非引用变量,此时初始化表达式对象需要拷贝一份,变量所绑定的是初始化表达式对象拷贝的各个子对象。
2. 引用变量,此时初始化表达式对象不需要拷贝,变量所绑定的是初始化表达式对象本身的各个子对象。

结构化绑定中的初始化表达式有三种类型:
1. 数组类型,此时变量所绑定的是数组的各个元素。
2. pair tuple等支持 tuple_size 的类型,此时变量所绑定的是 get<0>(e),get<1>(e),get<2>(e)
这里E是指类型,e是指对象。
3. 带有 public 成员的结构类型,此时变量所绑定的是结构对象的各个 public 成

7. 关于花括号

在C++11中,使用{}可进行如下各项的初始化:

  • 类成员快速初始化
  • 数组、集合(列表)初始化
  • 自定义类型初始化

  C++11可以将{}初始化器用于任何类型(可以使用等号,也可以不适用),这是一种通用的初始化语法。

int a[] = { 1, 2, 3 };            //C++98支持,C++11支持
int b[]{2, 3, 4};                //C++98不支持,C++11支持
vector<int> c{ 1, 2, 3 };        //C++98不支持,C++11支持
map<int, float> d = {{ 1, 1.0f }, { 2, 2.0f }, { 3, 3.0f } };//C++98不支持,C++11支持
最后两种形式也可以用于获取堆内存new操作符中,例如:

int* i = new int(1);
double* d = new double(1.2f);

8. pair p1;

https://blog.csdn.net/sevenjoin/article/details/81937695

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值