C++核心编程<类和对象>

  • 4.类和对象
    • 4.1封装
      • 4.1.1封装的意义
        • 封装的意义1
        • 封装的意义2
      • 4.1.2struct和class区别
      • 4.1.3成员属性设置为私有
    • 4.2对象的初始化和清理
      • 4.2.1构造函数和析构函数
        • 1.1构造函数语法:类名(){}
        • 1.2析构函数语法: ~类名(){}
      • 4.2.2构造函数的分类及调用
        • 2.1构造函数的分类
        • 2.2构造函数的调用
      • 4.2.3拷贝构造函数调用时机
        • 遗留问题(待解决)
      • 4.2.4构造函数调用规则
      • 4.2.5深拷贝与浅拷贝
      • 4.2.6初始化列表
      • 4.2.7类对象作为类成员
      • 4.2.8静态成员
        • 8.1静态成员变量(有访问权限)
        • 8.2静态成员函数(都有访问权限)
    • 4.3C++对象模型和this指针
      • 4.3.1成员变量和成员函数分开存储
      • 4.3.2this指针概念
      • 4.3.3空指针访问成员函数
      • 4.3.4const修饰成员函数
        • 4.1常函数
        • 4.2常对象
    • 4.4友元
      • 4.4.1友元的三种实现
        • 1.1全局函数做友元
        • 1.2类做友元
        • 1.3成员函数做友元
    • 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继承方式
        • 2.1继承方式共有三种
          • 1.1公共继承
          • 1.2保护继承
          • 1.3私有继承
      • 4.6.3继承中的对象模型
        • 3.1打开开发人员命令提示工具查看对象模型
      • 4.6.4继承中的构造和析构顺序
      • 4.6.5继承同名成员处理方式
      • 4.6.6继承同名静态成员处理方式
      • 4.6.7多继承语法
      • 4.6.8菱形继承
        • 8.1菱形继承概念
    • 4.7多态
      • 4.7.1多态的基本概念
        • 1.1多态分为两类
        • 1.2静态多态和动态多态
        • 1.3动态多态满足条件
        • 1.4动态多态使用
      • 4.7.2纯虚函数和抽象函数
        • 2.1抽象类特点
      • 4.7.3虚析构和纯虚析构
        • 3.1虚析构和纯虚析构共性
        • 3.2虚析构和纯虚析构区别
        • 3.3语法

4.类和对象

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

4.1封装

4.1.1封装的意义

封装的意义1
  • 设计类的时候属性行为写在一起,表现事物
  • 语法
class 类名{访问权限: 属性 / 行为}
  • 案例
#include<iostream>
using namespace std;const double PI = 3.14;
class Circle {// 访问权限
public:// 属性double m_r;// 行为double calculateZC() {return 2 * PI * m_r;}
};int main() {// 实例化// 通过圆类  创建具体的圆(对象)Circle cl;// 给圆对象的属性进行赋值cl.m_r = 5;cout << "圆的周长为:" << cl.calculateZC() << endl;system("pause");return 0;
}
封装的意义2
  • 访问权限有三种

    • public 公共权限

      • 成员 类内可以访问 类外可以访问
    • protected 保护权限
      • 成员 类内可以访问 类外不可以访问 继承中子类可以访问父类
    • private 私有权限
      • 成员 类内可以访问 类外不可以访问 继承中子类不可以访问父类

4.1.2struct和class区别

  • 在C++中struct和class唯一的区别就在于默认的访问权限不同
  • 区别:
    • struct 默认权限为公共
    • class 默认权限为私有
#include<iostream>
using namespace std;
// struct 和 class区别
// struct  默认权限为公共
// class  默认权限为私有
class Circle {double PI = 3.14;double c_r = 0;void setR(int r = 5) {c_r = r;}void circleArea() {cout << "圆的面积为:" << PI * c_r * c_r << endl;}
};struct Person {int pAge = 15;string pName = "张三";void setName(string name) {pName = name;}void showInfo(){cout << "学生年龄:" << pAge << endl;cout << "学生姓名:" << pName << endl;}
};int main() {Circle cir;// 默认私有权限// cir.setR(10);Person per;per.setName("李四");per.showInfo(); per.pAge = 18;per.showInfo();system("pause");return 0;
}

4.1.3成员属性设置为私有

  • 优点

    • 将所有成员属性设置为私有,可以自己控制读写权限
    • 对于写权限,可以检测数据的有效性
  • 案例
#include<iostream>
#include<string>
using namespace std;
// 成员属性设置为私有
// 1.可以自己控制读写权限
// 2.对于写可以检测数据的有效性class Person {public:// 编辑姓名void setName(string name) {m_Name = name;}//获取姓名string getName() {return m_Name;}//设置年龄  (设置数据的有效性,默认为0)void setAge(int age) {if (age < 0 || age >150) {m_Age = 0;cout << "设置年龄有误" << endl;return;}m_Age = age;}//获取年龄int getAge() {return m_Age;}//设置爱好void setHobby(string hobbies) {m_Hobbies = hobbies;}
private://姓名    rwstring m_Name;//年龄 rwint m_Age;//爱好 wstring m_Hobbies;
};int main() {//struct 和 class 区别// struct 默认权限是 公用 public// class 默认权限是 私有  privatePerson person;person.setName("yinbb是真的大佬");cout << "原神大佬:" << person.getName() << endl;person.setAge(120);cout << "年龄:" << person.getAge() << endl; // 120person.setHobby("篮球");// 不能获取爱好// cout << "爱好:" << person.getHobby() << endl;system("pause");return 0;
}

4.2对象的初始化和清理

4.2.1构造函数和析构函数

  • 对象的初始化清理也是两个非常重要的安全问题

    • 一个对象或者变量没有初始状态,对其使用后果是未知的
    • 同样的使用完一个对象变量没有及时清理,也会造成一定的安全问题
  • c++利用了构造函数析构函数解决上述问题:
    • 构造函数: 主要作用在于创建对象时为对象的成员属性赋值构造函数由编译器自动调用,无须手动调用
    • 析构函数: 主要是作用在于对象销毁前系统自动调用,执行一些清理工作
1.1构造函数语法:类名(){}
  • 构造函数,没有返回值也不写void
  • 函数名称类名相同
  • 构造函数可以有参数,因此可以发生重载
  • 程序在调用对象时候会自动调用构造,无需手动调用,而且只会调用一次
1.2析构函数语法: ~类名(){}
  • 析构函数,没有返回值也不写void
  • 函数名称类名相同,在名称前加上符号~
  • 析构函数不可以有参数,因此不可以发生重载
  • 程序在对象销毁前会自动调用析构无需手动调用,而且只会调用一次
#include<iostream>
using namespace std;//对象的初始化和清理class Person {public://构造函数Person() {cout << "构造函数调用" << endl;}//析构函数~Person() {cout << "析构函数调用" << endl;}
};// 在栈上的数据,test()执行完毕后,释放这个对象void test() {Person p;
}int main() {test();// 构造函数调用      析构函数调用//在main函数执行完毕,才会调用析构函数Person p;// 构造函数调用system("pause");return 0;
}

4.2.2构造函数的分类及调用

2.1构造函数的分类
  • 两种分类方式

    • 按参数分为: 有参构造无参构造
    • 按类型分为:普通构造拷贝构造
2.2构造函数的调用
  • 三种调用方式

    • 括号法
    • 显示法
    • 隐式转换法
#include<iostream>
using namespace std;// 构造函数的分类及调用
class Person {public:// 无参构造Person() {cout << "构造函数的调用(默认)" << endl;}// 有参构造Person(int num) {age = num;cout << "构造函数的调用(有参)" << endl;}// 拷贝构造函数Person(const Person &person) {age = person.age;cout << "构造函数的调用(拷贝)" << endl;}// 析构函数~Person() {cout << "析构函数调用" << endl;}
private:int age;
};// 调用
void test(){// 1.括号法// 调用无参构造Person p1;// 调用有参构造Person p2(21);// 调用拷贝构造函数Person p3(p2);// 注意事项// 调用默认构造函数时候,不要添加括号;编译器会认为是一个函数声明,不认为在创建对象// 2.显示法Person p11;Person p22 = Person(20);Person p33 = Person(p22);// Person(20);//匿名对象,特点: 当前行执行结束后,系统会立即回收匿名对象// 注意事项2// 不要利用拷贝构造函数  初始化匿名对象  编译器会认为Person (p3) === Person p3;对象声明// 3.隐式法Person p4 = 10;//相当于 写了  Person p4 = Person(10); 有参构造Person p5 = p4;}int main() {test();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& person) {m_Age = person.m_Age;cout << "Person拷贝构造函数的调用" << endl;}~Person() {cout << "Person析构函数的调用" << endl;}int m_Age;
};// 1.使用一个已经创建完毕的对象来初始化一个新对象
void test1() {Person p1(20);Person p2(p1);cout << "P2的年龄: " << p2.m_Age << endl;
}
// 2.值传递的方式给函数参数传值
Person doWork(Person p) {p.m_Age = 25;return p;
}
void test2() {Person p(12);doWork(p);cout << "p的年龄:" << p.m_Age << endl;//12cout << "拷贝构造调用之后对值的修改:" << (doWork(p)).m_Age << endl; //25
}// 3.值方式返回局部对象
Person doWork2() {Person p1;return p1;}
void test3() {Person p3 = doWork2();
}int main() {// test1();// test2();test3();system("pause");return 0;
}
遗留问题(待解决)
  • 以值方式返回局部对象,拷贝函数未执行(不符合) 显示:由于G++优化导致,RVO有相关技术详情

4.2.4构造函数调用规则

  • 默认情况下,c++编译器至少给一个类添加3个函数

    • 默认构造函数(无参,函数体为空)
    • 默认析构函数(无参,函数体为空)
    • 默认拷贝构造函数,对属性进行值拷贝
  • 构造函数的调用规则如下

    • 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
    • 如果用户定义拷贝构造函数,c++不会提供其他普通构造函数
#include<iostream>
using namespace std;
// 构造函数的调用规则class Person {public:Person() {cout << "Person的默认构造函数调用" << endl;}Person(int age) {cout << "Person的有参构造函数调用" << endl;m_Age = age;}Person(const Person &p) {m_Age = p.m_Age;cout << "person的拷贝构造函数调用" << endl;}~Person() {cout << "person的析构函数调用" << endl;}int m_Age;
};void test1() {Person p;p.m_Age = 18;/*如果不自定义拷贝函数,编译器也会提供默认的拷贝函数,进行值拷贝   p2.m_Age 依旧为 18*/Person p2(p);cout << "P2的年龄是:" << p2.m_Age << endl; //18
}void test2() {Person p(28);Person p2(p);cout << "p2的年龄:" << p2.m_Age << endl;// 28
}void test3() {Person p;
}int main() {// test1();// test2();// 如果只提供拷贝函数,其他普通函数不提供test3();// errorsystem("pause");return 0;
}

4.2.5深拷贝与浅拷贝

  • 深浅拷贝的区别

    • 浅拷贝简单的赋值拷贝操作
    • 深拷贝在堆区重新申请空间,进行拷贝操作
#include<iostream>
using namespace std;
class Person {public:Person() {cout << "默认构造函数的调用" << endl;}Person(int age,int height) {cout << "有参构造函数的调用" << endl;m_Age = age;m_Height = new int(height);}~Person() {// 析构代码,将堆区开辟数据做释放操作if (m_Height != NULL) {delete m_Height;// 防止野指针m_Height = NULL;}cout << "析构函数的调用" << 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);}int m_Age;int* m_Height;};/*
* 栈区先进后出
* p2释放执行后,p1再去释放(非法操作)
* 浅拷贝带来的问题就是堆区内存重复释放
*/
void test1() {Person p1(18,180);cout << "p1的年龄:" <<p1.m_Age<<"身高为:" <<*p1.m_Height<< endl; // 18   180Person p2(p1);cout << "p1的年龄:" << p2.m_Age << "身高为:" << *p2.m_Height << endl;// 18  180}int main() {test1();system("pause");return 0;
}

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 test1() {//Person p(1, 2, 30);Person p(10,50,100);cout << p.m_A << endl;cout << p.m_B << endl;cout << p.m_C << endl;
}
int main() {test1();system("pause");return 0;
}

4.2.7类对象作为类成员

  • C++类中的成员可以是另一个类的对象,我们称为该成员为 对象成员
#include<iostream>
#include<string>
using namespace std;
class Phone {public:Phone(string pName,float pPrice): p_Name(pName),p_Price(pPrice) {cout << "手机的构造函数调用" << endl;}~Phone() {cout << "手机的析构函数调用" << endl;}// 手机品牌string p_Name;// 手机价格float p_Price;
};
class Person {public:Person(string name,Phone phone): m_Name(name),m_Phone(phone) {cout << "人的构造函数调用" << endl;}~Person() {cout << "人的析构函数调用" << endl;}//姓名string m_Name;//物品Phone m_Phone;
};// 当其他类对象作为本类成员,构造时候先构造类对象,再构造自身;析构的顺序相反
void test1() {Phone ph("华为", 7999);Person p("张三", ph);
}
int main() {test1();system("pause");return 0;
}

4.2.8静态成员

  • 静态成员就是成员变量和成员函数前加上关键字static,称为静态成员
8.1静态成员变量(有访问权限)
  • 所有对象共享同一份数据
  • 编译阶段分配内存(全局区)
  • 类内声明,类外初始化
#include<iostream>
using namespace std;// 静态成员变量
class Person {public:static int m_A;
};
int Person::m_A = 100;
void test1() {Person p;cout << p.m_A << endl; // 100Person p2;p2.m_A = 200;cout << p.m_A << endl;// 200}
void test2() {//静态成员变量  不属于某一个对象上,所有对象共享同一份数据//1.通过对象进行访问Person p;cout << p.m_A << endl;//2.通过类名进行访问cout << Person::m_A << endl;
}
int main() {//test1();test2();system("pause");return 0;
}
8.2静态成员函数(都有访问权限)
  • 所有对象共享同一个函数
  • 静态成员函数只能访问静态成员变量
#include<iostream>
using namespace std;class Person {public:static void func() {m_A = 200;// 静态成员函数可以访问静态成员变量// m_B = 300; // 静态成员函数不能访问非静态成员变量  无法区分那个对象的属性cout << "static void func的调用" << endl;}static int m_A;int m_B;
};
int Person::m_A = 100;
void test1() {//1.通过对象访问Person p;p.func();//2.通过类名访问Person::func();
}int main() {test1();system("pause");return 0;
}

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

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

  • 只有非静态成员变量才属于类的对象上
#include<iostream>
using namespace std;
class Person {int m_A;// 非静态成员变量  属于类的对象上static int  m_B;// 静态成员变量 不属于类对象上void func() {} // 非静态成员函数 不属于类对象上static void func2(){}// 静态成员函数 不属于类对象上
};
int Person::m_B = 100;
void test1() {Person p;// c++编译器会给每个对象分配一个字节空间,是为了区分空对象占内存的位置// 每个对象也应该有一个独一无二的内存地址cout << "size of p = " << sizeof(p) << endl;// 4
}
int main() {test1();system("pause");return 0;}

4.3.2this指针概念

  • this指针指向被调用的成员函数所属的对象
  • this指针的用途
    • 形参成员变量同名时,可以用this指针来区分
    • 在类的非静态成员函数中返回对象本身,可使用return *this
#include<iostream>
using namespace std;class Person {public:Person(int age) {// this指针指向 被调用的成员函数 所属的对象this->age = age;}// 返回Person值对象,会默认调用拷贝构造函数,复制一份当前所调对象Person& PersonAddAge(Person &p) {this->age += p.age;return *this;}int age;
};// 解决名称冲突
void test1() {Person p(18);cout << "年龄大小:" << p.age << endl;// 18
}
// 返回对象本身用*this
void test2() {Person p1(10);Person p2(20);p2.PersonAddAge(p1).PersonAddAge(p1);cout << "年龄:" << p2.age << endl;// 40}int main() {//test1(); test2();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;}// m_Age  默认  this->m_Age// 报错原因 传入的指针为NULLvoid showPersonAge() {// 提高健壮性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 ;
}

4.3.4const修饰成员函数

4.1常函数
  • 成员函数后加const后,我们称为这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明是加关键词mutable后,在常函数总依然可以修改
4.2常对象
  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数
#include<iostream>
using namespace std;
// 常函数
class Person
{public:// this指针的本质  是指针常量  指针的指向是不可以修改的// const Person * const this;// 在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改void showPerson() const{this->m_B = 120;// this->m_A = 100;// this = NULL; //this指针不可以修改指针指向的}void showNum() {m_A = 130;}int m_A;mutable int m_B;// 特殊变量,即使在常函数中可以修改,加mutable关键词
};void test1() {Person p;p.showPerson();
}
void test2() {const Person p2;// 在对象前面加上const,变为常对象//p2.m_A = 100;// m_B是特殊值,在常对象下也可以修改p2.m_B = 120;p2.showPerson();// 常对象,不可以调用普通成员函数,因为普通成员函数可以修改属性// p2.showNum();
}int main() {test2();system("pause");return 0;
}

4.4友元

  • 在程序中,有些私有属性 也想让类外的特殊的一些函数或类进行访问,就需要用到友元的技术
  • 友元的关键词为friend

4.4.1友元的三种实现

1.1全局函数做友元
#include<iostream>
#include<string>
using namespace std;class Building
{//GoodF全局函数是Building的友元,可以访问Building中的私有成员friend void goodF(Building* building);
public:string m_SittingRom;Building() {m_SittingRoom = "客厅";m_BedRoom = "卧室";}
private:string m_BedRoom;
};
// 全局函数
void goodF(Building* building)
{cout << "全局函数被调用,当前访问:"<< building->m_SittingRoom << endl;// 添加了friend关键词,可以访问私有成员cout << "全局函数被调用,当前访问:" << building->m_BedRoom << endl;
}void test1() {Building building;goodF(&building);
}int main() {test1();system("pause");return 0;
}
1.2类做友元
#include<iostream>
#include<string>
using namespace std;
// 类做友元
class Building;
class GoodFri
{public:GoodFri();void visit();Building* building;
};
class Building
{friend class GoodFri;
public:Building();
public:string m_SittingRoom;
private:string m_BedRoom;
};//类外写成员函数
Building::Building()
{m_SittingRoom = "客厅";m_BedRoom = "卧室";
}
GoodFri::GoodFri()
{// 创建建筑物对象building = new Building;
}
void GoodFri::visit()
{cout << "正在访问:" << building->m_SittingRoom << endl;cout << "正在访问:" << building->m_BedRoom << endl;}void test1() {GoodFri gf;gf.visit();
}int main() {test1();system("pause");return 0;
}
1.3成员函数做友元
#include<iostream>
#include<string>
using namespace std;
class Building;
class GoodFri;class GoodFri
{public:GoodFri();void visit();// visit()函数能访问Building中的私有成员void visit2();// visit2()函数不能访问Building中的私有成员Building *building;
};
class Building
{friend void GoodFri::visit();
public:Building();
public:string m_SittingRoom;
private:string m_BedRoom;};Building::Building()
{m_SittingRoom = "客厅";m_BedRoom = "卧室";
}GoodFri::GoodFri()
{building = new Building;
}void GoodFri::visit()
{cout << "当前访问的位置:" << building->m_SittingRoom << endl; //客厅cout << "当前访问的位置:" << building->m_BedRoom << endl;// 卧室
}
void GoodFri::visit2()
{cout << "当前访问的位置:" << building->m_SittingRoom << endl;// 客厅//cout << "当前访问的位置:" << building->m_BedRoom << endl;// error
}void test1() {GoodFri gf;gf.visit();gf.visit2();
}
int main() {test1();system("pause");return 0;
}

4.5运算符重载

  • 运算符重载概念:

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

4.5.1加号运算符重载

#include<iostream>
using namespace std;//加号运算符重载
class Person
{public://成员函数重载+号/** 成员函数重载的本质调用* Person p3 = p1.operator+(p2);*///Person operator+(Person &p);int m_A;int m_B;
};//Person 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;
//}//全局函数重载+号
/*
* 全局函数重载本质调用
* Person p3 = operator+(p1,p2)
*/
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;
}void test1()
{Person p1;p1.m_A = 20;p1.m_B = 30;Person p2;p2.m_A = 40;p2.m_B = 20;Person p3 = p1 + p2;cout << "m_A=" << p3.m_A << endl;cout << "m_B=" << p3.m_B << endl;}
int main() {test1();system("pause");return 0;
}

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://成员函数重载  左移运算符/** 不会利用成员函数重载<<运算符,因为无法实现  cout在左侧 * *///void operator<<() {};int m_A;int m_B;
};//全局函数重载左移运算符
ostream& operator<<(ostream &cout, Person &p)
{cout << "m_A = " << p.m_A << " m_B = " << p.m_B << endl;return cout;
}void test1() {Person p(10,12);cout << p << endl;
}int main() {test1();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;}//重载前置++运算符MyInteger& operator++();//重置后置++运算符  int代表占位参数,可以用于区分前置和后置递增MyInteger operator++(int);
private:int m_NUM;
};
MyInteger& MyInteger::operator++()
{m_NUM++;return *this;
}
MyInteger  MyInteger::operator++(int) {// 先 记录当时结果 MyInteger temp = *this;// 后 递增m_NUM++;// 最后将记录结果做返回return temp;
}//重载<<运算符
ostream& operator<<(ostream& cout, MyInteger myInt)
{cout << myInt.m_NUM;return cout;
}void test1() {MyInteger myInteger;cout << ++myInteger << endl;cout << myInteger << endl;
}
void test2() {MyInteger myInteger;cout << myInteger++ << endl;cout << myInteger << endl;
}
int main() {//test1();test2();system("pause");return 0;
}

4.5.4赋值运算符重载

  • c++编译器至少给一个类添加4个函数

    • 默认构造函数(无参,函数体为空)
    • 默认析构函数(无参,函数体为空)
    • 默认拷贝函数,属性进行值拷贝
    • 赋值运算符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);int* m_Age;
};
Person& Person::operator=(Person& p)
{//先判断是否有属性在堆区,如果有先释放,在做深拷贝if (m_Age != NULL) {delete m_Age;m_Age = NULL;}//深拷贝m_Age = new int(*p.m_Age);return *this;
}
void test1()
{Person p1(18);cout << "年龄:" << *p1.m_Age << endl;Person p2(20);cout << "年龄: " << *p2.m_Age << endl;p2 = p1;cout << "年龄: " << *p2.m_Age << endl;
}int main()
{test1();system("pause");return 0;}

4.5.5关系运算符重载

  • 重载关系运算符,可以自定义类型对象进行对比操作
#include<iostream>
using namespace std;
class Person;class Person
{public:Person(string name, int age){this->m_Name = name;this->m_Age = age;}bool operator==(Person &p);string m_Name;int m_Age;
};bool Person::operator==(Person& p)
{if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {return true;}return false;
}void test1() {Person p1("Tom", 20);Person p2("Tom", 23);if (p1 == p2) {cout << "p1和p2是相等的" << endl;}else {cout << "p1和p2是不相等的" << endl;}}int main() {test1();system("pause");return 0;
}

4.5.6函数调用运算符重载

  • 函数调用运算符()也可以重载
  • 由于重载后使用的方式非常像函数的使用,因此称为仿函数
  • 仿函数没有固定写法,非常灵活
    #include<iostream>
    #include<string>
    using namespace std;class MyPoint
    {public://重载函数调用运算符void operator()(string str);
    };
    void MyPoint::operator()(string str)
    {cout << "重载函数调用运算符===>"<<str << endl;
    }void test1()
    {MyPoint myPoint;// 使用起来非常类似于函数调用,因此称为仿函数myPoint("hello world");
    }
    int main()
    {test1();system("pause");return 0;
    }
    

4.6继承

4.6.1继承的基本语法

  • 继承的好处

    • 减少重复代码
  • 语法
class 子类 : 继承方式 父类
#include<iostream>
using namespace std; Java页面
//class Java
//{//public:
//  void header();
//  void footer();
//  void left();
//  void content();
//};
//
//void Java::header()
//{//  cout << "首页、公开课、登录、注册...(公共头部)" << endl;
//}
//void Java::footer()
//{//  cout << "帮助中心、交流中心、站内地图...(公告底部)" << endl;
//}
//void Java::left()
//{//  cout << "JAVA、Python、C++...(公共分类列表)" << endl;
//}
//void Java::content()
//{//  cout << "JAVA学科的视频" << endl;
//}
//
Python页面
//class Python
//{//public:
//  void header();
//  void footer();
//  void left();
//  void content();
//};
//
//void Python::header()
//{//  cout << "首页、公开课、登录、注册...(公共头部)" << endl;
//}
//void Python::footer()
//{//  cout << "帮助中心、交流中心、站内地图...(公告底部)" << endl;
//}
//void Python::left()
//{//  cout << "JAVA、Python、C++...(公共分类列表)" << endl;
//}
//void Python::content()
//{//  cout << "Python学科的视频" << endl;
//}
//
CPP页面
//
Python页面
//class CPP
//{//public:
//  void header();
//  void footer();
//  void left();
//  void content();
//};
//
//void CPP::header()
//{//  cout << "首页、公开课、登录、注册...(公共头部)" << endl;
//}
//void CPP::footer()
//{//  cout << "帮助中心、交流中心、站内地图...(公告底部)" << endl;
//}
//void CPP::left()
//{//  cout << "JAVA、Python、C++...(公共分类列表)" << endl;
//}
//void CPP::content()
//{//  cout << "C++学科的视频" << endl;
//}//公共部分
class BasePage
{public:void header();void footer();void left();
};
void BasePage::header()
{cout << "帮助中心、交流中心、站内地图...(公告底部)" << endl;
}
void BasePage::left()
{cout << "JAVA、Python、C++...(公共分类列表)" << endl;
}
void BasePage::footer()
{cout << "帮助中心、交流中心、站内地图...(公告底部)" << endl;
}//JAVA页面
class Java : public BasePage
{public:void content();
};
void Java::content()
{cout << "Java视频下载页面" << endl;
}//Python页面
class Python : public BasePage
{public:void content();
};
void Python::content()
{cout << "Python视频下载页面" << endl;
}//C++页面
class CPP : public BasePage
{public:void content();
};
void CPP::content()
{cout << "C++视频下载页面" << endl;
}void test1() {cout << "JAVA下载视频页面如下" << endl;Java ja;ja.header();ja.left();ja.content();ja.footer();cout << "---------------------------------" << endl;cout << "Python下载视频页面如下" << endl;Python py;py.header();py.left();py.content();py.footer();cout << "---------------------------------" << endl;cout << "C++下载视频页面如下" << endl;CPP cpp;cpp.header();cpp.left();cpp.content();cpp.footer();
}int main()
{test1();system("pause");return 0;
}

4.6.2继承方式

2.1继承方式共有三种

1.1公共继承
#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();
};
void Son1::func()
{//父类中的公共权限成员  到子类中依然是公共权限m_A = 10;//父类中的保护权限成员  到子类依然是保护权限m_B = 20;//父类中的私有权限成员  子类访问不了//m_C = 30;
}void test1()
{Son1 s1;s1.m_A = 12;//保护权限成员访问不到   类外访问不到// s1.m_B = 13;}
int main()
{test1();system("pause");return 0;
}
1.2保护继承
#include<iostream>
using namespace std;
//继承方式----公共继承
class Base1
{public:int m_A;
protected:int m_B;
private:int m_C;
};class Son1 : protected Base1
{public:void func();
};
void Son1::func()
{//父类中的公共权限成员  到子类中是保护权限m_A = 10;//父类中的保护权限成员  到子类依然是保护权限m_B = 20;//父类中的私有权限成员  子类访问不了//m_C = 30;
}void test1()
{Son1 s1;//保护权限成员访问不到   类外访问不到//s1.m_A = 12;//保护权限成员访问不到   类外访问不到// s1.m_B = 13;}
int main()
{test1();system("pause");return 0;
}
1.3私有继承
#include<iostream>
using namespace std;
//继承方式----公共继承
class Base1
{public:int m_A;
protected:int m_B;
private:int m_C;
};class Son1 : private Base1
{public:void func();
};
void Son1::func()
{//父类中的公共权限成员  到子类中是私有权限m_A = 10;//父类中的保护权限成员  到子类中是私有权限m_B = 20;//父类中的私有权限成员  子类访问不了//m_C = 30;
}void test1()
{Son1 s1;//私有权限成员访问不到   类外访问不到//s1.m_A = 12;//私有权限成员访问不到   类外访问不到// s1.m_B = 13;}
int main()
{test1();system("pause");return 0;
}

4.6.3继承中的对象模型

  • 父类中所有非静态成员属性都会被子类继承下去
  • 父类中私有成员属性 是被编译器给隐藏了 因此访问不到,但是确实被继承了
#include<iostream>
using namespace std;class Person
{public:int m_A;
protected:int m_B;
private:int m_C;};
class Son :public Person
{public:int m_D;
};void test1() {/** 父类中所有非静态成员属性都会被子类继承下去* 父类中私有成员属性  是编译器给隐藏了 因此访问不到,但是确实被继承了*/cout << "sizeof = " << sizeof(Son) << endl;// 16}int main()
{test1();system("pause");return 0;
}
3.1打开开发人员命令提示工具查看对象模型
  • 找到文件所在位置(可用dir查看一下)
  • 查看命令
cl /d1 reportSingleClassLayout类名 "文件名"

4.6.4继承中的构造和析构顺序

  • 子类继承父类后,当创建子类对象,也会调用父类的构造函数
  • 顺序
    • 先构造父类,再构造子类
    • 先子类析构,再父类析构
#include<iostream>
using namespace std;
class Person
{public:Person() {cout << "调用父类的构造函数" << endl;}~Person() {cout << "调用父类的析构函数" << endl;}
};class Son : public Person
{public:Son(){cout << "调用子类的构造函数" << endl;}~Son(){cout << "调用子类的析构函数" << endl;}
};void test1()
{/*调用父类的构造函数调用父类的析构函数*/// Person p;/*调用父类的构造函数调用子类的构造函数调用子类的析构函数调用父类的析构函数*/Son son;
}int main() {test1();system("pause");return 0;
}

4.6.5继承同名成员处理方式

  • 访问子类同名成员 直接访问
  • 访问父类同名成员 需要加作用域
  • 子类中出现和父类同名的成员函数,子类的同名成员会隐藏父类中所有的同名成员函数
#include<iostream>
using namespace std;
class Base
{public:Base(){m_A = 100;}void func(){cout << "Base of func的函数" << endl;}void func(int a){cout << "Base of func(int a)的函数" << endl;}int m_A;
};
class Son : public Base
{public:Son() {m_A = 112;}void func(){cout << "Son of func的函数" << endl;}int m_A;
};
// 同名成员属性
void test1()
{Son son;cout << "Son of m_A = " << son.m_A << endl; // 112// 如果通过子类对象访问父类同名成员,需要加作用域cout << "Base of m_A = " << son.Base::m_A << endl; // 100}
// 同名成员函数
void test2()
{Son son;son.func(); //直接调用  调用是子类的func函数son.Base::func();//调用父类的func函数//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏父类中所有的同名成员函数// son.func(120);son.Base::func(12);
}int main()
{// test1();test2();system("pause");return 0;
}

4.6.6继承同名静态成员处理方式

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域
  • 子类中出现和父类同名的静态成员函数,子类的同名成员会隐藏父类中所有的同名成员函数
#include<iostream>
using namespace std;
class Base
{public:static void func(){cout << "Base of func()" << endl;}static int m_A;};
int Base::m_A = 100;class Son : public Base
{public:static void func(){cout << "Son of func()" << endl;}static int m_A;
};
int Son::m_A = 120;// 同名成员属性
void test1()
{// 通过对象访问Son son;cout << "Son of m_A = " << son.m_A << endl;// 120cout << "Base of m_A = " << son.Base::m_A << endl; // 100// 通过类名访问cout << "Son of m_A = " << Son::m_A << endl; //120// 第一::代表通过类名方式访问 第二个::代表父类的作用域访问cout << "Base of m_A = " << Son::Base::m_A << endl;
}
// 同名成员函数
void test2()
{// 通过对象方式访问Son son;son.func();son.Base::func();// 通过类名方式访问/*Son of func()Base of func()*/Son::func();Son::Base::func();
}
int main()
{// test1();test2();system("pause");return 0;
}

4.6.7多继承语法

  • C++允许一个类继承多个类
  • 语法
class 子类: 继承方式 父类1,继承方式 父类2,...
  • 实际开发环境不建议多继承
#include<iostream>
using namespace std;class Base1
{public:Base1(){m_A = 110;}int m_A;
};class Base2
{public:Base2(){m_A = 120;}int m_A;
};
class Base3
{public:Base3(){m_A = 130;}int m_A;
};
class Son :public Base1, public Base2, public Base3
{public:Son() {m_A = 200;}int m_A;
};void test1()
{Son son;cout << "Son of m_A = " << son.m_A << endl;// 200cout << "Base1 of m_A = " << son.Base1::m_A << endl;// 110cout << "Base3 of m_A = " << son.Base3::m_A << endl;// 130
}
int main()
{test1();system("pause");return 0;
}

4.6.8菱形继承

8.1菱形继承概念
  • 两个派生类继承同一个基类
  • 某一个类同时继承两个派生类
  • 这种继承方式称为菱形继承钻石继承
#include<iostream>
using namespace std;
/*
* 菱形继承的问题
*   菱形继承会导致继承两份数据,资源浪费
* 解决方式----虚继承  加上virtual
*/
// 动物类
class Animal
{public:int m_Age;
};
// 羊类
class Sheep :virtual public Animal
{public:};
//驼类
class Camel:virtual public Animal
{public:};
class Alpaca:public Camel,public Sheep
{public:};void test1()
{Alpaca alpaca;alpaca.Camel::m_Age = 5;alpaca.Sheep::m_Age = 8;//cout << "驼的年龄 age = " << alpaca.Camel::m_Age << endl;// 5//cout << "羊的年龄 age = " << alpaca.Sheep::m_Age << endl;// 8//添加了virtual关键词,实现虚继承cout << "驼的年龄 age = " << alpaca.Camel::m_Age << endl;// 8cout << "羊的年龄 age = " << alpaca.Sheep::m_Age << endl;// 8cout << alpaca.m_Age << endl;// 8
}
int main()
{test1();system("pause");return 0;
}

4.7多态

4.7.1多态的基本概念

1.1多态分为两类
  • 静态多态

    • 函数重载运算符重载属于静态多态,复用函数名
  • 动态多态
    • 派生类虚函数实现运行时多态
1.2静态多态和动态多态
  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
1.3动态多态满足条件
  • 继承关系
  • 子类重写父类的虚函数(virtual)
1.4动态多态使用
  • 父类的指针引用 指向子类的对象
#include<iostream>
using namespace std;
// 多态
//动物类
class Animal
{public:virtual void speak();
};// 猫类
class Cat :public Animal
{public:void speak();
};
void Animal::speak()
{cout << "动物在讲话" << endl;
}
void Cat::speak()
{cout << "喵~喵~喵~~~" << endl;
}
// 地址早绑定  在编译阶段确定函数地址
// 如果想执行猫在说话,那么这个函数地址就不能提前绑定,需要在运行阶段绑定,地址晚绑定
// 可以加virtual,实现晚绑定
void doSpeak(Animal& animal)
{animal.speak();
}void test1()
{Cat cat;// 未加virtual// doSpeak(cat);// 动物在说话doSpeak(cat);//喵~喵~喵~~
}
int main()
{test1();system("pause");return 0;
}


注意点: 上图默认在Cat类重写了speak()函数,如果不重写speak()函数,Cat类的虚拟函数指针指向的虚拟函数表依旧是&Animal::speak

4.7.2纯虚函数和抽象函数

2.1抽象类特点
  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
  • 当类有纯虚函数,这个类也称为抽象类
#include<iostream>
using namespace std;
// 纯虚函数  和  抽象类
class Base
{public:virtual void func() = 0;
};
class Son : public Base
{public:void func() {cout << "Son类重写Base的纯虚函数" << endl;}
};void test1() {//抽象类不能实例化// Base base;//new Base;// Son son;// 子类必须重写父类中的纯虚函数,否则无法实例化对象Base* base = new Son;base->func();
}
int main()
{test1();system("pause");return 0;
}

4.7.3虚析构和纯虚析构

  • 多态使用时,如果子类中有属性开辟了堆区,那么父类指针在释放时,无法调用到子类的析构代码
  • 解决方式
    • 将父类中的析构函数改为虚析构纯虚析构
3.1虚析构和纯虚析构共性
  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现
3.2虚析构和纯虚析构区别
  • 如果是纯虚析构,该类属于抽象类无法实例化对象
3.3语法
  • 虚析构
virtual ~类名(){}
  • 纯虚析构
virtual ~类名() = 0;
类名::~类名(){}
#include<iostream>
#include<string>
using namespace std;class Animal
{public: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 name) {cout << "Cat构造函数调用" << endl;this->m_Name = new string(name);}void speak(){cout <<*m_Name<< "喵~~喵~~~喵~~~~" << endl;}~Cat(){if (m_Name != NULL) {cout << "Cat的析构函数" << endl;delete m_Name;m_Name = NULL;}}string *m_Name;
};
void test1()
{Animal* am = new Cat("Tommy");am->speak();// 父类指针在析构时候,不会调用子类的析构函数,导致子类如果在堆区属性,出现内存泄露delete am;
}int main()
{test1();system("pause");return 0;
}

C++核心编程<类和对象>(4)相关推荐

  1. 面向对象编程——类和对象

    面向对象编程 类和对象 类(Class):用来描述具有相同属性和方法的对象的集合 class Student:name='stu'#属性def say(self):#方法print("hel ...

  2. python多个对象调用类方法、且之间有联系_趣味解读Python面向对象编程 (类和对象)...

    一.面向对象简介 考虑现实生活中,我们的思维方式是放在学生这个个人上,是学生做了自我介绍.而不是像我们刚刚写出的代码,先有了介绍的行为,再去看介绍了谁. 用我们的现实思维方式该怎么用程序表达呢? 面向 ...

  3. JAVA300集——面向对象编程-类和对象-构造方法及其重载-方法调用

    本章概述 "如何开车":(由多个步骤组成),简单问题,可以使用面向过程. "如何造车":(成千上万个零件,细节很多),复杂问题,宏观上用面向对象把握,微观操作用 ...

  4. python面向对象图片_趣味解读Python面向对象编程 (类和对象)

    一.面向对象简介 考虑现实生活中,我们的思维方式是放在学生这个个人上,是学生做了自我介绍.而不是像我们刚刚写出的代码,先有了介绍的行为,再去看介绍了谁. 用我们的现实思维方式该怎么用程序表达呢?面向过 ...

  5. Day16:面向对象编程——类和对象

    一.面向对象的程序设计 在此之前用到的事面向过程的程序设计,它的核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比设计好一条流水线,考虑周全什么时候处理什么东西. 优点:极大的降低 ...

  6. 黑马程序员C++笔记--第三阶段核心:【类和对象】

    类和对象 一.封装 1.1 封装定义-属性和行为 1.2 访问权限 1.3 struct和class区别 1.4 成员属性设置为私有 练习案例1:设计立方体类 练习案例2:点和圆的关系 二.对象特性 ...

  7. Python10/22--面向对象编程/类与对象/init函数

    类: 语法: class关键字 类名# 类名规范 大写开头 驼峰命名法class SHOldboyStudent: # 描述该类对象的特征 school = "上海Oldboy" ...

  8. 【Android开发日志】Kotlin 面向对象编程 - 类与对象(11)

    Android Studio 4.0已发布,此教程已更新至4.0版本. 面向对象,顾名思义,一定得是"对象",生活当中的人.电脑.等任何事物,他们都有各自的属性,我们编程里把这些属 ...

  9. ap协议java_AP计算机科学A:Java编程类和对象

    你将学到什么 classes objects and object-oriented design fields and visibility constructors, mutators and a ...

最新文章

  1. lambda函数详解
  2. 分模块开发创建父工程——(六)
  3. Spring Cloud Netflix项目进入维护模式之我见
  4. 4.1.2 OS之文件逻辑结构(顺序文件、索引文件、索引顺序文件、多级索引顺序文件)关于数据库的索引如聚簇索引可以看一下索引文件例题的解析,感觉还是可以收获到东西的
  5. 分享 - 普通程序员如何转向AI方向
  6. MSP430程序跑飞原因
  7. java中异常注意问题(发生在多态是的异常问题)
  8. 百练 06 股票买卖
  9. asp.net JavaScriptSerializer实现序列化和反序列化
  10. maven生成jar,提示没有“没有主清单属性
  11. python常量变量和对象_Python学习笔记——变量和常量
  12. 记TensorFlow项目开源一周年
  13. OpenCV探索之路(十四):绘制点、直线、几何图形
  14. HbuilderX启动 微信开发者工具
  15. matlab模拟方孔衍射,基于MATLAB的圆孔衍射实验模拟.pdf
  16. codeblocks怎么编程c语言,如何能使用Codeblocks进行C语言编程操作.doc
  17. 有测试狗狗好坏的软件吗,6个测试判断狗狗性格,胆小或凶猛一测便知,你家狗狗是哪种?...
  18. 3.28Javase笔记day01
  19. python数据分析之Panads-1
  20. 浅谈VMware的NAT模式

热门文章

  1. Spring表单验证
  2. App 软件开发《判断4》试卷及答案
  3. git:error: open(“.vs/xxb/v17/Browse.VC.opendb“): Permission denied
  4. 北京时间换算国际时间
  5. ImageView图片圆形显示
  6. ADB 端口伪占用解决(adb自己占用自己)
  7. 大数据、人工智能学习书籍推荐——Python计算机视觉编程
  8. 2022-2028年全球与中国玻璃碎玻璃行业市场需求预测分析
  9. 全网首创Python泛娱乐新闻图片文字自动剪辑第1版
  10. es5和es6的区别、以及es6的新特性