C++面向对象总结
2018-03-03 22:39
387 查看
1) C++中空类默认产生哪些类成员函数?
Ans:
编译器默认产生4个成员函数:
1. 默认构造函数
2. 析构函数
3. 复制构造函数
4. 赋值函数
2) C++中struct与class的区别?
Ans:
C++中的struct里面默认的访问控制是public, class中默认的访问控制是private。
3) 哪一种成员变量可以在同一个类的实例之间共享?
Ans:
必须使用静态成员变量在一个类的所有实例间共享数据。如果想限制对静态成员变量的访问,则必须把它们声明为protected或private。如果把静态成员变量设为private,可以通过公有静态成员函数访问。静态成员数据是在这个类的所有对象间共享的。
1. 静态成员函数中不能调用非静态成员。
2. 非静态成员函数中可以调用静态成员。
c++中静态成员变量一定要在类外部再定义或初始化
4) 初始化列表的初始化变量顺序是根据成员变量的声明顺序来执行的。
5) (非静态)常量和引用必须在构造函数的初始化列表里面初始化。
Ans:
有时我们可以忽略数据成员初始化和赋值之间的差异,但并非总能这样。
如果成员是const或者是引用的话,必须将其初始化。类似的,当成员属于某种类类型且该类没有定义默认构造函数时,也必须将这个成员初始化。
随着构造函数体一开始执行(即大括号里面部分),初始化就完成了(构造函数体内只是赋值操作)。因此,上面三种情况,比如初始化const或者引用类型的数据成员的唯一机会就是通过构造函数初始值,形如:ConstRef::ConstRef(int n) : i(n), j(n) { }
6) 虚拟的析构函数的必要性
Ans:
https://www.cnblogs.com/jin521/p/5602190.htmla
7) 析构函数可以为virtual型,构造函数则不能。那么为什么构造函数不能为虚呢?
Ans:
如果要创建一个对象,你势必要知道对象的准确类型,因此构造函数不能为虚。
8) 析构函数可以时内联函数
内联函数
9) 关于函数中对象的传送以及返回
Ans:
对于句子B temp=Play(5)
理论上该有两个复制构造函数,编译器把这两次合为一次 ,提高效率
1. 5->b
2. b->temp
有两个析构函数
1. b
2. temp
10) 编写类String的构造函数/析构函数和赋值函数
因为一个const变量是不能随意转化成非const变量。若
11) 重载(overload)和覆盖(override)的区别
Ans:
1. 虚函数总是在派生类中被改写,这种改写被称为”override”(覆盖)。override是指派生类重写基类的虚函数。重写的函数必须有一致的参数表和返回值(C++标准允许返回值不同的情况,但是很少有编译器支持这个特性)。
当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态地调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数是在运行期绑定的(晚绑定)。
2. overload(重载)是指编写一个与已有函数同名但是参数表不同的函数。重载不是一种面向对象的编程,而只是一种语法规则,重载与多态没有什么直接关系。重载的实现是编译器根据函数不同的参数列表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数。
例如,有两个同名函数function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是int_func,strc_func。对于这两个函数的调用,在编译器时间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定)。
12) 请描述模板类的友元重载,用C++代码实现。
更多
13) C++构造函数、析构函数与抛出异常
14) 虚函数覆盖问题
15) 公有继承,私有继承,保护继承的区别
16) 虚函数继承和虚继承
a. 什么是虚继承?它与一般的继承有什么不同?它有什么用?写出一段虚拟继承的c++代码
Ans:
虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。
类D继承自类B和类C,而类B和类C都继承自类A,因此出现如下图所示这种情况:
在类D中会两次出现A。为了节省内存空间,可以将B/C对A的继承定义为虚拟继承,而A就成了虚拟基类。
b. 空间占用
Ans:
更多
c. 为什么虚函数效率低?
Ans:
因为虚函数需要一次间接的寻址,而一般的函数可以在编译时定位到函数的地址,虚函数(动态类型调用)是要根据某个指针定位到函数的地址。多增加了一个过程,效率肯定会低一些,但带来了运行时的多态。
每个虚函数都在vtable中占了一个表项,保存着一条跳转到它的入口地址的指令(实际上就是保存了它的入口地址)。当一个包含虚函数的对象(注意,不是对象的指针)被创建的时候,它在头部附加一个指针,指向vtable中相应的位置。调用虚函数的时候,不管你是用什么指针调用的,它先根据vtable找到入口地址再执行,从而实现了“动态联编”。而不像普通函数那样简单地跳转到一个固定地址。
17) 多重继承
a. 请评价多重继承的优点和缺陷。
https://www.zhihu.com/question/39927143
b. 在多重继承的时候,如果一个类继承同时继承自class A和class B,而class A和class B中都有一个函数叫foo(),如何明确地再子类中指出override是哪个父类的foo()?
c. 下面程序的输出结果是多少?
Ans:
18) 如果鸟是可以飞的,那么鸵鸟是鸟么?鸵鸟如何继承鸟类?
Ans:
不能用鸵鸟继承鸟类,因为鸵鸟不能飞。
19) 什么时候需要基类初始化
20) 纯虚函数不能实例化
21) 重载操作符a++以及操作符++a
class Empty { public: };
Ans:
编译器默认产生4个成员函数:
1. 默认构造函数
2. 析构函数
3. 复制构造函数
4. 赋值函数
2) C++中struct与class的区别?
Ans:
C++中的struct里面默认的访问控制是public, class中默认的访问控制是private。
3) 哪一种成员变量可以在同一个类的实例之间共享?
Ans:
必须使用静态成员变量在一个类的所有实例间共享数据。如果想限制对静态成员变量的访问,则必须把它们声明为protected或private。如果把静态成员变量设为private,可以通过公有静态成员函数访问。静态成员数据是在这个类的所有对象间共享的。
1. 静态成员函数中不能调用非静态成员。
2. 非静态成员函数中可以调用静态成员。
class Test { private: static int ci; public: static int GetI() { return ci; } static void SetI(int i) { ci = i; } }; int Test::ci = 0;
c++中静态成员变量一定要在类外部再定义或初始化
4) 初始化列表的初始化变量顺序是根据成员变量的声明顺序来执行的。
#include <iostream> #include <string> class base { private: // 成员变量的声明 int m_i; int m_j; public: // 初始化并不按照m_j(i), m_i(m_j)初始化列表顺序,而是按照成员变量声明顺寻 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(10); // 输出的第一个为随机数,第二个是10 cout<<obj.get_i()<<endl<<obj.get_j()<<endl; return 0; {
5) (非静态)常量和引用必须在构造函数的初始化列表里面初始化。
Ans:
有时我们可以忽略数据成员初始化和赋值之间的差异,但并非总能这样。
如果成员是const或者是引用的话,必须将其初始化。类似的,当成员属于某种类类型且该类没有定义默认构造函数时,也必须将这个成员初始化。
随着构造函数体一开始执行(即大括号里面部分),初始化就完成了(构造函数体内只是赋值操作)。因此,上面三种情况,比如初始化const或者引用类型的数据成员的唯一机会就是通过构造函数初始值,形如:ConstRef::ConstRef(int n) : i(n), j(n) { }
6) 虚拟的析构函数的必要性
Ans:
https://www.cnblogs.com/jin521/p/5602190.htmla
7) 析构函数可以为virtual型,构造函数则不能。那么为什么构造函数不能为虚呢?
Ans:
如果要创建一个对象,你势必要知道对象的准确类型,因此构造函数不能为虚。
8) 析构函数可以时内联函数
#include <iostream> using namespace std; class A { public: void foo() {cout<<"A";}; ~A(); }; inline A::~A() {cout<<"inline";}
内联函数
9) 关于函数中对象的传送以及返回
#include <iostream> using namespace std; class B { private: int data; public: B() { cout<<"default constructor"<<endl;} ~B() { cout<<"destructed"<<endl;} B(int i):data(i) { cout<<"constructed by parameter"<<data<<endl;} }; B Play(B b) {return b} /* constructed by parameter5 destructed destructed */ int main() { B temp = Play(5); return 0; }
Ans:
对于句子B temp=Play(5)
理论上该有两个复制构造函数,编译器把这两次合为一次 ,提高效率
1. 5->b
2. b->temp
有两个析构函数
1. b
2. temp
10) 编写类String的构造函数/析构函数和赋值函数
#include<iostream> #include<string.h> using namespace std; class MyString { public: // 声明中说明了默认参数,则定义中不能写出来了,即定义中不能这样写 // MyString::MyString(const char *str=NULL) {} MyString(const char *str=NULL); MyString(const MyString &other); ~MyString(); MyString& operator = (const MyString &other); MyString& operator + (const MyString &other); void Print() { cout<<mdata<<endl;} private: char *mdata; }; MyString::~MyString() { delete []mdata; } MyString::MyString(const char *str) { if(str==NULL) { mdata = new char[1]; mdata[0] = '\0'; } else { // strlen不包括'\0' int length = strlen(str); mdata = new char[length+1]; strcpy(mdata, str); } } MyString::MyString(const MyString &other) { int length = strlen(other.mdata); mdata = new char[length+1]; strcpy(mdata, other.mdata); } MyString& MyString::operator =(const MyString &other) { //防止重复delete if (this == &other) return *this; delete []mdata; int length = strlen(other.mdata); mdata = new char[length+1]; strcpy(mdata, other.mdata); return *this; } MyString& MyString::operator +(const MyString &other) { MyString *dec = new MyString(); dec->mdata = new char[strlen(other.mdata)+strlen(mdata)+1]; strcpy(dec->mdata, mdata); // 连接第二个字符串 strcat(dec->mdata, other.mdata); return *dec; } int main() { MyString test1("abcd"); MyString test2("efgh"); MyString test3(test1); MyString test4 = test2; MyString test5 = test1+test2+test3+test4; test5.Print(); }
String& String::operator = (const String &other)中const的作用:
因为一个const变量是不能随意转化成非const变量。若
String& String::operator = (String &other);
Const MyString s4("hello"),则s = s4;这样是不行的
11) 重载(overload)和覆盖(override)的区别
Ans:
1. 虚函数总是在派生类中被改写,这种改写被称为”override”(覆盖)。override是指派生类重写基类的虚函数。重写的函数必须有一致的参数表和返回值(C++标准允许返回值不同的情况,但是很少有编译器支持这个特性)。
当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态地调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数是在运行期绑定的(晚绑定)。
2. overload(重载)是指编写一个与已有函数同名但是参数表不同的函数。重载不是一种面向对象的编程,而只是一种语法规则,重载与多态没有什么直接关系。重载的实现是编译器根据函数不同的参数列表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数。
例如,有两个同名函数function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是int_func,strc_func。对于这两个函数的调用,在编译器时间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定)。
12) 请描述模板类的友元重载,用C++代码实现。
#include<iostream> using namespace std; template <class T> class Test { private: T num; public: Test(T n){num=n;} //<>必不可少 friend ostream& operator<< <>(ostream &out,const Test<T> &); }; template <class T> ostream& operator<< <>(ostream &out,const Test<T> &obj){ out<<obj.num; return out; } int main() { Test<int> t(414); cout<<t; }
更多
13) C++构造函数、析构函数与抛出异常
14) 虚函数覆盖问题
// 例子1 #include <iostream> using namespace std; class A { protected: int m_data; public: A(int data = 0) { m_data = data;} int GetData() {return doGetData();} virtual int doGetData() {return m_data;} }; class B:public A { protected: int m_data; public: B(int data = 1) {m_data = data;} int doGetData() {return m_data;} }; class C:public B { protected: int m_data; public: C(int data=2) {m_data = data;} }; int main() { C c(10); cout<<c.GetData()<<endl; // 1 cout<<c.A::GetData()<<endl; // 1 cout<<c.B::GetData()<<endl; // 1 cout<<c.C::GetData()<<endl; // 1 cout<<c.doGetData()<<endl; // 1 cout<<c.A::doGetData()<<endl; // 0 cout<<c.B::doGetData()<<endl; // 1 cout<<c.C::doGetData()<<endl; // 1 return 0;
// 例子2 #include <iostream> using namespace std; class A { public: void virtual f() {cout<<"A"<<endl;} }; class B:public A { public: void virtual f() {cout<<"B"<<endl;} }; int main() { A* pa = new A(); pa->f(); // A B* pb = (B*)pa; // pa的指针始终没有发生变化 pb->f(); // A delete pa, pb; pa = new B(); pa->f(); // B pb = (B*)pa; pb->f(); // B }
15) 公有继承,私有继承,保护继承的区别
#include<iostream> using namespace std; ////////////////////////////////////////////////////////////////////////// class A //父类 { private: int privatedateA; protected: int protecteddateA; public: int publicdateA; }; ////////////////////////////////////////////////////////////////////////// class B :public A //基类A的派生类B(共有继承) { public: void funct() { int b; b=privatedateA; //error:基类中私有成员在派生类中是不可见的 b=protecteddateA; //ok:基类的保护成员在派生类中为保护成员 b=publicdateA; //ok:基类的公共成员在派生类中为公共成员 } }; ////////////////////////////////////////////////////////////////////////// class C :private A //基类A的派生类C(私有继承) { public: void funct() { int c; c=privatedateA; //error:基类中私有成员在派生类中是不可见的 c=protecteddateA; //ok:基类的保护成员在派生类中为私有成员 c=publicdateA; //ok:基类的公共成员在派生类中为私有成员 } }; ////////////////////////////////////////////////////////////////////////// class D :protected A //基类A的派生类D(保护继承) { public: void funct() { int d; d=privatedateA; //error:基类中私有成员在派生类中是不可见的 d=protecteddateA; //ok:基类的保护成员在派生类中为保护成员 d=publicdateA; //ok:基类的公共成员在派生类中为保护成员 } }; ////////////////////////////////////////////////////////////////////////// int main() { int a; B objB; a=objB.privatedateA; //error:基类中私有成员在派生类中是不可见的,对对象不可见 a=objB.protecteddateA; //error:基类的保护成员在派生类中为保护成员,对对象不可见 a=objB.publicdateA; //ok:基类的公共成员在派生类中为公共成员,对对象可见 C objC; a=objC.privatedateA; //error:基类中私有成员在派生类中是不可见的,对对象不可见 a=objC.protecteddateA; //error:基类的保护成员在派生类中为私有成员,对对象不可见 a=objC.publicdateA; //error:基类的公共成员在派生类中为私有成员,对对象不可见 D objD; a=objD.privatedateA; //error:基类中私有成员在派生类中是不可见的,对对象不可见 a=objD.protecteddateA; //error:基类的保护成员在派生类中为保护成员,对对象不可见 a=objD.publicdateA; //error:基类的公共成员在派生类中为保护成员,对对象不可见 return 0; }
16) 虚函数继承和虚继承
a. 什么是虚继承?它与一般的继承有什么不同?它有什么用?写出一段虚拟继承的c++代码
Ans:
虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。
A A / / B C
类D继承自类B和类C,而类B和类C都继承自类A,因此出现如下图所示这种情况:
A A \ / B C \ / D
在类D中会两次出现A。为了节省内存空间,可以将B/C对A的继承定义为虚拟继承,而A就成了虚拟基类。
A / \ B C \ / D
class A; class B : public virtual A; class C : public virutal A; class D : public B, public C;
b. 空间占用
Ans:
#include <iostream> #include <memory.h> #include <assert.h> using namespace std; class A { public: virtual void aa() {}; }; class B : public virtual A { private: char j[3]; public: // 关键字virtual告诉编译器它不应当完成早绑定,相反,它应当自动安装实现晚绑定所必需的所有机制。 virtual void bb() {}; }; class C : public virtual B { private: char i[3]; public: virtual void cc() {}; }; int main() { /* A 4 // aa的虚指针 */ cout<<sizeof(A)<<endl; // 4 /* B j[3] 4 // 编译器里一般以4的倍数为对齐单位 4 // bb的虚指针 B->A 4 // 虚继承指针 A 4 // A的大小 */ cout<<sizeof(B)<<endl; //16 /* B i[3] 4 // 编译器里一般以4的倍数为对齐单位 4 // cc的虚指针 C->B 4 // 虚继承指针 B 16 // B的大小 */ cout<<sizeof(C)<<endl; // 28 return 0; }
更多
c. 为什么虚函数效率低?
Ans:
因为虚函数需要一次间接的寻址,而一般的函数可以在编译时定位到函数的地址,虚函数(动态类型调用)是要根据某个指针定位到函数的地址。多增加了一个过程,效率肯定会低一些,但带来了运行时的多态。
每个虚函数都在vtable中占了一个表项,保存着一条跳转到它的入口地址的指令(实际上就是保存了它的入口地址)。当一个包含虚函数的对象(注意,不是对象的指针)被创建的时候,它在头部附加一个指针,指向vtable中相应的位置。调用虚函数的时候,不管你是用什么指针调用的,它先根据vtable找到入口地址再执行,从而实现了“动态联编”。而不像普通函数那样简单地跳转到一个固定地址。
17) 多重继承
a. 请评价多重继承的优点和缺陷。
https://www.zhihu.com/question/39927143
b. 在多重继承的时候,如果一个类继承同时继承自class A和class B,而class A和class B中都有一个函数叫foo(),如何明确地再子类中指出override是哪个父类的foo()?
#include <iostream> using namespace std; class A { public: void foo(){} }; class A2 { public: void foo(){} }; class D : public A, public A2 { }; int main() { D d; d.A::foo(); return 0; }
c. 下面程序的输出结果是多少?
#include <iostream> uisng namespace std; class A { int m_nA; }; class B { int m_nB; }; class C : public A, public B { int m_nC; }; int main() { C* pC = new C; B* pB = dynamic_cast<B*>(pC); if(pC==pB) { cout<<"equal"<<endl; } else { cout<<"not equal"<<endl; } if(int(pC)===int(pB)) { cout<<"equal"<<endl; } else { cout<<"not equal"<<endl; } return 0; }
Ans:
equal not equal
18) 如果鸟是可以飞的,那么鸵鸟是鸟么?鸵鸟如何继承鸟类?
Ans:
不能用鸵鸟继承鸟类,因为鸵鸟不能飞。
#include <string> #include <iostream> using namespace std; class bird { public: void eat() {cout<<"bird is eating"<<endl;} void sleep() {cout<<"bird is sleeping"<<endl;} void fly(){cout<<"bird is flying"}; } }; class ostrich { public: void eat() { smallBird.eat(); } void sleep() { smallBird.sleep(); } private: bird smallBird; };
19) 什么时候需要基类初始化
class base { protected: int i; public: base(int x){i=x;} }; class derived : public base { private: int i; public: derived(int x, int y) : base(x) { i = y; } void printTotal() { int total = i+base::i; } };
20) 纯虚函数不能实例化
// Vehicle是ADT class Vehicle { public: virtual void Move() = 0; virtual void Haul() = 0; }; // Car不是ADT class Car : public Vehicle { public: virtual void Move(); virtual void Haul(); }; // Bus不是ADT class Bus : public Vehicle { public: virtual void Move(); virtual void Haul(); };
21) 重载操作符a++以及操作符++a
#include <iostream> using namespace std; class A { private: int a; public: A() {a=0;} void operator++() {// 这是一元操作 a += 1; } void operator++(int) {// 这是二元操作 a += 2; } friend void print(const A &c); }; void print(const A &c) { cout<<c.a;} int main() { A classa; print(classa); ++classa; // 这是一元操作 print(classa); classa++; // 这是二元操作 print(classa); return 0; }
相关文章推荐
- C++专题 - 面向对象总结
- C++总结之继承和面向对象的设计
- C++ 面向对象不同于 Java 之处总结
- C++面向对象总结(二)--友元函数
- C++面向对象总结
- c++面向对象基础学习自我总结
- C++ STL开发温习与总结(四): 4.C++面向对象机制的实现
- c++面向对象语法总结
- C++总结—面向对象
- C++面向对象部分内容总结
- C++内容总结--面向对象语言特点
- C++面向对象总结
- C++面向对象总结
- PHP面向对象基础知识总结
- 黑马程序员-面向对象知识总结
- 黑马程序员--面向对象知识点总结(二)
- 黑马程序员-java面向对象知识点总结
- (Boolan) C++面向对象高级编程(五)
- C++ 面向对象基本释义
- linux设备驱动归纳总结(三):3.设备驱动面向对象思想和lseek的实现【转】