C++笔试题

栏目: C++ · 发布时间: 5年前

内容简介:答案是:Print1调用正常,打印出hello world,但运行至Print2时,程序崩溃。调用Print1时,并不需要pA的地址,因为Print1的函数地址是固定的。编译器会给Print1传入一个this指针,该指针为NULL,但在Print1中该this指针并没有用到。先后打印出两行:A is constructed. B is constructed. 调用B的构造函数时,先会调用B的基类A的构造函数。然后在A的构造函数里调用Print。由于此时实例的类型B的部分还没有构造好,本质上它只是A的一个实
#include "stdafx.h"
#include<iostream>
using namespace std;

class A  
{  
private:  
        int m_value;  
   
public:  
        A(int value)  
        {  
                m_value = value;  
        }  
        void Print1()  
        {  
                printf("hello world");  
        }  
        void Print2()  
        {  
                printf("%d", m_value);  
        }  
    virtual void Print3()  
        {  
                printf("hello world");  
        }  
};

int _tmain(int argc, _TCHAR* argv[])
{
	A* pA;
	pA->Print1();
	pA->Print2();
	pA->Print3();

	return 0;
}
复制代码

答案是:Print1调用正常,打印出hello world,但运行至Print2时,程序崩溃。调用Print1时,并不需要pA的地址,因为Print1的函数地址是固定的。编译器会给Print1传入一个this指针,该指针为NULL,但在Print1中该this指针并没有用到。 只要程序运行时没有访问不该访问的内存就不会出错 ,因此运行正常。在运行print2时,需要this指针才能得到m_value的值。由于此时this指针为NULL,因此程序崩溃了。对于Print3这一虚函数,C++在调用虚函数的时候,要根据实例(即this指针指向的实例)中虚函数表指针得到虚函数表,再从虚函数表中找到函数的地址。由于这一步需要访问实例的地址(即this指针),而此时this指针为空指针,因此导致内存访问出错。

2. 运行下面的C++代码,得到的结果是什么?

#include<iostream>
using namespace std;

class A
{
public:  
         A()  
        {  
                Print();  
        }  
        virtual void Print()  
        {  
                cout<<"A is constructed."<<endl;
        }  
}; 

class B: public A  
{  
public:  
        B()  
        {  
                Print();  
        }  
        virtual void Print()  
        {  
               cout<<"B is constructed."<<endl; 
        }  
};  

int main()  
{  
        A* pA = new B();  
        delete pA;  

        return 0;  
}
复制代码

先后打印出两行:A is constructed. B is constructed. 调用B的构造函数时,先会调用B的基类A的构造函数。然后在A的构造函数里调用Print。由于此时实例的类型B的部分还没有构造好,本质上它只是A的一个实例,他的虚函数表指针指向的是类型A的虚函数表。因此此时调用的Print是A::Print。接着调用类型B的构造函数,并调用Print。此时已经开始构造B,并且虚函数表的指针已指向类B的虚函数表地址,因此此时调用的Print是B::Print。

3. 运行下面的C++代码,输出是什么?

class A{  
private:  
        int n1;  
        int n2;  
public:  
        A(): n2(0), n1(n2 + 2)  
        {  
        }  
   
        void Print()  
        {  
                std::cout << "n1: " << n1 << ", n2: " << n2 << std::endl;  
        }  
};  
int _tmain(int argc, _TCHAR* argv[])  
{  
        A a;  
        a.Print();  
        return 0;  
} 
复制代码

输出n1是一个随机的数字,n2为0。 在C++中,成员变量的初始化顺序与变量在类型中的申明顺序相同,而与它们在构造函数的初始化列表中的顺序无关。 因此首先初始化n1,而初始n1的参数n2还没有初始化,是一个随机值。初始化n2时,根据参数0对其初始化,故n2=0。

4. 编译运行下面的C++代码,结果是什么?(A)编译错误;(B)编译成功,运行时程序崩溃;(C)编译运行正常,输出10。请选择正确答案并分析原因。

class A{  
private:  
        int value;  
public:  
        A(int n)  
        {  
                value = n;  
        }  
        A(A other)  
        {  
                value = other.value;  
        }  
        void Print()  
        {  
                std::cout << value << std::endl;  
        }  
};  

int _tmain(int argc, _TCHAR* argv[])  
{  
        A a = 10;  
        A b = a;  
        b.Print();  
        return 0;  
} 
复制代码

编译错误。在复制构造函数中传入的参数是A的一个实例。 由于是传值,把形参拷贝到实参会调用复制构造函数。因此如果允许复制构造函数传值,那么会形成永无休止的递归并造成栈溢出。因此C++的标准不允许复制构造函数传值参数,而必须是传引用或者常量引用。 复制构造函数的参数需要改为:const A& other。

5. 运行如下的C++代码,输出是什么?

class A{  
    public:  
       virtual void Fun(int number = 10)  
       {  
           std::cout << "A::Fun with number " << number;  
       }  
    };  
       
    class B:public A  
    {  
    public:  
       virtual void Fun(int number = 20)  
       {  
           std::cout << "B::Fun with number " << number;  
       }  
    };  
       
    int main()  
    {  
       B b;  
       A &a = b;  
       a.Fun();  

	return 0;
    }  
复制代码

输出 B::Fun with number 10。由于a是一个指向B实例的引用,因此在运行的时候会调用B::Fun。但缺省参数是在编译期决定的。在编译的时候,编译器只知道a是一个类型a的引用,具体指向什么类型在编译期是不能确定的,因此会按照A::Fun的声明把缺省参数number设为10。这一题的 关键在于理解确定缺省参数的值是在编译的时候,但确定引用、指针的虚函数调用哪个类型的函数是在运行的时候

6. 运行如下的C代码,输出是什么?

char* GetString1()  
{  
   char p[] = "Hello World";//指向临时分配的桟空间,当运行至函数体外时,空间将被释放  
   return p;  
}  
char* GetString2()  
{  
   char *p = "Hello World";//指向全局常量区  
   return p;  
}  
int _tmain(int argc, _TCHAR* argv[])  
{  
   printf("GetString1 returns: %s. \n", GetString1());  
   printf("GetString2 returns: %s. \n", GetString2());  
   return 0;  
}
复制代码

输出两行,第一行GetString1 returns: 后面跟的是一串随机的内容,而第二行GetString2 returns: Hello World.两个函数的区别在于GetString1中是一个数组,而GetString2中是一个指针。运行到GetString1时,p是一个数组,会开辟一块内存,并拷贝"Hello World"初始化该数组。接着返回数组的首地址并退出该函数。由于p是GetString1内的一个局部变量,当运行到这个函数外面的时候,这个数组的内存会被释放掉。因此在_tmain函数里再去访问这个数组的内容时,结果是随机的。运行到GetString2时,p是一个指针,它指向的是字符串常量区的一个常量字符串。该常量字符串是一个全局的,并不会因为退出函数GetString2而被释放掉。

7. 运行下图中C代码,输出的结果是什么?

int _tmain(int argc, _TCHAR* argv[])  
    {  
       char str1[] = "hello world";//桟空间  
       char str2[] = "hello world";//桟空间,临时分配,地址不同  
       char* str3 = "hello world";//常量区  
       char* str4 = "hello world";//指向同一块全局常量区  
       if(str1 == str2)  
           printf("str1 and str2 are same.\n");  
       else  
           printf("str1 and str2 are not same.\n");  
       if(str3 == str4)  
           printf("str3 and str4 are same.\n");  
       else  
           printf("str3 and str4 are not same.\n");  
       return 0;  
    }  
复制代码

这个题目与上一题目类似。str1和str2是两个字符串数组。我们会为它们分配两个长度为12个字节的空间,并把"hello world"的内容分别拷贝到数组中去。这是两个初始地址不同的数组,因此比较str1和str2的值,会不相同。str3和str4是两个指针,我们无需为它们分配内存以存储字符串的内容,而只需要把它们指向"hello world“在内存中的地址就可以了。由于"hello world”是常量字符串,它在内存中只有一个拷贝,因此str3和str4指向的是同一个地址。因此比较str3和str4的值,会是相同的。

8. 运行Test,输出结果是什么?

void Test()  
{  
    class B  
    {  
    public:  
        B(void)  
        {  
            cout<<"B\t";  
        }  
        ~B(void)  
        {  
            cout<<"~B\t";  
        }  
    };  
    struct C  
    {  
        C(void)  
        {  
            cout<<"C\t";  
        }  
        ~C(void)  
        {  
            cout<<"~C\t";  
        }  
    };  
    struct D : B  
    {  
        D()  
        {  
            cout<<"D\t";  
        }  
        ~D()  
        {  
            cout<<"~D\t";  
        }  
    private:  
        C c;  
    };  
    D d;  
}  
复制代码

运行结果:B C D ~D ~ C ~B。当实例化D对象时,由于继承自B,因而首先调用B的构造函数,之后初始化私有成员C,完成父类的构造与私有成员的初始化后再进入D的构造函数体内;之后,按照相反顺序完成对象的析构操作。初始化与赋值是不同的,一般初始化是在初始化列表完成的,构造函数体中进行的是赋值操作。

9. 下列程序输出结果是什么?

class A  
{  
public:  
    int a;//4字节  
    char b;//1字节  
    double c;//8字节,以此为基本单位进行字节对齐,上面的两个变量对齐后共为8字节,加上当前字节数,共为8+8=16字节。  
    virtual void print()//虚函数,构建虚函数表,虚函数表指针需要4字节,字节对其,扩充为8字节  
    {  
        cout<<"this is father's function!"<<endl;  
    }  
    virtual void print1()//地址存于虚函数表  
    {  
        cout<<"this is father's function1!"<<endl;  
    }  
        virtual void print2()//无需分配内存  
        {  
            cout<<"this is father's function2!"<<endl;  
        }  
private:  
    float d;//4字节,字节对其,扩充为8字节  
};  
  
class B : A//首先承载A的大小:32字节  
{  
public:  
    virtual void print()//修改虚函数表地址  
    {  
        cout<<"this is children's function!"<<endl;  
    }  
    void print1()//仅存有函数入口地址,无需分配内存  
    {  
        cout<<"this is children's function1!"<<endl;  
    }  
private:  
    char e;//1字节,字节对齐,扩充为8字节(可以发现,继承后,字节对齐单位也放生变化)  
};  
int main(void)  
{  
    cout<<sizeof(A)<<"  "<<sizeof(B)<<endl;  
    system("pause");  
    return 0;  
}  
复制代码

运行结果:32,40.这个题目解决的关键在于掌握字节对齐的相关知识点。具体见上面注释。

10. 以下程序,在编译与运行时或发生什么?

class A  
{  
public:  
    virtual void foo()   {  }  
};  
class B  
{  
public:  
    virtual void foo()   {  }  
};  
class C : public A , public B  
{  
public:  
    virtual void foo()   {  }  
};  
void bar1(A *pa)  
{  
    B *pc = dynamic_cast<B*>(pa);//运行期遍历继承树  
}  
void bar2(A *pa)  
{  
    B *pc = static_cast<B*>(pa);//两个类无关,编译出错  
}  
void bar3()  
{  
    C c;  
    A *pa = &c;  
    B *pb = static_cast<B*>(static_cast<C*>(pa));//存在继承关系,编译正确  
} 
复制代码

对于bar1,dynamic_cast是在运行时遍历继承树,所以,在编译时不会报错。但是因为A和B无继承关系,所以运行时报错。static_cast:编译器隐式执行的任何类型转换都可由它显示完成。其中对于:(1)基本类型。如可以将int转换为double(编译器会执行隐式转换),但是不能将int 用它转换到double (没有此隐式转换)。(2)对于用户自定义类型,如果两个类无关,则会出错,如果存在继承关系,则可以在基类和派生类之间进行任何转型,在编译期间不会出错。所以bar3可以通过编译。

11. 执行下列程序,会发生什么?

class A  
    {  
    public:  
        string a;  
        void f1()  
        {  
            printf("Hello World");  
        }  
        void f2()  
        {  
            a = "Hello World";  
            printf("%s",a.c_str());  
        }  
        virtual void f3()  
        {  
            a = "Hello World";  
            printf("%s",a.c_str());  
        }  
        static void f4()  
        {  
            printf("Hello World");  
        }  
    };  
      
    int main(void)  
    {  
        A *aptr = NULL;  //创建一个A对象,对象指针为空,意味着对象仅有空壳,无法借助指针访问成员变量  
        aptr->f1();      //运行成功,调用f1函数仅需函数入口地址,无需访问对象中的成员变量  
            aptr->f2();      //运行失败,调用f2需访问成员变量  
            aptr->f3();      //运行失败,同上  
            aptr->f4();      //静态成员不属于任何对象,运行成功  
        return 0;  
    }  
复制代码

此题解答如程序注释所示。

12. 下列函数运行情况如何?

int func()  
{  
    char b[2]={0};  
    strcpy(b,"aaa");  
}
复制代码

Debug版崩溃,Release版正常。因为在Debug中有ASSERT断言保护,所以要崩溃,而在Release中就会删掉ASSERT,所以正常运行。但是不推荐这么做,因为这样会覆盖不属于自己的内存。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Algorithms on Strings, Trees and Sequences

Algorithms on Strings, Trees and Sequences

Dan Gusfield / Cambridge University Press / 1997-5-28 / USD 99.99

String algorithms are a traditional area of study in computer science. In recent years their importance has grown dramatically with the huge increase of electronically stored text and of molecular seq......一起来看看 《Algorithms on Strings, Trees and Sequences》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具