赞
踩
例如:
封装意义一:在设计类的时候,属性和行为写在一起,表现事物
语法:class 类名{访问权限: 属性 / 行为};
类中的属性和行为 我们统一称为成员
- #include <iostream>
- using namespace std;
- // 圆周率
- const double PI = 3.1415926;
- // 设计一个圆类,求圆的周长
- // 圆求周长的公式 : 2 * PI * 半径
- class Circle {
- // 访问权限
- // 公共权限
- public:
- // 行为
- // 获取圆的周长
- double calculatePerimeter(double radius) {
- return 2 * PI * radius;
- }
- // 属性:半径
- int m_radius;
- };
- int main() {
- // 通过圆类,创建具体的圆(对象)
- // 实例化(通过一个类 创建一个对象的过程)
- Circle c1;
- c1.m_radius = 10;
- cout<<"圆的周长为 : "<<c1.calculatePerimeter(c1.m_radius)<<""<<endl;
- return 0;
- }
- #include <iostream>
- using namespace std;
- #include <string>
- // 设计一个学生类,属性有姓名和学号
- // 可以给姓名和学号赋值,可以显示学生的姓名和学号
-
- // 设计学生类
- class Student{
- public://公共权限
- // 类中的属性和行为 我们统一称为成员
- // 属性 -> 成员属性/成员变量
- // 行为 -> 成员函数/成员方法
- string m_name; // 姓名
- int m_Id; // 学号
-
- // 行为
- void setName(string name){ // 设置姓名
- m_name = name;
- }
- void setId(int id){ // 设置学号
- m_Id = id;
- }
- void display(){ // 显示姓名和学号
- cout << "姓名:" << m_name << endl;
- cout << "学号:" << m_Id << endl;
- }
- };
-
- int main() {
- // 创建一个具体学生 实例化对象
- Student s1;
- // 给s1对象 进行属性赋值操作
- s1.m_name = "张三";
- s1.m_Id = 2019001;
- // 显示学生信息
- s1.display();
-
- s1.setName("李四");
- s1.setId(2019002);
- s1.display();
- }
运行结果:
- PS D:\Work\c++\build\bin> ."D:/Work/c++/bin/app.exe"
- 姓名:张三
- 学号:2019001
- 姓名:李四
- 学号:2019002
- PS D:\Work\c++\build\bin>
封装意义二:
类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
- #include <iostream>
- using namespace std;
-
- /*
- 三种访问权限:
- 公共权限 public 成员 类内可以访问 类外可以访问
- 保护权限 protected 成员 类内可以访问 类外不可以访问(儿子可以访问父亲中的保护内容)
- 私有权限 private 成员 类内可以访问 类外不可以访问(儿子不可以访问父亲中的私有内容)
- */
-
- class Person{
- public:
- void func() {
- m_Name = "张三";//公共权限
- m_Car = "拖拉机";//保护权限
- m_Password = 123456;//私有权限
- }
- public:
- // 公共权限
- string m_Name;//姓名
- protected:
- // 保护权限
- string m_Car;//汽车
- private:
- // 私有权限
- int m_Password;//密码
- };
-
- int main() {
- // 实例化具体对象
- Person p1;
- p1.m_Name = "呵呵哒";//类外可以访问(public)
- // p1.m_Car = "保时捷";//保护权限内容,在类外访问不到 error:成员"Person::m_Car"不可访问
- // p1.m_Password = 123456;//私有权限内容,在类外访问不到 error:成员"Person::m_Password"不可访问
- p1.func();//类外可以访问(public)
- return 0;
- }
区别:
- #include <iostream>
- using namespace std;
-
- class C1{
- int m_A;// 默认权限 是私有
- };
-
- struct C2{
- int m_A;// 默认权限 是公共
- };
-
- int main() {
- /*
- 在C++中struct和class唯一的区别就是默认的访问权限不同
- 区别:
- struct默认权限是公共权限
- class默认权限是私有权限
- */
-
- C1 c1;
- // c1.m_A = 10;// error:成员"C1::m_A"不可访问
- C2 c2;
- c2.m_A = 100;// ok
- return 0;
- }
演示:控制读写权限
- #include <iostream>
- using namespace std;
- /*
- 成员属性设置私有
- 优点1:将所有成员属性设置为私有,可以自己控制读写权限
- 优点2:对于写权限,我们可以检测到数据的有效性
- */
- // 人类
- class Person{
- public:
- // 设置姓名
- void setName(string name) {
- m_Name = name;
- }
- // 获取姓名
- string getName() const {// 返回类型为const,表示返回的是常量,不可修改返回值。
- return m_Name;// 返回m_Name的值。
- }
- // 获取年龄
- int getAge() const {// 返回类型为const,表示返回的是常量,不可修改返回值。
- return m_Age;// 返回m_Age的值。
- }
- // 设置偶像
- void setIdol(string idol) {
- m_Idol = idol;
- }
- private:
- string m_Name;// 姓名 可读可写
- int m_Age = 18;// 年龄 只读
- string m_Idol;// 偶像 只写
- };
-
- int main() {
- Person p;
- // 姓名设置
- p.setName("张三");
- cout<<"姓名:"<<p.getName()<<endl;// 姓名:张三
-
- // 获取年龄
- cout<<"年龄:"<<p.getAge()<<endl;// 输出年龄:18
-
- // 偶像设置
- p.setIdol("迪丽热巴");
- return 0; // 程序执行成功,返回0
- }
演示:检测到数据的有效性,例如年龄设置为0-150之间
- #include <iostream>
- using namespace std;
- /*
- 成员属性设置私有
- 优点1:将所有成员属性设置为私有,可以自己控制读写权限
- 优点2:对于写权限,我们可以检测到数据的有效性
- */
- // 人类
- class Person{
- public:
- // 设置姓名
- void setName(string name) {
- m_Name = name;
- }
- // 获取姓名
- string getName() const {// 返回类型为const,表示返回的是常量,不可修改返回值。
- return m_Name;// 返回m_Name的值。
- }
- // 设置年龄 0-150
- void setAge(int age) {
- if (age < 0 || age > 150) {
- cout << "年龄: "<< age << ",输入错误!" << endl;// 输出错误信息。
- return;
- }
- m_Age = age;
- }
- // 获取年龄
- int getAge() const {// 返回类型为const,表示返回的是常量,不可修改返回值。
- return m_Age;// 返回m_Age的值。
- }
- // 设置偶像
- void setIdol(string idol) {
- m_Idol = idol;
- }
- private:
- string m_Name;// 姓名 可读可写
- int m_Age = 18;// 年龄 只读
- string m_Idol;// 偶像 只写
- };
-
- int main() {
- Person p;
- // 姓名设置
- p.setName("张三");
- cout<<"姓名:"<<p.getName()<<endl;// 姓名:张三
-
- // 获取年龄
- p.setAge(250);
- cout<<"年龄:"<<p.getAge()<<endl;// 输出年龄:18
-
- // 偶像设置
- p.setIdol("迪丽热巴");
- return 0; // 程序执行成功,返回0
- }
练习案例1:设计立方体类
1.类和对象-封装-设计案例1-立方体类
- #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 getArea(){return 2*(m_L*m_W+m_L*m_H+m_W*m_H);}
-
- // 获取立方体体积
- int getVolume(){return m_L*m_W*m_H;}
-
- // 利用成员函数判断两个立方体是否相等
- bool isSameByClass(Cube &c){
- if(getL() == c.getL() && getW() == c.getW() && getH() == c.getH()) {
- return true;
- }else{
- return false;
- }
- }
- private:
- int m_L;//长
- int m_W;//宽
- int m_H;//高
- };
-
- // 利用全局函数判断 两个立方体是否相等
- bool isSame(Cube &c1,Cube &c2) {
- if(c1.getL() == c2.getL() && c1.getW() == c2.getW() && c1.getH() == c2.getH())
- return true; // 判断长宽高是否相等,如果相等则返回true,否则返回false
- else return false;
- }
-
- int main() {
- Cube c1,c2;
- c1.setL(5);
- c1.setW(4);
- c1.setH(3);
- c2.setL(10);
- c2.setW(10);
- c2.setH(10);
- cout<<"c1的面积为:"<<c1.getArea()<<endl;
- cout<<"c1的体积为:"<<c1.getVolume()<<endl;
- cout<<"***************************"<<endl;
- cout<<"c2的面积为:"<<c2.getArea()<<endl;
- cout<<"c2的体积为:"<<c2.getVolume()<<endl;
-
- // 判断c1和c2是否相等
- // 利用全局函数判断
- 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;
- return 0;
- }
2.类和对象-封装-设计案例2-点和圆关系案例
- #include <iostream>
- using namespace std;
-
- // 点和圆关系案例
-
- // 点类
- 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(9);
- // 判断关系
- isInCircle(c,p);
- return 0;
- }
进一步完善项目
- #pragma once
- // 点类
- 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;
- };
- #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;}
- #pragma once
- #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;// 圆心
- };
- #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;}
对象的初始化和清理也是两个非常重要的安全问题
C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现
构造函数语法:类名(){}
析构函数写法:~类名(){}
- #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;// 在栈上的数据,test01执行完毕后,释放这个对象
- }
-
- int main() {
- test01();
- return 0;
- }
执行结果:
- PS D:\Work\c++\bin> .\app
- Person 构造函数的调用
- Person 析构函数的调用
- PS D:\Work\c++\bin>
两种分类方式:
三种调用方式:
- #include <iostream>
- using namespace std;
-
- // 1.构造函数的分类及调用
- // 分类
- // 按照参数分类 无参构造(默认构造)和有参构造
- // 按照类型分类 普通构造 拷贝构造
- 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;
- };
-
- void test01() {
- // 1. (括号法)
- Person p1;// 默认构造调用
- Person p2(10);// 有参构造调用
- Person p3(p2);// 拷贝构造调用
- cout<< "p2的年龄是: "<<p2.m_age<<endl;
- cout<< "p3的年龄是: "<<p3.m_age<<endl;
-
- // 注意事项1
- // 调用默认构造函数时候,不要加()
- // 因为下面这行代码,编译器会认为是一个函数的声明,不会认为在创建对象
- // Person p1();
- // void func();
- }
-
- void test02() {
- // 2.显示法
- Person p1;
- Person p2 = Person(10);// 有参构造
- Person p3 = Person(p2);// 拷贝构造
-
- Person(20);// 匿名对象 特点:当前行执行结束后,系统会立即回收掉匿名对象
- cout<<"asasasa"<<endl;
-
- // 注意事项2
- // 不要利用拷贝构造函数 初始化匿名对象
- // 编译器会认为Person(p3) == Person p3;
- // 会认为这是一个对象的声明
- // Person(p3); // 此时重定义了
- }
-
- void test03() {
- // 3,隐式转换法
- Person p4 = 10;// 相当于 写了 Person p4 = Person(10); 有参构造
- Person p5 = p4;// 拷贝构造
- }
-
- // 调用
- int main() {
- test02();
- return 0;
- }
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;
- private:
-
- };
-
- // 1.使用一个已经创建完毕的对象来初始化一个新对象
- void test01() {
- Person p1(20);
- Person p2(p1);
-
- cout<<"p2: "<<p2.m_Age<<endl;
- /*
- Person有参构造函数调用
- Person拷贝构造函数调用
- p2: 20
- Person析构函数调用
- Person析构函数调用
- */
- }
-
- // 2.值传递的方式给函数参数传值
- void doWork(Person p) {// 值传递会拷贝一个临时的副本出来,在调用它的拷贝构造函数,
-
- }
-
- void test02() {
- Person p;
- doWork(p);
- /*
- Person默认构造函数调用
- Person拷贝构造函数调用
- Person析构函数调用
- Person析构函数调用
- */
- }
-
- // 3.以值方式返回局部对象
- Person doWork2() {
- Person p1;
- cout<<(int*)&p1<<endl;
- return p1;
- }
- void test03() {
- Person p = doWork2();
- cout<<(int*)&p<<endl;
- }
-
- int main() {
- test03();
- return 0;
- }
默认情况下,C++编译器至少给一个类添加3个函数
构造函数调用规则如下:
- #include <iostream>
- using namespace std;
-
- // 构造函数的调用规则
- // 1.创建一个类,C++编译器会给每个类都添加至少3个函数
- // 默认构造(空实现)
- // 析构函数(空实现)
- // 拷贝构造(值拷贝)
-
- // 2.如果我们写了有参构造函数,编译器就不再提供默认构造,依然提供拷贝构造
- // 如果我们写了拷贝构造函数,编译器就不再提供其他普通构造函数了
- 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;
- };
-
- void test01() {
- Person p;
- p.m_Age = 18;
- Person p2(p);
- cout<<"p2的年龄为: "<<p2.m_Age<<endl;
- }
-
-
- int main() {
- test01();
- return 0;
- }
执行结果:
- PS D:\Work\c++\bin> ."D:/Work/c++/bin/app.exe"
- Person的默认构造函数调用
- p2的年龄为: 18
- Person的析构函数调用
- Person的析构函数调用
- #include <iostream>
- using namespace std;
-
- // 构造函数的调用规则
- // 1.创建一个类,C++编译器会给每个类都添加至少3个函数
- // 默认构造(空实现)
- // 析构函数(空实现)
- // 拷贝构造(值拷贝)
-
- // 2.如果我们写了有参构造函数,编译器就不再提供默认构造,依然提供拷贝构造
- // 如果我们写了拷贝构造函数,编译器就不再提供其他普通构造函数了
- 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;
- };
-
- void test01() {
- Person p;
- p.m_Age = 18;
- Person p2(p);
- cout<<"p2的年龄为: "<<p2.m_Age<<endl;
- }
-
- int main() {
- test01();
- return 0;
- }
执行结果:
- Person的默认构造函数调用
- Person的拷贝构造函数调用
- p2的年龄为: 18
- Person的析构函数调用
- Person的析构函数调用
- #include <iostream>
- using namespace std;
-
- // 构造函数的调用规则
- // 1.创建一个类,C++编译器会给每个类都添加至少3个函数
- // 默认构造(空实现)
- // 析构函数(空实现)
- // 拷贝构造(值拷贝)
-
- // 2.如果我们写了有参构造函数,编译器就不再提供默认构造,依然提供拷贝构造
- // 如果我们写了拷贝构造函数,编译器就不再提供其他普通构造函数了
- 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;
- };
-
- void test02() {
- Person p(18);
- Person p2(p);
- }
-
- int main() {
- test02();
- return 0;
- }
执行结果:
- Person的有参构造函数调用
- Person的析构函数调用
- Person的析构函数调用
深拷贝是面试经典问题,也是常见的一个坑
- #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_Age; // 编译器默认实现就是这行代码
-
- // 深拷贝操作
- 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() {
- // 深拷贝,p1走p1的析构,p2走p2的析构.注意:堆栈是先进后出的
- // 浅拷贝,有交叉重复释放的问题
- 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();
- return 0;
- }
执行结果:
- Person的有参构造函数调用
- p1的年龄: 18身高: 160
- Person的拷贝构造函数调用
- p2的年龄: 18身高: 160
- Person的析构函数调用
- Person的析构函数调用
总结:如果属性有在堆区开辟时,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
- #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(1,2,3);
- cout << p.m_A << " " << p.m_B << " " << p.m_C << endl; // 输出:1 2 3
- }
-
- int main() {
-
- return 0;
- }
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_PName = pName;
- cout<<"Phone有参构造调用"<<endl;
- }
- ~Phone() { cout<<"Phone的析构函数调用"<<endl; }
- string m_PName;// 品牌名称
- };
-
- // 人类
- class Person {
- public:
- // Phone m_Phone = pName 隐式转换法
- Person(string name, string pName) : m_name(name), m_phone(pName) {
- cout<<"Person有参构造调用"<<endl;
- }
- ~Person() { cout<<"Person的析构函数调用"<<endl; }
- // 姓名
- string m_name;
- // 手机
- Phone m_phone;
- private:
-
- };
-
- // 当其他类对象作为本类成员,构造时候先构造类对象,再构造自身,析构的顺序与构造相反
- void test01() {
- Person p("张三", "苹果");
- cout << "姓名:" << p.m_name << " 手机品牌:" << p.m_phone.m_PName << endl;
- }
-
- int main() {
- test01();
- return 0;
- }
执行结果:
- PS D:\Work\c++\bin> ."D:/Work/c++/bin/app.exe"
- Phone有参构造调用
- Person有参构造调用
- 姓名:张三 手机品牌:苹果
- Person的析构函数调用
- Phone的析构函数调用
- PS D:\Work\c++\bin>
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
静态成员变量
- #include <iostream>
- using namespace std;
-
- // 静态成员变量
- class Person {
- public:
- // 1.所有对象都共享同一份数据
- // 2.编译阶段就分配内存
- // 3.类内声明,类外初始化操作
- static int m_A; // 静态成员变量
-
- // 静态成员变量也是有访问权限的
- private:
- static int m_B; // 静态成员变量
- };
-
- int Person::m_A = 100; // 类外初始化操作
- int Person::m_B = 200; // 类外初始化操作
-
- void test01() {
- Person p;
- cout << "m_A = " << p.m_A << endl; // 100
-
- Person p2;
- p2.m_A = 400;
- cout << "m_A = " << p.m_A << endl; // 400
- }
-
- void test02() {
- // 静态成员变量,不属于某个对象上,所有对象都共享同一份数据
- // 因此静态成员变量有两种访问方式
- // 1.通过对象进行访问
- // Person p;
- // cout<<"p.m_A = "<<p.m_A<<endl; // 100
- // 2.通过类名进行访问
- cout << "m_A = " << Person::m_A << endl; // 100
- // 类外访问不到私有静态成员变量
- // cout << "m_B = " << Person::m_B << endl; // error:成员"Person::m_B"不可访问
- }
-
- int main() {
- test02();
- return 0;
- }
静态成员函数
- #include <iostream>
- using namespace std;
-
- // 静态成员函数
- // 所有对象共享同一个函数
- // 静态成员函数只能访问静态成员变量
- /*
- 因为m_B必须通过创建对象才能访问,得创建一个对象,
- 才能够去读/写这块内存.当你去调用静态成员函数func()
- 这个函数体的内部不知道改变的是哪个对象的m_B
- 非静态成员变量属于特定对象的成员变量
- */
- class Person {
- public:
- // 静态成员函数
- static void func() {
- m_A = 100;//静态成员函数是可以访问静态成员变量
- // m_B = 200;//静态成员函数是不可以访问非静态成员变量的,无法区分到底是哪个对象的m_B属性
- cout<<"static void func调用"<<endl;
- }
- static int m_A;// 静态成员变量
- int m_B;// 非静态成员变量
-
- // 静态成员函数也是有访问权限的
- private:
- static void func2() { cout<<"static void func2调用"<<endl; }
- };
-
- int Person::m_A = 0;
-
- // 有两种访问方式
- void test01() {
- // 1.通过对象访问
- Person p;
- p.func();
-
- // 2.通过类名访问(类外访问不到私有静态成员函数)
- Person::func();
- // Person::func2();// 错误,不可访问
- }
-
- int main() {
- test01();
- return 0;
- }
因为m_B必须通过创建对象才能访问,得创建一个对象,才能够去读/写这块内存.当你去调用静态成员函数func()。这个函数体的内部不知道改变的是哪个对象的m_B,非静态成员变量属于特定对象的成员变量。静态成员函数是不可以访问非静态成员变量的,无法区分到底是哪个对象的m_B属性。
在C++中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上。
- #include <iostream>
- using namespace std;
-
- // 成员变量和成员函数是分开存储的
- class Person {
- public:
- int m_A;// 非静态成员变量 属于类的对象上
- static int m_B;// 静态成员变量 不属于类的对象上
- void func(){} // 非静态成员函数 不属于类的对象上
- static void func2(){} // 静态成员函数 不属于类的对象上
- };
-
- int Person::m_B = 100;// 初始化静态成员变量
-
- void test01() {
- Person p;
- // 空对象占用内存空间为:1
- // C++编译器会给每个空对象特分配一个字节空间,是为了区分空对象
- // 占内存的位置
- // 每个空对象也应该有一个独一无二的内存地址
- cout<<"size of p = " <<sizeof(p)<<endl; // 1
- }
-
- void test02() {
- Person p;
- cout<<"size of p = " <<sizeof(p)<<endl; // 4
- }
-
- int main() {
- test02();
- return 0;
- }
通过4.3.1 我们知道在C++中成员变量和成员函数是分开存储的。每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码。那么问题是:这一块代码是如何区分那个对象调用自己的呢?C++通过提供的对象指针,this指针,解决上述问题,this指针指向被调用的成员函数所属的对象
this指针的用途:
- #include <iostream>
- using namespace std;
- class Person {
- public:
- Person(int age) {
- // this指针指向的是被调用的成员函数 所属的对象
- this->m_Age = age;
- }
- Person& PersonAddPerson(Person& p) {
- this->m_Age += p.m_Age;
-
- // this指向p2的指针,而*this指向的就是p2这个对象本体
- return *this; // 返回对象本身
- }
- int m_Age;
- };
- // 1.解决名称冲突
- void test01() {
- Person p1(18); // 创建一个Person对象,年龄为18
- cout <<"p1的年龄为: "<<p1.m_Age << endl; // 输出Person对象的年龄
- }
-
- // 2.返回对象本身用*this
- void test02() {
- Person p1(10); // 创建一个Person对象,年龄为10
- Person p2(20); // 创建另一个Person对象,年龄为20
- // 链式编程思想
- p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
- cout<<"p2的年龄为: "<<p2.m_Age<<endl;
- }
-
- int main() {
- test02();
- return 0;
- }
- #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 = "<<this->m_Age<<endl;
- }
- int m_Age;
- };
-
- void test01() {
- Person *p = NULL;
- p->showClassName();
- p->showPersonAge();
- }
-
- int main() {
- test01();
- return 0;
- }
常函数:
常对象:
const 和 this
注意:
- #include <iostream>
- using namespace std;
-
- // 常函数
- class Person {
- public:
- Person();
- // this指针的本质 是指针常量 指针的指向是不可以修改的,指针指向的值是可以修改的
- // Person * const this;
- // const Person * const this; 指针的指向的值也不可以修改了
- // 在成员函数后面加const,修饰的是this指向,让指针指向的
- // 值也不可以修改
- void showPerson() const {// 常函数
- // this->m_A = 10;
- // this 指针是不可以修改指针的指向的
- // this = NULL;// error 分配到"this"(记时错误)
-
- this->m_B = 100;
- }
-
- void func() {
- m_A = 100;
- }
- int m_A;
- mutable int m_B;// 特殊变量,即使在常函数中,也可以修改这个值.需要加上关键字mutable
- };
-
- void test01() {
- Person p;
- p.showPerson();
- }
-
- // 常对象
- void test02() {
- const Person p;// 在对象前加上const,变为常对象
- // p.m_A = 100;//error
- p.m_B = 100;// m_B是特殊值,在常对象也可以修改
-
- // 常对象只能调用常函数
- p.showPerson();
- // p.func();// error:常对象不可以调用普通成员函数,因为普通成员函数可以修改属性
- }
-
- int main() {
-
- return 0;
- }
生活中你的家有客厅(Public),有你的卧室(Private)客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去。但是呢,你也可以允许你的好闺蜜好基友进去。在程序里,有些私有属性,也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术,友元的目的就是让一个函数或者类访问另一个类中私有成员。友元的关键字为friend
友元的三种实现
全局函数做友元
- #include <iostream>
- using namespace std;
- #include <string>
-
- // 建筑物
- class Building {
- // 告诉编译器 goodGay全局函数是 Building好朋友,可以访问Building中私有成员
- friend void goodGay(Building *building);
- public:
- Building() {
- m_SittingRoom = "客厅";
- m_BedRoom = "卧室";
- }
- public:
- 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();
- return 0; // 返回0表示正常退出
- }
执行结果:
- PS D:\Work\c++\bin> ."D:/Work/c++/bin/app.exe"
- 好基友全局函数 正在访问 : 客厅
- 好基友全局函数 正在访问 : 卧室
类做友元
- #include <iostream>
- using namespace std;
- #include <string>
- // 类做友元
- class Building;
- class GoodGay
- {
- public:
- GoodGay();
- void visit();// 参观函数 访问Building中的属性
- Building *building;
- };
-
- class Building{
- // 告诉编译器 GoodGay 类是本来的好朋友,可以访问本类中私有成员
- friend class GoodGay;
- public:
- Building();
- public:
- string m_SittingRoom;// 客厅
- private:
- string m_BedRoom;// 卧室
- };
-
- // 类外写成员函数
- Building::Building() {
- m_SittingRoom = "客厅";
- m_BedRoom = "卧室";
- }
-
- 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();
- return 0;
- }
成员函数做友元
- #include <iostream>
- using namespace std;
- #include <string>
- class Building;
- class GoodGay{
- public:
- GoodGay();
- void visit();// 让visit函数可以访问Building中私有成员
- void visit2();// 让visit2函数不可以访问Building中私有成员
- Building* building;
- };
-
- class Building{
- // 告诉编译器 GoodGay类下的visit成员函数作为本类的好朋友,可以访问私有成员
- friend void GoodGay::visit();
- public:
- Building();
- string m_SittingRoom; // 客厅
- private:
- string m_BedRoom; // 卧室
- };
-
- // 类外实现成员函数
- Building::Building(){
- m_SittingRoom = "客厅";
- m_BedRoom = "卧室";
- }
-
- GoodGay::GoodGay(){
- building = new Building;
- }
-
- void GoodGay::visit() {
- cout<<"visit 函数正在访问: "<<building->m_SittingRoom<<endl;
- cout<<"visit 函数正在访问: "<<building->m_BedRoom<<endl;
- }
-
- void GoodGay::visit2() {
- cout<<"visit 函数正在访问: "<<building->m_SittingRoom<<endl;
- }
-
- void test01() {
- GoodGay gg;
- gg.visit();
- gg.visit2();
- }
-
- int main() {
- test01();
- return 0;
- }
作用:实现两个自定义数据类型相加的运算
总结:
- #include <iostream>
- using namespace std;
-
- // 加号运算符重载
- 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;
- 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;
- // 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;
- 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();
- return 0;
- }
- #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){}
- // 利用成员函数重载 左移运算符 p.operator<<(cout) 简化版本 p<<cout
- // 不会利用成员函数重载<<运算符,因为实现cout在左侧
- // void operator<<(Person &p) {
-
- // }
-
- private:
- 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,20);
- cout<<p<<", hello,world!"<<endl;
- }
-
- int main() {
- test01();
- return 0;
- }
总结:重载左移运算符配合友元可以实现输出自定义数据类型
- #include <iostream>
- using namespace std;
-
- // 重载递增运算符
-
- // 自定义整型
- class MyInterger {
- friend ostream& operator<<(ostream& cout,MyInterger myint);
- public:
- MyInterger() {
- m_Num = 0;
- }
- // 重载前置++运算符 返回引用为了一直对一个数据进行递增
- MyInterger& operator++() {
- // 先进行++运算
- m_Num++;
- // 再将自身做返回
- return *this;
- }
- // 重载后置++运算符
- // void operator++(int) int代表占位参数,可以用于区分前置和后置递增
- MyInterger operator++(int) {
- // 先 记录当时结果
- MyInterger temp = *this;
- // 后 递增
- m_Num++;
- // 最后将记录结果做返回
- return temp;
- }
- private:
- int m_Num;
- };
-
-
- // 重载<<运算符
- ostream& operator<<(ostream& cout,MyInterger myint) {
- cout<<myint.m_Num;
- return cout;
- }
-
-
- void test01() {
- MyInterger myint;
- cout<<++(++myint)<<endl;
- cout<<myint<<endl;
- }
-
- void test02() {
- MyInterger myint;
- cout<<myint++<<endl;
- cout<<myint;
- }
-
- int main() {
- test02();
- return 0;
- }
C++编译器至少给一个类添加4个函数
- #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();
- return 0;
- }
执行结果:
- PS D:\Work\c++\bin> ."D:/Work/c++/bin/app.exe"
- p1 的年龄为: 18
- p2 的年龄为: 18
- p3 的年龄为: 18
- #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",18);
- Person p3("Jerry",18);
- if(p1 == p2) {
- cout<<"p1 和 p2 是相等的"<<endl;
- }
- if(p1 != p3) {
- cout<<"p1 和 p3 是不相等的"<<endl;
- }else{
- cout<<"p1 和 p3 是相等的"<<endl;
- }
- }
-
- int main() {
- test01();
- return 0;
- }
执行结果:
- PS D:\Work\c++\bin> ."D:/Work/c++/bin/app.exe"
- p1 和 p2 是相等的
- p1 和 p3 是不相等的
- PS D:\Work\c++\bin>
- #include <iostream>
- using namespace std;
- #include <string>
- // 函数调用运算符重载
-
- // 打印输出类
- 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,heheda");
- }
-
- // 仿函数非常灵活,没有固定的写法
- class MyAdd{
- public:
- int operator()(int num1,int num2){
- return num1+num2;
- }
- };
-
- void test02() {
- MyAdd myadd;
- int ret = myadd(100,200);
- cout<<"ret = "<<ret<<endl;
-
- // 匿名函数对象
- cout<<MyAdd()(100,10)<<endl;
- }
-
- int main() {
- test02();
- return 0;
- }
有些类与类之间存在特殊的关系,例如下图中:
我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。
这个时候我们就可以考虑利用继承的技术,减少重复代码
- #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 = 10;// 父类中的保护权限成员 到子类中依然是保护权限
- // m_C = 10;// 父类中的私有权限成员 子类访问不到
- }
- };
- void test01(){
- Son1 s1;
- s1.m_A = 100;
- // s1.m_B = 20; // 到Son1中 m_B是保护权限 类外访问不到
- }
-
- // 保护继承
- class Base2{
- public:
- int m_A;
- protected:
- int m_B;
- private:
- int m_C;
- };
- class Son2:protected Base2{
- public:
- void func(){
- m_A = 100; // 父类中公共成员,到子类中变为保护权限
- m_B = 200; // 父类中保护成员,到子类中变为保护权限
- // m_C = 400;// 父类中私有成员 子类访问不到
- };
- };
-
- void test02() {
- Son2 s2;
- // s2.m_A = 1000;// 在Son2中 m_A变为保护权限,因此类外访问不到
- // s2.m_B = 120; // 在Son2中 m_B为保护权限,因此类外访问不到
- }
-
- // 私有继承
- class Base3{
- public:
- int m_A;
- protected:
- int m_B;
- private:
- int m_C;
- };
- class Son3:private Base3{
- public:
- void func(){
- m_A = 100; // 父类中公共成员,到子类中变为私有权限
- m_B = 200; // 父类中保护成员,到子类中变为私有权限
- // m_C = 400;// 父类中私有成员 子类访问不到
- };
- };
-
- void test03() {
- Son3 s3;
- // s2.m_A = 1000;// 在Son3中 m_A变为私有权限,因此类外访问不到
- // s2.m_B = 120; // 在Son3中 m_B变为私有权限,因此类外访问不到
- }
-
- class GrandSon3:public Son3 {
- public:
- void func() {
- // m_A = 100; // 到了Son3中 m_A变为私有,即使是儿子,也是访问不到
- // m_B = 20; // 到了Son3中 m_B变为私有,即使是儿子,也是访问不到
- }
- };
- #include<iostream>
- using namespace std;
-
- // 继承中的对象模型
- class Base{
- public:
- int m_A;
- protected:
- int m_B;
- private:
- int m_C;
- };
-
- class Son:public Base{
- public:
- int m_D;
- };
-
- // 利用开发人员命令提示工具查看对象模型
- // 跳转盘符 F:
- // 跳转文件路径 cd 具体路径下
- // 查看命名
- // c1 /d1 reportSingleClassLayout类名 文件名
-
- void test01() {
- // 16
- // 父类中所有非静态成员属性都会被子类继承下去
- // 父类中私有成员属性 是被编译器给隐藏了,因此是访问不到,但是确实被继承了
- cout<<"size of Son = "<<sizeof(Son)<<endl;
- }
-
- int main() {
-
- return 0;
- }
打开工具窗口后,定位到当前CPP文件的盘符
然后输入:c1 /d1 reportSingleClassLayout查看的类名 所属文件名
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题:父类和子类的构造和析构顺序是谁先谁后?
继承中的构造和析构顺序如下:先构造父类,再构造子类,析构的顺序与构造的顺序相反
- #include <iostream>
- using namespace std;
- // 继承中的构造和析构顺序
- class Base{
- public:
- Base() {cout << "Base constructor" << endl;}
- ~Base() {cout << "Base destructor" << endl;}
- private:
-
- };
-
- class Son:public Base{
- public:
- Son() {cout << "Son constructor" << endl;}
- ~Son() {cout << "Son destructor" << endl;}
- };
-
- void test01() {
- // Base b;
-
- // 继承中的构造和析构顺序如下:
- // 先构造父类,再构造子类,析构的顺序与构造的顺序相反
- Son s;
- }
-
- int main() {
- test01();
- return 0;
- }
执行结果:
- PS D:\Work\c++\bin> ."D:/Work/c++/bin/app.exe"
- Base constructor
- Son constructor
- Son destructor
- Base destructor
- PS D:\Work\c++\bin>
问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?
总结:
- #include <iostream>
- using namespace std;
-
- // 继承中同名成员处理
- class Base{
- public:
- Base() {
- m_A = 100;
- }
- void func() {
- cout << "Base func()" << endl;
- }
- void func(int a) {
- cout << "Base func(int a)" << endl;
- }
- int m_A;
- };
-
- class Son:public Base{
- public:
- Son() {
- m_A = 200;
- }
- void func() {
- cout << "Son func()" << endl;
- }
- int m_A;
- };
-
- // 同名成员属性处理
- void test01() {
- Son s;
- cout << "Son下的 m_A = " << s.m_A << endl;//200
- // 如果通过子类对象 访问到父类中同名成员,需要加作用域
- cout << "Base下的 m_A = " << s.Base::m_A << endl;//100
- cout << "Son::m_A = " << s.Son::m_A << endl;//200
-
- }
-
- // 同名成员函数处理
- void test02() {
- Son s;
- s.func();//Son func() 直接调用 调用的是子类中的同名成员
-
- // 如何调用到父类中同名成员函数?
- s.Base::func();//Base func()
-
- // 如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有的同名成员函数
- // s.func(100); // error
-
- // 如果想访问到父类中被隐藏的同名成员函数,需要加作用域
- s.Base::func(100);//Base func(int a)
- }
-
- int main() {
- // test01();
- test02();
- return 0;
- }
问题:继承中同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致
- #include <iostream>
- using namespace std;
-
- // 继承中的同名静态成员处理方式
- class Base {
- public:
- static int m_A;
- static void func() {
- cout << "Base func()" << endl;
- }
- static void func(int a) {
- cout<<"Base func(int a)"<<endl;
- }
- };
-
- int Base::m_A = 100;
-
- class Son:public Base{
- public:
- static int m_A;
- static void func() {
- cout<<"Son func()"<<endl;
- }
- };
- int Son::m_A = 200;
-
- // 同名的静态成员属性
- void test01() {
- Son s;
- // 1.通过对象访问
- cout<<"通过对象访问: "<<endl;
- cout<<"Son 下的 m_A = "<<s.m_A<<endl;
- cout<<"Base 下的 m_A = "<<s.Base::m_A<<endl;
-
- // 2.通过类名访问
- cout<<"通过类名访问: "<<endl;
- cout<<"Son 下的 m_A = "<<Son::m_A<<endl;
- // 第一个::代表通过类名方式访问
- // 第二个::代表访问父类作用域下
- cout<<"Base 下的 m_A = "<<Son::Base::m_A<<endl;
- }
-
- // 同名的静态成员函数
- void test02() {
- Son s;
- // 1.通过对象访问
- cout<<"通过对象访问: "<<endl;
- s.func();//Son func()
- s.Base::func();//Base func()
-
- // 2.通过类名访问
- cout<<"通过类名访问: "<<endl;
- Son::func();//Son func()
- Son::Base::func();//Base func()
- // 子类出现和父类同名静态成员函数,也会隐藏父类中所有同名成员函数
- // 如果想访问父类中被隐藏同名成员,需要加作用域
- Son::Base::func(1);//Base func(int a)
- }
-
-
- int main() {
- test02();
- return 0;
- }
总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问方式(通过对象 和 通过类名)
C++允许一个类继承多个类
语法:class 子类:继承方式1 父类1,继承方式2 父类2...
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++实际开发中不建议多继承
- #include <iostream>
- using namespace std;
-
- // 多继承语法
- class Base1 {
- public:
- Base1() {
- m_A = 100;
- }
- int m_A;
- };
-
- class Base2 {
- public:
- Base2() {
- m_A = 200;
- }
- int m_A;
- };
-
- // 子类 需要继承Base1和Base2
- // 语法:class 子类:继承方式1 父类1,继承方式2 父类2...
- class Son:public Base1,public Base2 {
- public:
- Son() {
- m_C = 300;
- m_D = 400;
- }
- int m_C;
- int m_D;
- };
-
- void test01() {
- Son s;
- cout<<"size of Son: "<<sizeof(s)<<endl;
- // 当父类中出现同名成员需要加作用域区分
- cout<<"Base1::m_A = "<<s.Base1::m_A<<endl;//100
- cout<<"Base2::m_A = "<<s.Base2::m_A<<endl;//200
- }
-
- int main() {
- test01();
- return 0;
- }
总结:多继承中如果父类中出现了同名情况,子类使用时候要加作用域
菱形继承概念:
典型的菱形继承案例:
菱形继承问题:
- #include <iostream>
- using namespace std;
-
- // 动物类
- class Animal{
- public:
- int m_Age;
- };
-
- // 羊类
- class Sheep:public Animal{};
-
- // 驼类
- class Tuo:public Animal{};
-
- // 羊驼类
- class SheepTuo:public Sheep,public Tuo{};
-
- void test01() {
- SheepTuo st;
- st.Sheep::m_Age = 18;
- st.Tuo::m_Age = 28;
- // 当菱形继承,两个父类拥有相同数据,需要加以作用域区分
- cout<<"st.Sheep::m_Age = "<<st.Sheep::m_Age<<endl;//18
- cout<<"st.Tuo::m_Age = "<<st.Tuo::m_Age<<endl;//28
-
- // 这份数据我们知道,只要有一份就可以,菱形继承导致数据有两份,资源浪费
-
- }
-
- int main() {
- test01();
- return 0;
- }
- #include <iostream>
- using namespace std;
-
- // 动物类
- class Animal{
- public:
- int m_Age;
- };
-
- // 利用虚继承 解决菱形继承的问题
- // 继承之前 加上关键字 virtual 变为虚继承
- // Animal类称为 虚基类
-
- // 羊类
- class Sheep:virtual public Animal{};
-
- // 驼类
- class Tuo:virtual public Animal{};
-
- // 羊驼类
- class SheepTuo:public Sheep,public Tuo{};
-
- void test01() {
- SheepTuo st;
- st.Sheep::m_Age = 18;
- st.Tuo::m_Age = 28;
- // 当菱形继承,两个父类拥有相同数据,需要加以作用域区分
- cout<<"st.Sheep::m_Age = "<<st.Sheep::m_Age<<endl;//28
- cout<<"st.Tuo::m_Age = "<<st.Tuo::m_Age<<endl;//28
- cout<<"st.m_Age = "<<st.m_Age<<endl;//28
-
- // 这份数据我们知道,只要有一份就可以,菱形继承导致数据有两份,资源浪费
-
- }
-
- int main() {
- test01();
- return 0;
- }
总结:
多态是C++面向对象三大特性之一
多态分为两类
静态多态和动态多态区别:
- #include <iostream>
- using namespace std;
-
- // 多态
-
- // 动物类
- class Animal {
- public:
- // 虚函数
- virtual void speak() {
- cout<<"动物在说话"<<endl;
- }
- };
-
- // 猫类
- class Cat:public Animal{
- public:
- // 重写 函数返回值类型 函数名 参数列表 完全相同
- void speak() {
- cout<<"小猫在说话"<<endl;
- }
- };
-
- // 狗类
- class Dog:public Animal{
- public:
- void speak() {
- cout<<"小狗在说话"<<endl;
- }
- };
-
- // 执行说话的函数
- // 地址早绑定 在编译阶段确定函数地址
- // 如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,
- // 地址晚绑定
-
- // 动态多态满足条件
- // 1.有继承关系
- // 2.子类要重写父类的虚函数
-
- // 动态多态使用
- // 父类的指针或者引用,指向子类对象
-
- void doSpeak(Animal &animal) { // Animal &animal = cat;
- animal.speak();
- }
-
- void test01() {
- Cat cat;
- doSpeak(cat);
-
- Dog dog;
- doSpeak(dog);
- }
-
- int main() {
- test01();
- return 0;
- }
总结:
多态满足条件:
多态使用条件:
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
多态的原理剖析:
当子类重写父类的虚函数,子类中的虚函数表内部会替换成子类的虚函数地址。当父类的指针或者引用指向子类对象时候,发生多态。
- Animal& animal = cat;
- animal.speak()
案例描述:
多态的优点:
- #include <iostream>
- using namespace std;
-
- // 分别利用普通写法和多态技术实现计算器
- // 普通写法
- class Calculator {
- public:
- int getResult(string oper) {
- if(oper == "+") {
- return m_Num1 + m_Num2;
- }
- else if(oper == "-") {
- return m_Num1 - m_Num2;
- }
- else if(oper == "*") {
- return m_Num1 * m_Num2;
- }
- // 如果想扩展新的功能,需要修改源码
- // 在真实开发中 提倡 开闭原则
- // 开闭原则: 对扩展进行开放,对修改进行关闭
- }
- int m_Num1;// 操作数1
- int m_Num2;// 操作数2
- };
-
- void test01() {
- // 创建计算器对象
- Calculator cal;
- cal.m_Num1 = 10;// 操作数1为10
- cal.m_Num2 = 10;// 操作数2为10
-
- cout<<cal.getResult("+")<<endl;// 输出20
- cout<<cal.getResult("-")<<endl;// 输出0
- cout<<cal.getResult("*")<<endl;// 输出100
- }
-
- // 利用多态实现计算器
- // 多态好处:
- // 1.组织结构清晰
- // 2.可读性强
- // 3.对于前期和后期扩展以及维护性高
-
- // 实现计算器抽象类
- class AbstractCalculator {
- public:
- virtual int getResult() {
- return 0;
- }
- int m_Num1;// 操作数1
- int m_Num2;// 操作数2
- };
-
- // 加法计算器类
- class AddCalculator:public AbstractCalculator {
- public:
- int getResult() {
- return m_Num1 + m_Num2;
- }
- };
-
- // 减法计算器类
- class SubCalculator:public AbstractCalculator {
- public:
- int getResult() {
- return m_Num1 - m_Num2;
- }
- };
-
- // 乘法计算器类
- class MulCalculator:public AbstractCalculator {
- public:
- int getResult() {
- return m_Num1 * m_Num2;
- }
- };
-
- void test02() {
- // 多态使用条件
- // 父类指针或者引用指向子类对象
-
- // 加法运算
- AbstractCalculator* abc = new AddCalculator;
- abc->m_Num1 = 10;// 操作数1为10
- abc->m_Num2 = 100;// 操作数2为100
-
- cout<<abc->m_Num1 <<"+"<<abc->m_Num2<<"="<<abc->getResult()<<endl;// 输出20
- // 用完后记得销毁
- delete abc;
- abc = nullptr;
-
- // 减法运算
- abc = new SubCalculator;
- abc->m_Num1 = 10;// 操作数1为10
- abc->m_Num2 = 100;// 操作数2为100
- cout<<abc->m_Num1 <<"-"<<abc->m_Num2<<"="<<abc->getResult()<<endl;
- delete abc;
- abc = nullptr;
-
- // 乘法运算
- abc = new MulCalculator;
- abc->m_Num1 = 10;// 操作数1为10
- abc->m_Num2 = 100;// 操作数2为100
- cout<<abc->m_Num1 <<"*"<<abc->m_Num2<<"="<<abc->getResult()<<endl;
- delete abc;
- abc = nullptr;
- }
-
- int main() {
- test02();
- return 0;
- }
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容。因此可以将虚函数改为纯虚函数
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
- #include <iostream>
- using namespace std;
-
- // 纯虚函数和抽象类
- class Base{
- public:
- // 纯虚函数
- // 只要有一个纯虚函数,这个类称为抽象类
- // 抽象类特点:
- // 1.无法实例化对象
- // 2.抽象类的子类 必须要重写父类中的纯虚函数,否则也属于抽象类
- virtual void func() = 0; // 纯虚函数
- };
-
- class Son:public Base{
- public:
- void func() { cout << "Son::func()" << endl; }// 重写父类的纯虚函数
- };
-
- void test01() {
- // Base b;// 抽象类无法实例化对象
- // new Base;//抽象类是无法实例化对象
- Son s;// 子类必须重写父类中的纯虚函数,否则无法实例化对象
-
- Base* base = new Son;
- base->func();// Son::func()
- delete base;
- base = NULL;
- }
-
- int main() {
- test01();
- return 0;
- }
案例描述:制作饮品大致流程:煮水-冲泡-倒入杯中-加入辅料
- #include <iostream>
- using namespace std;
-
- // 多态案例2: 制作饮品
- class AbstractDrinking{
- public:
- // 制作饮品
- virtual void boil() = 0;
- // 冲泡
- virtual void brew() = 0;
- // 倒入杯中
- virtual void pourInCup() = 0;
- // 加入辅料
- virtual void putSomething() = 0;
- // 制作饮品
- void makeDrink() {
- boil();
- brew();
- pourInCup();
- putSomething();
- }
- };
-
- // 制作咖啡
- class Coffee:public AbstractDrinking{
- public:
- // 煮水
- void boil() {
- cout<<"煮农夫山泉"<<endl;
- }
- // 冲泡
- void brew() {
- cout<<"冲泡咖啡"<<endl;
- }
- // 倒入杯中
- void pourInCup() {
- cout<<"倒入咖啡杯中"<<endl;
- }
- // 加入辅料
- virtual void putSomething() {
- cout<<"加入牛奶和糖"<<endl;
- }
- };
-
- // 制作红茶
- class RedTee:public AbstractDrinking{
- public:
- // 煮水
- void boil() {
- cout<<"煮怡宝"<<endl;
- }
- // 冲泡
- void brew() {
- cout<<"冲泡茶叶"<<endl;
- }
- // 倒入杯中
- void pourInCup() {
- cout<<"倒入茶杯中"<<endl;
- }
- // 加入辅料
- virtual void putSomething() {
- cout<<"加入冰糖和柠檬"<<endl;
- }
- };
-
- // 制作函数
- // AbstractDrinking* drinkings = new Coffee
- void doWork(AbstractDrinking *drinking) {
- drinking->makeDrink();
- delete drinking;// 释放
- drinking = nullptr;
- }
-
- void test01() {
- // 制作咖啡
- doWork(new Coffee);
- cout<<"================"<<endl;
- // 制作红茶
- doWork(new RedTee);
- }
-
- int main() {
- test01();
- return 0;
- }
执行结果:
- PS D:\Work\c++\bin> ."D:/Work/c++/bin/app.exe"
- 煮农夫山泉
- 冲泡咖啡
- 倒入咖啡杯中
- 加入牛奶和糖
- ================
- 煮怡宝
- 冲泡茶叶
- 倒入茶杯中
- 加入冰糖和柠檬
- PS D:\Work\c++\bin>
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
虚析构和纯虚析构区别:
虚析构语法: virtual ~ 类名() {}
纯虚析构语法:virtual ~ 类名() = 0;
类名::类名(){}
- #include <iostream>
- using namespace std;
- #include <string>
-
- // 虚析构和纯虚析构
- class Animal{
- public:
- // 纯虚函数
- Animal() { cout<<"Animal 构造函数调用" <<endl; }
- virtual void speak() = 0;
- // 利用虚析构可以解决 父类指针释放子类对象的时不干净的问题
- // virtual ~Animal(){ cout<<"Animal 虚析构函数调用" <<endl; }
-
- // 纯虚析构 需要声明也需要实现
- // 有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
- virtual ~Animal() = 0;
- };
-
- Animal::~Animal() {
- cout<<"Animal 纯虚析构函数调用" <<endl;
- }
-
- class Cat:public Animal{
- public:
- Cat(string name) {
- cout<<"Cat 构造函数调用" << endl;
- m_Name = new string(name); // 动态分配内存
- }
- void speak(){
- cout << *m_Name <<"小猫在说话" << endl;
- }
- ~Cat(){
- cout << "Cat 析构函数调用" << endl;
- if(m_Name != NULL) {
- delete m_Name;
- m_Name = NULL;
- cout<<"释放m_Name内存"<<endl;
- }
- }
- string* m_Name;
- };
-
- void test01() {
- Animal* animal = new Cat("Tom");
- animal->speak();
- // 父类指针在析构时候,不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄漏
- delete animal; // 父类指针指向子类对象,调用子类的析构函数
- animal = NULL; // 防止野指针
- }
-
- int main() {
- test01();
- return 0;
- }
执行结果:
- PS D:\Work\c++\bin> ."D:/Work/c++/bin/app.exe"
- Animal 构造函数调用
- Cat 构造函数调用
- Tom小猫在说话
- Cat 析构函数调用
- 释放m_Name内存
- Animal 纯虚析构函数调用
- PS D:\Work\c++\bin>
总结:
案例描述:电脑主要组成部件为CPU(用于计算),显卡(用于显示),内存条(用于存储)。将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商。创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口,测试时组装三台不同的电脑进行工作。
- #include <iostream>
- #include <string>
- using namespace std;
-
- // 目的:抽象不同的零件类
-
- // 抽象的CPU类
- class CPU {
- public:
- // 抽象的计算函数
- virtual void calculate() = 0;
- };
-
- // 抽象的显卡类
- class VideoCard {
- public:
- // 抽象的显示函数
- virtual void display() = 0;
- };
-
- // 抽象的内存条类
- class Memory {
- public:
- // 抽象的存储函数
- virtual void storage() = 0;
- };
-
- // 具体零件厂商 Intel厂商
- class IntelCPU : public CPU {
- public:
- void calculate() {
- cout << "IntelCPU 计算中..." << endl;
- }
- };
-
- class IntelVideoCard : public VideoCard {
- public:
- void display() {
- cout << "IntelVideoCard 显示中..." << endl;
- }
- };
-
- class IntelMemory : public Memory {
- public:
- void storage() {
- cout << "IntelMemory 存储中..." << endl;
- }
- };
-
- // 具体零件厂商 Lenovo厂商
- class LenovoCPU : public CPU {
- public:
- void calculate() {
- cout<<"LenovoCPU 计算中..." << endl;
- }
- };
-
- class LenovoVideoCard : public VideoCard {
- public:
- void display() {
- cout << "LenovoVideoCard 显示中..." << endl;
- }
- };
-
- class LenovoMemory : public Memory {
- public:
- void storage() {
- cout << "LenovoMemory 存储中..."<<endl;
- }
- };
-
- // 抽象的电脑类
- class Computer {
- public:
- // 电脑需要CPU、显卡、内存条
- // 构造函数中,传入三个零件指针
- Computer(CPU* cpu, VideoCard* videoCard, Memory* memory){
- m_cpu = cpu;
- m_videoCard = videoCard;
- m_memory = memory;
- }
- // 提供工作的函数,调用每个零件工作的接口
- void doWork() {
- m_cpu->calculate();
- m_videoCard->display();
- m_memory->storage();
- }
- // 提供析构函数 释放三个电脑零件
- ~Computer() {
- cout << "Computer 析构" << endl;
- if(m_cpu != NULL) { // 释放CPU零件
- delete m_cpu;
- m_cpu = NULL;
- cout<<"释放m_cpu"<<endl;
- }
- if(m_videoCard != NULL) { // 释放显卡零件
- delete m_videoCard;
- m_videoCard = NULL;
- cout<<"释放m_videoCard"<<endl;
- }
- if(m_memory != NULL) { // 释放内存条零件
- delete m_memory;
- m_memory = NULL;
- cout<<"释放m_memory"<<endl;
- }
- }
- private:
- CPU* m_cpu; // CPU的零件指针
- VideoCard* m_videoCard; // 显卡零件指针
- Memory* m_memory; // 内存条零件指针
- };
-
- void test01() {
- // 第一台电脑组装和工作~
- cout<<"第一台电脑组装和工作~~"<<endl;
- CPU* cpu = new LenovoCPU;
- VideoCard* videoCard = new LenovoVideoCard;
- Memory* memory = new LenovoMemory;
-
- // 创建一个Lenovo电脑
- Computer* computer1 = new Computer(cpu, videoCard, memory);
- computer1->doWork();
- delete computer1;
- computer1 = NULL;
- cout<<"===================================="<<endl;
- // 第二台电脑组装和工作~
- cout<<"第二台电脑组装和工作~"<<endl;
- cpu = new IntelCPU;
- videoCard = new IntelVideoCard;
- memory = new IntelMemory;
- // 创建一个Intel电脑
- Computer* computer2 = new Computer(cpu, videoCard, memory);
- computer2->doWork();
- delete computer2;
- computer2 = NULL;
- cout<<"===================================="<<endl;
- // 第三台电脑组装和工作~
- cout<<"第三台电脑组装和工作~"<<endl;
- cpu = new IntelCPU;
- videoCard = new LenovoVideoCard;
- memory = new IntelMemory;
- // 创建一个Intel电脑
- Computer* computer3 = new Computer(cpu, videoCard, memory);
- computer3->doWork();
- delete computer3;
- computer3 = NULL;
- }
-
- int main() {
- test01();
- return 0;
- }
执行结果:
- PS D:\Work\c++\bin> ."D:/Work/c++/bin/app.exe"
- 第一台电脑组装和工作~~
- LenovoCPU 计算中...
- LenovoVideoCard 显示中...
- LenovoMemory 存储中...
- Computer 析构
- 释放m_cpu
- 释放m_videoCard
- 释放m_memory
- ====================================
- 第二台电脑组装和工作~
- IntelCPU 计算中...
- IntelVideoCard 显示中...
- IntelMemory 存储中...
- Computer 析构
- 释放m_cpu
- 释放m_videoCard
- 释放m_memory
- ====================================
- 第三台电脑组装和工作~
- IntelCPU 计算中...
- LenovoVideoCard 显示中...
- IntelMemory 存储中...
- Computer 析构
- 释放m_cpu
- 释放m_videoCard
- 释放m_memory
- PS D:\Work\c++\bin>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。