C++之对象模型和this 指针,友元,和运算符重载

时间:2024-03-12 16:43:40

1,对象模型和this 指针

1.1成员变量和成员函数分开存储

在C++中,类内的成员变量和成员函数分开存储

只有非静态成员变量才属于类的对象上

示例:

#include<iostream>
using namespace std;

//成员变量 和 成员函数 分开存储
class Person
{
	int m_A;//非静态成员变量 属于类对象上

	static int m_B;//静态成员变量 不属于类对象上

	void func() {}//非静态成员函数 不属于类对象上

	static void func2() {}//静态成员函数 不属于类对象上
};

int Person::m_B = 0;

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

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

1.2 this指针概念

 C++中成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码

那么问题是:这一块代码是如何区分那个对象调用自己的呢?
this指针指向被调用的成员函数所属的对象c++通过提供特殊的对象指针,this指针,解决上述问题。

this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可

this指针的用途:
当形参和成员变量同名时,可用this指针来区分
在类的非静态成员函数中返回对象本身,可使用return*this

#include<iostream>
using namespace std;

class Person
{
public:
	Person(int age)
	{
		//1,当形参和成员变量同名时,用this指针来区分
		this->age = age;
	}

	Person& PersonAddPerson(Person p)
	{
		this->age += p.age;
		//返回对象本身
		return *this;
	}
	int age;
};

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

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

	
}

int main()
{
	test01();

	system("pause");

	return 0;
}

1.3 空指针访问成员函数

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

如果用到this指针,需要加以判断保证代码的健壮性

错误示范:

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

//空指针访问成员变量
class Person
{
public:
	void ShowClassName()
	{
		cout << "我是Person类!" << endl;
	}
	void ShowPersonAge()
	{
		//报错原因因为传入的指针时为NULL
		cout << "age=" << m_Age << endl;
		if (this == NULL)
		{
			return;
		}
		cout << "this is Person class" << endl;
	}
	int m_Age;
};

void test01()
{
	Person* p = NULL;

	p->ShowClassName();

	p->ShowPersonAge();
}

int main()
{
	test01();

	system("pause");

	return 0;
}

 正确示范:

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

//空指针访问成员变量
class Person
{
public:
	void ShowClassName()
	{
		cout << "我是Person类!" << endl;
	}
	void ShowPersonAge()
	{
		//报错原因因为传入的指针时为NULL
		
		if (this == NULL)
		{
			return;
		}
		cout << "age=" << this->m_Age << endl;
	}
	int m_Age;
};

void test01()
{
	Person* p =NULL;

	p->ShowClassName();

	p->ShowPersonAge();
}

int main()
{
	test01();

	system("pause");

	return 0;
}

1.4 const修饰成员指针

常函数:

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

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

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

常对象:

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

常对象只能调用常函数 

示例:

#include<iostream>
using namespace std;
#include<string>
class Person
{
public:
	//this指针的本质 是指针常量  指针的指向是不可以修改的
	//const Person * const this;
	//在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改
	void showPerson() const
	{
		//this->m_A = 100;
		//this = NULL;//this指针不可以修改指针的指向的值
	}

	void func()
	{

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


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

void test02()
{
	const Person p;
	//p.m_A = 100;
	p.m_B = 100;//m_B是特殊值,在常对象下也可以修改

	p.showPerson();
	//p.func();//常对象 不可以调用普通成员函数,因为普通成员函数可以修改属性
}

int main()
{
	test01();

	test02();

	system("pause");

	return 0;
}

2,友元

生活中你的家有客厅(Public),有你的卧室(Private)
客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去
但是呢,你也可以允许你的好闺蜜好基友进去。 


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


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


友元的关键字为 friend


友元的三种实现
2.1 全局函数做友元

示例:

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

//建筑物类
class Building
{
	//告诉编译器 goodGay全局变量 是 Building 类的好朋友,可以访问类中的私有内容
	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;
}


2.2 类做友元

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

class Building
{
	friend class GoodGay;

public:
	public:
		Building();

	string m_SettingRoom;

private:
	string m_Bedroom;

};

class GoodGay
{
public:
	GoodGay();

public:

	void visit();//参观函访问building中的属性

	Building* building;
};

//类外写成员函数
Building::Building()
{
	m_SettingRoom = "客厅";
	m_Bedroom = "卧室";
}

GoodGay::GoodGay()
{
	//创建建筑物对象
	building = new Building;
}

void GoodGay::visit()

{
	cout << "好基友正在访问:" << building->m_SettingRoom << endl;

	cout << "好基友正在访问:" << building->m_Bedroom<< endl;
}
void test01()
{
	GoodGay gg;
	gg.visit();
}

int main()
{
	test01();

	system("pause");

	return 0;
}


2.3 成员函数做友元

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

class Building;

class GoodGay
{
public:
	GoodGay();

	void visit();//让visit函数访问Building中私有成员
	void visit2();//让visit2函数不可以访问Building中私有成员

	Building* building;
};
class Building
{
	//告诉编译器 GoodGay类下的visit成员函数作为本类的好朋友,可以访问私有成员
	friend void GoodGay::visit2();
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 << "visit 函数正在访问:" << building->m_SittingRoom << endl;
	//cout << "visit2 函数正在访问:" << building->m_Bedroom << endl;
}
void GoodGay::visit2()
{
	cout << "visit2 函数正在访问:" << building->m_SittingRoom << endl;
	cout << "visit2 函数正在访问:" << building->m_Bedroom << endl;
}
void test01()
{
	GoodGay gg;
	gg.visit();
	gg.visit2();
}

int main()
{
	test01();

	system("pause");

	return 0;
}

3,运算符重载

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

3.1 加号运算符重载

作用:实现两个自定义数据类型

方式一: 成员函数重载

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

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

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.operator+(p2);
	cout << "p3.m_A = " << p3.m_A << endl;
	cout << "p3.m_B = " << p3.m_B << endl;
	cout << endl;

}

int main()
{
	test01();

	system("pause");

	return 0;
}

 方式二:全局函数重载+号

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

class Person
{
public:
	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 = operator+(p1, p2);
	//简化为Person p3 = p1 + p2;
	cout << "p3.m_A = " << p3.m_A << endl;
	cout << "p3.m_B = " << p3.m_B << endl;
	cout << endl;

}

int main()
{
	test01();

	system("pause");

	return 0;
}

 

方式三:运算符重载 也能发生函数重载

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

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

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 p4 = p1+100;//Person + int

	cout << "p4.m_A = " << p4.m_A << endl;
	cout << "p4.m_B = " << p4.m_B << endl;
	cout << endl;

}

int main()
{
	test01();

	system("pause");

	return 0;
}

 

总结1:对于内置的数据类型的表达式的运算符是不可能改变的

总结2:不要滥用运算符重载 

3.2 左移运算符重载

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

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

class Person
{
	friend ostream& operator<<(ostream &cout, Person &p);
public:
	Person(int a, int b)
	{
		this->m_A=a;
		this->m_B=b;
	}

	//利用成员函数重载 左移运算符 p.operator<<(cout)  简化版本 p<<cout
	// 不会利用成员函数重载<<运算符,因为无法实现cout在左侧
	//void operator<<(Person& p)
	//{
	//
	//}

private:
	int m_A;
	int m_B;
};

//只能利用全局函数重载左移运算符
//ostream对象只能有一个
ostream & operator<<(ostream & cout, Person & p)//本质 operator<<(cout,p)  简化版本 cout<<p
{
	cout << "m_A= " << p.m_A << "m_B" << p.m_B;
	return cout;
}

void test()
{
	Person p(10, 20);
	//p.m_A = 10;
	//p.m_B = 10;
	cout << p <<" "<< "hello world" << " " << endl;//链式编程
}
int main()
{
	test();

	system("pause");

	return 0;
}

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

3.3  递增运算符重载

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

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

	//重载前置++,返回引用为了一直对一个数据进行递增操作
	MyInterger& operator++()
	{
		//先进行++运算
		m_Num++;
		//再将自身返回
		return *this;
	}
	//重载后置++
	MyInterger& operator++(int)//int 代表占位参数,可以用于区分前置和后置递增
	{
		//先记录当时结果
		MyInterger temp = *this;
		//后递增
		m_Num++;
		//最后将记录结果返回
		return temp;
	}
private:
	int m_Num;
};

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

	cout << ++myint << endl;
	cout << endl;
}

void test02()
{
	MyInterger myint;

	cout <<myint++ << endl;
	cout << myint << endl;
}

int main()
{
	test01();

	test02();

	system("pause");

	return 0;
}

 

3.4  赋值运算符重载

C++编译器至少给一个类添加4个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
4.赋值运算符 operator=,对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题 

示例:

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

//自定义整型
class Person
{
	friend void test01();
public:
	Person(int age)
	{
		m_Age = new int(age);
	}

	~Person()
	{
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
	}
	int *m_Age;
};

void test01()
{
	Person p1(18);

	Person p2(20);

	p2 = p1;//赋值操作

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

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

int main()
{
	test01();


	system("pause");

	return 0;
}

以上代码程序会崩溃,解释如下图所示

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

//自定义整型
class Person
{
	friend void test01();
public:
	Person(int age)
	{
		m_Age = new int(age);
	}

	~Person()
	{
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
	}
	//重载
	Person& operator= (Person& p)
	{
		//编译器提供浅拷贝
		//m_Age = p.m_Age;

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

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

	int *m_Age;
};


void test01()
{
	Person p1(18);

	Person p2(20);

	p2 = p1;//赋值操作

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

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

int main()
{
	test01();


	system("pause");

	return 0;
}

 

3.5 关系运算符重载

作用:重载关系运算符,可以让两个自定义类型对象进行对比操作

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

//自定义整型
class Person
{
	friend void test01();
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;
	}
	bool operator != (Person& p)
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
			return false;
		}
		return true;
	}

	int m_Age;
	string m_Name;
};


void test01()
{
	Person p1("Tom",18);

	Person p2("Tom",20);

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

 

3.6 函数调用运算符重载

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

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

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

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

//自定义整型
class MyPrint
{
public:
	//重载函数的调用运算符
	void operator()(string text)
	{
		cout << text << endl;
	}

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

//仿函数非常灵活,没有固定的写法
//加法类
class MyAdd
{
public:
	int operator()(int num1, int num2)
	{
		return num1 + num2;
	}
};

void test01()
{
	//重载的()操作符也称为仿函数
	MyPrint myFunc;

	myFunc("hello world");//由于使用起来非常类似于函数调用,因此称为仿函数

	string myFunc02("Hello World");

	
}

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

	//匿名对象
	cout << MyAdd()(100, 100) << endl;
}

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

	system("pause");

	return 0;
}