C++面试笔记--面向对象
说到面向对象,大家第一反应应该就是它的三大特性:封装性、继承性和多态性。那么我们先简单的了解一下这三大特性:
(1)封装性:封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
在C++中类中成员的属性有:public, protected, private,这三个属性的访问权限依次降低。
(2)继承性:继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
(3)多态性:多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。实现多态,有二种方式,覆盖,重载。
覆盖,是指子类重新定义父类的虚函数的做法。
重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
-
1、C++中空类默认产生哪些类成员函数?
答案:
对于一个空类,编译器默认产生4个成员函数:
(1)默认构造函数
(2)析构函数
(3)拷贝构造函数
(4)赋值函数
-
2、结构是否可以有构造函数、析构函数及成员函数?如果可以,那么结构和类还有什么区别吗?
答案:
区别是class中变量默认是private,struct中的变量默认是public。class继承默认是private继承,而struct继承默认是public继承。struct可以有构造函数、析构函数,之间也可以继承甚至是多重继承,等等。C++中的struct其实和class意义一样,唯一不同就是struct里面默认的访问控制是public,class中默认访问控制是private。C++中存在struct关键字的唯一意义就是为了让C程序员有个归属感,是为了让C++编译器兼容以前用C开发的项目。
下面我们写一段struct中继承的例子。
1 #include<iostream> 2 using namespace std; 3 enum Bread{Golden,Cairn,Dandie,SheTland,Lab}; 4 5 struct mannal{ 6 public: 7 mannal():itsAge(2),itsWeight(5){} 8 ~mannal(){} 9 10 int getage()const {return itsAge;} 11 void setage(int age){itsAge=age;} 12 int getweight()const {return itsWeight;} 13 void setweight(int weight){itsWeight=weight;} 14 15 void speak()const{cout<<"mannal sound!"<<endl;} 16 void sleep()const {cout<<"sleeping!"<<endl;} 17 18 protected: 19 int itsAge; 20 int itsWeight; 21 }; 22 struct dog:public mannal{ 23 private: 24 Bread itebread; 25 26 public: 27 dog():itebread(Golden){} 28 ~dog(){} 29 Bread getbread()const {return itebread;} 30 void getbread(Bread bread){itebread=bread;} 31 void wagtail()const {cout<<"wagging!"<<endl;} 32 void begforfood()const{cout<<"food!"<<endl;} 33 34 }; 35 int main(){ 36 dog fido; 37 fido.speak(); 38 fido.wagtail(); 39 cout<<"fiod is "<<fido.getage()<<"years old!"<<endl; 40 return 0; 41 }
-
3、下面程序哪个语句是错的?
struct test{ test(int){} test(){} void fun(){} }; int main() { test a(1);//语句一 test b();//语句二 a.fun();//三 b.fun();//四 //答案选四,因为test b(),这个声明了一个函数,然而本意是想创建一个对象b,但是这个声明函数的错误在编译的时候是检测不出来的,所以就调用不了fun,所以四错误。 }
- 4、下面程序打印出的结果是什么?
#include<iostream> using namespace std; class base { private: int m_i; int m_j; public: base( int i ) : m_j(i),m_i(m_j) { base() : m_j(0),m_i(m_j){} int get_i() {return m_i;} int get_j() {return m_j;} }; int main () { base obj(98); cout << obj.get_i() <<endl<< obj.get_j() <<endl; return 0; }
解析:本题想得到的结果是“98,98”。但是成员变量的声明是先 m_i ,然后是 m_j;初始化列表的初始化变量顺序是根据成员变量的声明顺序来执行的,因此,先初始化 m_i,但此时 m_j 还未初始化,m_i 会被赋予一个随机值。改变一下成员变量的声明顺序可以得到预想的结果。
答案:
输出结果第一个为随机数,第二个是98。
-
5、析构函数可以为 virtual 型,构造函数则不能,为什么?
答案:
虚函数采用一种虚调用的办法。虚调用是一种可以在只有部分信息的情况下工作的机制,特别允许我们调用一个只知道接口而不知道其准确对象类型的函数。但是如果要创建一个对象,你势必要知道对象的准确类型,因此构造函数不能为 virtual。
6、如果虚函数是非常有效的,我们是否可以把每个函数都声明为虚函数?
答案:
不行,这是因为虚函数是有代价的:由于每个虚函数的对象都必须维护一个 v 表,因此在使用虚函数的时候会产生一个系统开销。如果仅是一个很小的类,且不行派生其他类,那么根本没必要使用虚函数。
-
6、哪一种成员变量可以在同一类的实例间共享?
答案:
必须使用静态成员变量在一个类的所有实例间共享数据。如果想限制对静态成员变量的访问,必须把它们声明为保护型或私有型。不允许用静态成员去存放某一个对象的数据。静态成员函数是在这个类的所有对象间共享的。如果静态成员数据设为私有的,可以通过共有静态和产能原函数访问。
-
7、编写类 String 的构造函数、析构函数和赋值函数。
#include<iostream> using namespace std; class string{ string(const char *str); string(const string &); ~string(); string &operator=(const string &) ; private: char *m_data; }; string::~string(){//析构函数 delete []m_data; } string::string(const char *str){//构造函数,利用复制操作进行。 if(NULL==str){ m_data=new char[1]; *m_data='\0'; } else{ int length=strlen(str); m_data=new char [length+1]; strcpy(m_data,str); } } string::string(const string & a){ int lenght=strlen(a.m_data); m_data=new char[length+1]; strcpy(m_data,a,m_data); } string & string::operator=(const string &a){ if(this==&other){//检查自赋值 return *this; } delete []m_data;//释放原有的内存资源。 int length=strlen(a.m_data);//分配新内存资源,并复制内容 m_data=new char[length+1]; strcpy(m_data,a.m_data); return *this; }
-
8、重载与覆盖有什么不同?
答案:
虚函数总是在派生类中被改写,这种改写被称为“override”(覆盖)。
override 是指派生类重写基类的虚函数,重写的函数必须有一致的参数表和返回值。Override这个单词好像一直没什么合适的中文词汇来对应。有些人译为“覆盖”,还贴切一些。
overload约定成俗地被翻译为“重载”,是指编写一个与自己已有函数同名但是参数表不同的函数。例如一个函数既可以接受整型数作为参数,也可以接收浮点数作为参数。重载不是一种面向对象的编程,而是一种语法规则,重载与多态没什么直接关系。
-
9、为什么析构函数可以是virtual,但是构造函数不行呢?
答案:
虚函数采用的是一种虚调用的方法。虚调用是一种可以在只有部分信息的情况下工作的机制,特别允许我们调用一个只知道借口而不知道准确对象类型的函数,但是如果要创建一个对象你势必要知道对象的准确类型,因此构造函数不能为虚。
- 10、友元函数去实现两个点之间的距离,写一个poiny类
答案:
1 #include<iostream> 2 using namespace std; 3 class point{ 4 private: 5 double x,y; 6 public: 7 point(double a=0.0,double b=0.0):x(a,b){} 8 friend double distance(point &left,point &right){} 9 }; 10 double distance(point &left,point &right){ 11 return ((left.x-right.x)^2+(left.y-right.y)^2)^0.5; 12 }
- 11、最好不要从析构函数中抛出异常!!!,构造函数中可以抛出异常,虚方法中也是可以抛出异常的。
答案: