C++面向对象程序设计_期末考试真题分析

日志

  1. 2023-06-06 更新了卷一的部分内容
  2. 2023-06-21 更新了卷一的部分内容
  3. 2023-06-30 更新了卷一的部分内容
  4. 2023-07-01 卷一更新完结
  5. 2023-07-01 更新了卷二的部分内容
  6. 2023-07-02 考前更新到卷二的(二、7)
  7. 2023-070-2 考后放置试卷文件链接
  8. 2024-01-18 修改文章标题,完善部分题目的小标题

前言


考试形式是笔试,考试时间是2h,建议不要使用任何第三方工具,纯思考以及纯手写

卷一


一、语法分析题(每题5分,共30分)

以下每题的代码都有一个错误。在不改动Test函数代码的前提下,请修改或补充类的定义,使代码正确。
注:所有程序都含有相应的头文件
所有的主函数都是void main(){ Test(); }

1、类的调用权限

class Time
{
int hour,minute,second;
public:
Time(int hour,int minute=20,int second=10):hour(hour),minute(minute),second(second){ }
virtual ~Time() { Output(); cout<<"~Time"<<endl; }
void Output(){ cout<<hour<<"-"<<minute<<"-"<<second<<endl; }
};
void Test()
{
Time *pt=new Time(13);
cout<< pt->hour<<"-"<< pt->minute<<"-"<< pt->second<<endl;
delete pt;
}

考点:类的调用权限
显然Test()函数中调用了类中的私有成员,这是不合法的,把类中的私有成员设置为公有即可

2、有动态申请空间的复制构造函数重载

class A
{
int *p;
public:
A(int x){ p=new int; *p=x; }
virtual ~A(){ delete p; }
};
void Test()
{
A a1(1);
A a2(a1);
}

考点:有动态申请空间的复制构造函数重载
显然,在复制构造的时候,由于对象中有动态申请的空间,因此需要重载复制构造函数
在类中重载一个复制构造函数即可

A(const A& obj)
{
p = new int;
*p = *obj.p;
}

3、静态变量

class A
{
int x;
public:
A(int x):x(x){}
~A(){}
static void Add(){ x++; }
void Output(){ cout<<x<<endl; }
};
void Test()
{
A *pAs[2];
pAs[0]=new A(1); pAs[1]=new A(2);
pAs[0]->Add(); pAs[1]->Add();
pAs[0]->Output(); pAs[1]->Output();
}

考点:静态变量
显然,类的方法中有静态方法操作动态数据的错误操作,删除方法前面的static关键字即可

4、类的语法知识

struct Node
{
int data; Node *next;
Node(int data)
{
this->data=data; this->next=NULL;
}
};
class LinkList
{
Node *head;
public:
LinkList() { head=NULL; }
virtual ~LinkList()
{
while(head!=NULL)
{
Node *p=head; head=head->next; delete p;
}
}
void Push(int x)
{
Node *newp=new Node(x); newp->next=head; head=newp;
}
}
void Test()
{
LinkList L;
L.Push(1); L.Push(2); L.Push(3);
}

考点:类的语法知识
…是不是看了半天,其实是类的最后没有加分号…(就当复习一遍单链表的删除结点与头插入hhh)

5、虚函数

class A
{
protected:
int a;
public:
A(int a):a(a){ }
void Output(){ cout<<a<<" "; }
};
class A1: public A
{
int a1;
public:
A1(int a,int a1):A(a),a1(a1){ }
void Output()
{
A::Output();
cout<<a1<<endl;
}
};
void Test()
{
A1 a1(1,2); a1.Output();
}

这个程序在本地Clion上也是可以跑的,但是会有警告Function 'Output' hides a non-virtual function from class 'A'
考点:虚函数
为了自定义子类A1中与基类同名的函数Output,需要在基类的同名函数Output前加上virtual关键词

6、虚函数与纯虚函数

class A
{
public:
virtual void Do()=0;
};
class A1: public A
{
public:
virtual void Do(){ cout<<"A1.Do()"<<endl; }
};
class A2: public A
{
public:
virtual void Do(){ cout<<"A2.Do()"<<endl; }
};
void Test()
{
A *pAs[3];
pAs[0]=new A; pAs[1]=new A1; pAs[2]=new A2;
for(int i=0; i<3; i++) pAs[i]->Do();
}

考点:虚函数与纯虚函数
由于不能动Test中的内容,那么由于Test中有创建A对象这句话,我们就可以知道A不是一个抽象类,不可以有纯虚函数,因此将A中的纯虚函数改成虚函数即可
当然了,如果可以修改Test中的内容,我们可以删掉创建A对象的这句话pAs[0]=new A;,同时i1开始枚举进行Output

二、程序分析题(每题5分,共40分)

读程序,写出Test函数的输出结果。

1、动态申请空间的析构调用

class Date
{
int year,month,day;
public:
Date(int year,int month=2,int day=26):year(year),month(month),day(day){ }
virtual ~Date() { cout<<"~Date "; Output(); }
void Output(){ cout<<year<<"-"<<month<<"-"<<day<<endl; }
};
void Test()
{
Date *pd1=new Date(2019),*pd2=new Date(2019,6,18);
pd1->Output(); pd2->Output();
delete pd1; delete pd2;
}

考点:动态申请空间的析构调用

输出:

2019-2-26
2019-6-18
~Date 2019-2-26
~Date 2019-6-18

2、对象的析构顺序

class myInt
{
int *p;
public:
myInt(int x=0) { p=new int; *p=x; }
virtual ~myInt(){ cout<<"~myInt:"<<*p<<endl; delete p; }
friend ostream& operator<<(ostream &o,myInt &i){ return o<<*(i.p); }
};
void Test()
{
myInt xs[3]={myInt(1),myInt(2),myInt(3)};
for(int i=0; i<3; i++)
cout<<xs[i]<<" ";
cout<<endl;
}

考点:对象的析构顺序

输出:

1 2 3
~myInt:3
~myInt:2
~myInt:1

3、运算符重载后的调用顺序

class myInt
{
int *p;
public:
myInt(int x=0) { p=new int; *p=x; }
myInt(myInt &i){ p=new int; *p = *(i.p); }
virtual ~myInt(){ delete p; }
myInt operator+(myInt &i)
{
myInt ans(*this);
*(ans.p) += *(i.p); cout<<"+:"<<*(ans.p)<<endl;
return ans;
}
myInt &operator=(const myInt &i)
{
if(this==&i) return *this;
*p=*(i.p); cout<<"=:"<<*p<<endl;
return *this;
}
};
void Test()
{
myInt x,x1(1),x2(2),x3(3);
x=x1+x2+x3;
}

考点:运算符重载后的调用顺序
对于加减,重载的函数调用顺序是从左到右,对于赋值运算符的调用顺序是从右到左

输出:

+:3
+:6
=:6

4、冒泡排序

class Complex
{
int real,image;
public:
Complex(int real=0, int image=0):real(real),image(image){}
bool operator>(Complex &c)
{
return real<c.real || (real==c.real && image<c.image);
}
friend ostream & operator<<(ostream &o, Complex &c)
{
return o<<c.real<<"+"<<c.image<<"i"<<endl;
}
};
void Test()
{
Complex cs[]={Complex(2,3),Complex(1,2),Complex(2,4)}; int n=3;
for(int i=0; i<n-1; i++)
for(int j=n-1; j>i; j--)
if(cs[j-1]>cs[j])
{
Complex tmp;
tmp=cs[j-1]; cs[j-1]=cs[j]; cs[j]=tmp;
}
for(i=0; i<n; i++) cout<<cs[i];
}

考点:冒泡排序
注意!这里的大于号重载的逻辑是小于号!

输出:

2+4i
2+3i
1+2i

5、多继承的构造与析构

class A
{
public:
A(){ cout<<"A"<<endl; }
~A(){ cout<<"~A"<<endl; }
};
class B
{
public:
B(){ cout<<"B"<<endl; }
~B(){ cout<<"~B"<<endl; }
};
class C: public A,public B
{
public:
C(){ cout<<"C"<<endl; }
~C(){ cout<<"~C"<<endl; }
};
void Test()
{
C c;
}

考点:多继承的构造与析构
爹生子生,子死爹死

输出:

A
B
C
~C
~B
~A

6、多继承之作用域运算符

class A
{
public:
void DoA(){ cout<<"DoA()"<<endl; }
void Do (){ cout<<"ADo()"<<endl; }
};
class B
{
public:
void DoB(){ cout<<"DoB()"<<endl; }
void Do (){ cout<<"BDo()"<<endl; }
};
class C: public A,public B
{
public:
void Do()
{
DoA(); DoB();
cout<<"DoC()"<<endl;
A::Do();
B::Do();
}
};
void Test()
{
C c;
c.Do();
}

考点:多继承之作用域运算符

输出:

DoA()
DoB()
DoC()
ADo()
BDo()

7、多态性之纯虚函数

class A
{
public:
virtual void Output() = 0;
virtual ~A (){ cout << "~A" << endl; }
};
class A1 :public A
{
public:
void Output() { cout << "OutputA1" << endl; }
virtual ~A1 (){ cout << "~A1" << endl; }
};
class A2 :public A
{
public:
void Output() { cout << "OutputA2" << endl; }
virtual ~A2 (){ cout << "~A2" << endl; }
};
void Test()
{
A *pAs[2];
pAs[0]=new A1(); pAs[1]=new A2();
for(int i=0; i<2; i++) pAs[i]->Output();
for(int i=0; i<2; i++) delete pAs[i];
}

考点:多态性之纯虚函数
基类中的纯虚函数作为接口,在两个子类中重写Output函数,最后注意一下析构的顺序即可

输出:

OutoutA1
OutoutA2
~A1
~A
~A2
~A

8、静态成员

class A
{
private:
static int n;
int a;
public:
A(int a):a(a) { n++; }
~A(){ n--; }
static int f1() { return n; }
static void f2(A &obj)
{
obj.a++; n++;
cout<<obj.a<<" "<<n<<endl;
}
};
int A::n=10;
void Test()
{
A a(1), *pa=new A(2);
cout<<A::f1()<<endl;
A::f2(a);
delete pa; cout<<A::f1()<<endl;
}

考点:静态成员
补充一下静态成员的知识点

  • 所谓静态,对于面向对象时的类来说,其实就是可以在一个对象创建出来之前就可以访问类中的一些内容。
  • 静态成员不属于对象中的内容,而属于类中的内容。因此在通过一个对象调用类中的静态成员时,既可以使用对象.方法()的方法,也可以通过作用域运算符类名::静态成员名来调用
  • 静态变量
    注意点是:只可以在类外通过作用域运算符进行初始化,否则会被默认编译初始化为0
  • 静态函数
    没有this指针的方法,因为静态函数不被允许调用非静态函数

静态函数主要用来修改静态变量而存在的


本题中主要是弄懂静态函数的调用方法以及静态成员被改变后的情况

输出:

12
2 13
12

三、编程题(每题10分,共30分)

1、

下面仅给出myVector类的数据成员定义和部分成员函数定义。要求补充相关函数(拷贝构造函数、运算符[]和=的重载函数、相关的成员函数)的定义,使之能满足Test函数的功能需求。

#define N 10
class myVector
{
int *p; int n;
public:
myVector() { p=new int[N]; n=0; }
virtual ~myVector(){ delete []p; }
};
void Test()
{
myVector v1;
for(int x=1; x<5; x++) v1.Append(x);
myVector v2(v1);
for(int i=0; i<v2.Length(); i++) v2[i]=v2[i]+10;
}

添加以下公有函数

int& operator[] (int i) { return p[i]; }
int Length() { return n; }
void Append(int x) { p[n++] = x; }
myVector(myVector& v)
{
p = new int[N];
for (int i = 0; i < v.n; i++)
p[i] = v[i];
}

2、

下面仅给出myMatrix类的数据成员定义和部分成员函数定义。要求补充相关函数(拷贝构造函数、运算符()重载函数)的定义,使之能满足以下Test函数的功能需求。

class myMatrix
{
int *p; int row,col;
public:
myMatrix(int row=1,int col=1)
{
this->row=row; this->col=col;
p=new int[row*col];
}
virtual ~myMatrix(){ delete []p; }
friend ostream& operator<<(ostream &o,myMatrix &m)
{
for(int i=0; i<m.row; i++)
for(int j=0; j<m.col; j++)
o<<m.p[i*m.col+j]<<" ";
return o;
}
};
void Test()
{
myMatrix m1(2,3); int v=0;
for(int i=0; i<2; i++)
for(int j=0; j<3; j++)
m1(i,j)=v++;
myMatrix m2(m1); cout<<m2; // 输出0 1 2 3 4 5
}

添加以下公有函数

int& operator()(int i, int j)
{
return *(p + i * col + j);
}
myMatrix(const myMatrix& obj)
{
row = obj.row, col = obj.col;
p = new int[row * col];
for (int i = 0; i < row * col; i++)
p[i] = obj.p[i];
}

拷贝构造的另一种写法为,但是不可以加const关键词,不够安全

myMatrix(myMatrix& obj)
{
row = obj.row, col = obj.col;
p = new int[row * col];
for (int i = 0; i < row; i++)
for (int j = 0; j < col; j++)
(*this)(i, j) = obj(i, j);
}

3、

下面仅给出图形类的数据成员定义和部分成员函数定义。要求补充相关函数的定义,使之能满足以下Test函数的功能需求。

class Elem
{
public:
virtual void Draw()=0;
};
class Circle: public Elem
{
int r; // 半径
public:
Circle(int r):r(r){}
};
class Rectangle: public Elem
{
int wx,wy; // 长,宽
public:
Rectangle(int wx,int wy):wx(wx),wy(wy){}
};
class Elems
{
Elem *Es[10]; int n;
public:
Elems():n(0){}
~Elems()
{
for(int i=0; i<n; i++) delete Es[i];
}
};
void Test()
{
Elems es;
es.Append(new Circle(1));
es.Append(new Circle(10));
es.Append(new Rectangle(1,2));
es.Draw();
}
输出:
Draw Circle:1
Draw Circle:10
Draw Rectangle:1,2

在两个子类中,分别添加以下函数

virtual void Draw()
{
cout << "Draw Circual:" << r << endl;
}
virtual void Draw()
{
cout << "Draw Rectangle:" << wx << "," << wy << endl;
}

Elems类中添加以下公有函数

void Append(Elem* e)
{
Es[n++] = e;
}
virtual void Draw()
{
for (int i = 0; i < n; i++)
Es[i]->Draw();
}

卷二

一、简答题((每小题 5 分,共 15 分)

1、

问:什么是默认构造函数,它的定义方法和作用?

答:

  • 默认构造函数是一种特殊的构造函数,它没有参数。当创建一个类的对象时,如果没有提供任何初始化式,则会使用默认构造函数。
  • 在C++中,默认构造函数的定义方法为:第一种构造函数没有参数,即是A()形式的;第二种构造函数的全部参数由缺省值提供,A(int a=0,int b=0)。
  • 作用是:当创建一个类的对象时,如果没有提供任何初始化式,则会使用默认构造函数进行初始化。

2、

问:解释指针和引用的主要区别

答:

  • 引用必须被初始化,但是不分配存储空间。指针不声明时初始化,在初始化的时候需要分配存储空间。
  • 引用初始化后不能被改变,指针可以改变所指的对象。
  • 不存在指向空值的引用,但是存在指向空值的指针。
  • 引用必须在定义时进行初始化,而指针可以在定义之后进行初始化。
  • 引用只能用于已经存在的变量,而指针可以用于任何类型的变量。

3、

问:为什么多态基类的析构函数一般声明为 virtual 析构函数?

答:

  • 多态基类的析构函数一般声明为virtual析构函数是为了防止内存泄漏。
    如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。
    所以,为了防止这种情况的发生,C++中基类的析构函数应采用virtual虚析构函数 。

二、程序分析题(每题 5 分,共 40 分)

读程序,写出main函数的输出结果

1、值传递与引用传递 & 静态变量

#include <iostream>
using namespace std;
int f(int& x, int y = 1)
{
static int a = 1;
int b = 2;
x += a++;
y += b++;
return x + y;
}
int main()
{
int a = 0, b = 1;
cout << f(a) << endl;
cout << a << ',' << b << endl;
cout << f(a, b) << endl;
cout << a << ',' << b << endl;
return 0;
}

考点:值传递与引用传递 & 静态变量

输出

4
1,1
6
3,1

2、引用 & 常量与变量

#include <iostream>
using namespace std;
class A
{
int a, b;
public:
A(int x, int y) : a(x), b(y) {}
int f() { return a; }
int f(int& x)
{
a += x;
return a + b;
}
int f(const int& x)
{
b += x;
return a + b;
}
};
int main()
{
A a(1, 2), b(3, 4);
int c = 10;
cout << a.f() << endl;
cout << a.f(100) << endl;
cout << b.f(c) << endl;
return 0;
}

考点:引用 & 常量与变量
这道题还是有点意思的。抛开引用这个知识点不谈,关键在于C++中会将一个纯数字(题中的100)识别为const类型,想想也能理解,因为传了个数字,你也没法修改啊hhh

输出

1
103
17

3、生死与拷贝

#include <iostream>
using namespace std;
class A
{
int x, y;
void show() { cout << x << "-" << y << endl; }
public:
A(int a, int b) : x(a), y(b)
{
cout << "Construct A ";
show();
}
A(const A& a) : x(a.x + 10), y(a.y + 10)
{
cout << "Copy Construct A ";
show();
}
~A()
{
cout << "Destruct A ";
show();
}
};
int main()
{
A a(10, 20), * pa;
pa = new A(30, 40);
A b(a);
delete pa;
return 0;
}

考点:生死与拷贝

输出

Construct A 10-20
Construct A 30-40
Copy Construct A 20-30
Destruct A 30-40
Destruct A 20-30
Destruct A 10-20

4、静态成员

#include <iostream>
using namespace std;
class A
{
private:
static int n;
int data;
public:
A(int x = 1)
{
data = x;
n += data;
}
~A()
{
n -= data;
}
static int GetN()
{
return n;
}
void print() { cout << "n=" << n << ",data=" << data << endl; };
};
int A::n = 10;
int main()
{
A a;
a.print();
A* p = new A(2);
p->print();
A c(3);
delete p;
cout << "n=" << A::GetN() << endl;
return 0;
}

考点:静态成员
对于我来说,最容易犯的错是忽视静态变量的初始值,比如此题n被初始化为10

输出

n=11,data=1
n=13,data=2
n=14

5、继承下的动态生死

#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << "A::A()called.\n";
}
virtual ~A()
{
cout << "A::~A()called.\n";
}
};
class B : public A
{
public:
B(int i)
{
cout << "B::B()called.\n";
buffer = new char[i];
}
virtual ~B()
{
delete[]buffer;
cout << "B::~B()called.\n";
}
private:
char* buffer;
};
void fun(A* a)
{
delete a;
}
int main()
{
A* b = new B(10);
fun(b);
return 0;
}

考点:继承下的动态生死

输出

A::A()called.
B::B()called.
B::~B()called.
A::~A()called.

6、虚继承

#include<iostream>
using namespace std;
class A
{
public:
int n;
A()
{
n = 10;
cout << "A" << endl;
}
};
class B : virtual public A
{
public:
B()
{
n = 20;
cout << "B" << endl;
}
};
class C : virtual public A
{
public:
C()
{
n = 30;
cout << "C" << endl;
}
};
class D : public B, public C
{
public:
D() { n = 40; }
int getn() { return B::n; }
};
int main()
{
D d;
cout << d.getn() << "," << d.B::n << "," << d.C::n << endl;
return 0;
}

考点:虚继承

输出

A
B
C
40,40,40

7、前++ & 后++的重载

#include <iostream>
using namespace std;
class Add
{
int n;
public:
Add(int i) { n = i; }
int& operator++()
{
n += 5;
return n;
}
int operator++(int)
{
int t = n;
n += 2;
return t;
}
void show() { cout << n << '\t'; }
};
int main()
{
Add A(5), B(5);
++A;
B++;
A.show();
B.show();
cout << endl;
return 0;
}

考点:前++ & 后++的重载
又是一个知识盲点。

  • 前++的逻辑是先将当前对象++,再返回这个对象的引用;
  • 而后+=的逻辑是先将当前对象用临时对象保存下来,然后将当前对象++,再返回那个临时对象

输出

10 7




考前没有完全更新完所有的内容,特此将 卷1卷2 放在这里,静待有缘人~(密码均为6666

posted @   Mr_Dwj  阅读(148)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
点击右上角即可分享
微信分享提示