C++_核心编程_类和对象

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

万物皆有对象,对象上有其属性和行为

一、封装

1、封装意义
  • 将属性和行为作为一个整体,表现生活中的事务

  • 将属性和行为加以权限作为控制

在设计类时,将属性和行为写在一起,表现事物

语法:class 类名 { 访问权限: 属性 / 行为 }

// 设计一个圆类,求圆周长
class circle{
    // 类中的属性和行为统一称为 成员
    // 属性 :成员属性、成员变量
    // 行为 :成员方法、成员函数

    //访问权限-公共权限
public:
    //属性
    int r;
    //行为 获取圆的周长
    double zhouchang(){
        return 2 * PI * r;
    }

};

int main(){

    // 通过圆类创建具体的圆
    circle c1;
    //给圆对象的属性进行赋值
    c1.r = 10;
    cout << "圆的周长为: " << c1.zhouchang() << endl;

    return 0;
}

类在设计时,可以把属性和行为放在不同的权限下,加以控制

权限有三种: 1、public 公共权限 2、protected 保护权限 3、private 私有权限

访问权限

  1. 公共权限 public 成员在类内可以访问,类外也可访问

  2. 保护权限 protected 成员在类内可以访问,类外不可以访问,儿子可以访问父中保护内容

  3. 私有权限 private 成员在类内可以访问,类外不可以访问,儿子不可以访问父中隐私内容

// 访问权限
//1   公共权限 public     成员在类内可以访问,类外也可访问
//2   保护权限 protected  成员在类内可以访问,类外不可以访问  儿子可以访问父中保护内容
//3   私有权限 private    成员在类内可以访问,类外不可以访问  儿子不可以访问父中隐私内容

class Persion{
public: // 公共权限
    string pname;

protected: // 保护权限
    string pcar;

private:  // 私有权限
    string pmoney;

public:
  void showdata(){
      pname = "张三";
      pcar = "宝马";    //在类内可以访问,没有问题
      pmoney = "1000";
  }

};

int main(){ 
    // 访问权限
    Persion p1;
    p1.pname = "张三";
    //p1.pcar = "宝马";  //报错,保护成员在类外不可以访问
    //p1.pmoney = "1000";  // 报错,私有成员在类外不可以访问



    return 0;
}
2、struct和class 区别

在C++中struct和class 唯一的区别就在于:默认访问的权限不一样

  • struct: 默认权限为公共

  • class:默认权限为私有

//struct 和 class 使用的区别
class c1{
    int a;  // 默认权限是私有
};
struct c2{
    int a; // 默认权限是公共
};


int main(){
    // struct 和 class 使用的区别
    c1 c1;
    c2 c2;
    //c1.a;  // 访问报错,class成员变量默认的权限是私有
    c2.a = 1;  // 访问正确,没有问题

    return 0;
}
3、成员属性设置为私有
  • 优点1:将所有成员变量设置为私有,可以自己控制读写权限

  • 优点2:对于写权限,我们可以检测数据的有效性

//成员属性设置为私有
class per{
public:
    // 设置姓名
    void setName(string Sname){
        name = Sname;
    }
    // 读姓名
    void showName(){
        cout << "人的姓名是 :" << name << endl;
    }
    void showAge(){
        cout << "人的年龄是 :" << age << endl;
    }
    void setIcar(string car){
        icar = car;
    }


private:
    string name;  // 可读可写

    int age = 18;  // 只读

    string icar;  //只写

};



int main(){

    //成员属性设置为私有
    per p;
    p.setName("娜娜");
    p.showName();
    p.setIcar("pamei");
    p.showAge();




    return 0;
}

二、对象的初始化和清理

1、构造函数和析构函数

构造函数:进行初始化

析构函数:进行清理

以上两个函数是由编译器自动调用

语法:

构造函数:类名(){}

析构函数:~类名(){}

// 构造函数和析构函数,对象化的初始化和清理

class Pers{
public:
    //1、构造函数:没有返回值不用写void
    // 函数名和类名相同
    //构造函数可以有参数,可以发生重载
    //构造对象的时候,构造函数会自动调用,而且只调用一次
    Pers(){
        cout << "构造函数的调用" << endl;
    }

    //2、析构函数
    //没有返回值,不谢void
    //函数名和类名也是相同,需要加 ~
    //不可以有参数,不可以发生重载
    // 对象在销毁前,会自动调用析构函数,并且只发生一次
    ~Pers(){
        cout << "析构函数的调用 " << endl;
    }

};

int main(){

    // 构造函数和析构函数,对象化的初始化和清理
    // 构造和析构都是必须有的,如果自己不提供,编译器会提供一个空实现的构造和析构函数
    Pers per; // 构造对象后,会自动调用构造函数,并且只调用一次

    return 0;
}
2、构造函数的分类和调用

按参数分类:有参构造和无参构造

按类型分类:普通构造和拷贝构造

三种调用方式:括号法、显示法、隐式转换法

class Pers{
public:
    //1、构造函数:没有返回值不用写void
    // 函数名和类名相同
    //构造函数可以有参数,可以发生重载
    //构造对象的时候,构造函数会自动调用,而且只调用一次
    Pers(){ //无参构造、默认构造
        cout << "无参构造函数的调用" << endl;
    }

    //2、析构函数
    //没有返回值,不谢void
    //函数名和类名也是相同,需要加 ~
    //不可以有参数,不可以发生重载
    // 对象在销毁前,会自动调用析构函数,并且只发生一次
    ~Pers(){
        cout << "析构函数的调用 " << endl;
    }

    // 构造函数的分类及调用
    // 分为无参构造和有参构造
    Pers(int a ){
        age = a;
        cout << "有参构造函数的调用" << endl;
    }

    // 分为普通构造函数和拷贝构造函数,以上均属于普通
    // 拷贝构造函数
    Pers(const Pers &p){   //将传入人 p 身上的所有属性拷贝到我自己的身上
        age = p.age;
        cout << "拷贝构造函数的调用" << endl;

    }

    int age;

};

// 构造函数的调用
void test01(){

    // 1、括号法
    Pers p1;     // 调用默认构造函数、无参构造函数
    Pers p2(10); // 调用有参构造函数
    Pers p3(p2); // 调用拷贝构造函数

    cout << "p2 的年龄: " << p2.age << endl;
    cout << "p3 的年龄: " << p3.age << endl;

    /*
     注意事项:
     1、调用默认参数时,不要加括号。   Pers p1()会被当做是函数的声明
     2、不要利用拷贝构造函数初始化匿名对象。 Pers(p5) == Pers p5; 编译器会当做是对象的声明
     */

    // 2、显示法
    Pers p4;  // 调用默认构造
    Pers p5 =  Pers(9) ; // 显示调用有参构造
    Pers p6 =  Pers(p5); // 显示调用拷贝构造函数
    cout << "p5 的年龄: " << p5.age << endl;
    cout << "p6 的年龄: " << p6.age << endl;

    Pers(9);  //当前属于匿名对象,此行执行结束会立马回收匿名对象


    // 3、隐式转换法
    Pers p7 = 11;  //等价于 Pers p7 =  Pers(11); 和显示调用有参一致 
    cout << "p7 的年龄: " << p7.age << endl;

}

int main(){

    test01();

    return 0;
}
3、拷贝构造函数调用时机

1、使用一个创建完毕的对象来初始化一个新的对象

2、值传递方式给函数参数传值

3、以值方式传回局部对象

// 拷贝构造函数的调用时机
class girl{
public:
    girl(){
        cout << "girl默认构造" << endl;
    }
    girl(int a){
        age = a;
        cout << "girl有参构造" << endl;

    }
    girl(const girl &g){
        age = g.age;
        cout << "girl拷贝构造" << endl;
    }
    ~girl(){
        cout << "girl析构函数" << endl;
    }

    int age;
};
void test03(girl a);
girl test04();

void test02(){

    girl g1(23);

    //1、使用一个创建完毕的对象来初始化一个新的对象
    girl g2(g1);

    // 2、值传递方式给函数参数传值
    test03(g1);

    // 3、以值方式传回局部对象
    girl g3 = test04();
    cout << "g3 的年龄是: " << g3.age << endl;
    cout << (long long)&g3 << endl;


}
void test03(girl a){  //值传递,修改参数不会影响数据本身

    cout << "值传递方式给函数参数传值" << endl;

}

girl test04(){
    girl g(90);
    cout << "g 的年龄是: " <<g.age << endl;
    cout << (long long)&g << endl;
    return  g; //返回的 g 和 接受的不是同一个,返回的是副本
}
4、构造函数调用规则

在默认情况下,编译器会给每个类添加三个函数:默认构造、析构函数、拷贝函数

构造函数调用规则如下

1、如果用户定义有参构造函数,C++ 不再提供默认无参构造函数,但是会提供默认拷贝构造

2、如果用户定义拷贝构造函数,C++ 不会在提供其他构造函数

// 构造函数的调用规则
class rule{
public:
//    rule(){
//        cout << "rule的默认构造函数调用" << endl;
//    }
    rule(int a){
        age = a;
    }
//    rule(const rule &r){
//        age = r.age;  // 编译器会默认写这行代码,
//    }
    ~rule(){
        cout << "rule的析构函数调用" << endl;
    }

    int age;

};
void test05(){
    rule r(23);
    rule r1(r);
    cout << "r1 的年龄为:" << r1.age << endl;
}
void test06(){
    rule r(10);
    rule r1(r);
    cout << r1.age << endl;

}
5、深拷贝和浅拷贝

深拷贝:在堆区重新申请空间,进行拷贝操作 (浅拷贝带来的问题就是,会两次释放同一块内存)

浅拷贝:简单的赋值拷贝操作

attention:如果属性有在堆区开辟的,必须自己提供拷贝构造函数,否则会出现浅拷贝带来的问题

// 构造函数的调用规则
class rule{
public:
    rule(){
        cout << "rule的默认构造函数调用" << endl;
    }
    rule(int a,int height){
        age = a;
        hei = new int(height);  //在堆区申请内存空间
    }

    // 编译器生成的浅拷贝构造函数有问题,重新写深拷贝构造函数
    rule(const rule &r){
        age = r.age;
        hei = new int(*r.hei);
    }

//    rule(const rule &r){  浅拷贝
//        age = r.age;  // 编译器会默认写这行代码,
//        hei = r.hei;
//    }
    ~rule(){
        //析构代码:将堆区开辟的数据做释放操作
        if(hei != NULL){
            delete hei;
            hei = NULL;
        }
        cout << "rule的析构函数调用" << endl;
    }

    int age;
    int * hei;

};

void test07(){
    // 浅拷贝带来的问题就是,会两次释放同一块内存
    rule r1(23,173);
    rule r2(r1);  //在栈区,r2先进行析构,r1后进行析构
    cout << r1.age <<"  身高:" << *r1.hei << endl;
    cout << r2.age <<"  身高:" << *r2.hei << endl;

}
6、初始化列表

作用:初始化属性的操作

// 初始化参数列表
class can{
public:

    // 初始化列表 初始值
//    can():a(2),b(3),c(4){    // 默认值写死,无法改变
//
//    }
    can(int a, int b ,int c):a(a),b(b),c(c){

    }

    int a;
    int b;
    int c;


};
7、类对象作为类成员

C++ 类中的成员可以是另一个类的对象,称为对象成员

当其他类对象作为本类成员,构造时候先构造其他类对象,在构造自身

析构函数顺序:先释放自身,再去释放其他对象,与构造相反

// 类对象作为类成员
class B{

public:
    string phone;
    B(string bname){
        phone = bname;
    }

};
class Aii{
public:

    Aii(string name,string bn):aname(name),b(bn){ // B b = bn 隐式转换法

    }
    string aname;
    B b;

};
8、静态成员

静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员

静态成员分为

静态成员变量:

  • 所有对象共享同一份数据

  • 在编译阶段分配内存

  • 类内声明,类外初始化

静态成员函数:

  • 所有对象共享同一个函数

  • 静态成员函数只能访问静态成员变量

//静态成员变量
class ppersion{
public:
    /*
     - 所有对象共享同一份数据

     - 在编译阶段分配内存

     - 类内声明,类外初始化

*/
    static int name; // 类内声明

    // 静态访问对象也是有访问权限
private:
    static int pp_b;
};

int ppersion::name = 100;  // 类外初始化
int ppersion::pp_b = 200;

void test08(){
    ppersion p;
    cout << p.name << endl;

    ppersion p1;
    p1.name = 90;
    cout << p.name << endl; // p1 修改值后,原来p 的值也发生改变
}
void test09(){
    /*
     静态成员变量不属于某一个对象上,所有对象都共享同一份数据
     因此静态成员变量有两种访问方式
     1、通过对象进行访问
     2、通过类名进行访问
     */

    //1、通过对象进行访问
    ppersion pp;
    cout << pp.name << endl;

    //2、通过类名进行访问
    cout << ppersion::name << endl;
    //cout << ppersion::pp_b << endl;  // 访问不了,因为是私有权限的
}
// 静态成员函数
class pererson{
    /*
     - 所有对象共享同一个函数

     - 静态成员函数只能访问静态成员变量

     - 也是有访问权限的

    */
public:
    static void fun(){
        name = 90;
        //a = 10; // 报错,不能修改非静态成员变量,因为静态成员变量是公用的,不知道修改谁的值
        cout << "静态成员函数的 调用" << endl;
    }
    static int name;
    int a = 1;
};
int pererson::name = 100;

void te01(){
    pererson p;
    p.fun();
    p.a = 2;
    cout << p.name << endl;
    // a不是静态成员变量,每个对象只能修改自己的a值
    cout << "p 的a: " << p.a << endl;

    pererson p1;     // 通过对象访问
    p1.fun();
    cout << "p1 的a: " << p1.a << endl;
    pererson::fun();  //通过类名访问
}

三、C++的对象模型和this指针

1、成员变量和成员函数分开存储

在C++ 中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上

/* 成员函数和成员变量是分开存储的

空对象占用的内存空间为1

给空对象分配一个字节空间,是为了区分空对象占内存的位置

每个空对象也应该有个独一无二的内存地址

*/

2、this 指针概念

this指针指向被调用的成员函数所属的对象

  • this 指针是隐含每一个非静态成员函数内的一种指针

  • this 指针不需要定义,直接使用即可

用途:

  • 当形参和成员变量同名时,可以用this指针区分

  • 在类的静态成员函数中返回对象本身,可使用 return * this

//this 指针
class perp{
    /*
     - 当形参和成员变量同名时,可以用this指针区分

     - 在类的静态成员函数中返回对象本身,可使用 return * this

*/
public:
    perp(int age){
        this->age = age;  // this 指向的是被调用的成员函数 所属的对象
    }
    perp& add(perp &p){
        this->age += p.age;
        return *this;  //this 指向p1的指针,解引用就是指向对象
    }
    int age;
};

void te02(){
    perp p(19);
    cout << p.age << endl;

    perp p1(23);
    p1.add(p).add(p);
    cout << p1.age << endl;

}
3、空指针访问成员函数

空指针可以调用成员函数,用this指针保证代码的健壮性

// 空指针访问成员函数
class unllPoint{
public:
    void showclass(){
        cout << "空指针访问成员函数" << endl;
    }
    void showage(){
        if(this == NULL)
            return;
        cout << "年龄是:" << age << endl;// 此时 age 默认就是 = this.age
    }

    int age;
};

void te03(){
    unllPoint *p = NULL;
    //p->showage();  因为传入的指针是空,不能访问空里面的属性
    p->showclass();
}
4、const 修饰成员函数

常函数(const 修饰的函数称为 常函数):

  • 常函数内不可以修改成员属性

  • 成员属性声明时加关键字 mutable ,在常函数中依然可以改变

常对象:

  • 声明对象前加const 称对象称为常对象

  • 常对象只能调用常函数

// const 修饰成员函数
class pp1{
public:
    pp1(){

    }
    // this 指针是指针常量 * const this,指针的指向是不可以修改的
    // 常函数修饰的是 this 指针,让指针指向的值也不可修改
    void show() const{ // 等价于 const pp1 * const this
        //this->age = 100;
        this->grade = 100; //
    }
    void ii(){

    }
    int age;
    mutable int grade;  // 加上mutable可以修改此变量的值
};
void te04(){
    const pp1 p;  //此时 p 是常对象
    //p.age = 100;   报错
    p.grade = 90;
    cout << p.grade << endl;

    // 常对象只能调用常函数
    //p.ii();  不能调用普通函数
    p.show();

}

四、友元

1、全局函数作友元

目的:就是让一个函数或者类,访问另一个类中私有成员

关键字:friend

三种实现:

  • 全局函数做友元

  • 类做友元

  • 成员函数做友元

// 友元
class building{
    // goodFreind 是builging 好朋友。可以访问building中的私有成员
    friend void goodFreind(building *b);
public:
    building(){
        keitng = "客厅";
        room = "卧室";
    }
public:
    string keitng;
private:
    string room;
};
// 全局函数
void goodFreind(building *b){
    cout << "好朋友正在访问:" << b->keitng << endl;
    cout << "好朋友正在访问:" << b->room << endl;  // 可以访问私有成员
}
void te05(){
    building b;
    goodFreind(&b);

}
2、类做友元
// 友元
class building{

    // 类做友元
    friend class goodF;

public:
    building(){
        keitng = "客厅";
        room = "卧室";
    }
public:
    string keitng;
private:
    string room;
};

// 类做友元
class goodF{
public:
    goodF();  // 类外写构造函数
    void visit(){  // 访问 builing 中的私有属性
        cout << "类做友元,好朋友正在访问:" << b->keitng << endl;
        cout << "类做友元,好朋友正在访问:" << b->room << endl;   // 可以访问私有成员
    }
    building *b;
};

// 类外写构造函数
goodF::goodF(){
    b = new building;  //new 返回的是一个指针

}

void te05(){

    goodF d;
    d.visit();

}
3、成员函数做友元(对结构有严格的强调,在此并未指出)
// 友元
// 成员函数做友元
class building;
class goodd{
public:
    goodd();

    void visit();   // 可以访问私有成员

    void visit2();  // 不可以访问私有成员

    building *b;
};

class building{

    // 成员函数做友元
    friend void goodd::visit();

    // goodFreind 是builging 好朋友。可以访问building中的私有成员
    friend void goodFreind(building *b);

    // 类做友元
    friend class goodF;


public:
    building(){
        keitng = "客厅";
        room = "卧室";
    }
public:
    string keitng;
private:
    string room;
};


goodd::goodd(){
    b = new building;
}

void goodd::visit(){// 可以访问私有成员
    cout << "成员函数做友元,visit()正在访问:" << b->keitng << endl;
    cout << "成员函数做友元,visit()正在访问:" << b->room << endl;
}

void goodd::visit2(){  // 不可以访问私有成员
    cout << "成员函数做友元,visit2()正在访问:" << b->keitng << endl;
    //cout << "成员函数做友元,visit2()正在访问:" << b->room << endl;
}

void te06(){
    goodd g;
    g.visit();
    g.visit2();
}

五、运算符重载

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

1、加号运算符重载

作用:实现两个自定义数据类型相加的运算

成员函数实现:

// 1、加号运算符重载 : 成员函数实现 全局函数实现
class persion{
public:
    //成员函数实现
    persion operator+(persion &p){
        persion 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;
};

void te01(){
    persion p1;
    p1.m_a = 9;
    p1.m_b = 3;

    persion p2;
    p2.m_a = 5;
    p2.m_b = 7;

    persion p3 = p1 + p2;
    cout << "p3 的a :" << p3.m_a << endl;
    cout << "p3 的b :" << p3.m_b << endl;
}

全局函数实现:

// 1、加号运算符重载 : 成员函数实现 全局函数实现
class persion{
public:
    int m_a;
    int m_b;
};

//全局函数实现
persion operator+ (persion &p1,persion &p2){
    persion temp;
    temp.m_a = p1.m_a + p2.m_a;
    temp.m_b = p1.m_b + p2.m_b;
    return temp;
}

void te01(){
    persion p1;
    p1.m_a = 9;
    p1.m_b = 3;

    persion p2;
    p2.m_a = 5;
    p2.m_b = 7;

    persion p3 = p1 + p2;   // 简化的写法
    cout << "p3 的a :" << p3.m_a << endl;
    cout << "p3 的b :" << p3.m_b << endl;
}
2、左移运算符重载

作用:可以输出自定义数据类型

// 1、加号运算符重载 : 成员函数实现 全局函数实现
class persion{
public:
    //成员函数实现左移运算符,
    // 不会利用成员函数实现左移运算符。无法实现 cout 在左侧
//    void operator<<(cout){
//
//    }

    int m_a;
    int m_b;
};


//全局函数实现左移运算符
ostream &operator<< (ostream &cout , persion &p){
    cout << "m_a = " << p.m_a << " m_b = " << p.m_b << endl;
    return cout;
}



// 2、左移运算符重载
void te02(){
    persion p1;
    p1.m_a = 90;
    p1.m_b = 13;

    cout << p1 << endl ;

}
3、递增运算符重载

作用:通过重载递增运算符,实现自己的整型数据

// 3、递增运算符重载
class per{

    friend ostream & operator<< (ostream &cout,per &p);

public:
    per(){
        m_num = 1;
    }

    // 前置运算符重载
    per & operator++(){
        // 先加1,在放回返回自身
        m_num ++;
        return *this;
    }
    // 后置运算符重载
    int operator++(int){  // int 代表是一个占位参数,区分前置和后置
        // 先返回自身,后+1
        per pp = *this;  // 先记录自身结果,在+1,然后返回记录
        m_num++;
        return pp.m_num;
    }


private:
    int m_num;
};

ostream & operator<< (ostream &cout,per &p){
    cout << p.m_num << endl;
    return cout;
}

// 前置递增运算符重载
void te03(){
    per p;
    cout << ++p << endl;
    cout << p << endl;

}
// 后置运算符重载
void te04(){
    per p1;
    cout << p1++ << endl;
    cout << p1 << endl;
}


// 递减运算符重载
class dele{
    friend ostream &operator << (ostream &cout,dele &de);
public:
    dele(){
        m_num = 9;
    }

    // 前置递增
    dele& operator--(){
        m_num--;
        return *this;
    }

    // 后置递增
    int operator--(int){
        dele d = *this;
        m_num--;
        return d.m_num;
    }


private:
    int m_num;
};

ostream &operator << (ostream &cout,dele &de){
    cout << de.m_num << endl;
    return  cout;
}


void te05(){
    dele de;
    cout << --de << endl;
    cout << de << endl;

    dele ded;
    cout << ded-- << endl;
    cout << ded << endl;
}
4、赋值运算符重载

如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝的问题

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

1、默认构造函数(无参,函数体为空

2、默认析构函数(无参,函数体为空

3、默认拷贝构造函数,对属性进行值拷贝

4、赋值运算符 ,对属性进行值拷贝

// 赋值运算符重载
class pers{
public:
    pers(int age){
         m_age = new int(age);  // 存放在堆区
    }

    ~pers(){
        if(m_age != NULL){
            delete m_age;
            m_age = NULL;
        }
    }

    // 重载赋值运算符
    pers & operator=(pers &p){

        // 先判断是否有属性在堆区,如果有先释放干净
        if(m_age != NULL){
            delete m_age;
            m_age = NULL;
        }
        // 深拷贝操作
        m_age = new int(*p.m_age);

        // 返回对象本身
        return *this;
    }

    int *m_age;
};

void te06(){
    pers p1(10);
    cout << "p1 的年龄是:" << *p1.m_age << endl;

    pers p2(9);
    pers p3(8);

    p2 = p1 = p3;   // 赋值操作,造成堆区的内容重复释放
    // 利用深拷贝解决浅拷贝的问题

    cout << "p2 的年龄是:" << *p2.m_age << endl;
    cout << "p1 的年龄是:" << *p1.m_age << endl;
    cout << "p3 的年龄是:" << *p3.m_age << endl;
}
5、关系运算符重载

作用:可以让两个自定义类型对象进行对比操作

//关系运算符重载
class compire{
public:
    compire(string name,int age){
        this->name = name;
        this->age = age;
    }

    //关系运算符重载
    bool operator==(compire &c2){
        if(this->name == c2.name && this->age == c2.age){
            return true;
        }else return false;
    }

    string name;
    int age;
};

void te07(){
    compire c1("哈哈",23);
    compire c2("娜娜",23);
    compire c3("娜娜",23);

    if(c1 == c2){
        cout << " c1 和 c2 是相等的" << endl;
    }else {
        cout << " c1 和 c2 是不等的" << endl;
    }

    if(c3 == c2){
        cout << " c3 和 c2 是相等的" << endl;
    }else {
        cout << " c3 和 c2 是不等的" << endl;
    }

}
6、函数调用运算符重载

函数调用运算符()也可以重载

由于重载后使用的方式非常像函数的调用,因此称为仿函数

仿函数没有固定写法,非常灵活

// 函数调用运算符重载
class myPrint{
public:

    // 函数调用运算符重载
    void operator()(string test){
        cout << test << endl;
    }
};
void te08(){
    myPrint m;
    m("daihan");  // 仿函数

  // 匿名函数对象, 匿名对象,使用的是调用运算符重载

    myPrint()("hhhhh");
}

六、继承

优点:减少重复代码

1、继承的基本语法

基本语法:class 子类 :继承方式 父类

子类也称为派生类

父类也称为基类

//使用继承
class baseP{
public:
    void head(){
        cout << "首页、公开课、登录、注册" << endl;
    }
    void footer(){
        cout << "帮助中心、交流合作、站内地图" << endl;
    }
    void left(){
        cout << "java、python、C++......" << endl;
    }
};
//java 页面
class Javaa:public baseP{
public:
    void content(){
        cout << "java 学习内容" << endl;
    }
};
//python 页面
class Pythonn:public baseP{
public:
    void content(){
        cout << "python 学习内容" << endl;
    }
};
//C++ 页面
class Cprogram:public baseP{
public:
    void content(){
        cout << "C++ 学习内容" << endl;
    }
};
void test02(){
    cout << "java学习页面如下:" << endl;
    Javaa jaa;
    jaa.head();
    jaa.footer();
    jaa.left();
    jaa.content();

    cout << "--------------------" << endl;

    cout << "python学习页面如下:" << endl;
    Pythonn pyy;
    pyy.head();
    pyy.footer();
    pyy.left();
    pyy.content();

    cout << "--------------------" << endl;
    cout << "C++学习页面如下:" << endl;
    Cprogram p1;
    p1.head();
    p1.footer();
    p1.left();
    p1.content();
}
2、继承方式

继承方式一共有三种:

  • 公共继承

  • 保护继承

  • 私有继承

// 继承方式
class father{
public:
    int a;
protected:
    int b;
private:
    int c;
};
class sonPublic :public father{
public:

    void fun(){
        a = 10;
        b = 9;
        //c = 8; // b报错
    }
};
class sonProtected :protected father{
public:

    void fun(){
        a = 10;
        b = 9;
        //c = 8; // b报错
    }
};
class sonPrivate : private father{
public:
    void fun(){
        a = 10;
        b = 9;
        //c = 8; // b报错
    }

};
void test03(){
    sonProtected s2;
    //cout << s2.a << endl; // a 是受保护的,报错

    sonPrivate s3;
    //cout << s3.a << endl; // a是私有成员,报错
}
3、继承中的对象模型

问题:从父类继承过来的成员,哪些属于子类对象中?

//继承中的对象模型
class base{
public:
    int a;
protected:
    int b;
private:
    int c;
};
class daughter :public base{
public:
    int m_d;
};
void test04(){

    //  父类中所有静态成员属性都会被子类继承下去
    //  父类中私有成员属性,是被编译器隐藏,访问不断,但是确实被继承下去

    daughter a;
    base b;
    cout << sizeof(a) << endl;   // 16
    cout << sizeof(b) << endl;   //12
}
4、继承中构造和析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数

顺序如下:

1父类的构造函数

2子类的构造函数

3子类的析构函数

4父类的析构函数

总结:继承中先调用父类构造函数,子啊调用子类构造函数,析构顺序与构造相反

5、继承同名成员处理方式

问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据?

  • 访问子类同名成员 直接访问即可

  • 访问父类同名成员 需加作用域

//继承中同名成员处理方式
class ba{
public:
    ba(){
        m_a = 100;
    }
    void fun(){
        cout << "父类中的成员函数" << endl;
    }
    void fun(int a){
        cout << "父类daiyou int a 中的成员函数" << endl;
    }
    int m_a;
};
class bason:public ba{
public:

    bason(){
        m_a = 200;
    }
    void fun(){
        cout << "子类中的成员函数" << endl;
    }

    int m_a;
};
void test05(){
    bason b;
    cout << b.m_a << endl;  // 直接访问的是子类 bason 下面的 m_a
    cout << b.ba::m_a << endl; // 访问的是父类 ba 下面的 m_a

    b.fun();  // 子类中的成员函数的调用
    b.ba::fun();  // 父类中的成员函数的调用

    // 如果子类中出现和父类同名的成员函数,子类中的同名成员会隐藏掉父类中的所有同名成员函数
    b.ba::fun(100) ;
}

如果子类中出现和父类同名的成员函数,子类中的同名成员会隐藏掉父类中的所有同名成员函数

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

问题:继承中同名的静态成员在子类对象上如何进行访问

静态成员和非静态成员出现同名,处理方式一致

  • 访问子类同名成员,直接访问即可

  • 访问父类同名成员,需要加作用域

//继承中静态同名成员处理方式
class dd{
public:
    static void fun(){
        cout << "静态父类下成员函数的调用" << endl;
    }

    static int a;
};
int dd::a = 100;  // 静态成员类外初始化(必须)

class ddson :public dd{
public:
    static void fun(){
        cout << "静态子类下成员函数的调用" << endl;
    }
    static int a;

};
int ddson::a = 90;

void test06(){
    // 静态成员变量
    //利用对象访问
    ddson s;
    cout << s.a << endl;   // 90 直接访问时子类的成员变量
    cout << s.dd::a << endl;  // 100 加上作用域访问的是 父类中的成员变量
    //利用类名访问
    cout << ddson::a << endl ;
    cout << ddson::dd::a << endl;  // 通过类名,访问作用域下的a

    // 静态成员函数
    // 利用对象访问
    ddson ss;
    ss.fun();  // 直接调用子类下的静态函数
    ss.dd::fun();  // 作用域调用父类下的静态函数

    // 利用类名访问
    ddson::fun();
    ddson::dd::fun();  // 通过类名,访问作用域下的fun
   // 如果子类中出现和父类同名的静态成员函数,子类中的同名成员会隐藏掉父类中的所有同名静态成员函数

}

// 如果子类中出现和父类同名的静态成员函数,子类中的同名成员会隐藏掉父类中的所有同名静态成员函数

7、多继承语法

C++ 中允许一个类继承多个类

语法:class 子类:继承方式 父类1,继承方式 父类2……..

多继承可能会引发父类中有同名成员出现,需要加作用域区分

C++ 实际开发中不建议使用多继承

// 多继承类
class dpdpd:public dd ,public ba{
public:
    dpdpd(){
        m_c = 90;
    }
    int m_c;
};
void test07(){
    dpdpd dd;
    cout << dd.dd::m_a << endl;  // 当出现同名的成员时,需要加上作用域
    cout << dd.ba::m_a << endl;
    cout << dd.m_c << endl;
    cout << dd.a << endl;

}
8、菱形继承 (虚继承解决此问题)

概念:两个派生类继承同一个基类,又有某个类同时继承两个派生类。成为菱形继承,或者钻石继承

// 菱形继承
class baseFather{
public:
    baseFather(){
        m_age = 25;
    }
    int m_age;
};

// 利用虚继承,解决菱形继承的问题
//  class son1:virtual public baseFather 就是虚继承
//  baseFather 称为虚基类
class son1:virtual public baseFather{
public:
//    son1(){
//        m_age = 11;
//    }
//    int m_age;
};
class son2:virtual public baseFather{
public:
//    son2(){
//        m_age = 23;
//    }
//    int m_age;
};
class sun:public son1,public son2{
public:

};

void test08(){
    sun ss;
    ss. son1:: m_age = 11;
    ss. son2:: m_age = 23;

    //  当两个父类拥有相同数据,需要加以作用域区分
    cout << ss.son1::m_age << endl;  // 11
    cout << ss.son2::m_age << endl;  // 23
    cout << ss.m_age << endl;

    //  加上虚继承都为23
    // 相当于只拥有静态变量,共享同一份数据
    cout << ss.son1::m_age << endl;  // 23
    cout << ss.son2::m_age << endl;  // 23
    cout << ss.m_age << endl;   // 23

}

七、多态

1、多态的基本概念

多态是C++ 面向对象三大特性之一

分类:

  • 静态多态 :函数重载和运算符重载属于静态多态,复用函数名

  • 动态多态 :派生类和虚函数实现运行时多态

两者区别:

  • 静态多态的函数地址早绑定:编译阶段确定函数地址

  • 动态多态的函数地址晚绑定:运行阶段确定函数地址

动态多态满足条件

1、有继承关系

2、子类要重写父类的虚函数

动态多态的使用:

父类的指针或者引用 执行子类对象

/*
 动态多态满足条件
 1、有继承关系
 2、子类要重写父类的虚函数

 动态多态的使用:
 父类的指针或者引用 执行子类对象
 */

class animal{
public:
    // 虚函数
    virtual void speak(){
        cout << "动物在说话" << endl;
    }
};
class Cat:public animal{
public:
    void speak(){
        cout << "xi猫在说话" << endl;
    }
};
class dog :public animal{
public:
    void speak(){
        cout << "狗狗狗在说话 " << endl;
    }
};
// 动作函数
// 地址早绑定,在编译阶段就确定函数地址
// 如果要猫先说话,那么这个函数地址就不可以早绑定,需要在运行阶段进行绑定,地址晚绑定
void doSpeak(animal &ani){
    ani.speak();
}
void test01(){
    Cat cat;
    doSpeak(cat);

    dog dd;
    doSpeak(dd);
}
2、多态案例-计算器类

多态优点:

  • 代码组织结构清晰

  • 可读性强

  • 利于前期和后期的扩展以及维护

案例:实现两个操作数进行运算的计算器类

// 案例-------计算器类
class computer{  // 普通写法
public:

    int getReuslt(string oper){
        if(oper == "+"){
            return num1 + num2;
        }else if(oper == "-"){
            return num1 - num2;
        }else if(oper == "*"){
            return num1 * num2;
        }else {
            return num1 / num2;
        }
    }

    int num1;
    int num2;
};

void test02(){
    computer c;
    c.num1 = 9;
    c.num2 = 8;
    cout << c.getReuslt("*") << endl;
}

// 利用多态实现计算器
class com{
public:

    virtual int getResult(){
        return 0;
    }

    int num1;
    int num2;
};
class addCom:public com{  // 加法
public:
    int getResult(){
        return num1 + num2;
    }
};
class deleCom:public com{  // 减法
public:
    int getResult(){
        return num1 - num2;
    }
};
class chengCom:public com{  // 乘法
public:
    int getResult(){
        return num1 * num2;
    }
};

int jisuan(com &cc){ // 父类的引用指向子类对象
    return cc.getResult();
}

void test03(){
    addCom d;
    d.num1 = 90;
    d.num2 = 10;
    cout << jisuan(d) << endl;

}
void test04(){
    com *abc = new chengCom;  // 父类指针指向子类对象
    abc->num1 = 9;
    abc->num2 = 7;
    cout << abc->getResult() << endl;
  // 用完需要销毁
    delete abc;
}
3、纯虚函数和抽象类

多态中,父类中的虚函数通常没有意义,主要都是调用子类重写的内容

因此可以将虚函数改为纯虚函数

基本语法: virtual 返回值类型 函数名 (参数列表)= 0;

当类中有了纯虚函数,这个类也称为 抽象类

抽象类特点:

  • 无法实例化对象

  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

//    virtual int getResult(){
//        return 0;
//    }
    // 改为纯虚函数为:
    virtual int getResult() = 0;
4、多态案例二-制作饮品
// 案例-----制作饮品
class dringk{
public:
    void firstStep(){
        cout << "1、煮水" << endl;
    }

    virtual void secondStep() = 0;

    void thirdStep(){
        cout << "3、倒入杯中" << endl;
    }

    virtual void lastStep() = 0;

    void make(){
        firstStep();
        secondStep();
        thirdStep();
        lastStep();
    }

};

class coffa:public dringk{
public:
    void secondStep(){
        cout << "2、冲泡咖啡" << endl;
    }
    void lastStep(){
        cout << "4、加糖和牛奶" << endl;
    }

};

class tea:public dringk{
public:
    void secondStep(){
        cout << "2、冲泡茶叶" << endl;
    }
    void lastStep(){
        cout << "4、加柠檬" << endl;
    }

};
void test05(){
    dringk *dd = new coffa;
    dd->make();
    delete dd;

    cout << "-----------" << endl;
    dd = new tea;
    dd->make();
    delete dd;
}
5、虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构泰马

解决方式:将父类中的析构函数改为虚析构或者纯虚构

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象

  • 都需要有具体的函数实现

虚析构和纯虚构区别:

如果是纯虚析构,给类属于抽象类,无法实例化对象

虚析构语法:virtual ~类名(){}

纯虚析构语法:virtual ~类名 () = 0 ;

//虚析构和纯虚析构
class annnn{
public:
    annnn(){
        cout << "父类构造函数调用" << endl;
    }
    // 虚析构
    // 利用虚析构可以解决父类指针释放子列对象时不干净的问题
//    virtual ~annnn(){
//        cout << "父类析构函数调用" << endl;
//    }

    // 纯虚构
    // 有了纯虚析构,此类也属于抽象类
    virtual ~annnn() = 0;

    virtual void speak() = 0;
};

 // 纯虚析构必要要有声明和实现。
// 父类也会有数据开辟到堆区
annnn::~annnn(){
    cout << "父类纯虚析构函数调用" << endl;
}

class caa :public annnn{
public:
    caa(string name){
        cout << "子类构造函数调用" << endl;
        m_name = new string(name);  // 堆区属性
    }
    virtual void speak(){
        cout << *m_name << "  小猫  在说话" << endl;
    }
    ~caa(){
        if (m_name != NULL) {
            cout << "子类析构函数调用" << endl;
            delete m_name;
            m_name = NULL;
        }
    }
    string *m_name;
};
void test06(){
    annnn * ad = new caa("haha");
    ad->speak();
    // 父类的指针在析构时不会调用子类中析构函数,导致子类如果有堆区的数据会出现内存泄漏
    delete ad;
}

总结:

  • 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象

  • 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构

  • 拥有纯虚析构函数的类也称为抽象类

6、多态案例三-电脑组装
// 案例三 --电脑组装
class Cpu{
public:
    virtual void prided() = 0;    
};
class Xianka{
public:
    virtual void display() = 0;

};
class Cache{
public:
    virtual void cash() = 0;

};
class buildComp{
public:
    buildComp(Cpu *cpuu,Xianka *xiankaa,Cache *cachee){
        cpu = cpuu;
        xianka = xiankaa;
        cache = cachee;
    }
    // 提供工作的函数
    void work(){
        cpu->prided();
        xianka->display();
        cache->cash();
    }
    // 提供析构函数 释放零件
    ~buildComp(){
        cout << "释放零件" << endl;
        if (cpu != NULL) {
            delete cpu;
            cpu = NULL;
        }
        if (xianka != NULL) {
            delete xianka;
            xianka = NULL;
        }
        if (cache != NULL) {
            delete cache;
            cache = NULL;
        }
    }
private:
    Cpu *cpu;
    Xianka *xianka;
    Cache *cache;
};
class InterCpu:public Cpu{
public:
    void prided(){
        cout << "inter的CPU提供计算" << endl;
    }

};
class InterXianka:public Xianka{
public:
    void display(){
        cout << "inter的显卡提供显示" << endl;
    }
};
class InterCache:public Cache{
public:
    void cash(){
        cout << "inter的cache提供存储" << endl;
    }
};
void test07(){
    // 创建第一台电脑
    Cpu *intercpu = new InterCpu;
    Xianka *interXianka = new InterXianka;
    Cache *interCache = new InterCache;
    buildComp *c1 = new buildComp(intercpu,interXianka,interCache);
    c1->work();
    delete c1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱空nnnn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值