C++类和对象学习笔记

大部分思考过程是在代码中标注出来

一、封装

       1.封装的意义

       (1)在设计类时,属性和行为写在一起,来表现事物。

示例:设计一个圆类,求圆的周长

#include<iostream>
using namespace std;
const double PI=3.14;
class Circle
{
	public:    //公共权限 
  		int m_r;    //属性:半径 
 		double calculateZC()   //行为:获取圆的周长 
		{
			return 2*PI*m_r;
		}
};
int main()
{
	Circle c1;  //通过圆类 创建具体的圆(实例化对象) 
	c1.m_r=10;  //赋值 
	cout<<"圆周长为:  "<<c1.calculateZC()<<endl;
	system("pause");
	return 0;
}

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

访问权限:①public  公共权限  成员类内类外均可访问

                  ②protected  保护权限  成员类内可以访问,类外不可以访问

                  ③private  私有权限  成员类内可以访问,类外不可以访问

       2.struct与class的区别: 默认的访问权限不同

             struct默认公共权限

             class默认私有权限

问题:类与结构体很相似,二者除了默认访问权限不同,还有什么其他不同吗?

       ①赋值方式区别

结构体struct可以直接赋值,class不行

struct student S={"张三",18,100};

        ②类型不同

struct是值类型,class是引用类型

         ③概念不同

二者语法基本相同,从声明到使用,都很相似,但struct的约束比class多,理论上,struct能做到的class也能做到,class能做到的struct不一定能做到。

       3.成员属性设置为私有

          好处:①可以自己控制读写权限

                     ②可以检测数据有效性

#include<iostream>
using namespace std;
class Person
{
	public:
		void setName(string name)  //设置姓名 
		{
			m_Name=name;
		}
		string getName()  //获取姓名 
		{
			return m_Name;
		} 
		int getAge()  //获取年龄 
		{
			return m_Age;
		}
		void setIdol(string idol)  //设置偶像 
		{
			m_Idol=idol;
		}
	
	private:
		string m_Name;  //可读可写(既设置又获取)
		int m_Age;  //只读(只获取)
		string m_Idol;  //只写(只设置)
};
int main()
{
	Person p;  //创建具体的人(实例化对象) 
	p.setName("张三");  //姓名设置 
	cout<<"姓名:  "<<p.getName()<<endl;  //获取姓名
	cout<<"年龄:  "<<p.getAge()<<endl;   //只读
	p.setIdol("小明");  //只写 
	system("pause");
	return 0;
}

练习:

(1)设置立方体类:求出面积体积,分别用全局函数和成员函数判断俩立方体是否相同

#include<iostream>
using namespace std;
class Cube 
{
    public:
	void setL(int l)  //设置长 
	{
		m_L = l;
	}
	int getL()   //获取长 
	{
		return m_L;
	}
	void setW(int w)   //设置宽 
	{
		m_W = w;
	}
	int getW()   //获取宽 
	{
		return m_W;
	}
	void setH(int h)   //设置高 
	{
		m_H = h;
	}
	int getH()   //获取高 
	{
		return m_H;
	}
	int calculateS()   //获取面积 
	{
		return 2*m_L*m_W+2*m_H*m_W+2*m_L*m_H;
	}
	int calculateV()   //获取体积 
	{
		return m_L*m_W* m_H;
	}
	
	//利用成员函数判断两个立方体是否相等
	bool isSameByClass(Cube &c) 
	{
		if (m_L==c.getL() && m_W==c.getW() && m_H==c.getH()) 
		{
			return true;
		}
			return false;
	}
	
    private:
	  int m_L; //长
 	  int m_W; //宽
	  int m_H; //高
};

    //利用全局函数判断两个立方体是否相等
    bool isSame(Cube& c1, Cube& c2) 
    {
	    if (c1.getL() == c2.getL() && c1.getW() == c2.getW() && c1.getH() == c2.getH()) 
	    {
		    return true;
	    }
		    return false;
    }
    
int main() 
{
	Cube c1;  //创建第一个立方体对象 
	c1.setL(10);
	c1.setW(10);
	c1.setH(10);
	cout << "c1的面积为:" << c1.calculateS() << endl;
	cout << "c1的体积为:" << c1.calculateV() << endl;
	Cube c2;  //创建第二个立方体对象 
	c2.setL(10);
	c2.setW(10);
	c2.setH(10);
	
	//利用全局函数判断
	bool ret = isSame(c1, c2);
	if (ret) 
	{
		cout << "全局函数判断:两个立方体相等" << endl;
	}
    else 
    {
		cout << "全局函数判断:两个立方体不相等" << endl;
	}
	
	//利用成员函数判断
	ret = c1.isSameByClass(c2);
	if (ret) 
	{
		cout << "成员函数判断:两个立方体相等" << endl;
	}
	else 
	{
		cout << "成员函数判断:两个立方体不相等" << endl;
	}
	
	system("pause");
	return 0;
} 

问题:成员函数和全局函数的区别?

①成员函数面向对象,全局函数面向过程

②成员函数比全局函数少一个参数

(2)点和圆的关系:设置一个圆类和点类,计算点与圆的关系

#include<iostream>
using namespace std;
class Point  //点类 
{
    public:
    	void setX(int x)  //设置x 
    	{
    		m_X=x;
		} 
		int getX()  //获取x 
		{
			return m_X;
		}
		void setY(int y)  //设置y 
    	{
    		m_Y=y;
		} 
		int getY()  //获取y 
		{
			return m_Y;
		}
    private:
        int m_X;
        int m_Y;
};

class Circle  //圆类 
{
	public:
		void setR(int r)  //设置半径 
		{
			m_R=r;
		}		
		int getR()  //获取半径 
		{
			return m_R;
		}
		void setCenter(Point center)  //设置圆心 
		{
			m_Center=center;
		}		
		Point getCenter()  //获取圆心 
		{
			return m_Center;
		}
	private:
		int m_R;  //半径 
		Point m_Center;  //圆心 
                         //在类中可以让另一个类作为本类中的成员
};

//判断点和圆位置关系的函数
void isInCircle(Circle &c,Point &p)  
{
	//计算两点间距离的平方
	int distance=      
	(c.getCenter().getX()-p.getX()) * (c.getCenter().getX()-p.getX())+
	(c.getCenter().getY()-p.getY()) * (c.getCenter().getY()-p.getY());
	
	//计算半径的平方
	int rDistance = c.getR() * c.getR();
	
	//判断关系
	if(distance == rDistance) 
	{
		cout<<"点在圆上"<<endl;
	}
	else if(distance > rDistance)
	{
		cout<<"点在圆外"<<endl;
	}
	else
	{
		cout<<"点在圆内"<<endl;
	}
} 

int main() 
{
    Circle c;  //创建圆 
    c.setR(10);
    Point center;  //圆心 
    center.setX(10);
    center.setY(0);
    c.setCenter(center);
    
    Point p;  //创建点 
    p.setX(10);
    p.setY(10);
    
    isInCircle(c,p);  //判断关系 
    
	system("pause");
	return 0;
} 

 把点类和圆类拆分到另一个文件中

①头文件point.h

#pragma once  //防止头文件重复包含
#include <iostream>
using namespace std;

class Point // 点类
{
public:
    void setX(int x); // 设置x

    int getX(); // 获取x

    void setY(int y); // 设置y

    int getY(); // 获取y

private:
    int m_X;
    int m_Y;
};

//只保留成员函数的声明,把实现部分删掉

 ②源文件point.cpp

#include "point.h"  //包含刚刚写的头文件

void Point::setX(int x) // 设置x
{
    m_X = x;
}
int Point::getX() // 获取x
{
    return m_X;
}
void Point::setY(int y) // 设置y
{
    m_Y = y;
}
int Point::getY() // 获取y
{
    return m_Y;
}

//只保留函数的所有实现
//Point::的作用是,告诉它是Point作用域下的成员函数

 ③头文件circle.h

#pragma once
#include <iostream>
using namespace std;
#include "point.h"

class Circle // 圆类
{
public:
    void setR(int r); // 设置半径

    int getR(); // 获取半径

    void setCenter(Point center); // 设置圆心

    Point getCenter(); // 获取圆心

private:
    int m_R;        // 半径
    Point m_Center; // 圆心
                    // 在类中可以让另一个类作为本类中的成员
};

④源文件circle.cpp

#include "circle.h"

void Circle::setR(int r) // 设置半径
{
    m_R = r;
}
int Circle::getR() // 获取半径
{
    return m_R;
}
void Circle::setCenter(Point center) // 设置圆心
{
    m_Center = center;
}
Point Circle::getCenter() // 获取圆心
{
    return m_Center;
}

二、对象的初始化和清理

       1.构造函数和析构函数

          构造函数:创建对象时为对象的成员属性赋值(初始化)

          析构函数:对象销毁前系统自动调用,执行一些清理工作(清理)

语法不同:

(1)构造函数:①无返回值也无void

         类名(){}  ②函数名与类名相同

                            ③可以有参数,可以发生重载

                            ④程序在调用对象时自动调用构造,且只调用一次

(2)析构函数:①无返回值也无void

       ~类名(){} ②函数名与类名相同,在名称前加上~

                            ③不可以有参数,不可以发生重载

                            ④程序在调用对象时自动调用构造,且只调用一次

class Person
{
public:
	//构造函数
	Person()
	{
		cout << "Person的构造函数调用" << endl;
	}
	//析构函数
	~Person()
	{
		cout << "Person的析构函数调用" << endl;
	}

};

void test01()  //测试案例
{
	Person p;   //创建对象(局部变量)。在栈上的数据,test01执行完毕后,释放这个对象
}

//test01执行完毕后自动调用对象的析构函数

int main() {
	
	test01();    //调用

	system("pause");

	return 0;
}

构造和析构都是必须有的实现,如果我们自己不提供,编译器就会提供一个空实现的构造和析构 

       2.构造函数的分类及调用

分类方式:

       (1)按参数分:①有参构造 ②无参构造(默认构造)

       (2)按类型分:①普通构造 ②拷贝构造

调用方式:

          ①括号法     ②显示法     ③隐式转换法

分类:

class Person 
{
  public:
	//无参(默认)构造函数
	Person() 
    {
		cout << "Person的无参构造函数调用" << endl;
	}
	//有参构造函数
	Person(int a) 
    {
		age = a;
		cout << "Person的有参构造函数调用" << endl;
	}
	//拷贝构造函数(用已有的成员初始化新的成员)
	Person(const Person& p)  //将传入的人身上的所有属性拷贝过来,但不改变其本身
    {
		age = p.age;
		cout << "Person的拷贝构造函数调用" << endl;
	}
	//析构函数
	~Person() 
    {
		cout << "Person的析构函数调用" << endl;
	}
  public:
	int age;
};

调用:

  ①括号法(最常用)

void test01()
{
	Person p1;  //默认构造函数调用
//	注意不要加 (),否则编译器会认为是一个函数声明 
	Person p2(10);  //有参构造函数 
	Person p3(p2);  //拷贝构造函数 
	cout << "p2的年龄为:  " << p2.age << endl; 
	cout << "p3的年龄为:  " << p3.age << endl;
}

运行结果:

  ②显示法 

void test01()
{
	Person p1;  //默认构造函数
	Person p2 = Person(10);  //有参构造 
	Person p3 = Person(p2);  //拷贝构造  
}

注意事项1:匿名对象

void test01()
{
    Person(10);  
    //匿名对象   特点:当前行执行结束后,系统会立即回收掉匿名对象
	cout << "aaaa" << endl; 
}

运行结果:

匿名对象的特点:aaaa在对象被销毁掉后才打印 

注意事项2:不要利用拷贝构造函数,初始化匿名对象

void test01()
{
	Person p1;  //默认构造函数
	Person p2 = Person(10);  //有参构造 
	Person p3 = Person(p2);  //拷贝构造 

    Person(p3);  //编译器会认为Person(p3)==Person p3; 对象声明
}

③隐式转换法

void test01()
{
    Person p4 = 10;  //相当于写了 Person p4 = Person(10);  有参构造
	Person p5 = p4;  //相当于写了 Person p5 = Person(p4);  拷贝构造
}

        3.拷贝构造函数的调用时机

通常有三种情况:

①使用一个已经创建完毕的对象来初始化一个新对象

②值传递的方式给函数参数传值

③ 以值方式返回局部对象

class Person 
{
public:
	Person() 
	{
		cout << "无参构造函数!" << endl;
		m_Age = 0;
	}
	Person(int age) 
	{
		cout << "有参构造函数!" << endl;
		m_Age = age;
	}
	Person(const Person& p) 
	{
		cout << "拷贝构造函数!" << endl;
		m_Age = p.m_Age;
	}
	//析构函数在释放内存之前调用
	~Person() 
	{
		cout << "析构函数!" << endl;
	}
public:
	int m_Age;
};
//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01() 
{
	Person man(100); //p对象已经创建完毕
	Person newman(man); //调用拷贝构造函数
	Person newman2 = man; //拷贝构造

	//Person newman3;
	//newman3 = man; //不是调用拷贝构造函数,而是赋值操作
}
//2. 值传递的方式给函数参数传值
//相当于Person p1 = p;
void doWork(Person p1) {}
void test02() 
{
	Person p; //无参构造函数
	doWork(p);
}
//3. 以值方式返回局部对象
Person doWork2()
{
	Person p1;
	cout << (int *)&p1 << endl;
	return p1;
}

void test03()
{
	Person p = doWork2();
	cout << (int *)&p << endl;
}

       4.构造函数的调用规则

默认情况下,C++编译器至少给一个类添加三个函数

①默认构造函数(无参,函数体为空)

②默认析构函数(无参,函数体为空)

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

调用规则:

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

②如果用户定义拷贝构造函数,C++不再提供其他构造函数

void test01()
{
	Person p1(18);
	//如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作
	Person p2(p1);

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

void test02()
{
	//如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造
	Person p1; //此时如果用户自己没有提供默认构造,会出错
	Person p2(10); //用户提供的有参
	Person p3(p2); //此时如果用户没有提供拷贝构造,编译器会提供

	//如果用户提供拷贝构造,编译器不会提供其他构造函数
	Person p4; //此时如果用户自己没有提供默认构造,会出错
	Person p5(10); //此时如果用户自己没有提供有参,会出错
	Person p6(p5); //用户自己提供拷贝构造
}

       5.深拷贝和浅拷贝

浅拷贝:简单的赋值拷贝操作(如果利用编译器提供的拷贝构造函数,就是浅拷贝)

深拷贝:在堆区中重新申请空间,进行拷贝操作

#include<iostream>
using namespace std; 

//浅拷贝问题:堆区的内存重复释放 
//要利用深拷贝来解决 

class Person
{
	public:
		Person()
		{
			cout << "Person的默认构造函数调用" << endl; 
		} 
		Person(int age,int height)
		{
			m_Age = age;
			m_Height = new int(height); //把数据创建到堆区 ,new int返回的是int* 
			//堆区的数据手动开辟,也需要手动释放 (在对象销毁前释放) 
			cout << "Person的有参构造函数调用" << endl; 
		} 
		
		//自己实现拷贝构造函数,解决浅拷贝带来的问题 
		Person(const Person &p)
		{
			cout << "Person拷贝构造函数调用" << 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 << "Person的析构函数调用" << endl; 
		} 
		int m_Age;
		int *m_Height;  //要把身高这个数据拆递到堆区 
};

void test01()
{
	Person p1(18,160);
	cout << "p1的年龄为:" << p1.m_Age << "身高为:" << *p1.m_Height << endl;//*p1解引用 
	Person p2(p1);
	cout << "p2的年龄为:" << p2.m_Age << "身高为:" << *p2.m_Height << endl;
}

析构代码的用途:将堆区开辟数据释放

       6.初始化列表

作用:给类中的属性进行初始化操作

语法:构造函数():属性1(值1);属性2(值2)...{ } 

class Person 
{
  public:
	传统方式初始化
	//Person(int a, int b, int c) 
	//{
	//	m_A = a;
	//	m_B = b;
	//	m_C = c;
	//}

	//初始化列表方式初始化
	Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}
	void PrintPerson() 
	{
		cout << "mA:" << m_A << endl;
		cout << "mB:" << m_B << endl;
		cout << "mC:" << m_C << endl;
	}
  private:
	int m_A;
	int m_B;
	int m_C;
};

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

	system("pause");
	return 0;
}

       7.类对象作为类成员

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

class A {}
class B
{
    A a;
}

B中有对象A作为成员,A是对象成员

当创建B对象时,A和B的构造和析构的顺序是谁先谁后?

构造顺序:先调用对象成员的构造,再调用本类构造

析构顺序相反

class Phone //手机类 
{
public:
	Phone(string name)
	{
		m_PhoneName = name;
		cout << "Phone构造" << endl;
	}
	~Phone()
	{
		cout << "Phone析构" << endl;
	}
	string m_PhoneName;
};

class Person  //人类 
{
public:
	//初始化列表可以告诉编译器调用哪一个构造函数
	//Phone m_Phone = pName 隐式转换法 
	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 test01()
{
	//构造的顺序是 :先调用对象成员的构造,再调用本类构造
	//析构顺序与构造相反
	Person p("张三" , "苹果X");
	cout << p.m_Name << "拿着:" << p.m_Phone.m_PhoneName << endl;
}

       8.静态成员

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

分类:(1)静态成员变量

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

                    ②在编译阶段分配内存

                    ③类内声明,类外初始化           

class Person
{
	//类内声明 
public:
	static int m_A; //静态成员变量

private:
	static int m_B; //静态成员变量也是有访问权限的
};

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

void test01()
{
	//静态成员变量两种访问方式

	//1、通过对象
	Person p1;
	p1.m_A = 100;
	cout << "p1.m_A = " << p1.m_A << endl;

	Person p2;
	p2.m_A = 200;
	cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据
	cout << "p2.m_A = " << p2.m_A << endl;

	//2、通过类名
	cout << "m_A = " << Person::m_A << endl;

	//cout << "m_B = " << Person::m_B << endl; //私有权限访问不到
}

            (2)静态成员函数

                     ①所有对象共享一个函数

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

class Person
{
public:	
	static void func() //静态成员函数 
	{
		m_A = 100;  //静态成员函数 可以访问 静态成员变量 
		cout << "static void func调用" << endl;
		//m_B = 100; //错误,静态成员函数 不可以访问 非静态成员变量,无法区分到底是哪个对象的m_B属性 
	}

	static int m_A;  //静态成员变量的类内声明 
	int m_B;  //非静态成员变量 
	
private:
	//静态成员函数也是有访问权限的
	static void func2()
	{
		cout << "static void func2调用" << endl;
	}
};
int Person::m_A = 10;  //静态成员变量的类外初始化 

void test01()
{
	//静态成员变量两种访问方式

	//1、通过对象
	Person p1;
	p1.func();

	//2、通过类名
	Person::func();

	//Person::func2(); //类外访问不到私有静态成员函数 
}

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

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

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

class Person 
{

};

void test01()
{
	Person p;
	//空对象占用内存空间为:1个字节
	//C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
    //每个空对象也应该有一个独一无二的内存地址 
	cout << "size of p = " << sizeof(p) <<endl;
}

空对象占内存空间为:1个字节

class Person 
{
	int m_A;  //非静态成员变量 属于类的对象上的
	
    static int m_B;  //静态成员变量 不属于类的对象上 
    
    void func(){}  //非静态成员函数 不属于类的对象上,所有函数共享一个函数实例
    
    static void func2() {}  //静态成员函数 不属于类的对象上
};
 int Person::m_B = 0;

void test01()
{
	Person p;
	cout << "size of p = " << sizeof(p) <<endl;
}

       2.this指针概念 

 每一个非静态成员函数只会诞生一份函数实例,也就是多个同类型的对象会共用一块代码

那这一块代码是如何区分哪个对象调用自己的呢?

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

概念:this指针是隐含每一个非静态成员函数内的一种指针,无需定义,直接使用就行

用途:①当形参和成员变量同名时,可用this指针来区分

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

①解决名称冲突

 this指向的age和属性的age是一样的(看颜色)

② 返回对象本身用*this

class Person
{
public:
	Person(int age) 
	{
		this->age = age;  //this指针指向 被调用的成员函数 所属的对象 
	}
	void PersonAddAge(Person &p) //&p用引用方式传入 
	{
		this->age += p.age;  //把p人的年龄加到自身 
	}
	int age;
};

void test01()
{
	Person p1(10); 
	cout << "p1.age = " << p1.age << endl;

	Person p2(10);
	p2.PersonAddAge(p1); 
	cout << "p2.age = " << p2.age << endl;
}

class Person
{
public:
	Person(int age) 
	{
		this->age = age; 
	}
	Person& PersonAddAge(Person &p) //Person&用引用方式返回 
                     //如果用Person则为值返回,会创建新的对象,每次返回都是一个新的对象
	{
		this->age += p.age;  
		return *this;  //this指向p2的指针,而*this指向的就是p2这个对象本体 
	}
	int age;
};

void test01()
{
	Person p1(10); 
	cout << "p1.age = " << p1.age << endl;

	Person p2(10);  //链式编程思想 
	p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);  //加三次 
	cout << "p2.age = " << p2.age << endl;
}

        3.空指针访问成员函数

空指针也可以调用成员函数,但要注意有没有用到this指针

如果用到this指针,需判断代码的健壮性

//空指针访问成员函数
class Person 
{
public:
	void ShowClassName() 
	{
		cout << "我是Person类!" << endl;
	}
	void ShowPersonAge() 
	{
		if (this == NULL) 
		{
			return;
		}
		cout << "age= " << m_Age << endl;  //m_Age是一个属性,属性前默认有个this->
		                                   //即m_Age==this->m_Age 
	}
public:
	int m_Age;
};

void test01()
{
	Person * p = NULL;  //空指针 
	p->ShowClassName(); //空指针,可以调用成员函数
	p->ShowPersonAge();  //但是如果成员函数中用到了this指针,就不可以了
}

       4.const修饰成员函数

常函数:①成员函数后加const后我们称这个函数为常函数

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

               ③成员属性声明时加关键字mutable后,常函数中依然可以修改

常对象:①声明对象前加const称该对象为常对象

               ②常对象只能调用常函数

常函数:

class Person 
{
public:
	//this指针的本质 是指针常量,指针的指向不可修改,指向的值可以修改 
	//即为Person * const this;
	
	//如果想让指针指向的值也不可以修改,需要声明常函数
	//即为const Person * const this; 
	
	void ShowPerson() const  //常函数 
	//在成员函数后面加const,修饰的是this指针,让指针指向的值也不可以修改了 
	{
		//this->m_A = 100;  不允许修改指针指向的值 
		//this = NULL;  this指针不可以修改指针的指向的 
		this->m_B = 100;
	}

	int m_A;
	mutable int m_B;  //特殊变量,即使在常函数中,也可以修改这个值 
};

void test01() 
{
	Person p;
	p.ShowPerson();
}

 常对象:

void test02()
{
	const Person p;  //常对象 
	//p.m_A = 100;  报错,不允许修改指针指向的值 
	p.m_B = 100;  //可修改
	
	p.ShowPerson();  //常对象只能调用常函数 
	//p.func();  不可以调用普通成员函数,因为普通成员函数可以修改属性 
}

 四、友元

在程序里,有些私有属性,也想让类外特殊的一些函数或者类进行访问,就需要用到友元

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

关键字:friend

三种实现:①全局函数做友元;②类做友元;③成员函数做友元

        1.全局函数做友元

friend+全局函数

class Building
{
	//告诉编译器 goodGay全局函数 是 Building类的好朋友,可以访问类中的私有内容
	friend void goodGay(Building * building);
public:
	Building()
	{
		this->m_SittingRoom = "客厅";
		this->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 b;
	goodGay(&b);
}

      2.类做友元 

 补充:公有成员函数的类外定义

class Person
{
public:
    void phone(int p);  //在类中声明函数原型
};

void Person::phone(int p)  //在类外定义函数
{
    // 函数体
}

类做友元:friend+类名

class Building;  //类声明 
class goodGay
{
public:
	goodGay();
	void visit();  //visit函数 访问Building中的属性 

private:
	Building *building;  //指向new出来的对象 
    //定义一个指针变量building,指针类型是Building这个类
};

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

Building::Building()  //类外写成员函数,作用域::表示是Building下的一个构造函数 
{
	m_SittingRoom = "客厅";
	m_BedRoom = "卧室";
}

goodGay::goodGay()  //类外写成员函数,作用域::表示是gooGay下的一个构造函数 
{
	building = new Building;  //创建建筑物对象 new在堆区创建了一个对象
	                          //new什么数据类型就返回什么数据类型的指针
							  //让building内部指针维护堆区的对象 
}

void goodGay::visit()  //类外写成员函数,作用域::表示是goodGay下的一个visit函数 
{
	cout << "好基友正在访问" << building->m_SittingRoom << endl;
	cout << "好基友正在访问" << building->m_BedRoom << endl;
}

void test01()
{
	goodGay gg;  //实例化对象 
	gg.visit();  //对象调用visit函数 
}

        3.成员函数做友元

friend+类名::+函数名()

易错点:

①声明“友元成员函数”的类(Building)必须在“该成员函数”的所属类(goodGay)之前声明,在之后定义

②该成员函数(void visit( );)必须在类内(goodGay内)声明,类外定义void goodGay::visit(){...}

③goodGay类中构造函数的实现必须在Building类的定义之后,因为goodGay中的构造函数需要调用Building的构造函数

class Building;
class goodGay
{
public:
	goodGay();
	void visit(); //让visit函数作为Building的好朋友,可以访问Building中私有成员 
	void visit2(); //让visit2函数不可以访问Building中私有成员 
private:
	Building *building;
};

class Building
{
	//告诉编译器  goodGay类中的visit成员函数 是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 << "好基友正在访问" << building->m_SittingRoom << endl;
	cout << "好基友正在访问" << building->m_BedRoom << endl;
}

void goodGay::visit2()
{
	cout << "好基友正在访问" << building->m_SittingRoom << endl;
	//cout << "好基友正在访问" << building->m_BedRoom << endl;
}

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

五、运算符重载

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

运算符重载,也可以发生函数重载

       1.加号运算符重载+

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

class Person 
{
public:
	int m_A;
	int m_B;
};
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;  //错误,没有与这些操作数匹配的+运算符 
}

①通过成员函数实现重载

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;
	}
	int m_A;
	int m_B;
};

②成员函数重载本质调用

Person p3 = p1.operator+(p2);
//Person p3 = p1 + p2;  //简化成这样

③通过全局函数实现重载

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

④全局函数重载本质调用

Person p3 = operator+(p1,p2);
//Person p3 = p1 + p2;  //简化成这样

⑤运算符重载,也可以发生函数重载

//函数重载的版本
Person operator+(Person &p1, int num)  
{
	Person temp;
	temp.m_A = p2.m_A + num;
	temp.m_B = p2.m_B + num;
	return temp;
}

void test01() 
{
	Person p1;
    p1.m_A = 10;
    p1.m_B = 10;

	Person p4 = p1 + 100; //Person + int,相当于 operator+(p1,10)
	cout << "p4.m_A" << p4.m_A << endl;
    cout << "p4.m_B" << p4.m_B << endl;
}

        2.左移运算符重载<<

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

class Person
{
public:
	int m_A;
	int m_B;
};

void test01()
{
	Person p;
	p.m_A = 10;
	p.m_B = 10;

	cout << p;  //报错,没有与这些操作数匹配的<<运算符 
}

① 不会利用成员函数重载左移运算符,因为无法实现cout在左侧

class Person
{
public:
	void operator<<(Person &p)  
	//最后调用结果为p.operator<<(p) 没有这么多对象 
	{
		
	} 
	int m_A;
	int m_B;
};
class Person
{
public:
	void operator<<(cout)  
	//本质:p.operator<<(cout),p调用operator成员函数,然后cout再参数传递进去 
	//简化版本:p << cout ,与预期cout << p结果不符 
	{
		
	} 
	int m_A;
	int m_B;
};

②只能利用全局函数重载左移运算符

cout是什么数据类型呢?cout是标准的输出流对象,通过ostream类创建出来的

void operator<<(ostream &cout,Person &p)  
//本质:operator << (cout,p)   //简化:cout << p 
{
	cout << "m_A = " << p.m_A << " m_B = " << p.m_B;
} 

void test01()
{
	Person p;
	p.m_A = 10;
	p.m_B = 10;
	
	cout << p; 
}

这个全局函数的意思就是,如果在别的函数中输出cout << (Person类的对象)时,就会把这个当作形参,然后执行内部的代码cout << "m_A = "... ...

ostream &cout的原因:cout对象全局只能有一个,用引用的方式传递过来。输出流不能拷贝,也就是说要传入输出流对象参数只能地址传递,不能值传递 

ostream & operator<<(ostream &cout,Person &p)  
{
	cout << "m_A = " << p.m_A << " m_B = " << p.m_B;
	return cout;
}
void test01()
{
	Person p;
	p.m_A = 10;
	p.m_B = 10;
	
	cout << p << "hello world" << endl;  //链式编程思想
    //如果cout << p调用之后返回的是void,那就无法继续后面 
	//如果返回的还是cout,就可以继续往后输出别的内容
}

 用ostream &的原因:return cout返回了cout本身,才能链式使用,返回值和参数都设为同一种类型,这里的ostream类型必须是引用类型,所以返回值必须是引用。ostream&用来修饰返回的类型,返回的是ostream类的对象cout

可以把cout改成其他名称(比如out)因为引用的本质是起别名,传参的时候,ostream &out=cout而且传入的实参是cout

如果把类的属性私有化,用友元:

class Person 
{
	friend ostream & operator<<(ostream &out,Person &p);
public:
	Person(int a,int b)  //构造函数赋初值
	{
		m_A = a;
		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);
	//p.m_A = 10;
	//p.m_B = 10;  //私有成员不可以外部赋值
	cout << p << "hello world" << endl;
}

       3.递增运算符重载++

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

分类:①前置++递增  ②后置++递增

区别:前置递增返回引用,后置递增返回值

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

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

void test01()
{
	Myinter myint;
	cout << ++myint << endl;  //报错,没有与这些操作数匹配的++运算符 
}

①前置++递增

class  Myinter  
{
	friend ostream& operator<<(ostream& cout,Myinter myint);
public:
	Myinter()
	{
		m_Num = 0;
	}
	
	Myinter& operator++()
	//Myinter&返回引用是为了一直对一个数据进行递增操作 
	//如果返回值就不能重新进行返回值的自加
	{
		m_Num++;  //先进行++运算 
		return *this;  //再返回自身,*this解引用 
	} 
private:
	int m_Num;
}; 

②后置++递增

class  Myinter  
{
	friend ostream& operator<<(ostream& cout,Myinter myint);
public:
	Myinter()
	{
		m_Num = 0;
	}
	
	Myinter operator++(int)  //int代表占位参数,可以用于区分前置和后置递增 
	//Myinter返回值,因为temp是局部变量不能引用传递
	{
		Myinter temp = *this;  //先记录当时的结果
		m_Num++;  //后递增 
		return temp;  //最后返回记录结果 
	}
private:
	int m_Num;
}; 

       4.赋值运算符重载=

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

①默认构造函数 ②默认析构函数 ③默认拷贝构造函数,对属性进行值拷贝

④赋值运算符operator=,对属性进行值拷贝

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

class Person 
{
public:
	Person(int age)
	{
		m_Age = new int(age);  
		//将年龄数据开辟到堆区,new int返回的是地址用int*指针接收 
		//堆区,由程序员手动开辟也手动释放 
	}
	~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 = p.m_Age;  //编译器提供的代码是浅拷贝

		m_Age = new int(*p.m_Age);  //深拷贝,解决浅拷贝的问题

		return *this;  //返回对象本身
	}
	
	int *m_Age;  //年龄指针 
}; 

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

       5.关系运算符重载==,!=

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

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;
		}
			return false;
	}
	string m_Name;
	int m_Age;
}; 

void test01()
{
	Person p1("Tom",18);
	Person p2("Jerry",18);
	if(p1==p2)
	{
		cout << "p1和p2是相等的" << endl;
	}
	else
	{
		cout << "p1和p2是不相等的" << endl;
	}
}

       6.函数调用运算符重载()

 由于重载后使用的方式很像函数的调用,因此称为仿函数,非常灵活没有固定写法

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

void test01()
{
	MyPrint myprint;
	myprint("hello world");  //仿函数
}

函数调用:

void MyPrint02(string test)  //函数调用
{
	cout << test << endl;
}

void test01()
{
	MyPrint myprint;
	MyPrint02("hello world");
}

非常灵活,没有固定写法:

class MyAdd
{
public:
	int operator()(int num1, int num2)
	{
		return num1 + num2;
	}
};

void test02()
{
	MyAdd add;
	int ret = add(100, 100);
	cout << "ret = " << ret << endl;

	//匿名函数对象调用
	//匿名对象:类型( ),特点:当前行执行完后立即被释放 
	cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}

六、继承 

有些类与类之间存在特殊的关系,比如:

定义这些类时,下级别的成员除了有上一级的共性,还有自己的特性,这时可以用继承来减少重复的代码

       1.继承的基本语法

class  A : public  B;

public为继承方式,A类为 子类 或 派生类,B类为 父类 或 基类

 如何实现网页中的内容?

普通实现:

//Java页面
class Java 
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}
	void content()
	{
		cout << "JAVA学科视频" << endl;
	}
};
//Python页面
class Python
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}
	void content()
	{
		cout << "Python学科视频" << endl;
	}
};
//C++页面
class CPP 
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}
	void content()
	{
		cout << "C++学科视频" << endl;
	}
};

void test01()
{
	//Java页面
	cout << "Java下载视频页面如下: " << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "--------------------" << endl;

	//Python页面
	cout << "Python下载视频页面如下: " << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "--------------------" << endl;

	//C++页面
	cout << "C++下载视频页面如下: " << endl;
	CPP cp;
	cp.header();
	cp.footer();
	cp.left();
	cp.content();
}

继承实现:

class BasePage  //公共页面类
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}
};

//Java页面
class Java : public BasePage
{
public:
	void content()
	{
		cout << "JAVA学科视频" << endl;
	}
};
//Python页面
class Python : public BasePage
{
public:
	void content()
	{
		cout << "Python学科视频" << endl;
	}
};
//C++页面
class CPP : public BasePage
{
public:
	void content()
	{
		cout << "C++学科视频" << endl;
	}
};

void test01()
{
	//Java页面
	cout << "Java下载视频页面如下: " << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "--------------------" << endl;

	//Python页面
	cout << "Python下载视频页面如下: " << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "--------------------" << endl;

	//C++页面
	cout << "C++下载视频页面如下: " << endl;
	CPP cp;
	cp.header();
	cp.footer();
	cp.left();
	cp.content();
}

       2.继承方式 

 三种:①公共继承     ②保护继承     ③私有继承

大小:公共 < 保护 < 私有,继承后,大的继承方式可以把父类的小的权限改变 

如果不写继承方式,则默认为private方式 

①公共继承

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

class Son1 :public Base1  //公共继承
{
public:
	void func()
	{
		m_A = 10; //父类中的公共权限成员,到子类中依然是公共权限
		m_B = 10; //父类中的保护权限成员,到子类中依然是保护权限
		//m_C = 10; //父类中的私有权限成员,子类访问不到 
	}
};

void test01()
{
	Son1 s1;
	s1.m_A = 100; //其他类只能访问到公共权限
	//s1.m_B = 100;  //到Son1中m_B是保护权限 类外访问不到 
}

②保护继承

class Son2:protected Base1  //保护继承
{
public:
	void func()
	{
		m_A = 100; //父类中公共成员,到子类中变为保护权限 
		m_B = 100; //父类中保护成员,到子类中变为保护权限 
		//m_C = 100; //父类中私有成员,子类访问不到 
	}
};
void test02()
{
	Son2 s1;
	//s1.m_A = 1000;  //在Son1中m_A变为保护权限 类外访问不到
	//s1.m_B = 1000; //在Son2中m_B是保护权限 类外访问不到 
}

 ③私有继承

class Son3:private Base1  //私有继承
{
public:
	void func()
	{
		m_A = 100; //父类中公共成员,到子类中变为私有成员 
		m_B = 100; //父类中保护成员,到子类中变为私有成员 
		//m_C = 100; //父类中私有成员,子类访问不到
	}
};

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

void test03()
{
	Son3 s1;
	//s1.m_A = 1000;  //在Son3中m_A变为私有成员 类外访问不到
	//s1.m_B = 1000; //在Son3中m_B变为私有成员 类外访问不到 
}

       3.继承中的对象模型 

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

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

 

       4.继承中构造和析构顺序

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

顺序:父构——子构——子析——父析

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()
{
	//继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
	Son s;
}

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

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

①访问子类同名成员:直接访问

②访问父类同名成员:加作用域

同名成员属性处理:

class Base 
{
public:
	Base()
	{
		m_A = 100;
	}
public:
	int m_A;
};

class Son : public Base 
{
public:
	Son()
	{
		m_A = 200;
	}
public:
	int m_A;
};

void test01()
{
	Son s;

	cout << "Son下的m_A = " << s.m_A << endl;  //子类:直接访问 
	cout << "Base下的m_A = " << s.Base::m_A << endl;  //父类:加作用域 
}

同名成员函数处理:

class Base 
{
public:
	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:
	void func()
	{
		cout << "Son - func()调用" << endl;
	}
public:
	int m_A;
};

void test01()
{
	Son s;

	s.func();  //子类:直接调用 
	s.Base::func();  //父类:加作用域 
	//s.func(10);  //报错 
	//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有的同名成员函数
	//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
	s.Base::func(10);
}

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

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

与上文处理方式一致,只不过有两种访问方式,1通过对象,2通过类名

①访问子类同名成员:直接访问

②访问父类同名成员:加作用域

同名成员属性处理:

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

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

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

int Son::m_A = 200;  //类外初始化

void test01()
{
	cout << "通过对象访问: " << endl;  //通过对象访问
	Son s;
	cout << "Son  下 m_A = " << s.m_A << endl;
	cout << "Base 下 m_A = " << s.Base::m_A << endl;

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

同名成员函数处理: 

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

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

       7.多继承语法

 一个类可以继承多个类

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

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

class Base1 
{
public:
	Base1()
	{
		m_A = 100;
	}
public:
	int m_A;
};

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

class Son : public Base1, public Base2
{
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;
	cout << "Base1::m_A= " << s.Base1::m_A << endl; //当父类中出现同名成员,需要加作用域区分
	cout << "Base2::m_A= " << s.Base2::m_A << endl;
}

       8.菱形继承 

概念:两个子类继承同一个父类,又有某个类同时继承着两个子类

羊和驼都继承了动物的数据,当羊驼使用数据时,就会产生二义性 

羊驼继承了两份动物的数据,但是这份数据只需要一份就行,导致资源浪费以及毫无意义

class Animal
{
public:
	int m_Age;
};

class Yang : public Animal {};
class Tuo : public Animal {};
class YangTuo : public Yang, public Tuo {};

void test01()
{
	YangTuo st;
	st.Yang::m_Age = 18;//当菱形继承,两个父类拥有相同数据,需要加以作用域区分 
	st.Tuo::m_Age = 28;

	cout << "st.Yang::m_Age = " << st.Yang::m_Age << endl;
	cout << "st.Tuo::m_Age = " <<  st.Tuo::m_Age << endl;
}

菱形继承导致数据有两份,资源浪费 

利用虚继承解决菱形继承的问题:

class Animal
{
public:
	int m_Age;
};

//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal类称为虚基类
class Yang : virtual public Animal {};
class Tuo : virtual public Animal {};
class YangTuo : public Yang, public Tuo {};

void test01()
{
	YangTuo st;
	st.Yang::m_Age = 18;
	st.Tuo::m_Age = 28;

	cout << "st.Yang::m_Age = " << st.Yang::m_Age << endl;
	cout << "st.Tuo::m_Age = " <<  st.Tuo::m_Age << endl;
	cout << "st.m_Age = " << st.m_Age << endl;
}

相当于开辟一个父类的变量名的类型空间存储,而子类继承的不是拷贝父类的内存空间,而是直接继承一个指针指向那块数据的存储空间 

七、多态

       1.多态的基本概念

分类:①静态多态:函数重载 和 运算符重载

           ②动态多态:派生类和虚函数

区别:①静态多态的函数地址早绑定,编译阶段确定函数地址

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

动态多态满足条件:①有继承关系  ②子类重写父类的虚函数 

动态多态使用条件:父类的指针或引用 指向子类对象

class Animal
{
public:
	//函数前面加上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();
}

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

	Dog dog;
	DoSpeak(dog);
}

       2.多态的原理剖析 

class Animal
{
public:
    void speak()  
	{
		cout << "动物在说话" << endl;
	}
};
void test01()
{
	cout << "sizeof Animal = " << sizeof(Animal) << endl;
}

 

函数前加上virtual关键字变成虚函数后:

(32位指针4个字节,64位指针8个字节) 

       3.多态案例一:计算器类

分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类

多态的优点:①代码组织结构清晰  ②可读性强  ③利于前期和后期的扩展以及维护

普通实现:

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;
		}
		//如果要提供新的运算,需要修改源码
	}
public:
	int m_Num1;  //操作数1 
	int m_Num2;  //操作数2 
};

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

在开发中,提倡开闭原则:对扩展进行开放,对修改进行关闭 

多态实现:

class AbstractCalculator  //抽象计算器类
{
public :
	virtual int getResult()  //父类中要有虚函数 
	{
		return 0;
	}
	int m_Num1;
	int m_Num2;
};

//加法计算器
class AddCalculator : public AbstractCalculator  //子类中要重写父类的虚函数 
{
public:
	int getResult()
	{
		return m_Num1 + m_Num2;
	}
};

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

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

void test01()
{
    //父类的指针或者引用指向子类时会发生多态
	//创建加法计算器
	AbstractCalculator *abc = new AddCalculator;  
	//指针写法,new在堆区上创建了一个对象,返回的是指针 
	//引用写法:AbstractCalculator & abc = AddCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;  //堆区数据手动开辟手动释放,用完了记得销毁

	//创建减法计算器
	abc = new SubCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;  

	//创建乘法计算器
	abc = new MulCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;
}

       4.纯虚函数和抽象类

在多态中,通常父类中虚函数的实现毫无意义,主要都是调用子类重写的内容

纯虚函数语法:virtualo 返回值类型 函数名 (参数列表)= 0;

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

抽象类特点:①无法实例化对象 

                      ②子类必须重写抽象类中的纯虚函数,否则子类也属于抽象类,无法实例化对象

虚函数的作用作用就是创建虚函数列表从而达到通过指针联系子类,即多态

相当于在父类中存了个指针,就能指向子类中的函数

或者说父类中的函数也抽象成了类,不同子类中重写的函数就是这个抽象函数类的对象 

class Base  //抽象类 
{
public:
	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();
	delete base;//记得释放
}

       5.多态案例二:制作饮品 

制作饮品流程:煮水——冲泡——倒入杯子——加入辅料

用多态实现,提供抽象制作饮品基类,提供子类制作咖啡和茶叶

class AbstractDrinking 
{
public:
	virtual void Boil() = 0;  //烧水
	virtual void Brew() = 0;  //冲泡
	virtual void PourInCup() = 0;  //倒入杯中
	virtual void PutSomething() = 0;  //加入辅料

	void MakeDrink()  //规定流程
	{
		Boil();
		Brew();
		PourInCup();
		PutSomething();
	}
};

class Coffee : public AbstractDrinking  //制作咖啡
{
public:
	virtual void Boil()  //烧水
	{
		cout << "煮农夫山泉!" << endl;
	}
	virtual void Brew()  //冲泡
	{
		cout << "冲泡咖啡!" << endl;
	}
	virtual void PourInCup()  //倒入杯中
	{
		cout << "将咖啡倒入杯中!" << endl;
	}
	virtual void PutSomething()  //加入辅料
	{
		cout << "加入牛奶!" << endl;
	}
};

class Tea : public AbstractDrinking  //制作茶水
{
public:
	virtual void Boil()  //烧水
	{
		cout << "煮自来水!" << endl;
	}
	virtual void Brew()  //冲泡
	{
		cout << "冲泡茶叶!" << endl;
	}
	virtual void PourInCup()  //倒入杯中
	{
		cout << "将茶水倒入杯中!" << endl;
	}
	virtual void PutSomething()  //加入辅料
	{
		cout << "加入枸杞!" << endl;
	}
};

void DoWork(AbstractDrinking* abs)  
//AbstractDrinking* abs = new Coffee 和 new tea  父类的指针指向子类对象
//形参                    实参 
{
	abs->MakeDrink();
	delete abs;  //记得释放
}

void test01() 
{
	DoWork(new Coffee);  //
	cout << "--------------" << endl;
	DoWork(new Tea);
}

       6.虚析构和纯虚析构

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

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

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

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

                             类名::~类名(){ }

共性:①可以解决父类指针释放子类对象时不干净的问题  ②都需要有具体的函数实现

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

虚析构:

class Animal 
{
public:
	Animal()
	{
		cout << "Animal 构造函数调用!" << endl;
	}

	virtual void Speak() = 0;  //纯虚函数 

	~Animal()
	{
		cout << "Animal 析构函数调用!" << endl;
	}
};

class Cat : public Animal 
{
public:
	Cat(string name)
	{
		cout << "Cat构造函数调用!" << endl;
		m_Name = new string(name);  //new返回string指针 
	}
	virtual void Speak()  //重写 
	{
		cout << *m_Name <<  "小猫在说话!" << endl;  //解引用 
	}
	~Cat()
	{
		cout << "Cat析构函数调用!" << endl;
		if (this->m_Name != NULL) 
		{
			delete m_Name;  //delete是清空指针指向的堆区内存,并不清空指针
			m_Name = NULL;  //NULL是把指针替换为NULL地址
		}
	}
public:
	string *m_Name;  //把名字创建到堆区,用指针维护它 
};

void test01()
{
	Animal *animal = new Cat("Tom");
	animal->Speak();
	delete animal;  //记得释放 
}

 

正确顺序:父类构造——子类构造——执行Speak函数——(子类析构)——父类析构 

问题:少了子类析构,说明堆区数据没有被释放干净,导致内存泄漏

原因:如果子类中有属性开辟到堆区,那么父类指针在释放时只会调用父类自己的析构函数,而无法调用到子类的析构代码,此时堆内存中的子类数据还没有被清除

内存泄漏是指:程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果

解决方式:把父类析构加上virtual关键字变成虚析构

纯虚析构:

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

	virtual ~Animal() = 0;  //纯虚析构 类内声明 
};

Animal::~Animal()  //类外实现 
{
	cout << "Animal 纯虚析构函数调用!" << endl;
}

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

       7.多态案例三:电脑组装

 电脑的主要组成部件为 CPU(计算)  显卡(显示)  内存条(存储),将每个零件封装出抽象基类,并提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商,创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口,测试时组装三太不同的电脑进行工作

  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值