C++面向对象基础:友元、运算符重载、构造和析构函数

今天开始着重学习C++面向对象的方法和知识点,以便填补自己的知识漏洞

1.友元

1.1友元函数和友元类的简单实现

友元的主要体现又友元函数和友元类,要在类内定义友元,只需在前面加上friend即可。要注意的是,友元虽然是在类内定义,但是它不是类的成员函数,只是类的“朋友”,允许访问类的全部成员(包括私有属性),权限与成员函数相同。

注意:调用友元函数的时候不要使用圆点.操作符,使用->

//友元 
#include <iostream>
#include <cstring>
#include <cstdlib>
using namespace std;

//友元
class Student{
	public:
//		初始化函数
		Student(string name, int age){
			this->stu_name = name;
			this->stu_age = age;
		} 
//		声明友元类 class Teacher 
		friend class Teacher;
//		公共方法work 
		void work(){
			cout << this->stu_name << " is Working.." << endl;
		}
		
	private:
		string stu_name;
		int stu_age;
}; 

class Teacher{
	public:
//		初始化函数
		Teacher(string name, int age){
			this->tea_name = name;
			this->tea_age = age;
		} 
//		定义公共方法老师教学生,需要使用到Stduent类中的私有属性 
		void teach_std(Student student){
			cout << "Teacher " << this->tea_name << " is teaching " << student.stu_name << endl;
		} 
//		定义一个老师类的公共方法是打印学生的姓名和年龄 
		void reply_age(Student *p);
	private:
		string tea_name;
		int tea_age;
};

//具体实现 
void Teacher::reply_age(Student *p){
	cout << "Student " << p->stu_name << " is " << p->stu_age << endl; 
} 

int main (){
//	创建实例
	Student s("小明", 18);
	Teacher t("dzzhyk", 27);
//	因为老师类是学生类的友元类,因此老师类也可以使用学生类中的私有属性
	t.reply_age(&s);
	t.teach_std(s);  
	
//	获取不到学生类中的方法 
//	t.work() 
	
	return 0;
} 

/*
结果:
 
Student 小明 is 18
Teacher dzzhykis teaching 小明

可以看到在声明了友元类之后,在老师类中就可以使用到学生类中的私有属性
注意只能获取到私有的属性 
 
*/ 

1.2友元的作用

  1. 在外部频繁操作对象(即调用成员函数)而引起调用开销增加时,可以通过直接访问对象的成员(而不是调用成员函数)来使得性能明显提高。简单地说,友元可以简化函数定义,从而提高效率。
  2. 用在无法成员话的操作符重载中

2.运算符重载

通常,运算符的分量是基本的数据类型,而当运算分量为对象的时候,其运算符具有新的功能,为运算符定义新功能的过程就成为运算符重载。运算符重载可以改进可读性,使得代码更加简洁。

2.1运算符重载规则

  1. 重载的运算符至少要有一个参数属于“类”的类型
  2. 不能新建一个运算符,只能对现有的运算符(如:++、--、+、-、/、%、>>、<<、关系运算符、赋值运算符、下标运算符[ ]、逗号运算符、new delete 等)进行重载。
  3. 不能改变运算符接受的参数数目,一元运算符重载后还是一元运算符,二元运算符重载后还是二元运算符。
  4. 重载的运算符保持原有的运算优先级
  5. 有的运算符不能重载: :: 、* 、 ?: 、sizeof、typeof
  6. 可以通过成员函数重载运算符,也可以通过友元函数重载运算符
  7. 运算符=、()、[ ] 、-> 和类型转换运算符只能以成员函数重载;运算符>>和<<只能以友元函数重载
  8. C++运算符总是位于表达式中,运算结果是要使用的,因此运算符函数的返回值不可能是void函数

2.2运算符重载的实现

//运算符重载 
#include <iostream>
using namespace std;

//首先定义复数类
class Complex{
	
	public:
//		初始化函数
		Complex(float real=0.0, float virt=0.0){
			this->real = real;
			this->virt = virt;
		} 
		
//		+ 运算符重载 1 - 复数加实数 
		Complex operator + (float num){
			return Complex(this->real + num, this->virt);
		}
		
//		+ 运算符重载 2 - 复数加复数
		Complex operator + (Complex c2){
			return Complex(this->real + c2.real, this->virt + c2.virt);
		}
		
//		输出复数
		void cout_complex(){
			cout << this->real << "+" << this->virt << "i" << endl;
		} 
	private:
		float real;
		float virt;
}; 

int main (){
	
//	实例化
	Complex c1(2, 4);	//2+4i 
	Complex c2(1, 7);	//1+7i
	Complex ans;		//初始为:0+0i 
	
//	先打印一下ans 
	ans.cout_complex();
	ans = c1 + c2;
	ans.cout_complex(); 
	
//	还原ans的值 
	ans = Complex(0.0, 0.0);
	ans = c1 + 89;
	ans.cout_complex(); 
	
//	还原ans的值 
	ans = Complex(0.0, 0.0);
//	设置出c3只有虚部 50 ,为 50i 
	Complex c3(0, 50);
	ans = c1 + c3;
	ans.cout_complex(); 	
	
	return 0;
} 
/*
结果:
0+0i
3+11i
91+4i
2+54i 
*/ 
 

3.构造函数

构造函数就是与类名同名的类成员函数,被声明为public公有函数,具有一般成员函数所有的特性,可以被重载。声明对象时,经常需要初始化变量或者全体成员变量,此时构造函数就在创建对象的时候被自动调用执行,用于为对象分配空间和初始化成员变量的值,并且进行其他可能需要进行的初始化操作

关键词:用于初始化操作

3.1构造函数的使用(重载)

//构造函数和析构函数
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
using namespace std;

class Person{
	public:
//		构造函数 - 即为初始化函数,和类名相同,不加任何修饰
		Person(string name, int age){
			 this->name = name;
			 this->age = age;
			 cout << "Instance " << this->name << " is created" << endl;
		} 
		
//		普通类方法
		void eat(){
			cout << this->name << " is eating" << endl;
		} 
		
	private:
		string name;
		int age;
};

int main (){
//	实例化
	Person p("dzzhyk", 19);
	p.eat();
	return 0;
}
/*
结果: 
Instance dzzhyk is created
dzzhyk is eating
*/

3.2拷贝构造函数

当将一个对象要拷贝给另一个对象的时候,需要调用的函数就叫做拷贝构造函数。拷贝构造函数实际上也是构造函数,具有一般构造函数的所有特性,其名字也与所属类名相同。拷贝构造函数中只有一个参数,即对某个同类对象的引用。拷贝构造函数是重载构造函数的一种重要形式。大致格式如下:

ClassName :: ClassName(ClassName &a){...}

3.3调用拷贝构造函数的三种情况

  1. 用类的一个已经初始化的对象去初始化该类的另一个新构造的对象,即一个对象需要通过另一个对象进行初始化时。
  2. 一个对象以值传递的方式调用形参是类对象的函数,进行形参和实参结合的时候。
  3. 函数返回值是类的对象,函数执行完返回调用者时。

3.4浅拷贝与深拷贝

C++默认的拷贝构造函数是浅拷贝,浅拷贝就是对象的数据成员之间的简单赋值。例如:设计了一个类而没有提供它的拷贝构造函数,当用该类的一个对象给另一个对象赋值的时候所执行的就是浅拷贝。

//浅拷贝
#include <iostream>
#include <cstring>
#include <string>
using namespace std;
class Person{
	public:
		Person(string name, int age){
			this->name = name;
			this->age = age;
		}
		void eat(){
			cout << "eat" << endl;
		}
		void show(){
			cout << "name: " << this->name << " age: " << this->age << endl;
		}
		private:
			string name;
			int age;
};

int main (){
	
	Person p1("dzzhyk", 19);
	Person p2 = p1;
	p2.show();
	return 0;
} 

浅拷贝只拷贝对象成员,而不拷贝相关资源,源对象与拷贝对象公用一个实体,仅仅是引用的变量不同,对其中任何一个对象的更改都会影响到其他的对象

浅拷贝存在的问题:

  • 浅拷贝只是对指针的拷贝,拷贝之后两个指针指向同一个内存空间,这样在对象块结束、程序进行析构的时候,同一份资源连续发生两次析构,即delete同一块内存两次,可能造成程序错误
#include <iostream>
#include <cstring>
#include <string>
using namespace std;

class Person{
	string name;
//	取得name的地址 
	string *p = &name;
	public:
//		初始化函数
		Person(string name){
			this->name = name;
		} 
//		重写析构函数 
		~Person(){
			cout << "析构中: " << this->name << endl;
			delete p;
		}
	private:
		
};

int main (){
	
	Person p1("dzzhyk");
//	这里使用浅拷贝 
	Person p2 = p1; 
	
	return 0;
} 

可以看到只能删除一次

深拷贝:指当前拷贝对象中有对其他资源的引用(可以是指针或者引用)时,拷贝对象要另外开辟一块新的资源,而不是进行单纯赋值

深拷贝不仅对指针进行拷贝,而且对指针指向的内容进行拷贝,经过深拷贝后的指针是指向两个不同地址的指针

深拷贝实例:
 

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

class Person{

	char *name;

	public:
		Person(char *);
		Person(Person &);
		~Person();
};

Person::Person(char *a){
	cout << "创建实例..." << endl;
	name = new char[strlen(a) + 1]; // 深拷贝
	if(name!=0)
	{
		strcpy(name, a);
	}
}

// 自定义拷贝构造函数
Person::Person(Person &p){
	cout << "正在进行深拷贝..." << endl;
	name = new char[strlen(p.name) + 1];
	if(name!=0){
		strcpy(name, p.name);
	}
}

Person::~Person(){
	cout << "正在析构: " << this->name << endl;
	delete name;
}

int main(int argc, char const *argv[])
{
	Person p1("dzzhyk");
	Person p2 = p1;
	
	return 0;
}

结果:可以看到此时的析构结果正常

4.析构函数

析构函数也是类的成员函数,析构函数的名字是在类名的前面加上~,没有参数也没有返回值,不能重载。

4.1调用析构函数的四种情况

  1. 对象生命周期结束而被销毁的时候,在函数体内的对象,当函数执行结束时,该对象所在类的析构函数会被自动调用。
  2. 使用new运算符动态创建的对象,在使用delete运算符释放它的时候,会调用析构函数
  3. delete指向对象的基类类型指针,而其基类虚构函数时虚函数的时候
  4. 对象i是o的成员,当o的析构函数被调用的时候,i的析构函数也被调用。

4.2析构函数的作用

析构函数的作用是在撤销对象占用的内存之前完成一些清理工作,使得这部分内存可以被程序分配给新对象使用。对象的生命期结束之后,程序就自动执行析构函数来完成这些工作。

4.3析构函数实例

// 析构函数
#include <iostream>
#include <string>
using namespace std;

class Person
{
public:
	string name;
	Person(name){
		cout << "创建实例" << endl;
		this->name = name;
	}
	~Person(){
		cout << "析构函数" << endl;
	}
	
};

int main(int argc, char const *argv[])
{
	Person p1("dzzhyk");

	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43826242/article/details/88070749