对象和类:相同性质的对象抽象为类
类中的属性(成员属性、成员变量)和行为(成员函数)叫做成员
空对象占用一个字节内存空间以表明存在;不空按照成员大小分配内存;
非静态函数和非静态成员变量是分开存储的
一、封装:将属性(变量)和行为(函数)作为整体;加以权限控制
const double PI=3.14;
//定义类
class Circle{
public://访问权限:公共
int m_r;
double calculateZC(){
return 2*PI*m_r;
}
};
//实例化:通过类创建一个对象
Circle c1;
//给对象进行属性赋值操作
c1.m_r=10;
//可以通过行为(函数)给属性赋值
cout<<c1.calculateZC()<<endl;
访问(读和写)权限:
- 公共权限 public:成员在类内可以访问,类外可以访问
- 保护权限 protected:成员类内可以访问,类外不可以访问,子类可以访问
- 私有权限 private:成员类内可以访问,类外不可以访问,子类不可以访问
struct和class的区别:
- struct内成员默认为公共:public
- class内成员默认为私有:private
通过公有public函数为私有属性设置读写权限,写同时满足要求
class Person{
private:
string m_Name;//可读可写
int m_Age=18;//只读
string m_Idol;//只写
public:
void setName(string name){
m_Name=name;
}
void setAge(int age){
if(age<0||age>150){
cout<<"年龄有误,赋值失败"<<endl;
return;//函数执行结束
}
m_Age=age;
}
}
Person p;
//可写:设置姓名
p.setName("张三");
//可读:获取姓名:直接s.m_Name ×,只能间接
cout<<p.getName()<<endl;
//可读=只读
p.setAge(160);//年龄有误,赋值失败
//可写=只写
类内类外的判断函数
构造函数:不写编译器会给一个类创建至少3个构造函数,拷贝构造只拷贝
- 没有返回值,不用写void
- 函数名与类名相同
- 构造函数可以有参数,可以发生重载
- 创建对象的时候,构造函数只会被自动调用一次
class Person{ public://写上这个外面才可以访问到 Person(){ cout<<"Person构造函数的调用"<<endl; } }; Person p;//Person构造函数的调用
构造函数的分类
- 按照参数:无参(默认)构造和有参构造
- 按照类型:普通构造和拷贝构造(把一个类粘到另一个类)
class Person{ public: Person(const Person &p){ age=p.age;//不修改传进来的对象属性 } int age; };
构造函数调用方法的分类以及匿名对象
//1、括号法
Person p1;//默认构造函数,不能加():会被认为是函数的声明,rj:自己写出来看看
Person p2(10);//有参构造函数
Person p3(p2);//拷贝构造函数
//2、显式法
Person p1;
Person p2=Person(10);//有参构造
Person p3=Person(p2);//拷贝构造
Person(10);
//匿名对象 特点:创建了对象,运行了构造函数,当前执行结束后,系统会立即回收掉,运行析构函数
//不要利用拷贝构造函数初始化匿名对象 会被认为是对象声明Person p3;rj:()当作看不见
cout<<"aaa"<<endl;
//3、隐式转换法
Person p4=10;//有参构造:相当于Person p4=Person(10);
Person p5=p4;//拷贝构造
拷贝构造函数(浅拷贝)调用时机 rj:可以通过地址看,不一样是拷贝
- 普通拷贝构造
- 值传递
- 返回局部变量
Person doWork2(){ Person p1; cout<<(int*)&p1<<endl; return p1; } void test03(){ Person p=doWork(); cout<<(int*)&p<<endl; } test03(); //Person默认构造函数调用 //一个地址 //Person拷贝构造函数调用 //Person析构函数调用 //另一个地址 //Person析构函数调用
构造函数的调用规则
- 只写有参:默认无,拷贝有;调默认无,有参有,拷贝有
- 只写拷贝:默认无,有参无:调默认无,有参无,拷贝有
用深拷贝解决浅拷贝的问题
class Person{
public:
Person(int age,int height){
m_Age=age;
m_Height=new int(height);
cout<<"Person的有参构造函数"<<endl;
}
~Person(){
if(m_Height!=NULL){
delete m_Height;//释放堆区内存
m_Height=NULL;//防止野指针
}
cout<<"Person的析构函数调用"<<endl;
}
int m_Age;
int *m_Height;
};
void test01(){
Person p1(18,160);
cout<<p1.m_Age<<*p1.m_Height<<endl;
Person p2(p1);
cout<<p2.m_Age<<*p2.m_Height<<endl;//报错
}
解决:自己实现拷贝构造函数
class Person{
public:
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=new int(*p.m_Height);//编译器默认操作:m_Height=p.m_Height
}
~Person(){
if(m_Height!=NULL){
delete m_Height;//释放堆区内存
m_Height=NULL;//防止野指针
}
cout<<"Person的析构函数调用"<<endl;
}
int m_Age;
int *m_Height;
};
void test01(){
Person p1(18,160);
cout<<p1.m_Age<<*p1.m_Height<<endl;
Person p2(p1);
cout<<p2.m_Age<<*p2.m_Height<<endl;//报错
}
用初始化列表代替有参构造函数赋值
//不灵活的构造
Person():m_A(10),m_B(20);m_C(30){
}
Person p;
//灵活的构造
Person(int a,int b,int c):m_A(a),m_B(b);m_C(c){
}
Person p(30,20,10);
类对象作为类成员时的传参数赋初值逻辑
//先构造人还是先构造手机?手机。
class Phone{
public:
Phone(string pName){
m_PName=pName;
}
string m_PName;
}
class Person{
public:
//Person m_Phone=Person(pName); 有参隐式转换法
Person(string name;string pName):m_Name(name),m_Phone(pName){
}
string m_Name;
Phone m_Phone;
}
void test01(){
Person p("张三","苹果MAX");
}
test01();
析构函数 :进行清理的操作
- 没有返回值,不用写void
- 函数名与类名相同,在名称前加~
- 构造函数不可以有参数,不可以发生重载
- 对象在销毁前,只会被调用一次
class Person{ public://写上这个外面才可以访问到 ~Person(){ cout<<"Person析构函数的调用"<<endl; } }; void test01(){ Person p;//栈上的数据,test01执行完毕后,会释放这个对象 } test01();
静态成员
- 对象(类的实例化)共享同一份数据,静态成员访问方式因此发生变化 rj:某类东西的固定属性
- 私有静态成员类外也是不可以访问的
- 必须类内声明,类外初始化
- 静态成员函数只能访问静态成员变量:静态函数体内无法体现调那个对象的成员变量
- 编译阶段分配内存
- 静态成员变量不占对象内存
int Person::m_A=0;//初始化
//1、通过对象进行访问
Person p;
cout<<p.m_A<<endl;
//2、通过类名(作用域下)进行访问
cout<<Person::m_A<<endl;
隐含在每一个非静态成员函数内的this指针 rj:函数怎么区分哪个对象调用自己?
指针指向调用自己的成员的对象
不用定义直接使用
本质是指针常量:指针的指向不可以修改Person *const this
this使用时机
- 区分形参和成员变量 规范命名可避免
- 类的非静态成员函数中返回对象本身用return *this
Person& PersonAddAge(Person &p){ //值方式返回:Person PersonAddAge(Person &p) //rj:返回局部变量满足拷贝构造函数调用时机? this->age+=p.age; return *this;//this是指向p2的指针,*this就是p2对象本体 } void test02(){ Person p1(10); Person p2(10); //链式编程思想 p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1); cout<<p2.age<<endl;//40 } test02();
-
空指针(指向类)可以访问成员函数,访问成员变量(this->m_Age不存在)会报错;提高健壮性的方法
if(this==NULL){//健壮性 return; } cout<<m_Age<<endl;//this->m_Age
this相关:const修饰成员函数、const修饰类(常对象):不允许修改类成员变量
class Person{
public:
void showPerson() const{
//this->m_A=100;const不让修改类的值
//this=NULL;this不让修改指针指向
this->m_B=100;//有const也能改
}
void func(){
}
mutable int m_B;
}
void test02(){
const Person p;
p.showPerson();
//p.fanc();常对象不能调用普通函数:普通函数可能修改类的值
}
友元:可以访问私有属性的函数或类,rj:为什么成员要在类外写??
- 全局函数做友元
class Buildng{ 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); }
-
类做友元
class Building; class GoodGay{ public: GoodGay(); void visit(); Building * building; } class Building{ 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;//friend后能访问 } void test01(){ GoodGay gg; gg.visit(); } test01();
-
成员函数做友元
class Building; class GoodGay{ public: GoodGay(); void visit();//可以访问Building中私有成员 void visit2();//不可以 Building * building; } class Building{ friend void GoodGay::visit(); 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<<"visit"<<building->m_SittingRoom<<endl;//可以 cout<<"visit"<<building->m_BedRoom<<endl;//friend后可以 } void GoodGay::visit2(){ cout<<building->m_SittingRoom<<endl;//可以 cout<<building->m_BedRoom<<endl;//不可以 } void test01(){ GoodGay gg; gg.visit();//可以 gg.visit2();//报错 }
二、继承:用于网页公共头公共底类内容重复
父类中所有非静态成员属性都被继承(占子类内存)了,包括私有属性变量
创建子类对象前会创建父类对象
class BasePage{
public:
void header(){
cout<<"页面头部"<<endl;
}
void footer(){
cout<<"页面尾部"<<endl;
}
};
//class 子类:继承方式 父类
// 派生类 基类
class Python:public BasePage{
public:
void content(){
cout<<"python学科视频"<<endl;
}
}
class CPP:public BasePage{
public:
void content(){
cout<<"C++学科视频"<<endl;
}
};
void test01(){
Python py;
py.header();
py.footer();
py.context();
cout<<"--------------"<<endl;
CPP cp;
cp.header();
cp.footer();
cp.context();
}
继承方式:父类的私有都访问不到
- 公共继承:原样继承
- 保护继承:都变成保护
- 私有继承:都变成私有
继承中构造和析构顺序:构造先父后子,析构先子后父
检验方法:在函数中编写输出内容
全局函数访问子类有父类也有的成员访问方式,访问父类重载子类不重载函数访问方法也得加作用域(父类所有同名成员函数被子类隐藏)
class Base{
public:
Base(){
m_A=100;
}
void func(){
cout<<"Base-func函数"<<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();//访问父类函数
}
int main(){
test01();
test02()
}
静态成员访问子类有父类也有的成员访问方式4:和非静态一样,多一种通过类名访问,再多一种子类到父类类名访问
// 通过类名 父类下
cout<<Son::Base::m_A<<endl;
多继承(多爸爸)语法:不建议用:不同爸爸有相同变量
class Base1{
public:
Base1(){
m_A=100;
}
int m_A;
};
class Base2{
public:
Base2(){
m_A=200;
}
int m_A;
};
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<<sizeof(s)<<endl;//16
//访问不同父类内容需要加作用域
cout<<s.Base1::m_A<<endl;//100
cout<<s.Base2::m_A<<endl;//200
}
菱形继承(钻石继承):动物:羊,驼:草泥马(羊驼);虚继承
用虚继承解决两份数据不一致
原理:羊驼继承两个虚基类指针vbptr,两个指针分别指向不同的虚基类表vbtable:偏移量,根据偏移量找到同一个地址,同一个内容
class Animal{//成为虚基类
public:
int m_Age;
};
class Sheep:virtual public Animal{};
class Tuo:virtual public Animal{};
class SheepTuo:public Sheep,public Tuo{};
void test01(){
SheepTuo st;
st.m_Age=18;//报错,不明确
st.Sheep::m_Age=18;
st.Tuo::m_Age=28;
cout<<st.Sheep::m_Age<<endl;//18,虚继承后成为28
cout<<st.Tuo::m_Age<<endl;//28
cout<<st.m_Age<<endl;//虚继承后28
}
三、多态:地址反绑定
静态多态(复用函数名):函数重载、运算符重载;编译时确定函数地址
动态多态(地址反绑定:传的子类,父类接收:指针或引用,用的子类):派生类和(父类中)虚函数(被重写)实现运行时多态;运行时确定函数地址
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;
}
};
void doSpeak(Animal &animal){
animal.speak();
}
void test01(){
Cat cat;
doSpeak(cat);//小猫在说话
Dog dog;
doSpeak(dog);//小狗在说话
}
底层原理
当子类继承父类是继承父类的指针,子类有重写时指针指向自己的函数;传入子类对象实现多态,是子类对象指针指向函数不同
好处:开闭原则:对拓展进行开放,对修改进行关闭
计算器:只需要改对象类型,可读性强
纯虚函数和抽象类:有纯虚函数的类称为抽象类:抽象类无法实例化对象,子类会继承纯虚函数
class Animal{
public:
//纯虚函数
virtual void speak()=0;
};
解决通过父类指针释放子类对象??:
虚析构:在析构父类时,指出还有子类析构函数;需要实现:释放某些父类开辟在堆区的对象
纯虚析构:无法实例化对象;必须具体实现,实现方式在外部
class Animal{
...
virtual void speak()=0;//纯虚析构
}
//纯虚析构的实现
Animal::~Animal(){
cout<<"Animal纯虚析构函数调用"<<endl;
}