类和对象
c++面向对象的三大特性:封装、继承、多态
1.封装
1.1 封装的意义
将属性与行为作为一个整体,表现事物
将属性行为加以权限控制
封装意义一:
在设计类的时候,属性与行为写在一起表现事物
语法:class 类名{ 访问权限: 属性/行为 };
示例:设计一个圆类,求圆周长
#include <iostream>
using namespace std;
#define pi 3.14
class Circle{
//访问权限
//公共权限
public:
//属性
int m_r; //半径
//行为
double calculateZC(){
return 2*pi*m_r;
}
};
int main(){
//通过圆类 创建具体的圆(对象)
Circle c1;
//给圆对象的属性进行赋值
c1.m_r=10;
cout<<"圆的周长为:"<<c1.calculateZC()<<endl;
return 0;
}
封装意义二:
类在设计时,可以吧属性和行为放在不同权限下加以控制
访问权限有三种:
public 公共权限 成员 类内、类外均可访问
protected 保护权限 成员 类内可访问,类外不可访问 子类可访问父类的保护内容
private 私有权限 成员 类内可访问,类外不可访问 子类不可访问父类的私有内容
#include <iostream>
#include<string>
using namespace std;
class Person{
public: //公共权限
string Name; //姓名
protected: //保护权限
string Car; //汽车
private: //私有权限
int Password;
public:
void func(){
Name="张三";
Car="拖拉机";
Password=123456;
}
};
int main(){
Person p1;
p1.Name="李四";
p1.Car="奔驰"; //非法语句,保护权限内容类外不可访问
p1.Password=123; //非法语句,私有权限内容类外不可访问
return 0;
}
1.2 class 和 struct 区别
在于默认的访问权限不同:
struct 默认权限为公有
class 默认权限为私有
1.3 成员属性设置为私有
优点一:将所有成员属性设为私有,可自己控制读写权限
优点二:对于写权限,我们可以检测数据有效性
实例:创建圆和点类,判断点和圆的关系:
#include <iostream>
using namespace std;
class Point{
public:
//设置x坐标
void set_X(int x){
m_X=x;
}
//设置Y坐标
void set_Y(int y){
m_Y=y;
}
//获取x坐标
int get_X(){
return m_X;
}
//获取Y坐标
int get_Y(){
return m_Y;
}
private:
int m_X;
int m_Y;
};
class Circle{
public:
//设置半径
void setm_R(int r){
m_R=r;
}
//获取半径
int getm_R(){
return m_R;
}
//设置圆心
void setm_Center(Point center){
m_Center=center;
}
//获取圆心
Point getm_Center(){
return m_Center;
}
private:
int m_R; //长
Point m_Center; //圆心点 在类中可以让另一类作本类成员
};
//判断点与圆的关系
void judge(Point &point, Circle &circle){
//计算两点间距离平方
int delta_s2=
(circle.getm_Center().get_X()-point.get_X())*(circle.getm_Center().get_X()-point.get_X())+
(circle.getm_Center().get_Y()-point.get_Y())*(circle.getm_Center().get_Y()-point.get_Y());
//计算半径平方
int r2=circle.getm_R()*circle.getm_R();
//判断关系
if(delta_s2>r2)cout<<"点在圆外!"<<endl;
else if(delta_s2<r2)cout<<"点在圆内!"<<endl;
else cout<<"点在圆上!"<<endl;
}
int main(){
Point point;
point.set_X(10);
point.set_Y(2);
Point center;
center.set_X(10);
center.set_Y(0);
Circle circle;
circle.setm_R(10);
circle.setm_Center(center);
judge(point,circle);
return 0;
}
ATTENTION! 这样的代码在工程上是不适合的——所有的类都在一个文件中,应分文件编写,具体步骤如下:
创建头文件:
创建源文件
同理创建circle的头文件与源文件
主文件仅需保留主函数与判断函数
2.对象的初始化和清理
2.1构造函数与析构函数
由编译器自动调用,完成对象初始化与清理。如果我们不构造与析构,编译器会提供,但编译器提供的构造与析构是空实现。
构造函数:用于创建对象时为对象属性赋值
析构函数:对象销毁前系统自动调用,执行清理
构造函数语法:类名(){}
构造函数,无返回值也不写void
函数名与类名相同
可有参数,可重载
程序调用对象时自动调用构造,无需手动且只调一次
析构函数语法:~类名(){}
析构函数,无返回值也不写void
函数名与类名相同,名称前加~
不可有参数,不可重载
程序对象销毁时自动调用析构,无需手动且只调一次
2.2构造函数的分类及调用
按参数分类:有参构造、无参构造
按类型分类:普通构造与拷贝构造
三种调用方式:括号法、显示法、隐式转换法
#include<iostream>
using namespace std;
//构造函数 初始化操作
class Person{
public:
Person(){
cout<<"Person无参构造函数的调用"<<endl;
}
Person(int a){
age=a;
cout<<"Person(int a)有参构造函数的调用"<<endl;
}
//拷贝构造
Person(const Person &p){
cout<<"Person(const Person &p)拷贝构造函数的调用"<<endl;
age=p.age;
}
~Person(){
cout<<"Person析构函数的调用"<<endl;
}
int age;
};
void test01(){
// 1.括号法
// Person p1; //默认构造的调用,注意不要用括号,否则编译器认为是函数声明而非对象创建
// Person p2(10); //有参构造调用
// Person p3(p2); //拷贝构造的调用
// cout<<"年龄为:"<<p2.age<<endl;
// cout<<"年龄为:"<<p3.age<<endl;
//2.显示法
Person p1;
Person p2=Person(10);
Person p3=Person(p2);
//Person(p3); 不要用拷贝构造函数初始化匿名对象
//3.隐式转换法
Person p4=10; //相当于Person p4=Person(10);
Person p5=p4; //相当于Person p5=Person(p4);
}
int main(){
test01();
system("pause");
return 0;
}
2.3 拷贝构造函数调用时机
通常三种情况:
使用一个已经创建完毕的对象初始化一个新对象
值传递的方式给函数参数传值
以值方式返回局部对象
#include<iostream>
using namespace std;
//构造函数 初始化操作
class Person{
public:
Person(){
cout<<"Person无参构造函数的调用"<<endl;
}
Person(int age){
m_age=age;
cout<<"Person(int age)有参构造函数的调用"<<endl;
}
//拷贝构造
Person(const Person &p){
cout<<"Person(const Person &p)拷贝构造函数的调用"<<endl;
m_age=p.m_age;
}
~Person(){
cout<<"Person析构函数的调用"<<endl;
}
int m_age;
};
//1.用一个已经创建完毕的对象初始化一个新对象
void test01(){
Person p1(20);
Person p2(p1);
cout<<"p2的年龄为:"<<p2.m_age<<endl;
}
//2.值传递的方式给函数参数传值
void doo(Person pp){
}
void test02(){
Person p;
doo(p);
}
//3.值方式返回局部对象
Person dooo(){
Person p3;
return p3;
}
void test03(){
Person P=dooo();
}
int main(){
//test01();
//test02();
test03();
system("pause");
return 0;
}
2.4 构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数:
默认构造(无参,函数体为空)
默认析构(无参,函数体为空)
默认拷贝
调用规则:
若用户定义有参构造函数,c++不再提供默认无参,但会提供默认拷贝
若用户定义拷贝,c++不会提供其他构造函数
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(int age)有参构造函数的调用"<<endl;
}
//拷贝构造
Person(const Person &p){
cout<<"Person(const Person &p)拷贝构造函数的调用"<<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(20,172);
cout<<"p1年龄为:"<<p1.m_age<<endl;
cout<<"p1身高为:"<<*p1.m_height<<endl;
Person p2(p1);
cout<<"p2的年龄为:"<<p2.m_age<<endl;
cout<<"p2身高为:"<<*p2.m_height<<endl;
}
int main(){
test01();
system("pause");
return 0;
}
SUMMARY: 如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝问题
2.6 初始化列表
语法:构造函数():属性1(值1),属性2(值2),...{}
eg.
class Person{
public:
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;
};
2.7 类对象作为类成员
当其他类的对象作为本类成员,先构造其他类对象,再构造自身;先析构自身,再析构其他类对象
#include<iostream>
#include<string>
using namespace std;
class Phone{
public:
Phone(string Brand,int num){
P_Brand=Brand;
P_num=num;
cout<<"Phone构造函数调用"<<endl;
}
string P_Brand; //手机品牌
int P_num; //手机号
};
class Person{
public:
Person(string Name,string Brand,int num):P_Name(Name),P_Phone(Brand,num){
cout<<"Person的构造函数调用"<<endl;
}
string P_Name; //姓名
Phone P_Phone; //手机
};
void test01(){
Person p1("昊京","苹果14",1232343);
cout<<p1.P_Name<<"拿着一个"<<p1.P_Phone.P_Brand<<"牌子的手机,手机号是:"
<<p1.P_Phone.P_num<<endl;
}
int main(){
test01();
system("pause");
return 0;
}
2.8 静态成员
即在成员变量和成员函数前加关键字static,成为静态成员
静态成员变量
所有对象共享一份数据
在编译阶段分配内存(全局区)
类内声明,类外初始化
静态成员函数
所有对象共享一个函数
静态成员函数只能访问静态成员变量
#include <iostream>
using namespace std;
class Person{
public:
static int m_A;
int m_C;
static void func(){
m_A=0;
//m_C=1; 静态成员函数仅能访问静态成员变量
cout<<"static void func的调用"<<endl;
}
protected:
static int m_B;
};
int Person::m_A=100;
void test01(){
Person p;
cout<<p.m_A<<endl;
//Person p;
p.func();
cout<<Person::m_A<<endl; //可通过类名访问静态成员变量
//cout<<Person::m_B<<endl; //类外访问不到私有与保护
Person::func();
}
void main(){
test01();
}
3. c++对象模型和this指针
3.1 成员变量和成员函数分开存储
非静态成员变量 属于类的对象
静态成员变量 不属于类对象
非静态成员函数 不属于类对象
静态成员函数 不属于类对象
空类所占内存空间为1,编译器为空对象分1字节空间以区分空对象占内存位置
3.2 this指针
this指针指向被调用的成员函数所属的对象,本质是指针常量
用途:
当形参与成员变量同名时,可用其区分
在类的非静态成员函数中返回对象本身,可用return *this
#include <iostream>
using namespace std;
class Person{
public:
Person(int age){
this->age=age; //this指向被调用的成员函数所属对象
}
Person &PersonADDage(Person p){
//返回值类型为引用才能返回自己,若去掉引用,则为值返回局部对象,将调用拷贝构造函数产生新对象
this->age+=p.age;
return *this;
}
int age;
};
void test01(){
Person p1(18);
cout<<"p1年龄为:"<<p1.age<<endl;
}
void test02(){
Person p2(10);
Person p3(10);
p3.PersonADDage(p2).PersonADDage(p2);
cout<<"p3的年龄为"<<p3.age<<endl;
}
void main(){
test02();
}
3.3 空指针访问成员函数
#include <iostream>
using namespace std;
class Person{
public:
void showClassName(){
cout<<"this is Person class!"<<endl;
}
void showPersonAge(){
/* 此段可提升代码健壮性,即使空指针访问有属性成员函数也不会报错
if(this==NULL){
return;
}
*/
cout<<"age="<<m_Age<<endl; //实际上此句为cout<<"age="<<this->m_Age<<endl;
}
int m_Age;
};
void test01(){
Person *p=NULL;
p->showClassName();
p->showPersonAge(); //非法操作,因为p为空指针,并无实际对象,加上/*...*/后才正常
}
void main(){
test01
();
}
3.4 const修饰成员函数
常函数:
成员函数后加const
常函数内不可修改成员属性
成员属性声明加关键字mutable后在常函数中可修改
常对象:
声明对象前加const为常对象
常对象只能调用常函数
#include <iostream>
using namespace std;
class Person{
public:
void showPerson()const{ //成员函数后加const,修饰的是this指针指向值,令其不可改
this->m_A=100; //非法操作
this->m_B=100; //合法操作
}
void func(){
}
int m_A;
mutable int m_B; //加mutable关键字,使属性可以在常函数中修改
};
void test01(){
Person p;
p.showPerson();
}
void test02(){
const Person pp;
pp.m_A=1; //非法操作,常对象属性不可改
pp.m_B=1;
pp.showPerson();
pp.func(); //非法操作,常对象只能调常函数,因为普通函数可修改属性
}
void main(){
test01();
test02();
}
4. 友元
目的:让一个函数或类访问另一个类中私有成员
关键字:friend
实现:
全局函数作友元
类作友元
成员函数作友元
#include <iostream>
#include<string>
using namespace std;
class Building;
class GoodGay1{
public:
Building *m_building;
GoodGay1();
void visit();
};
class GoodGay2{
public:
Building *mm_building;
GoodGay2();
void vvisit();
};
class Building{
friend void goodgay(Building &build);
friend class GoodGay1;
friend void GoodGay2::vvisit();
public:
Building();
string m_SittingRoom;
private:
string m_BedRoom;
};
Building::Building(){
m_SittingRoom="客厅";
m_BedRoom="卧室";
}
GoodGay1::GoodGay1(){
m_building=new Building;
}
GoodGay2::GoodGay2(){
mm_building=new Building;
}
void GoodGay1::visit(){
cout<<"GoodGay类正在访问:"<<m_building->m_SittingRoom<<endl;
cout<<"GoodGay类正在访问:"<<m_building->m_BedRoom<<endl;
}
void GoodGay2::vvisit(){
cout<<"GoodGay2类中vvsit()函数正在访问"<<mm_building->m_SittingRoom<<endl;
cout<<"GoodGay2类中vvsit()函数正在访问"<<mm_building->m_BedRoom;
}
void test01(){
GoodGay1 GG;
GG.visit();
}
void test02(){
GoodGay2 Gg;
Gg.vvisit();
}
void goodgay(Building &build){
cout<<"全局函数goodgay正在访问"<<build.m_SittingRoom<<endl;
cout<<"全局函数goodgay正在访问"<<build.m_BedRoom<<endl;
}
void main(){
Building building;
goodgay(building);
test01();
test02();
}
5. 运算符重载
5.1 加号运算符重载
实现自定义数据相加
#include <iostream>
#include<string>
using namespace std;
class Person{
public:
int m_A;
int m_B;
/*成员函数重载
Person operator+(Person &pers){
Person temp;
temp.m_A=this->m_A+pers.m_A;
temp.m_B=this->m_B+pers.m_B;
return temp;
}*/
};
/*全局函数重载*/
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 &p,int a){
Person temp;
temp.m_A=p.m_A+a;
temp.m_B=p.m_B+a;
return temp;
}
void test01(){
Person p1;
p1.m_A=10;
p1.m_B=10;
Person p2;
p2.m_A=20;
p2.m_B=20;
Person p3=p1+p2;
cout<<p3.m_A<<'\t'<<p3.m_B<<endl;
Person p4=p2+100;
cout<<p4.m_A<<'\t'<<p4.m_B<<endl;
}
void main(){
test01();
}
5.2 左移运算符重载
输出自定义数据类型
#include<iostream>
using namespace std;
// 左移运算符重载
// 作用:可以输出自定义数据类型
class Person {
// 友元,目的是让重载<<运算符的函数也可打印Person中的私有属性
friend ostream& operator<<(ostream& cout, Person& p);
friend void test01();
private:
int m_A;
int m_B;
// 通常不会用成员函数重载左移运算符,
//若void operator<<(Person &p),则p.operator<<(p),是错误的
// 而void operator<<(cout),会变成p<<cout,cout在右边,实际应该是cout<<P,在左边
};
// 本质 operator<<(cout,p),简化cout<<p;
//链式规则,返回cout,可以无限追加
// 如何知道cout属于什么数据类型呢?点击cout,右键:转到定义(或者按F12)
ostream& operator<<(ostream &cout,Person &p) {
cout << "p.m_A=" << p.m_A << " " << "p.m_B=" << p.m_B;
return cout;
}
void test01() {
Person p;
p.m_A = 10;
p.m_B = 10;
cout << "这是普通输出:" << endl;
cout << "p.m_A=" << p.m_A << " " << "p.m_B=" << p.m_B <<endl;
//链式规则,返回cout,可以无限追加
cout << endl << "链式规则:" << endl;
cout << p <<endl<<"hello,world"<<endl;
}
int main() {
test01();
system("pause");
return 0;
}
5.3 递增运算符重载
#include<iostream>
using namespace std;
class MyInteger{
friend ostream &operator<<(ostream &out,MyTnteger &myint);
public:
MyInteger(){
m_Num=0;
}
//重载前置++运算符
MyInteger& operator++(){
/*为什么返回引用不返回值?如果返回值,实际上返回了一个新对象而非本来的对象。返回引用是为了对一个对象进行操作*/
m_Num++; //先++
return *this; //再返回
/*若无return,则返回类型为void,错误;应返回对象自身,因此用this指针(返回被调用的成员函数的对象)*/
}
//重载后置++运算符
MyInteger operator++(int){ //注意参数列表要加int(一个占位参数,区分前置和后置递增),否则函数无法重载
/*为什么返回值不返回引用?若返回引用,返回结果实际是局部对象的引用,函数执行后该局部对象立即被释放,不可作返回值*/
//注意后置递增是先执行表达式后++操作
MyInteger temp=*this; //先 记录结果
m_Num++; //后递增
return temp; //最后返回记录的结果
}
private:
int m_Num;
};
//重载左移运算符
ostream &operator<<(ostream &out,MyTnteger &myint){
out<<myint.m_Num;
return out;
}
void test01(){
MyInteger myint;
cout<<++myint<<endl;
}
void test02(){
MyInteger myint;
cout<<myint++<<endl;
cout<<myint<<endl;
}
void main(){
test01();
test02();
}
5.4 赋值运算符重载
#include <iostream>
using namespace std;
class Person{
public:
Person(int age){
m_Age=new int(age);
}
//重载赋值运算符
Person &operator=( Person &p){
/*为什么返回引用不返回值?如果返回值,实际上返回了一个新对象而非本来的对象。返回引用是为了对一个对象进行操作*/
// m_Age=p.m_Age; //编译器提供的浅拷贝,但析构时会发生堆区数据重复释放
//1.先判断是否有属性在堆区,如果有,先释放干净
if(m_Age!=NULL){
delete m_Age;
m_Age=NULL;
}
//2.再进行深拷贝操作
m_Age=new int(*p.m_Age);
return *this;
}
~Person(){
if(m_Age!=NULL){
delete m_Age;
m_Age=NULL;
}
}
int *m_Age;
};
void test01(){
Person p1(23);
cout<<"p1年龄为:"<<*p1.m_Age<<endl;
Person p2(18);
cout<<"p2年龄为:"<<*p2.m_Age<<endl;
p2=p1;
cout<<"p1年龄为:"<<*p1.m_Age<<"p2年龄为:"<<*p2.m_Age<<endl;
}
void main(){
test01();
}
5.5 关系运算符重载
#include <iostream>
#include<string>
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;
}
else return false;
}
//重载!=
bool operator!=(Person &p){
if(this->m_Name!=p.m_Name||this->m_Age!=p.m_Age){
return true;
}
else return false;
}
string m_Name;
int m_Age;
};
void test01(){
Person p1("Tom",23);
Person p2("Jerry",23);
if(p2==p1)cout<<"p1=p2"<<endl;
if(p2!=p1)cout<<"p1!=p2"<<endl;
}
void main(){
test01();
}
5.6 函数调用运算符重载
#include <iostream>
#include<string>
using namespace std;
class MyPrint{
public:
void operator()(string str){
cout<<str<<endl;
}
int operator()(int num1,int num2){
return num1+num2;
}
};
int main(){
MyPrint myprint;
myprint("Hello SJTU!");
myprint("Hello SJTU!");
int a=myprint(1,2);
int b=myprint(1,2);
cout<<a<<'\t'<<b;
cout<<MyPrint()(100,100)<<endl; //匿名对象调用
return 0;
}
6. 继承
6.1 语法:class 子类:继承方式 父类
#include <iostream>
#include<string>
using namespace std;
class BasePage{
public:
void header(){
cout<<"首页 公开课 登录 注册 ···"<<endl;
}
void footer(){
cout<<"帮助 交流 地图···"<<endl;
}
void left(){
cout<<"Java c++ Python"<<endl;
}
};
class Java:public BasePage{
public:
void content(){
cout<<"Java course"<<endl;
}
};
class Cpp:public BasePage{
public:
void content(){
cout<<"c++ course"<<endl;
}
};
void test01(){
cout<<"Java page"<<endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout<<"c++ page"<<endl;
Cpp c;
c.header();
c.footer();
c.left();
c.content();
}
int main(){
test01();
return 0;
}
6.2 继承方式
公共继承
保护继承
私有继承
class Base{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son1:public Base{
public:
void func(){
m_A=10; //父类中public成员,到子类中依然public
m_B=20; //父类中protected成员,到子类中依然protected
//m_C=30; //父类中private成员,到子类中private,子类无法访问
}
};
void test01(){
Son1 s1;
s1.m_A=100;
//s1.m_B=200; //非法操作,保护权限类外不可访问
//s1.m_C=300; //非法操作,私有权限类外不可访问
}
class Son2:protected Base{
public:
void func(){
m_A=40; //父类中public成员,到子类中protected
m_B=50; //父类中protected成员,到子类中依然protected
//m_C=60; //父类中private成员,到子类中private,子类无法访问
}
};
class Son3:private:Base{
public:
void func{}{
m_A=70; //父类中public成员,到子类中private
m_B=80; //父类中protected成员,到子类中private
//m_C=90; //父类中private成员,到子类中private,子类无法访问
}
};
class GrandSon:public Son3{
public:
void func(){
//m_A=12; //非法,在Son3中已是private
//m_B=23; //非法,在Son3中已是private
//m_C=34; //非法,在Son3中已是private
}
};
6.3 继承中对象模型
子类会全部继承父类的属性与行为,只是根据继承方式不同编译器会隐藏某些属性行为
6.4 继承中构造和析构顺序
#include <iostream>
using namespace std;
class Base{
public:
Base(){
cout<<"Base的构造函数!"<<endl;
}
~Base(){
cout<<"Base的析构函数!"<<endl;
}
};
class Son:public Base{
public:
Son(){
cout<<"Son的构造函数!"<<endl;
}
~Son(){
cout<<"Son的析构函数!"<<endl;
}
};
void test01(){
//Base base;
Son son;
}
int main(){
test01();
return 0;
}
6.5 继承中同名成员处理
访问子类同名成员 直接访问
访问父类同名成员 加作用域
#include <iostream>
using namespace std;
class Base{
public:
int m_A;
Base(){
m_A=100;
}
void func(){
cout<<"Base中func调用"<<endl;
}
void func(int a){
cout<<"Base中func(int a)调用"<<endl;
}
};
class Son:public Base{
public:
int m_A;
Son(){
m_A=200;
}
void func(){
cout<<"Son中func调用"<<endl;
}
};
void test01(){
Son son;
cout<<"son.m_A="<<son.m_A<<endl;
cout<<"Base中 m_A="<<son.Base::m_A<<endl; //通过子类对象访问父类中同名属性需加作用域
}
void test02(){
Son s;
s.func();
/*若子类中出现与父类同名成员函数,子类同名成员会隐藏父类中所有同名成员函数,若想访问到父类中被隐藏的同名成员函数,需加作用域*/
s.Base::func();
s.Base::func(100);
}
int main(){
test01();
test02();
return 0;
}
6.6 继承同名静态成员处理
静态同名与非静态处理方式一致
还可通过类名直接访问:类名::静态属性、子类名::父类名::静态属性
6.7 多继承语法
c++允许一个类继承多个类
语法:class 子类:继承方式 父类1,继承方式 父类2···
多继承可能引发同名成员,需加作用域区分。实际工程不建议使用
6.8 菱形继承
概念:两个派生类继承同一个基类,又有某个类同时继承这两个派生类
7.多态
7.1 多态概念
分类:
静态多态:函数重载 运算符重载属于静态
动态多态:派生类与虚函数实现运行时多态
区别:
静态多态的函数地址早绑定——编译阶段确定函数地址
动态多态的函数地址晚绑定——运行阶段确定函数地址
#include <iostream>
using namespace std;
class Animal{
public:
virtual void speak(){ //虚函数 若无virtual,地址早绑定,编译阶段确定函数地址
cout<<"animal is talking!"<<endl;
}
/*此函数写完后类内产生一个指针——vfptr(virtual function pointer),指向虚函数表vftable(表内存有虚函数地址&Animal::speak)*/
};
class Cat: public Animal{
public:
void speak(){ //virtual可加可不加
cout<<"kitten is speaking!"<<endl;
}
/*若无子类重写,则子类由父类继承而来指针——vfptr(virtual function pointer),指向虚函数表vftable(表内存有虚函数地址&Animal::speak);但虚函数重写后子类中虚函数表内容会替换为子类虚函数地址&Cat::speak(父类的不变)*/
};
//多态条件:
//1.有继承关系
//2.子类重写父类虚函数,重写时virtual关键字可加可不加
//动态多态使用:父类的指针或引用指向子类对象
void doSpeak(Animal &animal){ //父类的引用指向子类对象
animal.speak();
}
void test01(){
Cat cat;
doSpeak(cat);/*此句相当于Animal &animal=cat;当animal调用speak()函数,由于animal指向cat,所以在子类中虚函数表中找speak()*/
}
int main(){
test01();
return 0;
}
案例:利多态实现加减乘计算
#include <iostream>
using namespace std;
class AbstractCalculater{
public:
int m_Num1;
int m_Num2;
virtual int getResult()=0;
/*{
return 0; 父类虚函数实现无意义,故写为纯虚函数
}*/
};
//加法类
class AddCalculater:public AbstractCalculater{
public:
virtual int getResult(){
return m_Num1+m_Num2;
}
};
//减法类
class SubCalculater:public AbstractCalculater{
public:
virtual int getResult(){
return m_Num1-m_Num2;
}
};
//乘法类
class MultiCalculater:public AbstractCalculater{
public:
virtual int getResult(){
return m_Num1*m_Num2;
}
};
void test(){
//加法
AbstractCalculater *calculater=new AddCalculater;
calculater->m_Num1=40;
calculater->m_Num2=10;
cout<<calculater->m_Num1<<"+"<<calculater->m_Num2<<"="<<calculater->getResult()<<endl;
delete calculater; // 堆区开辟的数据记得释放
//减法
calculater=new SubCalculater;
calculater->m_Num1=40;
calculater->m_Num2=10;
cout<<calculater->m_Num1<<"-"<<calculater->m_Num2<<"="<<calculater->getResult()<<endl;
delete calculater; // 堆区开辟的数据记得释放
//乘法
calculater=new MultiCalculater;
calculater->m_Num1=40;
calculater->m_Num2=10;
cout<<calculater->m_Num1<<"*"<<calculater->m_Num2<<"="<<calculater->getResult()<<endl;
delete calculater; // 堆区开辟的数据记得释放
}
int main(){
test();
return 0;
}
优点:
组织结构清晰
可读性强
对于后期扩展及维护性好
7.3 纯虚函数和抽象类
多态中,通常父类中虚函数的实现无意义,主要是调用子函数重写的内容,因此可将虚函数改为纯虚函数
语法:virtual 返回值类型 函数名(参数列表)=0;
当类中有纯虚函数,类称抽象类。其无法实例化对象,且子类必须重写纯虚函数,否则也属于抽象类
7.4 虚析构与纯虚析构
多态使用时,如果子类中有属性开辟至堆区,那么父类纸质恩释放时无法调用子类析构代码
解决:将 父类 中析构改为虚析构或纯虚析构
虚析构语法:~virtual 类名(){}
纯虚析构语法:~virtual 类名()=0; 且要在类外完成其实现,类名::~类名(){}
#include <iostream>
#include<string>
using namespace std;
class Animal{
public:
virtual void speak()=0;
Animal(){
cout<<"Animal构造调用!"<<endl;
}
//virtual ~Animal()=0; 纯虚析构函数,若要用,须在类外给出纯虚析构函数体以防父类开辟到堆区数据
~virtual Animal(){ //若无virtual,父类指针析构时不会调用子类析构,导致子类中若有堆区属性会发生内存泄漏
cout<<"Animal析构调用!"<<endl;
}
};
//Animal:: ~Animal(){} 纯虚析构类外实现
class Cat: public Animal{
public:
Cat(string name){
cout<<"Cat构造调用!"<<endl;
m_Name=new string(name);
}
void speak(){
cout<<*m_Name<<"_the kitten is speaking!"<<endl;
}
~Cat(){
if(m_Name!=NULL){
cout<<"Cat析构调用!"<<endl;
delete m_Name;
m_Name=NULL;
}
}
string *m_Name;
};
void test(){
Animal *animal=new Cat("Tom");
animal->speak();
//父类指针析构时不会调用子类析构,导致子类中若有堆区属性会发生内存泄漏
delete animal;
}
int main(){
test();
return 0;
}
实例:电脑组装
电脑主要零件:CPU、显卡、内存条。将每个零件封装出抽象基类,并提供不同厂商的零件,创建电脑类让电脑工作的函数,并调用每个零件工作接口。测试组装三台电脑
#include <iostream>
using namespace std;
class CPU{
public:
virtual void calculate()=0;
};
class VideoCard{
public:
virtual void display()=0;
};
class Memory{
public:
virtual void storage()=0;
};
class Computer{
public:
Computer(CPU *cpu,VideoCard *vc,Memory *mem){
m_cpu=cpu;
m_vc=vc;
m_mem=mem;
}
void work(){
m_cpu->calculate();
m_vc->display();
m_mem->storage();
}
~Computer(){
delete m_cpu;
delete m_vc;
delete m_mem;
}
private:
CPU *m_cpu;
VideoCard *m_vc;
Memory *m_mem;
};
//Intel
class IntelCPU:public CPU{
public:
virtual void calculate(){
cout<<"Intel CPU is calculating!"<<endl;
}
};
class IntelMemory:public Memory{
public:
virtual void storage(){
cout<<"Intel Memory is storagting!"<<endl;
}
};
class IntelVideoCard:public VideoCard{
public:
virtual void display(){
cout<<"Intel VideoCard is displaying!"<<endl;
}
};
//Lenovo
class LenovoCPU:public CPU{
public:
virtual void calculate(){
cout<<"Lenovo CPU is calculating!"<<endl;
}
};
class LenovoMemory:public Memory{
public:
virtual void storage(){
cout<<"Lenovo Memory is storagting!"<<endl;
}
};
class LenovoVideoCard:public VideoCard{
public:
virtual void display(){
cout<<"Lenovo VideoCard is displaying!"<<endl;
}
};
void test(){
CPU *intelcpu=new IntelCPU;
VideoCard *intelvideocard=new IntelVideoCard;
Memory *intelmemory=new IntelMemory;
Computer *computer=new Computer(intelcpu,intelvideocard,intelmemory);
computer->work();
delete computer;
}
int main(){
test();
return 0;
}