【C/C++笔试练习】继承和派生的概念、虚函数的概念、派生类的析构函数、纯虚函数的概念、动态编译、多态的实现、参数解析、跳石板

文章目录

  • C/C++笔试练习
  • 选择部分
    • (1)继承和派生的概念
    • (2)程序分析
    • (3)虚函数的概念
    • (4)派生类的析构函数
    • (5)纯虚函数的概念
    • (6)动态编译
    • (7)子类的析构函数
    • (8)多态的实现
    • (9)程序分析
    • (10)程序分析
  • 编程题 day13
    • 参数解析
    • 跳石板

C/C++笔试练习

选择部分

(1)继承和派生的概念

  下面叙述不正确的是()

  A. 派生类一般都用公有派生
  B. 对基类成员的访问必须是无二义性的
  C. 赋值兼容规则也适用于多重继承的组合
  D. 父类的公有成员在派生类中仍然是公有的

  答案:D

  在面向对象编程中,派生类是通过从一个或多个基类派生而来的。这里有几个关键概念和规则:

  派生方式:派生类可以从基类以公有(public)、保护(protected)或私有(private)的方式派生。这意味着基类的公有成员在派生类中仍然是公有的,保护成员在派生类中是保护的,私有成员在派生类中是不可访问的。

  考虑到这个规则,对于选项D,父类的公有成员在派生类中仍然是公有的,这个叙述是不正确的。应该是“父类的公有成员在派生类中仍然是公有的,而保护成员和私有成员不可访问”。

  访问基类成员:在派生类中访问基类的公有成员时,访问必须是明确且无二义性的。 这意味着如果基类有多个同名成员,那么在派生类中访问时必须明确指定是哪个基类的成员。所以选项B的叙述是正确的。

  赋值兼容规则:在多重继承的情况下,赋值兼容规则也适用。这意味着基类的类型可以被看作是派生类的类型。因此选项C的叙述是正确的。

  赋值兼容规则适用于多重继承:

class Base {};

class D: public Base
{};

int main()
{
	Base b;
	D d;
}

  (1)b=d; 子类对象可以复制父类无需转化。

  (2)Base* pd=&d; 子类对象地址可以直接赋给父类指针。

  (3)Base& rb=d; 可以使用子类对象对父类引用初始化,父类的公有成员在派生类仍为公有(前提是不是私有继承)。

  通常我们使用公有派生来创建派生类,因为这样可以使派生类具有和基类相同的接口,从而可以替换基类。 所以选项A的叙述是正确的。

  

(2)程序分析

  下面 C++ 程序的运行结果是()

#include 
using namespace std

class parent {
	int i;
protected:
	int x;
public:
	parent() { x = 0; i = 0; }
	void change() { x++; i++; }
	void display();
};

class son :public parent {
public:
	void modify();
};

void parent::display() {
	cout << "x=" << x << endl;
}

void son::modify() {
	x++;
}

int main() {
	son A;
	parent B;
	A.display();
	A.change();
	A.modify();
	A.display();
	B.change();
	B.display();
	return 0;
}

  A.x=1 x=0 x=2
  B.x=2 x=0 x=1
  C.x=0 x=2 x=1
  D.x=0 x=1 x=2

  答案:C

son A;//创建子类A,先调用父类构造函数parent(),x=0,y=0,再调用子类构造函数,这里系统默认生成了一个
parent B;//创建父类B,直接调用构造函数parent(),x=0,y=0
A.display();//打印A的x  x=0

A.change();//调用charge(),子类没有,去父类调用,x++,i++ 此时A的x=1,i=1
A.modify();//直接调用子类的modify(),x++,此时A的x=2,i=1
A.display();//打印A的x  x=2

B.change();//直接调用父类的charge(),x++,i++, 此时B的x=1,i=1
B.display();//这里打印B中的成员变量x  x=1  故答案为 0 2 1 

【C/C++笔试练习】继承和派生的概念、虚函数的概念、派生类的析构函数、纯虚函数的概念、动态编译、多态的实现、参数解析、跳石板_第1张图片

  

(3)虚函数的概念

  关于虚函数的描述正确的是()

  A. 派生类的虚函数与基类的虚函数具有不同的参数个数和类型
  B. 内联函数不能是虚函数
  C. 派生类必须重新定义基类的虚函数
  D. 虚函数可以是一个static型的函数

  答案:B

  A选项提到派生类的虚函数与基类的虚函数可以有不同的参数个数和类型。这是不正确的。如果只看这个语义,派生类的虚函数与基类的虚函数没有什么关系。即使指的是相同的虚函数,那其中的参数列表(包括个数和类型)也必须是一样的。

  B选项说内联函数不能是虚函数。这是正确的。内联函数会展开,但是在虚标中存放的是函数地址,如果内联函数根据地址展开,就无法找到函数地址。所以内联函数不能是虚函数。

  C选项说派生类必须重新定义基类的虚函数。这是不正确的。如果基类的虚函数是纯虚函数(即没有实现),那么任何派生类都必须提供这个函数的实现。但如果基类的虚函数有实现,那么派生类可以选择是否重新定义这个函数。

  D选项说虚函数可以是一个static型的函数。这也是不正确的。虚函数需要this指针调用对象,调用虚表,而静态成员函数中没有this指针,所以不能被声明为虚函数。

  

(4)派生类的析构函数

  当一个类对象的生命周期结束后,关于调用析构函数的描述正确的是()

  A. 如果派生类没有定义析构函数,则只调用基类的析构函数
  B. 如果基类没有定义析构函数,则只调用派生类的析构函数
  C. 先调用派生类的析构函数,后调用基类的析构函数
  D. 先调用基类的析构函数,后调用派生类的析构函数

  答案:C

  在C++中,当一个对象被销毁时,其析构函数会被首先调用。如果这个对象是由一个派生类(子类)对象实例化的(前提是它的的析构函数是形成了多态),那么派生类的析构函数将首先被调用,然后是基类的析构函数。

  这个规则确保了派生类在基类之前释放任何由派生类管理的资源,以防止资源泄漏。 如果派生类没有定义析构函数,那么编译器会自动生成一个析构函数,这个析构函数只会调用基类的析构函数。如果基类也没有定义析构函数,那么编译器会为基类生成一个默认的析构函数。

  

(5)纯虚函数的概念

  以下关于纯虚函数的说法,正确的是()

  A. 声明纯虚函数的类不能实例化
  B. 声明纯虚函数的类成虚基类
  C. 子类必须实现基类的纯虚函数
  D. 纯虚函数必须是空函数

  答案:A

  A. 声明纯虚函数的类不能实例化 - 这是正确的。当一个类包含一个或多个纯虚函数时,它被称为抽象类。抽象类不能被实例化,只能被其他类继承。

  纯虚函数的定义:

virtual void fun()=0;

  B. 声明纯虚函数的类成虚基类 - 这是不正确的。虚函数和虚基类完全是两个概念,一个类包含纯虚函数并不影响它作为基类的类型。 只有当一个类作为虚基类使用时,它才需要至少一个纯虚函数。虚基类一般用于解决菱形继承。

  C. 子类必须实现基类的 - 这是不正确的。如果一个子类继承自一个包含纯虚函数的基类,那么子类不一定实现这些纯虚函数。多态实现,否则不实现。

  D. 纯虚函数必须是空函数 - 这是不正确的。纯虚函数只是声明,没有提供实现。它们在基类中的存在是为了让派生类实现它们。纯虚函数可以是任何类型的函数,包括带有实现的非空函数。

  

(6)动态编译

  下列描述,正确的一共有多少个()

  1)const char *p,这是一个常量指针,p的值不可修改
  2)在64位机上,char *p= “abcdefghijk”; sizeof( p )大小为12
  3)inline会检查函数参数,所以调用开销显著大于宏
  4)重载是编译时确定的,虚函数是运行时绑定的

  A. 1
  B. 2
  C. 3
  D. 4

  答案:A

  const char *p这是一个常量指针,p的值可以修改:const char *p是一个 指向常量的指针,这意味着你不能通过这个指针来改变它所指向的值,但你可以改变这个指针本身所指向的地址。

  在64位机上,char *p= “abcdefghijk”; sizeof( p )大小为12:这个描述是错误的。在64位机器上,一个指针的大小通常是8字节。所以sizeof(p)应该返回8,而不是12。

  inline会检查函数参数,所以调用开销显著大于宏:这个描述也是错误的。内联函数的主要目的是减少函数调用的开销,因为它可以避免进行一次函数调用的额外开销。但其实开销和宏也差不多,提不上显著的说法。

  重载是编译时确定的,虚函数是运行时绑定的:这个描述是正确的。重载是在编译时确定的(静态编译),编译器会根据函数名和参数类型在编译时确定调用哪个函数。而虚函数是在运行时动态绑定的(动态编译),编译器会生成一个虚函数表(vtable),并在运行时根据对象的实际类型来选择调用哪个虚函数的实现。

  

(7)子类的析构函数

  C++将父类的析构函数定义为虚函数,下列正确的是哪个()

  A. 释放父类指针时能正确释放子类对象
  B. 释放子类指针时能正确释放父类对象
  C. 这样做是错误的
  D. 以上全错

  答案:A

  在C++中,如果一个父类的析构函数被定义为虚函数,那么当通过父类指针释放一个子类对象时,能正确释放子类对象。这是因为虚析构函数的目的是为了在释放对象时正确地调用子类的析构函数。

  然而,如果子类的析构函数没有定义为虚函数,那么通过父类指针释放子类对象时,可能会导致子类的析构函数不被调用,从而造成资源泄漏。

  

(8)多态的实现

  下列关于多态性说法不正确的是()

  A. 多态性是指同名函数对应多种不同的实现
  B. 重载方式仅有函数重载
  C. 重载方式包含函数重载和运算符重载
  D. 多态性表现为静态和动态两种方式

  答案:B

  多态性是指同名函数对应多种不同的实现, 所以选项A是正确的。

  多态性有两种表现形式:编译时的多态性(静态)和运行时的多态性(动态)。编译时的多态性可以通过函数重载和运算符重载实现,所以选项C是正确的。运行时的多态性可以通过虚函数实现,所以选项D也是正确的。

  重载方式不只有函数重载,还包括运算符重载,所以选项B是不正确的。

  

(9)程序分析

  分析一下这段程序的输出

#include
using namespace std;
class B
{
public:
	B()
	{
		cout << "default constructor" << " ";
	}
	
	~B()
	{
		cout << "destructed" << " ";
	}
		
	B(int i): data(i)
	{
		cout << "constructed by parameter" << data << " ";
	}
private: 
	int data;
};

B Play( B b)
{
	return b;
}

int main(int argc, char *argv[])
{
	B temp = Play(5);
	return 0;
}

A. constructed by parameter5 destructed destructed
B. constructed by parameter5 destructed
C. default constructor" constructed by parameter5 destructed
D. default constructor" constructed by parameter5 destructed destructed

  答案:A

class B
{
public:
	B()
	{
		cout << "default constructor" << " ";
	}
	~B()//6.最后调用析构函数,打印destructed
	{
		cout << "destructed" << " ";
	}
	B(int i) : data(i)//3.自动调用单参数构造函数,i=5,打印constructed by parameter5
	{
		cout << "constructed by parameter" << data << " ";
	}
private: int data;
};

B Play(B b)//2.将5作为参数给B的构造函数  4.返回构造好的B对象,结束要调用一次析构, 打印destructed
{
	return b;
}

int main(int argc, char* argv[])
{
	B temp = Play(5);//1.先调用Play()函数  5.返回的对象赋值给temp
	return 0;
}

【C/C++笔试练习】继承和派生的概念、虚函数的概念、派生类的析构函数、纯虚函数的概念、动态编译、多态的实现、参数解析、跳石板_第2张图片

  

(10)程序分析

  求输出结果

#include 
using namespace std;
class A
{
public:
	virtual void print()
	{
		cout << "A::print()" << "\n";
	}
};

class B: public A
{
	public: 
virtual void print()
	{
		cout << "B::print()" << "\n";
	}
};

class C: public A
{
	public: virtual void print()
	{
		cout << "C::print()" << "\n";
	}
};

void print(A a)
{
	a.print();
}

int main()
{
	A a, *aa, *ab, *ac;
	B b;
	C c;
	aa = &a;
	ab = &b;
	ac = &c;
	a.print();
	b.print();
	c.print();
	aa->print();
	ab->print();
	ac->print();
	print(a);
	print(b);
	print(c);
}

A. C::print() B::print() A::print() A::print() B::print() C::print() A::print() A::print() A::print()
B. A::print() B::print() C::print() A::print() B::print() C::print() A::print() A::print() A::print()
C. A::print() B::print() C::print() A::print() B::print() C::print() B::print() B::print() B::print()
D. C::print() B::print() A::print() A::print() B::print() C::print() C::print() C::print() C::print()

  答案:B

A a, * aa, * ab, * ac;//创建A对象a,和A类型指针aa,ab,ac 
B b;//实例化B对象b
C c;//实例化C对象c
aa = &a;//父类指针指向父类 A->A
ab = &b;//父类指针指向子类 A->B
ac = &c;//父类指针指向子类 A->C
//静态编译
a.print();//A对象直接调用A中成员函数print()  打印A::print()
b.print();//B对象直接调用B中成员函数print()  打印B::print()
c.print();//C对象直接调用C中成员函数print()  打印C::print()
//多态
aa->print();//父类对象直接调用父类函数print() 打印A::print()
ab->print();//父类指针A指向子类对象B,且为虚函数,实现多态  打印B::print()
ac->print();//父类指针A指向子类对象C,且为虚函数,实现多态  打印C::print()
//永远调用的是父类的函数
print(a);//父类对象A转入父类对象A  打印A::print()
print(b);//子类对象B传入父类对象A  打印A::print()
print(c);//子类对象C传入父类对象A  打印A::print()

【C/C++笔试练习】继承和派生的概念、虚函数的概念、派生类的析构函数、纯虚函数的概念、动态编译、多态的实现、参数解析、跳石板_第3张图片

            

编程题 day13

参数解析

参数解析

  本题通过以空格和双引号为间隔,统计参数个数。对于双引号,通过添加flag,保证双引号中的空格被输出。

#include
#include
#include
using namespace std;

void cmdLineParse(const string& str) 
{
    string tmp = "";
    vector<string> svec;
    bool flag = false; 
    //用于判断是否处于字符串的状态
    for (int i = 0; i < str.size(); ++i) 
    {
        if (str[i] == '"') 
        { 
            //判断是否是字符串的起始或者结束
            flag = !flag; 
            //说明处于了字符串的状态
        } 
        else if (str[i] == ' ' &&!flag) 
        { 
            //判断参数的分隔或者是否为字符串的内容
            svec.push_back(tmp);
            tmp = "";
        } 
        else 
        { //正常的参数内容
            tmp += str[i]; //xcopy
        }
    }
    //追加最后一个参数
    svec.push_back(tmp); 
    cout << svec.size() << endl;
    for (int i = 0; i < svec.size(); ++i)
        cout << svec[i] << endl;
}

int main() 
{
    string str;
    while (getline(cin, str)) 
    {
        cmdLineParse(str);
    }
    return 0;
}

  

跳石板

跳石板

  将1 - M个石板看做一个结果数组stepNum,每个stepNum[i]储存着从起点到这一步最小的步数,其中0为不能到达。 从起点开始对stepNum进行遍历,先求i的所有约数(即从stepNum[i]能走的步数),然后更新那几个能到达的位置的最小步数。如果不能到达则更新为此时位置的最小步数 + 1,如果是能到达的就更新为min(已记录的最小步数,此处的最小步数 + 1)),遍历一遍后得到结果。

#include
#include
#include
#include
using namespace std;

void get_div_num(int v, vector<int>& a) 
{
    for (int i = 2; i <= sqrt(v); ++i)
    {
        if (v % i == 0) 
        {
            a.push_back(i);
            if (v / i != i)
                a.push_back(v / i);
        }
    }
}

int Jump(int n, int m) 
{
    vector<int> step(m + 1, INT_MAX); //int_max表示不可达到
    step[n] = 0; //当前位置初始化
    for (int i = n; i < m; ++i)
    {
        if (step[i] == INT_MAX)
            continue;
        vector<int> a;
        //获取i的约数,并保存
        get_div_num(i, a);
        for (int j = 0; j < a.size(); ++j)
        {
            if (a[j] + i <= m && step[a[j] + i] != INT_MAX) 
            {
                //需要挑选一个最小值
                step[a[j] + i] = step[a[j] + i] < step[i] + 1 ? step[a[j] + i] : step[i] + 1;
            } 
            else if (a[j] + i <= m) 
            {
                step[a[j] + i] = step[i] + 1;
            }
        }
    }
    return step[m] == INT_MAX ? -1 : step[m];
}

int main()
{
    int n, m, min_step;
    while (cin >> n >> m) 
    {
        min_step = Jump(n, m);
        cout << min_step << endl;
    }
    return 0;
}

你可能感兴趣的:(C/C++笔试练习,c语言,c++)