笔试强训错题总结(二)

笔试强训错题总结(二)

选择题

  1. 下列哪一个是析构函数的特征()

A. 析构函数定义只能在类体内

B. 一个类中只能定义一个析构函数

C. 析构函数名与类名不同

D. 析构函数可以有一个或多个参数

析构函数可以在类中声明,类外定义,一个类只能有一个析构函数,函数名为~类名,不能有参数

所以这题选:B


  1. 若MyClass是一个类名,其有如下语句序列:
MyClass c1,*c2;
MyClass *c3=new MyClass;
MyClass &c4=c1;

上面的语句序列调用构造函数个数是( )

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

只有在创建对象的时候才会调用构造函数,上述代码中c1和c3创建了对象,所以只会调用两次构造函数。

本题选:B


  1. 如果有一个类是 myClass , 关于下面代码正确描述的是:
myClass::~myClass()
{
    delete this;
    this = NULL;
}

A.正确,我们避免了内存泄漏

B. 它会导致栈溢出

C. 无法编译通过

D. 这是不正确的,它没有释放任何成员变量。

这段代码有好几个错误:

1.析构函数是用于对象销毁时,清理对象的资源的,但是并不是所有的类都申请了资源

2.this是const类型,不可更改,所以this=NULL这条语句无法通过编译

3.delete this一定会让程序崩溃

综上所述,这题选:C


  1. C++中关于堆和栈的说法,哪个是错误的()

A. 堆的大小仅受操作系统的限制,栈的大小一般较小

B. 在堆上频繁的调用new/delete容易产生内存碎片,栈没有这个问题

C. 堆和栈都可以静态分配

D. 堆和栈都可以动态分配

堆的大小只受操作系统的限制(主要取决于操作系统在进程分配时对内存块如何布局),堆的一般比较大(大小在GB级别),栈一般都比较小(大小在MB级别),如果频繁的调用malloc/new在堆上频繁申请小的内存块就会有内存碎片的产生(可以通过内存池来减少碎片),静态分配是指在编译阶段就可以确定需要开辟多大的空间,堆无法做到这点,堆只能动态分配,栈既可以动态分配又可以静态分配,通过alloca函数就可以在栈上动态申请空间。

所以这题选:C


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

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

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

C. 先调用派生类的析构函数,后调用基类的析构函数

D. 先调用基类的析构函数,后调用派生类的析构函数

析构函数作为六大默认成员函数,就算我们不去显示的定义,编译器也会自动生成析构函数。而调用析构函数的规则就是先调用派生类的析构,再调用基类的析构函数。

所以这题选:C


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

A. 声明纯虚函数的类不能实例化

B. 声明纯虚函数的类成虚基类

C. 子类必须实现基类的

D. 纯虚函数必须是空函数

有纯虚函数的类被称为抽象类,不能实例化对象,如果子类不对父类的纯虚函数进行重写,那么子类也是抽象类(也就是说子类可以不对父类重写),此外纯虚函数并不是只能为空函数:

笔试强训错题总结(二)_第1张图片

所以这题选:A


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

1)const char *p,这是一个常量指针,p的值不可修改

2)在64位机上,char *p= “abcdefghijk”; sizeof§大小为12

3)inline会检查函数参数,所以调用开销显著大于宏

4)重载是编译时确定的,虚函数是运行时绑定的

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

首先第一个const修饰的是* p也就是说,指针p的值可以更改,但是*p的值不能更改;

p是一个指针,对于64位操作系统而言指针的大小是八个字节,对于32位系统而言指针的大小是四个字节

inline和宏都是在调用的地方展开的,所以它们的调用开销其实是相当的,但是宏没有类型安全的检查,而inline有检查

只有第四个是正确,

所以本题选:A


  1. 下面说法正确的是()

A. 一个空类默认一定生成构造函数,拷贝构造函数,赋值操作符,引用操作符,析构函数

B. 可以有多个析构函数

C. 析构函数可以为virtual,可以被重载

D. 类的构造函数如果都不是public访问属性,则类的实例无法创建

一个空类也具有六大默认成员函数(构造,析构,拷贝构造,赋值重载,普通对象取地址重载,const对象取地址重载);一个类只能有一个析构函数,析构函数可以为虚函数,可以被重写,但是不能被重载。如果一个类的构造函数是私有的,那么它只能通过公有成员接口来实例化对象;

所以本题选:A
笔试强训错题总结(二)_第2张图片


  1. 以下程序的输出是()
class Base
{
public:
 Base(int j): i(j) {}
 virtual~Base() {}
    
 void func1() 
 {
 	i *= 10;
 	func2();
 }
 int getValue() 
 {
 	return i;
 }
protected:
 virtual void func2() 
 {
 	i++;
 }
protected:
 	int i;
};

class Child: public Base 
{
public:
 Child(int j): Base(j) {}
 void func1() 
 {
     i *= 100;
     func2();
 }
protected:
 void func2() 
 {
 	i += 2;
 }
};
int main() 
{
     Base * pb = new Child(1);
     pb->func1();
     cout << pb->getValue() << endl; 
     delete pb; 
}

A. 11 B. 101 C. 12 D. 102

func1函数不是虚函数,所以调用只看类型,因此调用的是父类的func1函数,执行i*=10,接下来调用func2函数,因为func2函数是虚函数且被重写,是多态调用(按对象调用),所以调用Child类的func2函数,执行i+=2;所以i=1*10+2=12

本题选:C


  1. 下面C++代码运行结果为()
#include
#include
using namespace std;
class B0 {
public:
	virtual void display() {
		cout << "B0::display0" << endl;
	}
};
class B1 :public B0 {
public:
	void display() { cout << "B1::display0" << endl; }
};
class D1 : public B1 {
public:
	void display() {
		cout << "D1::display0" << endl;
	}
};

void fun(B0 ptr) {
	ptr.display();
}
int main() {
	B0 b0;
	B1 b1;
	D1 d1;
	fun(b0);
	fun(b1);
	fun(d1);
}

A. B0::display0 B0::display0 B0::display0

B. B0::display0 B0::display0 D1::display0

C. B0::display0 B1::display0 D1::display0

D. B0::display0 B1::display0 B1::display0

首先要明确fun是一个全局函数,它的参数是一个父类对象,而多态的前提是必须要是父类的指针或者引用,所以这里并不构成多态,按类型调用函数,因为是父类的对象,所以都调用的是父类的display函数;

所以本题选:A


  1. 下列哪种函数可以定义为虚函数()

A. 构造函数 B. 析构函数 C. 内联成员函数 D. 静态成员函数

构造函数是肯定不可以被定义为虚函数的,因为对象都还没完成实例化,而析构函数建议被定义成析构函数,详情可以看:三大特性之多态,内联函数无法被定义为虚函数,因为内联函数是在调用的地方直接展开的,没有地址,也就无法填充虚函数表;虚函数的调用是通过this指针的,静态成员函数没有this指针;

所以本题选:B


  1. 下面关于虚函数的描述,错误的是

A. 在成员函数声明的前面加上virtual修饰,就可把该函数声明为虚函数

B. 基类中说明了虚函数后,派生类中对应的函数也必须说明为虚函数

C. 虚函数可以是另一个类的友元函数,但不能是静态成员函数

D. 基类中说明的纯虚函数在其任何需要实例化的派生类中都必须实现

基类中将该函数声明成虚函数以后,即使派生类不将该函数声明成虚函数,编译器也会将其认为是虚函数;而且重写不是强制要求的,派生类可以不重写基类的虚函数;

所以本题选:B


  1. 下面这段代码的执行结果:
class A
{
public:
	A()
	{
		printf("A ");
	}
	~A()
	{
		printf("deA ");
	}
};
class B
{
public:
	B()
	{
		printf("B ");
	}
	~B()
	{
		printf("deB ");
	}
};
class C : public A, public B
{
public:
	C()
	{
		printf("C ");
	}
	~C()
	{
		printf("deC ");
	}
};
int main()
{
	A* a = new C();
	delete a;
	return 0;
}

A. A B C deA

B. C A B deA

C. A B C deC

D. C A B deC

我要构造一个C对象,而C对象是继承自A类和B类(这里要注意先后顺序,写在前面的类先构造),所以构造顺序是A,B,C,最后delete的时候会调用析构函数,而析构函数不是虚函数,所以直接按类型调用也就是说调用的是A类的析构
所以本题选:A


  1. 在32位环境下,以下程序的输出结构:
#include
using namespace std;
class Base
{
public:
	virtual int foo(int x)
	{
		return x * 10;
	}
	int foo(char x[14])
	{
		return sizeof(x) + 10;
	}
};
class Derived : public Base
{
	int foo(int x)
	{
		return x * 20;
	}
	virtual int foo(char x[10])
	{
	
			return sizeof(x) + 20;
	}
};
int main()
{
	Derived stDerived;
	Base* pstBase = &stDerived;
	char x[10];
	printf("%d\n", pstBase->foo(100) + pstBase->foo(x));
	return 0;
}

A. 2000 B. 2004 C. 2014 D. 2024

这段代码糅合了重写和重载,对于整形的foo函数来说,子类重写了父类,构成多态,所以第一个foo函数调用的是子类的,执行x*20,而针对字符数组的foo并没有构成重写,所以按类型调用执行sizeof(x)+10,这里又有一点要注意:字符数组传参的时候其实是传的地址,也就是说char x[14]其实等价于char*x,这是32位系统,指针大小是4个字节,所以最后的结果是:2000+14

所以本题选:C


  1. 假设A为抽象类,下列声明()是正确的

A. int fun(A); B. A Obj; C. A fun(int); D. A *p;

抽象类是不能实例化对象的,所以凡是出现和对象有关的,都是错误的;只有D选项,虽然是一个A类的指针,但是并没有创建对象

所以本题选:D


  1. 下面代码的输出结果:
#include  
#include 
using namespace std;
int main(void)
{
	vector<int>array;
	array.push_back(100);
	array.push_back(300);
	array.push_back(300);

	array.push_back(300);
	array.push_back(300);
	array.push_back(500);
	vector<int>::iterator itor;
	for (itor = array.begin(); itor != array.end(); itor++)
	{
		if (*itor == 300)
		{
			itor = array.erase(itor);
		}
	}
	for (itor = array.begin(); itor != array.end(); itor++)
	{
		cout << *itor << "";
	}
	return 0;
}

A. 100 300 300 300 300 500

B. 100 300 300 300 500

C. 100 300 300 500

D. 100 300

vector的删除有迭代器失效的问题,所以在删除的时候都是覆盖删除后再返回当前位置的迭代器,而循环条件中还对迭代器进行了++操作,这就导致了会跳过一些数

笔试强训错题总结(二)_第3张图片

所以本题选:C


  1. 以下代码的运行结果为()
class Base {
public:
	Base() {
		echo();
	}
	virtual void echo() {
		printf("Base");
	}
};
class Derived :public Base {
public:
	Derived() {
		echo();
	}
	virtual void echo() {
		printf("Derived");
	}
};
int main() {
	Base* base = new Derived();
	base->echo();
	return 0;
}

A. DerivedDerivedDerived

B. DerivedBaseDerived

C. BaseDerivedBase

D. BaseDerivedDerived

因为Derived类继承了Base类,所以在调用构造函数时会首先调用Base类的构造函数,此后在调用Derived的构造函数,随后因为echo构成多态,按对象调用,所以调用的也是Dervied的echo函数

所以本题选:D


编程题

1.跳石板

小易来到了一条石板路前,每块石板上从1挨着编号为:1、2、3…
这条石板路要根据特殊的规则才能前进:对于小易当前所在的编号为K的 石板,小易单次只能往前跳K的一个约数(不含1和K)步,即跳到K+X(X为K的一个非1和本身的约数)的位置。 小易当前处在编号为N的石板,他想跳到编号恰好为M的石板去,小易想知道最少需要跳跃几次可以到达。
例如:
N = 4,M = 24:
4->6->8->12->18->24
于是小易最少需要跳跃5次,就可以从4号石板跳到24号石板


解法

该题要采用动态规划的思想,可以设定两个数组,一个数组存放k的约数来表明在当前位置有几种走法,另一个数组存放从起始位置走到当前位置所需要的最小步数,因为要是最小步数,所以存放步数的数组每次有新的值来临就要和当前值进行比较,如果小于当前值才更新。如果无法抵达就返回-1,判断是否无法抵达可以在初始化的时候做文章,比如将所有位置都初始化为整数的最大值,然后再将起始位置初始化为0,从起始位置往后走,能抵达的每一个位置都会被初始化,最后如果目标位置是整数的最大值就表示无法抵达。

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

void jump_way(int way,vector<int>&a)
{
    //求约数,1和本身不算
    for(int i=2;i<=sqrt(way);i++)
    {
        if(way%i==0)
        {
             a.push_back(i);
             if(way/i!=i)
                a.push_back(way/i);
            //因为这里开平方了,所以i只能取到约数的一遍,但是只要i是way的约数,那么way/i肯定也是way的约数
        }
    }
}

int Jump(int n,int m)
{
    //主要过程在这个函数中进行
    vector<int>step(m+1,INT_MAX);//数组用于存放起始位置到当前位置的最小步数
    step[n]=0;

    for(int i=n;i<=m;i++)
    {
        if(step[i]==INT_MAX)//第一次是起始位置,所以可以往后执行,并且会将所有可达位置改变为其他值,最后要是某个位置还是INT_MAX,说明该点不可达
            continue;

        vector<int> a;//这个数组用来存放约数个数
        jump_way(i,a);
        //a数组中存放的是i位置的约数
        for(int j=0;j<a.size();j++)
        {
            //如果位置合法,需要挑选一个最小值
            if(a[j]+i<=m&&step[a[j]+i]!=INT_MAX)
            {
                step[a[j]+i]=min(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,minstep;

    while(cin>>n>>m)//n表示当前位置,m是目标位置
    {
        //通过jump函数来得到最小步数
        minstep = Jump(n,m);
        cout<<minstep<<endl;
    }
    return 0;
}

2.扑克牌大小

扑克牌游戏大家应该都比较熟悉了,一副牌由54张组成,含3~A,2各4张,小王1张,大王1张。牌面从小到大用如下字符和字符串表(其中,小写joker表示小王,大写JOKER表示大王)
3 4 5 6 7 8 9 10 J Q K A 2 joker JOKER
输入两手牌,两手牌之间用“-”连接,每手牌的每张牌以空格分隔,“-”两边没有空格,如:4 4 4 4-joker JOKER 请比较两手牌大小,输出较大的牌,如果不存在比较关系则输出ERROR

基本规则:
(1)输入每手牌可能是个子,对子,顺子(连续5张),三个,炸弹(四个)和对王中的一种,不存在其他情况,由输入保证两手牌都是合法的,顺子已经从小到大排列;
(2)除了炸弹和对王可以和所有牌比较之外,其他类型的牌只能跟相同类型的存在比较关系(如,对子跟对子比较,三个跟三个比较),不考虑拆牌情况(如:将对子拆分成个子)
(3)大小规则跟大家平时了解的常见规则相同,个子,对子,三个比较牌面大小;顺子比较最小牌大小;炸弹大于前面所有的牌,炸弹之间比较牌面大小;对王是最大的牌;
(4)输入的两手牌不会出现相等的情况。

答案提示:
(1)除了炸弹和对王之外,其他必须同类型比较。

(2)输入已经保证合法性,不用检查输入是否是合法的牌

(3)输入的顺子已经经过从小到大排序,因此不用再排序了.

输入描述:

输入两手牌,两手牌之间用“-”连接,每手牌的每张牌以空格分隔,“-”两边没有空格,如4 4 4 4-joker JOKER。

输出描述:

输出两手牌中较大的那手,不含连接符,扑克牌顺序不变,仍以空格隔开;如果不存在比较关系则输出ERROR。

解题思路

这个题目描述真是长到吓人,但其实真正重要的是以下几点:

1.两手牌输入到同一个字符串中,以-分割,这就暗示了我们要在程序中以-位分割符来得到两手牌

2.只有炸弹和王炸可以跟其他类型的牌比较,其他的都只能跟同类型的牌比较,并且只需要比第一张牌的大小即可(因为是有序的)

3.已经保证了输入的合法性,也就是说如果长度相同肯定是同类型的,长度不同且不是王炸,那肯定是“ERROR”

所以首先要切割字符串得到两手牌,再看两个子串的长度是否相等,如果相等就比较点数,如果不相等就看是否有炸弹,如果既不相等,又没有炸弹,那么就输出"ERROR",此外王炸是最大的牌,可以在程序的开始判断是否有王炸,如果有,就直接输出王炸。此外还有两点需要注意:1.扑克牌大小的比较规则和数字有些不同,所以可以再设置一个字符串,在这个字符串中位置越靠后,点数越大;2.关于牌的张数不能使用size函数,这里可以使用算法库中的count函数

#include 
#include
#include
using namespace std;

string bigstr(const string&car)
{
    if(car.find("joker JOKER")!=string::npos)
        return "joker JOKER";//王炸最大

    //首先要切割字符串得到两手牌
    string car1,car2;
    int pos=car.find('-',0);
    car1=car.substr(0,pos);
    car2=car.substr(pos+1);

    //判断两手牌的长度是否一致
    int cnt1=count(car1.begin(),car1.end(),' ')+1;//count查找的是空格的个数,空格的个数+1就是字符个数
    int cnt2=count(car2.begin(),car2.end(),' ')+1;
    if(cnt1==cnt2)
    {
        //两手牌个数相等,比较第一张牌的大小,但是这里有一个问题,就是说比较规则并不是单纯按大小,所以这里又可以设置一个字符串,位置越靠后点数越大
        string car1_first=car1.substr(0,car.find(' '));
        string car2_first=car2.substr(0,car2.find(' '));
        string comp="345678910JQKA2jokerJOKER";

        if(comp.find(car1_first)>comp.find(car2_first))
            return car1;
        return car2;
    }

    //类型不一致看是不是炸弹
    if(cnt1==4)
        return car1;
    if(cnt2==4)
        return car2;
    return "ERROR";
}

int main()
{
    string str,res;
    
    while(getline(cin,str))
    {
        res=bigstr(str);
        cout<<res<<endl;
    }
    return 0;
}

3.杨辉三角的变形

笔试强训错题总结(二)_第4张图片

以上三角形的数阵,第一行只有一个数1,以下每行的每个数,是恰好是它上面的数、左上角数和右上角的数,3个数之和(如果不存在某个数,认为该数就是0)。

求第n行第一个偶数出现的位置。如果没有偶数,则输出-1。例如输入3,则输出2,输入4则输出3,输入2则输出-1。


解题思路

这题有两种解法:

1.可以构建一个二维数组,然后根据输入的n生成一个n行的杨辉三角,每行的列数就是行数的两倍(从0开始),构造完杨辉三角以后直接根据输入的行数进到里面查找,但是这样会显示超出内存限制

#include
#include
using namespace std;

int findnum(int n)
{
    //创建二维数组,生成杨辉三角
    vector<vector<int>> vv(n,vector<int>(2*n-1,0));

    //初始化
    vv[0][0]=1;

    for(int i=1;i<n;i++)
    {
        vv[i][0]=vv[i][2*i]=1;//第一列和最后一列都是1

        //除了第二列和倒数第二列都是两个数相加以外,其他位置的数都是三个数相加  
        for(int j=1;j<2*i;j++)
        {
            if(j==1)
                vv[i][j]=vv[i-1][j]+vv[i-1][j-1];
            else if(j==2*i-1)
                vv[i][j]==vv[i-1][j-2]+vv[i-1][j-1];
            else 
                vv[i][j]=vv[i-1][j]+vv[i-1][j-1]+vv[i-1][j-2];
            
             
        }
        
    }

    //初始化完毕,随后开始查找
    for(int j=0;j<2*n-1;j++)
    {
        if(vv[n-1][j]%2==0)
            return j+1;
    }
    return -1;
}

int main()
{
    int n;
    cin>>n;

    int ret=findnum(n);
    cout<<ret<<endl;
}

解法二

2.这里有个规律:仔细观察可以发现,第一行和第二行是没有偶数的;如果行数是奇数,那么它的第二列就是就是偶数,如果行数是4的倍数,那么偶数就在第三个位置,除此之外偶数都在第四个位置;(这里的行数是从1开始的)

#include
#include
using namespace std;

int findnum(int n)
{
    if(n<=2)
        return -1;
    if(n%2==1)
        return 2;
    if(n%4==0)
        return 3;
     
    return 4;
}

int main()
{
    int n;
    cin>>n;

    int ret=findnum(n);
    cout<<ret<<endl;
}

4.字符串通配符

问题描述:在计算机中,通配符一种特殊语法,广泛应用于文件搜索、数据库、正则表达式等领域。现要求各位实现字符串通配符的算法。
要求:
实现如下2个通配符:

*:匹配0个或以上的字符(注:能被 *和?匹配的字符仅由英文字母和数字0到9组成,下同)
?:匹配1个字符

注意:匹配时不区分大小写。

输入:
通配符表达式;
一组字符串。

输出:

返回不区分大小写的匹配结果,匹配成功输出true,匹配失败输出false

输入描述:

先输入一个带有通配符的字符串,再输入一个需要匹配的字符串

输出描述:

返回不区分大小写的匹配结果,匹配成功输出true,匹配失败输出false


解题思路

起初我想以暴力匹配的方式尝试解决这道题,但是无论如何也无法通过题解,最后发现题目说*可以匹配一个或者以上的字符,这句话的意思或许是在说,有时候*不用匹配字符,有时候却需要匹配一个或者多个字符;

于是这题可以选用递归的思想来解决,大致分为三种情况:

1.两者走到末尾,肯定是匹配的

2.两者只有一者走到末尾,这种情况就是不匹配

3.以上两种情况都是递归的结束条件,如果不是以上两种情况,如果当前是*那么可以递归匹配0个,匹配1个或者匹配多个;如果是?那么就只匹配一个;这里面还有一些注意事项,就是因为*可以匹配一个或者多个字符,所以一个*和多个*其实是一样的,如果有多个*我们要把它归成一个*来处理,否则会出现时间复杂度过大,此外因为不区分大小写,所以可以使用tolower函数来帮助匹配

#include 
#include
using namespace std;

bool ismatch(const char*match,const char*str)
{
    if(*match=='\0'&&*str=='\0')
        return true;
    if(*match=='\0'||*str=='\0')
        return false;

    if(*match=='?')
    {
        if(!isdigit(*str)&&!isalpha(*str)) 
            return false;
        return ismatch(match+1,str+1);
    }
        
    else if(*match=='*')
    {
        while(*match=='*')
        {
            match++;
        }
        match--;//前面的循环中将*全部跳过了,但是我们的本意是当成一个*来处理,所以这里要回退一位
        
    if(!isdigit(*str)&&!isalpha(*str))
             return false;
         return ismatch(match+1,str)||ismatch(match+1,str+1)||ismatch(match,str+1);
    }
       
    else if(tolower(*match)==tolower(*str))
        return ismatch(match+1,str+1);

    return false;
}

int main()
{
    string match,str;
    while(cin>>match>>str)
    {
       
        bool ret=ismatch(match.c_str(),str.c_str());
        if(ret)
            cout<<"true"<<endl;
        else
            cout<<"false"<<endl;
    }
     return 0;

}

你可能感兴趣的:(杂项,算法,服务器,c++,笔试题)