文章目录
- 笔试强训15
- 一、选择题
- 1-5题
- 6-10题
- 二、编程题
- 题目一
- 题目二
- 笔试强训16
- 一、选择题
- 1-5题
- 6-10题
- 二、编程题
- 题目一
- 题目二
- 笔试强训17
- 一、选择题
- 1-5题
- 6-10题
- 二、编程题
- 题目一
- 题目二
笔试强训15
一、选择题
1-5题
共有派生下,派生类的成员函数只能访问基类的公有成员和保护成员,基类的私有成员不可以被访问。
故选C
详细的访问关系请参照下图:
- 首先要明白一点C++中的结构体进行继承的时候默认继承方式是公有继承
- 其次在构造函数和析构函数中不会发生多态。
new了一个B类对象赋值给A类指针,由于B继承了A,所以会先调用A的构造函数,A的构造函数中执行了bar函数,构造函数中不发生多态,所以执行A的bar函数,输出bar
。之后p->foo(),foo不是虚函数,不构成多态,所以父类指针指向foo只会执行父类的foo函数,输出foo
。然后p->bar(),这次构成多态,执行B的foo函数,输出b_bar
。
故最后输出barfoob_bar
,故选A
ABC属于纯虚函数即和抽象类的基本知识,不做过多解释。
关于D,纯虚函数是可以有函数体的,如下图所示,编译器正常运行,无报错。
故选D。
首先要了解派生类B,是有自己的虚表的,p调用test之后,test在父类中是个虚函数,而且派生类B没有重写它,所有就会去调用父类A的test函数,test函数中会调用func函数,func函数也是个虚函数,而且子类B重写了该虚函数,那么虚表中A::func的地址就会被B::func的地址覆盖,所以就只能去调用B的func函数,虽然调用的是B的func函数,但实际上使用的缺省参数还是A中func给的缺省参数0,所以就会输出B->1
上面说的是发生多态的过程,这里有个疑问就是,多态要发生不是得父类的指针或者引用接收子类的对象嘛?这里父类函数调用了func,却发生了多态。 原因是父类test函数中是有this指针的,this指针是父类类型的,但它实际上的指向是子类的,所以就会发生多态了。
故选B。
A* p=&a,没发生多态,只会调用A的foo和fun,输出12
,然后p=&b,B重写了A的fun函数,构成多态,故调用B的fun函数,foo函数在A中不是虚函数,所以父类指针只会调用A的foo,故输出14
,最后三行和A* p=new B没区别,还是输出14
。
故最终输出121414,故选B。
6-10题
没有用父类的指针或者引用来接收,所以不会形成多态,所以b.x只会调用B自己的x函数。
故选B。
B:在C++中,如果基类中的某个函数被声明为虚函数,那么在派生类中对应的函数(即重写的函数)自动成为虚函数,即使没有在派生类中显式地将其声明为虚函数,故B错
选B。
A:virtual和void的前后顺序不对
C,D:没有=0
只有B对,故选B
在派生类中声明虚函数,但是基类的同名函数却没有声明为虚函数,这样是构不成多态的,题目本意可能是想要构成多态,然后输出B
,但没有构成多态,实际输出的是A
,因此可以选B,不符合预期的输出A。
但实际上,当你把这段代码复制粘贴到编译器上去运行的时候,输出A之后就会报错,停止运行的,所以其实实际上C也不正确,这里只能说题目不严谨了。这里出错的原因是当一个类中有虚函数的时候,在类的开始部分就会有一个vfptr指针,指向虚函数表(具体是不是在类的开始部分存vfptr取决于编译器,不同编译器可能实现会有区别。)。B中有虚函数所以会有这个vfptr的位置,但A没有虚函数,所以也就不会有vfptr这个位置,因此当A类指针接收B类对象的时候,在delete时候就会出问题。
解决方法就是A中的函数也有一个虚函数就好了。
new一个C,会调用C的构造函数,因为C继承了A,和B,所以,根据继承的顺序,先调用A的构造函数,再调用B的构造函数,最后调用C的构造函数,依次输出A B C
,之后delete a,只会调用A的析构函数,所以只会输出delA
。值得注意的是,这里会造成内存泄漏,会造成B和C的资源没有释放,只需要把基类的析构函数声明为虚函数就好了。
故选A。
二、编程题
题目一
题目链接:
查找输入整数二进制中1的个数
提交代码:
法1:
#include <iostream>
#include <bitset>
using namespace std;
int main() {
int n;
while (cin >> n) { // 注意 while 处理多个 case
bitset<32> b(n);
cout<<b.count()<<endl;
}
}
// 64 位输出请用 printf("%lld")
法2:
#include<iostream>
using namespace std;
int Count(size_t value) {
int count = 0;
while (value) {
value &= (value -
1); //表达式只跟1的个数有关系,跟1所在的位置无关
count++;
}
return count;
}
int main() {
size_t value; //unsigned int
int one_count = 0;
while (cin >> value) {
one_count = Count(value);
cout << one_count << endl;
}
return 0;
}
运行结果:
题目二
题目链接:
手套
提交代码:
#include <climits>
class Gloves {
public:
int findMinimum(int n, vector<int> left, vector<int> right) {
int left_sum = 0, left_min = INT_MAX;
int right_sum = 0, right_min = INT_MAX;
int sum = 0;
for (int i = 0; i < n; i++) {
if (left[i]*right[i] == 0) {
sum += (left[i] + right[i]);
} else {
left_sum += left[i];
left_min = left_min < left[i] ? left_min : left[i];
right_sum += right[i];
right_min = right_min < right[i] ? right_min : right[i];
}
}
return sum + min(left_sum - left_min + 1, right_sum - right_min + 1) + 1;
}
};
运行结果:
笔试强训16
一、选择题
1-5题
基类指针接收子类对象,第一个printf里100被强转为char类型的,所以会调用char版本的Bar函数,又因为父类char版本的Bar函数没声明为虚函数,所以不会构成多态,直接调用父类的char函数,输出强转int后的100
.
之后下一个printf,调用int版本的Bar函数,这个Bar函数被重写了,所以构成多态,调用子类的int版本Bar函数,输出100/2,即50.
故选100 50,B。
A:虚函数不能是一个static函数,static函数没this指针,但是要调用虚函数是需要this指针的,A错
B:派生类重写的父类的虚函数,这俩函数必须名字,返回值和参数类型,顺序都保持一致。B错
C:虚函数是一个成员函数,C错。
D: 基类中声明虚函数后,基类中重写了虚函数不需要前面加virtual,因为继承了父类的虚函数属性。D对。
故选D。
这里,这个类中如果没有虚函数的话,就会输出100,200,但是这个类中有个虚函数,那么在类内存的首部就会存在一个 指向虚表的指针_vfptr
,之后才是a和b,所以强转为int* 之后相当于把前4个字节修改成了100,同时意味着找不到虚表了,所以构不成多态了,然后把第二个4个字节修改成了200,也就是把a修改成了200,而b还是10.
所以最后会输出200 ,10
故选A。
当父类某个函数是虚函数时,所有继承该类的派生类中同原型函数会继承父类该函数的虚函数属性,都是虚函数。
故选D。
ABD都对,不做过多解释
C:通过上下文,父类指针如果指向的是派生类对象的话就会形成多态,调用子类的方法。故C错
故选C。
6-10题
B是私有继承A的,所以A的print函数在B中就是私有的,C又公有继承了B,但是C中是访问不了B中的私有函数的,所以会编译出错。
故选C。
A:指针可以指向nullptr,故A错
B:子类指针可以指向父类实例,只不过是需要强转一下。
C:引用声明时必须初始化,所以任何引用必须指向一个实例。C对
D:int* p=mew int(10); int* & pp=p,当delete p之后pp所指向的示例就是无效的,故D错
故选C。
ABD不做过多解释
C:模板和运行时多态没关系,C错
故选C
pa是父类指针指向子类对象,分析时注意多态,pa2是父类指针指向父类对象。
pa->FuncA(),这里父类不是虚函数,不构成多态调用父类的FuncA函数,输出FuncA called
pa->FuncB(),这个被子类重写了,构成多态,调用子类的FuncB,输出FuncBB called
pa2->FuncA(),没多态直接执行父类的FuncA,输出FuncA called
pa2->FuncB(),也没多态,调用父类地FuncB,输出FuncB called
故选B。
首先父类指针指向子类对象,注意多态
pstBase->foo(100),调用参数int类型的函数,父类中该函数被virtual修饰,子类中完成了重写,故形成多态,调用的是子类的foo函数,返回值是100*2=200.
pstBase->foo(x),调用的显然是参数数组类型的函数,虽然子类声明为虚函数,但是父类没声明为虚函数,故不够成多态,调用父类的foo函数,这里调用了sizeof(x),因为是32位下,所以算出来的值是4,4+10=14,所以最后是2000+14=2014。
有人这里会疑问,在这里x是x[10],sizeof算出来的大小不是10吗?,这里我们需要明确的是,在函数参数列表中的数组其实会退化成指针,例如char a[n],会退化成char* a,n是多少其实是无所谓的,所以在这里sizeof的时候就相当与求指针的大小了,还不懂的可以看下面的解释。
在C++中,sizeof
操作符用于获取类型或变量所占的内存大小(以字节为单位)。当你看到在你的代码示例中sizeof
的结果在main
函数中和在func
函数中对同一个类型的数组(char[10]
)调用时不同,这是因为在这两种情况下,sizeof
操作的对象类型是不同的。
在main
函数中
char a[10];
cout << sizeof(a) << endl;
这里,a
是一个具有10个元素的字符数组。因此,sizeof(a)
会计算整个数组所占的内存大小,即10 * sizeof(char)
,由于char
类型通常占用1个字节,所以结果是10
字节。
在func
函数中
void func(char a[10])
{
cout << sizeof(a) << endl;
}
尽管函数参数a
被声明为char a[10]
,但在这里,a
实际上被当作一个指向char
类型的指针来处理。这是因为当数组作为函数参数传递时,它会被退化为指向数组首元素的指针。因此,sizeof(a)
在这里实际上是计算指针的大小,而不是数组的大小。指针的大小取决于你的系统架构(通常是32位或64位),在32位系统上通常是4字节,在64位系统上通常是8字节。
总结
- 在
main
函数中,sizeof(a)
计算的是整个数组a
的大小。 - 在
func
函数中,sizeof(a)
计算的是传递给函数的数组参数(实际为指针)的大小。
这就是为什么你看到两次sizeof
的结果不同的原因。
二、编程题
题目一
题目链接:
完全数计算
提交代码:
#include <iostream>
using namespace std;
bool iswanshu(int n) {
int sum = 0;
for (int i = 1; i < n; i++) {
if (n % i == 0) {
sum = sum + i;
}
}
if (sum == n) {
return true;
}
return false;
}
int main() {
int n;
while (cin >> n) {
int sum = 0;
for (int i = 2; i <= n; i++) {
if (iswanshu(i)) {
++sum;
}
}
cout << sum;
}
}
// 64 位输出请用 printf("%lld")
运行结果:
题目二
题目链接:
扑克牌大小
提交代码:
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
string FindMax(const string& line) {
if (line.find("joker JOKER") != string::npos)
return "joker JOKER";
int dash = line.find('-');
//分开两手牌
string car1 = line.substr(0, dash);
string car2 = line.substr(dash + 1);
//获取两手牌的张数
int car1_cnt = count(car1.begin(), car1.end(), ' ') + 1;
int car2_cnt = count(car2.begin(), car2.end(), ' ') + 1;
//获取两手牌的各自第一张牌
string car1_first = car1.substr(0, car1.find(' '));
string car2_first = car2.substr(0, car2.find(' '));
if (car1_cnt == car2_cnt) { //两手牌的类型相同
string str = "345678910JQKA2jokerJOKER";
if (str.find(car1_first) > str.find(car2_first))
return car1;
return car2;
}
if (car1_cnt == 4) //说明是炸弹
return car1;
else if (car2_cnt == 4)
return car2;
return "ERROR";
}
int main() {
string line, res;
while (getline(cin, line)) {
res = FindMax(line);
cout << res << endl;
}
return 0;
}
运行结果:
笔试强训17
一、选择题
1-5题
因为A是抽象类,所以是不能进行实例化的,A、B、C,三个选项都实例化了A,所以是错误的。
D:只是定义了一个A*类型的指针,在没new A之前是没实例化A的,所以D对
故选D。
虚函数不可以重载为内联。
故选B。
虚函数的作用是为了实现多态,A直接错。其他的不作过多解释。
故选A。
看下图:
所以最后会输出100 300 300 500。
故选C。
动态多态虚函数,静态多态函数重载,模板,常识,不过多解释。
故选D。
6-10题
动态联编简单理解就是运行时才确定要调用哪个函数,这不就是考多态吗?
所以题目问的就是什么时候能引发多态,毫无疑问是通过父类指针或引用接收子类对象去调用虚函数了。
故选B。
D:覆盖其实就是重写,要保证函数名,参数类型,个数,顺序和返回值都相同,故D错。其实语法上叫重写,底层原理叫覆盖,因为子类重写的虚函数地址把虚表上父类的虚函数地址覆盖了嘛,所以重写和覆盖是一个东西。
选D。
下面重点区分一下重载(Overloading)、重写(Overriding)、隐藏(Name Hiding)和覆盖(Covering,但更常见的是指重写,这里为了区分而提及)的关系和区别:
1. 重载(Overloading)
定义:在同一个作用域内,可以有多个同名函数,但这些函数的参数列表(参数的数量、类型或顺序)必须不同。重载是编译时多态的一种体现。
特点:
- 发生在同一个类中或同一作用域内(如命名空间)。
- 函数名相同,但参数列表不同。
- 返回类型可以相同也可以不同,但通常不用于区分重载函数。
- 编译器根据函数调用时的参数类型和数量来确定调用哪个函数。
2. 重写(Overriding)
定义:在派生类中重新定义基类中的虚函数。当通过基类的指针或引用来调用该虚函数时,将调用派生类中的函数实现。
特点:
- 发生在基类和派生类之间。
- 函数名、返回类型(协变返回类型除外)和参数列表必须完全相同。
- 访问修饰符(如
public
、protected
)在派生类中不能比基类更严格。 - 是实现多态性的关键机制之一,属于运行时多态。
3. 隐藏(Name Hiding)
定义:在派生类中定义一个与基类同名的非虚函数(包括静态函数、全局函数和成员函数)。此时,派生类版本的函数会隐藏基类中的同名函数,而不是重写它。
特点:
- 发生在派生类中。
- 函数名相同,但不一定是虚函数,也不要求参数列表相同。
- 隐藏与访问修饰符无关。
- 通过基类的指针或引用调用时,仍然会调用基类中的函数(如果它是虚函数且未被重写)。
- 隐藏是静态的,即在编译时就确定了调用哪个函数。
4. 覆盖(通常指重写,但这里作为区分)
说明:在面向对象编程的术语中,“覆盖”通常就是指“重写”(Overriding)。但在某些上下文中,特别是在讨论不同编程语言或框架时,“覆盖”可能有更具体的含义,比如某些特定的覆盖机制或行为。然而,在大多数讨论中,可以将“覆盖”视为“重写”的同义词。
总结
- 重载:同一作用域内,函数名相同但参数列表不同。
- 重写(或覆盖):派生类中重新定义基类的虚函数。
- 隐藏:派生类中定义与基类同名的非虚函数,导致基类中的同名函数被隐藏。
- 覆盖:通常指重写,但在某些上下文中可能有更具体的含义。
选B,多态常识,不过多解释。
这里需要明确的是在构造函数和析构函数中是不会形成多态的,其次是构造的时候,先执行父类的构造函数,再执行子类的构造函数,析构的时候,先执行子类的析构函数,再执行父类的析构函数,满足先构造的后析构,后构造的先析构的规则。
故本题中先调用父类构造,由于构造函数不发生多态,所以执行父类p函数,输出A
,再执行子类构造函数,执行子类p函数,输出B
,析构函数反过来,先输出B后输出A
。故最后输出ABBA。
故选D。
这个题目和第九题考的知识点是一样的,构造函数不会发生多态,先执行父类构造函数,执行echo,输出Base
,后执行子类构造函数执行echo,输出Derived
,最后base->echo()构成多态,输出Derived
故选D。
二、编程题
题目一
题目链接:
杨辉三角的变形
提交代码:
#include <iostream>
#include<string>
using namespace std;
int main(int argc, char* argv[]) {
int nRow = 0;
while (cin >> nRow) {
int res = -1;
int myInt[] = {4, 2, 3, 2};
if (nRow > 2)
res = myInt[(nRow - 2) % 4];
cout << res << endl;
}
return 0;
}
运行结果:
题目二
题目链接:
计算某字符出现次数
提交代码:
#include <iostream>
#include <string>
using namespace std;
int main() {
string input;
char s, m;
getline(cin, input);
cin >> s;
m = s;
if ('A' <= s <= 'z') {
if ('A' <= s && s <= 'Z')
m = s + 32;
else if ('z' >= s && s >= 'a')
m = s - 32;
int count = 0;
int length = input.size();
for (int i = 0; i < length; i++) {
if (input[i] == s || input[i] == m) {
count++;
}
}
cout << count << endl;
}
return 0;
}
运行结果: