(C++)05 类和对象

本文详细探讨了C++中的类和对象,包括封装的含义、struct和class的区别、构造函数与析构函数、拷贝构造函数、深浅拷贝、初始化列表、友元、运算符重载、继承与多态的原理和实践。通过实例演示了如何运用这些概念处理对象的生命周期和操作。
摘要由CSDN通过智能技术生成


类和对象

C++面向对象三大特性:封装、继承、多态
C++认为万事万物都皆为对象,对象上有其属性和行为

1.封装

1.1 封装的意义

(1)将属性和行为作为一个整体,表现生活中的事务
语法:class 类名{ 访问权限: 属性 / 行为 };
(2)类在设计时,可以把属性和行为放在不同的权限下,加以控制

pubilc 公共权限 , 类内可以访问,类外可以访问
protected 保护权限 ,类内可以访问,类外不能访问,子类可以访问父类
private 私有权限 ,类内可以访问,类外不能访问,子类不能访问父类

#include<iostream>

using namespace std;

//圆周率
const double PI = 3.14;

//设计一个圆类,求圆的周长
class Circle
{
	//公共权限
public:
	//属性 成员属性、成员变量
	int m_r;//半径

	//行为
	double calculateZC()//获取圆的周长
	{
		m_Password = 1234;
		return 2 * PI * m_r;
	}

	//私有权限
private:
	int m_Password;//密码
};

int main() {
	//通过圆类 创建具体的圆(对象)实例化
	Circle circle;
	//给圆对象 的属性进行赋值
	circle.m_r = 10;
	//调用圆对象的 行为获得周长
	double ZC = circle.calculateZC();
	system("pause");
	return 0;
}

1.2 struct 和 class 区别

在C++中struct和class唯一的区别在于 默认的访问权限不同
区别:
struct默认权限为公共
class默认权限为私有

1.3 成员属性设置为私有

优点1:将所有成员属性设置为私有,可以自己控制读写权限
有点2:对于写权限,我们可以检测数据的有效性

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

class Person
{
public:
	//设置姓名
	void SetName(string name)
	{
		m_Name = name;
	}
	//获取姓名
	string getName()
	{
		return m_Name;
	}
	//获取年龄
	int getAge()
	{
		return m_Age;
	}
	void setAge(int age)
	{
		if (age < 0 || age>150)
		{
			cout << "年龄输入有误啊" << endl;
			return;
		}
		m_Age = age;
	}
	//写入爱人
	void SetLover(string lover)
	{
		m_Lover = lover;
	}
private:
	//姓名 可读可写
	string m_Name;
	//年龄 只读
	int m_Age;
	//爱人 只写
	string m_Lover;
};

class Cube
{
public:
	//设置长宽高
	void set_LWH(int L, int W, int H)
	{
		m_L = L;
		m_W = W;
		m_H = H;
	}

	int* get_LWH()
	{
		int LWH[] = { m_L,m_W,m_H };
		return LWH;
	}

	double get_Area()
	{
		return m_L * m_W * 2 + m_L * m_H * 2 + m_H * m_W * 2;
	}

	double get_Vol()
	{
		return m_L * m_W * m_H;
	}

private:
	//长宽高
	double m_L;
	double m_W;
	double m_H;
};

//利用全局函数判断 两个立方体是否相等
bool isSame(Cube& c1, Cube& c2)
{
	const int* a1 = c1.get_LWH();
	const int* a2 = c2.get_LWH();

	if (*a1==*a2&&*(a1+1)==*(a2+1)&& *(a1 + 2) == *(a2 + 2))
	{
		return true;
	}
	else
	{
		return false;
	}
}

int main() {
	/*Person p;
	p.SetName("Mike");
	cout << "姓名:" << p.getName() << endl;*/

	Cube cube1;
	cube1.set_LWH(10, 20, 30);
	double area1 = cube1.get_Area();
	double vol1 = cube1.get_Vol();

	Cube cube2;
	cube2.set_LWH(10, 20, 30);
	double area2 = cube2.get_Area();
	double vol2 = cube2.get_Vol();
	isSame(cube1, cube2);

	system("pause");
	return 0;
}

2.对象的初始化和清理

2.1 构造函数和析构函数

在这里插入图片描述
构造函数语法:类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时候会自动调用构造函数,无须手动调用,而且只会调用一次

析构函数语法:~类名(){}
1.析构函数,没有返回值也不写void
2.函数名称与类名相同,在名称上加上符号~
3.析构函数不可以有参数,因此不能发生重载
4.程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

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

//函数构造的分类及调用
//参数分类: 无参构造(默认构造)
//类型分类: 普通构造 拷贝构造

class Person
{
public:
	Person();
	Person(int a);
	Person(const Person& p);

	~Person();

private:
	int age;
};

Person::Person()
{
	cout << "构造函数调用" << endl;
}

Person::Person(int a)
{
	cout << "有参构造函数调用" << endl;
}

//拷贝构造函数
Person::Person(const Person& p)
{
	age = p.age;
	cout << "拷贝构造函数调用" << endl;
}

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

//构造和析构都是必须有的实现,如果我们自己不提供,编译器会提供一个空实现的构造和析构
void test01()
{
	//调用默认构造函数时候,不要加()
	Person p1;//在栈上的数据,test01执行完毕后,释放这个对象

	//1.括号法
	Person p2(10);
	Person p3(p2);
	//2.显示法
	Person p4 = Person(10);
	Person p5 = Person(p2);
	//3.隐式转换法
	Person p6 = 10;
}

int main()
{
	test01();
	//Person p;

	system("pause");
	return 0;
}

2.2 拷贝构造函数调用时机

在这里插入图片描述

2.3 构造函数调用规则

在这里插入图片描述

2.4 深拷贝与浅拷贝

浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作

总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题

#include<iostream>
using namespace std;

class Person
{
public:
	Person()
	{
		cout << "Person的默认构造函数" << endl;
	}
	Person(int age,int height)
	{
		m_Age = age;
		m_Height = new int(height);
		cout << "Person的有参构造函数" << endl;
	}

	//自己实现拷贝构造函数 解决浅拷贝带来的问题
	Person(const Person& p)
	{
		cout << "Person 拷贝构造函数调用" << endl;
		m_Age = p.m_Age;
		//m_Height = p.m_Height; //编译器默认实现的是这行代码

		//深拷贝操作
		m_Height = new int(*p.m_Height);
	}

	~Person()
	{
		//析构代码,将堆区开辟数据做释放操作
		if (m_Height!=NULL)
		{
			delete m_Height;
			m_Height = NULL;
		}
		cout << "Person的析构函数" << endl;
	}
	int m_Age;//年龄
	int* m_Height;
};

void test01()
{
	Person p1(18,160);
	cout << "p1的年龄为 " << p1.m_Age << endl;

	Person p2(p1);
	cout << "p2的年龄为 " << p2.m_Age << endl;
}

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

2.5 初始化列表

在这里插入图片描述

class Test
{
public:
	Test(int a, int b, int c) :m_A(a), m_B(b), m_C(c) 
	{
	}
	int m_A;
	int m_B;
	int m_C;
};

int main()
{
	Test t(30, 20, 10);
	cout << t.m_A << endl;
	//test01();
	system("pause");
	return 0;
}

2.6 类对象作为类成员

class A{}
class B{A a;}
当其他类对象作为本类成员,构造时候先构造类对象,再构造自身,析构的顺序与构造相反

2.6 静态成员

在这里插入图片描述

3. C++对象模型和this指针

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

在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上

class A
{
public:
	A() {}
	//非静态成员变量占对象空间
	int m_A;
	//静态成员变量不占对象空间
	static int m_B;
	//函数也不占对象空间,所有函数共享一个函数实例
	void func() 
	{
		cout << "mA:" << this->m_A << endl;
	}
};

Person p;
//空对象占用内存空间为:1
//C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
//每个空对象也应该有一个独一无二的内存地址

3.2 this指针概念

在这里插入图片描述

3.2 const 修饰成员函数

在这里插入图片描述

class Person
{
public:
	//this指针的本质  是指针常量  指针的指向是不可以修改的
	//const Person * const this
	//在成员函数后面加const 修饰的是this指向,让指针指向的值也不可以修改
	void showPerson()const//常函数
	{
		//this->m_A = 10;
		this->m_B = 100;
	}
	
	int m_A;
	//mutable修饰的是特殊变量,即使在常函数中,也可以修改这个值
	mutable int m_B;
};

void test02()
{
	const Person p;//在对象前加consr,变为常对象
	p.m_B = 100;

	//常对象只能调用常函数
	p.showPerson();
}

4. 友元

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

友元的关键字为 friend

友元的三种实现:全局函数做友元、类做友元、成员函数做友元

5.运算符重载

	//成员函数重载+运算符
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;
	}

//全局函数重载+运算符
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;
}

6.继承

继承的主要目的----减少代码量

class language
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册" << endl;
	}
};

class python:public language
{
public:
	void content()
	{
		cout << "python" << endl;
	}
};

class cplus :public language
{
public:
	void content()
	{
		cout << "cplus" << endl;
	}
};

在这里插入图片描述
继承中的构造和析构顺序如下:
先构造父类,再构造子类,析构的顺序与构造相反,先析构子类,再析构父类

当子类和父类存在同名属性,或者同名方法时,直接调用是调用子类对象,访问父类对象需要加 Base::作用域

Son s;
cout<<"son下的成员"<<s.m_A<<endl;
cout<<"Base下的成员"<<s.Base::m_A<<endl;

cout<<"son下的方法"<<s.func()<<endl;
cout<<"Base下的方法"<<s.Base::func()<<endl;

菱形继承:
两个派生类继承同一个基类,又有某个类同时继承这两个派生类,这种继承被称为菱形继承,或者砖石继承。

7.多态

7.1 多态的基本概念

多态是C++面向对象三大特性之一
多态分为两类
1.静态多态:函数重载 和 运算符重载属于静态多态,复用函数名
2.动态多态:派生类和虚函数实现运行时多态

静态多态和动态多态的区别:
1.静态多态的函数地址早绑定 - 编译阶段确定函数地址
2.动态多态的函数地址晚绑定 - 运行阶段确定函数地址

#include<iostream>

using namespace std;

class Animal {
public:
	//虚函数
	virtual void speak() {
		cout << "动物在说话" << endl;
	}
};

class Cat:public Animal {
public:
	void speak() {
		cout << "小猫在说话" << endl;
	}
};

class Dog :public Animal {
public:
	void speak() {
		cout << "小狗在说话" << endl;
	}
};

//执行说话的函数
//地址早绑定 在编译阶段确定函数地址
//如果想执行让猫说话 那么这个函数地址就不能提前绑定 需要在运行阶段进行绑定 地址晚绑定

//动态多态满足条件
//1.有继承关系
//2.子类重写父类的虚函数

//动态多态使用
//父类的指针或引用 执行子类对象
void doSpeak(Animal &animal) {
	animal.speak();
}

void test001() {
	Cat cat;
	doSpeak(cat);

	Dog dog;
	doSpeak(dog);
}

//利用多态实现计算器

//多态好处:1、组织结构清晰 2、可读性强  3、对于前期和后期扩展以及维护性高

//实现计算器 抽象类
class AbstractCalculator {
public:
	virtual int getResult() {
		return 0;
	}
	int m_Num1;
	int m_Num2;
};

//实现计算器 加法类
class AddCalculator:public AbstractCalculator {
	int getResult() {
		return m_Num1 + m_Num2;
	}
};

void test002() {
	AbstractCalculator* absCal = new AddCalculator;
	absCal->m_Num1 = 100;
	absCal->m_Num2 = 200;

	cout << absCal->getResult() << endl;
	delete absCal;
}

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

7.2 纯虚函数和抽象类

纯虚函数语法: virtual 返回值类型 函数名 (参数列表) = 0 ;
例如: virtual void func() = 0 ;
当类中有了 纯虚函数,这个类也称为 抽象类

抽象类特点:1、无法实例化对象 2、子类必须重写父类中的虚函数,否则也属于抽象类

7.3 虚析构和纯虚析构

在这里插入图片描述

7.4 多态案例

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

//抽象的CPU类
class CPU {
public:
	virtual void caculate() = 0;
};
//抽象的显卡类
class GPU {
public:
	virtual void display() = 0;
};
//抽象的内存条类
class Memory {
public:
	virtual void storage() = 0;
};

class Computer {
public:
	Computer(CPU* cpu, GPU* gpu, Memory* memory) {
		m_cpu = cpu;
		m_gpu = gpu;
		m_memory = memory;
	}

	void work() {
		m_cpu->caculate();
		m_gpu->display();
		m_memory->storage();
	}

	//提供析构函数 释放3个电脑零件
	~Computer(){
		if (m_cpu!=NULL)
		{
			delete m_cpu;
			m_cpu = NULL;
		}
		if (m_gpu != NULL)
		{
			delete m_gpu;
			m_gpu = NULL;
		}
		if (m_memory != NULL)
		{
			delete m_memory;
			m_memory = NULL;
		}
	}
private:
	CPU* m_cpu;
	GPU* m_gpu;
	Memory* m_memory;
};

class Intel :public CPU, public GPU, public Memory {
public:
	void caculate() { cout << "Intel的CPU进行计算" << endl; }
	void display() { cout << "Intel的显卡进行显示" << endl; }
	void storage() { cout << "Intel的内存条进行存储" << endl; }
};

class Lenovo :public CPU, public GPU, public Memory {
public:
	void caculate() { cout << "Lenovo的CPU进行计算" << endl; }
	void display() { cout << "Lenovo的显卡进行显示" << endl; }
	void storage() { cout << "Lenovo的内存条进行存储" << endl; }
};

class otherCPU :public CPU {
public:
	void caculate() { cout << "Other的CPU进行计算" << endl; }
};

class otherGPU :public GPU {
public:
	void display() { cout << "Other的显卡进行显示" << endl; }
};

class otherMemory :public Memory {
public:
	void storage() { cout << "Other的内存条进行存储" << endl; }
};

void test11()
{
	//屏蔽段的代码,delete时会报错,不知道原因

	/*CPU* intelCPU = new Intel;
	GPU* intelgpu = new Lenovo;
	Memory* intelmem = new Intel;

	Computer* computer1 = new Computer(intelCPU, intelgpu, intelmem);
	computer1->work();
	delete computer1;

	Computer* computer2 = new Computer(new Intel, new Lenovo, new Intel);
	computer2->work();
	delete computer2;*/

	Computer* computer3 = new Computer(new otherCPU, new otherGPU, new otherMemory);
	computer3->work();
	delete computer3;
}

int main() {

	test11();

	system("pause");
	return 0;
}

8.文件操作

8.1 文本文件

文件类型分为两种:
1、文本文件 - 文件以文本的ASCII码形式存储在计算机中
2、二进制文件 - 文件以文本的二进制形式存储在计算机中,用户一般不能直接读它们

操作文件的三大类:
1、ofstream : 写操作
2、ifstream :读操作
3、fstream : 读写操作

8.1.1 写文件

写文件步骤如下:
1.包含头文件 #include
2.创建流对象 ofstream ofs;
3.打开文件 ofs.open(“文本路径”,打开方式);
4.写数据 ofs<<“写入的数据”;
5.关闭文件 ofs.close();
在这里插入图片描述

8.1.2 读文件

读文件步骤如下:
1.包含头文件 #include
2.创建流对象 ifstream ifs;
3.打开文件 ifs.open(“文本路径”,打开方式);
4.读数据 四种方式读取
5.关闭文件 ifs.close();

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

//写文件
void fun_write()
{
	//1.包含头文件 fstream

	//2.创建流对象
	ofstream ofs;
	//3.指定打开方式
	ofs.open("test.txt", ios::out);
	//4.写内容
	ofs << "姓名:张三" << endl;
	//5.关闭文件
	ofs.close();
}

void fun_read()
{
	//1.包含头文件 fstream

	//2.创建流对象
	ifstream ifs;
	//3.指定打开方式
	ifs.open("test.txt", ios::out);

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


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

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

8.2 二进制文件

写文件:
二进制方式写文件主要利用流对象调用成员函数write
函数原型:ostream& write(const char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间,len时读写的字节数

读文件:
二进制方式读文件主要利用流对象调用成员函数read
函数原型:istream& read(char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间,len时读取的字节数

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

//二进制文件  写文件
class Person {
public:
	char m_Name[64];
	int m_Age;
};

void test_write()
{
	ofstream ofs("person.txt", ios::out || ios::binary);
	Person p = { "张三",18 };
	ofs.write((const char*)&p, sizeof(Person));
	ofs.close();
}
void test_read()
{
	ifstream ifs;
	ifs.open("person.txt", ios::in || ios::binary);
	if (!ifs.is_open())
	{
		cout << "文件打开失败" << endl;
		return;
	}
	Person p;
	ifs.read((char*)&p, sizeof(Person));
	cout << p.m_Name << "--" << p.m_Age << endl;
	ifs.close();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值