【C++】day03 - 【类型与对象的概念】【类型】【构造函数】【一个对象创建的过程】【构造函数的应用】【头文件和实现文件的分离】【this指针】【析构函数】

一、类型与对象的概念

1.1什么是对象

	万物皆对象
	程序就是由一组对象组成的,对象和对象通过发消息,通知
		做什么。如对象——电池通知对象——电动机做动作
	对象是由其他类型的对象组成的存储区
	每一个对象都属于一个特定的类型
	同类型的对象,都能接收相同的消息。

1.2类型

	人类认识世界的过程就是面向对象的(猫、房子等事物)
	人类认识世界的过程就是由具体到抽象(比如,小孩刚出生,见过真狼
		之后才会对狼有一种概念;我们对炼钢工厂真实考察过之后,才
		能抽象出炼钢的程序)

	类型具有共同特征的对象,这些对象属于同一共同类别;
	类型是类别的抽象;
	对象是类型创建的具体变量;
	类型刻画就是抽取对象的共同特征与行为
	
	好,看一个例子你就明白了类型、对象、抽象这几个概念了!
#include <iostream>
using namespace std;

struct Student{
    
    
	/*特征*/
	string name;
	string sno;
	int score;
	/*行为*/
	void learn(string par){
    
    
		cout << "learn" << par << endl;//学生学什么
	}
	void eat(string par){
    
    //学生吃什么
		cout << "eat" << par << endl;
	}
	
};
int main(){
    
    
	Student one;
	one.name = "Zhangsan";
	one.sno = "A07160289";
	one.score = 188;
	one.learn("English");
	one.eat("米饭");
	Student two;
	two.name = "Lisi";
	two.sno = "A07160300";
}
来了一个需求:要你写出学生的特征和行为信息。
我们根据对学生的实际考察,抽取学生的共同特征与行为(学生都有名
	字、学号、分数的特征以及学什么、吃什么的行为),我们据此,
	抽象出一个学生类型,即定义了一个struct Student{
    
    }结构体类型,
而我们用结构体定义的两个学生(变量):Zhangsan、Lisi就是对象。

二、如何在计算机中描述类型

2.1使用结构体(struct)对类型进行描述

	如1.2中的程序例子所示。
	再写一个例子:
		(设置一个时钟,并给其设置一个初始时间)
/*下例的struct Mytime{}是类,mt是对象*/
#include <iostream>
#include <cstdio>
#include <iomanip>
using namespace std;
struct Mytime{
    
    
	int hour;
	int min;
	int sec;
	void show(){
    
    
		cout << setfill('0') << setw(2) <<
		hour << ":" << setw(2) << min << ":"
		<< setw(2) << sec << '\r' << flush;
	}
	void dida(){
    
    
		sleep(1);
		if(60 == ++sec){
    
    
			sec = 0;
			if(60 == ++min){
    
    
				min = 0;
				if(24 == ++hour){
    
    
					hour = 0;
				}
			}
		}
	}
	void run(){
    
    
		while(1){
    
    
			show();
			dida();
		}
	}
	
};	
		
int main(){
    
    
	Mytime mt;
	mt.hour = 9;
	mt.min = 28;
	mt.sec = 35;
	mt.run();
}

2.2使用类(class)对类型进行描述

	类内成员默认是私有的private
		private只能在类内访问,不让外界访问
			所谓类内是指在class Student{};大括号之内
	public可以在类内和类外访问
	private、public权限修饰范围:
		从一个权限开始 到 下一个权限的开始
	在c++中,如果类的成员只是变量而无函数,且变量公开,则我们一般使用struct,
		否则,我们一般使用class。二者没啥区别)
	程序举例:
	(把上例的struct改为class,并添加private、public权限)
#include <iostream>
#include <cstdio>
#include <iomanip>
using namespace std;
class Mytime{
    
    
	public:
	int hour;
	int min;
	int sec;
	
	private://设置show()dida()为类内私有
	void show(){
    
    
		cout << setfill('0') << setw(2) <<
		hour << ":" << setw(2) << min << ":"
		<< setw(2) << sec << '\r' << flush;
	}
	void dida(){
    
    
		sleep(1);
		if(60 == ++sec){
    
    
			sec = 0;
			if(60 == ++min){
    
    
				min = 0;
				if(24 == ++hour){
    
    
					hour = 0;
				}
			}
		}
	}
	public:
	void run(){
    
    
		while(1){
    
    
			show();
			dida();
		}
	}
	
};	
		
int main(){
    
    
	Mytime mt;
	mt.hour = 9;
	mt.min = 28;
	mt.sec = 35;
	mt.run();
}
	程序再改进:
		(把时间的初始化挪到类内,并将hour、min、sec私有化)
#include <iostream>
#include <cstdio>
#include <iomanip>
using namespace std;
class Mytime{
    
    
	private:
	int hour;
	int min;
	int sec;
	
	private://设置show()dida()为类内私有
	void show(){
    
    
		cout << setfill('0') << setw(2) <<
		hour << ":" << setw(2) << min << ":"
		<< setw(2) << sec << '\r' << flush;
	}
	void dida(){
    
    
		sleep(1);
		if(60 == ++sec){
    
    
			sec = 0;
			if(60 == ++min){
    
    
				min = 0;
				if(24 == ++hour){
    
    
					hour = 0;
				}
			}
		}
	}
	public:
	void run(){
    
    
		while(1){
    
    
			show();
			dida();
		}
	}
	void initMytime(){
    
    
		mt.hour = 9;
		mt.min = 28;
		mt.sec = 35;
	}
	
};	
		
int main(){
    
    
	Mytime mt;
	mt.initMytime();
	//mt.hour = 9;
	//mt.min = 28;
	//mt.sec = 35;
	mt.run();
}
我们改进的这个程序,通过调用类型中的public函数mt.initMytime()
实现了对类内私有变量hour、min、sec的初始化。
但是,如果我们不小心重复初始化了呢?比如不小心重复调用了
mt.initMytime(),因此对于类内成员的初始化我们一般不这样写,
我们使用构造函数。见下节。

三、构造函数

3.1构造函数概述

概念:创建一个对象时,编译器自动帮你调用一次,并且仅仅调用一次
创建 构造函数的条件:
    和类名相同
	没有返回值类型
目的是:初始化对象
如2.2第二个例子中的:
void initMytime(){
    
    
		mt.hour = 9;
		mt.min = 28;
		mt.sec = 35;
	}
	
	改为:
	
class Mytime{
    
    
	省略代码......
	Mytime(){
    
    
		mt.hour = 9;
		mt.min = 28;
		mt.sec = 35;
	}	
};
int main(){
    
    
		Mytime mt;
		//mt.initMytime();
		//mt.hour = 9;
		//mt.min = 28;
		//mt.sec = 35;
		mt.run();
	}
	这就行了。注意,你不用再调用Mytime()进行初始了化。编译时编译器自动帮
	你执行Mytime()进行初始化。并且仅仅调用一次。

3.2一个知识点

int main(){
	Mytime mt;//在栈中申请mt
	Mytime mt2();//这不是申请类mt,这是函数的声明,因此不能随意加括号
	Mytime *pt = new Mytime;//在堆中申请一块内存
	Mytime *pt2 = new Mytime();//在堆中申请一块内存
	mt.run();
}

四、一个对象创建的过程

第一步:根据类型的大小进行分配内存;
第二步:进行处理成员变量
	①如果成员是基本类型(int、double等)的数据,编译器就什么都不做
	②如果成员是类类型的成员,则构建这个成员。
		也就是说,第一步不是已经分配好了一块内存了吗,现在在这片
		内存里面开辟出一片空间给这个类类型的成员。
第三步:调用这个类型的构造函数。

概念有点抽象,我们来写个程序:
#include <iostream>
using namespace std;
class Data{
    
    
	private:
	int year;
	int month;
	int day;
	
	public:
	Data(){
    
    
		cout << "Data's_构造函数" << endl;
		year = 2020;
		month = 8;
		day = 20;
	}
	void show(){
    
    
		cout << year << "-" << month << "-" << day
		<< endl;
	}
};
class Person{
    
    
	double salary;
	Data data;
	public:
	Person(){
    
    
		cout << "Person's_构造函数" << endl;
	}
	void show(){
    
    
		data.show();
		cout << salary << endl;//salary没有初始化,会输出一个垃圾数
	}
	
};	
		
int main(){
    
    
	/*
	Data data;
	data.show();
	要点一:构造函数Data()对year、month、day进行了初始化。
	如果你不对year、month、day进行初始化,那么输出的year、month、day会
	是垃圾数*/
	
	/*要点二:类Person中的一个成员是类Data data*/
	Person p;
	p.show();
	/*运行结果:
	Data's_构造函数
	Person's_构造函数
	2020-8-20
	2.07368e-317
	*/
	
}

五、构造函数的应用

5.1 要点

	要点一:构造函数可以设计参数,可以有多个构造函数,能形成重载关系。
			一旦提供构造函数,则系统提供的默认构造函数就会自动消失。
	要点二:可以利用参数的默认值简化构造函数的个数;
	程序示例:
#include <iostream>
using namespace std;
class A({
    
    
	public:
	A(){
    
    
		
	}
	A(int x){
    
    cout << "A(int)" << endl;}
	A(int x, double y){
    
    
		
	}
};
		
int main(){
    
    
	A a(111);//一:这个111就是传参,传递给构造函数;
	//二:A a(111);编译器不会理解成函数声明。函数声明里没有带111数值的是吧。
	A b;
	A c(1,1.5);
	/*以上三个类的定义都能够编译通过,形成重载机会*/
	
	/*看,上面例子咱们定义了三个构造函数,其实咱们之前学过函数参数的默认值吧,
	可以利用函数参数默认值,从而把构造函数的个数缩减为一个。比如上面的
	这个例子就可以只要构造函数A(int x = 0, double y = 0.0){}*/
	A *pa = new A(100,20.5);//100,20.5给构造函数传参
}

5.2 初始化列表

	如果类中有const类型的成员 或者 引用类型的成员,则使用初始化参数列表是一个
		不二的选择。
	初始化参数列表的位置是在构造函数参数列表(即构造函数的()里面的东东)之后,
		实现体(即构造函数的{}里面的东东)之前。
	初始化参数列表的作用是:
		保证参数初始化在构造函数执行之前被调用
	程序举例:
#include <iostream>
using namespace std;
int g_var = 188;
class A({
    
    
	const int x;//const修饰,则必须初始化参数列表,否则编译失败
	int y;
	int& z;//引用类型变量,则必须初始化参数列表,否则编译失败
	public:
	A():x(100),y(199),z(g_var){
    
    
	/*初始化参数列表只能在构造函数的()和{}之间;const修饰的和引用
		类型的成员都必须初始化参数列表。其他类型的成员无此限制*/	
	//这里的:x(100)就是给x赋初值100。当然你要是给y初始化也没啥问题
	//对z初始化,z为g_var别名
		
	}
	void show(){
    
    
		cout << x << ":" << y << endl;//x=100;y=199
	}
};	
int main(){
    
    
	A a;
	a.show();
}

六、头文件和实现文件的分离

在实际开发中头文件和实现文件要分离
背景知识:首先你要直到什么是声明、定义、extern,去看这篇文章:

https://blog.csdn.net/weixin_45519751/article/details/108142857
程序举例:
mytime.h文件

#ifndef MYTIME_H
#define MYTIME_H
/*在c++的头文件中只能进行变量的声明、函数的声明,类的定义*/
extern int g_var;//变量声明而非定义。必须要有extern
void show();//函数声明
class Mytime{
    
    //类的定义
	private:
	int hour;
	int min;
	int sec;
	
	public:
	Mytime(int hour=0,int min=0,int sec=0);
	void show();
	void dida();
	void run();
	
	
};
#endif
	mytime.cpp文件
#include "mytime.h"
#include <iostream>
#include <iomanip>
#include <unistd.h>
using namespace std;
void show(){
    
    
	cout << "g_var" << g_var << endl;
}

/*来自于类中的函数,前要加上  类名:: */
Mytime::Mytime(int hour,int min,int sec):hour(hour),
min(min),sec(sec){
    
    
	/*解释一下hour(hour)
		第一个hour是类的成员变量,第二个hour是构造函数的参数,
		hour(hour)即把0赋值给成员变量hour
		咱们这里只不过让类的成员变量hour与构造函数的参数hour重名了而已
		*/
}	
void Mytime::show(){
    
    
	cout << setfill('0') << setw(2) << hour << ":" <<
	setw(2) << ":" << min << ":" << setw(2) << sec << '\r' << flush;
}
void Mytime::dida(){
    
    
	sleep(1);
	if(60 == ++sec){
    
    
		sec = 0;
		if(60 == ++min){
    
    
			min = 0;
			if(24 == ++hour){
    
    
				hour = 0;
			}
		}
	}
}
void Mytime::run(){
    
    
	::show();//这里调的全局函数show()
	while(1){
    
    
		show();//这里调的是类中的成员函数
		dida();//这里调的是类中的成员函数
	}
}
	testmytime.cpp文件
#include "mytime.h"
int g_var = 100;//这里是定义

int main(){
    
    
	Mytime mt;
	mt.run();
}
	运行结果:
		lioker:Desktop$ g++ ./*.cpp
		lioker:Desktop$ ./a.out 
		g_var100
		00:0:01

七、this指针

7.1 this指针概述

	在构造函数中this代表正在被构建的对象的地址
	在成员函数中,this指向调用这个函数的对象的地址。
	程序举例:
#include <iostream>
using namespace std;
class Mytime{
    
    
	int hour;
	int min;
	int sec;
	public:
	Mytime(int hour=0,int min=0,int sec=0){
    
    
		/*对hour赋上构造函数参数值0,可以用初始化参数列表
		但我们这里用this指针的方法实现*/
		this->hour = hour;
		this->min = min;
		this->sec = sec;
	}
	void show(){
    
    
		cout << "show()" << this << endl;//打印show()的this地址
		cout << this->hour << ":" << this->min << ":" << 
		this->sec << endl;
	}
};
int main(){
    
    
	Mytime mt;
	cout << "main mt=" << &mt << endl;
	mt.show();
}
	运行结果:
	lioker:Desktop$ g++ this.cpp 
	lioker:Desktop$ ./a.out 
	main mt=0x7ffe788792e0
	show()0x7ffe788792e0
	0:0:0
	总结:this就是类的一个对象的地址;当this指向mt这个对
	象时是一个地址,指向另一个对象就是另一个地址了

7.2 this的使用

	①用在函数的参数上
	②用于成员函数的返回值
	程序举例:
#include <iostream>
using namespace std;
/*声明类型Data 和 函数show()*/
class Data;
void show(Data *data);
/*写一个日期类型*/
class Data{
    
     
	public:
	int year;
	int month;
	int day;
	public:
	Data(int year=0,int month=0,int day=0){
    
    
		this->year = year;
		this->month = month;
		this->day = day;
	}
	void showSelf(){
    
    
		show(this);//this作为参数。
	}
	/*写一个成员函数,作用:调用一次day就加一*/
	Data* addOneDay(){
    
    
		day++;
		return this;/*这里的this就是对象data的地址,你把它
						返回,再->addOneDay(),就相当于调用
						了两次addOneDay()函数,即++day两次.
						即day=2*/
	}
	/*写法二:
		Data addOneDay(){
		day++;
		return *this;/*这里的*this就相当于你把data复制了一份。
					你把它这个复制的部分返回了,
					再addOneDay()(注:main函数中语句改为
					data.addOneDay().addOneDay();),其实是对复制的data
					中的day++而非data本身++。
					也就是day只加了一次,即day=1*/
	}
	*/
	/*写法三:
		Data& addOneDay(){
		day++;
		return *this;/*这里的*this就是对象data的值,(Data&引用
						即返回的还是data本身)。
					再.addOneDay()(注:main函数中语句改为
					data.addOneDay().addOneDay();),其实是对data中的
					day++。也就是day加了2次,即day=2*/
	}
	*/

};
/*写一个全局函数show()显示一个日期*/
void show(Data *data){
    
    
	cout << data->year << "-" << data->month <<
	"-" << data->day << endl; 
}
int main(){
    
    
	Data data;
	data.showSelf();
	data.addOneDay()->addOneDay();//则day变为2(++2次)
	data.showSelf();
}

八、析构函数

8.1概述

	析构函数和类型名相同,但函数名前有一个~
	在对象销毁之前,系统(编译器)自动调用一次析构函数
	析构函数是无参的;
	一个类型只有一个析构函数
	作用是:即在对象销毁之前,做一些清理工作

8.2析构函数程序举例:

(利用析构函数释放掉在构造函数中建立的堆内存)

#include <iostream>
using namespace std;
class A{
    
    
	int *parr;
	public:
	A(int size=0){
    
    
		parr = new int[size];//分配堆内存;可以在析构函数里释放掉
		cout << "A()" << endl;
	}
	~A(){
    
    
		detele[] parr;//释放掉parr堆内存
		parr=NULL;
		cout << "~A()" << endl;
	}
};	
		
int main(){
    
    
	/*A *parr = new A[5];
	delete[] parr;/*
	运行结果:
		5个A(),5个~A()
	*/
	
	A a(10);
}

猜你喜欢

转载自blog.csdn.net/weixin_45519751/article/details/108174863
今日推荐