您的位置:首页 > 编程语言 > C语言/C++

C++求职宝典:第14章 面向对象

2014-06-07 20:46 316 查看

第14章 面向对象

14.1 面向对象基本概念

    面试真题1:简述C和C++有何不同?

    考点解析:本题从宏观上考察面试人员对C和C++的理解。在回答本体时,只要描述出C语言和C++语言各自的特色和主要用途即可。

    参考答案:C语言属于面向过程语言,通过函数来实现程序功能。而C++是面向对象语言,主要是通过类来实现程序功能。使用C++编写的面向对象应用程序比C语言编写的程序更容易维护、扩展性更强。C++多用于开发上层的应用软件,而C语言代码体积小、执行效率高,多用于编写系统软件和嵌入式开发。例如,Windows操作系统、Linux操作系统大部分内核都是用C语言编写的。从名称上,可以说C++是C语言的超集,它兼容C语言,同事扩充了许多内容,例如面向对象,STL模板库等。总之,C语言属于面向过程编程语言,侧重于函数;而C++属于面向对象编程语言,侧重于类。

    面试真题2:请问C++的类和结构体有什么区别?

    考点解析:C++中的结构体与C语言中结构体有很大的区别。它不再只是简单地包含一组数据成员(或者称之为域)。在C++语言中,结构体内可以添加方法,可以将C++中的结构体与类等同。但是它们之间在成员默认的访问权限上还是存在区别的。

    参考答案:在类中定义的数据成员和方法,如果不适用访问权限限定符(public、protected、private等),默认是私有的,即private访问权限。而在结构体中,如果数据成员和方法不适用权限限定符,默认为公有的,即public访问权限。

    面试真题3:请简述面向对象的三个基本特征。

    考题解析:总所周知,封装、继承和多态是面向对象的三个基本特征。但是要准确说出它们各自的含义却不太容易,这需要面试人员对面向对象程序设计有深入的了解,同时参与过相关项目开发,才能够很好地总结出它们各自的含义。

    参考答案:

    ①封装:面向对象中的封装特性是指将客观事物抽象成类,也就是以类的形式来描述客观事物。例如,描述一个学生对象,它需要包含学号、专业、班级等成员,同时还要包含考试、结业等方法。

    ②继承:继承是面向对象中的核心技术。它允许一个类(子类)从另一个已有类(父类)中继承,这样在子类中即使不写任何代码也能够继承父类中保护和公有成员(父类中的私有成员不能够被子类继承),真正实现了不通过粘贴、复制的方式实现代码重用。面向对象中的多态性、动态绑定技术都是在继承的基础上实现的。

    ③多态:多态是面试对象技术的精华。它能够利用虚函数实现动态绑定功能。即一样的一条语句,由于运行时对象的类型不同,导致其行为也不同。它的主要实现方式是定义父类对象或借口时,将其复制为子类对象,也就是利用子类对象的构造函数来构造父类对象。这样在调用父类对象的某一个虚方法时,由于子类对象的不同,导致同样的语句执行行为也不同。

    面试真题4:类和结构的区别(Sony面试题)?

    以下对于C++中类(class)与结构体(struct)的描述正确的为( )。

    A. 类中的成员默认是private的,结构体中定义的成员默认的都是public

    B. 结构体中不允许定义成员函数,但是类中可以定义成员函数

    C. 结构体实现使用malloc()动态创建,类对象使用new操作符动态分配内存

    D. 结构体中不可以定义虚函数,类中可以定义虚函数

    考题解析:C++中的结构体与类基本相同,只是默认的访问级别不同而已,这与C语言中的结构体有很大不同。因此,上述描述中只有A是正确的。

    参考答案:A

    面试真题5:简述多态的作用?

    考题解析:多态是面向对象三个最主要特征之一。本题虽然是多态的作用,但实际是考察面试人员对面向对象的理解。从技术角度看,多态是面向对象的精华,不了解多态,就等于不理解面向对象。

    参考答案:多态主要有两个作用:一是隐藏实现细节,使代码能够模块化;二是实现代码重用,利用接口和抽象类可以设计更通用的功能。

    面试真题6:什么是局部类?

    考题解析:在C++中允许在函数体内定义一个类,这样的类被称之为局部类。例如,下面的代码就定义了一个局部类CBook:

<span style="font-size:18px;">void LocalClass()                     // 定义一个函数
{
class CBook                       // 定义一个局部类CBook
{
private:
int m_Pages;                  // 定义一个私有数据成员
public:
void SetPages(int page)       // 定义公有成员函数
{
if (m_Pages != page)
m_Pages = page;       // 为数据成员赋值
}
int GetPages()                // 定义公有成员函数
{
return m_Pages;           // 获取数据成员信息
}
};
CBook book;                       // 定义一个CBook对象
book.SetPages(300);               // 调用SetPages方法
printf("%d\n", book.GetPages());  // 输出信息
}</span>


    上述代码在LocalClass()函数中定义了一个类CBook,该类被称为局部类。对于局部类CBook,在函数之外是不能够被访问的,因为局部类被封装在了函数的局部作用于中。

    参考答案:如果将类的定义放在一个函数体内,则该类称之为局部类。对于局部类来说,它只能在函数内部使用,函数外是无法访问局部类的。

14.2 类的定义

    面试真题7:类定义的语法格式。

    下面代码关于类的定义有哪些错误?

<span style="font-size:18px;">struct CBook
{
p
4000
rivate:
CString m_szBookName = "Visual C++学习手册";
CString m_szAuthor = "宋坤";
public:
CBook()
{

}
}</span>
    考点解析:通常,在C++中使用class关键字定义一个类,但是也可以使用struct关键字定义类。因此,上述代码中使用struct定义类是合法的。本例在此只是设计了一个陷阱,如果面试人员在改错时将struct改为class就“中计”了。C++语言与Java不同,它不允许在定义数据成员时直接为其赋值,同时在类结尾处应该有分号。本题中关于类的定义就犯了这两个错误。

    参考答案:关于类的定义犯的错误有:

    ♢ 在定义数据成员m_szBookName和m_szAuthor时不能直接进行初始化,为数据成员提供初始值应该放在构造函数中。

    ♢ 类定义的末尾应添加分号。

    正确的定义如下:

<span style="font-size:18px;">struct CBook
{
private:
CString m_szBookName;
CString m_szAuthor;
public:
CBook()
{
m_szBookName = "Visual C++学习手册";
m_szAuthor = "宋坤";
}
};</span>


    面试真题8:C++中类成员访问权限有哪几种?简述其作用。

    考点分析:本题是一道基础的面试题,也是面试人员必须掌握的内容。在C++中为了使用类更好地封装对象,并且不被外界所破坏,为类成员提供了3中访问权限,分别为private、protected和public。在设计类时,应尽量使用private成员,这样可以对类起到保护作用。

    参考答案:在C++中,类成员有3中访问权限,分别为private、protected和public。其作用分别如下:

    ♢ private:类中的private成员只能够在本类中或者友元类中进行访问,子类或者外界是无法访问私有成员的。

    ♢ protected:类中的protected成员只允许本类或者子类中进行访问,外界无法访问protected成员。在定义类时,如果希望该成员能够被子类继承,但是不被外界访问,可以定义protected成员。

    ♢ public:类中的public

    面试真题9:类访问限定符?

    在C++中类成员的默认访问权限是( )。

    A. public成员          B. protected成员

    C. private成员        D. 以上都不是

    考点分析:在定义类时,通常明确使用权限限定符标识成员的访问权限。如果没有标识,默认为private访问权限。但是,如果在结构体中没有标识访问权限,默认为public访问权限。

    参考答案:C

    面试真题10:如何在类中定义常量成员并为其初始化?

    考点分析:在定义类时,不能够为数据成员直接赋值。例如,下面的代码是错误的:

<span style="font-size:18px;">class CBook
{
public:
double m_Price = 89.8;  // 错误的代码,不能直接为数据成员赋值
CBook()
{

}
};</span>
    而应该在构造函数中进行。例如:

<span style="font-size:18px;">class CBook
{
public:
double m_Price;
CBook()
{
m_Price = 89.8;  // 在构造函数中为成员变量赋值
}
};</span>
    但是本题是对常量成员进行初始化,如果写成如下的形式将出现错误,这也是本题的考点。

<span style="font-size:18px;">class CBook
{
public:
const double m_Price;      // 定义常量成员
CBook()
{
m_Price = 89.8;        // 错误的代码,不能为常量赋值
}
};</span>
    要为常量初始化,需要在类的构造函数的初始化部分进行,也就是在构造函数的函数体前使用“:”定义初始化区域,在该区域进行初始化。

    参考答案:

<span style="font-size:18px;">class CBook
{
public:
const double m_Price;      // 定义常量成员
CBook()
: m_Price(89.8)        // 为常量进行初始化
{

}
};</span>


    面试真题11:由析构函数引起的递归调用?

    下面关于CNode类的定义有何错误?

<span style="font-size:18px;">class CNode
{
public:
CNode* pNext;
CNode()                // 构造函数
{
pNext = this;
}
~CNode()               // 析构函数
{
if (pNext != NULL)
{
delete pNext;
pNext = NULL;
}
}
};</span>
    考点解析:析构函数有系统自动调用。有两种情况将导致析构函数被调用:一是对象的作用域消失,二是使用delete运算符释放对象。本题中就是使用delete运算符释放对象。但是本题中是在析构函数中使用delete运算符释放自身,这将导致再次调用析构函数,导致析构函数出现递归。

    参考答案:出现了递归调用。在析构函数中执行“delete pNext;”语句时导致递归,因为调用“delete pNext;”语句将致使~CNode()析构函数被调用。

    面试真题12:在C++中如何定义内联成员函数?

    考点解析:在C++中定义内联成员函数有两种方式:一种是在定义成员函数时使用inline关键字;另一种是在定义成员函数时直接写出函数体。

    参考答案:

    第一种方式:

<span style="font-size:18px;">class CBook
{
private:
double m_Price;
public:
inline void SetPrice( double Price );   // 使用inline关键字定义内联函数
CBook()
{

}
};
void CBook::SetPrice( double Price )
{
m_Price = Price;
}</span>
    第二种方式:

<span style="font-size:18px;">class CBook
{
private:
double m_Price;
public:
inline void SetPrice( double Price )   // 在定义成员函数时直接给出函数体,自动为内联函数
{
m_Price = Price;
}
CBook()
{

}
};</span>


    面试真题13:内存泄露

    下面的代码会出现内存泄露吗?为什么?

<span style="font-size:18px;">class CBook                         // 定义一个类
{
private:
double m_Price;                 // 定义数据成员
public:
void SetPrice( double Price )   // 定义方法
{
m_Price = Price;
}
CBook()
{

}
~CBook()
{

}
};
void main()
{
CBook* pBooks = new CBook[5];   // 定义一个对象数组
// ...
delete pBooks;
}</span>
    考点解析:在本题中由于在CBook类中并没有为数据成员在堆中分配空间,所以即使使用delete释放堆中的数组对象也不会出现内存泄露。但是这不是好的编码习惯,应该使用delete[]来释放数组对象。如果对上述代码进行修改,采用如下的形式,将导致内存泄露。

<span style="font-size:18px;">class CBook
{
private:
double* m_pPrice;               // 定义一个指针成员
public:
void SetPrice( double Price )
{
*m_pPrice = Price;
}
CBook()
{
m_pPrice = new double;       // 在堆中为成员分配空间
}
~CBook()
{
delete m_pPrice;            // 释放堆空间
}
};
void main()
{
CBook* pBooks = new CBook[5];
delete pBooks;                  // 将导致内存泄露
}</span>
    上述代码在释放数组pBooks时必须使用delete[]运算符。

    参考答案:不会出现内存泄露,但是不推荐使用delete释放数组对象,最佳方式是使用delete[]来释放数组对象。

    面试真题14:C++中类型为private的成员变量可以由哪些函数访问?

    考点分析:通常,C++中private成员只能由本类的成员函数访问。但是C++中有一种特殊的函数——友元函数,能够访问类的私有成员。下面的代码就演示了使用友元函数来访问类的私有数据。

<span style="font-size:18px;">class CItem;                                 // 前导声明CItem类
class CList                                  // 定义CList类
{
private:
CItem* m_pItem;                          // 定义私有数据成员m_pItem
public:
CList();                                 // 定义默认构造函数
~CList();                                // 定义析构函数
void OutputItem();                       // 定义OutputItem成员函数
};
class CItem                                  // 定义CItem类
{
friend void CList::OutputItem();         // 声明友元函数
private:
char m_Name[128];                        // 定义私有数据成员
void OutputName()                        // 定义私有成员函数
{
printf( "%s\n", m_Name );            // 输出数据成员信息
}
public:
void SetItemName( const char* pchData )  // 定义公有方法
{
if (pchData != NULL)                 // 判断指针是否为空
{
strcpy( m_Name, pchData );       // 赋值字符串
}
}
CItem()                                  // 构造函数
{
memset( m_Name, 0, 128 );            // 初始化数据成员m_Name
}
};
void CList::OutputItem()                     // CList类的OutputItem成员函数的实现
{
m_pItem->SetItemName( "BeiJing" );       // 调用CItem类的公有方法
m_pItem->OutputName();                   // 在友元函数中访问CItem类的私有方法
}
CList::CList()                               // CList类的默认构造函数
{
m_pItem = new CItem();                   // 构造m_pItem对象
}
CList::~CList()                              // CList类的析构函数
{
delete m_pItem;                          // 释放m_pItem对象
m_pItem = NULL;                          // 将m_pItem对象设置为空
}
int main(int argc, char* argv[])             // 主函数
{
CList list;                              // 定义CList对象list
list.OutputItem();                       // 调用CList的OutputItem方法
return 0;
}</span>
    上述代码中,在定义CItem类时,使用friend关键字将CList类的OutputItem方法设置为友元函数,在CList类的OutputItem方法中访问了CItem类的私有方法OutputName。执行上述代码,输出结果为:Beijing。

    对于友元函数来说,不仅可以是类的成员函数,还可以是一个全局函数。下面的代码在定义CItem类时,将一个全局函数定义为友元函数,这样在全局函数中就可以访问CItem类的私有成员了。

<span style="font-size:18px;">class CItem                                  // 定义CItem类
{
friend void OutputItem(CItem* pItem);    // 将全局函数OutputItem定义为友元函数
private:
char m_Name[128];                        // 定义数据成员
void OutputName()                        // 定义私有方法
{
printf( "%s\n", m_Name );            // 输出数据成员信息
}
public:
void SetItemName( const char* pchData )  // 定义公有方法
{
if (pchData != NULL)                 // 判断指针是否为空
{
strcpy( m_Name, pchData );       // 赋值字符串
}
}
CItem()                                  // 定义构造函数
{
memset( m_Name, 0, 128 );            // 初始化数据成员
}
};
void OutputItem(CItem* pItem)                // 定义全局函数
{
if (pItem != NULL)                       // 判断参数是否为空
{
m_pItem->SetItemName( "同一个世界,同一个梦想\n" );   // 调用CItem类的公有方法
m_pItem->OutputName();               // 调用CItem类的私有方法
}
}
int main(int argc, char* argv[])             // 主函数
{
CItem Item;                              // 定义一个CItem类对象Item
OutputItem( &Item );                     // 通过全局函数访问CItem类的私有方法
return 0;
}</span>
    执行上述代码,输出结果为:同一个世界,同一个梦想!

    参考答案:private成员只能由本类中的成员函数或者友元函数来访问。

    面试真题15:构造函数与普通函数相比在形式上有什么不同?

    考点解析:每个类都具有构造函数。它在定义对象时被调用,用于为数据成员进行初始化。如果用户没有提供构造函数,系统将提供默认的构造函数和析构函数。构造函数是一个与类同名的方法,可以没有参数,有一个参数或多个参数,但是构造函数没有返回值。如果构造函数没有参数,该函数被称为类的默认构造函数。下面的代码显式地定义了一个默认构造函数。

<span style="font-size:18px;">class CUser                         // 定义CUser类
{
private:
char m_Username[128];           // 定义数据成员m_Username
char m_Password[128];           // 定义数据成员m_Password
public:
CUser()                         // 定义默认的构造函数
{
strcpy(m_Username, "MR");   // 为数据成员赋值
strcpy(m_Password, "KJ");   // 为数据成员赋值
}
char* GetUsername() const       // 定义成员函数GetUsername
{
return (char*)m_Username;
}
char* GetPassword() const
be9f
// 定义成员函数GetPassword
{
return (char*)m_Password;
}
};</span>
    如果用户为类定义了构造函数,无论是默认构造函数还是非默认构造函数,系统均不会提供默认的构造函数。下面定义一个CUser类的对象,它将调用用户定义的默认构造函数。

<span style="font-size:18px;">int main(int argc, char* argv[])
{
CUser user;                        // 定义CUser类对象
printf("%s\n", user.GetPassword);  // 调用GetPassword方法输出m_Password
return 0;
}</span>
    执行上述代码,输出结果为:KJ。

    参考答案:构造函数是类的一种特殊成员函数,它通常用于为数值成员进行初始化。在C++中,构造函数必须与类的名字相同,并且不能有返回值,即使是void类型也不可以。

    面试真题16:如何定义两个类互为成员的情况?

    考点解析:实现两个类互为成员开起来是一个矛盾。例如,下面的代码模拟两个类互为成员的情况:

<span style="font-size:18px;">class A
{
private:
B m_B;
};
class B
{
private:
A m_A;
};</span>
    不用进行编译,就可以预知存在的错误。因为在类A之前,没有发现类B的定义,直接使用了类B。为了解决这个矛盾,需要在类A上方前导声明类B。即:
<span style="font-size:18px;">class B;</span>
    但是这还不能完全解决问题。因为前导声明只是声明一个类,而没有类的定义,编译器在编译类A时需要实例化类B,但是却没有发现类B的定义,而只是发现了类B的声明。解决方式是在类A中,将m_B对象修改为指针类型。

    参考答案:

<span style="font-size:18px;">class B;      // 前导声明B
class A
{
private:
B* m_B;   // 定义类B的指针类型
};
class B
{
private:
A m_A;
};</span>


    面试真题17:下面关于局部类的定义有何错误?

<span style="font-size:18px;">void LocalClass                        // 定义一个函数
{
class CCircle                      // 定义一个局部类
{
public:
int m_Area;
static double m_PI;
CCircle()
{

}
};
double CCircle::m_PI = 3.1415926;  // 对静态成员进行初始化
}</span>


    考点解析:本题实际考查的是对局部类的了解。局部类有一项限制,就是不允许在类中定义静态成员。

    参考答案:在局部类中不允许定义静态成员。上述代码需要修改为:

<span style="font-size:18px;">void LocalClass
{
class CCircle
{
public:
int m_Area;
double m_PI;
CCircle()
{
m_PI = 3.1415926;
}
};
}</span>


    面试真题18:已知String类定义,请添加实现部分。

<span style="font-size:18px;">class String
{
public:
String(const char* str = NULL);        // 通用构造函数
String(cosnt String &another);         // 复制构造函数
~String();                             // 析构函数
String &operator =(const String &rhs); // 赋值函数
private:
char* m_data;                          // 用于保存字符串
};</span>
    考点解析:本题考查面试人员对构造函数、析构函数、复制构造函数和赋值函数的理解。在定义一个类时如果没有指定这些函数,编译器会为其提供这4个特殊的函数。除了构造函数之外,其余3个函数都具有特定的函数原型。例如,析构函数的名称为“~”加上类名,没有参数和返回值;复制构造函数与类名相同,参数为一个常量引用类型参数;赋值函数参数为常量引用类型,返回值为类引用类型。这些函数都具有特殊的用途,如果了解了这些用途,本题也就迎刃而解了。

    构造函数用于创建类对象;析构函数用于释放对象;复制构造函数用于在类对象作为函数参数或函数返回值时被调用,用于临时构建对象;赋值函数用于实现对象间的直接赋值。

    参考答案:

<span style="font-size:18px;">String::String(const char* str)                    // 实现构造函数
{
if (str == NULL)                               // 判断参数是否为空
{
m_data = new char[1];
m_data[0] = '\0';
}
else
{
m_data = new char[strlen(str) + 1];
strcpy(m_data, str);
}
}
String::String(const String &another)              // 实现复制构造函数
{
m_data = new char[strlen(another.m_data) + 1];
strcpy(m_data, another.m_data);
}
String& String::operator =(const String& rhs)      // 实现赋值函数
{
if (this == &rhs)
return *this;
delete []m_data;                               // 删除原来的数据,新开一块内存
m_data = new char[strlen(rhs.m_data) + 1];
strcpy(m_data, rhs.m_data);
return *this;
}
String::~String()
{
delete []m_data;
}</span>


    面试真题19:友元函数

   下面关于类的友元函数叙述正确的是( )。

    A. 允许在类外访问类中除私有成员以外的任何成员

    B. 允许在类外访问类中的任何成员

    C. 友元函数也是该类的成员函数

    D. 友元函数的定义必须放在该类的公有部分

    考点解析:友元函数是C++语言的一个特性,它允许访问其他类的私有数据成员,当然公有成员和受保护成员也可以访问,因此选项B是正确的。选项A描述是错误的,友元函数允许访问私有成员。选项C也是错误的,友元函数不属于当前类的成员函数,在当前类中只是声明了另一个类的方法为该类的友元函数。选项D的描述是错误的,友元函数可以放在类的私有部分和受保护部分。

    参考答案:B

    面试真题20:完成括号部分的代码访问被局部变量隐藏的类成员。

<span style="font-size:18px;">class CStudent
{
private:
int Age;
char Name[128];
public:
void SetAge(int Age);
};
void CStudent::SetAge(int Age)
{
(     ) = Age;
}</span>

    考点解析:本题中在方法SetAge中需要将参数赋值给Age成员。但是该成员与SetAge方法的参数同名。如果写为“Age=Age;”将不会为Age成员赋值,而是将参数Age赋值为自身。那么,如何在SetAge方法中访问Student类的Age成员呢?

    在类的方法(静态方法除外)中,会隐含一个参数this,它的类型是当前类的指针类型,作用是用于区分同一个类的不同对象。例如,学生1的Age为21,学生2的 Age为22,当学生1调用SetAge方法时,编译器如何确定是为学生1的Age赋值呢?答案是通过this指针。本题中我们可以通过this指针来访问成员Age。

    参考答案:this->Age

    面试真题21:在定义类的成员函数时使用mutable关键字的作用是什么?

    考点解析:在定义类的方法时,如果在方法的末尾使用const关键字,表示该方法为const方法,此时在方法中不允许修改对象的信息。例如,下面的代码将出现编译错误:

    上述代码在const方法中修改Weight成员导致了语法错误。为了能够在const方法中修改对象的成员信息,可以在成员钱使用mutable关键字。例如,下面的代码是完全合法的:

    参考答案:当需要在const方法中修改对象的数据成员时,可以在数据成员前使用mutable关键字,防止出现编译错误。

    面试真题22:方法的参数传递。

    下面的代码在运行时会出现什么错误?如何进行修改?

    考点分析:本题中调用AllocateMem方法时传递了一个
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息