个人主页:Lei宝啊
愿所有美好如期而遇
选择题
第一道
关于虚函数的描述正确的是()
- A. 派生类的虚函数与基类的虚函数具有不同的参数个数和类型
- B. 内联函数不能是虚函数
- C. 派生类必须重新定义基类的虚函数
- D. 虚函数可以是一个static型的函数
解析:
关于虚函数的描述,我们来逐项分析:
A. 派生类的虚函数与基类的虚函数具有不同的参数个数和类型
- 这是不正确的。派生类中的虚函数如果要重写(override)基类中的虚函数,那么它们的函数签名(即函数名、参数类型和个数)必须相同。
B. 内联函数不能是虚函数
- 这是正确的。内联函数是在编译时展开的,而虚函数是在运行时动态绑定的。因此,内联函数和虚函数是互斥的,一个函数不能同时被声明为内联和虚函数。
C. 派生类必须重新定义基类的虚函数
- 这是不正确的。派生类可以选择重写基类的虚函数,但不是必须的。如果派生类没有重写基类的虚函数,那么当通过派生类对象调用该虚函数时,将会调用基类中的版本。
D. 虚函数可以是一个static型的函数
- 这是不正确的。静态成员函数与类的实例无关,它们没有this指针,因此不能声明为虚函数。虚函数是依赖于对象的,它们通过vptr(虚函数表指针)和vtable(虚函数表)来实现动态绑定。
综上所述,正确的选项是 B。
第二道
当一个类对象的生命周期结束后,关于调用析构函数的描述正确的是()
- A. 如果派生类没有定义析构函数,则只调用基类的析构函数
- B. 如果基类没有定义析构函数,则只调用派生类的析构函数
- C. 先调用派生类的析构函数,后调用基类的析构函数
- D. 先调用基类的析构函数,后调用派生类的析构函数
解析:
当一个类对象的生命周期结束时,析构函数的调用遵循特定的规则。对于继承体系中的析构函数调用,规则如下:
- 首先调用派生类(子类)的析构函数。
- 然后调用基类(父类)的析构函数。
- 如果某个类没有定义析构函数,编译器会为其生成一个默认的析构函数(如果需要的话)。但是,这并不意味着只调用定义了析构函数的类的析构函数;整个继承链上的析构函数都会被调用,从派生类开始,到基类结束。
根据这些规则,我们来分析选项:
A. 如果派生类没有定义析构函数,则只调用基类的析构函数 - 不正确。即使派生类没有定义析构函数,编译器也会生成一个默认的析构函数(如果需要的话),并且它会被首先调用,然后调用基类的析构函数。
B. 如果基类没有定义析构函数,则只调用派生类的析构函数 - 不正确。即使基类没有定义析构函数,编译器也会生成一个默认的析构函数(如果需要的话),并且它会在派生类的析构函数之后被调用。但实际上,在C++中,如果基类有虚函数(包括析构函数),且派生类定义了析构函数(无论显式还是隐式),则基类也应该定义一个虚析构函数以确保正确的析构顺序和多态行为。但此选项的表述本身是不准确的。
C. 先调用派生类的析构函数,后调用基类的析构函数 - 正确。这是C++析构函数调用顺序的规则。
D. 先调用基类的析构函数,后调用派生类的析构函数 - 不正确。这与C++的析构函数调用顺序相反。
因此,正确的选项是C。但需要注意的是,在实际编程中,如果基类有虚函数,通常应该定义一个虚析构函数,即使它什么也不做。这样可以确保当通过基类指针或引用删除派生类对象时,派生类的析构函数也会被正确调用。
第三道
C++将父类的析构函数定义为虚函数,下列正确的是哪个()
- A. 释放父类指针时能正确释放子类对象
- B. 释放子类指针时能正确释放父类对象
- C. 这样做是错误的
- D. 以上全错
解析:
在C++中,将父类的析构函数定义为虚函数(virtual destructor)的目的是允许通过父类指针来正确地释放子类对象。这是多态性的一个重要方面,特别是在使用动态分配的对象时。
分析选项:
A. 释放父类指针时能正确释放子类对象
- 这是正确的。如果父类的析构函数是虚函数,并且你通过父类指针指向一个子类对象,然后删除这个指针,那么首先会调用子类的析构函数,然后调用父类的析构函数。这样可以确保子类对象被正确释放。
B. 释放子类指针时能正确释放父类对象
- 这个描述有点误导。实际上,当你释放一个子类指针时,你正在释放的是子类对象,这个过程中会自动调用父类的析构函数(如果它是虚的)。但说“释放子类指针时能正确释放父类对象”并不准确,因为你并没有直接释放父类对象;你是在释放子类对象,而父类析构函数是这个过程中的一部分。
C. 这样做是错误的
- 这是错误的。将父类的析构函数定义为虚函数通常是一个好的做法,特别是当父类被设计为基类,并且预期会有派生类时。
D. 以上全错
- 由于A是正确的,所以这个选项是错误的。
因此,正确答案是 A。然而,需要注意的是,选项B虽然表述上有问题,但其核心意思(即释放子类对象时会调用父类的析构函数)是正确的。在考试中,如果遇到类似的情况,最好根据具体的题目要求和上下文来判断。
编程题
首先一定有一个问题需要解决,就是约数问题,所以我们要写一个函数去求得一个数的所有不重复约数,我们使用set进行去重并返回。
接着我们确认dp[i]代表什么,经过我们分析,他代表的从吧n到i这个位置所走的最少步数,剩下的就是去求状态转移方程:
首先我们将dp表全部初始化为INT_MAX-1,dp[n] = 0。
我们可以想到,当前位置dp[i]加上他的一个约数div,我们走到这样一个位置dp[i+div]所需的最少步数应该是走到当前位置所需最少步数加1,即dp[i] + 1,但是前提是我们当前位置是有效的,也就是说这个位置到达过,并且dp[i] + 1要小于dp[i+div]才能赋值,否则保持原有值,所以我们的状态转移方程就是:dp[i+div] = dp[i]+1 < dp[i+div] ? dp[i]+1 : dp[i+div];于是这道题我们可以开始写了。
#include <climits>
#include <iostream>
#include <cmath>
#include <vector>
#include <set>
using namespace std;
set<int> Getdiv(int n)
{
set<int> st;
for(int i=2; i<=sqrt(n); i++)
{
if(n % i == 0)
{
st.insert(i);
st.insert(n / i);
}
}
return st;
}
int main()
{
//本题dp[i]应该是表示从第四个位置跳到第i个位置时所需要的最少次数
//那么从某个位置到另一个位置,应该是相减。
int n, m;
cin >> n >> m;
vector<int> dp(m * 2, INT_MAX-1);
dp[n] = 0;
//现在我们应该考虑状态转移方程了。
for(int i=n; i<=m; i++)
{
if(dp[i] < INT_MAX)
{
set<int> st = Getdiv(i);
for(auto e : st)
{
dp[e+i] = dp[i] + 1 < dp[e+i] ? dp[i] + 1 : dp[e+i];
}
}
}
if(dp[m] == INT_MAX-1) cout << -1;
else cout << dp[m];
return 0;
}
// 64 位输出请用 printf("%lld")