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方法时传递了一个
相关文章推荐
- C++多线程面向对象解决方案
- 从C++到Java --理解面向对象是关键所在
- 面向过程与面向对象的C++
- 读书笔记-Thinking in C++-第14章 继承和组合
- 两种面向对象的C++线程模型
- c++ 面向对象的三个特性的几个规则
- 世界500强高管的求职宝典
- 两种面向对象的C++线程模型(转载CSDN)
- 世界500强高管的求职宝典
- 两种面向对象的C++线程模型
- C++非面向对象的特征
- C++各大名库宝典[收藏]
- C/C++程序员求职面试
- 编程宝典 保持C/C++程序代码可伸缩性
- C/C++ C++ / 面向对象 FAQ
- 面向对象的Windows编程实战(上)(使用C++和Win32 API)
- 求职宝典:时尚求职者面试成功的四大法宝
- IT专业学生求职简历宝典(Leo谈谈写简历)
- 面向对象C++基础(书中的“你懂得C,所以C++不在话下”)
- 求职宝典之面试