上行转换,就是取一个子类的对象的地址,并且按照其父类进行处理。因为子类是继承基类的,所以这种转换是不需要显式类型的转换的。而C++的多态就是按照这一特性实现的呢。上行转换(派生类的指针或者引用转换成基类)是安全的。
上行转换示例:
//上行转换
std::cout << "************上行转换************" << std::endl;
class Driver driver1;
class Base *base1 = &driver1;
base1->display();
std::cout << "mBase Data = " << base1->mBaseData << std::endl;
std::cout << "********************************" << std::endl;
std::cout << std::endl;
1.2下行转换
所谓下行转换是把基类的指针或引用转换为派生类表示,按照类的继承树来看,这种转换是有多种的,不知道转换成哪个子类,所以需要显式转换dynamic_cast (下文详细说明)。
下行转换示例:
//下行转换
class Base base2;
class Driver *driver2;
driver2 = (Driver *)&base2;
std::cout << "************下行转换************" << std::endl;
driver2->display();
std::cout << "Base data = " << driver2->mBaseData << " Driver data = " << driver2->mDriverData << std::endl;
std::cout << "********************************" << std::endl;
下面是上行转换和下行转换的对比:
/*************************************************************************
> File Name: test.cpp
> Author: 小和尚念经敲木鱼
> Mail:
> Created Time: Fri 22 Apr 2022 11:27:07 PM CST
***********************************************************************/
#include
#include
using namespace std;
/************************************************************************
* 文件说明
* static_pointer_cast、dynamic_pointer_cast、
* const_pointer_cast、reinterpret_pointer_cast
* 学习笔记
************************************************************************/
class Base {
public:
Base() {
printf("%s @%d\n", __func__, __LINE__);
}
~Base() {
printf("%s @%d\n", __func__, __LINE__);
}
void display() {
printf("%s @%d this is a base print display\n" , __func__, __LINE__);
}
public:
int mBaseData = 1;
};
class Driver : public Base {
public:
Driver() {
printf("%s @%d\n", __func__, __LINE__);
}
~Driver() {
printf("%s @%d\n", __func__, __LINE__);
}
public:
int mDriverData = 10;
};
int main(int agc,char * agv[])
{
///
//上行转换
std::cout << "************上行转换************" << std::endl;
class Driver driver1;
class Base *base1 = &driver1;
base1->display();
std::cout << "mBase Data = " << base1->mBaseData << std::endl;
std::cout << "********************************" << std::endl;
std::cout << std::endl;
std::cout << std::endl;
/
//下行转换
class Base base2;
class Driver *driver2;
driver2 = (Driver *)&base2;
std::cout << "************下行转换************" << std::endl;
driver2->display();
std::cout << "Base data = " << driver2->mBaseData << " Driver data = " << driver2->mDriverData << std::endl;
std::cout << "********************************" << std::endl;
return 0;
}
//************上行转换************
//Base @22
//Driver @39
//display @29 this is a base print display
//mBase Data = 1
//********************************
//
//
//Base @22
//************下行转换************
//display @29 this is a base print display
//Base data = 1 Driver data = 1067911696
//********************************
//~Base @25
//~Driver @42
//~Base @25
/******************************end of file******************************/
上面简单的介绍上下行转换的基本概念,那如何区分上下行转换呢? 就是从 = 号从右往左看,如果右边是子类指针或者子类的引用,右边是父类的指针或者引用,按照继承树来看,则是上行转换。如果右边是父类的引用或者指针,左边是子类的指针或者引用,则是下行转换。这里还有一个知识点,那就是切割。切割的定义是覆盖方法和子类数据丢失的现象生成切割(slice)。
下面是产生切割的示例:
/*************************************************************************
> File Name: shared_pointer_cast.cpp
> Author: 小和尚念经敲木鱼
> Mail:
> Created Time: Fri 22 Apr 2022 11:27:07 PM CST
***********************************************************************/
#include
#include
using namespace std;
/************************************************************************
* 文件说明
* static_pointer_cast、dynamic_pointer_cast、
* const_pointer_cast、reinterpret_pointer_cast
*
************************************************************************/
class Base
{
public:
int mBaseData = 10;
virtual void Test()
{
cout << "Base" <<endl;
}
};
class Driver : public Base
{
public:
int mDriverData = 100;
virtual void Test()
{
cout << "Driver" <<endl;
}
};
int main()
{
Driver d;
Base b = d;//直接赋值(产生切割)
b.Test();
Base& b2 = d;//使用引用赋值(不产生切割)
b2.Test();
Base* b3 = &d;//使用指针赋值(不产生切割)
b3->Test();
return 1;
}
//Out
//Base
//Driver
//Driver
/******************************end of file******************************/
2、static_cast、dynamic_cast、const_cast、reinterpret_cast区别
static_cast、dynamic_cast、const_cast、reinterpret_cast都是c/c++里面的转换方式,下面就针对这四种转换方式分别做解析。
2.1 static_cast 关键字static_cast 是"静态转换",其是在编译期间进行转换的,如果转化失败则编译异常。该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。所以其只能用于良性转换,转换风险较低。
用法需要注意点如下:
(1)在用于类层次结构中基类(父类)和 派生类 (子类)之间指针或引用的转换。
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成派生类表示时,由于 没有动态类型检查,所以是不安全的。
(2)用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
(3)把空指针转换成目标类型的空指针。
(4)把任何类型的表达式转换成void类型。
注意:static_cast不能转换掉expression的const、volatile、或者__unaligned属性。
使用示例:
基本数据类型的转换:
/*******************************************************************************/
char a1 = 'a';
int b1 = static_cast<char>(a1);//正确,将char型数据转换成int型数据
double *a2 = new double;
void *b2 = static_cast<void*>(a2);//正确,将double指针转换成void指针
int a3 = 10;
const int b3 = static_cast<const int>(a3);//正确,将int型数据转换成const int型数据
const int a4 = 20;
int *b4 = static_cast<int*>(&a4);//编译错误,static_cast不能转换掉g的const属性
/*******************************************************************************/
类的static_cast转换:
/*************************************************************************
> File Name: shared_pointer_cast.cpp
> Author: 小和尚念经敲木鱼
> Mail:
> Created Time: Fri 22 Apr 2022 11:27:07 PM CST
***********************************************************************/
#include
#include
#include
using namespace std;
/************************************************************************
* 文件说明
* static_pointer_cast、dynamic_pointer_cast、
* const_pointer_cast、reinterpret_pointer_cast
*
************************************************************************/
class Base
{
public:
int mBaseData = 10;
virtual void Test() {
cout << "Base" <<endl;
}
};
class Driver : public Base
{
public:
int mDriverData = 100;
virtual void Test() {
cout << "Driver" <<endl;
}
};
int main()
{
//类的下行转换,下行转换是不安全的
Base *pBase = new Base();
Driver *pDriver = static_cast<Driver *> (pBase); //下行转换是不安全的,所以下面打印是没有DriverData.
std::cout << "Driver BaseData = " << pDriver->mBaseData << std::endl;
std::cout << "Driver DriverData = " << pDriver->mDriverData << std::endl;
//类的上行转换,上行转换是安全的
std::cout << "--------------------------" << std::endl;
Driver *pDriver2 = new Driver();
Base *pBase2 = static_cast<Base*> (pDriver2); //上行转换是安全的
std::cout << "Base BaseData = " << pBase2->mBaseData << std::endl;
}
//OUT
//Driver BaseData = 10
//Driver DriverData = 0
//--------------------------
//Base BaseData = 10
/*******************************************************************************/
2.2 dynamic_cast 关键字
dynamic_cast < type-id > (expression)
在上面类型中,Type-id可以是类的指针、类的引用或者void*。type-id 和 expression应该是一样的,如果type-id是指针(引用)的话,那么expression也应该是指针(引用)。
dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一模一样的。但是在进行下行转换时,dynamic_cast比static_cast更加智能些,其具有类型检查功能,比static_cast更加安全。而父类dynamic_cast到子类时,父类必须要有虚函数,否则编译器会报错,还可以用于类之间的交叉转换。
dynamic_cast将一个基类对象指针(或引用)cast到继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理, 即会作出一定的判断。
若对指针进行dynamic_cast,失败返回null,成功返回正常cast后的对象指针;
若对引用进行dynamic_cast,失败抛出一个异常,成功返回正常cast后的对象引用。
使用示例:
/*************************************************************************
> File Name: shared_pointer_cast.cpp
> Author: 小和尚念经敲木鱼
> Mail:
> Created Time: Fri 22 Apr 2022 11:27:07 PM CST
***********************************************************************/
#include
#include
#include
using namespace std;
/************************************************************************
* 文件说明
* static_pointer_cast、dynamic_pointer_cast、
* const_pointer_cast、reinterpret_pointer_cast
*
************************************************************************/
class Base
{
public:
Base() {
cout << "Base" <<endl;
}
public:
int mBaseData = 10;
public:
virtual void Test()
{
cout << "Base" <<endl;
}
};
class Driver1 : public Base
{
public:
Driver1() {
cout << "Driver1" <<endl;
}
public:
int mDriver1Data = 100;
virtual void Test()
{
cout << "Driver1" <<endl;
}
};
class Driver2 : public Base
{
public:
Driver2() {
cout << "Driver2" <<endl;
}
public:
int mDriver2Data = 100;
public:
virtual void Test()
{
cout << "Test" <<endl;
}
};
class tDriver : public Driver1 , public Driver2
{
public:
tDriver() {
cout << "tDriver" <<endl;
}
virtual void tDriverTest()
{
cout << "tDriver" <<endl;
}
public:
int mTDriverData = 100;
};
int main()
{
class Driver1* driver1 = new Driver1();
class Driver2* driver2 = new Driver2();
class tDriver* tdriver1 = dynamic_cast<class tDriver*> (driver1);
class tDriver* tdriver2 = dynamic_cast<class tDriver*> (driver2);
// tDriver --> Base 转换的时候需要中间调整一下。
class tDriver* tdriver3 = new tDriver();
class Driver1* testDriver1 = dynamic_cast<Driver1*>(tdriver3);
Base* base1 = dynamic_cast<Base*>(testDriver1);
class tDriver* tdriver4 = new tDriver();
class Driver2* testDriver2 = dynamic_cast<Driver2*>(tdriver4);
Base* base2 = dynamic_cast<Base*>(testDriver2);
}
//OUT
//Base
//Driver1
//Base
//Driver2
//Base
//Driver1
//Base
//Driver2
//tDriver
//Base
//Driver1
//Base
//Driver2
//tDriver
2.3 const_cast 关键字
const_cast从字面来看是针对常量类型进行转换的,其可以修改类型的const和volatile属性。也是C++中唯一有此能力的类型转化符。
用法:
1.常量指针被转换成非常量指针,并且还是指向原来的对象。
2.常量引用转换成非常常量引用,并且还是依然指向原来的对象。
3.const_cast一般用来修改基本类型指针(char *、int * 、long *)等。
使用示例:
对基本数据类型的使用:
const int test_a = 10;
int *test_a1 = const_cast<int*>(&test_a);//去掉const常量const属性
std::cout<< "test_a1=" << *test_a1 << std::endl;
const int test_b = 15;
int &test_b1 = const_cast<int &>(test_b);//去掉const引用const属性
std::cout << "test_b1=" << test_b1 << std::endl;
const char *test_c = "hello";
char *test_c1 = const_cast<char *>(test_c);//去掉const指针const属性
std::cout << "test_c1=" << test_c1 << std::endl;
//OUT
//test_a1=10
//test_b1=15
//test_c1=hello
对类使用:
/*************************************************************************
> File Name: shared_pointer_cast.cpp
> Author: 小和尚念经敲木鱼
> Mail:
> Created Time: Fri 22 Apr 2022 11:27:07 PM CST
***********************************************************************/
#include
#include
#include
using namespace std;
/************************************************************************
* 文件说明
* static_pointer_cast、dynamic_pointer_cast、
* const_pointer_cast、reinterpret_pointer_cast
*
************************************************************************/
class Base
{
public:
Base(int point,std::string data) :
mBasePoint(point),
mBaseData(data) {
std::cout << "Base point:" << point << std::endl;
std::cout << "Base Data:" << data << std::endl;
}
public:
int mBasePoint;
std::string mBaseData;
public:
void Base_Test() {
std::cout << "Point:" << mBasePoint << "\t Data:" << mBaseData << std::endl;
}
};
class Driver1 : public Base
{
public:
Driver1(int point, std::string data) : Base(point, data) {
mDriverPoint = point + 10;
mDriverData = data;
cout << "Driver1" <<endl;
}
public:
int mDriverPoint;
std::string mDriverData;
public:
void Display() {
cout << "Driver1 DriverPoint:" << mDriverPoint << " Data:" << mDriverData <<endl;
}
void SetData(std::string data) {
mDriverData = data;
}
};
int main()
{
const Base* base_test = new Base(10,"this is a test!");
Base* base_test_1 = const_cast<Base*>(base_test);
base_test_1->Base_Test();
Driver1* driver1 = new Driver1(20, "this is a demo!");
driver1->SetData("##############\n");
//const_cast 把const修饰的类型转换为非const类型
Driver1* driver2 = const_cast<Driver1*> (driver1);
driver2->Display();
return 0;
}
2.4 reinterpret_cast 关键字
reinterpret_cast
new_type必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值。但是reinterpret_cast意图执行低级转型,实际动作(及结果)可能取决于编辑器,这也就表示它不可移植。按照理解就是可以把一个整型转换为指针,也可以把一个指针转换为整型。
使用示例:
int display(int data) {
std::cout << __func__ <<"data:" << data << std::endl;
return 0;
}
typedef int (*test_func)(int);
int main(){
int data = 10;
test_func fun_1 = display;
fun_1(data);
//将函数display转换成一个值
auto value = reinterpret_cast<test_func>(display);
std::cout << "value:" << value << std::endl;
//将函数value转换成一个函数
test_func test_value = reinterpret_cast<test_func>(value);
test_value(1000);
//不能直接将数据转换成函数进行使用
//test_func fun2 = reinterpret_cast(&data);
//fun2(data);//...处有未经处理的异常: 0xC0000005: Access violation
return 0;
}
//Out
//displaydata:10
//value:1
//displaydata:1000
//段错误 (核心已转储)
从上面中我们可以看出,不能直接将一个数据转换成函数进行使用。reinterpret_cast 在转化数据成函数时,数据本身应该是由函数转换得来的,不然会出问题。
类之间的使用
/*************************************************************************
> File Name: shared_pointer_cast.cpp
> Author: 小和尚念经敲木鱼
> Mail:
> Created Time: Fri 22 Apr 2022 11:27:07 PM CST
***********************************************************************/
#include
#include
#include
using namespace std;
/************************************************************************
* 文件说明
* static_pointer_cast、dynamic_pointer_cast、
* const_pointer_cast、reinterpret_pointer_cast
*
************************************************************************/
class TestA
{
public:
int mA;
};
class TestB
{
public:
int mB;
};
class TestC : public TestA, public TestB {};
int main(){
TestC test_c;
printf("%p\n%p\n%p\n", &test_c, reinterpret_cast<TestB*>(&test_c), static_cast <TestB*>(&test_c));
return 0;
}
//Out
//0x7ffdc350a320
//0x7ffdc350a320
//0x7ffdc350a324
/******************************end of file******************************/
前两个的输出值是相同的,而最后一个则会在原基础上偏移4个字节,这是因为static_cast计算了父子类指针转换的偏移量,并将之转换到正确的地址。test_c里面有mA和mB的成员变量,转换成TestB的指针时指到了mB的地方。reinterpret_cast没有进行这个 *** 作。所以和前面一个一样。** reinterpret_cast慎用**
3、智能指针之间的转换前面介绍了几种指针之间的转换方式,下面我们就来看一下智能指针的转换方式。
3.1 std::static_pointer_cast 关键字std::static_pointer_cast 是static_cast针对智能指针的转换。其可以上行转换,也可以下行转换,但是下行转换的时候是不安全的,并且子类的指针是不能访问自己内部的成员函数和成员变量,只能访问父类的成员变量和成员函数。
使用示例:
/*************************************************************************
> File Name: shared_pointer_cast.cpp
> Author: 小和尚念经敲木鱼
> Mail:
> Created Time: Fri 22 Apr 2022 11:27:07 PM CST
***********************************************************************/
#include
#include
#include
#include
using namespace std;
class Base
{
public:
Base() {
std::cout << __func__ << std::endl;
}
virtual ~Base() {
std::cout << __func__ << std::endl;
}
void BaseDisplay() {
std::cout << "mBase data:" << mBaseData << std::endl;
}
protected:
int mBaseData = 10;
};
class Driver : public Base
{
public:
Driver() {
std::cout << __func__ << std::endl;
}
virtual ~Driver() {
std::cout << __func__ << std::endl;
}
void DriverDisplay() {
std::cout << "mDriver data:" << mDriverData << std::endl;
}
private:
int mDriverData = 100;
};
int main() {
//上行转换 子类 ---> 父类
std::cout << "-----------------上行转换-----------------------" <<std::endl;
std::shared_ptr<Driver> pdriver1 = std::make_shared<Driver>();
shared_ptr<Base> pbase1 = std::static_pointer_cast<Base>(pdriver1);
pbase1->BaseDisplay();
//调用不了
//pbase1->DriverDisplay();
pdriver1.reset();
std::cout << "----------------下行转换------------------------" <<std::endl;
//下行转换 父类 ---> 子类
std::shared_ptr<Base> pbase2 = std::make_shared<Base>();
shared_ptr<Driver> pdriver2 = std::static_pointer_cast<Driver>(pbase2);
pdriver2->BaseDisplay();
pdriver2->DriverDisplay();
pdriver2.reset();
return 0;
}
//OUT
//-----------------上行转换-----------------------
//Base
//Driver
//mBase data:10
//----------------下行转换------------------------
//Base
//mBase data:10
//mDriver data:0 打印为0 不安全
//~Base
//~Driver
//~Base
/******************************end of file******************************/
3.2 std::dynamic_pointer_cast 关键字
dynamic_pointer_cast使用方法和dynamic_cast类似.其在用于下行转换的时候具有类型的检测功能.
使用示例:
/*************************************************************************
> File Name: shared_pointer_cast.cpp
> Author: 小和尚念经敲木鱼
> Mail:
> Created Time: Fri 22 Apr 2022 11:27:07 PM CST
***********************************************************************/
#include
#include
#include
#include
using namespace std;
/************************************************************************
* 文件说明
* static_pointer_cast、dynamic_pointer_cast、
* const_pointer_cast、reinterpret_pointer_cast
*
************************************************************************/
class Base {
public:
Base() {
std::cout << __func__ << std::endl;
}
virtual ~Base() {
std::cout << __func__ << std::endl;
}
public:
void BaseDisplay() {
std::cout << "mBaseData:" << mBaseData << std::endl;
}
protected:
int mBaseData = 10;
};
class Driver : public Base {
public:
Driver() {
std::cout << __func__ << std::endl;
}
virtual ~Driver() {
std::cout << __func__ << std::endl;
}
void setData(std::string& str) {
mDriverData = str;
}
void DriverDisplay() {
std::cout << __func__ << " DriverData:" << mDriverData << std::endl;
}
public:
std::string mDriverData = "this is DriverData";
};
class DriverImpl : public Driver {
public:
DriverImpl() {
std::cout << __func__ << std::endl;
}
~DriverImpl() {
std::cout<< __func__ << std::endl;
}
void DriverImplDisplay() {
std::cout <<"mDriverImplData:" << mDriverImplData << std::endl;
}
public:
int mDriverImplData = 200;
};
int main (int argc,char *argv[])
{
std::cout << "---------------上行转换----------------" << std::endl;
//上行转换
std::shared_ptr<Driver> impl1 = std::make_shared<Driver>();
std::shared_ptr<Base> base1 = std::dynamic_pointer_cast<Base> (impl1);
//访问不了子类的函数
base1->BaseDisplay();
base1.reset();
impl1.reset();
std::cout << "---------------下行转换----------------" << std::endl;
//下行转换 自动检测类 安全
std::shared_ptr<Base> base2 = std::make_shared<DriverImpl>();
std::shared_ptr<Driver> driver2 = std::dynamic_pointer_cast<Driver> (base2);
std::cout << std::endl;
driver2->BaseDisplay();
driver2->DriverDisplay();
std::cout << std::endl;
std::shared_ptr<DriverImpl> impl2 = std::dynamic_pointer_cast<DriverImpl> (base2);
impl2->DriverImplDisplay();
impl2->DriverDisplay();
impl2->BaseDisplay();
std::cout << std::endl;
impl2.reset();
}
//OUT
//---------------上行转换----------------
//Base
//Driver
//mBaseData:10
//~Driver
//~Base
//---------------下行转换----------------
//Base
//Driver
//DriverImpl
//
//mBaseData:10
//DriverDisplay DriverData:this is DriverData
//
//mDriverImplData:200
//DriverDisplay DriverData:this is DriverData
//mBaseData:10
//
//~DriverImpl
//~Driver
//~Base
/******************************end of file******************************/
3.3 std::const_pointer_cast 关键字
std::dynamic_pointer_cast和dynamic_cast使用方法类似。
使用示例:
/*************************************************************************
> File Name: shared_pointer_cast.cpp
> Author: 小和尚念经敲木鱼
> Mail:
> Created Time: Fri 22 Apr 2022 11:27:07 PM CST
***********************************************************************/
#include
#include
#include
#include
using namespace std;
/************************************************************************
* 文件说明
* static_pointer_cast、dynamic_pointer_cast、
* const_pointer_cast、reinterpret_pointer_cast
*
************************************************************************/
class Base {
public:
Base() {
std::cout << __func__ << std::endl;
}
virtual ~Base() {
std::cout << __func__ << std::endl;
}
public:
void BaseDisplay() {
std::cout << "mBaseData:" << mBaseData << std::endl;
}
protected:
int mBaseData = 10;
};
class Driver : public Base {
public:
Driver() {
std::cout << __func__ << std::endl;
}
virtual ~Driver() {
std::cout << __func__ << std::endl;
}
void setData(std::string& str) {
mDriverData = str;
}
void DriverDisplay() {
std::cout << __func__ << " DriverData:" << mDriverData << std::endl;
}
public:
std::string mDriverData = "this is DriverData";
};
class DriverImpl : public Driver {
public:
DriverImpl() {
std::cout << __func__ << std::endl;
}
~DriverImpl() {
std::cout<< __func__ << std::endl;
}
void DriverImplDisplay() {
std::cout <<"mDriverImplData:" << mDriverImplData << std::endl;
}
public:
int mDriverImplData = 200;
};
int main (int argc,char *argv[])
{
std::shared_ptr<int> data1 = std::make_shared<int>(100);
std::shared_ptr<const int> data2 = std::const_pointer_cast<const int>(data1);
std::cout << "data2 value: " << *data2 << std::endl;
*data1 = 20;
std::cout << "data2 value: " << *data2 << std::endl;
//执向同一地址
std::cout << "data1:"<< data1 << "\tdata2:" << data2 << std::endl;
const std::shared_ptr<Base> base1 = std::make_shared<Base>();
std::shared_ptr<Base> driver1 = std::const_pointer_cast<Base> (base1);
std::cout << "base1:" << base1 << "\tdriver1:" << driver1 << std::endl;
driver1->BaseDisplay();
}
//OUT
//data2 value: 100
//data2 value: 20
//data1:0x1cb0c38 data2:0x1cb0c38
//Base
//base1:0x1cb1078 driver1:0x1cb1078
//mBaseData:10
//~Base
/******************************end of file******************************/
3.4 std::reinterpret_pointer_cast 关键字
本地环境没有升级C++的库,所以reinterpret_pointer_cast
4、总结std::static_cast、std::dynamic_cast、std::const_cast、std::reinterpret_cast是针对不同场景下的使用,优先使用std::dynamic_cast,其次才是使用std::static_cast,针对const类型的使用std::const_cast,如果前后可以确认的转换数据结构的可以使用std::reinterpret_cast。而std::static_pointer_cast、std::dynamic_pointer_cast、 std::const_pointer_cast、std::reinterpret_pointer_cast是针对智能指针使用转换类型的关键字,用法上面也进行了分析。最后,如有错误,望大佬斧正。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)