C++笔记 day31 运算符重载 继承

148 阅读5分钟

运算符重载

关系运算符的重载

对于自定义数据类型,编译器不知道如何进行比较

重载  ==   !=号

    bool operator==( Person & p)

    bool operator!=(Person & p)

示例

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class Person 
{
public:
	Person(string name, int age) 
	{
		this->m_Name = name;
		this->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;
		}
	}
	bool operator!=(Person& p) 
	{
		return !(this->m_Name == p.m_Name && this->m_Age == p.m_Age);
	}
	string m_Name;
	int m_Age;
};

void test01() 
{
	int a = 10;
	int b = 20;
	int c = 30;
	if (a == b) 
	{
		cout << "a == b" << endl;
	}
	else 
	{
		cout << "a != b" << endl;
	}

	Person p1("Tom", 18);
	Person p2("Tom", 18);
	if (p1 == p2) 
	{
		cout << "p1 == p2" << endl;
	}
	else 
	{
		cout << "p1 != p2" << endl;
	}
	if (p1 != p2)
	{
		cout << "p1 不等于 p2" << endl;
	}
	else
	{
		cout << "p1 等于 p2" << endl;
	}
}

int main()
{

	test01();

	system("pause");
	return EXIT_SUCCESS;
}

函数调用运算符重载

重载 ()

使用时候很像函数调用,因此称为仿函数

    void operator()(*string* text)
    
    int operator()(int a,int b)

仿函数写法不固定,比较灵活

*cout* << MyAdd()(1, 1) << *endl*; // 匿名函数对象 特点:当前行执行完立即释放

示例:

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


class MyPrint 
{
public:
	void operator()(string text) 
	{
		cout << text << endl;
	}

};
void myPrint2(string text)
{
	cout << text << endl;
}

class MyAdd 
{
public:
	int operator()(int a, int b) 
	{
		return a + b;
	}
};

void test01() 
{
	MyPrint myprint;
	myprint("hello world"); //仿函数,本质是一个对象,重载了一下小括号,函数对象

	myPrint2("hello world"); //普通函数
}

void test02() 
{
	MyAdd myadd;
	cout << myadd(1, 1) << endl;
	cout << MyAdd()(1, 2) << endl;//MyAdd()匿名对象,当前行执行完立即释放,是使用仿函数的一种快速方式
}

int main()
{

	test01();
	test02();

	system("pause");
	return EXIT_SUCCESS;
}

不要重载 && 和 ||

原因是无法实现短路特性

建议:将<< 和 >>写成全局函数,其他可重载的符号写到成员即可

image.png

强化训练-字符串类封装

myString类 实现自定义的字符串类

属性

char * pString; 维护 在堆区真实开辟的字符数组

int m_Size; 字符串长度

行为

有参构造 MyString(char * str)

拷贝构造 MyString(const MyString & str);

析构  ~MyString();

重载<< 运算符

重载 >> 运算符

重载 =   赋值运算

重载 []  str[0]  按照索引位置设置获取字符

重载 +  字符串拼接

重载 == 对比字符串

示例

头文件

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class MyString 
{
public:
	friend ostream& operator<<(ostream& cout, MyString& str);
	friend istream& operator>>(istream& cin, MyString& str);
	//有参构造
	MyString(const char* str);
	//拷贝构造
	MyString(const MyString& str);
	//重载 = 运算符
	MyString& operator=(const char * str);
	MyString& operator=(const MyString& str);
	//重载[]运算符
	char& operator[](int index);
	//重载+运算符
	MyString operator+(const char* str);
	MyString operator+(const MyString& str);
	//重载==运算符
	bool operator==(const char * str);
	bool operator==(const MyString& str);
	~MyString();

private:

	char* pString;//维护在堆区开辟的字符数组

	int m_Size;//字符串长度 不统计\0
};

实现

#include "MyString.h"

//重载左移运算符
ostream& operator<<(ostream& cout, MyString& str) 
{
	cout << str.pString << endl;
	return cout;
}
//重载右移运算符
istream& operator>>(istream& cin, MyString& str) 
{
	//先清空原来堆区数据
	if (str.pString) 
	{
		delete[] str.pString;
		str.pString = NULL;
	}

	char buf[1024];//开辟临时数组 记录用户输入内容
	cin >> buf;

	str.pString = new char[strlen(buf) + 1];
	strcpy(str.pString, buf);
	str.m_Size = strlen(buf);

	return cin;
}
MyString::MyString(const char* str) 
{
	cout << "MyString 有参构造函数调用" << endl;
	this->pString = new char[strlen(str) + 1];
	strcpy(this->pString, str);
	this->m_Size = strlen(str);
}

MyString::MyString(const MyString& str) 
{
	cout << "MyString 拷贝构造函数调用" << endl;
	this->pString = new char[strlen(str.pString)+1 ];
	strcpy(this->pString, str.pString);
	this->m_Size = str.m_Size;
}

MyString& MyString::operator= (const char* str)
{
	//先判断原来堆区是否有内容,如果有先释放
	if (this->pString != NULL) 
	{
		delete[]this->pString;
		this->pString = NULL;
	}
	this->pString = new char[strlen(str) + 1];
	strcpy(this->pString, str);
	this->m_Size = strlen(str);
	return *this;

}
MyString& MyString::operator= (const MyString& str)
{
	//先判断原来堆区是否有内容,如果有先释放
	if (this->pString != NULL)
	{
		delete[]this->pString;
		this->pString = NULL;
	}
	this->pString = new char[strlen(str.pString) + 1];
	strcpy(this->pString, str.pString);
	this->m_Size = strlen(str.pString);
	return *this;
}

char& MyString::operator[](int index)
{
	return this->pString[index];
}

MyString MyString::operator+(const char* str)
{
	//本身 abc 传入def
	// 计算开辟内存大小
	int newSize = this->m_Size + strlen(str) + 1;
	char * temp=new char[newSize];
	memset(temp, 0, newSize);

	strcat(temp, this->pString);
	strcat(temp, str);

	MyString newString = temp;//调用我们写好的有参构造来初始化newString

	delete[] temp;//把临时指针释放掉
	return newString;

}

MyString MyString::operator+(const MyString& str)
{
	int newSize = this->m_Size + strlen(str.pString) + 1;
	char* temp = new char[newSize];
	memset(temp, 0, newSize);

	strcat(temp, this->pString);
	strcat(temp, str.pString);

	MyString newString = temp;//调用我们写好的有参构造来初始化newString

	delete[] temp;//把临时指针释放掉
	return newString;
}

bool MyString::operator==(const char* str)
{
	if (strcmp(this->pString, str) == 0) 
	{
		return true;
	};
	return false;
}

bool MyString::operator==(const MyString& str)
{
	if (strcmp(this->pString, str.pString) == 0)
	{
		return true;
	};
	return false;
}

MyString::~MyString() 
{
	cout << "MyString 析构函数调用" << endl;
	if (this->pString != NULL) 
	{
		delete[]this->pString;
		this->pString = NULL;
	}
}

主函数

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include "MyString.h"

void test01() 
{
	MyString str = "abc";
	//MyString str2 = str;

	cout << str << endl;

	cout << "请重新给str赋值" << endl;
	cin >> str;
	cout << "str新的值为:" << str << endl;

	MyString str2 = str;
	cout << "str2 = " << str2 << endl;
}

void test02() 
{
	MyString str = "abcd";
	MyString str2 = " ";
	str2 = str;
	cout << "str2= " << str2 << endl;

	cout << str2[0] << endl;
	str2[0] = 'z';
	cout << "str2[0]改为z后输出" << str2[0] << endl;

	MyString str3 = "abc";
	MyString str4 = "def";
	MyString str5 = str3 + str4;

	cout << "str5" << str5 << endl;
	MyString str6 = str5 + "ghe";
	cout << "str6" << str6 << endl;

	if (str5 == str6) 
	{
		cout << "str5 == str6" << endl;
	}
	else 
	{
		cout << "str5 != str6" << endl;
	}
	if (str6 == "abcdefghe")
	{
		cout << "str6 == abcdefghe" << endl;
	}
	else
	{
		cout << "str6 != abcdefghe" << endl;
	}

}

int main()
{
	//test01();
	//int a = 10;
	//cin >> a;
	//cout << "a = " << a << endl;
	test02();
	system("pause");
	return EXIT_SUCCESS;
}

继承

继承基本语法

image.png

继承优点:减少重复的代码,提高代码复用性

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

// News          子类    派生类

// BasePage 父类    基类

示例


//利用继承模拟网页
//集成优点:减少重复的代码,提高代码复用性
class BasePage
{
public:
	void header()
	{
		cout << "公共的头部" << endl;
	}
	void footer()
	{
		cout << "公共的底部" << endl;
	}
	void leftList()
	{
		cout << "公共的左侧列表" << endl;
	}
};
// class 子类 : 继承方式 父类
// News 子类 也叫 派生类
// BasePage 父类 也叫 基类
class News : public BasePage
{
public:
	void content()
	{
		cout << "新闻播报" << endl;
	}
};

class Sport :public BasePage
{
public:
	void content()
	{
		cout << "世界杯" << endl;
	}
};

void test01() 
{
	News news;
	cout << "新闻界面的内容如下:" << endl;
	news.header();
	news.footer();
	news.leftList();
	news.content();

	Sport sp;
	cout << "体育界面的内容如下:" << endl;
	sp.header();
	sp.footer();
	sp.leftList();
	sp.content();
}



int main()
{
	test01();


	system("pause");
	return EXIT_SUCCESS;
}

继承方式

image.png

公共继承

父类中公共权限,子类中变为公共权限

父类中保护权限,子类中变为保护权限

父类中私有权限,子类访问不到

保护继承

父类中公共权限,子类中变为保护权限

父类中保护权限,子类中变为保护权限

父类中私有权限,子类访问不到

私有继承

父类中公共权限,子类中变为私有权限

父类中保护权限,子类中变为私有权限

父类中私有权限,子类访问不到

示例

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

// 公共继承
class Base1
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

class Son1 : public Base1
{
public:
	void func() 
	{
		m_A = 100; //父类中公共权限,子类中还是公共权限
		m_B = 100; //父类中的保护权限,子类中还是保护权限
		//m_C = 100; //父类中私有成员,子类无法访问
	}
};

// 保护继承
class Base2
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

class Son2 : protected Base2
{
public:
	void func()
	{
		m_A = 100; //父类中公共权限,子类中变为保护权限
		m_B = 100; //父类中的保护权限,子类中还是保护权限
		//m_C = 100; //报错 父类中私有成员,子类无法访问
	}
};

// 私有继承
class Base3
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

class Son3 : private Base3
{
public:
	void func()
	{
		m_A = 100; //父类中公共权限,子类中变为私有权限
		m_B = 100; //父类中的保护权限,子类中还是私有权限
		//m_C = 100; //报错 父类中私有成员,子类无法访问
	}
};

class GrandSon3 : public Son3
{
public:
	void func()
	{
		m_A = 100; //Son3中是私有权限,GrandSon3访问不到
		m_B = 100; //Son3中是私有权限,GrandSon3访问不到
	}
};

void test01() 
{
	Son1 s1;
	s1.m_A = 100;//在Son1 中 m_A 是公共权限 类外可以访问
	//s1.m_B = 100;//报错,Son1 中 m_B是保护权限 类外不能访问

	Son2 s2;
	//s2.m_A = 100;//报错,在Son2 中 m_A 是保护权限 类外不能访问
	//s2.m_B = 100;//报错,在Son1 中 m_B 是保护权限 类外不能访问

	Son3 s3;
	s3.m_A = 100; //报错,在Son3中是私有权限,类外访问不到
	s3.m_A = 100; //报错,在Son3中是私有权限,类外访问不到
}
int main()
{



	system("pause");
	return EXIT_SUCCESS;
}

继承中的对象模型

父类中的私有属性,子类是继承下去了,只不过由编译器给隐藏了,访问不到

可以利用开发人员工具查看对象模型

C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\Shortcuts

打开开发人员命令工具

跳转盘符 E:

跳转文件路径  cd到文件路径下

cl /d1 reportSingleClassLayout类名  文件名

image.png

继承中的构造和析构

先调用父类构造,再调用其他成员构造, 再调用自身构造 ,析构的顺序与构造相反

利用初始化列表语法  显示调用父类中的其他构造函数

image.png

父类中 构造、析构、拷贝构造 、operator=  是不会被子类继承下去的

原因是只有父类知道如何初始化自己的属性

示例:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

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

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

class Son1 :public Base1 
{
public:
	Son1()
	{
		cout << "Son1的构造函数调用" << endl;
	}
	~Son1()
	{
		cout << "Son1的析构函数调用" << endl;
	}
	Other other;
};



void test01() 
{
	Son1 s; //先调用父类构造,再调用自身构造,析构的顺序与构造相反
	//当有其他类作为本类成员的时候,先调用父类构造,再调用其他类构造,在调用自身构造,析构的顺序与构造相反
}

class Base2 
{
public:
	Base2(int a)
	{
		this->m_A = a;
		cout << "Base2的构造函数调用" << endl;
	}
	int m_A;
};

class Son2 :public Base2 
{
public:
	Son2(int a) :Base2(a) //利用初始化列表语法,显示调用父类中的其他构造函数 
	//也可以写成 Son2() :Base2(10)但就相当于写死了 
	//也可以给父类增加默认构造函数
	{
		cout << "Son2的构造函数调用" << endl;
	}
};

void test02() 
{
	Son2 s2(100); //此处100是传给父类的有参构造的
}

//父类中的默认构造、析构、拷贝构造、operator=是不会被子类继承下去的

int main()
{

	test01();

	system("pause");
	return EXIT_SUCCESS;
}

继承中的同名成员处理

我们可以利用作用域 访问父类中的同名成员

当子类重新定义了父类中的同名成员函数,子类的成员函数会 隐藏掉父类中所有重载版本的同名成员,可以利用作用域显示指定调用

示例

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class Base 
{
public:
	Base() 
	{
		this->m_A = 10;
	}

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

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

	int m_A;
};

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

	int m_A;
};

void test01() 
{
	Son s1;

	cout << "s1.m_A = " << s1.m_A << endl; //子类中有跟父类同名成员,优先访问子类

	//我们可以利用作用域,访问父类同名成员
	cout << "Base 中的m_A = " << s1.Base::m_A << endl;
}

void test02() 
{
	Son s2;
	s2.func();
	s2.Base::func();
	//s2.func(1);  报错
	//原因:当子类重新定义了父类中的同名成员函数,子类的成员函数会隐藏掉父类中所有重载版本的同名成员
	//可以利用作用域显示同名调用
	s2.Base::func(1);
}

int main()
{
	test01();
	test02();


	system("pause");
	return EXIT_SUCCESS;
}

继承中的同名 静态成员处理

结论和 非静态成员 一致

只不过调用方式有两种

通过对象

通过类名

通过类名的方式 访问 父类作用域下的m_A静态成员变量

Son::Base::m_A

多继承基本语法

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

当多继承的两个父类中有同名成员,需要加作用域区分

image.png

示例

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class Base1 
{
public :
	Base1() 
	{
		this->m_A = 10;
		this->m_E = 30;
	}
	int m_A;
	int m_E;
};

class Base2
{
public:
	Base2()
	{
		this->m_B = 20;
		this->m_E = 40;
	}
	int m_B;
	int m_E;
};

//多继承
class Son : public Base1, public Base2 
{
public:
	int m_C;
	int m_D;
};

void test01() 
{
	cout << "Size of Son = " << sizeof(Son) << endl;//16

	Son s;
	cout << s.m_A << endl;
	cout << s.m_B << endl;
	//当多继承的两个父类中有同名成员,需要加作用域区分
	cout << s.Base1::m_E << endl;
	cout << s.Base2::m_E << endl;
}

int main()
{
	test01();


	system("pause");
	return EXIT_SUCCESS;
}

菱形继承

image.png 问题:

image.png

两个类有公共的父类  和共同的子类 ,发生菱形继承

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

解决方案:利用虚继承可以解决菱形继承问题(左侧是虚继承内存结构,右边是未使用虚继承内存结构)

class Sheep : **virtual** public Animal{};

image.png

当发生虚继承后,sheep和tuo类中 继承了一个  vbptr指针   虚基类指针   指向的是一个 虚基类表  vbtable

虚基类表中记录了  偏移量 ,通过偏移量 可以找到唯一的一个m_Age

利用地址偏移找到 vbtable中的偏移量 并且访问数据

示例

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

//动物类
class Animal 
{
public:
	int m_Age;//年龄
};

//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 << "Sheep::m_Age = " << st.Sheep::m_Age << endl;
	cout << "Tuo::m_Age = " << st.Tuo::m_Age << endl;
	cout << "age = " << st.m_Age << endl;
	//均为20,虚继承情况下 m_Age只有一份
}
//虚继承内存结构,通过偏移量查看,test02不重要

void test02()
{
	SheepTuo st;
	st.m_Age = 10;

	//通过Sheep找到 偏移量
	//*(int *)&st 解引用到了 虚基类表中
	cout << *((int *)*(int *)&st + 1) << endl;

	//通过Tuo 找到偏移量
	cout << *((int *)*((int *)&st + 1) + 1) << endl;

	//通过偏移量  访问m_Age

	cout << "m_Age = " << ((Animal *)((char *)&st + *((int *)*(int *)&st + 1)))->m_Age << endl;

	cout << "m_Age = " << *((int *)((char *)&st + *((int *)*(int *)&st + 1))) << endl;
}
int main()
{
	test01();


	system("pause");
	return EXIT_SUCCESS;
}