精华内容
下载资源
问答
  • C++面试题

    千次阅读 多人点赞 2019-01-07 17:06:10
    C++面试题 1. C++的类和C里面的struct有什么区别? 答:struct和class的区别在于C里面的struct没有定义方法,而C++的类是将不同类型的数据和与这些数据相关的操作封装在一起的集合体,包含了操作方法。   2. 请...

    C++面试题

    1. C++的类和C里面的struct有什么区别?

    答:structclass的区别在于C里面的struct没有定义方法,而C++的类是将不同类型的数据和与这些数据相关的操作封装在一起的集合体,包含了操作方法。

     

    2. 请说出const#define相比,有何优点?

    答:const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。

    有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。

     

    3. 类成员函数的重载、覆盖和隐藏区别?

    答:成员函数被重载的特征:相同的范围(在同一个类中);函数名字相同;参数不同;virtual关键字可有可无。

    覆盖是指派生类函数覆盖基类函数,其特征是:函数名字相同;参数相同;范围不同(分别位于派生类与基类中);基类函数必须有 virtual 关键字。

    “隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:

    如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类函数将被隐藏(注意别与重载混淆)。如果派生类的函数与基类函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类函数被隐藏(注意别与覆盖混淆)。

     

    4. 堆和栈的区别?

    答:栈是由编译器自动分配释放,用来存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

    堆一般是由程序员分配释放,若程序员不释放,程序结束时可能由操作系统回收。

     

    5. 面向对象的三个基本特征,并简单叙述之?

    答:

    封装:将客观事物抽象成类,每个类对自身的数据和方法实行访问权限保护(privateprotectedpublic)

    继承:广义的继承有三种实现形式:实现继承、可视继承、接口继承。前两种(类继承)和后一种(对象组合=>接口继承以及纯虚函数)构成了功能复用的两种方式。

    多态:是将父类对象设置成为和一个或多个他的子类对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给其父类类型的指针。

     

    6. 构造函数可否是虚函数,为什么?析构函数呢,可否是纯虚的呢?

    答:构造函数不能为虚函数,要构造一个对象,必须清楚地知道要构造什么,否则无法构造一个对象。析构函数可以为纯虚函数。

     

    7. C++是不是类型安全的?

    答:C++不是类型安全的。因为两个不同类型的指针之间可以强制转换(用reinterpret cast)。

     

    8. 多态的作用?

    答:多态的作用包括:隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用。接口重用:为了在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用。

     

    9. 请找出下面代码中的错误。

    void test ()

    {

      char string[10];

      char* str1 = "0123456789";

      strcpy( string, str1 );

    }

    答:字符串str1需要11个字节才能存放下(包括末尾的’\0’),而string只有10个字节的空间,strcpy会导致数组越界。

     

    10. 请找出下面代码中的错误。

    void test(char* str1)

    {

      char string[10];

      if( strlen( str1 ) <= 10 )

    {

        strcpy( string, str1 );

      }

    }

    答:if(strlen(str1) <= 10)应改为if(strlen(str1) < 10),因为strlen的结果未统计’\0’所占用的1个字节。

     

    11. 参数传递具体有哪几种方式?

       答:参数传递具体包括:值传递,指针传递和引用传递三种方式。

     

    12. 重载和重写的区别?

    答:首先从定义上来说:

    重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。

    重写:是指子类重新定义复类虚函数的方法。

    其次从实现原理上来说:

    重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_funcstr_func。对于这两个函数的调用,在编译期间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关。

    重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。

     

    13. 虚函数的本质和实现机制?

    答:虚函数的本质就是通过基类访问派生类定义的函数。虚函数只能借助于指针或者引用来达到多态的效果。

     

    14. 堆栈溢出一般是由什么原因导致的?

    答:没有回收垃圾资源。

     

    15. 关键字static的作用是什么?

    答:在C语言中,关键字static有三个明显的作用:

    1. 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
    2. 在模块内,一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
    3. 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。

     

    16. 论述含参数的宏与函数的优缺点。

    答:

    宏的优点:执行效率高,宏的缺点:容易出错。

    函数的优点:不容易出错,函数的缺点:执行效率低。

     

    17. 解释局部变量、全局变量和静态变量的含义。

    答:局部变量、全局变量和静态变量是相对于生命周期说的,全局变量伴随程序直到最后,局部变量离开了作用域就会销毁。静态变量分为静态局部变量和静态全局变量,它们的生命周期伴随程序直到最后,二者的区别在于可见性不同。

     

    18. do……whilewhile……do有什么区别?

    答:前一个循环一遍再判断,后一个判断以后再循环。

     

    19. 队列和栈有什么区别?

    答:队列先进先出,栈后进先出。

     

    20. 用变量a给出下面的定义。

      a)一个整型数

      b)一个指向整型数的指针

      c)一个指向指针的的指针,它指向的指针是指向一个整型数

      d)一个有10个整型数的数组

      e)一个有10个指针的数组,该指针是指向一个整型数的

      f)一个指向有10个整型数数组的指针

      g)一个指向函数的指针,该函数有一个整型参数并返回整型数

      h)一个有10个指针的数组,该指针是指向一个函数的,该函数有一个整型参数并返回整型数。

       答:用变量a给出的定义如下:

     a int a;                 b int *a;

     c int **a;             d int a[10];

     e int *a[10];        f int (*a)[10];

     g int (*a)(int);     h int (*a[10])(int);

     

    21. 全局变量和局部变量在内存中是否有区别?如果有,是什么区别?

    答:全局变量储存在静态数据库,局部变量在堆栈。

     

    22. int i=(j=4,k=8,l=16,m=32); printf(“%d”, i);该语句的输出结果是多少?

    答:输出结果为:32

     

    23. 局部变量能否和全局变量重名?

    答:能,局部会屏蔽全局。如果要使用全局变量,需要使用"::"

    局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如,在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内。

     

    24. 请写出下面代码的输出结果。

    #include<stdio.h>

    main()

    {

      int a,b,c,d;

      a=10;

      b=a++;

      c=++a;

      d=10*a++;

      printf("bcd%d%d%d"bcd);

    }

    答:输出结果为:bcd1012120

     

    25. 如果用VC开发程序,常见这么几个错误,C2001, C2005, 这些错误的原因是什么。

    答:在学习VC++的过程中,遇到的C2001错误的错误消息主要为:unresolved external symbol(不确定的外部符号)。如果连接程序不能在所有的库和目标文件内找到所引用的函数、变量或标签,将产生此错误消息。

    一般来说,发生该错误的原因有两个:一是所引用的函数、变量不存在、拼写不正确或者使用错误;其次可能使用了不同版本的连接库。

    编程中也经常能遇到C2005错误,即重复定义错误,其实LNK2005错误并不是一个很难解决的错误。

     

    26. 请说明虚函数的本质和实现机制

    答:虚函数的本质是通过基类访问派生类定义的函数。虚函数只能借助于指针或者引用来达到多态效果。

     

    27. 分别说出描述点对象、大小对象和矩形对象的类名称。

       答:CPoint类,CSize类和CRect类。

     

    28. 异步socket编程中,send不出数据的原因是什么,你是怎么处理的?

    答:异步socket编程中,send不出数据的原因共有两个,分别是:TCP下连接断开了和该socket处在阻塞状态(也就是说在发送数据中)。处理的办法就是记录下该SOCKET的状态,当状态为阻塞的时间,放入缓冲,当该SOCKET再次可写时,发送。

     

    29. 描述并比较以下对象:事件,信标,临界区,互斥对象。

    答:这些对象都是用于线程同步的对象。

    临界区:一种保证在某一时刻只有一个线程能访问数据的简便办法。它只可以在同一进程内部使用。主要API函数有,产生临界区:InitializeCriticalSection,删除临界区:DeleteCriticalSection,进入临界区:EnterCriticalSection,退出临界区:Le***eCriticalSection

    互斥对象:互斥对象跟临界区相似,但它不仅仅能够在同一应用程序不同  线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享,当然下面两者也有这个特点。主要API函数有,创建互斥量: CreateMutex,打开一个存在的互斥量: OpenMutex,释放互斥量的使用权:ReleaseMutex,关闭互斥量: CloseHandle

    信标:使用信号量(信标)最重要用途是:信号允许多个线程同时使用共享资源,它指出了同时访问共享资源的线程最大数目。它的API函数和使用方法都与互斥对象相似,如创建信号灯:CreateSemaphore,传入的参数可以指定信号灯的初始值。

    事件:用来通知其他进程/线程某件操作已经完成。API函数有创建,打开事件对象等,特殊点的是可以用函数SetEvent人工设置事件为有无信号状态,因此创建事件对象时可以有两种方式,一种为自动重置,一种为人工重置。只有人工重置方式创建的事件对象才能正确使用函数SetEvent

     

    30. 异步IO和同步IO有什么区别?

    答:异步IO当函数返回时不一定就完成了IO操作,而同步IO已经完成了。所以异步IO需要有一个事件,当IO完成时会设置此事件,调用者在事件上等待。

     

    31. 分别描述类CDatabase和类CRecordset的用途。

    答:CDatabase类用于建立与数据源的连接,CRecordset类功能强大,可以将它看做数据源的一个记录集,其中封装了对记录集的各种操作,如:滚动,修改,增加,删除,查询等。

     

    32. cdeclstdcallfastcall是什么?哪种可以实现个数不定的入口参数,为什么?

    答:三者都是函数调用的约定。

    1. fastcall:采用寄存器传递参数,特点就是快了。
    2. cdeclcdeclare的缩写,是CC++程序的缺省调用方式,规则是,按从右至左的顺序压参数入栈,由调用者把参数弹出栈,对于传送参数的内存栈是由调用者来维护的,正因为如此,只有这种调用方式可实现个数不定的入口参数。
    3. stdcall:是Pascal程序的缺省调用方式,规则是,按从右至左的顺序压参数入栈,被调用的函数在返回前清理传送参数的内存栈。 上两者的主要区别是前者由调用者清理栈,后者由被调用的函清理栈。当然函数名的修饰部分也是不同的。

      

    33. 如何查出内存泄漏和非法操作的BUG(在Release版本下)?

    答:使用map文件。

     

    34. 程序中的局部变量、全局变量和动态申请数据具体存在于什么位置?

       答:程序的局部变量存在于堆栈中,全局变量存在于静态区中,动态申请数据存在于堆中。

     

    35. new delete malloc free的联系与区别?

    答:new delete malloc free都是在堆(heap)上进行动态的内存操作。用malloc函数需要指定内存分配的字节数并且不能初始化对象,new 会自动调用对象的构造函数。delete会调用对象的destructor,而free不会调用对象的destructor

     

    36. 描述内存分配方式以及它们的区别?

    答:

    1. 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
    2. 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。
    3. 从堆上分配,亦称动态内存分配。程序在运行的时候用mallocnew 申请任意多少的内存,程序员自己负责在何时用freedelete释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。

     

    37. 指针和引用有什么区别?

    答:

    1. 引用在创建的同时必须初始化,即引用到一个有效的对象;而指针在定义的时候不必初始化,可以在定义后面的任何地方重新赋值。
    2. 不存在NULL引用,引用必须与合法的存储单元关联;而指针则可以是NULL
    3. 引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用;而指针在任何时候都可以改变为指向另一个对象。给引用赋值并不是改变它和原始对象的绑定关系。
    4. 引用的创建和销毁并不会调用类的拷贝构造函数。
    5. 语言层面,引用的用法和对象一样;在二进制层面,引用一般都是通过指针来实现的,只不过编译器帮我们完成了转换。

     

    38. 有一段文本,统计其中的单词数。例如:As a technology , "HailStorm"is so new that it is still only known by its code name.(注意:单词间的间隔不一定是一个空格。)

    答:假设该文本已存入text这个数组里,可执行程序代码如下:

    void main(){

       char text[1000]={"As a technology,'HailStorm'is so new that it is still only known by its code name."};

       int i=0,count=0;

       bool flag=true;

       while (text&&i<1000){

          if (text==' '){

               flag=true;

          }else if (flag==true && ((text>='a'&&text<='z')

          ||(text>='A'&&text<='Z'))){?

               count++;flag=false;

          }

          i++;

       }

       cout<<count;

    }

    展开全文
  • c++面试题

    2016-06-19 02:02:30
    程序员面试题精选题C++面试题 网络转载请注明出处http://zhedahht.blog.163.com/。整理出版物请和作者联系。 题目(一):我们可以用static修饰一个类的成员函数,也可以用const修饰类的成员函数(写在函数的...

    看了几套面试题 发现基础很不牢靠啊


    程序员面试题精选题C++面试题

    网络转载请注明出处http://zhedahht.blog.163.com/。整理出版物请和作者联系。

    题目(一):我们可以用static修饰一个类的成员函数,也可以用const修饰类的成员函数(写在函数的最后表示不能修改成员变量,不是指写在前面表示返回值为常量)。请问:能不能同时用staticconst修饰类的成员函数?

    分析:答案是不可以。C++编译器在实现const的成员函数的时候为了确保该函数不能修改类的实例的状态,会在函数中添加一个隐式的参数const this*。但当一个成员为static的时候,该函数是没有this指针的。也就是说此时static的用法和static是冲突的。

    我们也可以这样理解:两者的语意是矛盾的。static的作用是表示该函数只作用在类型的静态变量上,与类的实例没有关系;而const的作用是确保函数不能修改类的实例的状态,与类型的静态变量没有关系。因此不能同时用它们。

    题目(二):运行下面的代码,输出是什么?

    class A

    {

    };

     

    class B

    {

    public:

            B() {}

            ~B() {}

    };

     

    class C

    {

    public:

            C() {}

            virtual ~C() {}

    };

     

    int _tmain(int argc, _TCHAR* argv[])

    {

            printf("%d, %d, %d\n"sizeof(A), sizeof(B), sizeof(C));

            return 0;

    }

    分析:答案是1, 1, 4class A是一个空类型,它的实例不包含任何信息,本来求sizeof应该是0。但当我们声明该类型的实例的时候,它必须在内存中占有一定的空间,否则无法使用这些实例。至于占用多少内存,由编译器决定。Visual Studio 2008中每个空类型的实例占用一个byte的空间。

    class Bclass A的基础上添加了构造函数和析构函数。由于构造函数和析构函数的调用与类型的实例无关(调用它们只需要知道函数地址即可),在它的实例中不需要增加任何信息。所以sizeof(B)sizeof(A)一样,在Visual Studio 2008中都是1

    class Cclass B的基础上把析构函数标注为虚拟函数。C++的编译器一旦发现一个类型中有虚拟函数,就会为该类型生成虚函数表,并在该类型的每一个实例中添加一个指向虚函数表的指针。在32位的机器上,一个指针占4个字节的空间,因此sizeof(C)4

    题目(三):运行下面中的代码,得到的结果是什么

    class A

    {

    private:

            int m_value;

     

    public:

            A(int value)

            {

                    m_value = value;

            }

            void Print1()

            {

                    printf("hello world");

            }

            void Print2()

            {

                    printf("%d", m_value);

            }

    };

     

    int _tmain(int argc, _TCHAR* argv[])

    {

            A* pA = NULL;

            pA->Print1();

            pA->Print2();

     

            return 0;

    }

    分析:答案是Print1调用正常,打印出hello world,但运行至Print2时,程序崩溃。调用Print1时,并不需要pA的地址,因为Print1的函数地址是固定的。编译器会给Print1传入一个this指针,该指针为NULL,但在Print1中该this指针并没有用到。只要程序运行时没有访问不该访问的内存就不会出错,因此运行正常。在运行print2时,需要this指针才能得到m_value的值。由于此时this指针为NULL,因此程序崩溃了。

    题目(四):运行下面中的代码,得到的结果是什么

    class A

    {

    private:

            int m_value;

     

    public:

            A(int value)

            {

                    m_value = value;

            }

            void Print1()

            {

                    printf("hello world");

            }

            virtual void Print2()

            {

                    printf("hello world");

            }

    };

     

    int _tmain(int argc, _TCHAR* argv[])

    {

            A* pA = NULL;

            pA->Print1();

            pA->Print2();

     

            return 0;

    }

    分析:答案是Print1调用正常,打印出hello world,但运行至Print2时,程序崩溃。Print1的调用情况和上面的题目一样,不在赘述。由于Print2是虚函数。C++调用虚函数的时候,要根据实例(即this指针指向的实例)中虚函数表指针得到虚函数表,再从虚函数表中找到函数的地址。由于这一步需要访问实例的地址(即this指针),而此时this指针为空指针,因此导致内存访问出错。

    题目(五):静态成员函数能不能同时也是虚函数

    分析:答案是不能。调用静态成员函数不要实例。但调用虚函数需要从一个实例中指向虚函数表的指针以得到函数的地址,因此调用虚函数需要一个实例。两者相互矛盾。

    题目(六):运行下列C++代码,输出什么?

    struct Point3D

    {

            int x;

            int y;

            int z;

    };

     

    int _tmain(int argc, _TCHAR* argv[])

    {

            Point3D* pPoint = NULL;

            int offset = (int)(&(pPoint)->z);

     

            printf("%d", offset);

            return 0;

    }

    答案:输出8。由于在pPoint->z的前面加上了取地址符号,运行到此时的时候,会在pPoint的指针地址上加z在类型Point3D中的偏移量8。由于pPoint的地址是0,因此最终offset的值是8

    &(pPoint->z)的语意是求pPoint中变量z的地址(pPoint的地址0z的偏移量8),并不需要访问pPoint指向的内存。只要不访问非法的内存,程序就不会出错。

    题目(七):运行下列C++代码,输出什么?

    class A

    {

    public:

            A()

            {

                    Print();

            }

            virtual void Print()

            {

                    printf("A is constructed.\n");

            }

    };

     

    class B: public A

    {

    public:

            B()

            {

                    Print();

            }

     

            virtual void Print()

            {

                    printf("B is constructed.\n");

            }

    };

     

    int _tmain(int argc, _TCHAR* argv[])

    {

            A* pA = new B();

            delete pA;

     

            return 0;

    }

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

    同样是调用虚拟函数Print,我们发现在类型A的构造函数中,调用的是A::Print,在B的构造函数中,调用的是B::Print。因此虚函数在构造函数中,已经失去了虚函数的动态绑定特性。

    题目(八):运行下列C#代码,输出是什么?

    namespace ChangesOnString

    {

        class Program

        {

            static void Main(string[] args)

            {

                String str = "hello";

                str.ToUpper();

                str.Insert(0, " WORLD");

     

                Console.WriteLine(str);

            }

        }

    }

    答案:输出是hello。由于在.NET中,String有一个非常特殊的性质:String的实例的状态不能被改变。如果String的成员函数会修改实例的状态,将会返回一个新的String实例。改动只会出现在返回值中,而不会修改原来的实例。所以本题中输出仍然是原来的字符串值hello

    如果试图改变String的内容,改变之后的值可以通过返回值拿到。用StringBuilder是更好的选择,特别是要连续多次修改的时候。如果用String连续多次修改,每一次修改都会产生一个临时对象,开销太大。

    题目(九):C++C#中,structclass有什么不同

    答案:C++中,如果没有标明函数或者变量是的访问权限级别,在struct中,是public的;而在class中,是private的。

                    C#中,如果没有标明函数或者变量的访问权限级别,structclass中都是private的。structclass的区别是:struct定义值类型,其实例在栈上分配内存;class定义引用类型,其实例在堆上分配内存。

    题目(十):运行下图中的C#代码,输出是什么

    namespace StaticConstructor

    {

        class A

        {

            public A(string text)

            {

                Console.WriteLine(text);

            }

        }

     

        class B

        {

            static A a1 = new A("a1");

            A a2 = new A("a2");

     

            static B()

            {

                a1 = new A("a3");

            }

     

            public B()

            {

                a2 = new A("a4");

            }

        }

     

        class Program

        {

            static void Main(string[] args)

            {

                B b = new B();

            }

        }

    }

    答案:打印出四行,分别是a1a3a2a4

    在调用类型B的代码之前先执行B的静态构造函数。静态函数先初始化类型的静态变量,再执行静态函数内的语句。因此先打印a1再打印a3。接下来执行B b = new B(),即调用B的普通构造函数。构造函数先初始化成员变量,在执行函数体内的语句,因此先后打印出a2a4

    题目(11运行下图中的C#代码,输出是什么?

    namespace StringValueOrReference

    {

        class Program

        {

            internal static void ValueOrReference(Type type)

            {

                String result = "The type " + type.Name;

     

                if (type.IsValueType)

                    Console.WriteLine(result + " is a value type.");

                else

                    Console.WriteLine(result + " is a reference type.");

            }

     

            internal static void ModifyString(String text)

            {

                text = "world";

            }

     

            static void Main(string[] args)

            {

                String text = "hello";

     

                ValueOrReference(text.GetType());

                ModifyString(text);

     

                Console.WriteLine(text);

            }

        }

    }

    答案:输出两行。第一行是The type String is reference type. 第二行是hello。类型String的定义是public sealed class String {...},既然是class,那么String就是引用类型

    在方法ModifyString里,对text赋值一个新的字符串,此时改变的不是原来text的内容,而是把text指向一个新的字符串"world"。由于参数text没有加ref或者out,出了方法之后,text还是指向原来的字符串,因此输出仍然是"hello".

    题目(12:运行下图中的C++代码,输出是什么

    #include <iostream>

     

    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是一个随机的数字,n20。在C++中,成员变量的初始化顺序与变量在类型中的申明顺序相同,而与它们在构造函数的初始化列表中的顺序无关。因此在这道题中,会首先初始化n1,而初始n1的参数n2还没有初始化,是一个随机值,因此n1就是一个随机值。初始化n2时,根据参数0对其初始化,故n2=0

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

    #include <iostream>

     

    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++的标准不允许复制构造函数传值参数,而必须是传引用或者常量引用。在Visual StudioGCC中,都将编译出错。

    题目(14)运行下图中的C++代码,输出是什么

    int SizeOf(char pString[])

    {

            return sizeof(pString);

    }

     

    int _tmain(int argc, _TCHAR* argv[])

    {

            char* pString1 = "google";

            int size1 = sizeof(pString1);

            int size2 = sizeof(*pString1);

     

            char pString2[100] = "google";

            int size3 = sizeof(pString2);

            int size4 = SizeOf(pString2);

     

            printf("%d, %d, %d, %d", size1, size2, size3, size4);

     

            return 0;

    }

    答案4, 1, 100, 4pString1是一个指针。在32位机器上,任意指针都占4个字节的空间。*pString1是字符串pString1的第一个字符。一个字符占一个字节。pString2是一个数组,sizeof(pString2)是求数组的大小。这个数组包含100个字符,因此大小是100个字节。而在函数SizeOf中,虽然传入的参数是一个字符数组,当数组作为函数的参数进行传递时,数组就自动退化为同类型的指针。因此size4也是一个指针的大小,为4.

    题目(15:运行下图中代码,输出的结果是什么?这段代码有什么问题

    #include <iostream>

     

    class A

    {

    public:

            A()

            {

                    std::cout << "A is created." << std::endl;

            }

     

            ~A()

            {

                    std::cout << "A is deleted." << std::endl;

            }

    };

     

    class B : public A

    {

    public:

            B()

            {

                    std::cout << "B is created." << std::endl;

            }

     

            ~B()

            {

                    std::cout << "B is deleted." << std::endl;

            }

    };

     

    int _tmain(int argc, _TCHAR* argv[])

    {

            A* pA = new B();

            delete pA;

     

            return 0;

    }

    答案:输出三行,分别是:A is created. B is created. A is deleted。用new创建B时,回调用B的构造函数。在调用B的构造函数的时候,会先调用A的构造函数。因此先输出A is created. B is created.

    接下来运行delete语句时,会调用析构函数。由于pA被声明成类型A的指针,同时基类A的析构函数没有标上virtual,因此只有A的析构函数被调用到,而不会调用B的析构函数。

    由于pA实际上是指向一个B的实例的指针,但在析构的时候只调用了基类A的析构函数,却没有调用B的析构函数。这就是一个问题。如果在类型B中创建了一些资源,比如文件句柄、内存等,在这种情况下都得不到释放,从而导致资源泄漏。

    问题(16):运行如下的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();

    }

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

                这一题的关键在于理解确定缺省参数的值是在编译的时候,但确定引用、指针的虚函数调用哪个类型的函数是在运行的时候。

    问题(17:运行如下的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"初始化该数组。接着返回数组的首地址并退出该函数。由于pGetString1内的一个局部变量,当运行到这个函数外面的时候,这个数组的内存会被释放掉。因此在_tmain函数里再去访问这个数组的内容时,结果是随机的

    当运行到GetString2时,p是一个指针,它指向的是字符串常量区的一个常量字符串。该常量字符串是一个全局的,并不会因为退出函数GetString2而被释放掉。因此在_tmain中仍然根据GetString2返回的地址得到字符串"Hello World"

    问题(18)运行下图中C#代码,输出的结果是什么

    namespace StaticVariableInAppDomain

    {

        [Serializable]

        internal class A : MarshalByRefObject

        {

            public static int Number;

     

            public void SetNumber(int value)

            {

                Number = value;

            }

        }

     

        [Serializable]

        internal class B

        {

            public static int Number;

     

            public void SetNumber(int value)

            {

                Number = value;

            }

        }

     

        class Program

        {

            static void Main(string[] args)

            {

                String assamblyName = Assembly.GetEntryAssembly().FullName;

                AppDomain domain = AppDomain.CreateDomain("NewDomain");

     

                A.Number = 10;

                String nameOfA = typeof(A).FullName;

                A a = domain.CreateInstanceAndUnwrap(assamblyName, nameOfA) as A;

                a.SetNumber(20);

                Console.WriteLine("Number in class A is {0}"A.Number);

     

                B.Number = 10;

                String nameOfB = typeof(B).FullName;

                B b = domain.CreateInstanceAndUnwrap(assamblyName, nameOfB) as B;

                b.SetNumber(20);

                Console.WriteLine("Number in class B is {0}"B.Number);

            }

        }

    }

    答案输出两行,第一行是Number in class A is 10,而第二行是Number in class B is 20。上述C#代码先创建一个命名为NewDomain的应用程序域,并在该域中利用反射机制创建类型A的一个实例和类型B的一个实例。我们注意到类型A是继承自MarshalByRefObject,而B不是。虽然这两个类型的结构一样,但由于基类不同而导致在跨越应用程序域的边界时表现出的行为将大不相同。

          由于A继承MarshalByRefObject,那么a实际上只是在缺省的域中的一个代理,它指向位于NewDomain域中的A的一个实例。当a.SetNumber时,是在NewDomain域中调用该方法,它将修改NewDomain域中静态变量A.Number的值并设为20由于静态变量在每个应用程序域中都有一份独立的拷贝,修改NewDomain域中的静态变量A.Number对缺省域中的静态变量A.NewDomain没有任何影响。由于Console.WriteLine是在缺省的应用程序域中输出A.Number,因此输出仍然是10

        B只从Object继承而来的类型,它的实例穿越应用程序域的边界时,将会完整地拷贝实例。在上述代码中,我们尽管试图在NewDomani域中生成B的实例,但会把实例b拷贝到缺省的域。此时,调用b.SetNumber也是在缺省的域上进行,它将修改缺省的域上的A.Number并设为20。因此这一次输出的是20

    问题(19)运行下图中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 and str2 are not same,第二行是str3 and str4 are same

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

    问题(20)运行下图中C#代码,输出的结果是什么?并请比较这两个类型各有什么特点,有哪些区别

    namespace Singleton

    {

        public sealed class Singleton1

        {

            private Singleton1()

            {

                Console.WriteLine("Singleton1 constructed");

            }

            public static void Print()

            {

                Console.WriteLine("Singleton1 Print");

            }

            private static Singleton1 instance = new Singleton1();

            public static Singleton1 Instance

            {

                get

                {

                    return instance;

                }

            }

        }

     

        public sealed class Singleton2

        {

            Singleton2()

            {

                Console.WriteLine("Singleton2 constructed");

            }

            public static void Print()

            {

                Console.WriteLine("Singleton2 Print");

            }

            public static Singleton2 Instance

            {

                get

                {

                    return Nested.instance;

                }

            }

            class Nested

            {

                static Nested() { }

     

                internal static readonly Singleton2 instance = new Singleton2();

            }

        }

     

        class Program

        {

            static void Main(string[] args)

            {

                Singleton1.Print();

                Singleton2.Print();

            }

        }

    }

    答案: 输出三行:第一行“Singleton1 constructed”,第二行“Singleton1 Print”,第三行“Singleton2 Print”

    当我们调用Singleton1.Print时,.NET运行时会自动调用Singleton1的静态构造函数,并初始化它的静态变量。此时会创建一个Singleton1的实例,因此会调用它的构造函数Singleton2的实例是在Nested的静态构造函数里初始化的。只有当类型Nested被使用时,才回触发.NET运行时调用它的静态构造函数。我们注意到我们只在Sington2.Instance里面用到了Nested。而在我们的代码中,只调用了Singleton2.Print。因此不会创建Singleton2的实例,也不会调用它的构造函数

    这两个类型其实都是单例模式(Singleton)的实现。第二个实现Singleton2只在真的需要时,才会创建实例,而第一个实现Singleton1则不然。第二个实现在空间效率上更好。

    问题(21):C#是一门托管语言,那么是不是说明只要用C#,就能保证不会出现内存泄露和其他资源泄漏?如果不是,在哪些情况下可能会出现泄漏?

    答案:C#不能保证没有资源泄漏。比如如下几种情况可能会造成资源泄漏:(1) 调用Native code,比如用P/Invoke或者调用COM;(2) 读写文件时的,没有及时close stream, 或者ADO.NET连数据库时,没有及时关闭连接,也算资源泄漏?(3)注册事件后没有remove,导致publisher和subscriber的强依 赖,垃圾回收可能会被推迟;(4).NET还定义了一些方法直接申请非托管内存,比如Marshal.AllocHGlobal和Marshal.AllocCoTaskMem。通过这种方式得到的内存,如果没有及时释放,也会造成内存泄露。

    问题(22):下面的两段C#有哪些不同?

    static void CatchException1()

    {

        try

        {

            Function();

        }

        catch

        {

            throw;

        }

    }

     

    static void CatchException2()

    {

        try

        {

            Function();

        }

        catch (Exception e)

        {

            throw e;

        }

    }

    答案:两个函数的catch都是重新抛出截获的exception,但抛出的exception的call stack是不一样的。对于第一种方法,exception的call stack是从最开始的抛出地点开始的。对于第二种方法,exception的call stack是从CatchException2开始的,最初抛出的地方相关的信息被隐藏了。

    问题(23):运行下图中的C++代码,打印出的结果是什么?

    bool Fun1(char* str)

    {

        printf("%s\n", str);

        return false;

    }

     

    bool Fun2(char* str)

    {

        printf("%s\n", str);

        return true;

    }

     

    int _tmain(int argc, _TCHAR* argv[])

    {

        bool res1, res2;

        res1 = (Fun1("a") && Fun2("b")) || (Fun1("c") || Fun2("d"));

        res2 = (Fun1("a") && Fun2("b")) && (Fun1("c") || Fun2("d"));

     

        return res1 || res2;

    }

    答案:打印出4行,分别是a、c、d、a。

    在C/C++中,与、或运算是从左到右的顺序执行的。在计算rest1时,先计算Fun1(“a”) && Func2(“b”)。首先Func1(“a”)打印出内容为a的一行。由于Fun1(“a”)返回的是false, 无论Func2(“b”)的返回值是true还是false,Fun1(“a”) && Func2(“b”)的结果都是false。由于Func2(“b”)的结果无关重要,因此Func2(“b”)会略去而不做计算。接下来计算Fun1(“c”) || Func2(“d”),分别打印出内容c和d的两行。

                    在计算rest2时,首先Func1(“a”)打印出内容为a的一行。由于Func1(“a”)返回false,和前面一样的道理,Func2(“b”)会略去不做计算。由于Fun1(“a”) && Func2(“b”)的结果是false,不管Fun1(“c”) && Func2(“d”)的结果是什么,整个表达式得到的结果都是false,因此Fun1(“c”) && Func2(“d”)都将被忽略。

    问题(24):运行下面的C#代码,打印出来的结果是什么?

    struct Person

    {

        public string Name;

     

        public override string ToString()

        {

            return Name;

        }

    }

     

    class Program

    {

        static void Main(string[] args)

        {

            ArrayList array = new ArrayList();

            Person jim = new Person() {Name = "Jim"};

            array.Add(jim);

     

            Person first = (Person)array[0];

            first.Name = "Peter";

            Console.WriteLine(array[0].ToString());

        }

    }

    答案:Person的定义是一个struct,因此是一个值类型。在运行到语句Person first = (Person)array[0]的时候,first是array[0]的一个拷贝,first和array[0]不是一个实例。因此修改first对array[0]没有影响。

    问题(25):运行下面的C++代码,打印的结果是什么?

    class Base

    {

    public:

        void print() { doPrint();}

     

    private:

        virtual void doPrint() {cout << "Base::doPrint" << endl;}

    };

     

    class Derived : public Base

    {

    private:

        virtual void doPrint() {cout << "Derived::doPrint" << endl;}

    };

     

    int _tmain(int argc, _TCHAR* argv[])

    {

        Base b;

        b.print();

     

        Derived d;

        d.print();

     

           return 0;

    }

    答案:输出两行,分别是Base::doPrint和Derived::doPrint。在print中调用doPrint时,doPrint()的写法和this->doPrint()是等价的,因此将根据实际的类型调用对应的doPrint。所以结果是分别调用的是Base::doPrint和Derived::doPrint2。如果感兴趣,可以查看一下汇编代码,就能看出来调用doPrint是从虚函数表中得到函数地址的。

    分类: 面试题
    0
    0
    (请您对文章做出评价)
    « 上一篇:memcpy和memmove的区别??
    » 下一篇:拷贝构造函数的参数为什么必须使用引用类型

    posted on 2011-09-18 16:41 原来... 阅读(13443) 评论(0编辑 收藏

    < 2011年9月 >
    28 29 30 31 1 2 3
    4 5 6 7 8 9 10
    11 12 13 14 15 16 17
    18 19 20 21 22 23 24
    25 26 27 28 29 30 1
    2 3 4 5 6 7 8

    导航

    统计

    • 随笔 - 210
    • 文章 - 0
    • 评论 - 36
    • 引用 - 0

    公告

    昵称:原来...
    园龄:4年11个月
    粉丝:121
    关注:7

    展开全文
  • c++ 面试题

    2015-09-08 22:22:20
    最近由于想换工作又看了看c++面试题,在这里总结一下: 1、构造析构 c++ 构造析构函数一看会考生成顺序 构造函数的顺序是,先运行基类构造函数,再初始化成员变量,再运行自身构造函数。 析构函数的顺序是,先...

    最近由于想换工作又看了看c++面试题,在这里总结一下:

    1、构造析构

    c++ 构造析构函数一看会考生成顺序

    构造函数的顺序是,先运行基类构造函数,再初始化成员变量,再运行自身构造函数。

    析构函数的顺序是,先运行自身析构函数,再释放成员变量,再运行基类析构函数。

    成员变量的初始化顺序由书写顺序由上而下。

    多继承由左至右,优先虚继承

    #include "stdafx.h"
    #include <iostream> 
    using namespace std; 
    
    class A
    {
    public:
    	A(){ cout<<"A"<<endl;}
    	virtual ~A(){ cout<<"~A"<<endl; }
    };
    
    class B: public A
    {
    public:
    	B(){ cout<<"B"<<endl;}
    	~B() {cout<<"~B"<<endl; }
    private:
    	A a;
    };
    
    class C: public A, public B     //类在派生表中的继承列表
    {
    public:
    	C() {cout<<"C"<<endl;}
    	~C() {cout<<"~C"<<endl; }
    private:
    	B b;
    public:
    	A a;
    };
    
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	C * p = new C;
    	delete p;
    
    	system("PAUSE");
    	return 0;
    }
    上面代码输出为

    A      
    A      
    A      
    B      
    A      
    A
    B
    A      
    C      
    ~C
    ~A
    ~B
    ~A
    ~A
    ~B
    ~A
    ~A
    ~A
    还有一种是讨论拷贝构造函数与运算符重载关系的

    #include "stdafx.h"
    #include <iostream> 
    using namespace std; 
    
    class A
    {
    public:
    	A(){ cout<<"1"<<endl;}
    	A(int n){cout<<"2"<<endl;}
    	A (const A &){cout<<"3"<<endl; }
    private:
    	int n;
    };
    
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	A a;
    	A b = a;
    	system("PAUSE");
    	return 0;
    }
    以上输出为

    1

    3是调用了拷贝构造函数

    int _tmain(int argc, _TCHAR* argv[])
    {
    	A a;
    	A b = 1;
    	system("PAUSE");
    	return 0;
    }

    输出为

    1

    2

    只有在变量声明后再赋值才会调用重载的=函数,否则调用拷贝构造函数



    展开全文
  • 华为c++面试题

    2018-04-09 16:20:00
    华为c++面试题,比较全,值得下载。华为c++面试题,比较全,值得下载华为c++面试题,比较全,值得下载
  • C++面试题合集

    2019-05-03 20:47:00
    C++面试题
  • c++面试题.zip

    2020-07-20 11:10:45
    c++面试题.zip
  • C++ 面试题

    2014-06-22 22:19:27
    面试题:.实现函数void f(int a, int b, int c),编码中不允许出现任何if,switch,for,while之类的关键词以及“?:”表达式,并要求:a=1时,打印b+c的值;a=2时,打印b-c的值;a=3时,打印b*c的值;a=4时,打印b/c...

    面试题:

    实现函数void f(int a, int b, int c),编码中不允许出现任何if,switch,for,while之类的关键词以及“?:”表达式,并要求:a=1时,打印b+c的值;a=2时,打印b-c的值;a=3时,打印b*c的值;a=4时,打印b/c的值;a=5时,打印b的阶乘加上c的阶乘;(不用考虑传入的a,b,c的值域错误所导致的异常结果或者崩溃)。

    解法一:

    class CBase
    {
    public:
    	virtual void show(int b, int c)=0;
    };
    class CDerived1:public CBase   //继承的时候注意些public
    {
    public:
    	virtual void show(int b, int c)
    	{
    		cout<< b+c<<endl;
    	}
    };
    class CDerived2:public CBase
    {
    public:
    	virtual void show(int b, int c)
    	{
    		cout<< b-c<<endl;
    	}
    };
    class CDerived3:public CBase
    {
    public:
    	virtual void show(int b, int c)
    	{
    		cout<<b*c<<endl;
    	}
    };
    class CDerived4:public CBase
    {
    public:
    	virtual void show(int b, int c)
    	{
    		cout<< b/c<<endl;
    	}
    };
    
    
    CBase *p[]={new CDerived1(), new CDerived2(), new CDerived3(), new CDerived4()};
    
    
    void f(int a, int b, int c)
    {
    	p[a-1]->show(b,c);
    }
    
    
    int main()
    {
    	f(1,2,3);
    	f(2,2,3);
    	f(3,2,3);
    	f(4,2,3);
    	return 0;
    }
    解法二:函数指针数组和子类数组有点类似,函数指针可以看做c语言实现c++多态的一种方式

    #include <iostream>
    
    using namespace std;
    
    void add(int b,int c)
    {
    	cout<<b+c<<endl;
    }
    
    void minus(int b,int c)
    {
    	cout<<b-c<<endl;
    }
    
    void (*p[2])(int b,int c);  //函数指针
    
    void f(int a, int b, int c)
    {
    	(*p[a-1])(b,c);
    }
    
    int main()
    {
    	p[0] = add;
    	p[1] = minus;
    
    	f(1,2,3);
    	f(2,2,3);
    
    	return 0;
    }
    



    解法三:采用异或的方式 ^ 和 并&& 的性质

    void f(int a, int b, int c)
    {
            int sum;
            (!(a ^ 1) && printf("%d\n", b + c));
            (!(a ^ 2) && printf("%d\n", b - c));
            (!(a ^ 3) && printf("%d\n", b*c));
            (!(a ^ 4) && printf("%d\n", b / c));
    }
    


    展开全文
  • 20道必须掌握的C++面试题问1:请用简单的语言告诉我C++ 是什么?问2:C和C++的区别?问3:什么是面向对象(OOP)?问4:什么是多态?问5:设计模式懂嘛,简单举个例子?问6:STL库用过吗?常见的STL容器有哪些?算法...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 12,910
精华内容 5,164
关键字:

c++面试题

c++ 订阅