文章目录

  • 关键字protected(带来方便同时带来危险,最好不用)
  • 抽象基类和纯虚函数(is-a关系用公有继承实现有时候也不太合适)
    • 用圆和椭圆的笨拙派生为例,挑拨is-a和公有继承的搭档关系
      • 替代笨拙继承的办法1:单独定义Circle类(不做父子,自立门户)
      • 替代笨拙继承的办法2:ABC, 抽象类(做不了父子就做兄弟)
        • 抽象基类至少有一个纯虚函数
        • 示例 圆
    • 抽象基类的开发设计,对组件编程的影响
  • 示例 银行账户
    • 代码
    • 遇到的问题

关键字protected(带来方便同时带来危险,最好不用)

关于访问控制,即访问类的私有数据成员,之前一直是只用private和public来控制的。现在介绍一个新工具,关键字protected,但他并不是一个多好的东西,有利有弊,是一把双刃剑,使用双刃剑一定要十分小心,很容易杀敌一万自损八千,其实最好不要用这个关键字。下面仔细解释我说的这些。

private数据成员,外部不可以访问,必须通过类的公有接口访问。所以上篇文章写银行账户取款方法和存款方法的时候,访问账户余额必须要通过公有方法Balance()才可以访问到私有数据成员balance,看似麻烦(要多写一个返回私有数据成员的值的方法),实则安全。因为银行账户类的设计,希望账户余额绝对安全,只能让存款取款方法访问和改写余额的值,这样,余额不可能被其他方法修改,是很安全的。

现在有了关键字protected,我们为了方便,不想写Balance()方法,于是干脆把余额设为保护成员,而非私有成员

protected:double balance;

保护成员的双重身份:
保护成员对于外部世界和私有成员一样,不可以被直接访问,必须通过类的公有接口;
但是对于派生类,以及派生链条上的所有类,保护成员却和公有成员一样,任何派生类都可以直接访问他们。

对于我们的程序,在plus会员派生类中确实不需要写Balance()方法访问balance成员了,可以直接在派生类的方法中访问,方便了,但也危险了,因为派生类的任何方法都可以修改余额,派生类的派生类,派生链条上的所有类,的公有方法都可以随意修改余额。。。你敢把钱存到这家银行吗?

所以protected是一个给成员函数偷懒的好工具,但是如果不是脑子里很清楚被派生类随意访问并无大碍,那就不要用保护成员,乖乖设置为私有成员(C++的开创者Stroustrup都是这么建议的),然后写一个返回其值的公有方法,并不很费神,但百分百安全,毫无后顾之忧,何乐而不为呢?

balance是私有成员,用Balance()访问,下面是派生类取钱方法的代码,要用double bal = Balance();,如果balance是保护成员,则后面都直接用balan
ce了

void BrassPlus::Withdraw(double amt)//虚方法
{format initialState = setFormat();precis prec = std::cout.precision(2);double bal = Balance();if (amt < 0)std::cout << "Withdraw amount must be positive!\n"<< "Withdraw cancelled!\n";else if (amt <= bal)//不可写amt <= balance,因为不能直接访问基类数据成员Brass::Withdraw(amt);//不可写balance -= amt;else if(amt - bal + owesBank <= maxLoan){double advance = amt - bal;owesBank += advance * (1.0 + rate);//rate是BrassPlus类的私有成员std::cout << "Bank advance: $" << advance << '\n';std::cout << "Finance charge: $" << advance * rate << '\n';//分两步实现扣除账户全部余额(由于基类不允许取款金额超出余额,所以只能先存再取)Deposit(advance);//放贷Brass::Withdraw(amt);}elsestd::cout << "Credit limit exceeded. Transaction cancelled.\n";restore(initialState, prec);
}

抽象基类和纯虚函数(is-a关系用公有继承实现有时候也不太合适)

前面说了有三种继承——公有继承,私有继承,保护继承。

但是目前还是一直在讨论公有继承。

我们说到基类和派生类的五种常见关系:is-a, has-a, is-like-a, uses-a, is-implemented-by-a,其中只有is-a用公有继承来实现是比较好的,其他四个关系都不适合公有继承。

但是现在我们又要说,is-a关系和公有继承之间也不是百分百合拍,完美和谐。有时候,is-a关系不能简单使用公有继承,否则麻烦不断后患无穷,总之不是好的设计。

用圆和椭圆的笨拙派生为例,挑拨is-a和公有继承的搭档关系

数学上,圆是一种特殊的椭圆,是椭圆的一个特例,特殊在于长半轴和短半轴长度相等。于是满足is-a关系,于是我们使用公有继承,从Ellipse类派生出Circle类。

Ellipse类声明,私有数据成员包括位置坐标,长短半轴长度,角度。方法有移动,旋转,缩放。

由于Circle类也要用这三种方法,所以都设置为了虚函数。

由于Ellipse类要成为基类,所以设置了虚析构函
数。

//ellipse.h
#ifndef ELLIPSE_H_
#define ELLIPSE_H_class Ellipse{private:double x;//位置横坐标double y;//位置纵坐标double a;//长半轴double b;//短半轴double angle;//x轴和长轴的夹角
public:Ellipse(double nx = 0.0, double ny = 0.0, double na = 1.0, double nb = 1.0, double ang = 0.0):x(nx), y(ny), a(na), b(nb), angle(ang){}virtual ~Ellipse(){}virtual void Move(double mx, double my){x = mx;y = my;}virtual void Rotate(double ang){angle += ang;}virtual void Scale(double sx, double sy){a *= sx; b *= sy;}
};
#endif // ELLIPSE_H_

于是Circle类的声
明是

//circle.h
#ifndef CIRCLE_H_
#define CITCLE_H_class Circle : public Ellipse
{public:Circle(double nx, double ny, double a, double b):x(nx), y(ny), a(r), b(r){}
};
#endif // CIRCLE_H_

写Circle类声明的时候,会发现很多不太好的地方:

  • Cirlcle类本不需要长半轴,短半轴和角度三个数据成员,可是公有继承使得Circle被迫有了这几个不相干的成员,就好像你家里非要养几个别人的孩子,显然是不合理的。每个Circle对象都要多几个完全用不着的成员。

虽Circle的构造函数把半径传给a,b(Ellipse类的轴),但用两个变量来表示半径很冗余。

  • Circle类本不需要旋转方法,圆旋转是没有意义的。但是公有继承使它被迫有了。
  • Circle类虽然不需要改写移动,缩放方法,但是方法的实现里用了轴,但是圆本没有轴,虽然计算和树枝上没错,但总觉得怪拐的。

所以公有继承并不适用于圆和椭圆的这种is-a关系。

替代笨拙继承的办法1:单独定义Circle类(不做父子,自立门户)

一种办法是,直接单独定义Circle类,和Ellipse类毫无瓜葛,两个独立
的类

//circle.h
#ifndef CIRCLE_H_
#define CITCLE_H_class Circle{private:double x;//位置横坐标double y;//位置纵坐标double radius;//半径
public:Circle(double nx, double ny, double r):x(nx), y(ny), radius(r){}~Circle(){}double Area() const {return 3.1415 * radius * radius;}void Move(double mx, double my){x = mx;y = my;}void Scale(double s){radius *= s;}
};
#endif // CIRCLE_H_

现在Circle无需再包含自己不需要的成员

替代笨拙继承的办法2:ABC, 抽象类(做不了父子就做兄弟)

第一种办法虽然可以解决问题,但终究只是次优解。因为它还是造成了冗余。冗余来自于椭圆和圆的诸多共性。

前面因为他们的共性,我们使用了公有继承,但是发现他们的不同之处使得公有继承并不合适,所以我们又取消了公有继承,直接划分界限,彼此独立,大家都单干。但是这样虽然是一个办法,但是我们等于是蒙住自己的眼睛,让自己看不见圆和椭圆的共性。不能因为二者的相异之处来阻挡一下,就放弃融合二者的共性。

那到底怎么拉拢像这种情况下的共性呢?

C++给出的答案是抽象基类。就像Java的抽象类一样。(所以以前的那些类都是具体类concrete class)

它的思路是:
圆和椭圆有共性,是is-a,但是又不适合公有继承,为了避免二者自立门户带来的冗余,就把二者的共性剥离出来,用这些共性凌驾于圆和椭圆之上,单独建立一个基类,把共性都封装在这个基类里。
由于共性是通用的,所以是抽象的,所以把这个基类叫做抽象基类。Abstract base class。
让Circle类和Ellipse类都去继承这个抽象基类。

简单地说,就是做不了父子就做兄弟,找一个共同的家长。

这样做不仅不再有冗余,并且每个类都只有自己需要的成员,更重要的是,可以建立指向基类对象的指针数组,同时管理Ellipse类对象和Circle类对象,从而实现多态。

说了解决方案,我们看看具体实现,会不会再碰到什么问题,从而引出什么新的工具呢?(套路脸)

Circle类和Ellipse类的共性有:
数据:

  • 中心坐标,即横坐标,纵坐标两个数据成员

方法:

  • 计算面积的方法,两个类实现不同;且要用到抽象基类没有的数据成员(圆:半径;椭圆:长半轴,短半轴)
  • 移动方法,两个类实现相同;且只需要用抽象基类的数据(中心点的横纵坐标)
  • 缩放方法,两个类实现不同;且要用到抽象基类没有的数据成员(圆:半径;椭圆:长半轴,短半轴)

问题来了:移动方法好说,那缩放和面积怎么写代码,抽象基类中没法写他俩的定义啊。也不能只写个原型,前面有教训了,只有原型没有定义的函数在编译器势利的眼睛里形同虚设。

所以C++只好再出手,祭出一个纯虚函数(这名字有没有很玄乎的感觉,又纯又虚,倒是很适合做仙侠玄幻类影视作品里某个神座的名字,纯虚元君,纯虚真人,哈哈哈,开脑洞了)

抽象基类至少有一个纯虚函数

纯虚函数由于是“纯粹虚无”的,所以一般是不会写他的具体定义的。他只是描述了一个接口,具体怎么实现还是派生类自己去根据需要实现,注意纯虚函数在派生类时自动成为虚函数哈。

之前说的虚函数是有定义的哦,只是派生类可以重写新定义从而隐藏基类的旧定义从而使得虚函数的旧定义似有还未似无还有虚无缥缈。但是纯虚函数自然是要更虚的,虚无到直接没有定义了。

但是纯虚函数也可以有定义的哦。只是一般不写。

方法原型后的 = 0 指出类的身份是ABC。

一个抽象基类至少有一个纯虚函数。
至少有一个纯虚函数才能成为抽象基类。
所有虚函数中至少有一个是纯虚的

有几点需要注意:

  • 类声明有纯虚函数,则该类只能作为抽象基类,且不可以创建该类的对象

不能创建抽象基类的对象。因为抽象基类是抽象的,没法实例化一个具体的对象出来。只有具体类才可以创建自己的对象,因为他们够具体,他们的对象有具体的数据成员和具体的操作方法。

但是可以创建该类的指针或引用,以管理他的派生类。

  • 如果抽象基类的所有方法,包括纯虚函数,都有定义(可以在头问价写内联定义,也可以在方法文件写定义)。那也要把这个类声明为抽象的——通过在所有函数的原型后面加上 = 0。
  • ABC的纯虚函数最后都会被派生类的实现所覆盖,隐藏。

示例 圆

和椭圆

//BaseEllipse.h
#ifndef BASEELLIPSE_H_
#define BASEELLIPSE_H_class BaseEllipse{private:double x;double y;
public:void Move(double nx, double ny) {x = nx; y = ny;}virtual void Scale(double s) =0;//´¿Ð麯Êývirtual double Area() const =0;//´¿Ð麯Êý};
#endif // BASEELLIPSE_H_

抽象基类的开发设计,对组件编程的影响

开发ABC就像开发一个接口,是抽象的,这对于基于组件的编程模式很常见,ABC的设计人员设计了纯虚函数等“接口约定”,派生类具体类的设计人员就要严格遵循ABC接口规则来编程,所以由ABC派生出来的所有组件全部都支持ABC想要的功能。

只要可以,就设计包含纯虚函数的抽象基类,这是一个很好很好的工具,然后从它派生出其他类。

示例 银行账户

设计开发ABC之前,应该仔细分析编程问题所需要的类,以及这些类之间的关系,是否有共性以设计一个ABC类。

要把这些类的继承层次梳理清楚,把那些不会被继承的类设计为具体类。

代码

三个类的声明放
在一起

//AcctABC.h
#ifndef ACCTABC_H_
#define ACCTABC_H_class AcctABC{private://这三个成员不可以是保护成员,那样不安全std::string fullName;long acctNum;double balance;
protected://保护数据成员,派生类对象可以访问struct Formatting{std::ios_base::fmtflags flag;std::streamsize pr;};//保护成员方法,提供了对客户名等数据的只读访问const std::string & FullName() const {return fullName;}long AcctNum() const {return acctNum;}Formatting SetFormat() const;void Restore(Formatting & f) const;
public:AcctABC(const std::string & s = "None None",long an = -1, double bal = 0.0);virtual ~AcctABC(){}void Deposit(double amt);//普通会员和plus会员存钱方法一样,直接在抽象基类定义virtual void WithDraw(double amt) = 0;//普通会员和plus会员存钱方法不一样,在抽象基类定义定义为虚函数,在派生类修改重写//这里不好写定义,要重写,所以是纯虚函数double Balance() const {return balance;}//不需要重写,所以不是虚方法virtual void ViewAcct() const = 0;//这里不好写定义,要重写,所以是纯虚函数
};class Brass : public AcctABC
{public:Brass(const std::string & s = "None None", long an = -1,double bal = 0.0):AcctABC(s, an, bal){}~Brass(){}virtual void WithDraw(double amt);virtual void ViewAcct() const;
};class BrassPlus : public AcctABC
{private:double maxLoan;double rate;double owesBank;
public:BrassPlus(const std::string s = "None None", long an = -1,double ba = 0.0, double max = 500.0,double r = 0.1125, double owe = 0.0):AcctABC(s, an, ba), maxLoan(max), rate(r), owesBank(owe){}~BrassPlus(){}virtual void WithDraw(double amt);virtual void ViewAcct() const;void ResetMax(double m){maxLoan = m;}void ResetRate(double r){rate = r;}void ResetOwes(){owesBank = 0.0;}
} ;#endif // ACCTABC_H_

三个类的方法放
在一起

#include <iostream>
#include "AcctABC.h"
using std::cout;//AcctABC methods
AcctABC::AcctABC(const std::string & s, long an, double bal)
{fullName = s;acctNum = an;balance = bal;
}void AcctABC::Deposit(double amt)
{if (amt < 0)cout << "Negative deposit not allowed!"<< " deposit is cancelled.\n";elsebalance += amt;
}void AcctABC::WithDraw(double amt)
{if (amt < 0)cout << "Negative withdraw not allowed!"<< " withdraw is cancelled.\n";elsebalance -= amt;
}AcctABC::Formatting AcctABC::SetFormat() const
{//set up ###.## formatFormatting f;f.flag = cout.setf(std::ios_base::fixed, std::ios_base::floatfield);f.pr = cout.precision(2);return f;
}void AcctABC::Restore(Formatting & f) const
{cout.setf(f.flag, std::ios_base::floatfield);cout.precision(f.pr);
}//Brass methods
void Brass::WithDraw(double amt)
{if (amt < 0)cout << "Negative withdraw not allowed!"<< " withdraw is cancelled.\n";else if (amt <= Balance())AcctABC::WithDraw(amt);elsecout << "Withdraw amount of $" << amt<< " exceed account balance.\n"<< "Withdraw cancelled!\n";
}void Brass::ViewAcct() const
{Formatting f = SetFormat();cout << "Brass Client: " << FullName() << '\n'<< "Account Number: " << AcctNum() << '\n'<< "Balance: $" << Balance() << '\n';Restore(f);
}//BrassPlus methods
void BrassPlus::WithDraw(double amt)
{Formatting f = SetFormat();if (amt < 0)cout << "Negative withdraw not allowed!"<< " withdraw is cancelled.\n";else if (amt <= Balance())AcctABC::WithDraw(amt);else if (amt <= Balance() + maxLoan - owesBank){double advance = amt - Balance();Deposit(advance);AcctABC::WithDraw(amt);//ук╩╖н╙0owesBank += advance * (1.0 + rate);cout << "Bank advance: $" << advance << '\n'<< "Finance Charge: $" << advance * rate << '\n';}elsecout << "Withdraw amount of $" << amt<< " exceed account's overdraft limit.\n"<< "Withdraw cancelled!\n";Restore(f);
}void BrassPlus::ViewAcct() const
{Formatting f = SetFormat();cout << "BrassPlus Client: " << FullName() << '\n'<< "Account Number: " << AcctNum() << '\n'<< "Balance: $" << Balance() << '\n'<< "Max Loan: $" << maxLoan << '\n'<< "Owed Bank: $" << owesBank << '\n';cout.precision(3);cout << "Loan Rate: " << rate * 100 << "%\n";Restore(f);
}

主程序和继承的第二篇文章里面的基类指针数组实现多态的主程序一样,只是基类改为了抽
象基类

//main.cpp
#include <iostream>
#include "AcctABC.h"
const int NUM = 4;
void eatline();int main()
{using std::cout;using std::cin;AcctABC * p_clients[NUM];//抽象基类指针数组std::string tempName;long tempAcct;double openBal;char kind;//会员种类,普通会员或plus会员int i;for (i = 0; i < NUM; ++i){cout << "Enter client's name: ";getline(cin, tempName);cout << "Enter client's accout number: ";cin >> tempAcct;eatline();cout << "Enter opening balance: $";cin >> openBal;eatline();cout << "Enter 1 for Brass Account or 2 for BrassPlus Account:";while ((kind = cin.get()) != '1' && (kind != '2'))continue;eatline();//否则换行符会被读取为下一个客户的名字if (kind == '1'){p_clients[i] = new Brass(tempName, tempAcct, openBal);cout << '\n';}else if (kind == '2'){cout << "Enter the overdraft limit: ";double tempLimit;cin >> tempLimit;eatline();cout << "Enter the interest rate: ";double tempRate;cin >> tempRate;eatline();p_clients[i] = new BrassPlus(tempName, tempAcct, openBal, tempLimit, tempRate);cout << '\n';}}//显示四位顾客的信息for (i = 0; i < NUM; ++i){cout << '\n';p_clients[i]->ViewAcct();//展示多态特性的核心代码delete p_clients[i];//总是记不住delete}return 0;
}void eatline()
{while (std::cin.get() != '\n');
}
`
`````cpp
Enter client's name: gfd fg
Enter client's accout number: 456465
Enter opening balance: $456456
Enter 1 for Brass Account or 2 for BrassPlus Account:1Enter client's name: adsfasd fdfd
Enter client's accout number: 465465
Enter opening balance: $456456
Enter 1 for Brass Account or 2 for BrassPlus Account:2
Enter the overdraft limit: 465465
Enter the interest rate: 0.2Enter client's name: fasdf df
Enter client's accout number: 4564564
Enter opening balance: $4545
Enter 1 for Brass Account or 2 for BrassPlus Account:2
Enter the overdraft limit: 454
Enter the interest rate: 0.1Enter client's name: fdasf
Enter client's accout number: 465
Enter opening balance: $554
Enter 1 for Brass Account or 2 for BrassPlus Account:1Brass Client: gfd fg
Account Number: 456465
Balance: $456456.00BrassPlus Client: adsfasd fdfd
Account Number: 465465
Balance: $456456.00
Max Loan: $465465.00
Owed Bank: $0.00
Loan Rate: 20.000%BrassPlus Client: fasdf df
Account Number: 4564564
Balance: $4545.00
Max Loan: $454.00
Owed Bank: $0.00
Loan Rate: 10.000%Brass Client: fdasf
Account Number: 465
Balance: $554.00

遇到的问题

  • 保证项目中只有自己需要使用的文件被编译

我习惯只建立一个项目,然后不断添加头文件和方法文件,修改主程序,代码都是保存在博客的。

之前新建了一个文件,后缀不小心写为.c,于是就直接叉掉了,但是他仍然在项目中,且没有生成.o目标代码,于是编译报错:

在项目的路径下没有untitled2.c, debug目录下也没有untiled2.o,很奇怪,但是项目认为还有,所以必须要找到项目的文件目录

code::blocks 菜单栏 点击 项目–属性–编译目标–勾选需要编译的文件, 搞定


其他问题都是小问题,比如方法调用时忘记首字母大写,结果找不到这个方法,但是好多地方都写错了,还是要反省
,不严谨

C++ day24 继承(四)抽象基类,纯虚函数,protected相关推荐

  1. c++中的虚特性(虚基类、虚函数、纯虚函数)

    1. 虚基类 1.1 虚基类作用 为了解决多继承时的命名冲突和冗余数据问题,使得派生类中只保留一份间接基类的成员. 其本质是是让某个类做出声明,承诺愿意共享它的基类.其中,这个被共享的基类就称为虚基类 ...

  2. cnbloger: 北岛知寒, C++ - 虚基类、虚函数与纯虚函数; csdner: Hsuxu, C++虚基类的实现机制

    If the author of the article is not allowed to reprint, this article will be deleted C++ - 虚基类.虚函数与纯 ...

  3. 虚基类、虚函数和纯虚基类

    http://blog.csdn.net/lovemysea/article/details/5298589 首先看一个例子: class Base { public:     virtual voi ...

  4. 理解虚基类、虚函数与纯虚函数的概念

    引言 一直以来都没有写过一篇关于概念性的文章,因为我觉得这些概念性的东西书本上都有并且说的也很详细写来也无用,今天突发奇想想写 一写,下面就和大家讨论一下虚基类.虚函数与纯虚函数,一看名字就让人很容易 ...

  5. C++ - 虚基类、虚函数与纯虚函数

    虚基类        在说明其作用前先看一段代码 class A { public:    int iValue; }; class B:public A { public:    void bPri ...

  6. 基类、派生类、虚基类、虚函数、虚析构、纯虚函数、抽象类

    基类:被其它类通过继承方式作为父类继承的类称为基类:描述派生类的统一种类的方式. 派生类:通过继承其他类(并可能含有自定义成员)实现的子类:为提高代码的重用性及与同样继承于同一个父类的其它类形成统一种 ...

  7. C++虚继承和虚基类;虚函数与继承

    ref http://blog.csdn.net/owen7500/article/details/52432970?locationNum=4&fps=1 http://blog.csdn. ...

  8. 【虚基类、虚函数及应用】

    虚基类 1.虚基类存在的意义 当在多条继承路径上有一个公共的基类,在这些路径中的某几条汇合处,这个公共的基类就会产生多个实例(或多个副本),若只想保存这个基类的一个实例,可以将这个公共基类说明为虚基类 ...

  9. 多态情况下,怎么用基类指针去访问基类的虚函数?

    class Base { // 基类 public:virtual void f() {cout<<"Base"<<endl;} }; class Deri ...

  10. java基类能调用虚函数_如果我要重写基类的虚函数,可以调用它吗?

    MYYA 是,class Bar : public Foo{    ...    void printStuff()    {        Foo::printStuff();    }};它与su ...

最新文章

  1. sqlserver数据库类型对应Java中的数据类型
  2. 《LeetCode力扣练习》第94题 二叉树的中序遍历 Java
  3. JS三种简单排序算法
  4. Codeforces 861D - Polycarp's phone book 字典树/hash
  5. 哪些信用卡取现0手续费?
  6. Oracle Golden Gate概要
  7. 让WordPress达到最高性能的13个优化技巧
  8. 【信息系统项目管理师】第6章-项目进度管理 知识点详细整理
  9. spring mysql 中文_Spring+mysql+velocity 中文问题解决方法
  10. 阶段3 1.Mybatis_08.动态SQL_03.mybatis中动态sql语句-foreach和sql标签
  11. 《信号与系统》解读 前言:经典教材的选择
  12. android打印机驱动4521,三星scx4521f驱动下载
  13. 机械设计基础课程设计【1】
  14. 网页常用字体 【参考】
  15. 吉林大学计算机学院辅导员王,毕业30载 吉林大学计算机学院1982级校友重返母校...
  16. 解决Elasticsearch集群 master_not_discovered_exception 异常
  17. Edge浏览器查看请求头(2022)
  18. 手把手教你mockjs实际项目快速搭建
  19. php的md5(),php MD5加密详解
  20. [4G5G基础学习]:流程 - 4G LTE 接入网的随机接入流程

热门文章

  1. Amazon CodePipeline 与 GitHub 集成
  2. Hinton 最新研究:神经网络的未来是前向-前向算法
  3. MFC中使用sqlite3操作数据库 创建,插入数据,查询数据
  4. Ubuntu下系统CPU/内存/GPU/硬盘监控查看指令
  5. 【Web】分页简单实现
  6. 如何在CMD命令行下批量ping多个ip并且将结果输出
  7. Future.get()抛出ExecutionException或InterruptedException?
  8. Mac下从安装Git到使用github进行版本控制(git命令/Xcode管理)
  9. vscode打开文件方式小结
  10. 一个5节点的polardb mysql_POLARDB云数据库分布式存储引擎揭秘,POLARDB和MySql 5.6兼容性能对比...