1.计算下面程序base的大小
class Object
{
int value;
int sum;
};
class Base : public Object //公有继承
{
int num;
};
int main()
{
Base base;
sizeof(Base);
sizeof(base);
return 0;
}
答案:12个字节
base有两个成员,一个是num,是4个字节,一个是隐藏父对象,是8个字节,隐藏父对象有两个成员,一个是value,是4个字节,一个是sum,是4个字节
注意sizeof(Base) 和 sizeof(base) 两个的意思是一样的,结果也是一样的,都是12个字节
Object 和 Base 之间的继承关系在编译的时候就确定了
2.多态性与虚函数
多态性是面向对象程序设计的关键技术之一,若程序设计语言不支持多态性,不能称为面向对象的语言,利用多态性技术,可以调用同一个函数名的函数,实现完全不同的功能
在C++中有两种多态性:
①编译时的多态(早期绑定也叫做静态绑定或者静态联编)
通过函数的重载和运算符的重载来实现的
int Max(int a,int b) { return a>b?a:b; }
char Max(char a,char b) { return a>b?a:b; }
double Max(double a,double b) { return a>b?a:b; }
int main()
{
int x = Max(12,23);
char ch = Max('c','b');
double dx = Max(12.23,34.45);
return 0;
}
早期绑定在编译的时候就确定了关系,int x = Max(12,23); 12和23是整型就和整型的Max函数绑定,char ch = Max(‘c’,‘b’); 'c’和’b’是字符型,就和字符型的Max绑定,double dx = Max(12.23,34.45); 12.23和34.45是double型,就和double型的Max绑定
早期绑定是靠名字粉碎技术来实现的
根据故事来理解:可以理解为两对夫妻怀孕了,约定如果是两个男孩,就结为兄弟,如果是两个女孩,就结为姐妹,如果是一男一女,就结为夫妻,这种在还没有出生前就确定关系的就叫做早期绑定,如果一开始没有绑定娃娃亲,两个孩子在社会上长大了,结为兄弟、姐妹或者夫妻,就称为晚绑定
②运行时的多态(晚期绑定也叫做动态绑定或者动态联编)
运行时的多态性是指在程序执行前,无法根据函数名和参数来确定该调用哪一个函数,必须在程序执行过程中,根据执行的具体情况来动态的确定,它是通过类的 public 继承关系和虚函数来实现的,目的也是建立一种通用的程序。通用性是程序追求的主要目标之一
虚函数主要进行的是晚绑定
3.虚函数的定义
虚函数是一个类的成员函数,定义格式为:
virtual 返回类型 函数名 (参数列表) ;
关键字 virtual 指明该成员函数为虚函数,virtual 仅用于类定义中,如虚函数在类外定义,不可加 virtual
class Object
{
public:
virtual void fun();//虚函数的声明,注意:这里的 virtural 必须加
};
void Object::fun()//注意:这里不能加virtual
{}//虚函数的定义
当某一个类的一个类成员函数被定义为虚函数,则由该类派生出来的所有派生类中,该函数始终保持虚函数的特征
class Animal
{
private:
string name;
public:
Animal(const string& na) : name(na) {}
public:
virtual void eat() {}
virtual void walk() {}
virtual void tail() {}
virtual void PrintInfo() {}
string& get_name() { return name; }
const string& get_name() const { return name; }
};
class Dog : public Animal // public 意味着"是一个"的意思,狗是一个动物
{
private:
string owner;//主人
public:
Dog(const string& ow,const string& na) : Animal(na),owner(ow) {}
virtual void eat() { cout << "Dog Eat : bone " << endl; }
virtual void walk() { cout << "Dog Walk : run" << endl; }
virtual void tail() { cout << "Dog Tail : wang wang..." << endl; }//狗的叫声
virtual void PrintInfo()
{
cout << "Dog Owner : " << owner << endl;
cout << "Dog name : " << get_name() << endl;
}
};
class Cat : public Animal // public 意味着"是一个"的意思,狗是一个动物
{
private:
string owner;//主人
public:
Cat(const string& ow,const string& na) : Animal(na),owner(ow) {}
virtual void eat() { cout << "Cat Eat : fish " << endl; }
virtual void walk() { cout << "Cat Walk : silent" << endl; }
virtual void tail() { cout << "Cat Tail : Miao Miao..." << endl; }//狗的叫声
virtual void PrintInfo()
{
cout << "Cat Owner : " << owner << endl;
cout << "Cat name : " << get_name() << endl;
}
};
void fun(Animal& animal)
{
animal.eat();
animal.walk();
animal.tail();
animal.PrintInfo();
}
int main()
{
Dog dog("yhping","hashiqi");
Cat cat("hm","bosimao");
fun(dog);
fun(cat);
return 0;
}
class Dog : public Animal // 公有继承 public 意味着"是一个"的意思,狗"是一个"动物
因为每个动物的吃,走,和叫声是不一样的,所以设为虚函数,而获取狗的名字的方式都是一样的,所以不设为虚函数
当我把dog作为实参时,打印的是dog的内容,当我把cat作为实参时,打印的是cat的内容,这就是运行时的多态
我们要想达到运行时的多态,必须使用引用或者指针调动虚函数,才能达到运行时的多态
实现运行时的多态的三个步骤:
①必须是公有继承
②必须把函数定义为虚函数
③必须使用引用或者指针调动虚函数
条件①为什么必须是公有继承呢?因为只有公有继承才意味者“是一个”的意思
条件②
virtual void eat() { cout << "Cat Eat : fish " << endl; }
virtual void walk() { cout << “Cat Walk : silent” << endl; }
virtual void tail() { cout << “Cat Tail : Miao Miao…” << endl; }//狗的叫声
条件③ 引用:(void fun(Animal& animal)) 或者 指针:(void fun(Animal * animal))
如果fun函数既没有使用引用也没有使用指针(void fun(Animal animal)),那么它就变成了早期绑定(也叫静态联编),它既不打印 cat 的内容,也不打印 dog 的内容,而是打印 Animal 的内容
4.构造函数不能写成虚函数,但是析构函数可以写成虚函数
5.成员函数应尽可能的设置为虚函数,但必须注意一下几条
①派生类中定义虚函数必须与基类中的虚函数同函数名外,还必须是同参数表,同返回类型,否则被认为是函数的隐藏,而不是虚函数。如基类中返回基类指针, 派生类中返回派生类指针是允许的,这是一个例外
②只有类的成员函数才能说明为虚函数。这是因为虚函数仅适用于有继承关系的类对象
③静态成员函数,是所有同一类对象共有,不受限于某个对象,不能作为虚函数,静态成员函数、友元函数、内联函数、构造函数、拷贝构造函数、移动构造函数,只要是构造函数都不能作为虚函数,析构函数可以作为虚函数
④实现动态多态性时,必须使用基类类型的指针变量或引用,使该指针指向该基类的不同派生类的对象,并通过该指针指向虚函数,才能实现动态的多态性
⑤内联函数每个对象一个拷贝,无映射关系,不能作为虚函数
⑥析构函数可定义为虚函数,所有的构造函数(包括构造函数,拷贝构造函数,移动构造函数)都不能定义为虚函数,因为在调用构造函数时对象还没有完成实例化,在基类中及其派生类中都动态分配的内存空间时,必须把析构函数定义为虚函数,实现撤销对象时的多态性
⑦函数执行速度要稍慢一些,为了实现多态性,每一个派生类
中均要保存相应虚函数的入口地址表,函数的调用机制也是间接实现,所以多态性总是要付出一定代价,但通用性是一个更高的目标
⑧如果定义放在类外,virtual 只能加在函数声明前面,不能加在函数定义前面,正确的定义必须不包括 virtual
6.虚函数是覆盖,同名函数是隐藏
7.为什么通过指针可以达到运行时的多态,为什么不能通过对象达到运行时的多态?
8.虚函数表
class Object
{
private:
int value;
public:
Object(int x = 0) : value(x) {}
virtual void add() { cout << "Object::add" << endl; }
virtual void fun() { cout << "Object::fun" << endl; }
virtual void print() const { cout << "Object::print" << endl; }
};
class Base : public Object
{
private: int sum;
public:
Base(int x = 0) : Object(x+10),sum(x) {}
virtual void add() { cout << "Base:add" << endl; }
virtual void fun() { cout << "Base::fun" << endl; }
virtual void print() const { cout << "Base::print" << endl; }
};
在编译的时候到Object的时候,会生成一个虚函数表(Object::vftable),里面存放着add、fun、print函数的指针,当编译到Base时,编译器发现是共有继承,并且函数名相同,参数列表相同,就会用Base中add、fun、print函数的指针覆盖掉Object的add、fun、print函数指针
有虚函数就会生成虚函数表
9.sizeof(base)的大小是多少?
class Object
{
private:
int value;
public:
Object(int x = 0) : value(x) {}
virtual void add() { cout << "Object::add" << endl; }
virtual void fun() { cout << "Object::fun" << endl; }
virtual void print() const { cout << "Object::print" << endl; }
};
class Base : public Object
{
private: int sum;
public:
Base(int x = 0) : Object(x+10),sum(x) {}
virtual void add() { cout << "Base:add" << endl; }
virtual void fun() { cout << "Base::fun" << endl; }
virtual void print() const { cout << "Base::print" << endl; }
};
int main()
{
Base base(10);
Object *op = &base;
sizeof(base);
return 0;
}
答案:12个字节
当执行到Base base(10);时要调用Base的构造函数,因为Base是共有继承Object的,所以会又先调用Object的构造函数,由于Object中有虚函数,所以会创建一个虚函数表,虚函数指针指向Object的虚函数表,是4个字节,value的值是20,int类型占4个字节,回到Base的构造函数,由于Base中也有虚函数,所以也会创建虚函数表,这时虚函数指针就不指向Object了(在整个继承链上,虚表指针只有一份),改为指向Base的虚函数表,然后再初始化sum的值,值是10,int类型,占4个字节,所以一共是12个字节
在Base的构造函数中加上memset(this,0,sizeof(Base));会导致什么样的后果?
答案:sizeof的大小是12个字节,包括虚函数表指针,如果置为0,就把虚表的指向弄丢了,所以在构造函数中使用memset函数是十分危险的
为什么不能把构造函数(包括构造、拷贝构造、移动构造)定义成虚函数?
答案:构造函数是建立虚表的前提。如果把构造函数定义成虚函数,调动虚函数需要查表,而构造函数是我们设置虚表指针的一个方案,通俗来说就是调动构造函数查表时,表还没有建立起来呢,没有表可查,所以不能把构造函数定义成虚函数
10.父指针调用函数和对象调用函数有什么区别?
class Object
{
private:
int value;
public:
Object(int x = 0) : value(x) {}
virtual void add() { cout << "Object::add" << endl; }
virtual void fun() { cout << "Object::fun" << endl; }
virtual void print() const { cout << "Object::print" << endl; }
};
class Base : public Object
{
private: int sum;
public:
Base(int x = 0) : Object(x+10),sum(x) {}
virtual void add() { cout << "Base:add" << endl; }
virtual void fun() { cout << "Base::fun" << endl; }
virtual void print() const { cout << "Base::print" << endl; }
};
int main()
{
Base base(10);
Object *op = NULL;
Object obj(0);
op = &base;
op->add();
op->fun();
op->print();
obj = base;
obj.add();
obj.fun();
obj.print();
return 0;
}
答案:编译的方式是不一样的
如果是指针或者引用调用虚方法时,采取动态联编,如果是对象调用虚方法,采取静态联编
如果是用指针或者引用来调动方法时,采取的是动态联编,动态联编是指编译时并不知道要调动哪个方法,在运行的时候才能确定调动关系,当执行到op->add();时会根据虚表指针指向的谁虚表就调动谁的方法
如果是用"对象名.方法"的方式调用方法,这个方法无论是虚方法还是非虚方法,都采取的是静态联编,静态联编的意思就是说必须在编译时就确定对象和哪个方法结合,obj是一个Object类型,所以在编译的时候就确定要和Object的方法结合,所以obj.add(); obj.fun(); obj.print();调用的是Object的方法
注意:每一个都对象都有虚表指针,但是虚表只有一个
11.下面程序的虚表是什么样的?
class Object
{
private:
int value;
public:
Object(int x = 0) : value(x) {}
virtual void add() { cout << "Object::add" << endl; }
virtual void fun() { cout << "Object::fun" << endl; }
virtual void print() const { cout << "Object::print" << endl; }
};
class Base : public Object
{
private: int sum;
public:
Base(int x = 0) : Object(x+10),sum(x) {}
virtual void add() { cout << "Base:add" << endl; }
virtual void fun() { cout << "Base::fun" << endl; }
virtual void show() { cout << "Base::print" << endl; }
};
class Test : public Base
{
private:
int num;
public:
Test(int x = 0) :Base(x+10),num(x)
{
}
virtual void add() { cout << "Test::add" << endl; }
virtual void print() const { cout << "Test::print" << endl; }
virtual void show() {cout << "Test::show" << endl; }
};
int main()
{
Test test(10);
return 0;
}
编译的时候先构建Object的虚表,当构建Base虚表的时候,会把Object的虚表直接拷贝下来,看看虚表上的函数有没有重写,如果有,就用新写的虚函数覆盖掉原来写的虚函数,如果这个方法没有重写就保留下来最后在虚表上加上新写的方法,这就是同名覆盖,把虚表的指针覆盖掉了
注意:虚表每个类型都有一个(即如果定义Test t1; Test t2; 他们共用一个虚表),虚表指针只有一个,整个继承链上只有头(根)有虚表指针,其他都没有虚表指针,如Object除了有value还有虚表指针,Base有sum,Test有num,所以sizeof(Test)的大小为16个字节
12.对于Object类型的指针来说,它只能访问到add、fun、print方法,对于Base指针来说,可以访问到add、fun、print、show方法,这是为什么呢?
class Object
{
private:
int value;
public:
Object(int x = 0) : value(x) {}
virtual void add() { cout << "Object::add" << endl; }
virtual void fun() { cout << "Object::fun" << endl; }
virtual void print() const { cout << "Object::print" << endl; }
};
class Base : public Object
{
private: int sum;
public:
Base(int x = 0) : Object(x+10),sum(x) {}
virtual void add() { cout << "Base:add" << endl; }
virtual void fun() { cout << "Base::fun" << endl; }
virtual void show() { cout << "Base::print" << endl; }
};
class Test : public Base
{
private:
int num;
public:
Test(int x = 0) :Base(x+10),num(x) {}
virtual void add() { cout << "Test::add" << endl; }
virtual void print() const { cout << "Test::print" << endl; }
virtual void show() {cout << "Test::show" << endl; }
};
int main()
{
Test test(10);
Object* op = &t1;
op->add();
op->fun();
op->print();
Base* bp = &t1;
bp->add();
bp->fun();
bp->print();
bp->show();
return 0;
}
答案:对于Object来说只定义了三个虚函数,所以只能访问到add、fun、print,但是对于Base来说新添加了一个show方法,重写了两个方法(add、fun),继承了一个方法(print),所以add、fun、print、show都能访问到
13.判断下面程序输出的结果是什么
class Object
{
private:
int value;
public:
Object(int x = 0) : value(x) {}
virtual void add() { cout << "Object::add" << endl; }
virtual void fun() { cout << "Object::fun" << endl; }
virtual void print() const { cout << "Object::print" << endl; }
void fn_a()
{
fun();
}
};
class Base : public Object
{
private: int sum;
public:
Base(int x = 0) : Object(x+10),sum(x) {}
virtual void add() { cout << "Base:add" << endl; }
virtual void fun() { cout << "Base::fun" << endl; }
virtual void show() { cout << "Base::print" << endl; }
};
class Test : public Base
{
private:
int num;
public:
Test(int x = 0) :Base(x+10),num(x) {}
virtual void add() { cout << "Test::add" << endl; }
virtual void print() const { cout << "Test::print" << endl; }
virtual void show() {cout << "Test::show" << endl; }
};
int main()
{
Test t1;
Base base;
Object obj;
t1.fn_a();
base.fn_a();
obj.fn_a();
return 0;
}
答案:Baset::fun Base::fun Object::fun
fun()函数调用的时候前面有一个this指针,即this->fun();所以采取的是动态联编,Test中的fun是继承Base来的,所以调用的是Base的fun,Base中本来有fun就调用Base的fun,Object中本来也有fun,就调用Object中的fun,所以就是Baset::fun Base::fun Object::fun
14.C语言的继承方式和C++的继承方式
class Shape
{
public:
virtual float area() const;
};
class Square : public Shape
{
private:
float length;
public:
float area() const
{
return length * length;
}
};
const float pi = 3.14;
typedef struct _Ops
{
float (*area)(void*);
void (*Draw)(void*);
}Ops;
typedef struct _Shape
{
Ops ops;
}Shape;
typedef struct _Square
{
Shape shape;
float length;
}Square;
typedef struct _Circle
{
Shape shape;
float radius;
}Circle;
float square_area(void* thiz)
{
if (thiz == NULL) return -1;
printf("Square area: \n");
Square* sp = (Square*)thiz;
float area = sp->length * sp->length;
return area;
}
float circle_area(void* thiz)
{
if (thiz == NULL) return -1;
printf("Circle area: \n");
Circle* sp = (Circle*)thiz;
float area = sp->radius * sp->radius * pi;
return area;
}
int main()
{
Square square;
memset(&square, 0, sizeof(Square));
square.length = 10;
square.shape.ops.area = square_area;
Circle circle;
memset(&circle, 0, sizeof(Circle));
circle.radius = 10;
circle.shape.ops.area = circle_area;
Shape* shape = NULL;
shape = (Shape*)□
cout << shape->ops.area(&square) << endl;
shape = (Shape*)&circle;
cout << shape->ops.area(&circle) << endl;
return 0;
}
15.判断下面程序的输出结果是什么?
class Object
{
public:
virtual void print(int x = 10)
{
cout << "Object::print : x " << x << endl;
}
};
class Base : public Object
{
private:
virtual void print(int a = 20)
{
cout << "Base::print:: a : " << a << endl;
}
};
int main()
{
Base base;
Object* op = &base;
op->print();
return 0;
}
答案:Base::print:: a : 10