C++学习笔记——第五天

 对象和类:相同性质的对象抽象为类

类中的属性(成员属性、成员变量)和行为(成员函数)叫做成员

空对象占用一个字节内存空间以表明存在;不空按照成员大小分配内存;

非静态函数和非静态成员变量是分开存储的

一、封装:将属性(变量)和行为(函数)作为整体;加以权限控制

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;

访问(读和写)权限:

  1. 公共权限 public:成员在类内可以访问,类外可以访问
  2. 保护权限 protected:成员类内可以访问,类外不可以访问,子类可以访问
  3. 私有权限 private:成员类内可以访问,类外不可以访问,子类不可以访问

 struct和class的区别:

  1. struct内成员默认为公共:public
  2. 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个构造函数,拷贝构造只拷贝

  1. 没有返回值,不用写void
  2. 函数名与类名相同
  3. 构造函数可以有参数,可以发生重载
  4. 创建对象的时候,构造函数只会被自动调用一次
    class Person{
    public://写上这个外面才可以访问到
        Person(){
            cout<<"Person构造函数的调用"<<endl;
        }
    };
    
    Person p;//Person构造函数的调用

 构造函数的分类

  1.  按照参数:无参(默认)构造和有参构造
  2. 按照类型:普通构造和拷贝构造(把一个类粘到另一个类)
    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:可以通过地址看,不一样是拷贝

  1. 普通拷贝构造
  2. 值传递
  3. 返回局部变量
    Person doWork2(){
        Person p1;
        cout<<(int*)&p1<<endl;
        return p1;
    }
    void test03(){
        Person p=doWork();
        cout<<(int*)&p<<endl;
    }
    
    test03();
    //Person默认构造函数调用
    //一个地址
    //Person拷贝构造函数调用
    //Person析构函数调用
    //另一个地址
    //Person析构函数调用
    

 构造函数的调用规则

  1. 只写有参:默认无,拷贝有;调默认无,有参有,拷贝有
  2. 只写拷贝:默认无,有参无:调默认无,有参无,拷贝有

 用深拷贝解决浅拷贝的问题

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();

 析构函数 :进行清理的操作

  1. 没有返回值,不用写void
  2. 函数名与类名相同,在名称前加~
  3. 构造函数不可以有参数,不可以发生重载
  4. 对象在销毁前,只会被调用一次
    class Person{
    public://写上这个外面才可以访问到
        ~Person(){
            cout<<"Person析构函数的调用"<<endl;
        }
    };
    
    void test01(){
        Person p;//栈上的数据,test01执行完毕后,会释放这个对象
    }
    
    test01();

静态成员

  1. 对象(类的实例化)共享同一份数据,静态成员访问方式因此发生变化 rj:某类东西的固定属性
  2. 私有静态成员类外也是不可以访问的
  3. 必须类内声明,类外初始化
  4. 静态成员函数只能访问静态成员变量:静态函数体内无法体现调那个对象的成员变量
  5. 编译阶段分配内存
  6. 静态成员变量不占对象内存
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使用时机

  1.  区分形参和成员变量 规范命名可避免
  2. 类的非静态成员函数中返回对象本身用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();
  3. 空指针(指向类)可以访问成员函数,访问成员变量(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:为什么成员要在类外写??

  1. 全局函数做友元
    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);
    }
  2. 类做友元

    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();
  3. 成员函数做友元

    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();
}

继承方式:父类的私有都访问不到

  1. 公共继承:原样继承
  2. 保护继承:都变成保护
  3. 私有继承:都变成私有

继承中构造和析构顺序:构造先父后子,析构先子后父

检验方法:在函数中编写输出内容

全局函数访问子类有父类也有的成员访问方式,访问父类重载子类不重载函数访问方法也得加作用域(父类所有同名成员函数被子类隐藏)

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值