最新要闻
- 7499元 华硕ROG新款38英寸游戏显示器上架:4K 144Hz高刷屏
- 北京现代沐飒正式上市:12.18万元起、百公里油耗低至6.86L-全球热闻
- 【全球独家】银行股迎来回调,不急,我们最不缺的就是时间
- 董明珠称:全世界只有格力空调不吹人! 环球观察
- 每日讯息!中邮策略:央行降息提振信心 市场有望震荡上行
- 吃小龙虾戴了手套 为啥还是满手油? 每日快看
- 天天精选!AMD做了一个201MB缓存的怪物!可惜流产了
- 今年“6·18”河南消费力全国第八!胡辣汤、洛阳汉服、河南老字号美食全国热卖
- 政治权利名词解释电大 政治权利名词解释 最新资讯
- 微软确认月初遭DDoS网络攻击:一度导致Microsoft 365服务中断
- 终末的女武神同人 毗沙门天VS宇智波斑-资讯
- 249元 雷柏推出VT9S无线鼠标:原相3395引擎、26000 DPI|全球微头条
- 大四男生实习薪资1万4妈妈仰天大笑 姐姐回应:要在船上写论文 全球聚焦
- 一箭41星创中国纪录!当天传回超清大图:英格兰一览无余 环球观察
- 全球热讯:拜耳医药保健有限公司属于哪个国家_拜耳公司是哪个国家
- 天天快消息!苹果M2 Ultra首次开盖:Intel 56核心相形见绌
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
天天短讯!C++基础知识总结
2023/6/18
【资料图】
本篇章记录学习过程C++的基础概念和代码测试实现,还有很多需要补充。一是还不清楚,二是还没有学到。打算学习过程中后面再做补充。先看完《C++primer 》书之后再慢慢来添加补充
1.函数重载
- 一个函数名可以实现多个功能,这取决于函数参数不同来实现判断对应的功能,与返回值无关
- 函数可以重载,构造函数,成员函数都可以重载,但是,析构函数不能重载
#include using namespace std;void print(){ cout << "没有参数的print函数" << endl;}//int print() 错误//{// cout << "没有参数的print函数" << endl;//}void print(int a){ cout << "一个int参数的print函数"<< a << endl;}void print(string s){ cout << "一个string参数的print函数" << s << endl;}void print(int a,int b){ cout << "两个int参数的print函数" << a << b << endl;}int main(){ // 通过传入参数的不同,可以调用不同的重载函数 print(1); print(1,2); print(); print("hjahhah"); return 0;}
2.函数默认参数
函数可以设定默认值,当调用函数时,可以不传递参数,这样就会使用默认值。注意点:
- 函数声明与定义分离,函数的参数默认值可以写在声明或定义处,但是只能出现一次
- 遵循向右原则,这个原则就是说某个参数设定了默认值,那么它的右边参数必须设定默认值
- 默认参数与函数重载一起使用时,需要注意不能发生二义性
void add(int a =1,int b=2,int c=3);add(5);//这样是a为5,其余是默认b=2,c=3add(7,8);//这样a=7,b=8,默认值c=3;向右原则就是这样,a如果是默认值,那么b和c一定是默认值,不能b为默认值,c为传参。
#include using namespace std;void show(int a = 1,int b = 2,int c = 3){ cout << a << " " << b << " " << c << endl;}void print(int a,int b);void print(int a=1,int b =1){ cout << a << " " << b << endl;}void print2(int a = 1);void print2(int a){ cout << a << endl;}int main(){ show(); // 1 2 3 show(6); // 6 2 3 show(5,6); // 5 6 3 show(6,7,8); // 6 7 8 print(); // 1 1 print(2); // 2 1 print(2,3); // 2 3 print2(); // 1 print2(2); // 2 return 0;}
#include using namespace std;void show(int a = 1,int b = 2,int c = 3){ cout << a << " " << b << " " << c << endl;}void show(){ cout << "哈哈哈哈哈" << endl;}int main(){// show(); 错误:二义性 return 0;}
3.引用
&
可以改变引用的变量的值注意点:
- 可以改变引用值,但是不能再次成为其他变量的引用
- 声明引用时,需要初始化
- 初始化的值不能为NULL
- 初始值是纯数字,需要加const关键字来修饰引用,表示引用的值不可变
- 可以将变量引用的地址赋值给一个指针,此处指针指向的还是原来的变量
- 可以对指针建立引用
- 使用const关键词修饰引用,此时不能通过引用修改数值,但是可以修改引用原变量的数值
#include using namespace std;int main(){ int a = 1; int b = 2; int& c = a; // c是a的引用 c = b; // 把b的值赋给c cout << a << " " << &a << endl; // 2 0x61fe88 cout << b << " " << &b << endl; // 2 0x61fe84 cout << c << " " << &c << endl; // 2 0x61fe88// int& c = b; 错误 变量c已经引用了a,现在再引用b// &c = b; 错误 return 0;}
#include using namespace std;int main(){ int a = 1;// int& b; 错误 b = a; return 0;}
#include using namespace std;int main(){// int& a = NULL; 错误 return 0;}
#include using namespace std;int main(){ // 常引用 const int &a = 123;// a++; 错误 cout << a << endl; // 123 return 0;}
#include using namespace std;int main(){ int a = 1; int &b = a; int* c = &b; // 指针c指向b a++; cout << *c << endl; // 2 return 0;}
#include using namespace std;int main(){ int a = 1; int* b = &a; // b是a的指针 int*& c = b; // c是b的引用 cout << &a << " " << a << endl; // 0x61fe88 1 cout << b << " " << *b << endl; // 0x61fe88 1 cout << c << " " << *c << endl; // 0x61fe88 1 return 0;}
#include using namespace std;int main(){ int a = 1; const int &b = a; // b是a的常引用// b++; 错误 a++; cout << b << endl; // 2 return 0;}
4.对象
对象是面向对象编程思想的核心,面向对象的三个基本特征:封装、继承、多态这三个就是核心了,要讲清楚需要的篇章太多了。
4.1创建对象
创建对象的前提是有一个类,然后就选择是栈内存对象 还是堆内存对象
- 栈内存对象生命周期在所在的{}结束后,自动销毁
- 堆内存对象在
delete
关键字销毁,不手动销毁,对象持续存在
class Witch//创建了类{}int main(){ Witch con1;//con1为对象,栈内存对象 Witch* con2=new Witch//con2对象 ,堆内存对象}
4.2封装的体现
class Witch//创建了类{private: // 私有权限:只能在类内部访问public: //公有权限,全局,派生类,类内都可以访问。派生类就是继承的类protected: //保护权限,全局无法访问}
|| 类内访问 | 派生类内访问 | 全局访问 || --- | --- | --- | --- || 私有权限 private | √ | X | X || 保护权限 protected | √ | √ | X || 公有权限 public | √ | √ | √ |
使用prvite
将类中成员隐藏,开放public
接口公开访问
#include using namespace std;/** * @brief 所有类名都要使用帕斯卡(大驼峰)命名法,即所有单词的首字母使用大写 */class MobilePhone{private: // 私有权限:只能在类内部访问 string brand; // 可读可写 string model; // 只写 int weight = 188; // 只读,赋予了初始值public: string get_brand() // 读函数:getter { return brand; } void set_brand(string b) // 写函数:setter { brand = b; } void set_model(string m) // setter { model = m; } int get_weight() // getter { return weight; }};int main(){ MobilePhone mp1; mp1.set_brand("华为"); mp1.set_model("P60"); cout << mp1.get_brand() << endl; cout << mp1.get_weight() << endl; MobilePhone* mp2 = new MobilePhone; mp2->set_brand("魅族"); mp2->set_model("20"); cout << mp2->get_brand() << endl; cout << mp2->get_weight() << endl; delete mp2; return 0;}
4.2.1构造函数
创建类对象,代码就会进入构造函数,没写,默认一个构造函数构造函数有以下特点:
- 函数名称与类名完全相同
- 构造函数不写返回值
- 构造函数支持函数重载
- 构造函数可以使用初始化列表赋值
class Witch//创建了类{Witch()//构造函数{ }Witch(int a)//构造函数函数重载{ }Witch(int a,string b):num(a),modl(b)//初始化列表,num=a,modl=b;{ }private: // 私有权限:只能在类内部访问 int num=0; string modl;public: //公有权限,全局,派生类,类内都可以访问。派生类就是继承的类protected: //保护权限,全局无法访问}
4.2.2拷贝构造函数
每个类提供一个重载的拷贝构造函数,用于对象的拷贝,即基于某个对象创建一个数据完全相同的对象。注意点:
- 新创建的对象与原来对象是两个对象
- 浅拷贝(当类中出现了指针类型的成员变量时,默认的拷贝构造函数会造成浅拷贝的问题。不同对象的成员变量会指向同一个区域,不符合面向对象的设计。)
- 深拷贝(解决浅拷贝)
- 隐式调用构造函数(使用explicit修饰构造函数后,就只支持显示调用了)
class Witch//创建了类{Witch()//构造函数{ }Witch(const Witch& w)//默认的拷贝构造函数{ }}
#include #include using namespace std;class Dog{private: char* name;public: Dog(char* n) { name = n; } // 复原浅拷贝(写不写都行) Dog(const Dog& d) { name = d.name; } void show() { cout << name << endl; }};int main(){ char c[20] = "wangcai"; Dog d1(c);//构造函数 // 拷贝构造函数 Dog d2(d1); strcpy(c,"xiaobai"); d1.show(); // xiaobai d2.show(); // xiaobai //出现了指向同一位置导致数据发生改变 return 0;}
#include #include using namespace std;class Dog{private: char* name;public: Dog(char* n) { // 单独开辟一块堆内存 name = new strcpy(name,n); } // 深拷贝 Dog(const Dog& d) { // 单独开辟一块堆内存 name = new char[20]; strcpy(name,d.name); } void show() { cout << name << endl; }};int main(){ char c[20] = "wangcai"; Dog d1(c); // 拷贝构造函数 Dog d2(d1); strcpy(c,"xiaobai"); d1.show(); // wangcai d2.show(); // wangcai return 0;}
#include using namespace std;class Teacher{private: string name;public: // 使用explicit修饰构造函数后,只支持显式调用 Teacher(string n) { cout << "创建了一个老师" << endl; name = n; } string get_name() { return name; }};int main(){ string name = "罗翔"; // 隐式调用构造函数 Teacher t = name; // 创建了一个老师对象 cout << t.get_name() << endl; // 罗翔 return 0;}
4.2.3析构函数
析构函数是类中与构造函数完全对立的函数。
构造函数 | 析构函数 |
---|---|
手动调用后创建对象 | 对象销毁时自动调用 |
可以有参数,支持重载和参数默认值 | 无参数 |
函数名称是类名 | 函数名称是~类名 |
通常用于对象创建时初始化 | 通常用于对象销毁时回收内存和资源 |
#include #include using namespace std;class Dog{private: char* name;public: Dog(char* n) { // 单独开辟一块堆内存 name = new char[20]; strcpy(name,n); } // 复原浅拷贝(写不写都行) Dog(const Dog& d) { // 单独开辟一块堆内存 name = new char[20]; strcpy(name,d.name); } void show() { cout << name << endl; } // 析构函数 ~Dog() { delete name; //这里加上,就解决了深拷贝开辟空间的释放问题 }};int main(){ char c[20] = "wangcai"; Dog d1(c); // 拷贝构造函数 Dog d2(d1); strcpy(c,"xiaobai"); d1.show(); // wangcai d2.show(); // wangcai return 0;}
4.2.4.类内声明,类外定义
#include using namespace std;class Student{private: string name = "张三";public: // 声明 void study();};// 定义void Student::study(){ cout << name << "在努力学习!" << endl;}int main(){ Student s; s.study(); // 张三在努力学习! return 0;}
4.2.5.this指针
是一种特殊的指针,只能在类的成员函数,构造函数,析构函数中使用。this指针指向当前类的对象首地址作用:
- 区分局部变量和成员变量
- 链式调用
#include using namespace std;class Test{public: Test() { cout << this << endl; }};int main(){ Test t1; // 0x61fe7b cout << &t1 << endl; // 0x61fe7b Test* t2 = new Test; // 0x8f0fe0 cout << t2 << endl; // 0x8f0fe0 delete t2; return 0;}
#include using namespace std;class Computer{private: string brand;public: Computer(string brand) { // 区分重名 this->brand = brand; } string get_brand() { // 所有的成员在类内部调用都是靠this指针,平常可省略 return this->brand; } void show() { // 所有的成员在类内部调用都是靠this指针,平常可省略 cout << this->get_brand() << endl; }};int main(){ Computer c("联想"); cout << c.get_brand() << endl; // 联想 c.show(); // 联想
#include using namespace std;class Number{private: int value;public: Number(int value):value(value) // 重名时使用构造初始化列表也可以 {} Number& add(int v) // 支持链式调用 { value += v; return *this; // 固定用法 } int get_value() { return ; }};int main(){ Number n1(1); // 传统方式:非链式调用 n1.add(2); n1.add(3); n1.add(4); cout << n1.get_value() << endl; // 10 Number n2(1); // 链式调用 cout << n2.add(2).add(3).add(4).get_value() << endl; // 10 // string类的append函数在源代码中也采用了链式调用的设计 string s = "A"; cout << s.append("B").append("C").append("D") << endl; // ABCD return 0;}
4.3继承的体现
继承是在一个已经存在的类的继承上建立一个新的类,新的类拥有已经存在的类的特性,已经存在的类被称为“基类”或“父类”;新建立的类被称为“派生类”或“子类”。
class Father{}class Son:public Father{}
4.3.1析构与构造不能被继承
在继承中,任何一个派生类的任意一个构造函数都必须之间或间接调用基类的任意一个构造函数。因为在创建派生类对象时,需要调用基类的代码,使用基类的逻辑去开辟部分继承的空间
#include using namespace std;class Father{private: string first_name;public: Father(string first_name) { this->first_name = first_name; } string get_first_name() const { return first_name; }};class Son:public Father{};int main(){// Son s1("张"); 错误:构造函数不能被继承// Son s1; 错误:找不到Father::Father()构造函数 return 0;}
4.3.2派生类构造函数调用基类构造函数
- 透传构造
- 委托构造
- 继承构造
4.3.2.1透传构造
透传构造是派生类的构造函数中直接调用基类的构造函数
#include using namespace std;class Father{private: string first_name;public: Father(string first_name) { this->first_name = first_name; } string get_first_name() const { return first_name; }};class Son:public Father{public:// Son():Father(){} 如果不写构造函数,编译器自动添加 Son():Father("王"){} // 透传构造 Son(string name) :Father(name){} // 透传构造};int main(){ Son s1; cout << s1.get_first_name() << endl; Son s2("张"); cout << s2.get_first_name() << endl; return 0;}
4.3.2.2委托构造
同一个类中的构造函数可以调用另外一个重载构造函数,注意点是,最终必须有一个构造函数透传调用基类的构造函数
#include using namespace std;class Father{private: string first_name;public: Father(string first_name) { this->first_name = first_name; } string get_first_name() const { return first_name; }};class Son:public Father{public: Son():Son("王"){} // 委托构造 Son(string name) :Father(name){} // 透传构造};int main(){ Son s1; cout << s1.get_first_name() << endl; Son s2("张"); cout << s2.get_first_name() << endl; return 0;}
4.3.2.3继承构造
继承构造是C++11的特性,并不是真正的继承构造函数,而是编译器自动为派生类创建n个构造函数。
#include using namespace std;class Father{private: string first_name;public: Father(string first_name) { this->first_name = first_name; } Father():Father("张"){} string get_first_name() const { return first_name; }};class Son:public Father{public: // 一句话搞定 using Father::Father; // 相当于添加了下面的代码// Son(string first_name):// Father(first_name){}// Son():Father(){}};int main(){ Son s1; cout << s1.get_first_name() << endl; Son s2("张"); cout << s2.get_first_name() << endl; return 0;}
4.3.3.对象的创建和销毁流程
- 创建与销毁流程完全对称。2. 创建过程中,同类型功能都是基类先执行,派生类后执行。3. 静态成员变量的生命周期与程序运行的周期相同。通过上述例子,可以看到面向对象编程的特点:编写效率高,执行效率低。
#include using namespace std;/** * @brief The Value class 作为其他类的变量 */class Value{private: string name;public: Value(string name):name(name) { cout << name << "创建了" << endl; } ~Value() { cout << name << "销毁了" << endl; }};class Father{public: Value value = Value("Father类的成员变量"); static Value s_value; Father() { cout << "Father类的构造函数" << endl; } ~Father() { cout << "Father类的析构函数" << endl; }};Value Father::s_value = Value("Father类的静态成员变量");class Son:public Father{public: Value value2 = Value("Son类的成员变量"); static Value s_value2; Son():Father() { cout << "Son类的构造函数" << endl; } ~Son() { cout << "Son类的析构函数" << endl; }};Value Son::s_value2 = Value("Son类的静态成员变量");int main(){ cout << "程序开始执行" << endl; { Son s; cout << "对象s使用中......" << endl; } cout << "程序结束执行" << endl; return 0;}
4.3.4.多重继承
即一个派生类可以有多个基类
#include using namespace std;class Sofa{public: void sit() { cout << "能坐着!" << endl; }};class Bed{public: void lay() { cout << "能躺着!" << endl; }};class SofaBed:public Sofa,public Bed{};int main(){ SofaBed sb; sb.sit(); sb.lay(); return 0;}
4.3.4.1二义性问题
- 多个基类拥有重名的成员时,会出现二义性
- 菱形继承,如果一个派生类的多个基类拥有共同的基类,这种情况就是菱形继承(通过虚继承实现)
#include using namespace std;class Furniture // 家具{public: void show() { cout << "这是个家具" << endl; }};class Bed:public Furniture // 床{};class Sofa:public Furniture // 沙发{};class SofaBed:public Bed,public Sofa // 沙发床{};int main(){ SofaBed sb;// sb.show(); 错误:二义性 // 告诉编译器用哪个基类的代码 sb.Bed::show(); sb.Sofa::show();// sb.Furniture::show(); 错误:二义性 return 0;}
#include using namespace std;class Furniture // 家具{public: void show() { cout << "这是个家具" << endl; }};class Bed:virtual public Furniture // 床{};class Sofa:virtual public Furniture // 沙发{};class SofaBed:public Bed,public Sofa // 沙发床{};int main(){ SofaBed sb; sb.show(); return 0;}
4.3.5权限的继承
公有继承:派生类可以继承基类所有权限的成员,但是无法直接访问基类的私有成员,对于基类的保护成员与公有成员,在派生类中仍然是原来的权限。保护继承:派生类可以继承基类所有权限的成员,但是无法直接访问基类的私有成员,对于基类的保护成员与公有成员,在派生类中都变为保护权限。私有继承:派生类可以继承基类所有权限的成员,但是无法直接访问基类的私有成员,对于基类的保护成员与公有成员,在派生类中都变为私有权限。
#include using namespace std;class Father{private: string str1 = "Father的私有权限";protected: string str2 = "Father的保护权限";public: string str3 = "Father的公有权限";};class Son:public Father{};class Grandson:public Son{public: void test() { cout << str2 << endl; cout << str3 << endl; }};int main(){ Son s;// cout << s.str2 << endl; 错误 cout << s.str3 << endl; Grandson gs; gs.test(); return 0;}
#include using namespace std;class Father{private: string str1 = "Father的私有权限";protected: string str2 = "Father的保护权限";public: string str3 = "Father的公有权限";};class Son:protected Father{};class Grandson:public Son{public: void test() { cout << str2 << endl; cout << str3 << endl; }};int main(){ Son s;// cout << s.str2 << endl; 错误// cout << s.str3 << endl; 错误 Grandson gs; gs.test(); return 0;}
#include using namespace std;class Father{private: string str1 = "Father的私有权限";protected: string str2 = "Father的保护权限";public: string str3 = "Father的公有权限";};class Son:private Father{public: void test() { cout << str2 << endl; cout << str3 << endl; }};class Grandson:public Son{public: void test() { // cout << str2 << endl; 错误 // cout << str3 << endl; 错误 }};int main(){ Son s; // cout << s.str2 << endl; 错误 // cout << s.str3 << endl; 错误 Grandson gs; gs.test(); s.test(); return 0;}
4.4多态的体现
多态可以理解为只写一个函数接口,在程序运行时才决定调用类型对应的代码。多态的使用,需要使用以下条件:
- 必须使用公有继承
- 派生类要有函数覆盖
- 基类引用/指针指向派生类对象
4.4.1函数覆盖
函数覆盖与函数隐藏很相似(函数隐藏就是重名),区别在与基类被覆盖的函数需要设置为虚函数。虚函数有以下特点:
- 虚函数具有传递性,基类使用,派生类覆盖函数会自动生成虚函数
- 只有非静态成员函数与析构函数可以被定义为虚函数
- 如果声明定义分离,只需要使用virtual关键字修饰函数声明处
#include using namespace std;class Animal{public: virtual void eat() // 虚函数 { cout << "动物吃东西" << endl; }};class Cat:public Animal{public: void eat() // 虚函数 { cout << "猫吃鱼" << endl; }};class Dog:public Animal{public: void eat() // 虚函数 { cout << "狗吃骨头" << endl; }};
4.4.2使用多态
多态通常搭配函数参数使用,分别写两个函数,触发引用和指针类型的多态
#include using namespace std;class Animal{public: virtual void eat() // 虚函数 { cout << "动物吃东西" << endl; }};class Cat:public Animal{public: void eat() // 虚函数 { cout << "猫吃鱼" << endl; }};class Dog:public Animal{public: void eat() // 虚函数 { cout << "狗吃骨头" << endl; }};// 引用多态void test_eat1(Animal& a){ a.eat();}void test_eat2(Animal* a){ a->eat();}int main(){ Animal a1; Cat c1; Dog d1; // 测试引用多态效果 test_eat1(a1); // 动物吃东西 test_eat1(c1); // 猫吃鱼 test_eat1(d1); // 狗吃骨头 Animal* a2 = new Animal; Cat* c2 = new Cat; Dog* d2 = new Dog; test_eat2(a2); // 动物吃东西 test_eat2(c2); // 猫吃鱼 test_eat2(d2); // 狗吃骨头 return 0;}
4.4.2虚析构函数
通过基类引用或指针指向派生类对象,当使用delete销毁对象时,只会调用基类的析构函数,不会调用派生类的析构函数,此时,如果派生类中有new申请内存资源,那么会造成内存泄漏问题
#include using namespace std;class Animal{public: virtual ~Animal() { cout << "基类的析构函数" << endl; }};class Dog:public Animal{public: ~Dog() { cout << "派生类的析构函数" << endl; }};int main(){ Animal* a = new Dog; delete a; //派生类的析构函数 基类的析构函数 return 0;}
因此在设计一个类时,如果这个类会成为其他类的基类,哪怕析构函数什么都不写,也要手写空的析构函数并加上virtual关键字修饰,除非可以确定此类不会被任何类继承。
5.static关键字
5.1静态成员变量
特点:
- 需要类内声明,类外定义
- 所有对象共用一份,非静态的对象各持有一份
- 可以直接类名调用,无需成员调用
- 运行时就开辟了,结束运行自动回收
- this可以调用静态成员(静态成员变量和静态成员函数)
#include using namespace std;class Test{public: string str1 = "非静态成员变量"; static string str2; // 类内只声明 static const int a = 1; // 【特例】const修饰的静态成员变量可以类内初始化 void function() // 非静态的成员函数 { // 为了方便理解,加入this指针,实际编写的过程中可取消 cout << this->str1 << endl; cout << this->str2 << endl; }};// 类外初始化string Test::str2 = "静态成员变量";int main(){ // 直接使用类名调用 cout << Test::str2 << " " << &Test::str2 << endl; // 静态成员变量 0x40b038 Test t1; cout << t1.str1 << " " << &t1.str1 << endl; // 非静态成员变量 0x61fe8c cout << t1.str2 << " " << &t1.str2<< endl; // 静态成员变量 0x40b038 Test t2; cout << t2.str1 << " " << &t2.str1<< endl; // 非静态成员变量 0x61fe88 cout << t2.str2 << " " << &t2.str2<< endl; // 静态成员变量 0x40b038 t1.function(); // 非静态成员变量\n静态成员变量 return 0;}
5.2静态成员函数
#include using namespace std;class Test{private: string str1 = "非静态成员"; static string str2;public: void function1() { cout << "这是一个非静态成员函数:"; cout << str1; cout << str2; cout << endl; } static void function2() { cout << "这是一个静态成员函数:";// cout << str1; 错误 cout << str2; cout << endl; }};string Test::str2 = "静态成员";int main(){ Test::function2(); Test t; t.function1(); t.function2(); // 也能通过对象,虽然不建议 return 0;}
5.3静态局部变量
第一次被调用的时候,就开辟空间,结束后,不会销毁,下次被调用时,再次使用之前的静态局部变量。直到程序运行终止才会自动销毁
#include using namespace std;class Test{public: void func1() { int a = 1; // 非静态局部变量 cout << a++ << endl; } void func2() { static int a = 1; // 静态局部变量 cout << a++ << endl; }};int main(){ Test t1; Test t2; t1.func1(); // 1 t1.func1(); // 1 t2.func1(); // 1 cout << "---------------" << endl; t1.func2(); // 1 t1.func2(); // 2 t2.func2(); // 3 return 0;}
6.const关键字
6.1.修饰成员函数
修饰成员函数,称为常成员函数,特点:无法修改属性值,无法调用非const修饰的成员函数
#include using namespace std;class Car{private: string brand;public: Car(string brand):brand(brand){} string get_brand() const {// set_brand("奔驰"); 错误// brand = "宝马"; 错误// show(); 错误 show2(); return brand; } void set_brand(string brand) { this->brand = brand; } void show() { cout << "滴滴滴" << endl; } void show2() const { cout << "哒哒哒" << endl; }};int main(){ Car c("奥迪"); c.set_brand("大众"); cout << c.get_brand() << endl; return 0;}
6.2.修饰对象
修饰对象,表示该对象是一个常量对象。常量对象属性值不可以改,不能调用共非const成员函数
#include using namespace std;class Car{private: string brand;public: string model = "这个变量仅用于举例,就不封装了"; Car(string brand):brand(brand){} string get_brand() const {// set_brand("奔驰"); 错误// brand = "宝马"; 错误// show(); 错误 show2(); return brand; } void set_brand(string brand) { this->brand = brand; } void show() { cout << "滴滴滴" << endl; } void show2() const { cout << "哒哒哒" << endl; }};int main(){ // const两个位置都可以 const Car c1("奥迪"); Car const c2("凯迪拉克");// c1.set_brand("大众"); 错误 cout << c1.get_brand() << endl;// c1.show(); 错误 c1.show2();// c1.model = "A4"; 错误 cout << c1.model << endl; return 0;}
6.3.修饰成员变量
修饰后,表示常成员变量,出生在设定后,就不能在运行期间发生变化
#include using namespace std;class Person{private: const string name; // 常成员变量public: const int age = 1; // 赋予初始值方式二 Person():name("张三"){} //赋予初始值方式一(推荐) // 重载的构造函数 Person(string name,int age):name(name),age(age){} void set_name(string name) {// this->name = name; 错误 } string get_name() const { return name; }};int main(){ Person p; cout << p.get_name() << endl;// p.age++; 错误 cout << p.age << endl; Person p2("李四",18); cout << p2.age << endl; // age:18 return 0;}
6.4.修饰局部变量
表示局部变量不可变,通常用于引用类型的函数参数
#include using namespace std;void show(const int& a,const int& b){ cout << a << b << endl;}int main(){ int a = 1; int b = 2; show(a,b); // 12 return 0;}
7.运算符重载
7.1友元
友元可以突破封装性的权限限制,随意访问类中任意部分。可以分为:友元函数,友元类,友元成员函数注意点:
- 运算符重载只能在C++已有的运算符范围内,不能创建新的运算符。
- 运算符重载本质上也是函数重载。
- 重载之后的运算符无法改变原有的优先级和结合性,也不能改变运算符的操作数和语法结构
- 运算符重载的参数必须包含自定义数据类型,无法重载基本类型的运算符规则。
- 运算符重载尽量符合原功能定义。
- 运算符重载的参数不支持默认值的设定。
- 一般情况下,建议单目运算符使用成员函数重载,双目运算符使用友元函数重载
7.1.1友元函数
特点:
- 不属于任何一个类
- 没有this指针,突破权限需要对象
- 友元函数的声明可以放在类中任何位置,包括private
- 一个友元函数可以是多个类的友元函数,只需要各个类分别声明。
#include using namespace std;class Job{private:int income;public:Job(int income):income(income){ cout << &this->income << endl;}// “声明”友元函数friend void test_friend(Job&);};void test_friend(Job& j){ // 尝试访问私有成员 cout << ++j.income << " " << &j.income << endl;}int main(){ Job j1(20000); // 0x61fe8c test_friend(j1); // 20001 0x61fe8c return 0;}
7.1.2友元类
当一个类B成为另外一个类A的友元类的时候,类A的所有成员都可以被B类访问注意点:
- 友元关系与继承无关
- 友元关系是单向的,不具有交换性
- 友元关系不具有传递性
#include using namespace std;class A{private: int value = 1;public: int get_value() const { return value; } // “声明”友元关系 friend class B;};class B{public: void test_friend(A& a) { cout << ++a.value << endl; } void test_friend2(A& a,int v) { a.value += v; }};int main(){ A a; B b; b.test_friend(a); // 2 cout << a.get_value() << endl; // 2 b.test_friend2(a,100); cout << a.get_value() << endl; // 102 return 0;}
7.1.3友元成员函数
#include using namespace std;// 第三步:提前声明类Aclass A;// 第二步:因为友元用到了类B,补充类B和函数声明。class B{public: void func(A& a);};// 第一步:确定友元的函数格式并“声明”class A{private: string str = "这是类A私有的成员!"; // 友元关系 friend void B::func(A& a);};// 第四步:定义友元成员函数的内容void B::func(A &a){ cout << a.str.append("哈哈哈") << endl;}int main(){ A a; B b; b.func(a); // 这是类A私有的成员!哈哈哈 return 0;}
7.2.重载
可以把运算符看作是一个函数,给已有运算符赋新的功能,完成特定的操作,就需要运算符重载。运算符重载有2种重载方式,友元函数运算符重载,成员函数运算符重载
7.2.1友元函数运算符重载
#include using namespace std;/** * @brief 自定义整数类型 */class Integer{private: int value;public: Integer(int value):value(value){} int get_value() const { return value; } // “声明”友元关系 friend Integer operator +(const Integer& i1,const Integer& i2); friend Integer operator ++(Integer& i); // 前置 friend Integer operator ++(Integer& i,int); // 后置};Integer operator +(const Integer& i1,const Integer& i2){ return i1.value + i2.value;}Integer operator ++(Integer& i){ return ++i.value;}Integer operator ++(Integer& i,int){ return i.value++;}int main(){ Integer i1(1); cout << (++i1).get_value() << endl; // 2 Integer i2(2); Integer i3 = i1+i2; cout << (i3++).get_value() << endl; // 4 cout << i3.get_value() << endl; // 5 return 0;}
7.2.2成员函数运算符重载
区别在于,是成员函数,第一个操作数使用this指针表示注意:下面两个必须使用成员函数重载
- 赋值运算符重载
- 类型转换运算符重载
#include using namespace std;/** * @brief 自定义整数类型 */class Integer{private: int value;public: Integer(int value):value(value){} int get_value() const { return value; } // 成员函数运算符重载 Integer operator +(const Integer& i); // 类内声明 Integer operator ++(); // 前置++ Integer operator ++(int); // 后置++};// 类外定义Integer Integer::operator +(const Integer& i){ return this->value+i.value;}Integer Integer::operator ++(){ return ++this->value;}Integer Integer::operator ++(int){ return this->value++;}int main(){ Integer i1(1); cout << (++i1).get_value() << endl; // 2 Integer i2(2); Integer i3 = i1+i2; cout << (i3++).get_value() << endl; // 4 cout << i3.get_value() << endl; // 5 return 0;}
#include using namespace std;class City{private: string name;public: City(string name):name(name){} string get_name() const { return name; } // 以下代码写不写都一样 City& operator =(const City& c) { cout << "赋值运算符重载" << endl; // 默认无这行代码 this->name = c.name; return *this; }};int main(){ City c1("济南"); City c2("青岛"); // 赋值运算符 cout << (c2 = c1).get_name() << endl; // 济南 return 0;}
#include using namespace std;class Test{public: // 类型转换运算符重载 operator int() { return 123; }};int main(){ Test t; int a = t; // 类型转换 cout << a << endl; return 0;}
8.模板
使函数或声明为一种通用类型,这种编程方式称为泛型编程。
8.1.函数模板
#include using namespace std;template // 声明模板的通用数据类型TT add(T a,T b){ return a+b;}int main(){ // 运行时决定具体类型的计算方式 cout << add(2,3) << endl; // 5 cout << add(2.2,3.3) << endl; // 5.5 string s1 = "AAA"; string s2 = "BBB"; cout << add(s1,s2) << endl; // AAABBB // const char* 不支持加法 // cout << add("111","222") << endl; 错误 return 0;}
8.2.类模板
#include using namespace std;template // typename可与class关键字替换class Test{private: T value;public: Test(T v):value(v){} T get_value() const { return value; }};class Projector // 投影仪{public: void show() { cout << "投影仪播放内容中..." << endl; }};int main(){ Test t1(123); cout << t1.get_value() << endl; //123 Projector p; Test t2(p); t2.get_value().show(); // 投影仪播放内容中... return 0;}
#include using namespace std;template // typename可与class关键字替换class Test{private: T value;public: Test(T v); T get_value() const;};template Test::Test(T v){ value = v;}template T Test::get_value() const{ return value;}class Projector // 投影仪{public: void show() { cout << "投影仪播放内容中..." << endl; }};int main(){ cout << t1.get_value() << endl; //123 Projector p; Test t2(p); t2.get_value().show(); // 投影仪播放内容中... return 0;}
9.容器
容器是用来存放数据元素的集合。容器分为顺序容器,关联容器
9.1顺序容器
9.1.1array数组
#include #include // 容器类都需要引入头文件using namespace std;int main(){ // 创建一个长度为5的数组对象 array arr = {1,2,3}; // 前三个元素的值是1,2,3 cout << arr[0] << endl; // 1 // 默认值0(不同的编译环境可能有所区别) cout << arr[3] << endl; // 0 // 修改第四个元素的值 arr[3] = 888; cout << arr[3] << endl; // 888 // 也支持at函数(推荐) cout << arr.at(3) << endl; // 888 cout << "------普通for循环-------" << endl; for(int i = 0;i::iterator iter;//iterator 迭代器类型为读写,只是读为const_iteratorfor(iter=arr.begin();iter!=arr.end();iter++) { cout <<*iter << " "; } return 0;}
9.1.2vector向量
#include #include // 容器类都需要引入头文件using namespace std;int main(){ // 创建初始元素为5的向量对象 vector vec(5); cout << vec.size() << endl; // 5 // 可以使用[]或at函数取出元素,推荐后者 cout << vec.at(0) << endl; // 0 // 判断是否为空 cout << vec.empty() << endl; // 0 // 尾部追加 vec.push_back(8); cout << vec.at(vec.size()-1) << endl; // 8 // 在第一个位置插入一个元素 // 参数1:插入位置,begin函数返回一个迭代器指针,指向第一个元素 // 参数2:插入内容 vec.insert(vec.begin(),1); // 在倒数第二个位置插入一个元素 // 参数1:end函数返回一个迭代器指针,指向最后一个元素的后面 // 参数2:插入内容 vec.insert(vec.end()-1,678); // 修改第二个元素 vec[1] = 2; // 删除第二个元素 vec.erase(vec.begin()+1); // 删除倒数第二个元素 vec.erase(vec.end()-2); cout << "------普通for循环-------" << endl; for(int i = 0;i::const_iterator iter;//iterator 迭代器类型为读写,只是读为const_iteratorfor(iter=vec.begin();iter!=vec.end();iter++) { cout <<*iter << " "; } return 0;}
9.1.3list列表
#include #include // 容器类都需要引入头文件using namespace std;int main(){ // 创建一个元素是4个hello的列表对象 list lis(4,"hello"); // 判断是否为空 cout << lis.empty() << endl; // 0 // 向后追加元素 lis.push_back("bye"); // 头插 lis.push_front("hi"); // 在第二个位置插入元素“second” // 注意:迭代器指针不支持+运算,支持++运算 lis.insert(++lis.begin(),"second"); // 在倒数第二个位置插入元素"aaa" // 注意:迭代器指针不支持-运算,支持--运算 lis.insert(--lis.end(),"aaa"); // 到第5个位置插入元素“555” // 1. 先拿到第一个元素位置的迭代器指针 list::iterator iter = lis.begin(); // 2. 向后移动4位 // 参数1:迭代器指针 // 参数2:向后移动的数量,负数为向前 advance(iter,4); // 3. 插入元素“555” lis.insert(iter,"555"); // 修改第五个元素为“666”,【重新获取】并移动迭代器指针 iter = lis.begin(); advance(iter,4); *iter = "666"; // 删除第一个元素 lis.pop_front(); // 删除最后一个元素 lis.pop_back(); // 删除第四个元素 iter = lis.begin(); advance(iter,3); lis.erase(iter); // 取出第一个和最后一个元素 cout << lis.front() << " " << lis.back() << endl; // 不支持普通for循环遍历 cout << "------for each循环-------" << endl; for(string i:lis) { cout << i << " "; } cout << endl; cout << "------迭代器------" << endl; list::const_iterator iter;//iterator 迭代器类型为读写,只是读为const_iteratorfor(iter=lis.begin();iter!=lis.end();iter++) { cout <<*iter << " "; } return 0;}
9.1.4deque队列
从API上兼容vector和list,从性能上位于vector和list之间
9.2.关联容器
9.2.1map键值对
元素以键值对的方式存储,键必须具有唯一性,值可以重复;键通常是字符串类型,而值可以是任何类型。
#include #include
10.抽象类
如果我们的基类只表达一些抽象的概念,并不与具体的对象相联系,他可以为派生类提供一个框架,这就是抽象类。如果一个类有至少一个纯虚函数,则这个类是抽象类。如果一个类时抽象类,则这个类至少也有一个纯虚函数。纯虚函数是一种特殊的虚函数,纯虚函数只有声明,没有定义。
抽象类的析构函数都应该写为虚析构函数。
#include using namespace std;class Shape{public: //纯虚函数 virtual void perimeter() = 0; virtual void area() = 0; virtual ~Shape(){}};int main(){// Shape s; //错误 return 0;}
10.1抽象类的使用
使用派生类实例化所有虚函数,此时,所有的派生类都会变成一个普通的类。就可以实例化了。
#include using namespace std;//形状类class Shape{public: //纯虚函数 virtual void perimeter() = 0; virtual void area() = 0; virtual ~Shape() { }};//圆形类class Circle:public Shape{public: void perimeter() { cout << "周长:2πR" << endl; } void area() { cout << "面积:πR^2" << endl; }};int main(){ Circle c; c.perimeter(); c.area(); return 0;}
如果没有实例化完,还需要再实例化完为止
#include using namespace std;class Shape{public: //纯虚函数 virtual void perimeter() = 0; virtual void area() = 0; virtual ~Shape() { }};//多边形类class Polygon:public Shape{public: void perimeter() { cout << "周长:∑边长" << endl; }};//矩形类class Rectangle:public Polygon{public: void area() { cout << "面积" << endl; }};int main(){ // Polygon pg; 还是抽象类,只实现了部分纯虚函数 Rectangle ra; ra.perimeter(); ra.area(); return 0;}
11.字符串类
#include using namespace std;int main(){ //创建一个内容为空的字符串对象 string s; //判断字符串是否为空:empty() cout << s.empty() << endl; //1 string s1 = "Monday"; //隐式调用构造函数 string s2("Monady"); //显示调用构造函数 cout << (s1==s2) << endl; //0 string s3(s2); //拷贝构造函数 s2 = s1; //赋值运算符 cout << s3 << " " << s2 << endl; //Monady Monday //参数1:char*原字符串 参数2:从前往后保留字符数 string s4("ASDFF",2); cout << s4 << endl; //AS //参数1:string原字符串 参数2:从前往后不保留的一个字符数 string s5(s2,2); cout << s5 << endl; //nday //参数1:字符数量 参数2:char字符内存 string s6(5,"A"); cout << s6 << endl; //AAAAA //交换字符串--swap() swap(s5,s6); cout << s5 << " " << s6 << endl; //AAAAA nday //字符串连接:+ string s7 = s4 + s6; cout << s7 << endl; //ASnday //向后追加字符串:append() s7.append("YYY").append("WUW"); cout << s7 << endl; //ASndayYYYWUW //向后追加单字符:push_back() s7.push_back("p"); cout << s7 << endl; //ASndayYYYWUWp //插入字符串:insert() 参数1:插入的位置 参数2:插入的内容 s7.insert(2,"******"); cout << s7 << endl; //AS******ndayYYYWUWp //删除:erase() 参数1:删除的起始位置 参数2:删除的字符数量 s7.erase(4,3); cout << s7 << endl; //AS***ndayYYYWUWp //替换:replace() 参数1:替换的起始位置 参数2:替换的字符数量 参数3:替换的新内容 s7.replace(0,3,"===="); cout << s7 << endl; //====**ndayYYYWUWp //清空:clear() s7.clear(); cout << s7.size() << endl; //0 return 0;}
关键词:
天天短讯!C++基础知识总结
7499元 华硕ROG新款38英寸游戏显示器上架:4K 144Hz高刷屏
北京现代沐飒正式上市:12.18万元起、百公里油耗低至6.86L-全球热闻
【全球独家】银行股迎来回调,不急,我们最不缺的就是时间
董明珠称:全世界只有格力空调不吹人! 环球观察
【Unity3D】法线贴图和凹凸映射 视点
每日讯息!中邮策略:央行降息提振信心 市场有望震荡上行
吃小龙虾戴了手套 为啥还是满手油? 每日快看
天天精选!AMD做了一个201MB缓存的怪物!可惜流产了
今年“6·18”河南消费力全国第八!胡辣汤、洛阳汉服、河南老字号美食全国热卖
k8s 深入篇———— 一些容器操作的原理[三]|报资讯
政治权利名词解释电大 政治权利名词解释 最新资讯
微软确认月初遭DDoS网络攻击:一度导致Microsoft 365服务中断
终末的女武神同人 毗沙门天VS宇智波斑-资讯
249元 雷柏推出VT9S无线鼠标:原相3395引擎、26000 DPI|全球微头条
大四男生实习薪资1万4妈妈仰天大笑 姐姐回应:要在船上写论文 全球聚焦
一箭41星创中国纪录!当天传回超清大图:英格兰一览无余 环球观察
全球热讯:拜耳医药保健有限公司属于哪个国家_拜耳公司是哪个国家
头条焦点:全国大数据与计算智能挑战赛:面向低资源的命名实体识别基线方案,排名13/64
STL vector容器存储键值对
世界滚动:美国加速跌落美债深渊
天天快消息!苹果M2 Ultra首次开盖:Intel 56核心相形见绌
“迈克·马龙,你为什么不买詹姆斯的账?”
环球报道:美团一面:OOM后,JVM一定会退出吗?为什么?
JS(入门)-焦点速读
东土科技:定增募资不超8.75亿元申请获证监会注册批复 环球热文
当前滚动:最便宜折叠手机!moto razr 40开售:3999元起
官方通报“特校校长刘某某强制猥亵残障学生案”
Python调用外部系统命令
【环球报资讯】Python第三方模块:pymongo模块的用法
马斯克:针对智能电视的推特视频应用即将问世 世界快消息
速递!国内首款市域C型动车组亮相:时速达160公里
最新民调:超半数非裔美国人预计美国种族主义会变得更糟 环球报道
500元到5000元 各价位最值得买的10款CPU集合
世界今日报丨独特16:18“方形”比例辨识度拉满:LG推出新款DualUp 28MQ750显示器
演员胡兵50万航司积分被清零引关注 东航客服回应:没办法核实-热门
k8s 深入篇———— docker 镜像是什么[二]
马斯克脑机接口新进展:首例人体植入试验今年开展 环球热资讯
攀升618秒杀:12代酷睿16英寸护眼屏轻薄本仅1999元_全球通讯
天天热资讯!《变形金刚》独占鳌头:2023暑期档票房达15亿元
看了“细菌量” 你还敢给孩子用卷纸来擦嘴吗?_天天快资讯
网飞爆火网剧续作来了!《鱿鱼游戏》第二季官宣:老角色回归|焦点要闻
浦发银行:共湘发展十九载 奋楫扬帆正当时
满血8核+32GB+1TB 机械革命S mini主机到手2929元:办公神器|世界即时看
资讯推荐:上线12年的“QQ安全达人”将下线 网友:你为它装过腾讯电脑管家没?
商职全称是什么 济南商职全称是什么
2023中国高校计算机大数据挑战赛:论文学科分类baseline|清华主办|今日看点
【技术积累】算法中的排序算法【一】
极限科技旗下软件产品 INFINI Easysearch 通过统信 UOS 认证|天天热议
【环球财经】日本为何出现巨额贸易逆差
便宜的瓜不甜?百果园回应女子团购西瓜被嘲讽:是误解 已道歉|全球热闻
京东618发布“35711”梦想:打造3家万亿收入公司 创造100万就业_通讯
演员胡兵向东航维权失败:价值一万多的白金卡50万积分一夜清零
天天快讯:在 Cenntos6.8 下安装 Oracle11g
当前播报:京东限时优惠:Redmi 27英寸4K显示器享大降价
世界头条:人气漫改!网飞真人版《海贼王》预告片出炉:路飞橡胶手无敌
全球车企第一高!马斯克曝特斯拉市值上涨主要动力 今日快看
关注:广州一龙舟队全是富婆?当地:事实 但身价不是参赛门槛
焦点热门:一公司端午节只发了三颗荔枝 员工吐槽:是不是公司要凉了?
女子户外活动后因热射病去世 专家提醒:轻度中暑及时干预_每日时讯
春天的诗有哪些古诗 春天的100首古诗有哪些|快资讯
【热闻】JWT的基本组成结构
当前速递!校长猥亵残障女学生判3年 法院回应全案审查!简直是禽兽不如!
保时捷Taycan对手来了!法拉利“老乡”发布纯电轿跑:124万起-世界报道
世界短讯!上新啦!这些夏日冷饮新品你尝过了吗?今年中国饮品冷饮产业还有这些新趋势!
【技术积累】Java中的集合框架【一】
清除外卖行业“影子店铺”隐患
今天是父亲节 微信上线限时状态感谢老爸:教你2步设置|环球快播
环球信息:重新定义移动办公:华为MatePad Air“野趣办公”成年轻人办公新潮流
每日焦点!16GB显存直戳RTX 4070痛点 AMD RX 7800 XT显卡定了
读发布!设计与部署稳定的分布式系统(第2版)笔记04_集成点
张家界桑植,跳桥救人小哥彭清林的家乡:那里都是“彩色的人”
1799元!前小米9号员工李明发布全球首款Android桌面机器人
Windows游戏一键移植 苹果Mac电脑硬伤不再:暗黑4能跑近百帧 当前热闻
李想造车:网上全赢过 现实没输过 焦点资讯
今日观点!女子骑车手扶帽子头部着地身亡:戴头盔太重要 新国标下月施行
天天观热点:吸甲醛最快最有效方法不花钱(吸甲醛最好的方法是什么)
理想自研自动充电机器人亮相:自动插枪 车主全程免下车_实时焦点
观焦点:广汽本田首批ZR-V致在e:HEV出口欧洲
rust 使用第三方库构建mini命令行工具
spring-boot 项目 使用总结
淄博狂飙90天:烧烤降温流量下滑 大部分烧烤店不再需要排队|当前速看
跳河救人小哥说大家给我的太多了:我只是做了一件小事
95后小伙卖临期食品走红:极具性价比 也能防止浪费_天天视讯
余承东:除了华为和比亚迪 其他人活下来很难
焦点热议:小孩路边偷偷买猫 家长找商家退猫遭拒把猫摔死:网友看怒
83届奥斯卡获奖名单(关于83届奥斯卡获奖名单的基本详情介绍)-全球速递
Rachio3控制用水量的智能庭院洒水器_全球最资讯
【全球新视野】Linux批量文件操作——基于find-xargs
安全攻击溯源-钓鱼邮件溯源 环球滚动
天天百事通!保时捷Mission_E原型车在纽伯格林上飞奔
一加Ace 2V大促:12+256不到2000元 无塑料支架 前沿资讯
大雾致航班取消 乘客骂哭机场员工
焦点速看:江苏常州:把惠企的公交车开到企业“家门口”
环球动态:08. centos安装包方式安装nginx(推荐该方式)
nas docker安装mysql 整理
麻省理工学院开发出超吸水性水凝胶 具有巨大的应用潜力
为计划支持平稳过渡 谷歌千万域名将全部打包出售
网友将梅西亲笔签名纹在了手臂上 网友点评称将永远擦不掉
国产开放世界端游《仙剑世界》即将开启首测 研发时间超过两年