基类中的某些函数,基类希望派生类定义适合自己的版本,这些函数则被声明为虚函数,virtual。动态绑定发生在基类指针(或者引用)调用虚函数的时候。通常基类的析构函数被定义为虚函数,解析过程发生在运行期。如果基类把一个函数声明成虚函数,则函数在派生类中也是虚函数。
调用虚函数时,会调用距离其类最近的实现虚函数的类。
class A
{
public:
virtual void test()
{
std::cout << "111111" << std::endl;
}
int i;
};
class B :public A
{
public:
void test() override
{
std::cout << "2222222" << std::endl;
}
};
class C :public B
{
public:
};
int main()
{
std::shared_ptr<A> s;
C s1;
s.reset(new C());
s->test(); //输出22222
s->A::test();//强行调用基类函数
}
构造函数也不允许是虚函数,因为创建一个对象时我们总是要明确指定对象的类型,尽管我们可能通过实验室的基类的指针或引用去访问它。
派生类对象中含有从基类继承而来的成员,但是派生类不直接初始化这些基类成员。派生类调用基类的构造函数进行初始化。
构造析构顺序
class A
{
public:
A()
{
std::cout << "A contruct" << std::endl;
}
virtual ~A()
{
std::cout << "A decontruct" << std::endl;
}
};
class B :public A
{
public:
B()
{
std::cout << "B contruct" << std::endl;
}
~B()
{
std::cout << "B decontruct" << std::endl;
}
};
class C :public B
{
public:
C()
{
std::cout << "C contruct" << std::endl;
}
~C()
{
std::cout << "C decontruct" << std::endl;
}
};
int main()
{
C c; //初始化顺序 A B C和析构顺序 C B A
}
同名静态成员在继承体系中值情况
对于基类和继承类拥有同名变量的情况,如果基类指针指向一个由继承类构造出来的对象,基类指针指向的静态变量是与基类关联的静态变量,只有强行将基类指针转换成继承类后,再 获取静态变量的值的时候,才会得到对应继承类的静态变量,。
只有虚函数才会发生动态绑定,其他情况全部是静态绑定,基类会覆盖继承类的静态变量值
class A
{
public:
A()
{
std::cout << "A contruct" << std::endl;
}
virtual ~A()
{
std::cout << "A decontruct" << std::endl;
}
virtual void test()
{
std::cout << "A test" << std::endl;
}
void test2()
{
std::cout << "A test2" << std::endl;
}
static void test3()
{
std::cout << "A test3" << std::endl;
}
static int m;
};
int A::m = 15;
class B :public A
{
public:
B()
{
std::cout << "B contruct" << std::endl;
}
void test()
{
std::cout << "B test" << std::endl;
}
void test2()
{
std::cout << "B test2" << std::endl;
}
static void test3()
{
std::cout << "B test3" << std::endl;
}
~B()
{
std::cout << "B decontruct" << std::endl;
}
static int m;
};
int B::m = 10;
class C :public B
{
public:
C()
{
std::cout << "C contruct" << std::endl;
}
~C()
{
std::cout << "C decontruct" << std::endl;
}
void test()
{
std::cout << m << std::endl;
}
};
int main()
{
A * a = new B();
B * b = new B();
C *c = new C();
std::cout << a-> m << " " << b->m << " " << c->m << std::endl; //默认会优先获得该基类的静态成员, 结果时 15, 10, 10
std::cout << static_cast<B*>(a)->m << std::endl; //(强行将a转换成B*,这个时候会得到B的m的值)
a->test(); //虚函数发生动态绑定,所以这里会调用B的test
a->test2();//成员函数编译时绑定,所以这里会调用a的test2
a->test3();//静态函数也是编译时绑定,所以这里调用a的test3
delete a;
delete b;
delete c;
}
#include<iostream>
using namespace std;
class A
{
public:
static int m;
};
int A::m = 15;
class B :public A
{
public:
static int m;
int B::m = 10;
class C :public B
{
};
int main()
{
A * a = new B();
}
为啥基类析构函数要声明成virtual
#include<iostream>
using namespace std;
class A
{
public:
A()
{
std::cout << "A contruct" << std::endl;
}
~A()
{
std::cout << "A decontruct" << std::endl;
}
};
class B :public A
{
public:
B()
{
k = new char(10);
std::cout << "B contruct" << std::endl;
}
~B()
{
std::cout << "B decontruct" << std::endl;
}
char * k;
};
int main()
{
A * a = new B();
delete a; //如果A 析构不是虚函数,则delete调用的析构函数在编译的时候就被自动绑定到A自己的析构函数,从而不会执行B的析构函数,造成B 中堆的变量k 无法释放,引发内存泄露
}
可以这样理解不存在基类向派生类的隐式转换
class A
{
public:
A()
{
std::cout << "A contruct" << std::endl;
}
virtual ~A()
{
std::cout << "A decontruct" << std::endl;
}
};
class B :public A
{
public:
B()
{
std::cout << "B contruct" << std::endl;
k = new int(10);
}
void test()
{
std::cout << "B test2" << *k << std::endl;
}
~B()
{
std::cout << "B decontruct" << std::endl;
}
int *k;
};
int main()
{
A * a = new A();
static_cast<B*>(a)->test(); //a表示很懵逼,我没有一个指向int的指针k,怎么去解引用~~还是一起die把
}
纯虚函数
可以为纯虚函数定义,但是函数定义必须放在类的外面
class A
{
public:
virtual void test() = 0;
};
class B:public A
{
public:
void test()
{
std::cout << "1111111111" << std::endl;
}
};
int main()
{
A1 *s = new B1();
s->test();
}
派生类和基类的访问权限问题
class A1
{
public:
int x;
protected:
int y;
private:
int z;
};
class C1 : public A1
{
public:
friend void clobber1(A1 &);
friend void clobber1(C1 &);
void test()
{
std::cout << "public 继承 派生类对象内部(有this指针)能访问基类的公有和保护成员" << std::endl;
x = 0;
y = 0;
//z = 0;//无法访问
}
};
void clobber1(A1 &a)
{
std::cout << "public 继承 派生类友元函数 基类对象只能被访问自身的公有成员。" << std::endl;
a.x = 0;
/*a.y = 0;
a.z = 0;*///无法访问
}
void clobber1(C1 &c)
{
std::cout << "public 继承 派生类友元函数 派生类对象只能被访问其基类的公有和保护成员." << std::endl;
c.x = 0;
c.y = 0;
//c.z = 0;//无法访问
}
class C2 : protected A1
{
public:
friend void clobber2(A1 &);
friend void clobber2(C2 &);
void test()
{
std::cout << "protected 继承 派生类对象内部(有this指针)能访问基类的公有和保护成员. " << std::endl;
x = 0;
y = 0;
//z = 0;//无法访问
}
};
void clobber2(A1 &a)
{
std::cout << "protected 继承 派生类友元函数 基类对象只能被访问自身的公有成员." << std::endl;
a.x = 0;
/*a.y = 0;
a.z = 0;*///无法访问
}
void clobber2(C2 &c)
{
std::cout << "protected 继承 派生类友元函数 派生类类对象只能被访问其基类公有和保护成员." << std::endl;
c.x = 0;
c.y = 0;
//c.z = 0;//无法访问
}
class C3 : private A1
{
public:
friend void clobber3(A1 &);
friend void clobber3(C3 &);
void test()
{
std::cout << "private 继承 派生类对象内部(有this指针)能访问基类的公有和保护成员." << std::endl;
x = 0;
y = 0;
//z = 0;//无法访问
}
};
void clobber3(A1 &a)
{
std::cout << "private 继承 派生类友元函数 基类对象只能被访问自身的公有成员." << std::endl;
a.x = 0;
/*a.y = 0;
a.z = 0;*///无法访问
}
void clobber3(C3 &c)
{
std::cout << "private 继承 派生类友元函数 派生类对象只能被访问其基类的公有和保护成员." << std::endl;
c.x = 0;
c.y = 0;
//c.z = 0;//无法访问
}
int main()
{
A1 a = {};
a.x = 0;
/*a.y = 0;
a.z = 0;*/普通对象外部(没有this指针)只能获得public 成员
C1 c1 = {};
std::cout << "public 继承 派生类对象(类外部,没有this指针)只能被访问基类的public成员." << std::endl;
C2 c2 = {};
std::cout << "protected 继承 派生类对象(类外部,没有this指针)不能被访问基类任何成员." << std::endl;
C3 c3 = {};
std::cout << "private 继承 派生类对象(类外部,没有this指针)不能被访问基类任何成员." << std::endl;
c1.x = 0;
/*c1.y = 0;
c1.z = 0;
c1.x = 0;
c2.y = 0;
c2.z = 0;
c3.x = 0;
c3.y = 0;
c3.z = 0;*///无法访问
c1.test();
c2.test();
c3.test();
clobber1(c1);
clobber1(a);
clobber2(c2);
clobber2(a);
clobber3(c3);
clobber3(a);
}
友元函数和派生类相结合,造成了一个有意思的现象。 比较两个clobber 函数, 第一个clobber拿到了基类的对象,第二个拿到了派生类的对象。先来看看,非友元函数参数的普通对象(类外部),保护继承的情况下,无法访问基类的任何成员。友元函数扩大了一部分权限,可以让派生类对象既能访问基类的公有成员,也能访问基类的保护成员。但是友元不是万能的,基类的私有对象仍旧不能被访问。同时,友元函数只能在同一个类之间扩展,对于继承,友元不负责。因此,基类对象在继承类的友元函数中被当成了一个普通对象,所以只能访问其公有成员。不过可以理解,因为基类对象只能被访问公有成员,而不能被访问保护成员,因此也杜绝了我们以这种方式类修改基类的保护成员。
使用using改变个别成员的访问性
主要是方便对象(类外)直接访问. 同时 using 可以访问所有重载成员
class K1
{
public:
int size;
void tk() {}
void tk(int k) {}
};
class K2 :private K1
{
public:
using K1::tk;
//using K1::size;
};
int main()
{
K2 k = {};
k.tk();//可以访问
t2.tk(10);//可以访问
k.size//无法访问
}
编译器的名字查找
编译器主要进行静态类型分析。在查找某一成员时,会从当前类开始查找,如果找不到,再从其继承的类的去查找。因此基类不会找到派生类的成员,除非采用类型转换
class K1
{
};
class K2 :public K1
{
public:
void MM()
{
}
};
int main()
{
K2 t2 = {};
K1 *t1 = &t2;
t1->MM();//错误,无法访问
static_cast<K2*>(t1)->MM();
虚析构 和拷贝控制
基类需要析构函数。但是时虚函数。同时虚析构函数会阻止合成移动操作。可以理解成,虚析构函数运行时不会是基类使用,可以当成基类没有析构函数,没有析构,自然编译器就不会合成相应的移动操作。
基类和派生类的合成拷贝控制与普通的一样。唯一需要注意的是相应的成员是否可以访问并且不是一个被删除的函数。