300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > 【C++】黑马程序员C++核心编程学习笔记(完结)

【C++】黑马程序员C++核心编程学习笔记(完结)

时间:2020-10-28 03:14:25

相关推荐

【C++】黑马程序员C++核心编程学习笔记(完结)

目录

前言:一、内存1.1 内存四区1.2 new操作符二、引用2.1 引用基本使用和注意事项2.2 引用做函数参数2.3 引用函数返回值2.3 常量引用三、函数提高3.1 函数默认参数3.2 函数占位参数3.3 函数重载四、类与对象 4.1 封装4.1.1 成员属性设置为私有设计案例1:立方体类设计案例2:点和圆的关系 4.2 对象的初始化和清理 4.3.1 构造函数 4.3.2 析构函数 4.3.2.1 拷贝函数调用的时机 4.3.2.2 深拷贝与浅拷贝4.3.3 初始化列表4.3.4 类对象作为类的成员4.3.5 静态成员 4.3 C++对象模型和this指针4.4.1 成员变量和成员函数分开存储4.4.2 this指针概念4.4.3 空指针访问成员函数4.4.4 const修饰成员函数 4.4 友元 4.5 运算符重载4.5.1 加号运算符重载4.5.2 左移运算符重载 4.5.3 递增运算符重载 4.5.4 赋值运算符重载 4.5.5 关系运算符重载 4.5.6 函数调用运算符重载(仿函数) 4.6 继承 4.6.1 继承的基本语法 4.6.2 继承方式 4.6.3 继承中的对象模型 4.6.4 继承中构造和析构顺序 4.6.5 继承同名成员处理方式 4.6.6 多继承语法 4.6.7 菱形继承 4.7 多态 4.7.1 多态的基本概念 4.7.2 多态原理剖析(图解) 4.7.3 多态案例一:计算器类 4.7.4 纯虚函数和抽象类 4.7.5 多态案例二:制作饮品 4.7.6 虚析构函数和抽象类 4.7.7 多态案例三:电脑组装五、文件 5.1 文本文件 5.1.1 写文件 5.1.2 读文件 5.2 二进制文件 5.2.1 写文件 5.2.2 读文件

前言:

💙本学习笔记参考B站视频,在笔记里加了一些个人想法可能与原视频讲解内容有所差异。

因作者能力精力有限无法做到完美,若有不足和错误请大佬批评指正。

一、内存

1.1 内存四区

C++程序在执行时,将内存大致分为4个区域:代码区,全局区,栈区,堆区

代码区:存放函数体的的二进制代码,操作系统管理。

🔵特点:①共享 :对于频繁被执行的程序,只需要在内存中有一份代码即可。

②只读:目的是防止程序意外地修改了它的指令。

全局区:存放全局变量、静态变量和常量(除了const修饰的局部变量)。程序结束时由操作系统释放。

//全局变量int g_a = 10;const int c_g_a = 10;int main(){int l_a = 10;//静态变量static int s_a = 10;//常量:字符串常量/const修饰的变量(全局/局部变量)const char* a = "hello";const int c_l_a = 10;cout << "局部变量l_a的十进制地址为:" << (int)&l_a << endl;cout << "全局变量g_a的十进制地址为:" << (int)&g_a << endl;cout << "静态变量s_a的十进制地址为:" << (int)&s_a << endl;cout << "字符串常量a的十进制地址为:" << (int)a << endl;cout << "const修饰的全局变量c_g_a的十进制地址为:" << (int)&c_g_a << endl;cout << "const修饰的局部变量c_l_a的十进制地址为:" << (int)&c_l_a << endl;return 0;}

栈区:存放函数的参数值、局部变量。由编译器自动分配和释放。

🔴注意不要返回局部变量的地址。

int* funcation(){int a = 10;//存放在栈区,栈区的数据在函数执行完后自动释放return &a;}int main(){int* p = funcation();cout <<"局部变量a的值为:"<< *p << endl; //第一次正常打印,因为编译器做了保留cout << "局部变量a的值为:" << *p << endl;return 0;}

堆区:由程序员分配(new)和释放(delete),若程序员不释放,程序结束时由操作系统回收。

int* funcation(){//new关键字,可以将数据开辟到堆区上//指针本质上也是局部变量,存放在栈区上,但是保存的数据在堆区int* p = new int(10);return p;}int main(){int* p = funcation();cout << *p << endl;cout << *p << endl;return 0;}

Tip:

程序运行前分为:代码区,全局区程序运行后分为:栈区,堆区

[注]: 关于内存更详细的知识👉深度剖析数据在内存中的存储👉几分钟让你了解什么是函数栈帧的创建和销毁

1.2 new操作符

//new的基本用法int* p = new int(10);//在堆区创建整型变量,返回该变量的地址delete p;//释放int* parr = new int[10];//在堆区创建一个元素为10的整型数组,返回数组首元素的地址delete[] arr;//释放一个数组

测试delete

int* funcation(){int* p = new int(10);return p;}int main(){int* p = funcation();cout << *p << endl;cout << *p << endl;delete p;cout << *p << endl;return 0;}

二、引用

2.1 引用基本使用和注意事项

作用:给变量起个别名。

语法数据类型&别名=原名

本质:指针常量

//eg.int a = 10;int& b = a;

🔴注意

①引用必须初始化。

②引用一旦初始化就不可以更改了(如下的例子相当于int *const b=&a

//①引用必须初始化int a = 10;int& b;//错了!!!//②引用一旦初始化就不可以更改了int a = 10;int c = 20;int& b = a;int& b = c; //错了!!!

2.2 引用做函数参数

//eg./*实现数值交换*///1.传址交换void swap1(int* a, int* b){int temp = *a;*a = *b;*b = temp;}//2.引用void swap2(int& a, int& b)//起别名可以和原名一样{int temp = a;a = b;b = temp;}int main(){int a = 10;int b = 20;swap1(&a, &b);swap2(a, b);return 0;}

2.3 引用函数返回值

🔴注意

①不要返回局部变量的引用。函数返回时,局部变量会被释放,引用或指针指向的内容会失效

②函数的调用可以作为左值。

//eg.//①不要返回局部变量的引用int& test1(){int a = 10;return a;}int main(){int& ret = test1();cout << "ret=" << ret << endl;cout << "ret=" << ret << endl;test1() = 20; //②如果函数的返回值为引用,函数的调用可以作为左值cout << "ret=" << ret << endl;cout << "ret=" << ret << endl;return 0;}

2.3 常量引用

目的:用来修饰形参,防止误操作。

int &ret=10; //错了!,引用本身需要一个合法的内存空间。

/*相当于编译器先创建一个临时变量:int temp=10;然后进行起别名:int& ret=temp;*/const int& ret = 10;

🔴注意用常量引用之后不可以更改数据。

//eg.int main(){const int& ret = 10;ret = 100;//errcout << "ret=" << ret << endl;return 0;}

C++推荐引用,因为语法方便,编译器帮我们做了指针的内容。

三、函数提高

3.1 函数默认参数

语法返回类型函数名(参数 =默认值){}

用法:如果自己传入数据就用自己的,如果没有就用默认值。

int Add(int a = 0, int b = 0){return a + b;}int main(){int a = 10;int b = 20;int c = Add(a, b);int e = Add(a);int f = Add(b);int g = Add();cout << "c=" << c << endl;cout << "e=" << e << endl;cout << "f=" << f << endl;cout << "g=" << g << endl;return 0;}

🔴注意

①默认值必须放在右边。

int test(int a, int b = 10, int c);//err

②声明和实现,有且只能有一个有默认参数。否则可能出现二义。

3.2 函数占位参数

语法返回类型函数名(数据类型){}

//eg.void test1(int a, int){cout << "haha" << endl;}void test2(int a, int b=10)//占位参数可以有默认参数{cout << "haha" << endl;}int main(){test1(1);//errtest1(1, 1);test2(1);test2(1, 1);return 0;}

3.3 函数重载

作用:函数名可以相同,提高复用率。

满足条件

①同一个作用域。

②函数名称相同。

③参数类型不同/个数不同/顺序不同。

//eg.//在全局作用域void test(){cout << "调用test( )" << endl;}void test(int a) {cout << "调用test(int a)" << endl;}void test(double a){cout << "调用test(double a)" << endl;}void test(int a, int b){cout << "调用test(int a, int b)" << endl;}void test(int a, double b){cout << "调用test(int a, double b)" << endl;}void test(double a, int b){cout << "调用test(double a, int b)" << endl;}int main(){test();test(1);test(3.14);test(1,3.14);test(3.14, 1);return 0;}

🔴注意

①函数的返回值不可以作为函数重载的条件!出现二义。

//eg.void test(int a) {cout << "调用test(int a)" << endl;}int test(int a){cout << "调用test(int a)" << endl;}

②引用作为重载条件

//eg.//引用作为重载条件void test(int &a){cout << "调用test(int &a)" << endl;}void test(const int& b){cout << "调用test(const int& b)" << endl;}int main(){int a = 10;const int b = 10;test(a);test(b);return 0;}

③函数重载碰到默认参数

//eg.//函数重载碰到默认参数void test(int a,int b=10){cout << "调用test(int &a)" << endl;}void test(int a){cout << "调用test(const int& b)" << endl;}int main(){test(10);//err,出现二义return 0;}

四、类与对象

C++面向对象三大特性:封装、继承、多态

4.1 封装

🟦意义

①将属性和行为作为一个整体。(放在一个class里面)

②将属性和行为加以权限控制。

public公共权限:类内外都可以访问

protected保护权限: 类外不可以访问

private私有权限: 类外不可以访问

//eg.1定义一个圆类#define PI 3.14//class 定义一个类 circle是类的名字class circle{//访问权限:公共权限public://属性int r;//行为double circumference( ){return r * PI * 2;}};int main(){circle c1;//创建具体的圆(对象)(实例化)c1.r = 10;//给具体的圆的属性赋值cout << "圆的周长为:" << c1.circumference() << endl;return 0;}

//eg.2设计一个学生类class student{public://属性string name;//string 类处理起字符串来会方便很多,完全可以代替C语言中的字符数组或字符串指针。int id;//行为void show() {cout << "姓名:" << name << "学号:" << id << endl;}};int main(){student s1;s1.name = "xiyang";s1.id = 1;s1.show();return 0;}

//eg.3 公共权限,私有权限,保护权限访问的例子class person{public:string name;protected:string car;private:int password;public:void test(){name = "zyz";car = "ofo";password = 123;}};int main(){person p1;p1.name = "xiyang";p1.car = "ufo"; //errp1.password = 456;//errreturn 0;}

🔴注意structclass的区别:

struct默认权限为:共有

class默认权限为:私有

4.1.1 成员属性设置为私有

🟦意义

①可以直接控制读写的权限。

②对于写可以检测数据的有效性。

//eg.class person{private:string name;//可读写int age;//只读string lover;//只写public:void SetName(string s){name = s;}string GetName(){return name;}int GetAge(){age = 18;return age;}void SetLover(string s){lover = s;}};int main(){person p1;p1.SetName("xiyang");p1.SetLover("薇尔莉特·伊芙加登");cout << "姓名为:" << p1.GetName() << endl;cout << "年龄为:" << p1.GetAge() << endl;return 0;}

设计案例1:立方体类

/*要求:1.设计一个立方体类2.求出立方体的面积和体积3.分别用全局函数和成员函数判断两个立方体是否相等*/class cube{private://属性int L;int W;int H;public://行为//设置 获取长,宽,高void SetL(int a){L = a;}int GetL(){return L;}void SetW(int a){W = a;}int GetW(){return W;}void SetH(int a){H = a;}int GetH(){return H;}//获得面积int S(){return 2 * ((L * W) + (L * H) + (W * H));}//获得体积int V(){return L * W * H;}//成员函数判断bool isSameByClass(cube& c){if (c.GetL() == L && c.GetW() ==W && c.GetH() == H)return true;elsereturn false;}};//全局函数判断bool isSame(cube& c1, cube& c2){if (c1.GetL() == c2.GetL() && c1.GetW() == c2.GetW() && c1.GetH() == c2.GetH())return true;elsereturn false;}int main(){cube c1,c2;c1.SetL(10);c1.SetW(10);c1.SetH(10);c2.SetL(10);c2.SetW(5);c2.SetH(5);cout << "第一个立方体的面积为:" << c1.S() << endl;cout << "第一个立方体的体积为:" << c1.V() << endl;bool ret1 = isSame(c1, c2);if (ret1){cout << "全局函数判断c1 c2相等" << endl;}elsecout << "全局函数判断c1 c2不相等" << endl;bool ret2 = c1.isSameByClass(c2);if (ret2){cout << "成员函数判断c1 c2相等" << endl;}elsecout << "成员函数判断c1 c2不相等" << endl;return 0;}

设计案例2:点和圆的关系

/*要求:1.设计一个圆形类和一个点类2.计算点和圆的关系*///由简单的数学可知:一个点(x,y)和圆的(x,y,r)的关系有三种://1.点在圆内:点到圆心的距离d < r//2.点在圆上:d=r//3.点在圆外:d>rclass circle{private:int r;//写+读int x;//写+读int y;//写+读public:void SetX(int a){x = a;}int GetX(){return x;}void SetY(int a){y = a;}int GetY(){return y;}void SetR(int a){r = a;}int GetR(){return r;}};class point{private:int x;//写int y;//写public:void SetX(int a){x = a;}void SetY(int a){y = a;}int location(circle& c){if ((x - c.GetX()) * (x - c.GetX()) + (y - c.GetY()) * (y - c.GetY()) == c.GetR() * c.GetR())return 0;else if ((x - c.GetX()) * (x - c.GetX()) + (y - c.GetY()) * (y - c.GetY()) > c.GetR() * c.GetR())return 1;elsereturn -1;}};int main(){circle c;point p;c.SetX(0);c.SetY(0);c.SetR(1);p.SetX(0);p.SetY(0);int ret = p.location(c);if (ret == 1){cout << "点在圆外" << endl;}else if(ret == -1){cout << "点在圆内" << endl;}else{cout << "点在圆上" << endl;}return 0;}

优化:因为圆类里面包含点类(转到目录4.3.2.4类对象作为类的成员)

4.2 对象的初始化和清理

C++利用构造函数和析构函数解决了对象的初始化和清理。对象的初始化和清理工作是编译器强制要求我们做的事情,因此就算我们不提供构造和析构,编译器也会提供,只不过编译器提供的是构造函数和析构函数的空实现。

4.3.1 构造函数

定义:主要作用在创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。

语法类名(){ }

分类:按参数分:有参,无参;按类型分:普通构造,拷贝构造。

调用方式:括号法,显示法,隐式转换法.

🔴注意

①构造函数可以有参数,因此可以重载。

②程序在调用对象时会自动调用构造,无须手动调用,且只用调用一次。

//构造的分类和调用class person{public://无参(普通构造)(默认构造)person(){cout << "无参构造函数调用" << endl;}//有参(普通构造)person(int a){cout << "有参构造函数调用" << endl;}//拷贝构造函数person(const person &p){age = p.age;//克隆数据cout << "拷贝构造函数的调用" << endl;}public:int age;};int main(){//括号法person p1;//叫括号法,但是不能加(),加了()编译器会认为是一个函数声明person p2(10); //p2的年龄初始化为10person p3(p2); //显示法person p4 = person();person p5 = person(10);person p6 = person(p5);//person()为匿名对象,没有名字,但创建了对象//不要用拷贝构造函数初始化匿名对象,如person(p3),等价于person p3//隐式转换法person p7 = 10;//转换为:person p7=person(10)}

构造函数的调用规则

创建一个类,C++至少给每一个类添加4个函数:默认构造(空实现),析构函数(空实现),拷贝构造(值拷贝),赋值运算符Operator=对属性进行值拷贝(4.5.4中介绍)

①如果用户定义一个有参构造函数,C++不会提供默认构造函数,但是会提供拷贝构造函数。

②如果用户定义一个拷贝构造函数,C++不会提供别的构造函数。

//eg.①如果用户定义一个有参构造函数,C++不会提供默认构造函数,但是会提供拷贝构造函数class person{public:person(int a){age = a;cout << "默认构造函数的调用" << endl;}int show(){return age;}private:int age;};int main(){person p1;//errperson p2(18);person p3(p2);//拷贝构造函数cout << "p2的年龄为:" << p2.show() << endl;return 0;}

//②如果用户定义一个拷贝构造函数,C++不会提供别的构造函数class person{public:person(const person& p){age = p.age;//克隆数据cout << "拷贝构造函数的调用" << endl;}int show(){return age;}private:int age;};int main(){person p1;//errperson p2(18);//errperson p3(p1);cout << "p2的年龄为:" << p2.show() << endl;return 0;}

4.3.2 析构函数

定义:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

语法~类名(){ }

🔴注意

①析构函数不可以有参数,因此不可以重载。

②程序在对象销毁前会自动调用析构,无须手动调用,且只用调用一次。

//eg. 构造函数和析构函数例子class person{public:person(){cout << "构造函数的调用" << endl;}~person(){cout << "析构函数的调用" << endl;}};int main(){person p;//创建在栈上,在对象销毁前自动调用析构函数return 0;}

4.3.2.1 拷贝函数调用的时机

C++中拷贝函数调用一般有三种情况:

①使用一个已创建完毕的对象来初始化一个新对象。

②值传递的方式给函数参数传值。

③以值的方式返回局部对象。

class person{public:person(){cout << "默认构造函数的调用" << endl;}person(int a){age = a;cout << "默认构造函数的调用" << endl;}person(const person& p){age = p.age;//克隆数据cout << "拷贝构造函数的调用" << endl;}int show(){return age;}private:int age;};void test1(person p)//仅仅测试{}person test2(){person p;return p;}int main(){//使用一个已创建完毕的对象来初始化一个新对象person p1(20);person p2(p1);cout << "p2的年龄为:" << p2.show() << endl;//值传递的方式给函数参数传值person p3;test1(p3);//以值的方式返回局部对象test2();return 0;}

4.3.2.2 深拷贝与浅拷贝

浅拷贝:简单的赋值拷贝。深拷贝:在堆区重新申请空间,进行拷贝。

浅拷贝存在的问题:堆区内容重复释放。

✅解决方案·

//eg.class person{public:person(int a,int h){age = a;height = new int(h);cout << "有参构造函数的调用" << endl;}person(const person& p){age = p.age;//height=p.height;//编译器默认实现height = new int(*(p.height));cout << "拷贝构造函数的调用" << endl;}~person(){if (height != NULL){delete height;height = NULL;}cout << "析构函数的调用" << endl;}int GetAge(){return age;}int GetHeight(){return *height;}private:int age;int *height;};int main(){person p1(18, 160);cout << "p1的年龄为:" << p1.GetAge() << " p1的身高为:" << p1.GetHeight() << endl;person p2(p1);cout << "p2的年龄为:" << p2.GetAge() << " p2的身高为:" << p2.GetHeight() << endl;return 0;}

4.3.3 初始化列表

//eg.class person{public://初始化列表初始化属性person() :age(10), key(123), height(160){}person(int a, int b, int c) :age(a), key(b), height(c){}int age;int key;int height;};int main(){person p1;person p2(18, 456, 180);cout << "p1的年龄,密码,身高为:\n" << p1.age << p1.key << p1.height<<endl;cout << "p2的年龄,密码,身高为:\n" << p2.age << p2.key << p2.height<<endl;return 0;}

4.3.4 类对象作为类的成员

//eg.class phone{public:phone(string p):PhoneName(p){}string PhoneName;};class person{public:person(string s,string p) : name(s),Phone(p){}string name;phone Phone;};int main(){person p1("xiyang", "huawei");cout << "名字:" << p1.name << "手机:" << p1.Phone.PhoneName << endl;return 0;}

🔴注意:若class里面有其他类,则先构造其他类,再构造自身。析构相反。

//egclass phone{public:phone(string p):PhoneName(p){cout << "调用phone构造" << endl;}~phone(){cout << "调用phone析构" << endl;}string PhoneName;};class person{public:person(string s,string p) : name(s),Phone(p){cout << "调用person构造" << endl;}~person(){cout << "调用person析构" << endl;}string name;phone Phone;};int main(){person p1("xiyang", "huawei");cout << "名字:" << p1.name << "手机:" << p1.Phone.PhoneName << endl;return 0;}

4.3.5 静态成员

静态成员变量:

🔵特点

①所有对象共享一份数据。

②在编译阶段分配内存。

③类内声明,类外初始化。

//eg.class person{public:static int a;//类内声明};int person::a = 100;//类外初始化int main(){person p1;cout << "p1的值为:" << p1.a << endl;person p2;p2.a = 200;cout << "p1的值为:" << p1.a << endl;return 0;}

🔴注意

① 静态成员变量不属于某一个对象。因此有两种访问方式:①类名访问,②对象访问。

class person{public:static int a;};int person::a = 100;int main(){//对象访问person p1;cout << p1.a << endl;//类名访问cout << person::a << endl;return 0;}

②静态成员变量也有访问权限。

静态成员函数:

🔵特点

①所有对象共享一个函数。

②静态成员函数只能访问静态成员函数。

🔴注意静态成员函数也有访问权限。

//静态成员函数的访问class person{public:static void test(){cout << "static void test()调用" << endl;}};int main(){//对象访问person p;p.test();//成员访问person::test();return 0;}

//静态成员函数只能访问静态成员函数class person{public:static void test(){a = 200;b = 200;//err,对象不明cout << "static void test()调用" << endl;}static int a;//静态成员函数访问静态成员变量int b;//非静态成员函数};int person::a = 100;int main(){person p;p.test();return 0;}

4.3 C++对象模型和this指针

4.4.1 成员变量和成员函数分开存储

在C++中,类的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上。

//eg.class person1{};class person2{int a;//非静态成员变量};class person3{int a;static int b;//静态成员变量};class person4{int a;static int b;//静态成员变量void test()//非静态成员函数{}};int main(){//空对象占用内存是 1//C++会给每一个空对象分配一个字节的内存空间,为了区分空对象占内存的位置//每一个空对象也应该有一个独一无二的内存地址person1 p1;cout << "sizeof(p)=" << sizeof(p1) << endl;//1//虽然空对象有一个字节,但是一旦类里面不为空就跟着类中字节走person2 p2;cout << "sizeof(p)=" << sizeof(p2) << endl;//4//静态成员变量不属于类对象上的person3 p3;cout << "sizeof(p)=" << sizeof(p3) << endl;//4//类的成员变量和成员函数分开存储person4 p4;cout << "sizeof(p)=" << sizeof(p4) << endl;//4return 0;}

4.4.2 this指针概念

定义:this指针指向被调用的成员函数所属的对象。

🔵特点

①this指针隐含在每一个非静态成员函数内的一种指针。

②this指针不需要定义,可直接使用。

🟦意义

①当形参和成员变量同名时,可以用this指针来区分。

②在类的非静态成员函数中返回对象本身,可以用retrun *this

形参和成员变量名字相同

解决方案

//eg.当形参和成员变量同名时,可以用this指针来区分。class person{public:person(int age){//this 指向被调用的成员函数所属的对象this->age = age;}int age;};int main(){person p1(18);cout << "p1的年龄为:" << p1.age << endl;return 0;}

// 在类的非静态成员函数中返回对象本身,可以用retrun *thisclass person{public:person(int age){this->age = age;}person& test(person p)//person一定要加&,使用本体{this->age += p.age;return *this;}int age;};int main(){person p1(18);person p2(18);//链式编程p2.test(p1).test(p1);cout << "p2的年龄为:" << p2.age << endl;return 0;}

4.4.3 空指针访问成员函数

C++中空指针可以调用成员函数

class person{public:void test1(){cout << "test1" << endl;}void test2(){cout << "age="<<this->age<< endl;//err,传入的指针为空}int age;};int main(){person *p=NULL;p->test1();p->test2();return 0;}

void test2(){if(this->age==NULL)return;//提高代码的健壮性cout << "age="<<this->age<< endl;//err,传入的指针为空}

4.4.4 const修饰成员函数

常函数:成员函数后const

🔵特点

①常函数内不可以修改成员属性。

②成员属性声明时加关键字mutable后,在常函数中依然可以修改。

//eg.class person{void test2(){a = 100;b = 200; //err b是常量不可以修改c = 300;}//在成员函数后加const,修饰的是this指向,让指针指向的值不能改变void test1() const{a = 100; //err 相当于this->a=100 b = 200; //err b是常量不可修改c = 300;}int a;const int b;mutable int c;};int main(){person p;p.test1();p.test2();return 0;}

常对象:声明对象前加const

🔵特点

①常对象只能调用常函数。

//eg.class person{public:void test1(){}void test2() const{}int a;const int b;mutable int c;};int main(){const person p;//不能修改指针指向的值p.a = 100;//errp.b = 200;//errp.c = 300;p.test1();//err 常对象只能调用常函数p.test2();}

4.4 友元

目的:让一个函数或类,访问另一个类中的私有成员。

关键字friend

实现

①全局函数做友元。

②类做友元。

③成员函数做友元。

//全局函数做友元class building{friend void test2(building* b);//声明友元函数private:string bedroom;public:building(){bedroom = "卧室";livingroom = "客厅";}string livingroom;};void test1(building* b){cout << "访问" << b->livingroom << endl;cout << "访问" << b->bedroom<< endl;//err 不可访问私有成员}void test2(building* b){cout << "访问" << b->livingroom << endl;cout << "访问" << b->bedroom << endl;}int main(){building b1;test1(&b1);test2(&b1);return 0;}

//类做友元class building{friend class gay2;public:building();string livingroom;private:string bedroom;};building::building(){bedroom = "卧室";livingroom = "客厅";}//非友元class gay1{public:gay1();void vist();building* b;};gay1::gay1(){b = new building;}void gay1::vist(){cout << "朋友在访问" << b->livingroom << endl;cout<< "朋友在访问" << b->bedroom << endl;//err 没有访问权限}//友元class gay2{public:gay2();void vist();building* b;};gay2::gay2(){b = new building;}void gay2::vist(){cout << "朋友在访问" << b->livingroom << endl;cout << "朋友在访问" << b->bedroom << endl;}int main(){gay1 g1;g1.vist();gay2 g2;g2.vist();return 0;}

//成员函数做友元class building;//当用到友元成员函数时,需注意友元声明与友元定义之间的互相依赖。class gay{public:gay();void test1();void test2();building* b;};gay::gay(){b = new building;}void gay::test1(){cout << "正在访问" << b->livingroom <<endl;cout << "正在访问" << b->bedroom << endl;//err }void gay::test2(){cout << "正在访问" << b->livingroom << endl;cout << "正在访问" << b->bedroom << endl;}class building{public:building();string livingroom;friend void gay::test2();private:string bedroom;};building::building(){bedroom = "卧室";livingroom = "客厅";}int main(){gay g1;g1.test1();g1.test2();return 0;}

4.5 运算符重载

运算符重载对已有的运算符重新定义,赋予另一种功能,以适应不同的数据类型。

4.5.1 加号运算符重载

作用:实现两个自定义数据类型相加的运算。

//eg.//加号运算符重载class person{public:person(int x, int y) :a(x), b(y){}//重载运算符operator//成员函数重载+号person operator+(person& p){person temp(0, 0);temp.a = this->a + p.a;temp.b = this->b + p.b;return temp;}int a;int b;};//全局函数重载+号person operator+(person& p1, person& p2){person temp(0, 0);temp.a = p1.a + p2.a;temp.b = p1.b + p2.b;return temp;}int main(){person p1(10, 10);person p2(10, 10);person p3 = p1+p2;//方法一(若用此方法需要屏蔽上面一种重载方式,避免多个运算符+与操作数匹配)person p3 = p1.operator+(p2);//方法二person p3 = operator+(p1, p2);//方法三}

🔴注意:不要滥用运算重载符。

4.5.2 左移运算符重载

作用:可以输出自定义类型。

//egclass person{public://舍弃成员函数重载,p.operator<<cout相当于p<<cout与期望不相符//void operator<<(cout)//{//}person(int x) :a(x){}int a;int b;};//全局函数重载左移运算符//cout属于标准输出流ostream类型//返回ostream是链式编程思想,返回后cout<<p<<...后面可以再利用ostream& operator<<(ostream& cout, person& p){cout <<"p中a的值为:"<< p.a<<"p中b的值为:"<< p.b<<endl;return cout;}int main(){person p(10);cout << p;//方法一operator<<(cout, p);//方法二return 0;}

4.5.3 递增运算符重载

//重载递增运算符class MyInt{friend ostream& operator<<(ostream& cout, MyInt m);public:MyInt(){num = 0;}//前置++//&的作用是对同一个数据进行递增MyInt& operator++(){//先++num++;//再返回return *this;}//后置++//int是占位参数MyInt operator++(int){//先暂存MyInt temp = *this;//再++num++;//返回暂存的数据return temp;}private:int num;};ostream& operator<<(ostream& cout, MyInt m){cout << m.num;return cout;}int main(){MyInt m;cout << ++m << endl;cout <<m++<< endl;return 0;}

🔴注意:前置++,返回是引用。后置++,返回的是值。

4.5.4 赋值运算符重载

创建一个类,C++至少给每一个类添加4个函数:默认构造(空实现),析构函数(空实现),拷贝构造(值拷贝),赋值运算符Operator=对属性进行值拷贝

//赋值运算符的重载class person{public:person(int a){age=new int(a);}~person(){if (age != NULL){delete age;age = NULL;}}//重载赋值运算符以避免浅拷贝带来的问题person& operator=(person& p){//age=p.age;//编译器默认实现if (age != NULL){delete age;age = NULL;}age = new int(*p.age);//返回自身return *this;}int *age;};int main(){person p1(10);person p2(20);person p3(30);p3=p2 = p1;cout << *(p2.age) << endl;//10cout << *(p3.age) << endl;//10return 0;}

4.5.5 关系运算符重载

作用:重载关系运算符,可以让两个自定义类型对象进行对比操作。

//关系运算符重载 == !=class person{public:person(string s,int a):name(s),age(a){}bool operator==(person& p){if (this->name == p.name && this->age == p.age){return true;}elsereturn false;}string name;int age;};int main(){person p1("xi",10);person p2("xi",20);if (p1 == p2){cout << "p1和p2相等" << endl;}elsecout << "p1和p2不相等" << endl;return 0;}

4.5.6 函数调用运算符重载(仿函数)

//函数调用运算符重载class MyPrint{public:void operator()(string text)//与函数调用长得差不多,所以被称为仿函数{cout << text << endl;}};void print(string text){cout << text << endl;}int main(){MyPrint m1,m2;m1("xiyang");m2("xiyang");return 0;}

4.6 继承

4.6.1 继承的基本语法

例如我们看到很多网站中,都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同。接下来我们分别利用普通写法和继承的写法来实现网页中的内容,看一下继承存在的意义以及好处。

//普通实现页面class java{public:void head(){cout << "首页、公开课、登录、注册...(公共头部)" << endl;}void foot(){cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;}void left(){cout << "Java,Python,C++...(公共分类列表)" << endl;}void content(){cout << "JAVA学科视频" << endl;}};class cpp{public:void head(){cout << "首页、公开课、登录、注册...(公共头部)" << endl;}void foot(){cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;}void left(){cout << "Java,Python,C++...(公共分类列表)" << endl;}void content(){cout << "CPP学科视频" << endl;}};class python{public:void head(){cout << "首页、公开课、登录、注册...(公共头部)" << endl;}void foot(){cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;}void left(){cout << "Java,Python,C++...(公共分类列表)" << endl;}void content(){cout << "python学科视频" << endl;}};void test1(){cout << "java下载视频页面如下:" << endl;java j;j.head();j.foot();j.left();j.content();cout << "--------------------" << endl;}void test2(){cout << "cpp下载视频页面如下:" << endl;cpp c;c.head();c.foot();c.left();c.content();cout << "--------------------" << endl;}void test3(){cout << "python下载视频页面如下:" << endl;python p;p.head();p.foot();p.left();p.content();cout << "--------------------" << endl;}int main(){test1();test2();test3();return 0;}

//继承写法class BasePage{public:void head(){cout << "首页、公开课、登录、注册...(公共头部)" << endl;}void foot(){cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;}void left(){cout << "Java,Python,C++...(公共分类列表)" << endl;}void content(){cout << "python学科视频" << endl;}};class java :public BasePage{void content(){cout << "java学科视频" << endl;}};class cpp :public BasePage{void content(){cout << "cpp学科视频" << endl;}};class python :public BasePage{void content(){cout << "python学科视频" << endl;}};

优势:减少重复代码

语法class 子类 : 继承方式

4.6.2 继承方式

一共有三中继承方式: ①公共继承②保护继承③私有继承

//eg.class father{public:int a;protected:int b;private:int c;};//公共继承class son1 :public father{public:void test1(){a = 10;//父类中的公共权限成员,子类也是公共权限b = 10;//父类中的保护权限成员,子类也是保护权限//c = 10;//err 父类的私有权限成员,子类无法访问}};void test11(){son1 s;s.a;//s.b;//err保护权限,类外无法访问}//保护继承class son2 :protected father{public:void test2(){a = 10;//父类中的公共权限成员,子类是保护权限b = 10;//父类中的保护权限成员,子类是保护权限//c = 10;//err 父类的私有权限成员,子类无法访问}};void test22(){son2 s;//s.a;//err保护权限,类外无法访问//s.b;//err保护权限,类外无法访问}//私有继承class son3 :private father{public:void test3(){a = 10;//父类中的公共权限成员,子类是私有权限b = 10;//父类中的保护权限成员,子类是私有权限//c = 10;//err 父类的私有权限成员,子类无法访问}};void test22(){son3 s;//s.a;//err私有权限,类外无法访问//s.b;//err私有权限,类外无法访问}

4.6.3 继承中的对象模型

Q:从父类继承过来的成员,哪些属于子类对象中?

//egclass father{public:int a;static int d;//不继承protected:int b;private:int c;};class son :public father{public:int e;};int main(){//父类中的所有非静态成员属性都会被子类继承//父类的私有成员被编译器隐藏,访问不到但是被继承cout << "size of(son)=" << sizeof(son) << endl;//16return 0;}

用工具查看

cl /d1 reportSingleClassLayout查看的类名 所属文件名

4.6.4 继承中构造和析构顺序

//egclass father{public:father(){cout << "father的构造函数" << endl;}~father(){cout << "father的析构函数" << endl;}};class son :public father{public:son(){cout << "son的构造函数" << endl;}~son(){cout << "son的析构函数" << endl;}};int main(){son s;return 0;}

4.6.5 继承同名成员处理方式

//egclass father{public:father(){a = 10;}void test(){cout << "father中的test()函数调用" << endl;}int a;static int b;};int father::b=10;class son :public father{public:son(){a = 20;}void test(){cout << "son中的test()函数调用" << endl;}int a;static int b;};int son::b = 20;int main(){son s;//同名非静态属性处理方式cout << "son中a=" << s.a << endl;cout << "father中a=" << s.father::a << endl;//通过子类对象访问父类对象成员需要加作用域//同名函数处理方式1hjs.test();s.father::test();//同名静态属性处理方式//方法一:对象访问cout << "对象访问son中b=" << s.b << endl;cout << "对象访问father中b=" << s.father::b << endl;//方法二:类名访问cout << "类名访问son中b = " << son::b << endl;cout << "类名访问father中b = " << father::b << endl;cout << "类名访问father中b = " << son::father::b << endl;}

①子类对象访问子类同名成员:直接访问

②子类对象访问父类同名成员:加作用域::

4.6.6 多继承语法

语法class 子类 :继承方式 父类1,继承方式 父类2,...

//eg .用法与上面的大同小异,多继承可能会引发父类中有同名成员出现,需要加作用域区分class father1{public:father1(){a = 10;}int a;};class father2{public:father2(){b = 10;}int b;};class son :public father1, public father2{public:son(){c = 10;d = 20;}int c;int d;};int main(){son s;cout << "sizeof (son)=" << sizeof(son) << endl;cout << "father1中a的值" << s.a << endl;}

🔴注意C++实际开发中不建议用多继承

4.6.7 菱形继承

定义:两个派生类继承同一个基类,又有某个类同时继承者两个派生类。

Q:菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义

✅:虚继承可以解决菱形继承问题

//eg.class animal{public:int age;};//关键字virtual将yang变成虚继承class yang:virtual public animal{};class tuo:virtual public animal{};class YangTuo :public yang, public tuo{};int main(){YangTuo yt;yt.yang::age = 10;yt.tuo::age = 20;//数据只有一份cout << yt.yang::age << endl;//20cout << yt.tuo::age << endl;//20cout << yt.age << endl;//20}

4.7 多态

4.7.1 多态的基本概念

分类

①静态多态:函数重载 和 运算符重载

①动态多态:派生类和虚函数实现运行时多态

🔵特点

静态多态的函数地址早绑定 - 编译阶段确定函数地址动态多态的函数地址晚绑定 - 运行阶段确定函数地址

//egclass animal{public:void breathe(){cout << "动物呼吸" << endl;}//虚函数virtual void speak(){cout << "动物叫" << endl;}};class cat:public animal{public:void breathe(){cout << "喵呼吸" << endl;}//重写void speak(){cout << "miaomiao~" << endl;}};//地址早绑定 在编译阶段确定函数地址void DoSpeak(animal &a){a.speak();}//地址晚绑定 在函数前加 virtual,在运行阶段确定地址void DoBreathe(animal& a){a.breathe();}int main(){cat c;DoSpeak(c);//miaomiao~DoBreathe(c);//动物呼吸}

动态多态满足条件:

①有继承关系

②子类重写父类中的虚函数(子类的virtual可有可无)

动态多态的使用:父类的指针或引用,执行子类对象。

4.7.2 多态原理剖析(图解)

4.7.3 多态案例一:计算器类

案例描述:

分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类

//普通写法 + - * /class calculator{public:int GetResult(string s){if (s == "+")return a + b;else if (s == "-")return a - b;else if (s == "*")return a * b;else if (s == "/")return a / b;}int a;int b;};int main(){calculator c;string s;cout << "输入两个整型数字的+-*/运算:" << endl;;cin >> c.a >>s>> c.b;cout << "=" << c.GetResult(s) << endl;return 0;}

❗局限:扩展新的功能,需要修改源码。

✅:

//多态写法class AbstractCalculator{public:virtual int GetResult(){return 0;}int a;int b;};//加法计算器类class Add :public AbstractCalculator{public:int GetResult(){return a + b;}};//减法计算器类class Sub :public AbstractCalculator{public:int GetResult(){return a - b;}};//乘法计算器类class Mul :public AbstractCalculator{public:int GetResult(){return a * b;}};//除法计算器类class Div:public AbstractCalculator{public:int GetResult(){return a / b;}};void Do(AbstractCalculator& ac2){ac2.GetResult();}int main(){//加法//父类的指针或引用,执行子类对象AbstractCalculator* ac = new Add;ac->a = 10;ac->b = 20;cout << ac->a << "+" << ac->b << "=" << ac->GetResult() << endl;//记得销毁delete ac;//减法ac = new Sub;ac->a = 10;ac->b = 20;cout << ac->a << "-" << ac->b << "=" << ac->GetResult() << endl;//记得销毁delete ac;//乘法除法同理}

多态优势

①组织结构清晰,可读性强。

②对于前期和后期扩展及维护性高。

4.7.4 纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数

纯虚函数定义virtual 返回值类型 函数名 (参数列表)= 0 ;

抽象类定义:当类中有了纯虚函数,这个类也称为抽象类

🔵抽象类的特点

①无法实例化对象

②子类必须重写抽象类中的纯虚函数,否则也属于抽象类

class base{public://纯虚函数virtual void func() = 0;};class son1 :public base{public:};class son2 :public base{public:void func() {}};int main(){//base b;//err 抽象类无法实例化对象//new base;//err //son1 s;//子类必须重写抽象类中的纯虚函数,否则也属于抽象类son2 s;}

4.7.5 多态案例二:制作饮品

案例描述

制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料。

利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶。

class AbstractDrinking{public://煮水virtual void BoilWater() = 0;//冲泡virtual void Brew() = 0 ;//倒入杯中virtual void IntoCup() = 0;//加入辅料virtual void PutSomeing() = 0;//制作饮品void Make(){BoilWater();Brew();IntoCup();PutSomeing();}};class Tea :public AbstractDrinking{//煮水virtual void BoilWater() {cout << "煮500ml水" << endl;}//冲泡virtual void Brew(){cout << "加入10g茶叶" << endl;}//倒入杯中virtual void IntoCup(){cout<<"倒入杯子里"<<endl;}//加入辅料virtual void PutSomeing() {cout << "加入柠檬" << endl;}};class Coffee :public AbstractDrinking{//煮水virtual void BoilWater(){cout << "煮500ml水" << endl;}//冲泡virtual void Brew(){cout << "加入一勺咖啡" << endl;}//倒入杯中virtual void IntoCup(){cout << "倒入杯子里" << endl;}//加入辅料virtual void PutSomeing(){cout << "加入牛奶" << endl;}};void DoMake(AbstractDrinking* ad){ad->Make();delete ad;//记得销毁}int main(){//做一杯茶DoMake(new Tea);//做一杯咖啡DoMake(new Coffee);}

4.7.6 虚析构函数和抽象类

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,将父类中的析构函数改为虚析构或者纯虚析构就可以解决

虚析构语法virtual ~类名(){}

纯虚析构语法virtual ~类名() = 0;类名::~类名(){}

class Animal{public:Animal(){cout << "Animal构造函数的调用" << endl;}~Animal(){cout << "Animal析构函数的调用" << endl;}//虚析构可以解决 父类指针释放子类对象时不干净的问题//virtual ~Animal()//{//cout << "Animal虚析构函数的调用" << endl;//}//纯虚析构//virtual ~Animal() = 0;virtual void speak() = 0;};//纯虚析构函数要有具体的函数实现//Animal::~Animal()//{//cout << "Animal纯虚析构函数的调用" << endl;//}class Cat:public Animal{public:Cat(string n){cout << "Cat的构造函数的调用" << endl;name = new string(n);}void speak(){cout << *name<<"miao~" << endl;}~Cat(){cout << "Cat的析构函数的调用" << endl;if (name != NULL){delete name;name = NULL;}}string *name;};int main(){Animal* a = new Cat("mimi");a->speak();//父类指针在析构时候,不会调用子类中的析构函数,导致子类如果有堆区的属性,会出现内存的泄露delete a;}

虚析构和纯虚析构共性:

①可以解决父类指针释放子类对象

②都需要有具体的函数实现

🔴注意:纯虚析构,该类属于抽象类,无法实例化对象

4.7.7 多态案例三:电脑组装

案例描述

电脑主要组成部件为 CPU(用于计算),显卡(用于显示),内存条(用于存储)

将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商

创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口

测试时组装三台不同的电脑进行工作

class CPU{public:virtual void calulate() = 0;};class VideoCard{public:virtual void display() = 0;};class Memory{public:virtual void storage() = 0;};//intel 厂商class IntelCPU :public CPU{void calulate(){cout << "Intel的CPU工作啦!" << endl;}};class IntelVideoCard :public VideoCard{void display(){cout << "Intel的VideoCard工作啦!" << endl;}};class IntelMemory :public Memory{void storage(){cout << "Intel的Memory工作啦!" << endl;}};//XiaoMi厂商class XiaoMiCPU :public CPU{void calulate(){cout << "XiaoMi的CPU工作啦!" << endl;}};class XiaoMiVideoCard :public VideoCard{void display(){cout << "XiaoMi的VideoCard工作啦!" << endl;}};class XiaoMiMemory :public Memory{void storage(){cout << "XiaoMi的Memory工作啦!" << endl;}};class Computer{public:Computer(CPU* cpu, VideoCard* videpcard, Memory* memory){this->cpu = cpu;this->videpcard = videpcard;this->memory = memory;}void DoWork(){cpu->calulate();videpcard->display();memory->storage();}~Computer(){if (cpu!= NULL){delete cpu;cpu = NULL;}if (videpcard!= NULL){delete videpcard;videpcard = NULL;}if (memory!= NULL){delete memory;memory = NULL;}}private:CPU* cpu;VideoCard* videpcard;Memory* memory;};int main(){CPU* intel_cpu = new IntelCPU;VideoCard* intel_vc = new IntelVideoCard;Memory* intel_m = new IntelMemory;Computer c1(intel_cpu, intel_vc, intel_m);c1.DoWork();CPU* xiaomi_cpu = new XiaoMiCPU;VideoCard* xiaomi_vc = new XiaoMiVideoCard;Memory* xiaomi_m = new XiaoMiMemory;Computer c2(xiaomi_cpu, xiaomi_vc, xiaomi_m);c2.DoWork();}

五、文件

5.1 文本文件

程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放,通过文件可以将数据持久化

C++中对文件操作需要包含头文件< fstream >

文件类型分为两种:

文本文件- 文件以文本的ASCII码形式存储在计算机中

二进制文件- 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们

操作文件的三大类:

①ofstream:写操作

②ifstream: 读操作

③fstream : 读写操作

5.1.1 写文件

写文件步骤如下

包含头文件:#include <fstream>创建流对象:ofstream ofs;打开文件:ofs.open(“文件路径”,打开方式);写数据:ofs << "写入的数据";关闭文件:ofs.close();

文件打开方式

🔴注意:文件打开方式可以配合使用,利用|操作符。

//eg.#include <fstream>int main(){//1.头文件//2.创建流对象ofstream ofs;//3.打开文件,方式为:为写ofs.open("test.txt", ios::out);//4.写数据ofs << "姓名:张三" << endl;ofs << "性别:男" << endl;ofs << "年龄:18" << endl;//5.关闭文件ofs.close();return 0;}

5.1.2 读文件

读文件步骤如下

包含头文件:#include <fstream>创建流对象:ifstream ifs;打开文件并判断文件是否打开成功:ifs.open(“文件路径”,打开方式);读数据: 四种方式读取关闭文件:ifs.close();

//eg.#include <fstream>#include <string>void test(){//1.包含头文件//2.创建流对象ifstream ifs;//3.打开文件并判断文件是否打开成功ifs.open("test.txt", ios::in);if (!ifs.is_open()){cout << "文件打开失败" << endl;return;}//4.读数据//第一种方式char buf[1024] = {0 };while (ifs >> buf){cout << buf << endl;}//第二种char buf[1024] = {0 };while (ifs.getline(buf,sizeof(buf))){cout << buf << endl;}//第三种string buf;while (getline(ifs, buf)){cout << buf << endl;}//第四种char c;while ((c = ifs.get()) != EOF)//end of file{cout << c;}//5.关闭文件ifs.close();}int main() {test();return 0;}

5.2 二进制文件

以二进制的方式对文件进行读写操作打开方式要指定为ios::binary

5.2.1 写文件

二进制方式写文件主要利用流对象调用成员函数write

函数原型ostream& write(const char * buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

#include <fstream>#include <string>class Person{public:char m_Name[64];int m_Age;};void test01(){//1、包含头文件//2、创建输出流对象ofstream ofs("person.txt", ios::out | ios::binary);//3、打开文件//ofs.open("person.txt", ios::out | ios::binary);Person p = {"张三" , 18 };//4、写文件ofs.write((const char*)&p, sizeof(p));//5、关闭文件ofs.close();}int main(){test01();return 0;}

5.2.2 读文件

二进制方式读文件主要利用流对象调用成员函数read

函数原型istream& read(char *buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

#include <fstream>#include <string>class Person{public:char m_Name[64];int m_Age;};void test01(){//1.包含头文件//2.创建流对象//3.打开文件并判断文件是否打开成功ifstream ifs("person.txt", ios::in | ios::binary);if (!ifs.is_open()){cout << "文件打开失败" << endl;return;}//4.读文件(读到的是正常的不是乱码)Person p;ifs.read((char*)&p, sizeof(p));cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;//5.关闭文件ifs.close();}int main(){test01();return 0;}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。