4.1.2 struct和class区别
在C++中struct和class主要的区别就在于默认的访问权限不同。具体详细区别可自行百度。
区别:
- struct默认权限为公共
- class默认权限为私有
#include<iostream> using namespace std; class C1 { int m_B; //默认权限 私有 public: int m_A; }; struct C2 { int m_C; //默认权限 公共 }; int main() { //struct 和 class 区别 //struct 默认权限是 公共 public //class 默认权限是 私有 private C2 aa; //struct默认权限为公共,可以访问 aa.m_C = 1; C1 bb; bb.m_A = 123; //bb.m_B = 456; //报错 cout << "aa.m_C= " << aa.m_C << endl; cout << "bb.m_A= " << bb.m_A << endl; system("pause"); return 0; }
4.1.3 成员属性设置为私有
优点1:将所有成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性
#include<iostream> using namespace std; #include<string> //成员属性设置为私有 //1、可以自己控制读写权限 //2、对于写可以检测数据的有效性 //设计人类 class Person { private: //姓名 可读可写权限 string m_Name; //年龄 只读 int m_Age; //情人 只写 string m_Lover; public: //设置姓名 void setName(string name) { m_Name = name; } //获取姓名 string getName() { return m_Name; } //获取年龄 int getAge() { return m_Age; } //设置情人 只写 void setLover(string lover) { m_Lover = lover; } //设置年龄 void setAge(int age) { if (age < 0 || age>150) { m_Age = 0; cout << "输入的年龄有误!" << endl; return; } m_Age = age; } }; int main() { Person p1; p1.setName("张三"); p1.setLover("aaa"); p1.setAge(1000); cout << "姓名:" << p1.getName() << endl; cout << "年龄:" << p1.getAge() << endl; cout << "重新输入年龄后:" << endl; p1.setAge(10); cout << "年龄:" << p1.getAge() << endl; system("pause"); return 0; }
练习案例1:设计立方体类
1、设计立方体类(Cube)
2、求出立方体的面积和体积
3、分别用全局函数和成员函数判断两个立方体是否相等
#include<iostream> using namespace std; //设计立方体案例 //1、创建立方体类 //2、设计属性 //3、设计行为 获取立方体面积和体积 //4、分别用全局函数和成员函数 判断两个立方体是否相等 class Cube { public: //设置长 void setL(int l) { m_L = l; } //获取长 int getL() { return m_L; } //设置宽 void setW(int w) { m_W = w; } //获取宽 int getW() { return m_W; } //设置高 void setH(int h) { m_H = h; } //获取高 int getH() { return m_H; } //获取立方体面积 int calculateS() { return 2 * m_L * m_W + 2 * m_L * m_H + 2 * m_H * m_W; } //获取立方体体积 int calculateV() { return m_L * m_H * m_W; } //利用成员函数判断两个立方体是否相等 bool isSameByClass(Cube &c) //只需传入一个参数 { if (m_L == c.getL() && m_H == c.getH() && m_W == c.getW()) { return true; } return false; } private: int m_L; //长 int m_H; //高 int m_W; //宽 }; //利用全局函数判断 两个立方体是否相等 bool isSame(Cube& c1, Cube& c2) { if (c1.getL() == c2.getL() && c1.getH() == c2.getH() && c1.getW() == c2.getW()) { return true; } return false; } int main() { //创建立方体对象 Cube c1; c1.setL(10); c1.setW(10); c1.setH(10); cout << "面积:" << c1.calculateS() << endl; cout << "体积:" << c1.calculateV() << endl; //创建第二个立方体 Cube c2; c2.setL(10); c2.setW(10); c2.setH(11); //利用全局函数判断 bool ret = isSame(c1, c2); if (ret) { cout << "全局函数判断结果:c1和c2相等" << endl; } else { cout<< "全局函数判断结果:c1和c2不相等" << endl; } //成员函数判断结果 ret = c1.isSameByClass(c2); if (ret) { cout << "成员函数判断结果:c1和c2相等" << endl; } else { cout << "成员函数判断结果:c1和c2不相等" << endl; } system("pause"); return 0; }
练习案例2:点和圆的关系
设计一个圆形类(Circle)和一个点类(Point),计算点和圆的关系
circle.h
#pragma once #include<iostream> using namespace std; #include"point.h" //圆类 class Circle { public: //设置半径 void setR(int r); //获取半径 int getR(); //设置圆心 void setCenter(Point center); //获取圆心 Point getCenter(); private: int m_R; //半径 //核心内容:在类中可以让另一个类 作为本类中的成员 Point m_Center; //圆心 };
circle.cpp
#include"circle.h" //圆类 //设置半径 void Circle::setR(int r) { m_R = r; } //获取半径 int Circle::getR() { return m_R; } //设置圆心 void Circle::setCenter(Point center) { m_Center = center; } //获取圆心 Point Circle::getCenter() { return m_Center; }
point.h
#pragma once //防止头文件重复包含 #include<iostream> using namespace std; //点类 //留住函数声明和变量声明 class Point { public: //设置x void setX(int x); //获取x int getX(); //设置y void setY(int y); //获取y int getY(); private: int m_X; int m_Y; };
point.cpp
#include"point.h" //设置x void Point::setX(int x) { m_X = x; } //获取x int Point::getX() { return m_X; } //设置y void Point::setY(int y) { m_Y = y; } //获取y int Point::getY() { return m_Y; }
main.cpp
#include<iostream> using namespace std; #include"circle.h" #include"point.h" //点和圆的关系的案例 点类 //class Point //{ //public: // //设置x // void setX(int x) // { // m_X = x; // } // //获取x // int getX() // { // return m_X; // } // //设置y // void setY(int y) // { // m_Y = y; // } // //获取y // int getY() // { // return m_Y; // } // //private: // int m_X; // int m_Y; //}; // // 圆类 //class Circle //{ //public: // //设置半径 // void setR(int r) // { // m_R = r; // } // //获取半径 // int getR() // { // return m_R; // } // //设置圆心 // void setCenter(Point center) // { // m_Center = center; // } // //获取圆心 // Point getCenter() // { // return m_Center; // } //private: // int m_R; //半径 // // //核心内容:在类中可以让另一个类 作为本类中的成员 // Point m_Center; //圆心 //}; //判断点和圆的关系 void isInCircle(Circle& c, Point& p) { //计算两点之间距离 平方 int distance = (c.getCenter().getX() - p.getX())* (c.getCenter().getX() - p.getX()) + (c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY()); //计算半径的平方 int rDistance = c.getR() * c.getR(); //判断关系 if (distance == rDistance) { cout << "点在圆上" << endl; } else if (distance > rDistance) { cout << "点在圆外" << endl; } else { cout << "点在圆内" << endl; } } int main() { //创建圆 Circle c; c.setR(10); Point center; center.setX(10); center.setY(0); c.setCenter(center); //创建点 Point p; p.setX(10); p.setY(11); //判断关系 isInCircle(c, p); system("pause"); return 0; }
4.2 对象的初始化和清理
- 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用时候也会删除一些自己信息数据保证安全
- C++中的面向对象来源于生活,每个对象也都会有初始设置以及 对象销毁前的清理数据的设置
4.2.1 构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题
- 一个对象或者变量没有初始状态,对其使用后果是未知
- 同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供。编译器提供的构造函数和析构函数是空实现。
- 构造函数:主要作用在于创建对象时对对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用
- 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作
构造函数语法:类名() {}
- 1、构造函数,没有返回值也不写void
- 2、函数名称与类名相同
- 3、构造函数可以有参数,因此可以发生重载
- 4、程序在调用对象时候会主动调用构造,无须手动调用,而且只会调用一次
析构函数语法:~类名(){}
- 1、析构函数,没有返回值也不写void
- 2、函数名称与类名相同,在名称前加上符号**~**
- 3、析构函数不可以有参数,因此不可以发生重载
- 4、程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
#include <iostream> using namespace std; //对象的初始化和清理 //1、构造函数 进行初始化操作 class Person { public: //1、构造函数 //没有返回值 不用写void //函数名 与类名相同 //构造函数可以有参数,可以发生重载 //创建对象的时候,构造函数会自动调用,而且只调用一次 Person() { //无参构造 cout << "Person构造函数的调用" << endl; } //2、析构函数 进行清理的操作 //没有返回值 不写void //函数名和类名相同 在名称前加~ //析构函数不可以有参数,不可以发生重载 //对象在销毁前 会自动调用析构函数,而且只会调用一次 ~Person() { cout << "Person析构函数的调用" << endl; } }; //构造和析构都是必须有的实现,如果我们自己不提供,编译器会提供一个空实现的构造和析构 void test01() { Person p; //在栈上的数据,执行完毕后,释放这个对象 } int main() { Person p; //test01(); system("pause"); return 0; }
4.2.2 构造函数的分类及调用
两种分类方式:
- 按参数分为: 有参构造和无参构造
- 按类型分为: 普通构造和拷贝构造
三种调用方式:
- 括号法
- 显示法
- 隐式转换法
#include <iostream> using namespace std; //1、构造函数的分类及调用 //分类 class Person { public: //构造函数:无参、有参; 普通,拷贝 Person() { cout << "Person无参构造函数的调用" << endl; } Person(int a) { age = a; cout << "Person有参构造函数的调用" << endl; } //拷贝构造函数 Person(const Person &p) { //将传入的人身上的所有属性,拷贝到我身上 age = p.age + 20; cout << "Person拷贝构造函数的调用" << endl; } //析构函数 ~Person() { cout<< "Person析构函数的调用" << endl; } int age; }; //调用 void test01() //(1)括号法 { //注意事项1:调用默认构造函数时候,不要加() Person p; //默认构造函数调用 Person p2(10); //有参构造函数调用 Person p3(p2); //调用拷贝构造函数 cout << "p2的年龄为:" << p2.age << endl; cout << "p3的年龄为:" << p3.age << endl; } void test02() //(2)显示法 { Person p1; Person p2 = Person(10); //有参构造 Person p3 = Person(p2); //拷贝构造 Person(10); //匿名对象 特点:当前行执行结束后,系统会立即回收匿名对象 cout << "aaaaaaa" << endl; //注意事项2:不要利用拷贝构造函数 初始化匿名对象 编译器认为是对象声明 //Person(p3); } void test03() //(3)隐式转换法 { Person p4 = 10; //有参构造 相当于写了Person p4 = Person(10) Person p5 = p4; //拷贝构造 } int main() { test03(); system("pause"); return 0; }
4.2.3 拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
#include <iostream> using namespace std; //拷贝构造函数的调用时机 class Person { public: Person() { cout << "Person默认构造函数调用" << endl; } Person(int age) { m_Age = age; cout << "Person有参构造函数调用" << endl; } Person(const Person& p) { m_Age = p.m_Age; cout << "Person拷贝构造函数调用" << endl; } ~Person() { cout << "Person析构函数调用" << endl; } int m_Age; }; //1、使用一个已经创建完毕的对象来初始化一个新对象 void test01() { Person p1(20); Person p2(p1); cout << "p2的年龄:" << p2.m_Age << endl; } //2、值传递的方式给函数参数传值 void doWork(Person p){} void test02() { Person p; doWork(p); } //3、值方式返回局部对象 Person doWork2() { Person p1; cout << (int*)&p1 << endl; return p1; } void test03() { Person p = doWork2(); cout << (int*)&p << endl; } int main() { test03(); system("pause"); return 0; }
Person默认构造函数调用 000000E58CD2FA74 Person拷贝构造函数调用 Person析构函数调用 000000E58CD2FBB4 Person析构函数调用 请按任意键继续. . .
4.2.4 构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
- 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
#include <iostream> using namespace std; //构造函数的调用规则 //1、创建一个类,C++编译器会给每个类都添加至少3个函数:默认函数、析构函数、拷贝函数(值拷贝) //2、如果我们写了有参构造,编译器不再提供无参构造,依然提供拷贝构造 //如果我们写了拷贝构造函数,编译器就不再提供其他普通构造函数了 class Person { public: //Person() { // cout << "Person的无参构造函数的调用" << endl; //} Person(int age) { cout << "Person的有参构造函数的调用" << endl; m_Age = age; } //Person(const Person& p) { // cout<< "Person的拷贝构造函数的调用" << endl; // m_Age = p.m_Age; //} ~Person() { cout << "Person的析构函数的调用" << endl; } int m_Age; }; //void test01() { // Person p; // p.m_Age = 18; // // Person p2(p); // cout << "p2的年龄:" << p2.m_Age << endl; //} void test02() { Person p(18); Person p2(p); } int main() { test02(); system("pause"); return 0; }
4.2.5 深拷贝与浅拷贝
深浅拷贝是面试经典问题,也是常见的一个坑
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
#include <iostream> using namespace std; //深拷贝与浅拷贝 class Person { public: Person() { cout << "Person的无参构造函数调用" << endl; } Person(int age, int height) { m_Age = age; m_Height = new int(height); //堆区 手动开辟手动释放 cout << "Person的有参构造函数调用" << endl; } //自己实现拷贝构造函数 解决浅拷贝带来的问题 Person(const Person& p) { cout << "Person 拷贝构造函数调用" << endl; m_Age = p.m_Age; //m_Height = p.m_Height; //编译器默认实现就是这行代码,等号复制,即浅拷贝 //深拷贝操作 m_Height = new int(*p.m_Height); } ~Person() { //析构代码,将堆区开辟数据做释放操作 if (m_Height != NULL) { delete m_Height; m_Height = NULL; } cout << "Person的析构函数调用" << endl; } int m_Age; int* m_Height; }; void test01() { Person p1(18, 160); cout << "p1的年龄:" << p1.m_Age << ";身高为:" <<*p1.m_Height << endl; Person p2(p1); cout << "p2的年龄:" << p2.m_Age << ";身高为:" << *p2.m_Height << endl; } int main() { test01(); system("pause"); return 0; }
Person的有参构造函数调用 p1的年龄:18;身高为:160 Person 拷贝构造函数调用 p2的年龄:18;身高为:160 Person的析构函数调用 Person的析构函数调用 请按任意键继续. . .
4.2.6 初始化列表
作用:C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)... {}
#include<iostream> using namespace std; //初始化列表 class Person { public: //传统初始化操作 //Person(int a, int b, int c) { // m_A = a; // m_B = b; // m_C = c; //} //列表初始化属性 Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) { } int m_A; int m_B; int m_C; }; void test01() { Person p(10, 20, 30); cout << "m_A=" << p.m_A << endl; cout << "m_B=" << p.m_B << endl; cout << "m_C=" << p.m_C << endl; } int main() { test01(); system("pause"); return 0; }
4.2.7 类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员
例如:
class A {} class B { A a; }
B类中有对象A作为成员,A为对象成员
那么当创建B对象时,A与B的构造和析构的顺序是谁先谁后?
#include<iostream> using namespace std; #include<string> //类对象作为类成员 class Phone { public: Phone(string pName) { m_PNmae = pName; cout << "Phone构造函数调用" << endl; } string m_PNmae; ~Phone() { cout << "Phone析构函数调用" << endl; } }; class Person { public: Person(string name, string pName):m_Name(name),m_Phone(pName) { cout << "Person构造函数调用" << endl; } //姓名 string m_Name; //手机 Phone m_Phone; ~Person() { cout << "Person析构函数调用" << endl; } }; void test01() { Person p("张三", "苹果MAX"); cout << "name:" << p.m_Name << " Phone:" << p.m_Phone.m_PNmae << endl; } //先构造类对象,再构造本身;析构则相反。 int main() { test01(); system("pause"); return 0; }
Phone构造函数调用 Person构造函数调用 name:张三 Phone:苹果MAX Person析构函数调用 Phone析构函数调用 请按任意键继续. . .
4.2.8 静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
- 静态成员变量:所有对象共享同一份数据,在编译阶段分配内存,类内声明,类外初始化(类内初始化会报错)
- 静态成员函数:所有对象共享同一个函数,静态成员函数只能访问静态成员变量
#include<iostream> using namespace std; //静态成员函数 //所有对象共享一个函数 //静态成员函数只能访问静态成员变量 class Person { public: static void func() { m_A = 100; //静态成员函数 可以访问 静态成员变量 //m_B = 200; //静态成员函数 不可以访问 非静态成员变量 cout << "static void func调用" << endl; } static int m_A; //静态成员变量 //static int m_A=1; //报错 int m_B; private: static void func2() { cout << "static void func2调用" << endl; } }; int Person::m_A = 0; //类外初始化 void test01() { //1、通过对象访问 Person p; p.func(); cout << p.m_A << endl; //2、通过类名访问 Person::func(); //Person::func2(); //类外访问不到私有静态成员函数 } int main() { test01(); system("pause"); return 0; }
static void func调用 100 static void func调用 请按任意键继续. . .
4.3 C++对象模型和this指针
4.3.1 成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
#include<iostream> using namespace std; //成员变量 和 成员函数 分开存储的 class Person { int m_A; //非静态成员变量 属于类的对象上 static int m_B; //静态成员变量 不属于类对象上 void func(){} //非静态成员函数 不属于类对象上 static void func2(){} //静态成员函数 不属于类对象上 }; int Person::m_B=0; void test01() { Person p; //空对象占用内存空间为:1 //C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置 //每个空对象也应该有一个独一无二的内存地址 cout << "sizeof(p):" << sizeof(p) << endl; //Person类里面无内容:1字节 } void test02() { Person p; cout << "sizeof(p):" << sizeof(p) << endl; //Person类里面有int m_A:4字节 } int main() { test02(); system("pause"); return 0; }
4.3.2 this指针概念
通过4.3.1我们知道在C++中成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
那么问题是:这一块代码是如何区分那个对象调用自己的呢?
c++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针。this指针不需要定义,直接使用即可
this指针的用途:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用
return *this
#include<iostream> using namespace std; class Person { public: Person(int age) { this->age = age; } Person& PersonAddAge(Person& p) { this->age += p.age; //this指向p2的指针,而*this指向的就是p2这个对象的本体 return *this; } int age; }; //1、解决名称冲突 void test01() { Person p1(18); cout << "p1的age:" << p1.age << endl; } //2、返回对象本身用*this void test02() { Person p1(10); Person p2(10); //链式编程思想 p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1); cout << "p2的age:" << p2.age << endl; } int main() { test02(); system("pause"); return 0; }
4.3.3 空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
#include<iostream> using namespace std; //空指针调用成员函数 class Person { public: void showClassName() { cout << "this is Person class" << endl; } void showPersonAge() { //报错, 原因是传入的指针是为NULL if (this == NULL) { return; } cout << "age=" << m_Age << endl; } int m_Age; }; void test01() { Person* p = NULL; p->showClassName(); p->showPersonAge(); } int main() { test01(); system("pause"); return 0; }
this is Person class 请按任意键继续. . .
4.3.4 const修饰成员函数
常函数:
- 成员函数后加const后我们称为这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
#include<iostream> using namespace std; //常函数 class Person { public: //this指针的本质 是指针常量 指针的指向是不可以修改的 //在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改 void showPerson() const { //this->m_A = 100; this->m_B = 10; cout << "m_B:" << this->m_B << endl; //this = NULL; //this指针不可以修改指针的指向的 } void func() {} int m_A; mutable int m_B; //特殊变量,即使在常函数中,也可以修改这个值 }; void test01() { Person p; p.showPerson(); } //常对象 void test02() { const Person p; //在对象前加const,变为常对象 //p.m_A = 100; p.m_B = 100; //m_B是特殊值,在常对象下也可以修改 cout << "now,m_B:" << p.m_B << endl; //常对象只能调用常函数 p.showPerson(); //p.func(); //常对象 不可以调用普通成员函数,因为普通成员函数可以修改属性 } int main() { test02(); system("pause"); return 0; }
now,m_B:100 m_B:10 请按任意键继续. . .
4.4 友元
生活中你的家有客厅(Public),有你的卧室(Private)
客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去
但是呢,你也可以允许你的好闺蜜好基友进去。
在程序里,有些私有属性 也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的目的就是让一个函数或者类 访问另一个类中私有成员
友元的关键字为 friend
友元的三种实现:
- 全局函数做友元
- 类做友元
- 成员函数做友元
4.4.1 全局函数做友元
#include <iostream> using namespace std; #include <string> class Building //建筑物类 { //goodGay全局函数是 Building好朋友,可以访问Building中私有成员 friend void goodGay(Building* building); public: Building() { m_SittingRoom = "客厅"; m_BedRoom = "卧室"; } string m_SittingRoom; //客厅 private: string m_BedRoom; //卧室 }; //全局函数 void goodGay(Building *building){ cout << "好基友的全局函数正在访问:" << building->m_SittingRoom << endl; cout << "好基友的全局函数正在访问:" << building->m_BedRoom << endl; } void test01() { Building building; goodGay(&building); } int main() { test01(); system("pause"); return 0; }
好基友的全局函数正在访问:客厅 好基友的全局函数正在访问:卧室 请按任意键继续. . .
4.4.2 类做友元
#include<iostream> using namespace std; //类做友元 class Building { friend class GoodGay; public: Building(); //构造函数 string m_SittingRoom; //客厅 private: string m_BedRoom; //卧室 }; //类外写成员函数 Building::Building() { m_SittingRoom = "客厅"; m_BedRoom = "卧室"; } class GoodGay { public: GoodGay(); void visit(); //参观函数 访问Building中的属性 Building* building; }; GoodGay::GoodGay(){ //创建一个建筑物的对象 building = new Building; } void GoodGay::visit() { cout << "好基友类正在访问:" << building->m_SittingRoom << endl; cout << "好基友类正在访问:" << building->m_BedRoom << endl; } void test01() { GoodGay gg; gg.visit(); } int main() { test01(); system("pause"); return 0; }
4.4.3 成员函数做友元
#include<iostream> using namespace std; class Building; class goodGay { public: goodGay(); void visit(); //只让visit函数作为Building的好朋友,可以发访问Building中私有内容 void visit2(); private: Building* building; }; class Building { //告诉编译器 goodGay类中的visit成员函数 是Building好朋友,可以访问私有内容 friend void goodGay::visit(); public: Building(); public: string m_SittingRoom; //客厅 private: string m_BedRoom;//卧室 }; Building::Building() { this->m_SittingRoom = "客厅"; this->m_BedRoom = "卧室"; } goodGay::goodGay() { building = new Building; } void goodGay::visit() { cout << "好基友正在访问" << building->m_SittingRoom << endl; cout << "好基友正在访问" << building->m_BedRoom << endl; } void goodGay::visit2() { cout << "好基友正在访问" << building->m_SittingRoom << endl; //cout << "好基友正在访问" << building->m_BedRoom << endl; } void test01() { goodGay gg; gg.visit(); } int main() { test01(); system("pause"); return 0; }
4.5 运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
4.5.1 加号运算符重载
作用:实现两个自定义数据类型相加的运算
#include<iostream> using namespace std; //1、加号运算符重载 class Person { public: //1、成员函数重载+号 //Person operator+(Person& p) { // Person temp; // temp.m_A = this->m_A + p.m_A; // temp.m_B = this->m_B + p.m_B; // cout << "成员函数重载+号" << endl; // return temp; //} int m_A; int m_B; }; //2、全局函数重载+号 Person operator+(Person& p1, Person& p2) { Person temp; temp.m_A = p1.m_A + p2.m_A; temp.m_B = p1.m_B + p2.m_B; cout << "全局函数重载+号" << endl; return temp; } //函数重载的版本 Person operator+(Person& p1, int num) { Person temp; temp.m_A = p1.m_A + num; temp.m_B = p1.m_B + num; return temp; } void test01() { Person p1; p1.m_A = 10; p1.m_B = 10; Person p2; p2.m_A = 10; p2.m_B = 10; //Person p3 = p1.operator+(p2); //成员函数本质调用 Person p3 = operator+(p1, p2); //Person p3 = p1 + p2; //运算符重载 也可以发生函数重载 Person p4 = p1 + 100; //Person+int cout << "p3.m_A=" << p3.m_A << endl; cout << "p3.m_B=" << p3.m_B << endl; cout << "p4.m_A=" << p4.m_A << endl; cout << "p4.m_B=" << p4.m_B << endl; } int main() { test01(); system("pause"); return 0; }
总结1:对于内置的数据类型的表达式的的运算符是不可能改变的
总结2:不要滥用运算符重载
4.5.2 左移运算符重载
作用:可以输出自定义数据类型
#include<iostream> using namespace std; //左移运算符重载 class Person { friend ostream& operator<<(ostream& cout, Person p); public: Person(int a, int b) { m_A = a; m_B = b; } private: //利用成员函数重载 左移运算符 p.operator<<(cout) 简化版本 p<<cout //不会利用成员函数重载<<运算符,因为无法实现cout在左侧 //void operator<<(cout) { // //} int m_A; int m_B; }; //只能利用全局函数重载左移运算符 ostream& operator<<(ostream &cout, Person p) { //本质 operator<<(cout, p ) 简化 cout<<p cout << "m_A=" << p.m_A << " m_B=" << p.m_B; return cout; } void test01() { Person p(10, 10); cout << p << endl;; } int main() { test01(); system("pause"); return 0; }
总结:重载左移运算符配合友元可以实现输出自定义数据类型
4.5.3 递增运算符重载
作用: 通过重载递增运算符,实现自己的整型数据
#include<iostream> using namespace std; //重载递增运算符 //自定义整型 class MyInteger { friend ostream& operator<<(ostream& cout, MyInteger myint); public: MyInteger() { m_Num = 0; } //重载++运算符:前置递增、后置递增 //(1)重载前置运算符 返回引用为了一直对一个数据进行递增操作 MyInteger& operator++() { m_Num++; return *this; } //(2)后置递增 返回值 //void operator++(int) int代表占位参数,可以用于区分前置和后置递增 MyInteger operator++(int) { //先 记录当时结果 MyInteger temp = *this; //后 递增 m_Num++; //最后将记录结果做返回 return temp; } private: int m_Num; }; //重载左移运算符 ostream& operator<<(ostream& cout, MyInteger myint) { cout << myint.m_Num; return cout; } void test01() { MyInteger myint; cout << ++(++myint) << endl; cout << myint << endl; } void test02() { MyInteger myint; cout << myint++ << endl; cout << myint << endl; } int main() { test02(); system("pause"); return 0; }
总结: 前置递增返回引用,后置递增返回值
4.5.4 赋值运算符重载
c++编译器至少给一个类添加4个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符 operator=, 对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
#include<iostream> using namespace std; //复制运算符重载 class Person { public: Person(int age) { m_Age = new int(age); } ~Person() { //析构函数释放资源 if (m_Age != NULL) { delete m_Age; m_Age = NULL; } } //重载 复制运算符 Person& operator=(Person& p) { //编译器是提供浅拷贝 //m_Age = p.m_Age; //应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝 if (m_Age != NULL) { delete m_Age; m_Age = NULL; } //深拷贝操作 m_Age = new int(*p.m_Age); //返回对象本身 return *this; } int* m_Age; }; void test01() { Person p1(18); Person p2(20); Person p3(30); p3 = p2 = p1; //复制操作 cout << "p1的年龄:" << *p1.m_Age << endl; cout << "p2的年龄:" << *p2.m_Age << endl; cout << "p3的年龄:" << *p3.m_Age << endl; } int main() { test01(); //int a = 10; //int b = 20; //int c = 30; //c = b = a; //cout << a << b << c << endl; system("pause"); return 0; }
4.5.5 关系运算符重载
作用:重载关系运算符,可以让两个自定义类型对象进行对比操作
#include<iostream> using namespace std; //重载关系运算符 class Person { public: Person(string name, int age) { m_Name = name; m_Age = age; } //重载==号 bool operator==(Person &p) { if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) { return true; } return false; } bool operator!=(Person& p) { if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) { return false; } return true; } string m_Name; int m_Age; }; void test01() { Person p1("Tom", 18); Person p2("Tom", 19); if (p1 == p2) { cout << "p1和p2是相等的" << endl; } else { cout << "p1和p2是不相等的" << endl; } if (p1 != p2) { cout << "p1和p2是不相等的" << endl; } else { cout << "p1和p2是相等的" << endl; } } int main() { test01(); system("pause"); return 0; }
4.5.6 函数调用运算符重载
- 函数调用运算符 () 也可以重载
- 由于重载后使用的方式非常像函数的调用,因此称为仿函数
- 仿函数没有固定写法,非常灵活
#include<iostream> using namespace std; //函数调用运算符重载 class MyPrint { public: //重载函数调用运算符 void operator()(string test) { cout << test << endl; } }; void MyPrint02(string test) { cout << test << endl; } void test01() { MyPrint myPrint; myPrint("hello world"); //由于使用起来非常类似于函数调用,因此称为仿函数 MyPrint02("hello world"); } //仿函数非常灵活,没有固定的写法 //加法类 class MyAdd { public: int operator()(int num1, int num2) { return num1 + num2; } }; void test02() { MyAdd myadd; int result = myadd(100, 100); cout << "result:" << result << endl; //匿名函数对象 cout << MyAdd()(100, 100) << endl; } int main() { test01(); test02(); system("pause"); return 0; }
hello world hello world result:200 200 请按任意键继续. . .
4.6 继承
继承是面向对象三大特性之一
有些类与类之间存在特殊的关系,例如下图中:
我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。
这个时候我们就可以考虑利用继承的技术,减少重复代码
4.6.1 继承的基本语法
例如我们看到很多网站中,都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同
接下来我们分别利用普通写法和继承的写法来实现网页中的内容,看一下继承存在的意义以及好处
#include<iostream> using namespace std; //公共页面类 class BasePage { public: void header() { cout << "首页、公开课、登录、注册...(公共头部)" << endl; } void footer() { cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl; } void left() { cout << "Java、Python、C++...(公共分类列表)" << endl; } }; class Java: public BasePage { //Java页面 public: void content() { cout << "Java学科视频" << endl; } }; class Python:public BasePage { //Python页面 public: void content() { cout << "Python学科视频" << endl; } }; void test01() { Java ja; ja.header(); ja.footer(); ja.left(); ja.content(); cout << "---------------------" << endl; Python python; python.header(); python.footer(); python.left(); python.content(); } //继承的好处:减少重复代码 //语法:class子类 : 继承方式 父类 //子类 也称为 派生类; 父类 也称为 基类 int main() { test01(); system("pause"); return 0; }
总结:继承的好处:可以减少重复的代码。class A : public B;
A 类称为子类 或 派生类
B 类称为父类 或 基类
派生类中的成员,包含两大部分:
一类是从基类继承过来的,一类是自己增加的成员。
从基类继承过过来的表现其共性,而新增的成员体现了其个性。
4.6.2 继承方式
继承的语法:class 子类 : 继承方式 父类
继承方式一共有三种:
- 公共继承
- 保护继承
- 私有继承
#include<iostream> using namespace std; //继承方式:三种 //公共继承 class Base1 { public: int m_A; protected: int m_B; private: int m_C; }; class Son1 :public Base1 { public: void func() { m_A = 10; //公共权限 m_B = 20; //保护权限 //m_C = 30; } }; //保护继承 class Son2 :protected Base1 { public: void func() { m_A = 10; //现在是子类的保护成员 m_B = 20; //m_C = 30; } }; //私有继承 class Son3 : private Base1 { public: void func() { m_A = 10; //现在是子类的私有成员 m_B = 20; //m_C = 30; } }; class GrandSon3 : public Son3 { public: void func() { //m_A = 100; //m_A是父类私有,子类不可以访问 } }; void test01() { Son1 s1; s1.m_A = 100; //s1.m_B = 100; Son2 s2; //s2.m_A = 100; //保护权限类外不可以访问 Son3 s3; //s3.m_A = 1000; //私有权限类外不可以访问 } int main() { test01(); system("pause"); return 0; }