4 类和对象

类型和变量

类型=类型数据+类型操作,比如整型

数据结构=结构定义+结构操作

C++内部类型其实都是数据结构,类只是C++让我们自己来创造数据结构

数据结构的核心思想永远都是封装,将数据和行为封装到一起,也就是结构定义和结构操作封装到一起。反应到C++中其实就是类和对象这部分知识。

类其实就是数据类型,对象其实就是变量,在面向对象程序设计思想中,对象其实代表的是一个个体。

命名空间其实就是包,把不同的类,方法封装到一个包里去,防止冲突

namespace haizei {
    int a,b;  
}
//命名空间的使用,把ab打包到haizei的命名空间中
int main {
    haizei::a = 1;//域作用符来访问
    cout<<haizei::a<<endl;
}

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

C++认为万物皆为对象,对象上有属性和行为

 

访问权限

struct和class都可以表现一个类,区别在于:struct的默认权限为公共,class默认权限为私有

将成员属性私有化的优点:

  自己控制读写权限:提供公共接口来读写,可以控制可读可写或者只读不可写等权限

  对于写权限,我们可以检测数据的有效性

一个类中,可以让另一个类作为本类的成员

分文件编写

  #pragma once   防止头文件重复包含

  .h  函数声明和成员变量声明即可

  .c 当中实现函数,但是要加作用域::

  声明和实现分文件编写,很清晰

 

构造和析构都是必须有的,你不写编译器给你写,但是是空的;不管是不是你写的,编译器都是自动调用的。

构造函数可以有参数,能发生函数重载;但是析构函数不能有参数,不可以发生函数重载。

 

个人喜欢括号法。

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

// 1.构造函数的分类和调用
// 分类
//     按照参数分类:    无参构造    有参构造
//     按照类型分类:    普通构造    拷贝构造
class Person
{
public:
    // 普通构造函数
    Person()
    {
        cout << "Person的无参构造函数" << endl;
    }
    Person(int a)
    {
        age = a;
        cout << "Person的有参构造函数" << endl;
    }
    // 拷贝构造函数:
    Person(const Person &p) // 这种写法是固定的:把一个p这个Person完完全全的复制给当前的这个Person,因为你是复制的,所以你不能修改人家那个Person,所以要用const,还要用引用的形式传参,相当于拷贝了一模一样的一份数据出来
    {
        // 将传入的人身上所有的属性拷贝到“我”身上
        age = p.age;
        cout << "Person的拷贝构造函数调用" << endl;
    }
    ~Person()
    {
        cout << "Person的析构函数调用" << endl;
    }

    int age;
};

// 调用
int main()
{
    // 1.括号法
    //Person p; // 默认构造函数的调用
    //Person p2(10); // 调用有参构造函数
    // 拷贝构造函数
    //Person p3(p2); // 把p2身上所有的属性都拷贝到p3;
    // 注意事项
    // 调用默认构造函数时候不要加小括号
    // 否则编译器会认为是一个函数声明

    //2.显示法
    Person p1;
    Person p2 = Person(10);
    Person p3 = Person(p2);
    
    // 注意事项:
    //Person(10); // 匿名对象 特点:当前行执行结束后系统会立即回收掉匿名对象
    //不要利用拷贝构造函数来初始化匿名对象
    //Person(p3); // 不能这么写!编译器会认为这个等价于Person p3; 创建一个对象,还是无参构造,跟之前的重复了

    //3、隐式转换法
    Person p4 = 10; // 相当于Person p4 = Person(10);
    Person p5 = p4; // 拷贝构造
}

 

第二个:值传递,相当于创建了一个副本,也会调用构造函数,因为实参是类对象,所以调用的是拷贝构造函数

第三个:返回局部对象。局部对象在作用域执行完后就会释放,如果返回一个类对象,返回的不是当前的这个类对象,而是这个对象的值,调用方有一个接收对象,相当于调用拷贝构造函数进行对象创建。

 

值拷贝其实是对所有的值做了拷贝。

所以要不就只使用默认构造函数,不用自己写,编译器自己生成;

要不就所有的都自己写出来。

 

浅拷贝:编译器提供的等号的赋值操作

深拷贝: 在堆区再创建一块空间进行拷贝

什么时候用析构:如果堆区有内存,需要在析构函数里释放干净。

面试的坑!

#include<iostream>
using namespace std;

// 深拷贝与浅拷贝问题

class Person
{
public:
    Person()
    {
        
    }
    Person(int age, int height)
    {
        m_Age = age;
        // 堆区开辟的数据,需要程序员手动开辟,也需要程序员手动释放,在对象销毁前释放
        m_Height = new int(height); // new方法会在堆区分配一个int空间,并且返回一个int*指针,要有指针来接收;
    }
    ~Person()
    {
        // 将我们堆区开辟的数据进行释放操作
        if (m_Height != nullptr)
        {
            delete m_Height;
            m_Height = nullptr; // 为了防止野指针
        }
    }
    int m_Age;
    int* m_Height;
};

void test01()
{
    Person p1(18, 160);
    cout << "P1的年龄为:" << p1.m_Age << endl;

    Person p2(p1); // 拷贝构造函数
    cout << "P2的年龄为:" << p2.m_Age << endl; // 编译器自己会给类写拷贝构造函数,进行浅拷贝也就是简单的值拷贝
    cout<< "P2的身高为:" << *p2.m_Height << endl;

}

int main()
{
    test01();

    system("pause");
    return 0;
}

 

 

 

带来的问题:堆区内存的重复释放!

浅拷贝的问题要利用深拷贝来解决:

 

 

#include<iostream>
using namespace std;

class Person
{
public:
	Person()
	{
		cout << "默认构造函数" << endl;
	}
	Person(int age, int height)
	{
		this->age = age;
		this->height = new int(height);
	}
	Person(const Person& p)
	{
		cout << "有参构造函数" << endl;
		age = p.age;
		//height = p.height 默认的拷贝构造函数
		height = new int(*p.height);
	}
	~Person()
	{
		cout << "析构函数调用" << endl;
		if (height != nullptr)
		{
			delete height;
			height = nullptr;
		}
	}
	int age;
	int* height;
};

int main()
{
	Person p1(18,120);
	Person p2(p1);
}

总结:如果属性有在堆区开辟的,一定要提供一个拷贝构造函数,防止浅拷贝带来的问题。

用途:给类中的属性做初始化

一种方法:构造函数

另一种方法:初始化列表

#include<iostream>
using namespace std;

// 初始化列表

class Person
{
public:
    // 传统的默认构造函数赋值
    Person(int a, int b, int c)
    {
        ma = a;
        mb = b;
        mc = c;
    }
    // 利用初始化列表赋值
    Person() :ma(10), mb(20), mc(30)
    {

    }
    // 更加灵活
    Person(int a, int b, int c) :ma(a), mb(b), mc(c)
    {

    }
    int ma;
    int mb;
    int mc;
};


int main()
{
    system("pause");
    return 0;
}

 

 先有B还是先有A?
  先有A!当其他类的对象作为本类的对象成员时候,先构造其他类的对象。先有胳膊腿,才能构造人。

析构的顺序:

  先释放B,再释放A!

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

class Phone
{
public:
    Phone(string name)
    {
        p_Name = name;
    }
    string p_Name;
};

class Person
{
public:
    Person(string name, string pName) :m_Name(name), m_Phone(pName)
    {

    }
    string m_Name;
    Phone m_Phone; // 隐式构造函数
};

void test01()
{
    Person p("张三", "苹果MAX");
    cout << p.m_Name << "拿着:" << p.m_Phone.p_Name << "手机" << endl;
}

int main()
{
    test01();
    system("pause");
    return 0;
}

 

 静态成员变量!

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

  在编译阶段分配内存;

  类内声明,类外初始化。

静态成员函数:

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

  只能访问静态成员变量。

  静态成员函数也是根据public和private等访问权限访问的

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

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

class Person
{
public:
    static void func()
    {
        m_A = 100; // 这个是共享的,只有一份
        //m_B = 100; // 不能访问,因为这个函数只有一份,不同的对象调用这个方法的时候,根本不知道这个变量是谁的,无法区分到底是哪个对象的m_B
        cout << "static void func()函数的调用" << endl;
    }

    static int m_A; // 静态成员变量,类内声明,类外初始化
    int m_B: // 非静态成员变量
};

int Person::m_A = 0;

void test01()
{
    // 通过对象来访问
    Person p;
    p.func();
    // 通过类名来访问
    Person::func();
}

int main()
{
    test01();
    system("pause");
    return 0;
}

 

虽然封装的概念是把所有的成员和函数放到一起,但并不是真正的存储在一起的

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

// 成员变量和成员函数是分开存储的
class Person
{
    int m_A; // 非静态成员变量    属于类对象
    static int m_B; //静态成员变量    不属于类对象上
    void func() {} // 非静态成员函数    不属于类对象上
    static void func2() {} //静态成员函数    不属于类对象上
};

int Person::m_B = 2; // 类外初始化

void test01()
{
    Person p;
    // 空对象占用的内存空间:1
    // C++编译器会给每个空对象也分配一个字节空间,为了区分空对象占内存的位置,因为空对象很多,每个应该不一样
    // 每个空对象也应该有一个独一无二的内存地址
    cout << "size of p = " << sizeof(p) << endl;
}
void test02()
{
    Person p;
    // 占用4个字节,如果没有成员,就是1,有的话就按有的这个分配就行了
    cout << "size of p = " << sizeof(p) << endl;
}


int main()
{
    test01();
    system("pause");
    return 0;
}

 

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

用途:

  当形参和成员变量同名时,用this指针来区分。  

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

#include<iostream>
using namespace std;

// this指针
class Person
{
public:
    Person(int age)
    {
        // this指针指向 被调用的成员函数所属的对象,也就是p
        this->age = age;
    }
    Person& PersonAddAge(Person &p) // 返回本题要用引用的方式做返回,这样返回的是本体;如果返回值,返回的不是本体了,而是创建了一个新的对象(参见拷贝构造函数用值的形式返回)
    {
        age += p.age;
        return *this; // 相当于返回p2,因为this是指向P2的指针
    }

    int age;

};

//1.解决名称冲突
void test01()
{
    Person p(18);
    cout << "p的年龄为:" << p.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();
    system("pause");
    return 0;
}

 

 

 

 

#include<iostream>
using namespace std;

// 空指针调用成员函数
class Person
{
public:
    void showClassName()
    {
        cout << "this is Person class" << endl;
    }
    void showPersonAge()
    {
        if (this == nullptr) // 这是防止以下错误的发生,增强代码的健壮性!
            return;
        cout << "age = " << m_Age << endl; // this->m_Age 传入的指针为空 this=nullptr,所以会报错
    }
    int m_Age;
};

void test01()
{
    Person* p = nullptr; // 空指针
    p->showClassName(); // 空指针
    p->showPersonAge(); // 不能访问成员函数中带有成员变量的,因为成员变量默认前面加上了this指针,而此时this指针指向的是空的,没有实体对象
}


int main()
{
    test01();
    system("pause");
    return 0;
}

常函数!

常函数内成员属性是只读状态的,如果非要修改,就要加mutable关键字

常对象

常对象只能调用常函数

#include<iostream>
using namespace std;

// const修饰成员函数
class Person
{
public:
    // this指针的本质:指针常量,指针的指向是不可以修改的
    // const Person * const this // 以下的const相当于第一个const,将this变成了一个指向常量的指针常量。
    // 在成员函数后面加const,修饰的其实是this指针!
    void showPerson() const // 常函数
    {
        this->m_B = 100; // 这个就可以修改了
        //this->m_A = 10; // 如果const,指针指向的值是可以修改的
                        //  如果指针指向的值都不修改,就要加const,所以就是加const位置的问题 就加到后面了
        //this = nullptr; // this指针不可以被修改
    }
    int m_A;
    mutable int m_B; // 特殊变量,即使在常函数中,也可以修改,就要加关键字
};
// 常对象
void test01()
{
    const Person p; // 在对象前加const,变成常对象
    p.m_A = 100; // 不可以修改
    p.m_B = 100; // 加了关键字的特殊值,常对象下也可以修改

    // 常对象也只能调用常函数
    p.showPerson(); // 只能调用常函数

}

int main()
{
    system("pause");
    return 0;
}

 

 

4.4.1 全局函数作友元

#include<iostream>
using namespace std;

// 全部函数做友元
class Building
{
    // goodGay全局函数是Building的好朋友,可以访问building中私有成员,只要写到这里就行了,也不用写public什么的
    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); // 打印“好基友正在访问客厅”
}

int main()
{
    test01();
    system("pause");
    return 0;
}

4.4.2类作友元

一个类可以访问另一个类中的私有成员

#include<iostream>
using namespace std;

// 类做友元
class Building
{
    // GoodGay这个类是Building这个类的好朋友,可以访问它的私有成员
    friend class GoodGay;
public:
    Building()
    {
        m_SittingRoom = "客厅";
        m_BedRoom = "卧室";
    }
public:
    string m_SittingRoom; // 客厅
private:
    string m_BedRoom; // 卧室
};

// 好基友的类
class GoodGay
{
public:
    GoodGay()
    {
        // 创建一个建筑物对象
        buliding = new Building; // 在堆区创建一个Building对象
    }
public:
    void visit()// 餐参观函数,访问Building中的属性
    {
        cout << "好基友类正在访问:" << buliding->m_SittingRoom << endl;
        cout << "好基友类正在访问:" << buliding->m_BedRoom << endl;
    }
    Building* buliding;
};

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

int main()
{
    test01();
    system("pause");
    return 0;
}

4.4.3 成员函数做友元

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

// 成员函数做友元
class Building
{
    // visit这个GoodGay下的成员函数是Building这个类的好朋友,可以访问它的私有成员
    friend void GoodGay::visit();
public:
    Building()
    {
        m_SittingRoom = "客厅";
        m_BedRoom = "卧室";
    }
public:
    string m_SittingRoom; // 客厅
private:
    string m_BedRoom; // 卧室
};

// 好基友的类
class GoodGay
{
public:
    GoodGay()
    {
        // 创建一个建筑物对象
        buliding = new Building; // 在堆区创建一个Building对象
    }
public:
    void visit()// 参观函数,访问Building中的私有成员
    {
        cout << "visit正在访问:" << buliding->m_SittingRoom << endl;
        cout << "visit正在访问:" << buliding->m_BedRoom << endl;
    }
    void visit2() // 让它不可以访问Building的私有成员
    {
        cout << "visit2正在访问:" << buliding->m_SittingRoom << endl;
        //cout << "visit2正在访问:" << buliding->m_BedRoom << endl;
    }
    Building* buliding;
};

void test01()
{
    GoodGay gg;
    gg.visit();
    gg.visit2();
}

int main()
{
    test01();
    system("pause");
    return 0;
}

 

 

对于内置的数据类型,编译器知道如何运算

既可以通过成员函数重载也可以通过全局函数重载

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

// 加号运算符重载

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; // 返回的是值,会先创建一个对象

    //}
    // 成员函数重载的本质的调用: Person p3 = p1.operator+(p2);

    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; // 返回的是值,会先创建一个对象
}

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 + p2;
    // 运算符重载,也可以发生函数重载
    cout << "p3.m_A = " << p3.m_A << endl;
    cout << "p3.m_B = " << p3.m_B << endl;
}

int main()
{
    test01();
    system("pause");
    return 0;
}

 

 

1.利用成员函数重载

2.利用全局函数重载

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

// 加号运算符重载

class Person
{
    friend ostream& operator<<(ostream& cout, Person p);
public:
    Person(int a, int b)
    {
        m_A = a;
        m_B = b;
    }
private:
    int m_A;
    int m_B;
};
// 1.<<重载不能使用成员函数来写,因为无法实现cout在左侧
// 2.只能利用全局函数重载<<

ostream& operator<<(ostream &cout, Person p) // 本质 operator<< (cout, p)  简化 cout << p
{
    cout << "m_A = " << p.m_A << endl;
    cout << "m_B = " << p.m_B << endl;
    return cout;// 实现链式表达式的方式
}

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

int main()
{
    test01();
    system("pause");
    return 0;
}

总结:重载左移运算符配合友元可以实现输出自定义数据类型

 

 递增运算符重载++

#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; // 返回自身
    }

    // 重载后置++运算符
    MyInteger operator++(int) // 加int是为了区分函数重载,是一个占位参数,区分前置和后置递增,而且这里必须写int
    {
        // 先 记录当时的结果
        MyInteger temp = *this;
        // 后 递增
        m_Num++;
        // 最后 将记录的结果返回
        return temp; // 返回值 这是一个局部对象必须返回值
    }
    
private:
    int m_Num;
};

// 重载左移运算符
ostream& operator<<(ostream &out, MyInteger myint)
{
    cout << myint.m_Num;
    return out;
}

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


int main()
{
    test01();
    system("pause");
    return 0;
}

总结:前置递增反回引用,后置递增返回值。

编译器还提供赋值运算符函数

值拷贝会带来浅拷贝的问题

#include<iostream>
using namespace std;

// 重载赋值运算符
class Person
{
public:
    Person(int age)
    {
        m_Age = new int(age); // 堆区的数据
    }
    int* m_Age;
    ~Person()
    {
        if (m_Age != nullptr) 
        {
            delete m_Age;
            m_Age = nullptr;
        }
            
    }

    // 重载 赋值运算符
    Person& operator=(Person &p)
    {
        // 编译器浅拷贝
        //m_Age = p.m_Age;

        // 应该先判断是否有属性在堆区,如果有先释放干净,再赋值拷贝
        if (m_Age != nullptr) // 判断原来有没有堆区内存,如果有,先清干净
        {
            delete m_Age;
            m_Age = nullptr;
        }
        // 深拷贝操作
        m_Age = new int(*p.m_Age);

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

};

void test01()
{
    Person p1(18);
    Person p2(20);
    Person p3(30);
    p2 = p1; // 赋值操作,浅拷贝,特别注意堆区数据的赋值,赋值的其实是地址,在析构函数释放的时候,会二次释放,崩掉
    // 解决方案:深拷贝!

    p3 = p2 = p1; // 链式表达式

    cout << "p1的年龄为:" << *p1.m_Age << endl;
    cout << "p2的年龄为:" << *p2.m_Age << endl;
    cout << "p3的年龄为:" << *p3.m_Age << endl;
}


int main()
{
    test01();
    system("pause");
    return 0;
}

 

 

< > ==

#include<iostream>
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("Tom", 18);
    Person p2("Tom", 18);
    if (p1 == p2)
    {
        cout << "p1和p2是相等的" << endl;
    }
}

int main()
{
    test01();
    system("pause");
    return 0;
}

 

 

()居然也可以重载!

明明为仿函数!

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

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

};

void test01()
{
    MyPrint myPrint;
    myPrint("hello world"); // 由于使用起来非常像是函数调用,因此称为仿函数
}
// 仿函数非常灵活,没有固定的写法
// 匿名函数对象:当前行执行完,立刻被释放
int main()
{
    test01();
    system("pause");
    return 0;
}

 

 

 

 

 

好处:减少重复代码!

语法:class 子类 ; 继承方式   父类

子类——派生类

父类——基类

 

 

 

父类中私有的内容,子类中不管是哪种继承方式都不能访问:父亲也是有隐私的。

 

公共继承:

父类中公有的和保护的属性,子类中一样的。

保护继承:

父类中公有的和私有的都变为保护权限。

私有权限:

父类中的公有和保护内容在子类中都是私有。

 

 

父类中所有的非静态成员属性都会被子类继承下去,也就是说非静态成员属性是属于子类的,sizeof要算上;但是要注意,子类继承了父类所有的成员,这点跟前面说的不是一个意思哈

父类中私有的成员属性是被编译器隐藏了,访问不到,但确实是被继承下去了

#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()
{
    // 16 父类中所有的非静态成员属性都会被子类继承下去
    // 父类中私有的成员属性是被编译器隐藏了,访问不到,但确实是被继承下去了
    cout << "size of Son = " << sizeof(Son) << endl; //输出了:16
}


int main()
{
    test01();
    system("pause");
    return 0;
}

 

也就是父类和子类的构造顺序谁先谁后:先构造父类、再构造儿子

父类和子类的析构顺序谁先谁后:先析构儿子、再析构父亲

 

#include<iostream>
using namespace std;

// 继承中同名成员的处理方式
class Base
{
public:
    Base()
    {
        m_A = 100;
    }
    void func()
    {
        cout << "Base下的func成员函数调用" << endl;
    }
    void func(int a)
    {
        cout << "Son下的func(int a)成员函数调用" << 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;
    s.func(); // 直接调用调用的是子类中的同名函数
    s.Base::func(); // 访问父类的同名成员函数,加上作用域
    
    // 如果子类中出现和父类同名的成员函数,子类的同名成员函数会隐藏掉父类中所有的成员函数
    //s.func(100);
    // 如果想访问到,就要加作用域
    s.Base::func(100);

    cout << s.m_A << endl; // 访问的是儿子的
    cout << s.Base::m_A << endl; // 访问父类的,加上父类作用域
}

// 同名成员函数
void test02()
{

}


int main()
{
    test01();
    system("pause");
    return 0;
}

 

 

#include<iostream>
using namespace std;

// 继承中同名静态成员的处理方式
class Base
{
public:
    static int m_A; // 类内声明

    static void func()
    {
        cout << "父类static void func()调用" << endl;
    }

};
int Base::m_A = 100; // 类外初始化

class Son :public Base
{
public:
    static int m_A; // 类内声明

    static void func()
    {
        cout << "子类static void func()调用" << endl;
    }
};
int Son::m_A = 200; // 类外初始化

// 同名静态成员属性
void test01()
{
    // 通过对象访问数据
    Son s;
    cout << s.m_A << endl; // 直接访问访问子类
    cout << s.Base::m_A << endl; // 加作用域访问父类静态成员

    // 通过类名访问数据
    cout << Son::m_A << endl; // 访问父类的
    // 第一个::代表通过类名方式访问  第二个::代表访问父类作用域下的数据
    cout << Son::Base::m_A << endl; // 通过类名的方式访问父类下的数据
}

// 同名静态成员函数
void test02()
{
    // 通过对象访问
    Son s;
    s.func(); // 调用子类的
    s.Base::func(); // 调用父类的

    // 通过类名方式访问
    Son::func();
    Son::Base::func(); // 通过子类访问父类的static函数

}


int main()
{
    test01();
    system("pause");
    return 0;
}

 

 

 

当父类中出现了同名的内容,访问的时候需要加作用域,否则会出现二义性,指定不明确

通常不建议多继承,可能是多人开发,有可能会同名,增加麻烦

 

 

 

vbptr:虚基类指针    指向一个叫vbtable,虚基类表

所以虚继承解决多份数据的问题是通过虚基类指针解决的,指针指向的是同一个数据

例子当中,羊驼从羊和驼父类中各自继承了一个虚基类指针,这两个指针通过偏移量指向唯一的一份数据,所以解决了这个问题

#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.m_Age = 18; // 不明确
    // 当出现菱形继承的时候,两个父类拥有相同的数据,需要加以作用域区分
    st.Sheep::m_Age = 18; // 加作用域去区分
    st.Tuo::m_Age = 28; 

    // 这份数据我们知道只要有一份就可以了,菱形数据导致数据有两份,浪费资源

}


int main()
{

    system("pause");
    return 0;
}

 

 

C++经常说的多态是动态的多态

静态多态和动态多态的区别

#include<iostream>
using namespace std;

class Animal
{
public:
    // 虚函数    
    virtual void speak()
    {
        cout << "动物在说话" << endl;
    }
};

class Cat :public Animal
{
public:
    void speak() // 子类的virtual可写可不写
    {
        cout << "小猫在说话" << endl;
    }
};
// 执行说话的函数
// 地址早绑定 在编译阶段就确定了函数的地址
// 如果想执行让猫说话,这个函数的地址就不能提前绑定,需要在运行阶段进行绑定,也就是地址晚绑定

// 动态多态的满足条件
//    1.得有继承关系
//  2.子类要重写父类的虚函数    重写:所有内容都相同 返回值类型 函数名称 参数


// 动态多态的使用
// 父类的指针或者引用  指向子类对象
void doSpeak(Animal &animal) // Animal & animal = cat;
{
    animal.speak();
}

void test01()
{
    Cat cat;
    // 本意是传谁让谁说话
    doSpeak(cat);
}

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

 

原理:

Animal类中有虚函数,有虚函数指针,指向虚函数表,虚函数表中记录的是Animal::speak函数的入口地址,当Cat类继承Animal,但是并没有重写speak方法的时候,Cat类相当于继承了Animal类的所有,包括虚函数指针和虚函数表,这个时候Cat中的虚函数指针指向的虚函数表中记录的还是Animal::speak方法;但是如果Cat重写了speak虚函数,那么Cat的虚函数指针指向的虚函数表中记录的就是Cat::speak的入口地址了。当Animal &animal = cat时候,animal是引用cat的引用,调用speak方法时候,自然就调用的是cat的speak方法。

 

#include<iostream>
using namespace std;

// sizeof(Animal) = 1;这是正常情况下
// 但是如果有虚函数  sizeof(Animal) = 4;
// 指针vfptr:
// v:virtual
// f:function
// ptr:pointer
// 虚函数(表)指针
// 指针指向虚函数表vftable,里头存放的是虚函数表,表内记录虚函数的地址
class Animal
{
public:
    // 虚函数    
    virtual void speak()
    {
        cout << "动物在说话" << endl;
    }
};

class Cat :public Animal
{
public:
    void speak() // 子类的virtual可写可不写
    {
        cout << "小猫在说话" << endl;
    }
};
// 执行说话的函数
// 地址早绑定 在编译阶段就确定了函数的地址
// 如果想执行让猫说话,这个函数的地址就不能提前绑定,需要在运行阶段进行绑定,也就是地址晚绑定

// 动态多态的满足条件
//    1.得有继承关系
//  2.子类要重写父类的虚函数    重写:所有内容都相同 返回值类型 函数名称 参数


// 动态多态的使用
// 父类的指针或者引用  指向子类对象
void doSpeak(Animal &animal) // Animal & animal = cat;
{
    animal.speak();
}

void test01()
{
    Cat cat;
    // 本意是传谁让谁说话
    doSpeak(cat);
}

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

 

#include<iostream>
#include<string>
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;
    int m_Num2;
};

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:
    virtual int getResult()
    {
        return m_Num1 + m_Num1;
    }
};

// 减法计算器类
class SubCalculator :public AbstractCalculator
{
public:
    virtual int getResult()
    {
        return m_Num1- m_Num1;
    }
};

// 乘法计算器类
class MulCalculator :public AbstractCalculator
{
public:
    virtual int getResult()
    {
        return m_Num1 * m_Num1;
    }
};

void test02()
{
    // 多态使用条件:父类的指针或者引用来指向子类对象

    // 加法运算
    // 用父类指针去指向新创建的加法计算器类
    AbstractCalculator* abc = new AddCalculator;
    abc->m_Num1 = 100;
    abc->m_Num2 = 100;
    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()
{
    test02();
    return 0;
}

C++中非常提倡利用多态设计程序架构,因为多态优点很多

虚函数里面的代码实现是毫无意义的,就可以变为纯虚函数

只要写了一个纯虚函数,这个类就变成了抽象类

抽象类有两个特点

然后就可以利用正常的多态的技术了

#include<iostream>
#include<string>
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 son; // 子类重写了父类的纯虚函数

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


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

 

#include<iostream>
using namespace std;

// 多态案例二:制作饮品

class AbstructDrinking
{
public:
    // 煮水
    virtual void Boil() = 0;
    // 冲泡
    virtual void Brew() = 0;
    //倒入杯中
    virtual void PourInCup() = 0;
    // 加入辅料
    virtual void PutSomething() = 0;
    // 制作饮品
    void makeDrinking()
    {
        Boil();
        Brew();
        PourInCup();
        PutSomething();
    }
};

// 制作咖啡
class Coffe :public AbstructDrinking
{
public:
    // 煮水
    virtual void Boil()
    {
        cout << "煮冰水" << endl;
    }
    // 冲泡
    virtual void Brew()
    {
        cout << "冲泡咖啡豆" << endl;
    }
    //倒入杯中
    virtual void PourInCup()
    {
        cout << "倒入杯中" << endl;
    }
    // 加入辅料
    virtual void PutSomething()
    {
        cout << "加入牛奶" << endl;
    }
};

// 制作茶叶
class Tea :public AbstructDrinking
{
public:
    // 煮水
    virtual void Boil()
    {
        cout << "煮茶水" << endl;
    }
    // 冲泡
    virtual void Brew()
    {
        cout << "冲泡茶叶" << endl;
    }
    //倒入杯中
    virtual void PourInCup()
    {
        cout << "倒入杯中" << endl;
    }
    // 加入辅料
    virtual void PutSomething()
    {
        cout << "加入糖" << endl;
    }
};

// 制作咖啡
void doWork(AbstructDrinking * abs) // AbstructDrinking * abs = new Coffe
{
    abs->makeDrinking(); // 一个接口不同行为,只是传入的对象不一样
    delete abs; // 防止内存泄露
}

void test01()
{
    // 制作咖啡
    doWork(new Coffe);

    // 制作茶叶
    doWork(new Tea);
}


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

 

 

 

什么时候析构函数要用虚函数:当父类指针指向一个子类对象的时候,如果这个子类对象当中有从堆区创建的数据,你在删除父类指针的时候,子类的析构函数不会执行,会导致堆区的内存泄露,所以要把父类的析构函数写成虚析构,这样就能走到子类的析构函数了。

不管是虚析构还是纯虚析构,都是为了解决子类中的析构函数调不到的问题

 

 

#include<iostream>
#include<string>
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 ~Cat()
    {
        if (m_Name != nullptr)
        {
            cout << "Cat析构函数调用" << endl;
            delete m_Name; // 先释放
            m_Name = nullptr; // 再指向空指针,防止野指针出现
        }
    }

    virtual void speak()
    {
        cout <<*m_Name<< "Cat在说话" << endl;
    }

    string *m_Name; // 让小猫的名字创建在堆区,用这个指针去维护它
};

void test01()
{
    Animal* animal = new Cat("Tom"); // Cat是继承来的,先构造父类,再构造子类
    animal->speak();
    // 父类的指针在析构的时候不会调用子类的析构函数,导致如果子类有堆区数据造成内存泄漏
    delete animal;
}

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

 

 

 

 

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


// 抽象CPU类
class CPU
{
public:
    // 抽象计算函数
    virtual void calculate() = 0;
};

// 抽象显卡类
class VideoCart
{
public:
    // 抽象显示函数
    virtual void display() = 0;
};

/// 抽象内存条类
class Memory
{
public:
    // 抽象存储函数
    virtual void storage() = 0;
};

// 计算机抽象类
class Computer
{
public:
    Computer(CPU* cpu, VideoCart* vc, Memory* mem)
    {
        m_cpu = cpu;
        m_vc = vc;
        m_mem = mem;
    }
    // 提供工作的函数
    void work()
    {
        m_cpu->calculate();
        m_vc->display();
        m_mem->storage();
    }

    // 提供一个析构函数,释放三个电脑零件
    ~Computer()
    {
        if (m_cpu != nullptr)
        {
            delete m_cpu;
            m_cpu = nullptr;
        }
        if (m_vc != nullptr)
        {
            delete m_vc;
            m_vc = nullptr;
        }
        if (m_mem != nullptr)
        {
            delete m_mem;
            m_mem = nullptr;
        }
    }

private:
    CPU* m_cpu;
    VideoCart* m_vc;
    Memory* m_mem;
};

// 具体的厂商
class IntelCPU :public CPU
{
public:
    void calculate()
    {
        cout << "Intel的CPU开始计算了" << endl;
    }
};

class IntelVideoCart :public VideoCart
{
public:
    void display()
    {
        cout << "Intel的显卡开始显示了" << endl;
    }
};

class IntelMemory :public Memory
{
public:
    void storage()
    {
        cout << "Intel的内存条开始存储了" << endl;
    }
};

void test01()
{
    // 第一台电脑零件
    CPU* intelCPU = new IntelCPU;
    VideoCart* intelCard = new IntelVideoCart;
    Memory* intelMem = new IntelMemory;

    // 创建第一台电脑
    Computer* computer1 = new Computer(intelCPU, intelCard, intelMem);
    computer1->work();
    delete computer1; //delete的时候会调用Computer的析构函数,零件也会被释放干净

}

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

 

posted @ 2021-01-15 10:52  不妨不妨,来日方长  阅读(143)  评论(0编辑  收藏  举报