本文共 5763 字,大约阅读时间需要 19 分钟。
1)对于单个类来说,访问修饰符: public 修饰的成员变量 方法 在类的内部 类的外部都能使用; protected: 修饰的成员变量方法,在类的内部使用 ,在继承的子类中可用 ;其他 类的外部不能被使用; private: 修饰的成员变量方法 只能在类的内部使用 不能在类的外部;
2)C++中的继承方式会影响子类的对外访问属性: public继承:父类成员在子类中保持原有访问级别 private继承:父类成员在子类中变为private成员 protected继承:父类中public成员会变成protected 父类中protected成员仍然为protected 父类中private成员仍然为private private成员在子类中依然存在,但是却无法访问到。不论种方式继承基类,派生类都不能直接使用基类的私有成员 。
3)C++中的继承方式(public、private、protected)会影响子类的对外访问属性,判断某一句话,能否被访问: a)看调用语句,这句话写在子类的内部、外部 b)看子类如何从父类继承(public、private、protected) c)看父类中的访问级别(public、private、protected)
4)派生类访问控制的结论: a. protected 关键字 修饰的成员变量 和成员函数 ,是为了在家族中使用 ,是为了继承; b. 项目开发中 一般情况下 是 public;
5)类型兼容性原则: a)子类对象可以当作父类对象使用——父类指针 (引用) 指向 子类对象 b)子类对象可以直接赋值给父类对象——指针(引用)做函数参数,实参是子类指针(实例),形参却要求的是父类指针(实例) c)子类对象可以直接初始化父类对象——可以用子类对象直接 初始化 父类对象,子类就是一种特殊的父类 d)父类指针可以直接指向子类对象 e)父类引用可以直接引用子类对象 在替代之后,派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员。类型兼容规则是多态性的重要基础之一。
6)继承中的构造析构调用原则: 在子类对象构造时,需要 调用父类构造函数 对其 继承得来的成员 进行初始化; 在子类对象析构时,需要 调用父类析构函数 对其 继承得来的成员 进行清理; 子类对象在创建时会首先调用父类的构造函数,父类构造函数执行结束后,执行子类的构造函数,当父类的构造函数有参数时,需要在子类的初始化列表中显示调用; 析构函数调用的先后顺序与构造函数相反;
7)继承与组合混搭情况下,构造和析构调用原则:
(类的组合其实就是“包含”,A类里面有一个数据成员的B类) 原则:先构造父类(父之父),再构造成员变量(组合类的对象),最后构造自己 先析构自己, 再析构成员变量(组合类的对象),最后析构父类(父之父)
继承与组合混搭情况例子:
#includeusing namespace std;class Object //祖宗类{public: Object(int a, int b) { this->a = a; this->b = b; cout<<"object构造函数 执行 "<<"a "< <<" b "<< p = p; cout<<"父类构造函数..."< <
myp = p; cout<<"子类的构造函数"< <
8)虚继承:如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性;如果在多条继承路径上有一个公共的基类,那么在继承路径的某处汇合点,这个公共基类就会在派生类的对象中产生多个基类子对象;要使这个公共基类在派生类中只产生一个子对象,必须对这个基类 声明为虚继承,使这个基类成为虚基类。虚继承声明使用关键字——virtual。——解决下图第一种
注意:实际开发经验抛弃多继承,工程开发中真正意义上的多继承是几乎不被使用的,多重继承带来的代码复杂性远多于其带来的便利,多重继承对代码维护性上的影响是灾难性的。 多重继承接口不会带来二义性和复杂性等问题 ,多重继承可以通过精心设计用单继承和接口(纯虚函数抽象类定义一套接口)来代替,接口类只是一个功能说明,而不是功能实现,子类需要根据功能说明定义功能实现。
2)迟绑定————
3)虚析构函数:目的是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。 当然,并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针。所以,只有当一个类被用来作为最高的基类的时候,才把析构函数写成虚函数。
#define _CRT_SECURE_NO_WARNINGS#include4)区别重载、重写(虚函数)和重定义using namespace std;class A //最高基类{public: A() { p = new char[20]; strcpy(p, "obja"); printf("A()\n"); } virtual ~A() //虚析构函数 { delete[] p; printf("~A()\n"); }private: char *p;};class B : public A{public: B() { p = new char[20]; strcpy(p, "objb"); printf("B()\n"); } ~B() { delete[] p; printf("~B()\n"); }private: char *p;};class C : public B{public: C() { p = new char[20]; strcpy(p, "objc"); printf("C()\n"); } ~C() { delete[] p; printf("~C()\n"); }private: char *p;};//不使用虚析构函数//下面的程序不会表现成 多态 这种属性,只执行了 父类的 析构函数//想通过 父类指针 把 所有的子类对象的析构函数 都执行一遍//想通过 父类指针 释放所有的子类资源 //使用 虚析构函数 来实现void howtodelete(A *base){ delete base; //单独这句话不会表现成 多态 这种属性,需要借助虚析构}void main(){ C *myC = new C; //delete myC; //直接通过子类对象释放所有资源,能正确调用所有的析构函数(~C,~B,~A),不需要写virtual也可以实现 howtodelete(myC);//通过父类指针释放所有资源(~C,~B,~A) cout << "hello..." << endl; system("pause"); return;}
#include5)多态的实现原理 当类中声明虚函数时,编译器会在类中生成一个虚函数表,虚函数表是一个存储类成员函数指针的数据结构,虚函数表是由编译器自动生成与维护的,virtual成员函数会被编译器放入虚函数表中,当存在虚函数时,每个对象中都有一个指向虚函数表的指针(C++编译器给父类对象、子类对象提前布局vptr指针;当进行howToPrint(Parent *base)函数是,C++编译器不需要区分子类对象或者父类对象,只需要在base指针中,找vptr指针即可。),VPTR一般作为类对象的第一个成员 ,存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)。 说明1:通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。 说明2:出于效率考虑,没有必要将所有成员函数都声明为虚函数。 加上virtual关键字 c++编译器会增加一个指向虚函数表的指针 。对象在创建的时侯,由编译器对VPTR指针进行初始化 ;只有当对象的构造完全结束后VPTR的指向才最终确定;父类对象的VPTR指向父类虚函数表;子类对象的VPTR指向子类虚函数表。 6)父类指针和子类指针的步长 结论:父类p++与子类p++步长不同;不要混搭,不要用父类指针++方式操作数组。using namespace std;//重写(虚函数) 重载 重定义//重写发生在2个类之间//重载必须在一个类之间//重写分为2类//1 虚函数重写 将发生多态//2 非虚函数重写 (重定义)class Parent{ //这个三个函数都是重载关系public: void abc() { printf("abc"); } virtual void func() { cout << "func() do..." << endl; } virtual void func(int i) { cout << "func() do..." << i << endl; } virtual void func(int i, int j) { cout << "func() do..." << i << " " << j << endl; } virtual void func(int i, int j, int m, int n) { cout << "func() do..." << i << " " << j << endl; }};class Child : public Parent{public: void abc() { printf("child abc"); } /* void abc(int a) { printf("child abc"); } */ virtual void func(int i, int j) { cout << "func(int i, int j) do..." << i << " " << j << endl; } virtual void func(int i, int j, int k) { cout << "func(int i, int j) do.." << endl; }};//重载、重写(虚函数)和重定义void main(){ //: error C2661: “Child::func”: 没有重载函数接受 0 个参数 Child c1; //c1.func(); //子类无法重载父类的函数,父类同名函数将被名称覆盖 c1.Parent::func(); //想调用父类的四个函数的func //1 C++编译器看到func名字,因子类中func名字已经存在了(名称覆盖),编译器认为这是个自己的函数.所以c++编译器不会去找父类的4个参数的func函数(被子类中相同的名字覆盖了) //2 c++编译器只会在子类中,查找func函数,找到了两个func,一个是2个参数的,一个是3个参数的. //4 若想调用父类的func,只能加上父类的域名::..这样去调用.. c1.func(1, 3, 4, 5); //3 C++编译器开始报错..... error C2661: “Child::func”: 没有重载函数接受 4 个参数 c1.func(); //错误 2 error C2661: “Child::func”: 没有重载函数接受 0 个参数 //func函数的名字,在子类中发生了名称覆盖——覆盖了;子类的函数的名字,占用了父类的函数的名字的位置 //因为子类中已经有了func名字的重载形式。。。。 //编译器开始在子类中找func函数。。。。但是没有0个参数的func函数 cout << "hello..." << endl; system("pause"); return;}
class Parent{public: Parent(int a = 0) { this->a = a; } virtual void print() { cout << "我是爹" << endl; }private: int a;};class Child : public Parent{public: Child(int b = 0) :Parent(0) { this->b = b; } virtual void print() { cout << "我是儿子" << endl; }private: int b;};void main(){ Parent *pP = NULL; Child *pC = NULL; Child array[] = { Child(1), Child(2), Child(3) }; pP = array; pC = array; pP->print(); pC->print(); //多态发生 pP++; pC++; pP->print(); pC->print(); //多态发生 pP++; pC++; pP->print(); pC->print(); //多态发生 cout << "hello..." << endl; system("pause"); return;}程序自动宕机,从上图可以看出在基类指针++以后,指向内存的出错!