目录
1.在文件Array.h中声明了Array类,它管理一个int型动态数组,其部分声明如下:(30分)
2.在文件person.h中声明了CPerson类和CAddressed类,前者用来管理一个人的名字,后者从前者派生并增加管理该人住址,其部分声明如下:
复习卷A题目
一、选择最合适的一项填空(10分,每空1分)
1. 增加了一些新的运算符 2. 允许函数重载
3. 规定函数说明必须用原型 4. 封装性
5. 消息性 6. 继承性
7. 多态性 8. 重用性
9. 初始化数据成员 10. 初始化函数
11.初始化常数据成员 12. 初始化静态成员
13. 回收数据成员在堆上的空间 14. 回收数据成员占用的空间
15. 回收数据成员在栈上的空间 16. 构造函数和析构函数
17. 复制构造函数和赋值运算符 18. 复制构造函数和析构函数
19. > 20. ::
21. [] 22. <<
23. 是…一部分,或有一个 24.是一个
25. 单向的且可继承 26. 单向的但不可继承
27. 双向的且可继承 28. 双向的但不可继承
29. 组合类 30. 虚函数
31. 常成员函数 32. 纯虚函数
C++对C语言作了很多改进,其中引进了类和对象的概念使语言发生了质变,从而使C++支持面向对象程序设计。在面向对象程序设计中,( ① )的含义是指将对象的属性和方法形成一个不可分割的整体,对象只保留有限的对外接口,并尽可能隐藏对象内部的具体细节。
在封装一个类时,特别要注意构造函数和析构函数的设计,其中,构造函数的作用是( ② ),析构函数的作用是( ③ )。另外,如果类中有使用了堆内存的指针成员,为正确实现深复制的功能,我们需要为该类实现( ④ )。C++中的许多运算符都是可以重载的,不过有些仅可以重载为成员函数,例如( ⑤ )运算符;有些仅能重载为友元函数,例如( ⑥ )运算符;还有一个运算符不能被重载。
类之间有组合关系、聚合关系、继承与派生关系,还有友元关系。其中组合关系意味着类之间有( ⑦ )的关系,继承与派生关系意味着类之间有( ⑧ )的关系,友元关系是( ⑨ )。如果一个类有( ⑩ ),则称该类为抽象类,从而该类不可实例化。
①________ ② _______ ③ _______ ④ _______ ⑤ _______
⑥ ________ ⑦ _______ ⑧ _______ ⑨ _______ ⑩ _______
二、程序阅读题(30分)
1. 写出下面程序的输出结果。(10分)
#include <iostream>
using namespace std;
class A
{public:
A() : m_x(0) { cout << "A()" << endl; }
A(double x) : m_x(x) { cout << "A(int)" << endl; }
~A() { cout << "~A()" << endl; }
private:
double m_x;
};
class B
{public:
B(const A & a) : m_a(a) { cout << "B()" << endl; }
B(const B & r)
{ m_a = r.m_a; b = r.b; cout << "B(const B &)" << endl; }
~B() { cout << "~B()" << endl; }
private:
A m_a;
double b;
};
int main()
{ A a(1);
A a2 = a;
static B b(a);
B b2(b);
return 0;
}
2. 写出下面程序的输出结果。(10分)
#include<iostream>
using namespace std;
class A
{public:
A() { }
~A() { cout << "A::~A()" << endl; }
A(const A &) { cout << "A::A(const A &)" << endl; }
void func() { virfunc(); cout << "A::func()" << endl; }
virtual void virfunc() { cout << "A::virfunc()" << endl; }
};
class B : public A
{public:
B() { func(); }
~B() { cout << "B::~B()" << endl; }
void func() { virfunc(); cout << "B::func()" << endl; }
virtual void virfunc() { cout << "B::virfunc()" << endl; }
};
int main()
{ A * pA = new B;
B * pB = dynamic_cast<B*>(pA);
B b(*pB);
pA->func();
b.func();
delete pA;
return 0;
}
3. 写出下面程序的输出结果。(10分)
#include<iostream>
using namespace std;
class Expt {
public:
Expt() { cout << "Expt()" << endl; }
Expt(Expt & e) { cout << "Expt(Expt &)" << endl; }
virtual~Expt() { cout << "~Expt()" << endl; }
};
void Func() { throw Expt(); }
void main()
{
cout << "开始执行" << endl;
try {
try {
Expt e;
Func();
}
catch (Expt & e) { cout<<"Expt异常"<<endl; throw e; }
catch (...) { cout << "不明异常" << endl; }
}
catch (Expt & e) { cout << "Expt类型异常" << endl; }
cout << "继续执行" << endl;
}
三、程序设计问答题(60分)
1.在文件Array.h中声明了Array类,它管理一个int型动态数组,其部分声明如下:(30分)
......
class Except
{public:
Except(const char * p)
{ if (NULL != p)
{ int len = strlen(p) + 1;
pdata = new char[len];
strcpy(pdata, p);
}
else
pdata = new char(0);
}
virtual ~Except() { delete[] pdata; }
char * what() const { return pdata; }
private:
char * pdata;
};
class Array
{public:
Array(int d) { ++count; pdata = new int(d); length = 1; }
int & operator[](const int idx)
{ if (idx < 0 || idx >= length) throw Except("索引越界");
return pdata[idx];
}
Array & add_back(const int d)
{ ++length;
int * tmp = new int[length];
for(int i = 0; i < length - 1; ++i)
tmp[i] = pdata[i];
tmp[length - 1] = d;
delete[] pdata;
pdata = tmp;
return *this;
}
private:
int * pdata;
int length;
static int count;
};
其中封装了指向保存的整型数组的指针pdata、数组中元素的个数length和用于记录程序运行中当前Array类型的对象个数的静态变量count。假设上述未给出实现的函数及可能需要添加的函数均在实现文件Array.cpp中实现。请回答如下问题:
(1)如果不为该类设计析构函数,会存在什么问题?要解决这个问题,需要怎样设计析构函数。(5分)
(2)应该如何初始化静态成员count?请写出初始化的语句和能够输出count为0时的语句(如有必要,请设计必要的函数)。(6分)
(3)需要为该类设计复制构造函数吗?如果需要,请给出你的设计;如果不需要,请给出编译器提供的函数实现形式。(6分)
(4)请说明如下程序段:
const Array arr(100); cout << arr[0];
能否正常运行?如果能,输出是什么?如果不能,请提供修改方案(不能修改上述程序段)。(6分)
(5)为了使Array能够处理多种数据类型,请将其修改为类模板,只需要修改题目中已有的类声明部分(不含add_back函数),另外在类外实现Array的无参构造函数。(7分)
2.在文件person.h中声明了CPerson类和CAddressed类,前者用来管理一个人的名字,后者从前者派生并增加管理该人住址,其部分声明如下:
class CPerson
{public:
CPerson() { _name[0] = '\0'; }
CPerson(const char * name)
{ strcpy(_name, name); }
virtual void write(ostream & o)
{ o << strlen(_name) << " " << _name << " "; }
virtual CPerson * read(istream & in)
{ int len;
in >> len;
in.get();
in.read(_name, len);
_name[len] = '\0';
in.get();
return this;
}
private:
char _name[20];
};
class CAddressed : public CPerson
{
public:
CAddressed() { _addr[0] = '\0'; }
CAddressed(const char * name, const char * addr)
: CPerson(name)
{ if (NULL == addr)
_addr[0] = '\0';
else
strcpy(_addr, addr);
}
private:
char _addr[50];
};
回答下面的问题: (30分)
- 需要为CAddressed编写复制构造函数吗?假如我们自己实现这个函数,应该如何实现?(4分)
- 需要为CAddressed编写赋值运算符函数吗?假如我们自己实现这个函数,应该如何实现?(6分)
- CPerson类已经提供了将其封装的名字写入文本文件的函数write,但在其派生类CAddressed类中还没有。请为CAddressed编写对应的write函数。(5分)
- CPerson类已经提供了从文本文件中读取数据的函数read(文件的数据由提供的write函数写入),但在其派生类CAddressed类中还没有。请为CAddressed编写对应的read函数(文件的数据由上一问你编写的write函数写入)。(5分)
- 下面的函数功能如下:先定义了一个CPerson*的数组origin,它有三个元素;每个元素可以指向一个CPerson对象,也可以指向一个CAddressed对象;然后将该数组的数据写入到文本文件test.txt中;接着打开上述文件,将数据恢复到另一个数组restore中。不过,该函数中有A、B两处缺少了一些代码,请你补充完整。(10分)
void func()
{ CPerson * origin[3];
origin[0] = new CPerson("Zhao");
origin[1] = new CAddressed("Qian", "嘉园A座1024");
origin[2] = new CPerson("Sun");
ofstream out("test.txt");
int i;
for (i = 0; i < 3; ++i)
{ A:请补充此处代码 }
out.close();
for (i = 0; i < 3; ++i)
delete origin[i];
CPerson * restore[3];
ifstream in("test.txt");
for (i = 0; i < 3; ++i)
{ B:请补充此处代码 }
in.close();
for (i = 0; i < 3; ++i)
delete restore[i];
return 0;
}
复习卷A答案
一、选择
① 4 ② 9 ③ 13 ④ 17 ⑤ 21
⑥ 22 ⑦ 23 ⑧ 24 ⑨ 26 ⑩ 32
二、程序阅读题(30分)
1. A(int) ——1分
B() ——1分
A() ——1分
B(const B &) ——1分
~B() ——1分
~A() ——1分
~A() ——1分
~A() ——1分
~B() ——1分
~A() ——1分
2. B::virfunc() ——1分
B::func() ——1分
A::A(const A &) ——1分
B::virfunc() ——1分
A::func() ——1分
B::virfunc() ——1分
B::func() ——1分
A::~A() ——1分
B::~B() ——1分
A::~A() ——1分
3. 开始执行 ——1分
Expt() ——1分
Expt() ——1分
~Expt() ——1分
Expt异常 ——1分
Expt(Expt &) ——1分
~Expt() ——1分
Expt类型异常 ——1分
~Expt() ——1分
继续执行 ——1分
三、程序设计问答
1.
不为该类设计析构函数,会造成内存泄露,且对象计数不对。 ——2分
virtual ~Array() ——1分不定义虚的不扣分
{
--count; ——1分
delete[] pdata; ——1分
}
(2)
int Array::count = 0; ——2分
static int Array::get_count() ——2分(无static扣1分)
{
return count; ——1分
}
输出count为0时的语句:
cout << Array::get_count(); ——1分
(3)
需要设计复制构造函数。 ——2分
Array::Array(const Array & arr) ——1分
{
++count;
length = arr.length; ——1分
pdata = new int[length]; ——1分
memcpy(pdata, arr.pdata, length * sizeof(int)); ——1分,其他的复制方法也行
}
(4)
上面的程序无法运行,因为arr对象是一个常对象,而题目中提供的运算符不是常成员函数的形式,所以arr无法调用。为使上述程序能够运行,需要提供下标运算符的常成员函数的重载形式: ——2分
int Array::operator[](const int idx) const ——2分
{
if (idx < 0 || idx >= length) throw Except("索引越界");
return pdata[idx]; ——2分
}
(5)
template<class T> class Array ——1分
{
public:
Array( ); ——1分
T & operator[](const int idx) ——1分
{
if (idx < 0 || idx >= length) throw Except("索引越界");
return pdata[idx];
}
private:
T * pdata; ——1分
int length;
static int count;
};
template<class T> Array<T>::Array( ) ——2分
{ ++count; pdata = NULL; length = 0; } ——1分
2.
不需要。 ——1分
复制构造函数的实现:
CAddressed(const CAddressed & a) : CPerson(a) ——2分
{
strcpy(_addr, a._addr); ——1分
}
赋值运算符的实现:
不需要。 ——1分
赋值运算符函数的实现:
CAddressed & operator=(const CAddressed & a) ——1分
{
if (this != &a) ——1分
{
CPerson::operator=(a); ——1分
strcpy(_addr, a._addr); ——1分
}
return *this; ——1分
}
void write(ostream & o) ——1分
{
CPerson::write(o); ——2分
o << strlen(_addr) << " " << _addr << " "; ——2分
}
CPerson * read(istream & in) ——1分
{
CPerson::read(in); ——1分
int len;
in >> len;
in.get(); ——1分
in.read(_addr, len);
_addr[len] = '\0'; ——1分
in.get();
return this; ——1分
}
A处的程序:
int flag = 0; ——1分
if (dynamic_cast<CAddressed*>(origin[i]) != NULL)——2分
flag = 1;
out << flag << " ";
origin[i]->write(out); ——2分
B处的程序:
int flag; ——1分
in >> flag;
if (0 == flag)
restore[i] = new CPerson;
else
restore[i] = new CAddressed; ——2分
restore[i]->read(in); ——2分