1.题目及分析
1.对象数组的析构顺序
#include <iostream>
using namespace std;
class T {
public:
~T() { cout << "Destroying..." << i << endl; }
void Setij(int a, int b) { i = a, j = b; }
int GetMuti() { return i * j; }
protected:
int i, j;
};
int main()
{
T* p = new T[5];
for (int j = 0; j < 5; ++j) p[j].Setij(j, j);
for (int k = 0; k < 5; ++k) cout << "Multi[" << k << "] is:" << p[k].GetMuti() << endl;
//程序结束并不会隐式地调用析构函数,需要显式调用析构函数
//析构顺序与构造顺序相反
delete[]p;
return 0;
}
2.浅拷贝的隐患
#include <iostream>
using namespace std;
class Vector {
public:
Vector(int s = 100);
~Vector();
int& Elem(int idx);
void Display();
void Set();
protected:
int size;
int* buffer;
};
Vector::Vector(int s)
{
buffer = new int[size = s]; //巧妙1: 初始化size的同时也初始化了buffer
}
Vector::~Vector()
{
cout << "扫尾工作..." << endl;
delete []buffer;
}
int& Vector::Elem(int idx)
{
if (idx < 0 || idx >= size)
{
cout << "error in index" << endl;
exit(-1);
}
return buffer[idx];
}
void Vector::Display()
{
for (int i = 0; i < size; ++i)
cout << Elem(i) << endl;
}
void Vector::Set()
{
for (int i = 0; i < size; ++i)
Elem(i) = i + 1; //巧妙2: 利用返回值为引用,直接修改了buffer(i)
}
int main()
{
Vector a(10);
Vector b(a);
a.Set();
b.Display();
return 0;
}
可以很清楚看到浅拷贝所造成的错误:在析构的时候会重复析构,这是由于浅拷贝时,b的buffer指针和a的buffer指针指向的是同一片空间
如何更改?
换为深拷贝!
即弃用默认拷贝构造函数,自己写一个拷贝构造函数
#include <iostream>
using namespace std;
class Vector {
public:
Vector(int s = 100);
Vector(const Vector& v); //拷贝构造函数
~Vector();
int& Elem(int idx);
void Display();
void Set();
protected:
int size;
int* buffer;
};
Vector::Vector(int s)
{
buffer = new int[size = s]; //巧妙1: 初始化size的同时也初始化了buffer
}
Vector::Vector(const Vector& v)
{
//照猫画虎 重新开辟空间,使得通过调用拷贝构造函数创建的对象的buffer指向另一片空间
this->buffer = new int[this->size = v.size];
//拷贝数据 [也可不加this指针进行访问]
for (int i = 0; i < size; ++i)
buffer[i] = v.buffer[i];
}
Vector::~Vector()
{
cout << "扫尾工作..." << endl;
delete []buffer;
}
int& Vector::Elem(int idx)
{
if (idx < 0 || idx >= size)
{
cout << "error in index" << endl;
exit(-1);
}
return buffer[idx];
}
void Vector::Display()
{
for (int i = 0; i < size; ++i)
cout << Elem(i) << endl;
}
void Vector::Set()
{
for (int i = 0; i < size; ++i)
Elem(i) = i + 1; //巧妙2: 利用返回值为引用,直接修改了buffer(i)
}
int main()
{
Vector a(10);
Vector b(a);
a.Set();
b.Display();
return 0;
}
改为深拷贝后,a、b对象不会相互影响,由于b未调用set()函数,所以其buffer里面的数据仍为随机值
delete p 还是 delete[]p ?
#include <iostream>
using namespace std;
int main()
{
int* p1 = new int(5); //只分配一个int类型的空间,并初始化为5
cout << *p1 <<" "<< p1[0] << endl;
delete p1;
int* p2 = new int[5]; // 指向数组
p2[0] = 1;
p2[1] = 10;
cout << *p2 << " " << p2[0] << " " << p2[1] << endl;
delete[] p2; //依次销毁p2所指向的完整完整空间
return 0;
}
delete []p 是指 释放p所指向的完整空间
记清楚两者的适用条件即可
类似的题,自行查阅
3.常数据成员的初始化
常数据成员名只可在初始化列表中进行
(上机在VS2019尝试后,发现在定义时初始化也能编译通过,但并不建议这样做)
4.默认构造函数
默认构造函数(default constructor)就是在没有显式提供初始化式时调用的构造函数。
它由不带参数的构造函数,或者为所有的形参提供默认实参的构造函数定义
①
class T{
public:
T(){}
protected:
int x;
}
②
class T{
public:
T(int xx = 0) {}
protected:
int x;
}
5.cin、cout所属类
B
cin 属于 istream类
cout 属于 ostream类
6.重载
A
类模板不可以重载,但是函数模板可以重载。
7.静态数据成员
静态数据成员指的是 static修饰的数据成员
A
8.多态
A
多态分为4类: 重载多态 、 强制多态、参数多态、包含多态
前两种又被称为专用多态
后两种又被称为通用多态
前三种的绑定工作在编译连接阶段即可完成
包含多态的绑定工作在程序运行阶段才可完成
8.联编
B
多态分为静态多态(编译时多态、静态联编、早绑定)和动态多态(运行时多态、动态联编、晚绑定)
9.内联函数
正确
10.引用
❌
11.static
static修饰的数据成员为该类的所有对象_共享___
12.构造Complex类
#include <iostream>
using namespace std;
class Complex {
public:
Complex(double rr = 0.0 ,double ii = 0.0):r(rr),i(ii){}
void show() { cout << r << " " << i << endl; }
Complex operator+(const Complex& c) {
return Complex(this->r + c.r, this->i + c.i);
}
private:
double r, i;
};
int main()
{
Complex c1(-1, 4), c2(2, 5);
c1.show();
c2.show();
Complex c3 = c1 + c2;
c3.show();
return 0;
}
13.静态成员函数
B
#include <iostream>
using namespace std;
class T {
public:
static void show_x() { return this->x; } //错误,this指针只存在于非静态函数内部
private:
static int x;
};
int T::x = 1; //静态数据成员类外初始化
int main()
{
return 0;
}
14.抽象类
#include <iostream>
using namespace std;
#include <algorithm>
const double PI = acos(-1);
class CShape {
public:
virtual double GetLength() const = 0;
};
class CSqure :public CShape {
public:
CSqure(double x, double y):x(x),y(y){}
double GetLength()const { return 2 * (x + y); }
private:
double x, y;
};
class CCircle :public CShape {
public:
CCircle(double r):r(r){}
double GetLength()const { return 2 * PI * r; }
private:
double r;
};
int main()
{
CSqure c1(1, 2);
cout << c1.GetLength() << endl;
CCircle c2(1);
cout << c2.GetLength() << endl;
return 0;
}
15.标识符
16.指针数组
17.不可嵌套定义,可嵌套调用
18.可见性 、存在性
19.虚函数
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
20.子类重写虚函数的权限
#include <iostream>
using namespace std;
class B {
public:
B(){}
B(int i) { b = i; }
virtual void virfun()
{
cout << "B::virfun() called.\n";
}
private:
int b;
};
class D :public B {
public:
D(){}
D(int i, int j) :B(i) { d = j; }
private:
int d;
void virfun()
{
cout << "D::virfun() called.\n";
}
};
void fun(B* obj)
{
obj->virfun();
}
int main()
{
D* pd = new D;
fun(pd);
return 0;
}
虽然在子类中声明了private,但其实仍然可以调用虚函数
虚函数编译时的访问权限仅仅是和调用者(指针、引用)的类型有关,编译器只要知道这个类型有一个可以访问的虚函数就行了,并不查看具体对象类型中该虚函数的访问权限
简而言之,基类是public权限即可成功调用虚函数
21.虚析构函数
22.重载、覆盖
23.继承
24.类与对象
25.友元关系
A
友元关系破坏了封装性,可提高程序的运行效率
25.公有继承
C
只可访问公有成员
26.多态
27.
28.面向对象
面向对象将数据和对数据的操作作为一个相互依赖,不可分割的整体,采用了数据抽象和信息隐蔽技术。
29.四要素
抽象性、封装性、继承性和多态性
30.嵌套
在C++中,函数的定义不可以嵌套,类的定义可以嵌套。
31.public 、 private、 protected访问控制属性
public: 声明公有类型成员,公有类型定义了类的外部接口
private:声明私有类型成员,只允许本类的函数成员访问,而类外部的任何访问都是非法的
即 私有成员隐藏在类中,类外部无法看到,实现了对访问控制权限的有效控制
protected:声明保护类型成员,保护类型与私有类型的性质类似,其差别在于继承和派生时派生类的成员函数可以访问基类的保护成员
32.构造函数、析构函数、拷贝构造函数
构造函数的作用就是在对象被创建时,利用特定的值构造对象,将对象初始化为一个特定的状态,使此对象有区别于其他对象的特征,完成从一般到具体的过程,构造函数在对象创建时由系统自动调用
析构函数与构造函数的作用几乎相反,他是用来完成对象被删除前的一些清理工作,即专门做扫尾工作。一般情况下,析构函数是在对象的生存期即将结束时,由系统自动调用,在调用完成之后,对象即消失,相应的内存空间也被释放
拷贝构造函数是一种特殊的构造函数,其形参是本类对象的引用,作用是使用一个已经存在的对象,去初始化一个新的同类的对象。
三种情况:用类的一个对象去初始化该类的另一个对象;函数形参为类对象,调用函数进行形实参结合时;函数的返回值为类对象,函数调用完成返回时
33.类的3种继承 public 、 private、 protected
不同的继承方式会导致不同访问属性的基类成员在派生类中的访问属性也有所不同
对于公有继承,基类的public、ptrotected在派生类中不变,而基类的private成员不可访问
对于私有继承,基类的public 、protected以private出现,private不可访问
对于保护继承,基类的public、protected以protected出现,private不可访问
34.派生类构造函数的执行次序
(1.如果该类有直接或间接的虚基类,按照被继承时声明的顺序执行虚基类的构造函数)
2. 调用基类的构造函数(按照被继承时声明的顺序)
3. 按照类定义出现的顺序,调用成员对象的构造函数
4. 执行派生类函数体中的内容
35.虚基类
当某类的部分或全部直接基类是从另一个基类共同派生而来时,这些直接基类中,从上一级基类继承而来的成员就拥有相同的名称,派生类的对象这些同名成员在内存中同时拥有多个拷贝,可以用作用域分辨符来唯一标识并访问他们,也可以将直接基类的共同基类设置为虚基类,这时,从不同路径继承过来的该类成员在内存中只有一份拷贝,解决了同名成员的唯一标识问题
声明虚基类后,虚基类成员在进一步派生过程中,和派生类一起维护一个内存数据拷贝
class 派生类名:virtual 继承方式 基类名
36.组合与继承?
组合和继承它们都使已有对象成为新对象的一部分,从而达到代码复用的目的,组合和继承其实反映了两种不同的对象关系
组合反应的是“有一个”(has-a)关系,如果类B中存在一个类A的内嵌对象,表示的是每一个B类型的对象都“有一个”A类型对象,A类型对象与B类型对象是部分与整体的关系
继承反应的是“是一个”(is-a)关系,如果类A是类B的公有基类,那么这表示每一个B类型都“是一个”A类型的对象,B类型的对象与A类型的对象是特殊和一般的关系
37.二义性
调用不同基类的同名成员时会产生二义性,在多重继承情况下,派生类有多个基类,如果这些基类有有同名成员,那么在派生类和派生类的对象中调用同名成员时,可能产生二义性
38.基类与派生类对象、指针、引用
派生类指针/引用可隐含转换为基类指针/引用
(从特殊到一般的指针转换是安全的,因此允许隐含转换;从一般到特殊的指针是不安全的,所以只可显式转换)
基类对象不可转换为派生类对象,派生类对象可转换为基类对象
当涉及多重继承时,基类指针转换为派生类指针,有时需要调整指针所存储的地址值
当涉及虚继承时,假设类A为类B的虚基类,则类B的指针可隐含转换为A类指针,而A类指针无法隐含转换为B类指针
39.多态
同样的消息被不同类型的对象接收时导致完全不同的行为,是对类的特定成员函数的再抽象
C++多态:强制、重载、参数、包含
40.抽象类
带有纯虚函数的抽象类
作用是:为一个类族建立一个公共的接口,使它们能够更有效地发挥多态特性
抽象类声明了一组派生类共同操作接口的通用语义,而接口的完整实现,即纯虚函数的函数体,需要派生类自己给出
抽象类的派生类并非一定要给出纯虚函数的实现,如果派生类没有给出纯虚函数的实现,那么这个派生类仍然是抽象类
41.虚构造函数、虚析构函数
C++不可声明虚构造函数,多态是不同对象对同一消息有不同的行为特性,虚函数作为运行过程中多态的基础,主要针对对象的,而构造函数是在对象产生之前运行的,则虚构造函数无意义
C++中可声明虚析构函数,析构函数地作用是在该类对象消亡前,进行一些必要的清理工作,如果一个类的析构函数是虚函数,那么由它派生而来的所有子类的析构函数也是虚函数。析构函数设置为虚函数之后,在使用指针引用时可以动态联编,实现运行时的多态,保证使用基类的指针就可调用适当的析构函数针对不同的对象进行清理工作
42.虚函数实操
#include <iostream>
using namespace std;
class BaseClass {
public:
virtual void fn1() { cout << "BaseClass::fn1()" << endl; }
void fn2() { cout << "BaseClass::fn2()" << endl; }
};
class DerivedClass:public BaseClass {
public:
void fn1() { cout << "DerivedClass::fn1()" << endl; }
void fn2() { cout << "DerivedClass::fn2()" << endl; }
};
int main()
{
DerivedClass d;
BaseClass* pb = &d;
DerivedClass* pd = &d;
pb->fn1(); //d
pb->fn2(); // b
puts("");
//要想访问BaseClass的虚函数fn1(),只可加上作用域分辨符来标识
pb->BaseClass::fn1(); //b
puts("");
pd->fn1(); //d
pd->fn2(); //d
return 0;
}
43.虚析构函数实操
当用派生类指针析构时,并不会发生错误
#include <iostream>
using namespace std;
class Base {
public:
~Base() { cout << "Base" << endl; }
};
class Derived :public Base {
public:
~Derived() { cout << "Derived" << endl; }
};
int main()
{
Derived* pd = new Derived;
delete pd;
return 0;
}
但当用基类指针析构时,会发生无法析构派生类的错误,为了使基类指针也可以调用派生类的析构函数,所以要将其设置为虚析构函数
#include <iostream>
using namespace std;
class Base {
public:
virtual ~Base() { cout << "Base" << endl; }
};
class Derived :public Base {
public:
~Derived() { cout << "Derived" << endl; }
};
int main()
{
//Derived* pd = new Derived;
//delete pd;
Base* pb = new Derived;
delete pb;
return 0;
}
2.更新日志
2022.11.19 整理文章来源:https://www.toymoban.com/news/detail-494226.html
欢迎交流、讨论、指正~
不正确、不理解之处欢迎评论留言~文章来源地址https://www.toymoban.com/news/detail-494226.html
到了这里,关于C++程序设计期末考试复习试题及解析 3(自用~)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!