博客 / 詳情

返回

C++面試題進階

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)數組名可以轉換為指向其指代實體的指針,而且是一個指針常量,不能作自增、自減等操作,不能被修改;

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 
user avatar unka_malloc 頭像 yih1ko 頭像 keen_626105e1ef632 頭像
3 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.