c++类和对象
目录
一,对象的初始化和清理
1、构造函数和析构函数
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名(){}
1,构造函数,没有返回值也不写void
2,函数名称与类名相同
3,构造函数可以有参数,因此可以发生重载
4,程序在调用对象时会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法:~类名(){}
1,析构函数,没有返回值也不写void
2,函数名称与类名相同,在名称前加上符号~
3,析构函数不可以有参数,因此不可以发生重载
4,程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
class Person
{
public:
//1,1、构造函数
Person()
{
cout<<"Person 的构造函数调用"<<endl;
}
//1,2、析构函数
~Person()
{
cout<<"Person 的析构函数调用"<<endl;
}
};
void test01()
{
Person A;//在栈上的数据,test01执行完毕后,释放这个对象,释放后才会调用析构函数
}
int main()
{
test01();
return 0;
}
2、构造函数的分类及调用
两种分类方式:
按参数分为:有参构造和无参构造
按类型分为:普通构造和拷贝构造
三种调用方式:
1,括号法? ?2,显示法? 3,隐式转换法
//构造函数的分类及调用
//分类
// 按照参数分类 无参构造(默认构造)和有参构造
// 按照类型分类 普通构造 拷贝构造
class Person
{
public:
//普通构造函数 无参构造
Person()
{
cout<<"Person 的无参构造函数调用"<<endl;
}
Person(int a)//有参构造
{
age=a;
cout<<"Person 的有参析构造函数调用"<<endl;
}
//拷贝构造函数
Person(const Person& p)//注意要用const和引用
{
//将传入的人身上的所有属性,拷贝到我身上
age=p.age;
cout<<"Person 的拷贝函数调用"<<endl;
}
int age;
};
//调用
void test01()
{
//1、括号法
Person p1;//默认构造函数调用
Person p2(10);//有参构造函数
Person p3(p2);//拷贝构造函数调用
//注意事项
//调用默认构造函数时候,不要加()
//因为下面这行代码,编译器会认为是一个函数的声明,不会认为在创建对象
//Person p1();
//cout<< "p2的年龄: "<< p2.age <<endl;
//cout<< "p3的年龄: "<< p3.age <<endl;
//2、显示法
Person p1;
Person p2 = Person(10);//有参构造
Person p3 = Person(p2);//拷贝构造
Person(10);//匿名对象 特点:当前执行结束后,系统会立即收掉匿名对象
//注意事项
//不要利用拷贝构造函数 初始化匿名对象 编译器会认为Person(p3)==Person p3;对象声明
//Person(p3);
//3、隐式转换法
Person p4 = 10;//相当于写了Person p4 = Person(10); 有参构造
Person p5 = p4;//拷贝构造
}
int main()
{
test01();
return 0;
}
3、拷贝构造函数调用时机
c++中拷贝构造函数调用时机通常有三种情况:
1,使用一个已经创建完毕的对象来初始化一个新对象
2,值传递的方式给函数参数传值
3,以值方式返回局部对象
//拷贝构造函数的调用时机
//1,使用一个已经创建完毕的对象来初始化一个新对象
//2,值传递的方式给函数参数传值
//3,值方式返回局部对象
class Person
{
public:
Person()
{
cout<<"Person 的默认构造函数"<<endl;
}
Person(int age)
{
m_age=age;
cout<<"Person 的有参构造函数"<<endl;
}
Person(const Person& p)
{
m_age=p.m_age;
cout<<"Person 的拷贝构造函数调用"<<endl;
}
~Person()
{
cout<<"Person 的析构函数调用"<<endl;
}
int m_age;
};
//1,使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
Person p1(20);
Person p2(p1);
cout<<"p2的年龄为: "<<p2.m_age<<endl;
}
//2,值传递的方式给函数参数传值
void dowork(Person p)
{
}
void test02()
{
Person p;
dowork(p);
}
//3,值方式返回局部对象
Person dowork2()
{
Person p1;
return p1;
}
void test03()
{
Person p=dowork2();
}
int main()
{
test01();
test02();
test03();
return 0;
}
4、构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
1、默认构造函数(无参,函数体为空)
2、默认析构函数(无参,函数体为空)
3、默认拷贝构造函数,对属性进行值拷贝
?构造函数调用规则如下:
1、如果用户定义有参数构造函数,c++不在提供默认无参构造函数,但是会提供默认拷贝构造
2、如果用户定义拷贝构造函数,c++不会再提供其他构造函数
5、深拷贝与浅拷贝
深拷贝:在堆区重新申请空间,进行拷贝操作
浅拷贝:简单的赋值拷贝操作
//如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
class Person
{
public:
//无参(默认)构造函数
Person()
{
cout<<"无参构造函数!"<<endl;
}
//有参构造函数
Person(int age,int height)
{
cout<<"有参构造函数!"<<endl;
//浅拷贝操作
m_age=age;
m_height=new int(height);
}
//拷贝构造函数
Person(const Person& p)
{
cout<<"拷贝构造函数!"<<endl;
//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
m_age=p.m_age;//浅拷贝,直接赋值
m_height=new int(*p.m_height);//深拷贝,将p.m_height指向的地址存放的值重新申请一个内存,用于自身的拷贝函数
}
//析构函数
~Person()
{
cout<<"析构函数"<<endl;
if(m_height!=NULL)//在堆区申请的空间需要我们自己释放,所以需要这一步操作
{
delete m_height;
m_height=NULL;//避免野指针的出现
}
}
public:
int m_age;
int *m_height;
};
void test01()
{
Person p1(18,160);
Person p2(p1);
cout<<"p1的年龄: "<<p1.m_age<<" 身高: "<<*p1.m_height<<endl;
cout<<"p2的年龄: "<<p2.m_age<<" 身高: "<<*p2.m_height<<endl;
}
int main()
{
test01();
return 0;
}
6、初始化列表
作用:c++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)……{}
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){}
void printPerson()
{
cout<<"m_a: "<<m_a<<endl;
cout<<"m_b: "<<m_b<<endl;
cout<<"m_c: "<<m_c<<endl;
}
private:
int m_a,m_b,m_c;
};
int main()
{
Person p(1, 2, 3);
p.printPerson();
return 0;
}
7、类对象作为类成员
c++类的成员可以是另一个类的对象,我们称该成员为对象成员
例如:
class A{}
class B
{
A a;
}
B类中有对象A作为成员,A为对象成员
那么当创建B对象时,A与B的构造和析构的顺序是谁先谁后?
//手机类
class Phone
{
public:
Phone(string pName)
{
cout<<"Phone的构造函数调用"<<endl;
m_Pname=pName;
}
~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;
};
//当其他类作为本类成员,构造时候先构造类对象,再构造自身,析构的顺序与构造相反
void test01()
{
Person p("张三","苹果MAX");
cout<<p.m_Name<<"拿着: "<<p.m_Phone.m_Pname<<endl;
}
int main()
{
test01();
return 0;
}
8、静态成员
静态成员就是再成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
1.静态成员变量
? ? ? ? ?1.1所有对象共享同一份数据
? ? ? ? 1.2在编译阶段分配内存
? ? ? ? 1.3类内声明,类外初始化
2.静态成员函数
? ? ? ? 1.1所有对象共享同一个函数
? ? ? ? 1.2静态成员函数只能访问静态成员变量
#include <bits/stdc++.h>
using namespace std;
//静态成员变量
class Person
{
public:
//1所有对象共享同一份数据
//2在编译阶段分配内存
//3类内声明,类外初始化
static int m_a;
//静态成员变量也是有访问权限的
private:
static int m_b;
};
//Person作用域下的m_a初始化
int Person::m_a=100;
int Person::m_b=200;
void test01()
{
Person p;
cout<<p.m_a<<endl;//100
Person p2;
p2.m_a=200;
cout<<p.m_a<<endl;//200
}
void test02()
{
//静态成员变量 不属于某个对象上,所有对象都共享同一份数据
//因此静态成员变量有两种访问方式
//1、通过对象访问
Person p;
cout<<p.m_a<<endl;
//2、通过类名进行访问
cout<<Person::m_a<<endl;
//cout<<"m_b = "<<Person::m_b<<endl;私有权限访问不到,会报错
}
//静态成员函数
//所有对象共享同一个函数
//静态成员函数只能访问静态成员变量
class Person1
{
public:
static void func()//静态成员函数
{
m_A=10;//静态成员函数可以访问静态成员变量
//m_B=20;//静态成员函数不可以访问非静态成员变量,会报错,无法区分到底是哪个对象的m_B
cout<<"static void func调用"<<endl;
}
static int m_A;//静态成员变量
int m_B;//非静态成员变量
//静态成员函数也是有访问权限的
private:
static void func1()
{
cout<<"static void func1 调用"<<endl;
}
};
int Person1::m_A=10;
void test03()
{
//1、通过对象访问
Person1 p;
p.func();
//2、通过类名访问
Person1::func();
//Person::func1();类外访问不到私有静态成员函数
}
int main()
{
test01();
test02();
test03();
return 0;
}
二,对象模型和this指针
1、成员变量和成员函数分开存储
在c++中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上
#include <bits/stdc++.h>
using namespace std;
//成员变量和成员函数分开存储
class Person
{
int m_a;//非静态成员变量 属于类的对象上
static int m_b;//静态成员变量 不属于类的对象上
void func(){}//非静态成员函数 不属于类的对象上
static void func2(){}//静态成员函数 不属于类的对象上
};
int Person::m_b = 0;
void test01()
{
Person p;
//空对象占用内存空间为:1
//c++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
//每个空对象也应该有一个独一无二的内存地址
cout<<"size of p = "<<sizeof(p)<<endl;
}
void test02()
{
Person p;
cout<<"size of p = "<<sizeof(p)<<endl;
}
int main()
{
// test01();
test02();
return 0;
}
?2、this指针概念
通过上一小节,我们知道c++中成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码,那么问题是:这一块代码是如何区分哪个对象调用自己的呢?
答:c++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象。
this指针是隐含每一个非静态成员函数内的一种指针,this指针不需要定义,直接使用即可
this指针的用途:
1.当形参和成员变量同名时,可用this指针来区分
2.在类的非静态成员函数中返回对象本身,可使用return *this
#include <bits/stdc++.h>
using namespace std;
class Person
{
public:
Person(int age)
{
//this指针指向被调用的成员函数所属的对象
this->age=age;
}
Person& PersonAddAge(Person& p)//注意返回值要为引用
{
this->age+=p.age;
//this指向p2的指针,而*this指向的就是p2这个对象本体
return *this;
}
int age;
};
//1、解决名称冲突
void test01()
{
Person p1(18);
cout<<"p1的年龄为: "<<p1.age<<endl;
}
//2、返回对象本身用*this
void test02()
{
Person p1(10);
Person p2(10);
//链式编程思想
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
cout<<"p2的年龄为: "<<p2.age<<endl;
}
int main()
{
test01();
test02();
return 0;
}
3、空指针访问成员函数
c++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
#include <bits/stdc++.h>
using namespace std;
class Person
{
public:
void showClassName()
{
cout<<"this is Person class"<<endl;
}
void showPersonAge()
{
//报错原因是因为传入的指针为NULL
cout<<"age = "<<this->m_age<<endl;
//修正方式
if(this==NULL)return;
}
int m_age;
};
void test01()
{
Person* p=NULL;
p->showClassName();
p->showPersonAge();
}
int main()
{
test01();
return 0;
}
4、const修饰成员函数
常函数:
1.成员函数后加const后我们可以称这个函数为常函数
2.常函数内不可以修改成员属性
3.成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
1.声明对象前加const称该对象为常对象
2.常对象只能调用常函数
#include <bits/stdc++.h>
using namespace std;
class Person
{
public:
//this指针的本质 是指针常量 指针的指向是不可修改的
//this指针 等价于 Person* const this;
//在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改
void showPerson()const//等价于 const Person* const this
{
this->m_b=100;
// this->m_a=100;//错误,原因如上
//this=NULL //错误,原因如上
}
void fun()
{
}
int m_a;
mutable int m_b;//特殊变量,即使在常含数中,也可以修改这个值
};
void test01()
{
Person p;
p.showPerson();
}
//常对象
void test02()
{
const Person p{};//在对象前加const,变为常对象
// p.m_a=100;//报错
p.m_b=100;//m_b是特殊值,在常对象也可以修改
//常对象只能调用常函数
p.showPerson();//正确
// p.func();//报错 常对象不可以调用普通成员函数,因为普通成员函数可以修改属性
}
int main()
{
test01();
test02();
return 0;
}
三、友元
生活中你的家有客厅(public),有你的卧室(private),客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去,但是呢,你也可以允许你的好闺蜜好基友进去。
在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术。友元的目的就是让一个函数或者类访问另一个类中私有成员
友元关键字为friend
1、全局函数作友元
#include <bits/stdc++.h>
using namespace std;
//建筑物类
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;//想访问这个必须要有friend那一行的代码
}
void test01()
{
Building building;
GoodGay(building);
}
int main()
{
test01();
return 0;
}
2、类作友元
#include <bits/stdc++.h>
using namespace std;
class Building;
class GoodGay
{
public:
GoodGay();//构造函数
void visit();//参观函数访问Building中的属性
Building* building;
};
class Building
{
//GoodGay类是本类的好朋友,可以访问本类中私有成员
friend class GoodGay;
public:
Building();//构造函数
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;
}
3、成员函数作友元
#include <bits/stdc++.h>
using namespace std;
class Building;
class GoodGay
{
public:
GoodGay();
void visit();//让visit函数可以访问Building中私有成员
void visit2();//让visit2函数不可以访问Building中私有成员
Building* building;
};
class Building
{
//告诉编译器 GoodGay类中的visit成员函数是Building好朋友,可以访问私有内容
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<<"visit2函数正在访问: "<<building->m_SittingRoom<<endl;
// cout<<"visit2函数正在访问: "<<building->m_BedRoom<<endl;//报错,因为visit2不是friend
}
void test01()
{
GoodGay gg;
gg.visit();
}
int main()
{
test01();
return 0;
}
四、运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
1、加号运算符重载
作用:实现两个自定义数据类型相加的运算
#include <bits/stdc++.h>
using namespace std;
//加号运算符重载
//1、成员函数重载+号
class Person
{
public:
// 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;//相当于 operator+(p1,100);
cout<<p3.m_a<<" "<<p3.m_b<<"\n";
cout<<p4.m_a<<" "<<p4.m_b<<"\n";
}
int main()
{
test01();
return 0;
}
总结1:对于内置的数据类型的表达式的运算符是不可能改变的
总结2:不要滥用运算符重载
2、左移运算符重载
作用:可以输出自定义的数据类型。
#include <bits/stdc++.h>
using namespace std;
//左移运算符重载
class Person
{
public:
//利用成员函数重载左移运算符 p.operator<<(cout) 简化版本 p<<cout
//不会利用成员函数重载<<运算符,因为无法实现cout在左侧
// void operator<<(cout)
// {
// }
int m_a;
int m_b;
};
//只能利用全局函数重载左移运算符
//ostream对象只能有一个 ,注意要用引用的方式传递cout,返回值同样
ostream& operator<<(ostream &cout,Person &p)//本质 operator<<(cout,p) 简化 cout<<p
{
cout<<"m_a = "<<p.m_a<<" m_b = "<<p.m_b<<endl;
return cout;
}
void test01()
{
Person p;
p.m_a=10;p.m_b=10;
cout<<p;
}
int main()
{
test01();
return 0;
}
总结:重载左移运算符配合友元可以实现输出自定义的数据类型
3、递增运算符重载
作用:通过重载递增运算符,实现自己的整型数据
#include <bits/stdc++.h>
using namespace std;
// 自定义整形
class MyInteger
{
friend ostream& operator<<(ostream& cout,MyInteger myint);
public:
MyInteger()
{
m_num=0;
}
//重载前置++运算符,返回引用是为了一直对一个数据进行递增操作
MyInteger& operator++()
{
//先进行++运算
m_num++;
//再将自身返回
return *this;
}
//重载后置++运算符,注意这里是返回值而不是引用
MyInteger operator++(int)//int代表占位参数,可以用于区分前置和后置递增
{
//先 记录当时结果
MyInteger temp=*this;
//后 递增
m_num++;
//最后将记录的结果做返回操作
return temp;
}
private:
int m_num;
};
//重载<<运算符
ostream& operator<<(ostream& cout,MyInteger myint)
{
cout<<myint.m_num;
return cout;
}
void test01()
{
MyInteger myint;
cout<<++(++myint)<<endl;
cout<<myint<<endl;
}
void test02()
{
MyInteger myint;
cout<<myint++<<endl;
cout<<myint<<endl;
}
int main()
{
test01();
test02();
return 0;
}
总结:前置++返回引用,后置++返回值
4、赋值运算符重载
c++编译器至少给一个类添加4个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
4,赋值运算符operator=,对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深拷贝问题
#include<bits/stdc++.h>
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;
}
5、关系运算符重载
作用:重载关系运算符,可以让两个自定义类型对象进行比对操作。
#include <bits/stdc++.h>
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 false;
else return true;
}
string m_name;
int m_age;
};
void test01()
{
Person p1("Tom",18);
Person p2("Tom",18);
if(p1==p2)cout<<"YES"<<endl;
}
int main()
{
test01();
return 0;
}
6、函数调用运算符重载
1.函数调用运算符()也可以重载
2.由于重载后使用方式非常像函数的调用,因此称为仿函数
3.仿函数没有固定写法,非常灵活
#include<bits/stdc++.h>
using namespace std;
class MyPrint
{
public:
void operator()(string test)
{
cout<<test<<endl;
}
};
void MyPrint02(string test)
{
cout<<test<<endl;
}
void test01()
{
MyPrint myPrint;
myPrint("Hello Worle");//由于使用起来非常类似于函数调用,因此称为仿函数
MyPrint02("Hello Worle");
}
//仿函数非常灵活,没有固定写法
//加法类
class MyAdd
{
public:
int operator()(int num1,int num2)
{
return num1+num2;
}
};
void test02()
{
MyAdd myadd;
int ret=myadd(100,100);
cout<<"ret = "<<ret<<endl;
//匿名函数对象
cout<<MyAdd()(100,100)<<endl;
}
int main()
{
test01();
test02();
return 0;
}
?五、继承
继承是面向对象的三大特性之一
有些类与类之间存在特殊的关系。例如第一级是动物,第二级是猫,狗,那么第二级的成员除了拥有上一级的特性,还有自己的特性,这个时候就可以考虑利用继承的技术,减少重复代码。
1、继承基本语法
#include <bits/stdc++.h>
using namespace std;
//普通实现页面
// Java页面
// class Java
// {
// public:
// void header()
// {
// cout<<"首页、公开课、登录、注册……(公共头部)"<<endl;
// }
// void footer()
// {
// cout<<"帮助中心、交流合作、站内地图…(公共底部)"<<endl;
// }
// void left()
// {
// cout<<"Java、python、c++、……(公共分类列表)"<<endl;
// }
// void content()
// {
// cout<<"Java学科视频"<<endl;
// }
// };
// //Python页面
// class Python
// {
// public:
// void header()
// {
// cout<<"首页、公开课、登录、注册……(公共头部)"<<endl;
// }
// void footer()
// {
// cout<<"帮助中心、交流合作、站内地图…(公共底部)"<<endl;
// }
// void left()
// {
// cout<<"Java、python、c++、……(公共分类列表)"<<endl;
// }
// void content()
// {
// cout<<"Python学科视频"<<endl;
// }
// };
// //c++页面
// class CPP
// {
// public:
// void header()
// {
// cout<<"首页、公开课、登录、注册……(公共头部)"<<endl;
// }
// void footer()
// {
// cout<<"帮助中心、交流合作、站内地图…(公共底部)"<<endl;
// }
// void left()
// {
// cout<<"Java、python、c++、……(公共分类列表)"<<endl;
// }
// void content()
// {
// cout<<"CPP学科视频"<<endl;
// }
// };
//继承实现页面
//公共页面类
class BasePage
{
public:
void header()
{
cout << "首页、公开课、登录、注册……(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图…(公共底部)" << endl;
}
void left()
{
cout << "Java、python、c++、……(公共分类列表)" << endl;
}
};
//继承的好处:减少重复代码
//语法:class 子类 :继承方式 父类
//子类也称为派生类
//父类也称为基类
//Java页面
class Java:public BasePage
{
public:
void content()
{
cout<<"Java学科视频"<<endl;
}
};
//Python页面
class Python :public BasePage
{
public:
void content()
{
cout<<"Python学科视频"<<endl;
}
};
//c++页面
class CPP :public BasePage
{
public:
void content()
{
cout<<"CPP学科视频"<<endl;
}
};
void test01()
{
cout << "Java下载视频页面如下: " << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "Python下载视频页面如下: " << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "CPP下载视频页面如下: " << endl;
CPP c;
c.header();
c.footer();
c.left();
c.content();
}
int main()
{
test01();
return 0;
}
总结:
继承的好处:可以减少重复的代码
语法:class A:public B;
A类称为子类或派生类? ? ?B类称为父类或基类
2、继承方式
1.公共继承 2.保护继承 3.私有继承
1.在公共继承中,父类的私有权限不可访问,其余的权限都变成公共权限
2.在保护继承中,父类的私有权限不可访问,其余的权限都变成保护权限
3.在私有继承中,父类的私有权限不可访问,其余的权限都变成私有权限
#include <bits/stdc++.h>
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=100;//报错,到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=10;//父类种的公共权限成员到子类中变为保护权限
m_b=10;//父类中的保护权限成员到子类中依然是保护权限
//m_c=10;//父类中的私有权限成员到子类中访问不到,报错
}
};
void test02()
{
son2 s2;
// s2.m_a=100;//在son2中m_a变为保护权限,因此类外访问不到,报错
// s2.m_b=100;//在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=10;//父类种的公共权限成员到子类中变为私有权限
m_b=10;//父类中的保护权限成员到子类中依然是保护权限
//m_c=10;//父类中的私有权限成员到子类中访问不到,报错
}
};
void test03()
{
son3 s3;
// s3.m_a=1000;//在son3中m_a变为私有权限,因此类外访问不到,报错
// s3.m_b=1000;//在son3中m_a变为私有权限,因此类外访问不到,报错
}
int main()
{
return 0;
}
3、继承中的对象模型
问题:从父类继承过来的成员,哪些属于子类对象中?
#include <bits/stdc++.h>
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;
};
void test01()
{
//父类中所有非静态成员属性都会被子类继承下去
//父类中私有成员属性,是被编译器给隐藏了,因此访问不到,但是确实被继承下去了
cout<<"size of son = "<<sizeof (son)<<endl;//16
}
int main()
{
test01();
return 0;
}
4、继承中的构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题:父类和子类的构造和析构顺序是谁先谁后?
? ? 继承中的构造和析构顺序如下:
? ? 先构造父类,再构造子类,析构的顺序与构造的顺序相反
#include <bits/stdc++.h>
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()
{
// 继承中的构造和析构顺序如下:
// 先构造父类,再构造子类,析构的顺序与构造的顺序相反
son p;
}
int main()
{
test01();
return 0;
}
5、继承同名成员处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢/
1.访问子类同名成员,直接访问即可
2.访问父类同名成员,需要加作用域、
3.当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
#include <bits/stdc++.h>
using namespace std;
class Base
{
public:
Base()
{
m_a=100;
}
void func()
{
cout<<"Base-func()调用"<<endl;
}
void func(int a)
{
cout<<"Base-func(int)调用"<<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;//直接调用,是子类的同名成员
//如果通过子类对象访问到父类中同名成员,需要加作用域
cout<<"Base 下 m_a = "<<s.Base::m_a<<endl;
}
//同名成员函数处理
void test02()
{
son s;
s.func();//直接调用 调用时子类中的同名成员
//如何调用到父类中同名成员函数?
s.Base::func();//同样的,需要加作用域
//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏父类中的所有同名成员函数
//如果想访问到父类中被隐藏的同名成员函数,需要加作用域
// s.func(100);//报错
s.Base::func(100);
}
int main()
{
test01();
test02();
return 0;
}
?6、继承同名静态成员处理方式
问题:继承中同名静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致
?1.访问子类同名成员 直接访问即可
2.访问父类同名成员 需要加作用域
#include<bits/stdc++.h>
using namespace std;
class Base
{
public:
static int m_a;
static void func()
{
cout<<"Base - static void func()的调用"<<endl;
}
static void func(int a)
{
cout<<"Base - static void func(int)的调用"<<endl;
}
};
int Base::m_a=100;
class son:public Base
{
public:
static int m_a;
static void func()
{
cout<<"son - static void func()的调用"<<endl;
}
};
int son::m_a =200;
void test01()
{
// 1、通过对象访问
cout<<"通过对象访问: "<<endl;
son s;
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()
{
//1、通过对象访问
cout<<"通过对象访问: "<<endl;
son s;
s.func();
s.Base::func();
//2、通过类名访问
cout<<"通过类名访问"<<endl;
son::func();
son::Base::func();
//子类出现和父类同名静态成员函数,也会隐藏父类中所有同名成员函数
//如果想访问父类中被隐藏同名成员,需要加作用域
son::Base::func(100);
}
int main()
{
test01();
test02();
return 0;
}
总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问方式(通过对象和通过类名)
7、多继承语法
c++允许一个类继承多个类
语法:class 子类:继承方式 父类1,继承方式 父类2……
多继承可能会引发父类中有同名成员出现,需要加作用域区分
c++实际开发中不建议使用多继承
#include<bits/stdc++.h>
using namespace std;
class Base
{
public:
Base()
{
m_a=100;
}
int m_a;
};
class Base2
{
public:
Base2()
{
m_a=200;
}
int m_a;
};
//子类 需要继承Base1和Base2
class son:public Base,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(son)<<endl;
//当父类中出现同名成员,需要加作用域区分
cout<<"m_a = "<<s.Base::m_a<<endl;
cout<<"m_a = "<<s.Base2::m_a<<endl;
}
int main()
{
test01();
return 0;
}
8、菱形继承
菱形继承概念:
两个派生类继承同一个基类 ,又有某个类同时继承着两个派生类,这种继承方式被称为菱形继承,或者钻石继承
#include<bits/stdc++.h>
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;
cout<<"st.Tuo::m_age= "<<st.Tuo::m_age<<endl;
//这份数据我们知道,只要有一份就可以,菱形继承导致数据有两份,资源浪费
cout<<"st.m_age = "<<st.m_age<<endl;
}
int main()
{
test01();
return 0;
}
?六、多态
1.多态的基本概念
多态是c++面向对象三大特性之一
多态分为两类
1.静态多态:函数重载和运算符重载属于静态多态,复用函数名
2.动态多态:派生类和虚函数实现运行时多态
静态多态与动态多态区别:
1.静态多态的函数地址早绑定 - 编译阶段确定函数地址
2.动态多态的函数地址晚绑定 - 运行阶段确定函数地址?
#include<bits/stdc++.h>
using namespace std;
//多态
//动物类
class Animal
{
public:
//函数面前加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了
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.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
int main()
{
test01();
return 0;
}
重写:函数返回值类型,函数名,参数列表完全一致称为重写
2、多态案例--计算器实现
多态的优点:
1.代码组织结构清晰 2.可读性强 3.利于前期和后期的扩展以及维护
#include<bits/stdc++.h>
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 c;
c.m_num1=10;c.m_num2=10;
cout<<c.m_num1<<" + "<<c.m_num2<<" = "<<c.getResult("+")<<endl;
cout<<c.m_num1<<" - "<<c.m_num2<<" = "<<c.getResult("-")<<endl;
cout<<c.m_num1<<" * "<<c.m_num2<<" = "<<c.getResult("*")<<endl;
}
//利用多态实现计算器
//多态好处:
//1、组织结构清晰
//2、可读性强
//3、对于前期和后期扩展以及维护性高
//实现计算器抽象类
class AbstractCalculator
{
public:
virtual int getResult()
{
return 0;
}
int m_num1;
int m_num2;
};
//加法计算器类
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;
abc->m_num2=10;
cout<<abc->m_num1<<" + "<<abc->m_num2<<" = "<<abc->getResult()<<endl;
delete abc;//用完记得销毁
//减法运算
abc = new SubCalculator;
abc->m_num1=100;
abc->m_num2=100;
cout<<abc->m_num1<<" - "<<abc->m_num2<<" = "<<abc->getResult()<<endl;
delete abc;
//乘法运算
abc = new MulCalculator;
abc->m_num1=100;
abc->m_num2=100;
cout<<abc->m_num1<<" * "<<abc->m_num2<<" = "<<abc->getResult()<<endl;
delete abc;
}
int main()
{
test01();
test02();
return 0;
}
?3、纯虚函数和抽象类
在多态中,通常父类中虚函数的实现毫无意义的,主要是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数写法:virtual 返回值类型 函数名 (参数列表)= 0;
当类中有了纯虚函数,这个类也称为抽象类。
抽象类特点:
1.无法实例化对象
2.子类必须重写抽象类中的纯虚函数,否则也属于抽象类
#include<bits/stdc++.h>
using namespace std;
//纯虚函数和抽象类
class Base
{
public:
//纯虚函数
//只要有一个纯虚函数,这个类称为抽象类
//抽象类特点:
//1、无法实例化对象
//2、抽象类的子类 必须要重写父类中的纯虚函数,否则也属于抽象类
virtual void func()=0;
};
class son:public Base
{
public:
virtual void func()
{
cout<<"func函数调用"<<endl;
}
};
void test01()
{
// Base b;//抽象类是无法实例化对象,报错
// new Base;//抽象类是无法实例化对象,报错
son s;//子类必须重写父类中的纯虚函数,否则无法实例化对象
Base *base =new son;
base->func();
}
int main()
{
test01();
return 0;
}
4、多态案例--制作饮品
制作饮品的大致流程为:煮水-冲泡-倒入杯中-加入辅料
利用多态技术实现本案例,提供抽象类制作饮品基类,提供子类制作咖啡和茶叶
#include<bits/stdc++.h>
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:
//煮水
virtual void Boil()
{
cout<<"煮农夫山泉"<<endl;
}
//冲泡
virtual void Brew()
{
cout<<"冲泡咖啡"<<endl;
}
//倒入杯中
virtual void PourInCup()
{
cout<<"倒入杯中"<<endl;
}
//加入辅料
virtual void PutSomething()
{
cout<<"加入糖和牛奶"<<endl;
}
};
class Tea:public AbstractDrinking
{
public:
//煮水
virtual void Boil()
{
cout<<"煮矿泉水"<<endl;
}
//冲泡
virtual void Brew()
{
cout<<"冲泡茶叶"<<endl;
}
//倒入杯中
virtual void PourInCup()
{
cout<<"倒入杯中"<<endl;
}
//加入辅料
virtual void PutSomething()
{
cout<<"加入枸杞"<<endl;
}
};
//制作函数
void doWork(AbstractDrinking *abs)
{
abs->makeDrink();
delete abs;//释放
}
void test01()
{
//制作咖啡
doWork(new Coffee);
//制作茶叶
doWork(new Tea);
}
int main()
{
test01();
return 0;
}
5、虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
1.可以解决父类指针释放子类对象
2.都需要有具体的函数实现
虚析构和纯虚析构区别:
1.如果是纯虚析构,该类属于抽象类,无法实例化对象
?虚析构语法:virtual ~类名()
纯虚析构语法:virtual ~类名()=0;类名::~类名(){}
#include<bits/stdc++.h>
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;
m_name=new string(name);
}
virtual void speak()
{
cout<<*m_name<<"小猫在说话"<<endl;
}
~Cat()
{
if(m_name!=NULL)
{
cout<<"Cat析构函数调用"<<endl;
delete m_name;
m_name=NULL;
}
}
string *m_name;
};
void test01()
{
Animal *animal =new Cat("Tom");
animal->speak();
//父类指针在析构时候 不会调用子类中虚构函数,导致子类如果有堆区属性,出现内存泄漏情况
delete animal;
}
int main()
{
test01();
return 0;
}
总结:
1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2.如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3.拥有纯虚析构函数的类也属于抽象类
6、多态案例--电脑组装
案例描述:
电脑主要组成部件为CPU(用于计算),显卡(用于显示),内存条(用于存储)将每个零件封装除抽象基类,并且提供不同的厂商生产不同的零件,例如Intel和Lenovo厂商,创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
测试时组装三台不同的电脑进行工作
#include<bits/stdc++.h>
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;
};
//电脑类
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();
}
//提供析构函数释放3个电脑零件
~Computer()
{
//释放CPU零件
if(m_cpu!=NULL)
{
delete m_cpu;
m_cpu=NULL;
}
//释放显卡零件
if(m_vc!=NULL)
{
delete m_vc;
m_vc=NULL;
}
//释放内存条零件
if(m_mem!=NULL)
{
delete m_mem;
m_mem=NULL;
}
}
private:
CPU *m_cpu;//cpu的零件指针
VideoCard *m_vc;//显卡零件指针
Memory *m_mem;//内存条零件指针
};
//具体厂商
//Intel厂商
class IntelCPU:public CPU
{
public:
virtual void calculate()
{
cout<<"Intel的CPU开始计算了"<<endl;
}
};
class IntelVideoCard:public VideoCard
{
public:
virtual void display()
{
cout<<"Intel的显卡开始显示了"<<endl;
}
};
class IntelMemory:public Memory
{
public:
virtual void storage()
{
cout<<"Intel的内存条开始存储了"<<endl;
}
};
//Lenovo厂商
class LenovoCPU:public CPU
{
public:
virtual void calculate()
{
cout<<"Lenovo的CPU开始计算了"<<endl;
}
};
class LenovoVideoCard:public VideoCard
{
public:
virtual void display()
{
cout<<"Lenovo的显卡开始显示了"<<endl;
}
};
class LenovoMemory:public Memory
{
public:
virtual void storage()
{
cout<<"Lenovo的内存条开始存储了"<<endl;
}
};
void test01()
{
//第一台电脑零件
cout<<"第一台电脑开始工作"<<endl;
CPU *intelCpu =new IntelCPU;
VideoCard *intelCard =new IntelVideoCard;
Memory *intelMem =new IntelMemory;
//创建第一台电脑
Computer *computer1 =new Computer(intelCpu,intelCard,intelMem);
computer1->work();
delete computer1;
//第二台电脑组装
cout<<"第二台电脑开始工作"<<endl;
Computer *computer2 =new Computer(new LenovoCPU,new LenovoVideoCard,new LenovoMemory);
computer2->work();
delete computer2;
//第三台电脑组转
cout<<"第三台电脑开始工作"<<endl;
Computer *computer3 =new Computer(new LenovoCPU,new IntelVideoCard,new LenovoMemory);
computer3->work();
delete computer3;
}
int main()
{
test01();
return 0;
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!