结构化程序设计有什么不足?面向对象的程序设计又是如何改进这些不足的?(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
类和对象部分就暂时到这了,片尾扔几张总结的图