C++模块2:面向对象编程
1 .String类的实现:
class MyString
{
public:
MyString();
MyString(const MyString &);
MyString(const char *);
MyString(const size_t,const char);
~MyString();
size_t length();// 字符串长度
bool isEmpty();// 返回字符串是否为空
const char* c_str();// 返回c风格的trr的指针
friend ostream& operator<< (ostream&, const MyString&);
friend istream& operator>> (istream&, MyString&);
//add operation
friend MyString operator+(const MyString&,const MyString&);
// compare operations
friend bool operator==(const MyString&,const MyString&);
friend bool operator!=(const MyString&,const MyString&);
friend bool operator<=(const MyString&,const MyString&);
friend bool operator>=(const MyString&,const MyString&);
// 成员函数实现运算符重载,其实一般需要返回自身对象的,成员函数运算符重载会好一些
char& operator[](const size_t);
const char& operator[](const size_t)const;
MyString& operator=(const MyString&);
MyString& operator+=(const MyString&);
// 成员操作函数
MyString substr(size_t pos,const size_t n);
MyString& append(const MyString&);
MyString& insert(size_t,const MyString&);
MyString& erase(size_t,size_t);
int find(const char* str,size_t index=0);
private:
char *p_str;
size_t strLength;
};
2 .派生类中构造函数与析构函数,调用顺序:
构造函数的调用顺序总是如下:
A:基类构造函数。如果有多个基类,则构造函数的调用顺序是某类在类派生表中出现的顺序,而不是它们在成员初始化表中的顺序。
B:成员类对象构造函数。如果有多个成员类对象则构造函数的调用顺序是对象在类中被声明的顺序,而不是它们出现在成员初始化表中的顺序。如果有的成员不是类对象,而是基本类型,则初始化顺序按照声明的顺序来确定,而不是在初始化列表中的顺序。
C:派生类构造函数。
析构函数正好和构造函数相反。
3. 虚函数的实现原理:
虚函数表:
编译器会为每个有虚函数的类创建一个虚函数表,该虚函数表将被该类的所有对象共享。类的虚函数表是一块连续的内存,每个内存单元中记录一个JMP指令的地址。类的每个虚函数占据虚函数表中的一块,如果类中有N个虚函数,那么其虚函数表将有4N字节的大小。
编译器在有虚函数的类的实例中创建了一个指向这个表的指针,该指针通常存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能)。这意味着可以通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。
有虚函数或虚继承的类实例化后的对象大小至少为4字节(确切的说是一个指针的字节数;说至少是因为还要加上其他非静态数据成员,还要考虑对齐问题);没有虚函数和虚继承的类实例化后的对象大小至少为1字节(没有非静态数据成员的情况下也要有1个字节来记录它的地址)。
哪些函数适合声明为虚函数,哪些不能?
当存在类继承并且析构函数中有必须要进行的操作时(如需要释放某些资源,或执行特定的函数)析构函数需要是虚函数,否则若使用父类指针指向子类对象,在delete时只会调用父类的析构函数,而不能调用子类的析构函数,从而造成内存泄露或达不到预期结果;
内联函数不能为虚函数:内联函数需要在编译阶段展开,而虚函数是运行时动态绑定的,编译时无法展开;
构造函数不能为虚函数:构造函数在进行调用时还不存在父类和子类的概念,父类只会调用父类的构造函数,子类调用子类的,因此不存在动态绑定的概念;但是构造函数中可以调用虚函数,不过并没有动态效果,只会调用本类中的对应函数;
静态成员函数不能为虚函数:静态成员函数是以类为单位的函数,与具体对象无关,虚函数是与对象动态绑定的。
4. 虚继承的实现原理:
为了解决从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题,将共同基类设置为虚基类。这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。这样不仅就解决了二义性问题,也节省了内存,避免了数据不一致的问题。
构造函数和析构函数的顺序:虚基类总是先于非虚基类构造,与它们在集成体系中的次序和位置无关。如果有多个虚基类,则按它们在派生列表中出现的顺序从左到右依次构造。
#include <iostream>
using namespace std;
class zooAnimal
{
public: zooAnimal(){
cout<<"zooAnimal construct"<<endl;}
};
class bear : virtual public zooAnimal
{
public: bear(){
cout<<"bear construct"<<endl;}
};
class toyAnimal
{
public: toyAnimal(){
cout<<"toyAnimal construct"<<endl;}
};
class character
{
public: character(){
cout<<"character construct"<<endl;}
};
class bookCharacter : public character
{
public: bookCharacter(){
cout<<"bookCharacter construct"<<endl;}
};
class teddyBear : public bookCharacter, public bear, virtual public toyAnimal
{
public: teddyBear(){
cout<<"teddyBear construct"<<endl;}
};
int main()
{
teddyBear();
}
编译器按照直接基类的声明顺序依次检查,以确定其中是否含有虚基类。如果有,则先构造虚基类,然后按照声明顺序依次构造其他非虚基类。构造函数的顺序是:zooAnimal, toyAnimal, character, bookCharacter, bear, teddyBear。析构过程与构造过程正好相反。