概念: ①:友元的作用就是让类外的数据突破访问权限的设置,友元可以访问类内任意数据; ②:可以将类/函数声明为某个类的友元,这些类和函数就可以访问类中的数据。
语法 ①:友元函数:友元函数是一个全局函数,在类内部可以将该函数声明为友元,这样这个全局函数就可以访问类内的数据。
②:友元类:如果将类A声明为类B的友元,类A中就可以随问类B中的数据,不受访问属性的限制。
//友元函数:在类内部声明: friend 函数声明; //友元类:在类内部声明: friend class 类名; 注意: 友元不受访问属性的限制,可以在类外访问类中的所有数据,破坏了累的封装属性,如非必要,不要使用。概念: 实现代码和数据的复用,复用的实现在已有的代码和数据的基础上扩展。 例如:“狗”继承了“动物”这个类的一些特征,又拓展了独有的特征。
继承的方式及语法: ①公有继承 ----------- class B:public A{…};
父类的公有数据在子类中仍然公开 父类的受保护数据在子类中仍然受保护 父类的私有数据在子类中是隐藏的
②保护继承 ----------- class B:protected A{…};
父类的公有数据在子类中变为受保护 父类的受保护数据在子类中仍然受保护 父类的私有数据在子类中是隐藏的
③私有继承 ----------- class B:private A{…};
父类的公有数据在子类中变为私有 父类的受保护数据在子类中变为私有 父类的私有数据在子类中是隐藏的
注意: ①:父类中的成员在子类中的访问权限只会收缩,不会扩大,在子类中的访问属性不会超过继承方式。
②:所谓的继承方式就是能够提供给子类的最大访问权限,实际权限小于等于继承方式,私有的数据必然在子类中隐藏
①:构造子类时,自动调用父类的构造函数;析构子类时,自动调用父类的析构函数。
②:调用构造和析构的顺序是相反的,先构造父类在构造子类,先析构子类,再析构父类。
③:在继承中,父类的数据由父类构造和析构,子类新增的数据由子类构造和析构。
④:如果父类中有多个构造函数(构造函数有参数),子类默认只会去调用无参的构造函数,可以在子类构造函数的初始化参数列表中里选择对应的父类构造函数。(语法参照下面)
class A{ int a; int b; public: A(){a=1;b=2;cout<<"A()"<<endl;} A(int a,int b){ this->a=a; this->b=b; cout<<"A(int,int)"<<endl; } ~A(){cout<<"~A()"<<endl;} }; class B:public A{ int c; public: //通过初始化参数列表给父类构造函数传参数 B(int a,int b,int c):A(a,b),c(c){ cout<<"B(int,int,int)"<<endl; } ~B(){cout<<"~B()"<<endl;} };案例(猫、狗继承了动物的特征,并有自己独有的特征 ):
#include <iostream> using namespace std; //父类 class Animal{ public: string name; int age; void show(){ cout<<"Animal show"<<endl; } }; //Cat公有继承Animal class Cat:public Animal{ public: string color; void catfunc(){ cout<<"猫学会抓老鼠"<<endl; } }; //Dog公有继承Animal class Dog:public Animal{ public: string color; void dogfunc(){ cout<<"狗学会看门"<<endl; } }; int main() { Animal an; an.show(); Dog dg; dg.show(); dg.dogfunc(); Cat ct; ct.show(); ct.catfunc(); return 0; }①:子类使用默认拷贝构造函数,自动去调用父类的拷贝构造,除非重写; ②:在子类拷贝构造函数中应该使用初始化参数列表去调用父类的拷贝构造函数(传参使用子类对象来代表父类)。
B(const B &b):A(b){ //初始化参数列表:父类的类名A(子类的对象b) this->c = new char[10]; //c:子类B的成员变量 strcpy(this->c,b.c);①:如果子类中出现了和父类同名的成员,父类中的同名成员将被隐藏。 ②:如果需要访问父类中被隐藏的成员,需要使用类名作用域符来访问
//父类名::隐藏的成员 dg.Animal::show(); //dg子类对象调用父类Animal的show函数,变量同理概念 ①:C++允许一个子类继承多个父类,当一个类中包含多个已有类中的属性和功能时,可以使用多重继承。
②:多继承中子类中的数据和继承顺序有关,如果继承的数据不冲突(无同名数据),所有的数据都直接使用,如果发生冲突可以使用父类名加作用域符来区分。
③:多继承中易出现同名数据,造成代码冗余,一般不使用。
class 子类名:继承方式1 父类1,继承方式2 父类2....{ ..... }; 多继承的优化 ①:将父类中的共同成员抽取出来; ②:将这些成员放入一个更高层的父类; ③:父类虚继承(virtual)最高层的类; ④:注意: 虚继承对于单层继承来说,和普通继承没有任何区别;当多层继承时,子类在拷贝数据时,如果该数据属于更高层的父类,子类不会从直接父类中拷贝数据,而是从最高层的父类获取数据。 /****************** object类保存信息,person,company类虚继承object,employer类继承person,company *****************/ #include <iostream> #include <cstring> using namespace std; class Object{ char *p;//说明信息 public: Object(const char *s=NULL){ this->p = new char[strlen(s)+1]; strcpy(this->p,s); } ~Object(){ delete[] this->p; } //获取p const char *get_info(){ return this->p; } }; class Person:virtual public Object{ string p_name; public: Person(const char *s=NULL,string name="abc"):Object(s),p_name(name){ } //获取 string get_pname(){ return this->p_name; } void show(){ cout<<get_pname()<<":"<<get_info()<<endl; } }; class Company:virtual public Object{ string c_name; public: Comp(const char *s=NULL,string name="abc"):Object(s),c_name(name){ } //获取 string get_cname(){ return this->c_name; } }; class Employer:virtual public Person,virtual public Company{ double salary; public: Employer(const char *s=NULL,string p_name="abc",string c_name="abc",double salary=1000.0): Object(s),Person(s,p_name),Company(s,c_name),salary(salary){ cout<<"Employer()"<<endl; } void show(){ cout<<get_pname()<<":"<<get_cname()<<":"<<get_info()<<":"<<this->salary<<endl; } }; int main() { Employer ep("五虎上将","张飞","蜀国",20000.0); ep.show(); ep.Person::show(); return 0; }在 C++中,用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数,大大提高编程的效率和灵活性,即C++多态性。
编译时的多态性是通过重载实现的,而运行时的多态性则是通过虚函数实现的。
①:虚函数就是在类普通成员函数前加virtual修饰。一旦一个类中有虚函数(Virtual Function),编译器将为类生成虚函数列表(Virtual Table)。
②:虚函数表中每一个元素记录一个虚函数的地址,对象的前4(8)个字节记录虚函数表的地址。
③:如果父类中有虚函数,子类总对虚函数重写(overwrite),子类中重写的函数就会覆盖原虚函数的位置。
④:如果父类中有虚函数,子类中重写了该虚函数,使用父类类型记录子类对象时,调用这个函数就回去调用子类中被重写的函数,而不会调原虚函数。普通的成员函数没有这个特性。 1.1. 虚函数验证
#include <iostream> using namespace std; //对 void* 取别名 typedef void (*pfunc_t)(); class Animal{ public: //虚函数 virtual void show(){ cout<<"Animal show"<<endl; } virtual void run(){ cout<<"Animal run"<<endl; } virtual void eat(){ cout<<"Animal eat"<<endl; } }; class Dog:public Animal{ public: //虚函数重写 void show(){ cout<<"Dog show"<<endl; } void run(){ cout<<"Dog run by legs"<<endl; } void eat(){ cout<<"Dog eat bones"<<endl; } }; void Animal_test(Animal &an) { an.show(); } int main() { Animal *pa = new Animal; //pfunc_t *指向pa对象, 后加*指向虚函数表首地址, //前加*对首地址解引用,表示虚函数表, //[0]代表虚函数表第一个虚函数,()表示对该函数的调用 (*(pfunc_t **)pa)[0](); (*(pfunc_t **)pa)[1](); delete pa; pa = new Dog; pa->show(); //调用的是dog的show /*Animal a; Dog d; Animal_test(a); Animal_test(d); //调用的是各自类里的函数 */ return 0; }1.2. 函数重载(overload),名字隐藏(name hide),函数重写(overwrite)的区别
①:在同一作用域,函数名相同,参数列表不同的函数构成重载关系。 ②:子类中出现与父类同名的成员,父类的同名成员在子类中被隐藏 ③:子类中重写父类中的虚函数,父类的虚函数将被覆盖(虚函数表)1.3. 多态总结
①:多态的基础是继承 ②:虚函数是实现多态的关键 ③: 虚函数重写(函数覆盖)是多态的必要条件1.4. 对象个性恢复
多态在类型上实现了通用,同时也损失了对象的一些个性, 如果希望获取对象真正的类型(恢复个性), 可以使用以下语法实现具有多态性的父子对象之间的类型转换。 dynamic_cast<类型>(对象); //转换成功返回合法地址,失败返回NULL //只能转化成本类型/子类型,不能转换成父类型 Dog *dg = dynamic_cast<Dog *>(*an);//an为animal类型的指针