C++面试题进阶

作者: 拉普拉斯妖kk | 来源:发表于2016-09-27 10:39 被阅读797次

    1.问答题

    class ClassA
    {
    public:
        virtual ~ ClassA(){};
        virtual void FunctionA1(){};
        void FonctionA2(){};
    };
    class ClassB
    {
    public:
        virtual void FunctionB1(){};
        void FonctionB2(){};
    };
    class ClassC : public ClassA,public ClassB
    {
    public:
        void FunctionA1(){};
        void FonctionA2(){};
        void FunctionB1(){};
        void FonctionB2(){};
    };
    
    int main()
    {
        ClassC aObject;
        ClassA* pA=&aObject;
        ClassB* pB=&aObject;
        ClassC* pC=&aObject;
        cout<<pA<<endl;
        cout<<pB<<endl;
        cout<<pC<<endl;
    
        return 0;
    }
    

    这段代码中pA,pB,pC是否相等,为什么?
    答:

    pA和pC相等,pB和pC不相等,因为基类ClassA中定义了虚析构函数,运行时会将他直接指向派生类,而ClassB的则会进行一个隐式转换。

    2.问答题

    class Base {
        int m_tag;
    public:
        Base(int tag) : m_tag(tag) {}
    
        void print() {
            cout << "Base::print() called" << endl;
        }
    
        virtual void vPrint() {
            cout << "Base::vPrint() called" << endl;
        }
    
        virtual void printTag() {
            cout << "Base::m_tag of this instance is: " << m_tag << endl;
        }
    };
    
    class Derived : public Base {
    public:
        Derived(int tag) : Base(tag) {}
    
        void print() {
            cout << "Derived::print() called" << endl;
        }
    
        virtual void vPrint() {
            cout << "Derived::vPrint() called" << endl;
        }
    };
    
    class Derived1 : public Base {
    public:
        Derived1(int tag) : Base(tag) {}
    
        void print() {
            cout << "Derived1::print() called" << endl;
        }
    
        virtual void vPrint() {
            cout << "Derived1::vPrint() called" << endl;
        }
    };
    
    int main(int argc, char *argv[]) {
        Derived *foo = new Derived(1);
        Base *bar = foo;
    
        foo->print();
        foo->vPrint();
    
        bar->print();
        bar->vPrint();
    
        Base *ba = new Base(1);
        Derived *de = (Derived*)ba;
    
        ba->print();
        ba->vPrint();
    
        de->print();
        de->vPrint();
        
        return 0;
    }
    

    这段代码输出是怎样的?
    答:

    记住一点:普通函数在编译时就确定了,虚函数只有在运行时才确定调用哪个。

    3.找错题

    试题1:

    void test1()
    {
     char string[10];
     char* str1 = "0123456789";
     strcpy( string, str1 );
    }
    

    试题2:

    void test2()
    {
     char string[10], str1[10];
     int i;
     for(i=0; i<10; i++)
     {
      str1[i] = 'a';
     }
     strcpy( string, str1 );
    }
    

    试题3:

    void test3(char* str1)
    {
     char string[10];
     if( strlen( str1 ) <= 10 )
     {
      strcpy( string, str1 );
     }
    }
    

    答:

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

    • 试题2中str1循环赋值后没有‘\0’结束,所以在strcpy的时候会产生不确定的结果,这是因为在strcpy中是以‘\0’字符判断字符串是否结束的。

    • 试题3中if(strlen(str1) <= 10)应改为if(strlen(str1) < 10),因为strlen的结果未统计‘\0’所占用的1个字节。

    附录:
    如何编写一个标准strcpy函数(10分标准)。

    //将源字符串加const,表明其为输入参数,加2分
    char * strcpy( char *strDest, const char *strSrc )
    {
      //对源地址和目的地址加非0断言,加3分
     assert( (strDest != NULL) && (strSrc != NULL) );
     char *address = strDest;
     // 基本原理,2分
     while( (*strDest++ = * strSrc++) != ‘\0’ );
       //为了实现链式操作,将目的地址返回,加3分
      return address;
    }
    

    10分版的strlen函数。

    int strlen( const char *str ) //输入参数const
    {
     assert( strt != NULL ); //断言字符串地址非0
     int len;
     while( (*str++) != '\0' )
     {
      len++;
     }
     return len;
    }
    

    4.找错题

    试题4:

    void GetMemory( char *p )
    {
     p = (char *) malloc( 100 );
    }
    
    void Test( void )
    {
     char *str = NULL;
     GetMemory( str );
     strcpy( str, "hello world" );
     printf( str );
    }
    

    试题5:

    char *GetMemory( void )
    {
     char p[] = "hello world";
     return p;
    }
    
    void Test( void )
    {
     char *str = NULL;
     str = GetMemory();
     printf( str );
    }
    

    试题6:

    void GetMemory( char **p, int num )
    {
     *p = (char *) malloc( num );
    }
    
    void Test( void )
    {
     char *str = NULL;
     GetMemory( &str, 100 );//应该加上是否申请成功
     strcpy( str, "hello" );
     printf( str );
    }
    

    试题7:

    void Test( void )
    {
     char *str = (char *) malloc( 100 );
     strcpy( str, "hello" );
     free( str );
     ... //省略的其它语句
    }
    

    答:

    • 试题4传入中GetMemory( char *p )函数的形参为字符串指针,在函数内部修改形参并不能真正的改变传入形参的值,执行完GetMemory( str )函数后的str仍然为NULL;

    • 试题5的GetMemory函数中的p[]数组为函数内的局部自动变量,在函数返回后,内存已经被释放。

    • 试题6的GetMemory避免了试题4的问题,传入GetMemory的参数为字符串指针的指针,但是在GetMemory中执行申请内存及赋值语句*p = (char *) malloc( num )后未判断内存是否申请成功,应加上:
      if ( *p == NULL )
      {
       ...//进行申请内存失败处理
      }
      另外,Test函数中未对malloc的内存进行释放。

    • 试题7存在与试题6同样的问题,在执行char *str = (char *) malloc(100);后未进行内存是否申请成功的判断;另外,在free(str)后未置str为空,导致可能变成一个“野”指针,应加上:
      str = NULL;

    附录:
    看看下面的一段程序有什么错误:

    swap( int* p1,int* p2 )
    {
     int *p;
     *p = *p1;
     *p1 = *p2;
     *p2 = *p;
    }
    
    • 在swap函数中,p是一个“野”指针,有可能指向系统区,导致程序运行的崩溃。在VC++中DEBUG运行时提示错误“Access Violation”。该程序应该改为:
    swap( int* p1,int* p2 )
    {
     int p;
     p = *p1;
     *p1 = *p2;
     *p2 = p;
    }
    

    5.以下为Windows NT下的32位C++程序,请计算sizeof的值。

    void Func ( char str[100] )
    {
     sizeof( str ) = ?
    }
    
    void *p = malloc( 100 );
    sizeof ( p ) = ?
    

    答:
    sizeof( str ) = 4
    sizeof ( p ) = 4

    剖析:

    • Func ( char str[100] )函数中数组名作为函数形参时,在函数体内,数组名失去了本身的内涵,仅仅只是一个指针;在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。

    • 数组名的本质如下:

      • (1)数组名指代一种数据结构,这种数据结构就是数组;
        例如:

    char str[10];
    cout << sizeof(str) << endl;
    // 输出结果为10,str指代数据结构char[10]。
    ```

    - (2)数组名可以转换为指向其指代实体的指针,而且是一个指针常量,不能作自增、自减等操作,不能被修改;
    ```C++
    

    char str[10];
    str++;
    //编译出错,提示str不是左值 
    ```

    - (3)数组名作为函数形参时,沦为普通指针。
    Windows NT 32位平台下,指针的长度(占用内存的大小)为4字节,故sizeof( str ) 、sizeof ( p ) 都为4。
    

    6.编写一个函数,作用是把一个char组成的字符串循环右移n个。

    比如原来是“abcdefghi”如果n=2,移位后应该是“hiabcdefgh”。
    函数头是这样的:
    //pStr是指向以'\0'结尾的字符串的指针
    //steps是要求移动的n

    void LoopMove ( char * pStr, int steps )
    {
     //请填充...
    }

    答:

    // 正确解答1:
    void LoopMove ( char *pStr, int steps )
    {
     int n = strlen( pStr ) - steps;
     char tmp[MAX_LEN];
     strcpy ( tmp, pStr + n );
     strcpy ( tmp + steps, pStr);
     *( tmp + strlen ( pStr ) ) = '\0';
     strcpy( pStr, tmp );
    }
    
    
    // 正确解答2:
    void LoopMove ( char *pStr, int steps )
    {
     int n = strlen( pStr ) - steps;
     char tmp[MAX_LEN];
     memcpy( tmp, pStr + n, steps );
     memcpy(pStr + steps, pStr, n );
     memcpy(pStr, tmp, steps );
    }
    

    7.编写类String的构造函数、析构函数和赋值函数,已知类String的原型为:

    class String
    {
    public:
        String(const char *str = NULL); // 普通构造函数
        String(const String &other); // 拷贝构造函数
        ~String(); // 析构函数
        String & operator = (const String &other); // 赋值函数
    private:
        char *m_data; // 用于保存字符串
    };
    

    答:

    //普通构造函数
    String::String(const char *str)
    {
        if (str == NULL)
        {
            if (m_data == NULL)
                m_data = new char[1]; // 得分点:对空字符串自动申请存放结束标志'\0'的空
                //加分点:对m_data加NULL 判断   
            *m_data = '\0';
        }
        else
        {
            int length = strlen(str);
            if (m_data == NULL)
                m_data = new char[length + 1]; // 若能加 NULL 判断则更好
            strcpy(m_data, str);
        }
    }
    
    // String的析构函数
    String::~String()
    {
        delete[] m_data; // 或delete m_data;
        m_data = NULL;
    }
    
    //拷贝构造函数
    String::String(const String &other) // 得分点:输入参数为const型
    {
        int length = strlen(other.m_data);
        if (m_data == NULL)
            m_data = new char[length + 1]; //加分点:对m_data加NULL 判断
        strcpy(m_data, other.m_data);
    }
    
    //赋值函数
    String & String::operator = (const String &other) // 得分点:输入参数为const型
    {
        if (this == &other) //得分点:检查自赋值
            return *this;
        delete[] m_data; //得分点:释放原有的内存资源
        int length = strlen(other.m_data);
        if (m_data == NULL)
            m_data = new char[length + 1]; //加分点:对m_data加NULL 判断
        strcpy(m_data, other.m_data);
        return *this; //得分点:返回本对象的引用
    }
    

    在这个类中包括了指针类成员变量m_data,当类中包括指针类成员变量时,一定要重载其拷贝构造函数、赋值函数和析构函数,这既是对C++程序员的基本要求,也是《Effective C++》中特别强调的条款。

    8.请写一个C函数,若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1

    答:

    int checkCPU()
    {
     {
      union w
      {
       int a;
       char b;
      } c;
      c.a = 1;
      return (c.b == 1);
     }
    }
    

    剖析:嵌入式系统开发者应该对Little-endian和Big-endian模式非常了解。采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节, Big-endian 模式的CPU对操作数的存放方式是从高字节到低字节。在弄清楚这个之前要弄清楚这个问题:字节从左到右为从高到低! 假设从地址0x4000开始存放: 0x12345678,是个32位四个字节的数据,最高字节是0x12,最低字节是0x78:在Little-endian模式CPU内存中的存放方式为: (高字节在高地址, 低字节在低地址)

    内存地址0x4000 0x4001 0x4002 0x4003

    存放内容 0x78 0x56 0x34 0x12

    大端机则相反。

    相关文章

      网友评论

      本文标题:C++面试题进阶

      本文链接:https://www.haomeiwen.com/subject/cvycyttx.html