当前位置 : 主页 > 编程语言 > c语言 >

c++核心编程—类和对象2

来源:互联网 收集:自由互联 发布时间:2023-09-06
一、运算符重载 运算符重载:对已有的运算符重新定义,赋予其另一种功能,以适应不同的数据类型 1、加号运算符重载 作用:实现两个自定义数据类型相加的运算 方法1、成员函数重

一、运算符重载

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


1、加号运算符重载

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


方法1、成员函数重载

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

void test() {

	Person p1;
	p1.m_a = 10;
	p1.m_b = 20;

	Person p2;
	p2.m_a = 10;
	p2.m_b = 20;

	Person p3 = p1 + p2;	//等价于p1.operator+(p2)

	cout << "p3.m_a = " << p3.m_a << endl;
	cout << "p3.m_b = " << p3.m_b << endl;

}

int main() {
	test();

	system("pause");
	return 0;
}

c++核心编程—类和对象2_文件操作

方法2、全局函数重载

//定义类
class Person {
public:

	int m_a;
	int m_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;
}

void test() {

	Person p1;
	p1.m_a = 10;
	p1.m_b = 20;

	Person p2;
	p2.m_a = 10;
	p2.m_b = 20;

	Person p3 = p1 + p2;	//等价于p1.operator+(p2)

	cout << "p3.m_a = " << p3.m_a << endl;
	cout << "p3.m_b = " << p3.m_b << endl;

}

int main() {
	test();

	system("pause");
	return 0;
}

c++核心编程—类和对象2_c++_02

总结:

1、成员函数重载本质调用:Person p3 = p1.operator+(p2)

2、全局函数重载本质调用:Person p3 = operator(p1,p2)

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


2、左移运算符重载

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

c++核心编程—类和对象2_运算符重载_03

所以, 只能利用全局函数重载左移运算符


//左移重载
class Person {
public:

	int m_a;
	int m_b;
};

//全局函数重载
ostream& operator<<(ostream& cout, Person& p) {
	cout <<"p.m_a = "<<p.m_a <<"p.m_a = "<<p.m_b << endl;;
	return cout;
}

//测试
void test() {

	Person p1;
	p1.m_a = 10;
	p1.m_b = 20;
	
	cout << p1 << endl;
}

int main() {
	test();

	system("pause");
	return 0;
}

c++核心编程—类和对象2_多态_04

扩展:如果我们需要打印的成员属性是私有的呢?全局函数无法访问,怎么重载呢?

答:对全局函数使用友元

//左移重载
class Person {
public:
	//声明友元
	friend ostream& operator<<(ostream& cout, Person& p);

	//构造函数赋初值
	Person(int a,int b) {
		m_a = a;
		m_b = b;
	}


private:
	int m_a;
	int m_b;
};

//全局函数重载
ostream& operator<<(ostream& cout, Person& p) {
	cout <<"p.m_a = "<<p.m_a <<" p.m_a = "<<p.m_b << endl;;
	return cout;
}

//测试
void test() {

	Person p1(10,20);//调用有参构造函数
	cout << p1 << endl;

}

int main() {
	test();

	system("pause");
	return 0;
}

c++核心编程—类和对象2_文件操作_05

3、递增运算符重载

作用:通过重载,实现自己的数据类型递增


前置++:

//++运算符重载
class Person {

public:
	//前置++
	Person& operator++() {
		++m_a;
		return *this;
	}

	int m_a = 0;
};


ostream& operator<<(ostream& cout,Person p) {
	cout << p.m_a << endl;
	return cout;
}

//测试
void test01() {
	Person p;
	p.m_a = 100;
	cout << ++p << endl; //前置++
}

int main() {

	test01();

	system("pause");
	return 0;
}

c++核心编程—类和对象2_文件操作_06


后置++:

重载后置递增,需要用int代表占位参数,用于区分前置和后置递增

//++运算符重载
class Person {

public:

	//后置++
	Person operator++(int) {
		Person temp = *this;	//记录操作前的结果
		m_a++;
		return temp;
	}

	int m_a = 0;
};


ostream& operator<<(ostream& cout,Person p) {
	cout << p.m_a << endl;
	return cout;
}

//测试
void test02() {
	Person p1;
	p1.m_a = 100;
	cout << "后置++:" << p1++ << endl;//后置++ 
	cout << "后置++:" << p1 << endl;
}

int main() {
	test02();

	system("pause");
	return 0;
}

c++核心编程—类和对象2_继承_07

总结:

1、前置++,返回对象自身(*this),且是用引用的方式返回

2、后置++,需要记录操作前的结果,返回记录的结果,并且是用值的方式返回(因为用于记录操作前的结果是一个临时变量,返回引用会出错)

3、后置++,在参数上需要用int 作为占位参数


4、赋值运算符重载

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

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

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

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

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

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


示例:

class Person {

public:
	Person(int age) {
		m_a = new int(age);
	}

	~Person() {
		if (m_a != NULL) {
			delete m_a;
			m_a = NULL;
		}
	}

	Person& operator=(Person& p) {

		//系统默认浅拷贝
		//this->m_a = p.m_a;	

		//深拷贝
		if (this->m_a = NULL) {
			delete m_a;
			m_a = NULL;
		}
		this->m_a = new int(*p.m_a);

		//返回自身
		return *this;
	}

	int* m_a = NULL;
};

//测试
void test01() {
	Person p(10);
	Person p2(20);
	Person p3(30);

	cout << "赋值前:p1:" << *p.m_a << " p2:" << *p2.m_a << " p3:" << *p3.m_a << endl;

	p = p2 = p3;
	cout << "赋值后:p1:" << *p.m_a << " p2:" << *p2.m_a << " p3:" << *p3.m_a << endl;
}


int main() {

	test01();

	system("pause");
	return 0;
}

c++核心编程—类和对象2_c++_08

c++核心编程—类和对象2_文件操作_09

5、关系运算符重载

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


示例:

//关系运算符重载	
//1、==  2、 !=
class Person {

public:
	Person(string name,int age) {
		m_Name = name;
		m_Age = age;
	}

	//重载 ==
	int operator==(Person& p){
		if (this->m_Age == p.m_Age && this->m_Name == p.m_Name) {
			return 1;
		}
		else
			return 0;
	}

	//重载 !=
	int operator!=(Person& p){
		if (this->m_Age != p.m_Age || this->m_Name != p.m_Name) {
			return 1;
		}
		else
			return 0;
	}

	string m_Name;
	int m_Age;
};


//测试
void test01() {
	Person p("Tom",18);
	Person p2("Tom", 18);

	Person p3("Tom", 18);
	Person p4("Jiu", 18);
	
	if (p == p2) {
		cout << "p 和 p2 相等!" << endl;
	}
	else
		cout << "p 和 p2 不相等!" << endl;

	if (p3 != p4) {
		cout << "p3 和 p4  不相等!" << endl;
	}
	else
		cout << "p3 和 p4 相等!" << endl;
}


int main() {

	test01();

	system("pause");
	return 0;
}

c++核心编程—类和对象2_文件操作_10

c++核心编程—类和对象2_继承_11

6、函数调用运算符重载

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

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

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

示例:

//函数调用运算符重载
//打印类
class myPrint {
public:

	void operator()(string name) {
		m_Name = name;
		cout << m_Name << endl;
	}

	string m_Name;
};

void test() {
	myPrint mp;
	mp("Hello World");

}
int main() {
	test();

	system("pause");
	return 0;
}

c++核心编程—类和对象2_c++_12

二、继承

继承是面向对象的三大特征之一

在定义一些类的时候,如果下级别的成员除了拥有上一级的共性,还有自己的特性,这时候我们就可以选择继承,来减少重复代码


1、继承的基本语法

class 子类 :继承类型 父类

例如:

class son : public father{
	//成员
};

注意事项:

1、子类又称:派生类,父类又称:基类

2、子类的成员包括从父类继承来的,以及自己增加的成员

3、继承过来的表现共性,增加的表现个性


2、继承方式

继承方式分三种:

1、公有继承(public):当一个子类公有继承父类时,父类的公有成员也是子类的公有成员,父类的保护成员也是子类的保护成员,父类的私有成员不能直接被子类访问,但是可以通过调用父类的公有和保护成员来访问。

2、保护继承(protected): 当一个子类保护继承父类时,父类的公有和保护成员将成为子类的保护成员。

3、私有继承(private):当一个子类私有继承父类时,父类的公有和保护成员将成为子类的私有成员。

c++核心编程—类和对象2_继承_13

3、继承中的对象模型

问题:从父类继承过来的成员,那些属于子类对象?

答:在父类中的非静态成员属性都会被子类继承,私有属性也会继承,只是无法访问。会被编译器隐藏。

c++核心编程—类和对象2_运算符重载_14


扩展:利用开发人员命令提示工具查看对象模型

1、跳转盘符:E:(存放文件的盘符)

2、跳转文件路径: cd 具体路径下

3、查看命令:cl /d1 reportSinglcleClassLayout类名 "文件名"

c++核心编程—类和对象2_c++_15

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

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


问题:父类和子类的构造和析构顺序谁先谁后?

答:

1、先构造父类,在构造子类

2、析构与构造相反


c++核心编程—类和对象2_继承_16


可以看见,子类父类的顺序,同,一个类中的类成员顺序是一致的

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

问题:当子类和父类出现同名的成员,如何通过子类或父类中同名的数据?

答:访问子类同名成员,直接访问;访问父类同名成员,需要加作用域。

c++核心编程—类和对象2_多态_17


注意事项:

1、如果子类中出现了和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数,即,不可直接调用重载

c++核心编程—类和对象2_多态_18

c++核心编程—类和对象2_多态_19

解决方法:调用时,增加作用域即可

c++核心编程—类和对象2_c++_20

6、继承同名静态成员处理

静态成员和非静态成员出现同名时,处理方法一致

1、访问子类同名成员,直接访问

2、访问父类同名成员,需要加作用域

c++核心编程—类和对象2_c++_21

7、多继承语法

c++中允许一个类继承多个类

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


多继承可能回引发父类中同名成员出现,需要加作用域区分,实际开发中,不建议使用

//多父类继承
class Base1 {	//父类1
public:
	Base1() {
		m_b1 = 100;
	}
	int m_b1;
};

class Base2 {	//父类2
public:
	Base2() {
		m_b2 = 200;
	}
	int m_b2;
};

class Base3 {	//父类3
public:
	Base3() {
		m_b3 = 300;
	}
	int m_b3;
};


//子类多继承
class Son : public Base1,public Base2, public Base3{	
public:
	int m_s1 = 400;
};

void test() {
	Son s;
	cout << "多继承子类字节数:" << sizeof(s) << endl;

	cout << "Base1 = " << s.Base1::m_b1 << endl;;
	cout << "Base2 = " << s.Base2::m_b2 << endl;;
	cout << "Base3 = " << s.Base3::m_b3 << endl;;
	cout << "Son = " << s.m_s1 << endl;;
}

int main() {
	test();

	system("pause");
	return 0;
}

c++核心编程—类和对象2_文件操作_22

8、菱形继承

概念:

1、两个子类继承同一个父类

2、又有某个类同时继承两个子类

3、这种继承被称为菱形继承,或砖石继承

c++核心编程—类和对象2_运算符重载_23


菱形继承的问题:

1、羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性。

答:加以作用域区分!

2、羊驼继承自动物的数据继承了两份,导致资源浪费

答:利用虚继承(在继承之前加上关键字virtual,变为虚继承),可以解决!

//菱形继承
class BASE {	//父父类
public:
	int m_a;
};

//父类1
class Base1 :virtual public BASE {};

//父类2
class Base2 :virtual public BASE {};

//子类
class Son : public Base1, public Base2{};

void test() {
	Son s;
	s.Base1::m_a = 100;
	s.Base2::m_a = 200;

	cout << "父类1,base1 :" << s.Base1::m_a << endl;
	cout << "父类2,base3 :" << s.Base2::m_a << endl;
	cout << "子类:Son :" << s.m_a << endl;
}

int main() {
	test();

	system("pause");
	return 0;
}

c++核心编程—类和对象2_多态_24

虚继承原理:子类继承父类的两个指针,指针通过偏移量,找到同一份数据。

c++核心编程—类和对象2_文件操作_25

三、多态

1、多态的基本概念

多态是C++面向对象三大特性之一

多态分为两类:

1、静态多态: 函数重载 和 运算符重载 属于静态多态,复用函数名

2、动态多态: 子类和虚函数实现运行时多态

区别:

1、静态多态的函数地址早绑定 - 编译阶段确定函数地址

2、动态多态的函数地址晚绑定 - 运行阶段确定函数地址

示例:

//多态
//动物类
class Animal {	
public:
	//虚函数,用virtual关键字修饰的函数
	virtual void speek() {	
		cout << "动物说话..." << endl;
	}

};

//猫类
class Cat : public Animal {
	void speek() {
		cout << "猫在说话..." << endl;
	}
};

//狗类
class Dog : public Animal {
	void speek() {
		cout << "狗在说话..." << endl;
	}
};

void doSpeek(Animal& ani) {	//父类和子类类型可以相互转换
	ani.speek();
}

void test() {
	Cat c;
	Dog d;

	//调用执行说话函数
	doSpeek(c);
	doSpeek(d);
	
}

int main() {
	test();

	system("pause");
	return 0;
}

c++核心编程—类和对象2_c++_26


总结:

动态多态的满足条件:

1、有继承关系

2、子类重写父类的虚函数


区分重写和重载:

重写:函数返回值类型,函数名,参数列表完全相同

重载:函数的参数的数量,类型或位置不同

动态多态的使用:

父类的指针或引用指向子类对象

c++核心编程—类和对象2_继承_27

2、深入剖析多态原理:

Animal类内部结构:

c++核心编程—类和对象2_继承_28

c++核心编程—类和对象2_c++_29

c++核心编程—类和对象2_运算符重载_30

c++核心编程—类和对象2_多态_31

Cat类内部结构:

1、未重写父函数

c++核心编程—类和对象2_多态_32

c++核心编程—类和对象2_文件操作_33

c++核心编程—类和对象2_c++_34

2、重写父函数后

当子类重写父类虚函数后,子类的虚函数表内部会替换成子类的虚函数地址,父类不发生改变。

c++核心编程—类和对象2_继承_35

c++核心编程—类和对象2_文件操作_36

c++核心编程—类和对象2_运算符重载_37

3、纯虚函数和抽象类

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

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


抽象类特点:

1、无法实例化对象

2、子类必须重写抽象类中的纯虚函数,否则也属于抽象类

示例:

c++核心编程—类和对象2_多态_38

4、虚析构和纯虚析构

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


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


虚析构和纯虚析构共性:

1、可以解决父类指针释放子类对象

2、都需要有具体的函数实现


虚析构和纯虚析构区别:

如果是纯虚析构,该类属于抽象类,无法实例化对象


虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名() = 0;

类名::~类名(){}

c++核心编程—类和对象2_运算符重载_39

c++核心编程—类和对象2_多态_40


示例:

//虚析构和纯虚析构
class Base {
public:
	Base() {
		cout << "Base构造函数调用" << endl;
	}

	virtual ~Base() = 0;

	virtual void func() = 0;
};

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


class Son :public Base {
public:
	
	Son(string name) {
		m_name = new string(name);
		cout << "son构造函数调用" << endl;
	}

	~Son() {
		
		cout << "son析构函数调用" << endl;
		if (m_name != NULL) {
			delete m_name;
			m_name = NULL;
		}
	}

	void func() {
		cout << *m_name << endl;
	}
	string* m_name;
};

void test01() {
	
	Base* b = new Son("TOM");
	b->func();
	delete b;

}


int main() {

	test01();
	system("pause");
	return 0;
}

使用虚析构或纯虚析构前:

c++核心编程—类和对象2_继承_41

使用虚析构或纯虚析构后:

c++核心编程—类和对象2_c++_42

四、文件操作

文件操作头文件:<fstream>

操作文件的三大流:

1、ofstream:写操作

2、ifstream:读操作

3、fstream:读写操作


1、文本文件

写文件

步骤:

1、包含头文件:#include<fstream>

2、创建流对象:ofstream ofs;

3、打开文件:ofs.open("文件路径",打开方式);

4、写数据:ofs<<"写入的数据"

5、关闭文件:ofs.close();

文件打开方式:

打开方式

解释

ios::in

为读文件而打开文件

ios::out

为写文件而打开文件

ios::ate

初始位置:文件尾

ios::app

追加方式写文件

ios::trunc

如果文件存在先删除,再创建

ios::binary

二进制方式

注意:文件的打开方式可以配合使用,用 | (或)操作符

例如:用二进制方式写文件

ios::binary | ios::out


示例:

#include<fstream>	//1、包含头文件

void wfile() {
	
	//2、流对象
	ofstream ofs;
	//3、打开文件
	ofs.open("test.txt",ios::out);
	//4、写文件
	ofs << "大鹏一日同风起" << endl;
	ofs << "扶摇直上九万里" << endl;
	ofs << "有约不来过夜半" << endl;
	ofs << "闲敲棋子落灯花" << endl;

	//关闭文件
	ofs.close();

}


int main() {

	wfile();

	system("pause");
	return 0;
}

c++核心编程—类和对象2_多态_43

读文件:

步骤:

1、包含头文件:#include<fstream>

2、创建流对象:ifstream ifs;

3、打开文件并判断是否成功打开:ifs.open("文件路径",打开方式);

4、读数据:四种方式读取

5、关闭文件:ifs.close();


四种读取方式:

c++核心编程—类和对象2_多态_44

c++核心编程—类和对象2_多态_45

c++核心编程—类和对象2_文件操作_46

c++核心编程—类和对象2_c++_47

不推荐使用第四种,其余的可根据个人喜好使用,个人推荐第三种~

示例:

#include<fstream>	//1、包含头文件
#include <string>

void rfile() {

	//2、流对象
	ifstream ifs;
	//3、打开文件,判断是否成功
	ifs.open("test.txt", ios::in);
	//判断
	if (! ifs.is_open()) {
		cout << "文件打开失败" << endl;
		return;
	}

	//4、写文件
	////第一种
	//char Buf[1024] = { 0 };
	//while (ifs>>Buf) {
	//	cout << Buf << endl;
	//}

	////第二种
	//char Buf[1024] = { 0 };
	//while (ifs.getline(Buf,sizeof(Buf))) {
	//	cout << Buf << endl;
	//}

	//第三种
	string Buf;
	while (getline(ifs,Buf)) {
		cout << Buf << endl;
	}

	////第四种
	//char c;
	//while ((c = ifs.get()) != EOF) {
	//	cout << c;
	//}

	//关闭文件
	ifs.close();

}


int main() {
    
	rfile();

	system("pause");
	return 0;
}

c++核心编程—类和对象2_继承_48

2、二进制文件

打开方式指定为:ios::binary

写文件:

二进制方式写文件主要利用流对象调用成员函数write

函数原型 :ostream& write(const char * buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

示例:

#include<fstream>	//1、包含头文件
#include <string>

//二进制文件
class Person {
public:

	char Name[10];	//姓名
	int age;//年龄
};

//写二进制文件
void binWfile() {
	Person p = { "张三",18};

	//1、定义头文件

	//2、流对象
	ofstream ofs("Person.txt", ios::out | ios::binary);

	//3、打开文件
	//ofs.open("Person.txt", ios::in | ios::binary);

	//4、写文件
	ofs.write((const char*) & p,sizeof(Person));

	//5、关闭文件
	ofs.close();

}


int main() {
	binWfile();
}

c++核心编程—类和对象2_继承_49

读文件:

二进制方式读文件主要利用流对象调用成员函数read

函数原型:istream& read(char *buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数


示例:

#include<fstream>	//1、包含头文件

//二进制文件
class Person {
public:

	char Name[10];	//姓名
	int age;//年龄
};

//读二进制文件
void binRfile() {
	Person p;
	//1、定义头文件

	//2、流对象
	ifstream ifs("Person.txt", ios::in | ios::binary);

	if (!ifs.is_open()) {
		cout << "二进制文件打开失败" << endl;
		return;
	}

	//3、打开文件
	//ofs.open("Person.txt", ios::in | ios::binary);

	//4、读文件
	ifs.read((char*)&p, sizeof(Person));
	//ofs.write((const char*) & name, sizeof(Person));
	cout << "姓名:" << p.Name << " 年龄:" << p.age << endl;

	//5、关闭文件
	ifs.close();

}

int main() {
	binRfile();
}

c++核心编程—类和对象2_运算符重载_50

上一篇:C++虚函数表深度解析
下一篇:没有了
网友评论