IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++学习笔记(7):类和对象 -> 正文阅读

[C++知识库]C++学习笔记(7):类和对象


本博文是学习黑马程序员C++视频时做的笔记,记录一下只是方便温故知新,不做其他用途。

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

一、封装

封装:隐藏对象的属性和实现细节,仅对外公开接口和对象进行交互,将数据和操作数据的方法进行有机结合。
封装的意义:
(1)将属性和行为作为整体,表现生活中的事物
(2)将属性和行为加以权限控制

1.1 类的定义

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

#include<iostream>
using namespace std;

//创建类
class Student{
public:
//    属性
    string name;
    int s_id;
//    行为
    void ShowStudent()
    {
        cout<<"name:"<<name<<" student_id="<<s_id<<endl;
    }
};

int main()
{
//    实例化对象(通过类创建对象)
    Student s1;
    s1.name = "张三";
    s1.s_id = 001;
    s1.ShowStudent();
    return 0;
}


在这里插入图片描述

1.2 访问权限

把属性和行为放在不同的权限下,加以控制。
(1)public:公共权限——成员 类内可以访问,类外可以访问。
(2)protected:保护权限——成员 类内可以访问,类外不可访问,儿子可以访问父亲中的保护内容。
(3)private:私有权限——成员 类内可以访问,类外不可访问。

#include<iostream>
#include<string>
using namespace std;
//三种访问权限
//(1)public:公共权限——成员 类内可以访问,类外可以访问。
//(2)protected:保护权限——成员 类内可以访问,类外不可访问,儿子可以访问父亲中的保护内容。
//(3)private:私有权限——成员 类内可以访问,类外不可访问。
class Person
{
//    公共权限
public:
    string m_name; //名字
//    保护权限
protected:
    string m_car;//车
//    私有权限
private:
    int m_password;//支付密码
    
public:
    void func()
    {
        m_name="张三";
        m_car = "小车";
        m_password=123456;
        cout<<m_name<<m_car<<m_password<<endl;
    }
};
int main()
{
    Person p1;
    p1.m_name="王二麻子";//公共权限,类外也可以访问
//     p1.m_car="电动车";//保护权限,类外不可访问,其子类可以访问。
//     p1.m_password =987654;//私有属性,类外不可访问
    p1.func();
    return 0;
}

1.3 class和struct区别

类内都可以访问;类外,class 默认权限为私有,struct默认权限为公有

#include<iostream>
#include<string>
using namespace std;

class C1{
    int m_A;//class 默认权限为私有
};

struct S1{
    int m_A;//struct默认权限为公有
};

int main()
{
    C1 c1;
//    c1.m_A = 100;//报错
    S1 s1;
    s1.m_A = 100;//正常
    return 0;
}

1.4 成员属性私有化

优点:
(1)可以控制读写权限
(2)对于写可以检测数据的有效性

例子中,将所有变量都在设置为私有,然后在public里面分别添加各个对应的函数就可以实现各种权限。

#include<iostream>
#include<string>
using namespace std;
//实现目标:
//1、姓名 可读可写
//2、年龄 只读
//3、爱人 只写
class Person
{
//步骤一、将所有变量设置为私有
private:
    string m_name;
    int m_age;
    string m_lover;
//步骤二、添加各个对应的函数就可以实现各种权限
public:
//    1、姓名 可读可写
//    设置姓名(写入)
    void setname(string name)
    {
        m_name=name;
    }
//    显示姓名(读取)
    string showname()
    {
        return m_name;
    }

//    2、年龄 只读
    int readAge()
    {
        m_age=23;
        return m_age;
    }

//    3、设置爱人 只写
    void setlover(string lover)
    {
        m_lover = lover;
    }
};

int main()
{
    Person p1;
//    1 姓名可读可写
    p1.setname("王五");
    cout<<p1.showname()<<endl;

//    2 年龄 只读
    cout<<p1.readAge()<<endl;
    
//   3 爱人 只写
    p1.setlover("ccc");
}

在这里插入图片描述

二、对象特性

2.1构造函数和析构函数

构造函数:用于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用。
语法:类名(){}
(1)没有返回值 也不写void;
(2)函数名称与类名称相同;
(3)构造函数可以有参数,因此可以发生重载;
(4)创建对象时,会自动调用构造函数,无需手动调用,而且只会调用一次。

析构函数:用于对象销毁前系统自动调用,执行一些清理工作。
语法:~类名(){}
(1)没有返回值 也不写void;
(2)函数名称与类名称相同,需要加上符号~
(3)构造函数不可以有参数,因此不可以发生重载;
(4)对象销毁前,会自动调用构造函数,无需手动调用,而且只会调用一次。

#include<iostream>
#include<string>
using namespace std;
//1、构造函数分为:
//   按参数:无参构造、有参构造
//   按类型:普通构造、拷贝构造
class Person
{
//构造函数
private:
    string m_name;
public:
    Person()//无参构造
    {
        cout<<"无参构造函数!"<<endl;
    }
    Person(int a)//有参构造
    {
        m_name=a;
        cout<<"有参构造函数!"<<endl;
    }
    Person(const Person &p)//拷贝构造函数
    {
        cout<<"拷贝构造函数!"<<endl;
    }
//2 析构函数
    ~Person()
    {
        cout<<"析构函数!"<<endl;
    }
};
//调用函数
void test()
{
    //1、括号法
    Person p1;//默认调用
    Person p2(10);//有参构造函数
    Person p3(p2);//拷贝构造函数
//    //2、显示法
//    Person p1;
//    Person p2=Person(10);//有参构造函数
//    Person p3=Person(p2);//拷贝构造函数
//    //3、隐式转换法
//    Person p4=10;//相当于:Person p4=Person(10),有参构造函数
//    Person p5=p4;//拷贝构造函数
}
int main()
{
    test();
    return 0;
}

在这里插入图片描述

深拷贝和浅拷贝区别
深拷贝:在堆区重新申请空间,进行拷贝操作;
浅拷贝:简单的赋值拷贝操作。

#include<iostream>
#include<string>
using namespace std;

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 = p.m_height;  编译器默认创造的拷贝构造
        m_height = new int(*p.m_height);//深拷贝解决方法
    }

    //析构函数
    ~Person() {
        if (m_height != NULL)
        {
            delete m_height;
            m_height = NULL;
        }
        cout<<"析构函数调用"<<endl;
    }
public:
    int m_age;
    int* m_height;
};

void test()
{
    Person p1(26, 172);

    Person p2(p1);

    cout << "p1的年龄= " << p1.m_age << " 身高= " << *p1.m_height << endl;

    cout << "p2的年龄=" << p2.m_age << " 身高= " << *p2.m_height << endl;
}

int main() {
    test();
    return 0;
}

在这里插入图片描述

2.2 初始化列表

语法:构造函数():属性1,属性2…{}

#include<iostream>
#include<string>
using namespace std;

class Person
{
public:
    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;
    int m_B;
    int m_C;
};

int main() {
    Person p(1,2,3);
    p.PrintPerson();
    return 0;
}

在这里插入图片描述

2.3 类对象作为类成员

类中的成员可以是另一个类的对象。

#include<iostream>
#include<string>
using namespace std;

//手机类
class Phone
{
public:
    Phone(string Pname)
    {
        m_Pame = Pname;
        cout<<"Phone 类"<<endl;
    }
    ~Phone(){
        cout<<"Phone 析构"<<endl;
    }
    string m_Pame;
};
//人类
class Person
{
public:
    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 test()
{
//  会先构造类类,再构造自身;
//  先析构自身,再析构其他类
    Person p("张三","xiaomi");
    cout<<p.m_Name<<"has a "<<p.m_Phone.m_Pame<<endl;
}

int main()
{
    test();
    return 0;
}

在这里插入图片描述

2.4 静态成员函数

所有对象共享一个函数
静态成员函数只能访问静态成员变量

#include<iostream>
using namespace std;
//创建类
class Student{
public:
//静态成员函数
    static void func()
    {
        m_A = 100;//静态成员变量是共享的
//静态成员函数不能访问非静态成员,这是因为静态函数属于类而不是属于整个对象。
//        m_B = 200;//报错,静态函数不可以访问非静态成员变量。一个类可以实例化出多个对象,函数体无法区分属于m_B属于哪个对象
        cout<<"static void func:"<<endl;
    }
    static int m_A;//静态成员变量
    int m_B;//静态成员变量
};
int Student::m_A;//静态成员变量需要在外部声明

//两种访问方式
void test01(){
//   1. 通过对象访问
    Student s1;
    s1.func();

//    2. 通过类名访问
    Student::func();
}

int main()
{
    test01();
    return 0;
}

2.5 C++对象模型和this指针

2.5.1 成员变量成员函数分开存储

#include<iostream>
#include<string>
using namespace std;
class Person
{
    int m_name;//非静态成员属于类对象上,占用内存
    static int m_b; //静态成员变量不属于类对象上,访问内存是发现不占内存
    void func(){}//非静态成员函数也不占内存,不属于类的对象上

};
void test01()
{
    Person p;
//    空对象占用存储空间为:1字节
    cout<<"the size of p is:"<<sizeof(p)<<endl;

}
int main()
{
    test01();
    return 0;
}

2.5.2 this指针概念

1 概念:this指针指向被调用的成员函数所属的对象。
2 特点
(1)this指针隐含在每一个非静态成员函数内,
(2)this指针不需要定义直接使用
3 用途
(1)当形参和成员变量同名时,可用this指针来区分;
(2)返回对象本身return *this

#include<iostream>
#include<string>
using namespace std;
//this指针隐含在每一个非静态成员函数内,不需要定义直接使用
//作用:1、解决名称冲突;2、返回对象本身(*this)
class Person
{
public:
    Person(int age)
    {
        //this指针指向被调用的成员函数所属的对象
        this->age = age;
    }
    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 p1(10);
    Person p2(10);
//    链式编程思想
    p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);

    cout<<"p2的年龄"<<p2.age<<endl;
}
int main()
{
//    test01();
    test02();
    return 0;
}

在这里插入图片描述

2.5.3 空指针访问成员函数(判定this指针是否为空来解决)

空指针调用普通成员函数,如果使用到了this指针(访问非静态成员变量),程序会崩溃;如果没有使用到this指针,程序不会崩溃。

#include<iostream>
#include<string>
using namespace std;

class Person
{
public:
    void showClassName(){
        cout<<"this is Person class"<<endl;
    }
    void showClassage(){
/*+++++++++++++++++解决办法,空指针判定+++++++++++*/
    if(this == NULL){
        return;
    }
/*+++++++++++++++++++++++++++++++++*/
        cout<<"this is class age"<< this->m_age<<endl;
    }
    int m_age;
};

void test01()
{
    Person *p1  = NULL;
    p1->showClassName();
//    如果没有判定this指针是否为空,下面这行代码报错
    p1->showClassage();//报错原因,传入的指针为空
}

int main()
{
    test01();
    return 0;
}

在这里插入图片描述

2.5.4 const修饰成员函数(常函数和常对象)

1、常函数( void func() const {…} )
(1)成员函数后加const变成常函数;
(2)常函数内不可以修改成员属性;
(3)成员属性声明的时候加关键字mutable后,在常函数中就可以修改。
2、常对象
(1)声明对象的时候在前面加上const即可变成常对象;
(2)常对象只能访问常函数,普通函数不能被访问。

#include<iostream>
#include<string>
using namespace std;

class Person
{
public:
//    常量对象
    Person(){
        m_a = 0;
        m_B = 0;
    }
//    this指针的本质是指针常量 指针的指向是不可以修改的
//    1 常函数:成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改
    void showPerson() const
    {
//        this->m_a = 100;//加了const后,指针指向的值不能修改,此处会报错!
        this->m_B = 100;
        cout<<"常函数"<<endl;
    }

    void func1()
    {
        cout<<"普通函数"<<endl;
    }
public:
    int m_a;
    mutable int m_B;//特殊变量,即使在常函数中也可以修改这个值
};

void test01()
{
    Person p1;
//    类对象可以调用常函数、普通函数
    p1.showPerson();
    p1.func1();
//    也可修饰成员变量,带mutabe关键字的成员变量
    p1.m_a = 100;
    p1.m_B = 100;
}
void test02()
{
//    2 常对象:对象前加了const后,变为常对象,其值不能被修改
    const Person p2;
    p2.showPerson();
//    p1.func1();//报错,常对象只能调用常函数
//    p1.m_a = 100;//会报错
    p2.m_B = 100;//加了mutabe关键字的可以修改

}
int main()
{
    test01();
    test02();
    return 0;
}

三、 C++友元

友元的作用就是令全局函数或者类,可以访问另一个类。

3.1 全局函数做友元

例子中的void goodGay(Buiding *buiding)为全局函数,friend void goodGay(Buiding *buiding)为友元,可以访问Buiding中的私有成员。

#include<iostream>
#include<string>
using namespace std;
class Buiding
{
    //goodGay全局函数是Buiding的好朋友,可以访问Buiding中的私有成员,友元
    friend void goodGay(Buiding *buiding);
public:
    Buiding()
    {
        m_sintingroom="客厅";
        m_bedroom = "卧室";
    }
public:
    string m_sintingroom;//客厅
private:
    string m_bedroom;//卧室
};
//全局函数
void goodGay(Buiding *buiding)
{
    cout<<"goodGay正在访问:"<<buiding->m_sintingroom<<endl;
    cout<<"goodGay正在访问:"<<buiding->m_bedroom<<endl;
}

void test01()
{
    Buiding Buiding;
    goodGay(&Buiding);

}
int main()
{
    test01();
    return 0;
}

在这里插入图片描述

3.2 类做友元

例子中在Building类中添加friend class GoodGay;这一句,实现类做友元,GoodGay类可以访问Building类中的私有内容。

#include<iostream>
using namespace std;
#include<string>

//类做友元
class Building;
class GoodGay
{
public:
    GoodGay();
public:
    void visit();//参观函数	访问Building中的属性
    Building* building;
};

class Building
{
    //goodGay类是Building好朋友,可以访问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;
}

void test01()
{
    GoodGay g;
    g.visit();
}

int main()
{
    test01();
    return 0;
}

在这里插入图片描述

3.3 成员函数做友元

例子中friend void goodGay::visit1()这句,实现了让基友visit1可以访问卧室和客厅,基友visit2()只能访问客厅。

//成员函数做友元函数:
#include<iostream>
#include<string>
using namespace std;

class Building;
class goodGay
{
public:
    goodGay();
    void visit1();//让visit1函数可以访问Building中私有成员
    void visit2();//让visit2函数不可以访问Building中私有成员

private:
    Building * building;
};

class Building
{
//告诉编译器 goodGay类下的visit1函数是Building类的好朋友,可以访问到Building类中私有内容
    friend void goodGay::visit1();
public:
    Building();
public:
    string m_sittingroom;	//客厅
private:
    string m_bedroom;		//卧室
};

//类外实现成员函数
Building::Building()//Building的构造函数
{
    this->m_bedroom = "卧室";
    this->m_sittingroom = "客厅";
}

goodGay::goodGay()//goodGay的构造函数
{
    building = new Building;
}
void goodGay::visit1()
{
    cout << "基友visit1正在" << this->building->m_sittingroom << endl;
    cout << "基友visit1正在" << this->building->m_bedroom << endl;

}
void goodGay::visit2()
{
    cout << "基友visit2正在" << this->building->m_sittingroom << endl;
    //cout << "基友visit2正在" << this->building->m_bedroom << endl;
}
void test01()
{
    goodGay gg;
    gg.visit1();
    gg.visit2();
}

int main()
{
    test01();
    return 0;
}

在这里插入图片描述

四、运算符重载

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

4.1 加号运算符重载

作用:实现两个自定义数据类型相加的运算。
主要包括:
(1)成员函数重载+号:Person p3 = p1.operator+(p2);
(2)全局函数重载+号:Person p3 = operator+(p1,p2);

#include<iostream>
#include<string>
using namespace std;
//加号运算符重载
class Person{

public:
    //1 成员函数重载+号
//    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 = p2.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;
//  (1)简单版本的调用
//    Person p3 = p1 + p2;
//  (2)成员函数的本质调用
//    Person p3 = p1.operator+(p2);
//  (3)全局函数的本质调用
    Person p3 = operator+(p1,p2);
//   (4) 函数重载版本的调用
    Person p4 = p1 + 100;

    cout<<"p3.m_A = "<<p3.m_A<<endl;//20
    cout<<"p3.m_B = "<<p3.m_B<<endl;//20

    cout<<"p4.m_A = "<<p4.m_A<<endl;//110
    cout<<"p4.m_B = "<<p4.m_B<<endl;//110
}

int main()
{
    test01();
    return 0;
}

在这里插入图片描述

4.2 左移运算符重载

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


#include"iostream"
using namespace std;
class Person//自己创建一个类
{
//    声明为友元
    friend ostream & operator<<(ostream &out,Person &p);
public:
    Person(int a,int b)
    {
        this->m_A = a;
        this->m_B = b;
    }

private:
    int m_A;
    int m_B;
};

//利用全局函数重载左移运算符
ostream & operator<<(ostream &out,Person &p)
{
    out<<"m_A="<<p.m_A<<" m_B="<<p.m_B;
    return out;
}

void test01()
{
    Person p(10,10);
    cout<<p<<" Hello world!"<<endl;
}

int main()
{
    test01();
    return 0;
}

在这里插入图片描述

4.3 递增运算符重载

#include <iostream>
using namespace std;

//重载递增运算符

//自定义整型
class MyInteger {
    friend ostream &operator<<(ostream &cout, MyInteger myint);
public:
    MyInteger()
    {
        m_Num = 0;
    }

    //重载前置++运算符  返回引用是为了一直对一个数据进行递增操作
    MyInteger &operator++()
    {
        //先进行++运算
        m_Num++;
        //再将自身做一个返回
        return *this;
    }

    //重载后置++运算符
    //这个int代表的是占位参数,可以用于区分前置和后置递增
    MyInteger operator++(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;
}

void test02()
{
    MyInteger myint;
    cout<<myint++<<endl;
}

int main()
{
    test01();
    test02();
    return 0;
}

在这里插入图片描述

4.4 赋值运算符重载

#include <iostream>
using namespace std;
//赋值运算符重载
class Person
{
public:
    // 年龄开辟到堆区
    Person(int age)
    {
        m_Age = new int(age);  // new 来的对象都是一个内存地址
    }

    //  把年龄这个属性写成指针
    int *m_Age;

    // 析构函数,程序员自己控制内存
    ~Person()
    {
        if (m_Age != NULL)
        {
            delete m_Age; // 删除所占内存,并置空
            m_Age = NULL;
        }
    }

    Person& operator=(Person &p)
    {
        //首先,应该先判断是否有属性在堆区,如果有应该先释放干净
        if (m_Age != NULL)
        {
            delete m_Age;
            m_Age = NULL;
        }
        //再进行深拷贝 解决浅拷贝问题
        m_Age = new int(*p.m_Age);
        //最后返回自身
        return *this;
    }
};

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

在这里插入图片描述

4.5 关系运算符重载

#include<iostream>
#include <stdio.h>
#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;
        }
    }
    string m_Name;
    int m_Age;

};
void test01(){
    Person p1("王二",18);
    Person p2("王二",19);
    if(p1==p2)
    {
        cout<<"相等";
    }
    else
    {
        cout <<"不相等";
    }

}
int main(){
    test01();
    return 0;
}

在这里插入图片描述

4.6 函数调用运算符重载

函数调用运算符()可以重载,由于重载后使用的方式非常像函数的调用,称为仿函数。

#include <iostream>
#include <stdio.h>
using namespace std;

//函数调用运算符重载
class MyPrint
{
public:
    void operator()(string test)
    {
        cout <<test <<endl;
    }
};

void test01(){
    MyPrint myprint ;
    myprint("Hello World!");//由于使用起来非常像函数的调用,因此叫仿函数
}

class MyAdd
{
public:
    int operator()(int num1 ,int num2)
    {
        return num1+num2;
    }
};
void test02()
{
    MyAdd myadd;
    int sum = myadd(100,200);
    cout <<	"sum="<<sum<<endl;
//MyAdd()匿名函数对象
    cout <<MyAdd()(100,100);
}
int main(){
    test01();
    test02();
    return 0;
}

在这里插入图片描述

五、继承

继承是面向对象的三大特性之一。利用继承的方法可以有效减少重复代码。

5.1 继承的基本语法

语法:class 子类:继承方式 父类
class jpage : public page

#include <iostream>
using namespace std;

class basepage
{
public:
    void show()
    {
        cout << "基础界面" << endl;
    }

};
class javapage : public basepage
{
public:
    void javashow()
    {
        cout << "java界面" << endl;
    }
};

class cpppage : public basepage
{
public :
    void cppshow()
    {
        cout << "cpp界面" << endl;
    }
};
class pythonpage : public basepage
{
public:
    void pythonshow()
    {
        cout << "python界面" << endl;
    }
};

void test01(){
    javapage jav;
    jav.show();
    jav.javashow();
    cout << "**************" << endl;

    cpppage cp;
    cp.show();
    cp.cppshow();
    cout << "**************" << endl;

    pythonpage py;
    py.show();
    py.pythonshow();
    cout << "**************" << endl;
}

int main()
{
    test01();
    return 0;
}

在这里插入图片描述

5.2 继承方式

语法: class 子类 : 继承方式 父类
方式: 公共继承、保护继承、私有继承

#include <iostream>
using namespace std;

//父类1
class Base1
{
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C;
};

//1 公共继承
class Son1 :public Base1
{
public:
    void func()
    {
        m_A; //父类中的public权限成员,继承到子类中仍然是public权限
        m_B; //父类中的protected权限成员,继承到子类中仍然是protected权限
        //m_C; //父类中的private权限成员,子类不可访问
    }
};

void test01()
{
    Son1 s1;
    s1.m_A = 100; //父类继承到Son1中,公共权限可访问
//    s1.m_B;//报错,到Son1中m_B是保护权限,类外访问不到
}

//父类2
class Base2
{
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C;
};

//2 保护继承
class Son2:protected Base2
{
public:
    void func()
    {
        m_A; //父类中的public权限成员,继承到子类中是protected权限
        m_B; //父类中的protected权限成员,继承到子类中仍然是protected权限
        //m_C; //父类中的private权限成员,子类不可访问
    }
};
void test02()
{
    Son2 s;
    //s.m_A; //子类中的protected权限,类外不可访问
}

//3 私有继承
class Base3
{
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C;
};
class Son3:private Base3
{
public:
    void func()
    {
        m_A; //父类中的public权限成员,继承到子类中是private权限
        m_B; //父类中的protected权限成员,继承到子类中仍然是private权限
        //m_C; //父类中的private权限成员,子类不可访问
    }
};

void test03()
{
    Son3 s;
//    s.m_A = 1000; //子类中的private权限成员,类外不可访问
}

class GrandSon3 :public Son3
{
public:
    void func()
    {
        //Son3是私有继承,所以继承Son3的属性在GrandSon3中都无法访问到
        //m_A;
        //m_B;
        //m_C;
    }
};

int main(){

    return 0;
}

5.3 继承中的对象模型

#include <iostream>
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 << "sizeof Son = " << sizeof(Son) << endl;//16
}

int main() {
    test01();
    return 0;
}

5.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 b;
    //继承中的构造和析构顺序如下:
    // 先构造父类,再构造子类,析构顺序与构造相反
    Son s;
}

int main() {
    test01();
    return 0;
}


在这里插入图片描述

5.5 继承中同名成员处理方式

#include <iostream>
using namespace std;

//继承中同名成员处理方式
class Base {
public:
    Base()
    {
        m_A = 100;
    }

    void func()
    {
        cout << "Base - func()调用" << endl;
    }

    void func(int a)
    {
        cout << "Base - func(int a)调用" << endl;
    }

public:
    int m_A;
};


class Son : public Base {
public:
    Son()
    {
        m_A = 200;
    }
    
    void func()
    {
        cout << "Son - func()调用" << endl;
    }
public:
    int m_A;
};

void test01()
{
    Son s;
    cout << "Son下的m_A = " << s.m_A << endl;//200
    cout << "Base下的m_A = " << s.Base::m_A << endl;//100

    s.func();//子类对象可以直接访问子类中的同名成员
//    如果子类中出现父类中被隐藏的同名成员函数,子类的同名成员会隐藏父类中的所有同名成员函数
    s.Base::func();//子类对象加父类作用域,可以访问父类下的同名成员
    s.Base::func(10);

}
int main() {
    test01();
    return 0;
}

在这里插入图片描述

5.6 继承中同名静态成员处理方式

#include <iostream>
using namespace std;

//继承中同名静态成员处理方式
class Base {
public:
    static void func()
    {
        cout << "Base - static void func()" << endl;
    }
    static void func(int a)
    {
        cout << "Base - static void func(int a)" << endl;
    }

    static int m_A;
};

int Base::m_A = 100;

class Son : public Base {
public:
    static void func()
    {
        cout << "Son - static void func()" << endl;
    }
    static int m_A;
};

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()
{
    //通过对象访问
    cout << "通过对象访问: " << endl;
    Son s;
    s.func();
    s.Base::func();

    cout << "通过类名访问: " << endl;
    Son::func();
    Son::Base::func();
    //出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
    Son::Base::func(100);
}
int main() {

    test01();
    test02();
    return 0;
}

在这里插入图片描述

5.7 多继承语法

C++中允许一个类继承多个类
语法:class 子类:继承方式 父类1,继承方式 父类2…
多继承中可能会引发父类中有同名成员出现,需要加作用域区分

#include <iostream>
using namespace std;

//多继承语法
//创建父类1
class Base1 {
public:
    Base1()
    {
        m_A = 100;
    }
public:
    int m_A;
};

//创建父类2
class Base2 {
public:
    Base2()
    {
        m_A = 200;  //开始是m_B 不会出问题,但是改为mA就会出现不明确
    }
public:
    int m_A;
};

//子类继承父类1和父类2
//语法:class 子类:继承方式 父类1 ,继承方式 父类2
class Son : public Base2, public Base1
{
public:
    Son()
    {
        m_C = 300;
        m_D = 400;
    }
public:
    int m_C;
    int m_D;
};

//多继承容易产生成员同名的情况
//通过使用类名作用域可以区分调用哪一个基类的成员
void test01()
{
    Son s;
    cout << "sizeof Son = " << sizeof(s) << endl;//16,
    cout << s.Base1::m_A << endl;//100
    cout << s.Base2::m_A << endl;//200
}

int main() {

    test01();
    return 0;
}

5.8 菱形继承

两个派生类继承同一个基类;
又有两个类同时继承这两个派生类;

#include <iostream>
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 = 10;
    st.Tuo::m_Age = 20;

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

在这里插入图片描述

六、多态

6.1 多态的基本概念

多态分为两类:
(1)静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名;
(2)动态多态: 派生类和虚函数实现运行时多态。
静态多态和动态多态的区别:
(1)静态多态的函数地址早绑定 - 编译阶段确定函数地址
(2)动态多态的函数地址晚绑定 - 运行阶段确定函数地址

#include <iostream>
using namespace std;

//多态的基本概念
//动物类
class Animal
{
public:
    //Speak函数就是虚函数
    //函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
    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 & animal = cat;
{
    animal.speak();
}
//
//多态满足条件:
//1、有继承关系
//2、子类重写父类中的虚函数
//多态使用:
//父类指针或引用指向子类对象

void test01()
{
    Cat cat;
    DoSpeak(cat);

    Dog dog;
    DoSpeak(dog);
}

int main()
{
    test01();
    return 0;
}


在这里插入图片描述

6.2 纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数。

纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
当类中有了纯虚函数,这个类也称为抽象类

抽象类特点:
(1)无法实例化对象;
(2)子类必须重写抽象类中的纯虚函数,否则也属于抽象类。

#include <iostream>
using namespace std;

//纯虚函数和抽象类
class Base
{
public:
//    一、纯虚函数:类中只要有一个纯虚函数就称为抽象类
//    二、抽象类特点
    //1 抽象类无法实例化对象
    //2 子类必须重写父类中的纯虚函数,否则也属于抽象类

//    下面这是一个纯虚函数:virtual 返回值类型 函数名 (参数列表)= 0 ;
    virtual void func() = 0;
};

class Son :public Base
{
public:
//下面重写父类中的纯虚函数
    virtual void func()
    {
        cout << "func函数调用" << endl;
    };
};

void test01()
{
//    Base b;// 错误,抽象类无法实例化对象
//    new Base;错误,抽象类无法实例化对象

    Base *base = new Son;
    base->func();
}

int main()
{
    test01();
    return 0;
}

在这里插入图片描述

6.3 虚析构和纯虚析构

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

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

虚析构和纯虚析构共性:
(1)可以解决父类指针释放子类对象;
(2)都需要有具体的函数实现。

虚析构和纯虚析构区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象。

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

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

#include <iostream>
using namespace std;
#include <string>

//虚析构和纯虚析构
class Animal
{
public:
    Animal()
    {
        cout << "Animal 构造函数调用!" << endl;
    }
//    纯虚函数
    virtual void Speak() = 0;

    //虚析构函数就是用来解决通过父类指针去释放,会导致子类对象可能清理不干净的问题
    //析构函数加上virtual关键字,变成虚析构函数
    //virtual ~Animal()
    //{
    //	cout << "Animal虚析构函数调用!" << endl;
    //}

//    纯虚析构
    virtual ~Animal() = 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()
    {
        cout << "Cat析构函数调用!" << endl;
        if (this->m_Name != NULL) {
            delete m_Name;
            m_Name = NULL;
        }
    }

public:
    string *m_Name;
};

void test01()
{
    Animal *animal = new Cat("Tom");
    animal->Speak();
    delete animal;
}

int main()
{
    test01();
    return 0;
}


在这里插入图片描述

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-11-22 12:09:27  更:2021-11-22 12:10:16 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 6:27:05-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码