C++类与对象

一、类

1.定义:

类是具有相同属性和方法的一类对象集合的抽象。它包含数据抽象(数据成员)和行为抽象(即成员函数)。【有哪些人,干哪些事】定义类的过程就是对问题进行抽象和封装的过程。

补充OOP特征:

【封装、继承、多态】

1.封装:

面向过程编程:数据和处理数据的函数是分开的,通过参数传递数据。(如C中的main函数)

面向对象编程:一切都是对象,类把对象的数据和操作数据的方法作为一个整体考虑。(封装

2.(公有)继承:

继承了某对象将拥有该对象的公有属性和方法,并且还可以自己拓展添加自己的属性和方法。

代码实现
#include <iostream>
using namespace std;
class Entity
{
public:
	float X, Y;//分别占4个字节
	void Move(float xa,float ya)
	{
		X += xa;
		Y += ya;
	}
};
class Player :public Entity//inheritance
{
public:
	const char* Name;//占64/8=8个字节
	void PrintName()
	{
		cout << Name << endl;
	}
}; 
int main()
{
	cout << sizeof(Player) << endl;//一共是4+4+8=16个字节
	Player player;
	player.Move(5, 6);
	player.X = 2;
	return 0;
}
多态性

“多态(英语:polymorphism),是指计算机程序运行时,相同的消息可能会送给多个不同的类别之对象,而系统可依据对象所属类别,引发对应类别的方法,而有不同的行为。简单来说,所谓多态意指相同的消息给予不同的对象会引发不同的动作称之。”其实更简单地来说,就是“在用父类指针调用函数时,实际调用的是指针指向的实际类型(子类)的成员函数”。多态性使得程序调用的函数是在运行时动态确定的,而不是在编译时静态确定的。

简单的说就是能够去重写继承对象的方法,被利用的最多的例子莫过于ToString()方法了,我们查看语言的类库就可以知道,其实可以知道每个类(对象)都有ToString()方法,作用通常是输出对象的字符串信息。

虚函数 (virtual function) 指可以被子类继承和覆盖的函数。

基类声明成员函数为虚函数的方法:

virtual [类型] 函数名([参数表列])

2.类的一般形式

/*class Name
{
    public://公有数据和函数(可以通过对象来访问)

    protected://保护数据和函数(如果没有继承机制, 则其性质同private)

    private://私有数据和函数(被隐藏的,外界看不见)

}*/
#include <iostream>
using namespace std;
class Point
{
private://外界看不到我,也无法调用我
	int x, y;
public://外界可以看见,并且能够调用,而且我们内部(即成员函数或友元函数)互相可以看见
	void set1(int a, int b)
	{
		x = a;
		y = b;
	}
	void print()
	{
		cout << "x=" << x << endl;
		cout << "y=" << y << endl;
	}
};

int main()
{
	Point p1;
	//p1.x=1;//error
	//p1.y=2;//error
	p1.set1(1, 2);
	p1.print();
	return 0;
}

注意:1.私有成员只有该类的成员函数或者友元函数可以访问。

保护成员也不能通过对象访问,但是可以被该类或派生类的成员函数访问。【儿子请求访问】

公有成员定义了类的外部接口。

2.类的外部一般不直接访问(读和写)对象成员,而是用成员函数。(面向对象的思想)

3.结构体成员缺省为public,类的成员缺省为private(作用是保留系统隐私)。

4.类一般不用memset()来清空成员变量,而应该写一个专用于清空成员变量的成员函数

对类和对象用sizeof运算意义不大,一般不用。

3.类的实现

成员函数的实现,可以放在类体内,也可以放在外面,但是必须在类体内声明

放在类体内定义的函数被默认为内联函数,而放在类体外定义的函数是一般函数,如果要定义成内联函数需要在前面加上关键字inline(不然如何区分普通函数和成员函数呢),如:

public://外界可以看见,并且能够调用,而且我们内部(即成员函数或友元函数)互相可以看见
	void set1(int a, int b)
	{
		x = a;
		y = b;
	}
	void print();//体内声明
};
inline Point::print()//体外实现后,需要加上inline,其中::表示作用域
{
		cout << "x=" << x << endl;
		cout << "y=" << y << endl;
};
int main();

二、对象

1.对象的声明

2.对象的访问

【原点式】对象名.成员名  或   (*指向对象的指针).成员名

【指针式】对象指针->成员名  或    (&对象名)->成员名

三、类的界面与实现

3.1 定义

类的界面:是指将类封装好,通常放在.h头文件内

类的实现:通常是将类的成员函数实现过程单独放在.cpp文件内,并且注意要加上头文件<类名.h>

3.2 (普通)构造函数

(1)语法:类名(){ }

(2)访问权限必须是public,就是必须放在公有区。

(3)没有返回值,也不需写void。

(4)构造函数可以有参数,也可以有重载,可以有默认参数。

(5)构造函数实际上就是在创建类时,默认在所有动作之前自动对变量进行初始化为空的一个动作,防止乱码。且只会自动调用一次,不能手工调用

(6)意义:不需要的话,自定义它干嘛,只要你写出这个结构,系统自动为你初始化

3.3  析构函数

(1)语法:~类名(){}

(2)访问权限public

(3)没有返回值,也不需写void。

(4)没有参数,不能重载!

(5)销毁对象前只会自动调用一次,但是可以手工调用(但是基本不需要该功能)

(6)在构造函数名后面加括号和参数不是调用构造函数,而是创建匿名对象。

如果类中成员有指针,若没有使用到构造函数,并且在其他地方没有为指针分配内存,那么该指针就是野指针。或者释放内存的代码放在析构函数中,如果没有用到析构函数导致指针成为野指针,可能会导致系统崩溃,所以一定要保证使用到构造/析构函数。

注意:

1.如果不编译构造/析构函数,编译器会自动提供空实现的构造/析构函数【也叫默认构造函数】,也只能调用空白参数的构造/析构函数。

2.创建对象时,如果重载了构造函数,编译器会根据实参来匹配相应的构造函数。

『方法重载主要好处就是不用为了对不同的参数类型或参数个数,而写多个函数。多个函数用同一个名字,但参数表,即参数的个数或(和)数据类型可以不同,调用的时候,虽然方法名字相同,但根据参数表可以自动调用对应的函数。』

3.不建议在构造/析构函数中写太多代码,可以使用成员变量减少工作量。

如果调用的无参构造函数,使用类名即可【没有括号】;调用有参的构造函数一定要加上实参!

在创建对象时不用加空的圆括号,编译器会误认为是声明函数。在构造函数后加括号和参数不是调用构造函数,而是创建匿名对象

4.注意调用构造函数时,调用函数参数和其定义的参数类型必须对应,否则编译器找不到调哪个。

Point P1();//实际是声明一个返回值类型为Point的函数,类似于int girl(),未创建对象。

Point ;//无效操作,没有函数名,相当于光写了一个int在这。

Point P1;//创建一个类名为P1的Point类,并调用Point中的无参构造函数(默认构造函数)

Point("Steve")//调用了Point中有一个string参数的构造函数。

#pragma once
#include "iostream"//不用加.h
#include <string>
using namespace std;
class student//创建一个学生的类,用于记录某人的序号、名字和三科成绩
{
public:
	int m_num;
	string m_name;
	float m_score[3];
	student();//默认的无参构造函数,类外定义
	student(int age, string name, float score[],int n);//类内声明带参数的构造函数,类外定义,后面定义后,绿色波浪线会消失
	//~student()//析构函数
	//{
	//	cout << "destroy!" << endl;
	//}
	void display();
};
inline student::student()
{
	m_num = 0;
	m_name = "null";
	for (int i = 0; i < 3; i++)
	{
		m_score[i] = 0;
	}
	cout << "使用了默认构造函数" << endl;
}
inline student::student(int age, string name, float score[],int n)
{
	m_num = age;
	m_name = name;
	for (int i = 0; i < n; i++)
		m_score[i] = score[i];
	cout << "使用了带参数的构造函数" << endl;
}
inline void student::display()
{
	cout << m_num << '\t' << m_name << '\t';
	for (int i = 0; i < sizeof(m_score)/sizeof(m_score[0]); i++)
		cout << m_score[i] << '\t';
	cout << endl;
}
int main()
{
	float s[3] = { 93.2f,89.0f,97.2f };
	int len = sizeof(s) / sizeof(s[0]);
	student s1;
	student s2(100, "Alan", s, len);
	//数组的个数需要以单独的变量传入
	s1.display();
	s2.display();
	return 0;
}

 PS:匿名对象和普通对象的区别:

1,形式不同:

创建一个普通对象:   Userp = new User();     ||        创建一个匿名对象:new User();

2.内存分布不同:匿名对象创建的对象只存在于堆中;非匿名对象创建对象时的对象虽然也在堆中,但其类变量却在栈中,栈中的类变量通过创建变量的内存地址来指向相应的对象。

int main()
{
	student s1;
	student();//创建一个student类的匿名对象,执行完该行代码后,对象消失,被析构函数运行。
	student s2 = student();//创建一个student类的匿名对象,赋值给s2,则其周期变为s2的周期
	student* s3 = new student;//创建一个student类的变量的空间,由g3这个指针指向
	student* s4 = new student();//创建一个student类匿名对象的“空间”
	delete s3;
	delete s4;
	return 0;
}

运行结果

Created constructor!//s1的构造函数
Created constructor!
Destroyed!   //匿名对象立即被析构函数清除
Created constructor!//s2
Created constructor!//s3
Created constructor!//s4
Destroyed!   //s1被清除
Destroyed!   //s2被清除
Destroyed!   //s3被清除
Destroyed!   //s4被清除

3.4 拷贝构造函数

3.4.1 默认拷贝构造函数

(1)意义:

用已经存在的对象创建新的对象

(2)语法:

用一个已经存在的对象创建新对象的语法:

类名 新对象名(已存在的对象名)

类名 新对象名=已经存在的对象名

CGirl g1;//普通构造函数

CGirl g2(g1);

//CGirl g2=g1;

#pragma once
#include "iostream"
using namespace std;
class student
{
public:
	int m_num;
	string m_name;
	void display();
	student();
	~student()
	{
		cout << "Destroyed!" << endl;
	}
};
student::student()
{
	m_num = 0;
	m_name = "null";
	cout << "Created constructor!" << endl;
}
void student::display()
{
	cout << m_num << '\t' << m_name << '\t';
	cout << endl;
}
int main()
{
	student s1;
	s1.m_num = 2;
	s1.m_name = "Alan";
	student s2(s1);//将s1复制给s2,此时不会调用(普通)构造函数
//而是调用系统默认的拷贝构造函数。
	//student s2=s1;
	s2.display();//注意这里不能直接写display(),而是要先写变量名
	return 0;
}

运行结果:

Created constructor! //创建s1时,调用了默认构造函数
2       Alan                    //显示s2的类成员
Destroyed!                   //s1被销毁时,调用了一次析构函数
Destroyed!                   //同上

 3.4.2 拷贝构造函数

1.定义:

        一般用于变量初始化和拷贝相关代码。

2.自定义拷贝构造函数后,再次复制已经存在的对象时,不会调用(普通)构造函数,而是调用拷贝构造函数。
3.语法:

(1)必须带有类本身的常引用,否则就是普通构造函数。【小括号里变成被复制的实参对象】

类名(...const 类名& 对象名...){......}

#include <iostream>
using namespace std;
class CGirl
{
public:
	string m_name;
	int m_age;
	//没有参数的普通构造函数
	CGirl()
	{
		m_name.clear(); m_age = 0; cout << "Constructed!\n";
	}
	//没有重载的拷贝构造函数(默认拷贝构造函数)
	CGirl(const CGirl& gg)
	{
		m_name = "beautiful " + gg.m_name; m_age =gg.m_age - 1; cout << "Copy constructed!\n";
	}
	//析构函数
	~CGirl()
	{
		cout << "Destroyed!\n";
	}
	void show()
	{
		cout << "name: " << m_name << ",age: " << m_age << endl;
	}
};
int main()
{
	CGirl g1;
	g1.m_name = "Alice"; g1.m_age = 23;
	//CGirl g2 = g1;
	CGirl g2(g1);
	g2.show();
	return 0;
}

(2)可以加参数重载,若类中加参数重载了拷贝构造函数而没有定义默认拷贝构造函数,编译器会提供默认的拷贝构造函数并使用:【只拷贝,也没有显示日志什么的】

//重载后的拷贝构造函数
CGirl(const CGirl& gg,int some)
{
	m_name = "beautiful " + gg.m_name; m_age =gg.m_age - some;
    cout << "Copy constructed!\n";
}
//使用默认构造函数,成功!而且显示使用的默认拷贝构造函数
int main()
{
	CGirl g1;
	g1.m_name = "Alice"; g1.m_age = 23;
	CGirl g2(g1);
	g2.show();
	return 0;
}

(3)以值传递的方式调用函数时,若实参为对象,也会调用拷贝构造函数。【如func()函数】

void func(CGirl g)
{
	g.show();
}//定义在class外面
int main()
{
    CGirl g1;
    g1.m_name = "Alice"; g1.m_age = 23;
    func(g1);//这里也会用到拷贝构造函数
    return 0;
}

(4)函数以值的方式返回对象时,可能会调用拷贝构造函数【有的编译器不会,如新版CPP和Linux,会直接使用之前的对象的地址,只是换个函数名继续用】

CGirl func()
{
	CGirl gg;
	gg.m_name = "Alice"; gg.m_age = 23;
	cout << "The address of gg is" << &gg << endl;
	return gg;
}
int main()
{
	CGirl g = func();
	g.show();
	cout << "The address of g is" << &g << endl;
	return 0;
}
4.浅拷贝/深拷贝

(1)浅拷贝

        在使用拷贝构造函数时,只复制指向某个对象的指针,而不是复制对象本身【值】,新旧对象还是共享同一块内存

危害1:改变一个对象的指针内存,会影响到另一个指针的内存。

危害2:在系统自动调用析构函数时,销毁了一个对象的指针,另一个指针也就成了野指针,无法再被调用。

这两种情况都是我们不愿意看到的。可能引起堆区数据混乱甚至系统崩溃。

#include <iostream>
using namespace std;
class CGirl
{
public:
	string m_name;
	int m_age;
	int* m_ptr;//其他都只使用栈内存,而这里用了指针,使用堆内存!
	//【但仍然是空指针,没有指向任何地址!不能解引用】
	//没有参数的普通构造函数
	CGirl()
	{
		m_name.clear(); m_age = 0; 
		m_ptr = NULL; 
		cout << "Constructed!\n";
	}
	//没有重载的拷贝构造函数(默认拷贝构造函数)
	CGirl(const CGirl& gg)
	{
		m_name = gg.m_name; m_age =gg.m_age ; 
		m_ptr = gg.m_ptr;//问题在这,直接使用同一块内存!!!
		cout << "Copy constructed!\n";
	}
	//析构函数
	~CGirl()
	{
		//if (m_ptr == NULL)
		//{
		//	delete m_ptr;//释放内存
		//	m_ptr = NULL;//不要指向原来的地址栏,防止你成为野指针。
		//}
		delete m_ptr;
		m_ptr = NULL;//这句让删除后变成空指针而不是野指针了,空指针可以重复释放,它的地址指向虚无,不会被别人拿来用,所以会安全很多!
		cout << "Destroyed!\n";
	}
	void show()
	{
		cout << "name: " << m_name << ",age: " << m_age <<
			" m_ptr= " << m_ptr << " *m_ptr= " << *m_ptr << endl;
	}
};
int main()
{
	CGirl g1;
	g1.m_name = "Alice"; g1.m_age = 23;
	g1.m_ptr = new int(3);//给g1中的指针分配后,才能解引用
	g1.show();
	CGirl g2(g1);
	*g2.m_ptr = 8;//这里g2改变了指针的内存,g1中的值也会改变
	g1.show();
	g2.show();
	//调用析构函数销毁其中一个后,另一个就成了野指针,无法再被操作(释放内存)
	return 0;
}

 (2)深拷贝:

        

在使用拷贝构造函数进行拷贝已有对象时,重新分配一块内存给新对象,让大家各自操作自己的指针和内存。只需在拷贝构造函数中改动指针部分即可:

CGirl(const CGirl& gg)
{
	m_name = gg.m_name; m_age = gg.m_age;
	m_ptr = new int;
	//*m_ptr = *gg.m_ptr;
	memcpy(m_ptr, gg.m_ptr, sizeof(int));
	cout << "Copy constructed!\n";
}

3.5 初始化列表

1.意义:

        从概念上来讲,构造函数的执行可以分成两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段,因此在初始化时,将基本工作做好。

        有构造函数了为啥还需要初始化列表?直接带入更快捷!【区别于先创对象后传入

2.初始化的形式:

类名(int a,int b):m_A(a),m_B(b)

{

}

  【带入初值时,这初值可以是用系统的(默认无参数),也可以自定义(带参数,甚至可以带表达式,用加号连接)!】

ps:也可以在类外实现【类名::类名():】第二个类名表示初始化

#include <iostream>
using namespace std;
class CGirl {
public:
	string m_name;
	int m_age;
	CGirl() :m_name("ALice"), m_age(23)
	{
		cout << "constructed by Initial line!" << endl;
	}
	CGirl(string name, int age) :m_name("strong "+name), m_age(age+6)
	{
		cout << "Constructed by Initial line with parameter!" << endl;;
	}
	void show()
	{
		cout << "name: " << m_name << " age: " << m_age << endl;
	}
};
int main()
{
	CGirl g1;//默认就使用无参数的初始化列表
	g1.show();
	CGirl g2("Bob", 12);//有参数就使用对应有参数的初始化列表
	g2.show();
	return 0;
}

3.不使用初始化列表的代码实现:

        初始化列表与赋值有本质区别,若成员是类,使用初始化列表调用的是成员类的拷贝构造函数,而赋值则是先构造成员类的对象(会调用成员类的普通构造函数),然后再赋值。

用类创建对象,先初始化构造函数的形参对象(boy),然后再初始化类的成员(CGirl.m_boy)

#include <iostream>
using namespace std;
class CBoy
{
public:
	string m_xm;
	CBoy()	{m_xm.clear(); cout << "Constructed CBoy()!\n" << endl;}
	CBoy(string xm) { m_xm = xm; cout << "Constructed CBoy(string xm)!\n"; }
	CBoy(const CBoy& bb) { m_xm = bb.m_xm; cout<<"Constructed CBoy(const CBoy & bb)!\n"; }
	void show()
	{
		cout << "name: " << m_xm << endl;
	}
};
class CGirl {
public:
	string m_name;
	int m_age;
	CBoy m_boy;
	CGirl() 
	{
		cout << "constructed by Initial line!\n" << endl;
	}
	CGirl(string name, int age,CBoy boy) :m_name(name), m_age(age)
	{//CGirl的name、age按照初始化的方式传入
		m_boy.m_xm = boy.m_xm;//带入实值boy,让Girl里的m_boy被赋值
		cout << "Constructed CGirl(name,age,boy)!\n" << endl;
	}
	void show()
	{
		cout << "name: " << m_name << " age: " << m_age
			<<" boy: " <<m_boy.m_xm<< endl;
	}
};
int main()
{
	CBoy boy("王启舟");//声明一个CBoy类的boy,赋值采用 1.自带string的构造函数
	//将boy作为实参带入之前,需要先调用 2.拷贝构造函数传入CGirl
    //此外,超女类的m_boy也是类,需要使用 3.无参数的普通构造函数
	CGirl girl("小豆",18,boy);//最后带入实参,使用 4.带参数的拷贝构造函数。
	girl.show();
	return 0;
}

 ps:如果往CGirl传入实参boy时采用的引用符号,就不需要拷贝构造函数了。

【但是CGirl里的m_boy创建时还是需要普通构造函数!】

4.使用初始化列表的代码实现:

        初始化列表是利用当前变量直接赋值一步到位,效率有所提升。代码实现如下:

CGirl(string name, int age,const CBoy &boy) :m_name(name), m_age(age),m_boy(boy)
{
	cout << "Constructed CGirl(name,age,boy)!\n" << endl;
}

由此可知,使用初始化后,减少了CGirl自己用普通构造函数的过程,而是直接利用拷贝构造函数来带入即可。【初始化和赋值变成了一步操作】

5.注意

        (1)若类的成员为常量或者引用,必须使用初始化列表【因为它俩只能在定义时初始化】

比如class CGirl{const int age,CBoy &m_boy},此时age和bb必须在类里使用初始化列表。

        (2)若成员没有默认构造函数,则必须使用初始化列表。

 3.6 const 修饰成员函数

#include <iostream>
using namespace std;
class Person {
public:
	int m_A = 0;
	mutable int m_B = 0;//mutable变量不论在哪都能被修改
	void show()const//常函数下的常变量不允许被修改
	{
		//this->m_A = 100;
		this->m_B = 200;
		cout << m_A << endl;
		cout << m_B << endl;
	}
};
int main()
{
	Person p;//实例化
	p.show();
const
	return 0;
}

(1)被const修饰的函数或者成员无法修改值。

(2)mutable可以突破const限制,比如上式中show后面虽然有const,但是m_name变量有mutable,所以可以更改mutable的值。

(3)非const成员函数可以调用const成员函数和非const成员函数。【动态的谁都能调用】

(4)常对象和常函数只能调用常函数,连普通的成员函数也不能调用【静态的只能调用静态的,因为普通的成员函数是可以修改属性的】

3.7 this指针

1.意义:若类中的成员函数涉及多个对象,此时需要使用多个指针。

this指针的本质是指针常量,指针的指向是不可以修改的。【指向当前的类】

但是指针指向的值是可以更改的,如果想要变量值固定不变,需要在变量前加const/函数体后面加const

2.代码实现:

#include <iostream>
using namespace std;
class CGirl {
public:
	mutable string m_name;
	int m_age;
	
	CGirl(const string& name, int age)
	{
		m_name = name; m_age = age;
	}
	const CGirl& pk(const CGirl& g)const
	{
		if (g.m_age > m_age)
			return g;
		else return *this;
	}
	void show()const
	{
		cout << "I'm " << m_name << " ,the oldest girl!" << endl;
	}
};
//const CGirl& pk(const CGirl& gg1, const CGirl& gg2)
//{
//	if (gg1.m_age < gg2.m_age)
//		return gg2;
//	else return gg1;
//}//C语言的写法,单独设计一个函数->C++:一切皆是对象,将其变为成员函数
int main()
{	//两个对象的比较:
	//CGirl g1("Alice", 4), g2("Adare", 10);
	//const CGirl& g3 = g2.pk(g1);
	将g2和g1比较,结果附给p3;
	//g3.show();

	//多个对象的比较:
	CGirl g1("Alice", 4), g2("Adare", 10),g3("Justin",13);
	const CGirl& g = g3.pk(g2).pk(g1);
	g.show();
	return 0;
}

3.作用

       每个成员函数(包括构造函数和析构函数)都有一个this指针,除了可以用它访问调用者对象的成员,还可以解决成员变量名和函数形参名相同的问题。

class Entity
{
    int aa;
    void func(int aa)
    {
        this->aa=aa;
    }
}

3.8 静态成员

1.组成:类的静态成员包括静态成员变量和静态成员函数。

2.特点:多个对象之间的数据共享【同一份!】,比全局变量更具安全性。并不属于某一份对象。

3.如何定义:【类内声明,类外初始化】静态成员变量不会在创建对象时初始化,必须在程序的全局区范围解析运算符::来初始化

4.如何访问:由于编译阶段就分配内存。故:(1)可以通过类名来访问.【类名::静态成员】(2)也可以通过创建对象访问。

5.访问权限:公有权限下,类外可以访问。而私有权限下,类外无法访问

#include <iostream>
using namespace std;
class CGirl {
public:
	mutable string m_name;
	static int m_age;
	CGirl(const string& name, int age)
	{
		m_name = name; m_age = age;
	}
	void ShowName()
	{
		cout << "name: " << m_name << endl;
	}
	static void ShowAge()
	{
		cout << "age:" << m_age << endl;
	}
};
int CGirl::m_age=8;//初始化类的静态成员变量
int main()
{
	CGirl::ShowAge();
	cout << "CGirl::m_age:" << CGirl::m_age << endl;
	//不是使用类的成员变量函数ShowAge,而是不创建对象直接显示输出
	CGirl g1("Alice", 23);
	CGirl g2("Bob", 24);
	CGirl g3("Cab", 25);
	g1.ShowAge();
	g1.ShowName();
	g2.ShowAge();
	g2.ShowName();
	g3.ShowAge();
	g3.ShowName();
	return 0;
}

运行结果:打印静态变量初始值,再被赋值、赋值、赋值,最后age停在了25,再打印。

 3.9 友元

1.作用:

        让类外的一些函数或者类可以有权限访问私有属性。

2.关键字:friend

3.实现方法:

        全局函数做友元/类做友元/成员函数做友元。

(1)全局函数做友元
#include <iostream>
using namespace std;
#include <string>
class Building 
{
	friend void goodGay(Building* building);
public:
	Building() //初始化
	{
		this->m_SettingRoom = "客厅";
		this->m_BedRoom = "卧室";
	}
public:
	string m_SettingRoom;
private:
	string m_BedRoom;

};
void goodGay(Building* building)//这里要填地址,主函数中就需要用取址符
{
	cout << "好基友正在访问: " << building->m_SettingRoom << endl;
	cout << "好基友正在访问: " << building->m_BedRoom << endl;
}
void test01()
{
	Building building;
	goodGay(&building);
}
int main()
{
	test01();
	return 0;
}
(2)类做友元
#include <iostream>
using namespace std;
#include <string>
class Building 
{
	friend class GoodGay;//整个类都可以进入private处
public:
	Building();//可以用初始化列表,也可以普通构造函数
public:
	string m_SettingRoom;
private:
	string m_BedRoom;
};
//Building::Building() :m_SettingRoom("客厅"), m_BedRoom("卧室") {};//这里使用了类外初始化链表,也可以类内实现
Building::Building() 
{
	this->m_SettingRoom = "客厅";
	this->m_BedRoom = "卧室";
};
class GoodGay 
{
public:
	GoodGay();//普通构造函数
	void visit();//visit知道building指针,即地址,但是能否进入得看GoodGay是否为building的友元
private:
	Building* building;
};
GoodGay::GoodGay()
{
	//创建建筑对象给building
	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();
	return 0;
}
(3)成员函数做友元
#include <iostream>
using namespace std;
#include <string>
class GoodGay
{
public:
	GoodGay();//普通构造函数
	void visit01();//让GoodGay下的visit函数可以访问Building里的私有空间
	void visit02();
private:
	Building* building;
};
class Building
{
	friend void GoodGay::visit01();
public:
	Building();//可以用普通构造函数,也可以初始化列表
public:
	string m_SettingRoom;
private:
	string m_BedRoom;
};
//Building::Building() :m_SettingRoom("客厅"), m_BedRoom("卧室") {};//这里使用了类外初始化链表,也可以类内实现
Building::Building()
{
	this->m_SettingRoom = "客厅";
	this->m_BedRoom = "卧室";
};

GoodGay::GoodGay()//构造函数没有返回值
{
	//创建建筑对象给building
	building = new Building;
}
void GoodGay::visit01()
{
	cout << "好基友正在访问:" << building->m_SettingRoom << endl;
	cout << "好基友正在访问:" << building->m_BedRoom << endl;
}
void GoodGay::visit02()
{
	cout << "好基友正在访问:" << building->m_SettingRoom << endl;
	//cout << "好基友正在访问:" << building->m_BedRoom << endl;
}
void test01()
{
	GoodGay gg;
	gg.visit01();
	gg.visit02();
}
int main()
{
	test01();
	return 0;
}

1.无法运行

2.Building* building;的意义是创建新房子,并把地址给GoodGay!

3.10 运算符重载、函数重载【自定义】

 (1)加号运算符重载

对于自定义的数据类型,编译器不知道怎么算,所以这里要用到运算符重载。如两个Person类相加(p1+p2)

办法:要么使用成员函数重载,要么用全局函数

a.通过成员函数重载【只需再传入一个新变量】

#include <iostream>
using namespace std;
#include <string>
class Person
{
public:
	int m_A;
	int m_B;
	Person& operator+(Person& p)//operator+是系统取的通用名称,一个返回值为Person的重载加法!名叫【operator+】
	{
		Person temp;
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_B + p.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.operator+(p2);
	Person p3 = p1 + p2;//这里实际上是上一行的简化写法

	cout << "p3.m_A=" << p3.m_A << endl;
	cout << "p3.m_B=" << p3.m_B << endl;
}
int main()
{
	test01();
	return 0;
}

b.全局函数重载+号【需要传入两个变量】

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)//实质上就是调用了全局函数operator+

 注意:

对于内置的数据类型(如int,float等)表达式的运算符是无法改变的。

(2)左移运算符重载

无法用成员函数:因为p.operator(cout),此时p永远在左侧,无法实现cout<<p【p在右侧】

所以采用全局函数【operator<<】来实现

只要<<检测到了输出一个类,就会调用这个函数

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;
};
void test()
{
	Person p(10, 10);
	cout << p << " Helloworld " << endl;
}
ostream &operator<< (ostream& cout, Person& p)//这里把"<<"变成"cout<<p",并返回cout的地址
//全局变量cout不论是输入还是返回都需要加&
{
	cout << "m_A= " << p.m_A << " m_B= " << p.m_B;
	return cout;
}
int main()
{
	test();
	return 0;
}

 ps:this

#include "iostream"
using namespace std;
class Person
{
	public:
	Person(int age);
	Person& PersonAddAge(Person& p)
	{ this->age += p.age;
	return *this; }
	void show()
	{
		cout << age << endl;
	}
private:
	int age;
};
Person::Person(int age)
{
	this->age = age;
}
int main()
{
	Person p1(10);
	Person p2 = p1.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
	p1.show();
	p2.show();
	return 0;
}

运算结果 80,80

(3)复数运算符重载

#include <iostream>
using namespace std;

class complex{
public:
    complex(double real = 0.0, double imag = 0.0): m_real(real), m_imag(imag){ };
public:
    friend complex operator+(const complex & A, const complex & B);
    friend complex operator-(const complex & A, const complex & B);
    friend complex operator*(const complex & A, const complex & B);
    friend complex operator/(const complex & A, const complex & B);
    friend istream & operator>>(istream & in, complex & A);
    friend ostream & operator<<(ostream & out, complex & A);
private:
    double m_real;  //实部
    double m_imag;  //虚部
};

//重载加法运算符
complex operator+(const complex & A, const complex &B){
    complex C;
    C.m_real = A.m_real + B.m_real;
    C.m_imag = A.m_imag + B.m_imag;
    return C;
}

//重载减法运算符
complex operator-(const complex & A, const complex &B){
    complex C;
    C.m_real = A.m_real - B.m_real;
    C.m_imag = A.m_imag - B.m_imag;
    return C;
}

//重载乘法运算符
complex operator*(const complex & A, const complex &B){
    complex C;
    C.m_real = A.m_real * B.m_real - A.m_imag * B.m_imag;
    C.m_imag = A.m_imag * B.m_real + A.m_real * B.m_imag;
    return C;
}

//重载除法运算符
complex operator/(const complex & A, const complex & B){
    complex C;
    double square = A.m_real * A.m_real + A.m_imag * A.m_imag;
    C.m_real = (A.m_real * B.m_real + A.m_imag * B.m_imag)/square;
    C.m_imag = (A.m_imag * B.m_real - A.m_real * B.m_imag)/square;
    return C;
}

//重载输入运算符
istream & operator>>(istream & in, complex & A){
    in >> A.m_real >> A.m_imag;
    return in;
}

//重载输出运算符
ostream & operator<<(ostream & out, complex & A){
    out << A.m_real <<" + "<< A.m_imag <<" i ";;
    return out;
}

int main(){
    complex c1, c2, c3;
    cin>>c1>>c2;
 
    c3 = c1 + c2;
    cout<<"c1 + c2 = "<<c3<<endl;

    c3 = c1 - c2;
    cout<<"c1 - c2 = "<<c3<<endl;

    c3 = c1 * c2;
    cout<<"c1 * c2 = "<<c3<<endl;

    c3 = c1 / c2;
    cout<<"c1 / c2 = "<<c3<<endl;

    return 0;
}

(4)递增运算符重载

class MyInteger 
{
	friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
	MyInteger()
	{
		m_Num = 0;
	}
	//前置递增重载
	MyInteger& operator++()
	{
		this->m_Num++;
		//也可以直接写成m_Num++
		return *this;
	}
	//后置递增重载,先记录当前值,再++,最后输出记录的值【所以这里返回的值,而不是*this】
	//	MyInteger operator++(int)  int用于区分前置和后置的函数重载
	MyInteger operator++(int)
	{
		MyInteger temp = *this;
		m_Num++;
		return temp;
	}
private:
	int m_Num;
};
void test01()
{
	MyInteger myint;
	cout << ++myint << endl;//myint=1,输出1,
	cout << myint << endl;//输出myint=1
	cout << myint++ << endl;//输出1,myint=2
	cout << myint << endl; //输出myint = 2
}
//输出MyInteger里的整型数据,注意要用友元
ostream& operator<<(ostream& cout,MyInteger myint)
{
	cout << myint.m_Num << endl;
	return cout;
}
int main()
{
	test01();
	return 0;
}

1.为啥不要参数,参数需要与否如何定义?->这里主要是区分前置和后置递增。

2.返回值为啥是 *this不是this?->返回类本身,而不是返回指向它的指针。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值