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

C++面向对象总结

2018-03-03 22:39 387 查看
1) C++中空类默认产生哪些类成员函数?

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++ 2018