面向对象实验 ——(二)类与对象

以前的博客:
C++ 构造函数+析构函数+函数参数的传递
C++ 模块累积的理解

一、实验目的及要求

内容:
(1)定义一个具体的类(如Point类),该类应包含构造函数、析构函数;
(2)定义一个组合类(如Line类),该组合类应该含有(1)中所定义的基本类内嵌对象,定义该组合类的构造及析构函数;
(3)利用所定义的组合类实现一定的具体功能。
要求:
1)掌握对象与类的基本概念、定义与使用;
(2)构造函数、析构函数的定义与使用;
(3)组合类的用法;
(4)记录并分析实验结果,撰写实验报告。

二、实验过程及实验结果分析

1.模块的引入:
万物皆模块。对于模块没有一个明确的定义,可以把一个int变量看成是一个模块,可以把一个for语句看成是一个模块,也可以把一个函数看成是一个模块。

2.复杂模块类、对象:
在C++中,引入了类对象,可以把类对象看成是一个模块。
(1)类的语法描述和抽象意义
从信息封装和屏蔽角度来说,类模块的成员分为内部成员和对外接口;从数据操作角度来说,类的成员分为数据成员和函数成员,后者即操作数据的成员。
(2)类的对外可见性
由模块定义了对外展示哪些接口。
必须满足:仅对外公开接口,但是模块内部的所有元素都是相互可见的。
(3)存储
在C语言里,结构体也相当于一个模块,先来回顾一下结构体的存储:结构体所占用的内存等于其各部分子元素变量的空间之和。
我们可以拓展到类和对象,对于数据成员来说,他的存储和结构体的存储类似;对于函数成员来说,类仅仅是给函数提供了一个作用域。
需要注意的是,两成员的地位平等,均可以通过对象名,成员名调用。
(4)构造
一个模块,由要素集合和关联关系组成。对于一个int变量,我们可以直接进行赋值,赋值可以看成是已经构造好然后只需要组装。
对于类和对象来说,由于构造规则复杂且不同,必须要有构造函数,构造函数的语法是与类同名。
构造函数可以分为默认构造函数,无参构造函数,有参构造函数,拷贝构造函数,
对于这四类函数的区分可以结合函数重载进行理解。
(5)析构
C语言里的局部变量在使用完毕后要释放内存空间,而模块使用后也要把申请的资源释放掉,但是不能单纯的释放空间来达到限制模块的作用域的目的,这就需要析构函数把模块申请的资源释放掉。
(6)类与对象的函数参数传递
先来回想一下函数调用的过程:1.记录返回地址 2.为被调函数的执行分配资源,创造运行条件3.将控制交给被调函数执行。
函数在进行参数传递时,有两种方式,传值调用和传引用调用,这两种方式的区别就在于第二步资源的分配。前者会为实参数据在被调函数内部生成数据副本,后者是直接操作主调函数被的原始数据。
也就是说,对于类模块的参数传递中的传值方式,将会把主调函数中的实参转化为被调函数空间里的形参。
对于普通的变量(相当于C语言中的局部变量),这一过程仅仅是申请空间后进行赋值,执行完毕后系统会释放其内存,这也就保证了他的作用域。
而对于有自己的元素组成和构造方式的模块来说,仅仅是单纯的申请空间是无法生成新的模块的,这就需要用拷贝构造函数用实参生成形参。在执行完毕后,也不能单纯的释放空间来达到限制模块的作用域的目的,这就需要析构函数把模块申请的资源释放掉。
对于参数传引用的方式,仅仅是对同一个数据模块,在主调函数里是一个名字,在被调函数里是一个名字,相当于一个人的大名和小名~在这种方式下,无论是简单的变量,还是复杂的类对象,处理和理解都是一样的。
实验代码:

#include <iostream>
using namespace std;
class Point{///定义一个类对象相当于一个模块
	private:
		int x,y;///类对象的数据成员,对外不可见
	public:///类对象的对外接口
    ///构造函数:与类同名,初始化对象的数据成员,使得小模块形成完整模块
		Point(int px,int py):x(px),y(py){
		  cout<<"普通构造函数生成"<<this<<endl;;
		  ShowPoint();
		}
    ///拷贝构造函数:完成完整模块之间的复制
		Point(Point& p):x(p.x),y(p.y){
		  cout<<"拷贝构造函数生成"<<this<<endl;
		  ShowPoint();
		}
    ///显示函数
		void ShowPoint(){ cout<<"("<<x<<","<<y<<")"<<this<<endl;}
    ///析构函数:在类前面加~,释放模块申请的资源
		~Point(){cout<<"析构函数调用"<<this<<endl;}
};
///void ShowPointInfo(Point p) 传值调用:为实参数据在被调函数内部生成数据副本
///void ShowPointInfo(Point& p) 传引用调用:直接操作主调函数被的原始数据
void ShowPointInfo(Point p){
	cout<<"ShowPointInfo begin"<<endl;
	p.ShowPoint() ;
    cout<<"ShowPointInfo end"<<endl;
}
///传值调用需要用拷贝构造函数用实参生成形参,在执行完毕后,需要析构函数把模块申请的资源释放掉
int main(){
	Point pa(3,4);
	ShowPointInfo(pa);
    cout<<endl;
	return 0;
}

运行结果:
在这里插入图片描述

过程分析:
首先调用构造函数对类对象的数据成员进行初始化,构造出类对象A
然后在函数传值调用时调用拷贝构造函数将主调函数里的实参转为被调函数里的形参
这一过程是生成了一个新的类对象B
被调函数执行完毕后,类对象B被析构,返回主调函数
主调函数执行完毕,类对象A被析构,程序结束

3.组合类
即模块累积。
通俗的解释就是在构造时元素构成小模块,小模块构成大模块。
析构的过程正好相反,大模块析构后小模块被暴露,小模块再被析构
要注意:先构造的后析构
实验代码:

#include <bits/stdc++.h>
using namespace std;
class Point{///定义一个类对象相当于一个模块
	private:
		int x,y;///类对象的数据成员,对外不可见
	public:///类对象的对外接口
    ///构造函数:与类同名,初始化对象的数据成员,使得小模块形成完整模块
		Point(int px,int py):x(px),y(py){
		  cout<<"普通构造函数生成"<<this<<endl;;
		  ShowPoint();
		}
    ///拷贝构造函数:完成完整模块之间的复制
		Point(Point& p):x(p.x),y(p.y){
		  cout<<"拷贝构造函数生成"<<this<<endl;
		  ShowPoint();
		}
		void ShowPoint(){ cout<<"("<<x<<","<<y<<")"<<this<<endl;}
        int getx(){return x;}
        int gety(){return y;}
		~Point(){cout<<"析构函数调用"<<this<<endl;}
};
class Line{
    private:
        Point p1;
        Point p2;
        double len;
    public:
    ///pa->xp1 pb->xp2 拷贝构造函数
    ///xp1->p1 xp2->p2 拷贝构造函数
        /*Line(Point xp1,Point xp2):p1(xp1),p2(xp2){///构造函数
            cout<<"Line的构造函数"<<this<<endl;
        };
        Line(Line &L):p1(L.p1),p2(L.p2){
            cout<<"Line的拷贝构造函数"<<this<<endl;
        }*/
        Line(Point xp1,Point xp2);
        Line(Line &L);
        ~Line(){cout<<"Line的析构函数"<<this<<endl;}
        double getlen() {return len;}

};
///::预作用符 表示Line属于Line类
///内联函数:class内必须声明才能使用
Line::Line(Point xp1,Point xp2):p1(xp1),p2(xp2){
     cout<<"Line的构造函数"<<this<<endl;
     double x=p1.getx()-p2.getx();
     double y=p1.gety()-p2.gety();
     len=sqrt(x*x+y*y);
}
///p1(L.p1)调用Point的拷贝构造函数
Line::Line(Line &L):p1(L.p1),p2(L.p2){
    cout<<"Line的拷贝构造函数"<<this<<endl;
}
void ShowPointInfo(Point p){
	cout<<"ShowPointInfo begin"<<endl;
	p.ShowPoint() ;
    cout<<"ShowPointInfo end"<<endl;
}
///加Line的拷贝构造函数L2(L1)
int main(){
	/*Point pa(3,4);
	ShowPointInfo(pa);
    cout<<endl;*/
    Point pa(3,4);
    Point pb(10,9);

    Line L1(pa,pb);
    cout<<"L2***********"<<endl;
    Line L2(L1);
    /*cout<<"L1 start point:";
    pa.ShowPoint();
    puts("");

    cout<<"L1 end point:";
    pb.ShowPoint();
    puts("");

    cout<<"The lengh of L1 is:"<<L1.getlen()<<endl;*/
	return 0;
}

以下结合this指针分析程序执行过程
运行结果:
普通构造函数生成0x6dfec8 ->根据参数构造pa
(3,4)0x6dfec8
普通构造函数生成0x6dfec0 ->根据参数构造pb
(10,9)0x6dfec0
拷贝构造函数生成0x6dfed0 ->根据pb生成xp2
(10,9)0x6dfed0
拷贝构造函数生成0x6dfed8 ->根据pa生成xp1
(3,4)0x6dfed8
拷贝构造函数生成0x6dfea8 ->根据xp1生成p1
(3,4)0x6dfea8
拷贝构造函数生成0x6dfeb0 ->根据xp2生成p2
(10,9)0x6dfeb0
Line的构造函数0x6dfea8 ->构造出L1
析构函数调用0x6dfed8 ->L1构造完成,xp1析构
析构函数调用0x6dfed0 ->xp2析构
L2*********** ->以下为L2的拷贝构造
拷贝构造函数生成0x6dfe90 ->根据L1.p1构造L2的p1
(3,4)0x6dfe90
拷贝构造函数生成0x6dfe98 ->根据L1.p2构造L2的p2
(10,9)0x6dfe98
Line的拷贝构造函数0x6dfe90 ->L2拷贝构造成功
Line的析构函数0x6dfe90 ->L2析构
析构函数调用0x6dfe98 ->L2.p2析构
析构函数调用0x6dfe90 ->L2.p1析构
Line的析构函数0x6dfea8 ->L1析构
析构函数调用0x6dfeb0 ->L1.p2析构
析构函数调用0x6dfea8 ->L1.p1析构
析构函数调用0x6dfec0 ->pa析构
析构函数调用0x6dfec8 ->pb析构

终于完成了整理笔记的心愿?

posted @ 2020-08-07 19:09  OvO1  阅读(181)  评论(0编辑  收藏  举报