目录
- 一、类型与对象的概念
-
- 1.1什么是对象
- 1.2类型
- 二、如何在计算机中描述类型
-
- 2.1使用结构体(struct)对类型进行描述
- 2.2使用类(class)对类型进行描述
- 三、构造函数
-
- 3.1构造函数概述
- 3.2一个知识点
- 四、一个对象创建的过程
- 五、构造函数的应用
-
- 5.1 要点
- 5.2 初始化列表
- 六、头文件和实现文件的分离
- 七、this指针
-
- 7.1 this指针概述
- 7.2 this的使用
- 八、析构函数
-
- 8.1概述
- 8.2析构函数程序举例:
一、类型与对象的概念
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);
}