【C++的探索路6】类和对象习题课

习题课链接

结构化程序设计有什么不足?面向对象的程序设计又是如何改进这些不足的?(object oriented related)

结构化程序设计的不足:

① 结构化程序设计中,算法和函数结构分离,没有直观手段说明算法操作了哪些数据结构。

② 结构化程序设计也没有提供手段来限制数据结构可被操作的范围,任何算法均可以操作任何数据结构,容易出bug。

面向对象程序设计首先采用抽象的方法归纳同类事物的共同特点。然后运用封装,将数据的部分属性(对象的静态特征)和方法(函数)进行私有化。

面向对象的程序设计具备:抽象,封装,继承与多态四个基本的特点。

学生信息处理程序的实现(class related)

输入:姓名,年龄,学号,第一~四学年平均成绩  输出:姓名,年龄,学号,4年平均成绩。

for example

I: Tom 18 7817 80 80 90 70

O: Tom,18,7817,80

要求实现一个代表学生的类,并且所有成员变量私有。

程序1

#include<string>
#include<iostream>
using namespace std;
class Student {
private:
	string name;
	int age;
	int stunum;
	int Grade[4];
public:
	void StudentInit(string n,int a,int stu,int g[4]);
	void StudentInfo();
	double averGrade();
};
void Student::StudentInit(string n, int a, int stu, int g[4]) {
	name = n;
	age = a;
	stunum = stu;
	for (int i = 0; i < 4; ++i) {
		Grade[i] = g[i];
	}
}
double Student::averGrade() {
	int total = 0;
	for (int i = 0; i < 4; ++i) {
		total+=Grade[i];
	}
	return total / 4;
}
void Student::StudentInfo() {
	cout << name << ", " << age << ", " << stunum << ", "<<averGrade()<<endl;
}
int main()
{
	string name; int a, stu, g[4]; int*p;
	p = g;
	cin >> name >> a >> stu;
	for (int i = 0; i < 4; ++i) {
		cin>>g[i];
	}
	Student T;
	T.StudentInit(name, a, stu, p);
	T.StudentInfo();
    return 0;
}

如上,然而该程序有很多不足的地方,比如可以将初始化集成到类的内部进行操作,而不用写一长串。另外上述忽视了封装性,多此一举,经过改写可为下列程序:

#include<string>
#include<iostream>
using namespace std;
class Student {
private:
	string name;
	int age; 	int stunum;  	int Grade[4];
public:
	void StudentInit();   	void StudentInfo();
	double averGrade();
};
void Student::StudentInit() {
	cin >> name >> age >> stunum;
	for (int i = 0; i < 4; ++i) {
		cin >> Grade[i];
	}
}
double Student::averGrade() {
	int total = 0;
	for (int i = 0; i < 4; ++i) {
		total+=Grade[i];
	}
	return total / 4;
}
void Student::StudentInfo() {
	cout << name << ", " << age << ", " << stunum << ", "<<averGrade()<<endl;
}
int main()
{
	Student T;
	T.StudentInit();
	T.StudentInfo();
    return 0;
}

程序运行显示(constructor related)

#include<iostream>
using namespace std;
class Apple {
private:
	static int nTotalNumber;
public:
	Apple() {
		nTotalNumber++;
	}
	~Apple() {
		nTotalNumber--;
	}
	static void PrintTotal() {
		cout << nTotalNumber << endl;
	}
};
int Apple::nTotalNumber = 0;
Apple Func(const Apple&a) {
	a.PrintTotal(); return a;
}
int main() {
	Apple*p = new Apple[4];
	Func(p[2]);
	Apple p1, p2;
	delete[]p;
	p1.PrintTotal();
	return 0;
}

输出

4 1

分析:

结构上苹果类包括一个私有的静态成员变量nTotalNumber,一个实现nTotalNumber自增的构造函数以及一个nTotalNumber自减的析构函数与静态函数PrintTotal对nTotalNumber进行打印。

此外还有一个返回值类型为Apple类的Func函数

主程序内,首先动态分配了4个Apple对象。

nTotalNumber自增为4。

主程序进入到调用Func部分,首先打印出4,然后程序消亡减去1,nTotalNumber减为3

继续往下走,遇到两个新Apple类的对象:p1与p2,nTotalNumber增为5,接着delete掉p,数量减去4,最终nTotalNumber定格在1。

写出下列程序的运行结果(copy constructor related)

#include<iostream>
using namespace std;
class CSample {
public:
	int v;
	CSample() {
	}
	CSample(int n) :v(n){
	}
	CSample(CSample&x) { v = 2 + x.v; }
};
CSample PrintAndDouble(CSample o) {
	cout << o.v;//先打印
	o.v = 2 * o.v;//后加倍
	return o;
}
int main()
{
	CSample a(5);
	CSample b = a;
	CSample c = PrintAndDouble(b);
	cout << endl;
	cout << c.v << endl;
	CSample d;
	d = a;
	cout << d.v;
	return 0;
}

分析:

程序包含一个CSample类以及一个返回类型为CSample的PrintAndDouble函数

CSample类内包含供任意用户访问的整型变量v,以及三个构造函数

三个构造函数包括:

什么都不做的CSample(),将整数赋值给CSample的CSample(int n)以及复制构造函数CSample(CSample&x),复制构造函数的作用为对外来的x对象施行加2赋值。

PrintAndDouble函数的作用则为对对象打印和加倍


接下来来对程序进行分析

首先是CSample a(5),调用第二个构造函数,此时a的内部的v为5。

走到CSample b=a;这个时候回忆复制构造函数的内容:1,当用一个对象去初始化同类的另一个对象时,将引发复制构造函数的调用。2,作为形参的对象,是用复制构造函数初始化的,而且调用复制构造函数时的参数,就是调用函数时所给的实参。3,函数返回值为类的对象,则将引发复制构造函数的调用。

CSample b=a满足第一条,而且这不是赋值,是初始化,赋值将不会引发复制构造函数的调用。b中的成员变量v(b.v)变成了5+2=7

第三行CSample c=PrintAndDouble(b);这一段又是一个初始化语句,此外PrintAndDouble的参数为CSample对象,将两次引发复制构造函数的调用。

第一次进入PrintAndDouble中,打印b.v,而此时,b.v先调用复制构造函数+2得到9并且打印输出9,得到9后加倍得到b.v=18;

第二次调用复制构造函数并且初始化,c.v=b.v+2=20。

接着进行换行操作

换行结束后输出c.v=20。

该语句结束后 a.v=5,b.v=18,c.v=20;

输出20后进行则d=a的赋值操作,不会调用复制构造函数,此时d.v==a.v

应当输出5


因此,输出为

9

\n

20

5

总结:

1,初始化调用复制构造函数

2,赋值不调用复制构造函数

下面程序输出结果(constructor & *this related)

0

5

请填空

#include<iostream>
using namespace std;
class A {
public:
	int val;
	A(______) {
		val = n;
	};
	_____ GetObj() {
		return______;
	}
};
int main()
{
	A a;
	cout << a.val << endl;//0
	a.GetObj() = 5;
	cout << a.val << endl;//5
	return 0;
}

主程序分析

a> 主程序首先定义了一个A类对象a,并在下一行输出了0,说明这一段将调用构造函数。

b>程序下走,a.GetObj()=5,该段程序比较奇怪

首先成员函数在等号的左侧而不是右侧

其次,在赋值后a对象的val值发生了改变,变为5。

依据成员函数在等号的左侧,可以判断,该段类型返回值类型应当为引用,只有函数作为引用才可作为左值使用。

类的分析与填空

主程序实现了A a,并且a.val=0,则A的构造函数实现了0赋值的操作,此外函数体内部存在n,则空应该填int n=0;

进入GetObj()行,由于main程序中存在语句a.GetObj()=5;首先表明GetObj()的返回值为什么的 引用。与这一部分相关联的有两种解法。

其一种解法则比较简单粗暴,右边是int,则左边直接int&的类型,并且return val;

第二种解法,有点邪门,就是晕晕乎乎感觉是这样的。因为源程序的构造函数中,恰好val=n,所以可以利用构造函数作为桥接的方式。GetObj()的类型则为A& 返回*this,才会调用构造函数。如果val=n/2或者其他,则这种方式不可取。

不可取的程序如下

class A {
public:
	int val;
	A(int n = 0) {
		val = n/2;
		cout << "called" << endl;
	};
	A& GetObj() {
		return *this;
	}
};
int main()
{
	A a;
	cout << a.val << endl;//0
	a.GetObj() = 5;
	cout << a.val << endl;//5
	return 0;
}

知识点:

1,引用可以作为等号的左值

2,*this指向当前的对象

下面程序输出结果是 (封闭类)

5,5

5,5

填空

class Base {
public:
	int k;
	Base(int n):k(n){}
};
class Big {
public:
	int v; Base b;
	Big___{}
	Big___{}
};
int main() {
	Big a1(5);
	Big a2 = a1;
	cout << a1.v << "," << a1.b.k << endl;//5 5
	cout << a2.v << "," << a2.b.k << endl;//5 5
}

解:

一共包含两个类:Base类以及Big类,Base类的构造函数可以直接用int型数对k进行赋值。

而Big类内包含Base类,因此Big类为封闭类, b则为成员对象,在写Big的构造函数时应当对对象b进行初始化操作

主函数中显示一共存在两种构造函数调用形式,其中一种为Big a1直接用整形进行赋值的构造函数,另外一种则是用同类对象赋值的复制构造函数的形式

由于ax.v和ax.b.k均为5,因此是直接用数进行赋值,填空中分别填下述:

	Big(int t):v(t),b(t){}
	Big(const Big&a):v(a.v),b(a.v){}

Review

类和对象部分就暂时到这了,片尾扔几张总结的图



  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值