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

大端机则相反。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容

  • 史上最全的iOS面试题及答案 iOS面试小贴士———————————————回答好下面的足够了----------...
    Style_伟阅读 2,338评论 0 35
  • iOS面试小贴士 ———————————————回答好下面的足够了------------------------...
    不言不爱阅读 1,940评论 0 7
  • ———————————————回答好下面的足够了---------------------------------...
    恒爱DE问候阅读 1,705评论 0 4
  • 多线程、特别是NSOperation 和 GCD 的内部原理。运行时机制的原理和运用场景。SDWebImage的原...
    LZM轮回阅读 1,999评论 0 12
  • 1.写一个NSString类的实现 +(id)initWithCString:(c*****t char *)nu...
    韩七夏阅读 3,734评论 2 37