现在,在main()中为p分配的内存便是无用单元,因为它仍然是正在运行程序的一部分,但是,所有对它的引用都被销毁了。
无用单元不会立即对程序造成损害,但它将逐渐消耗内存,最终耗尽内存导致系统中止运行。
当指针所指向的内存被删除,但程序员认为被删除内存的地址仍有效时,就会产生悬挂引用(dangling reference)。例如:
main() { char *p; char *q; p = new char[1024]; // 分配1k字符的动态数组 // ... 使用它 q = p; // 指针别名(pointer aliasing) delete [] p; p = 0; // 现在q是一个悬挂引用,如果试图 *q = ‘A’,将导致程序崩溃。 }如果试图访问q所指向的内存,将引发严重的问题。在该例中,指针q称为悬挂引用。指针别名(即多个指针持有相同的地址)通常会导致悬挂引用。与无用单元相比,悬挂引用对于程序而言是致命的,因为它必定导致严重破坏(大多数可能是运行时崩溃)。
继承面向对象编程的主要目的之一就是提供可重用的代码。
尽管复制源码进行修改也可以实现代码重用,但是C++提供了更好的方法--继承,可以不用复制源码便可进行扩展和修改。
最原始的类叫做基类,继承基类的类叫做派生类。
要正确地使用继承,必须充分理解继承(或is-a)关系的含义。is-a关系意味着基类与派生类之间的一般/特殊的关系。
基类是一般类,派生类可以扩展基类或者覆写基类方法。
创建派生类对象时,程序首先调用基类构造函数,然后再调用派生类构造函数。基类构造函数负责初始化继承的数据成员;派生类构造函数主要用于初始化新增的数据成员。派生类的构造函数总是调用一个基类构造函数。可以使用初始化器列表语法指明要使用的基类构造函数,否则将使用默认的基类构造函数。
派生类对象过期时,程序将首先调用派生类析构函数,然后再调用基类析构函数。
class Student: public Person { public: Student(int stuid): Person("张三",20),m_stuid(stuid) { } void write() { cout<<"My id is:"<< m_stuid << endl; } private: int m_stuid; //学号 };上述代码完成了哪些工作呢?
派生类对象存储了基类的数据成员(派生类继承了基类的实现);
派生类对象可以使用基类的方法(派生类继承了基类的接口)。
需要在继承特性中添加什么呢?
派生类需要自己的构造函数。
派生类可以根据需要添加额外的数据成员和成员函数.
构造函数处使用了初始化列表方式,初始化列表语法相比赋值语句可减少一个步骤,因此执行更快。
访问权限
派生类不能直接访问基类的私有成员,而必须通过基类方法进行访问。如上述的 getName()和 getAge() 。
派生类构造函数必须使用基类构造函数。如果不调用基类构造函数,程序将使用默认的基类构造函数。如 Student(int stuid) 。
成员初始化列表
派生类构造函数可以使用初始化器列表机制将值传递给基类构造函数。请看下面的例子:
derived::derived(type1,type2):base(x,y)
{
...
}
其中derived是派生类,base是基类,x和y是基类构造函数使用的变量。例如,如果派生类构造函数接收到参数10和12,则这种机制将把10和12传递给被定义为接受这些类型的参数的基类构造函数。除虚基类外,类只能将值传递回相邻的基类,但后者可以使用相同的机制将信息传递给相邻的基类,依此类推。如果没有在成员初始化列表中提供基类构造函数,程序将使用默认的基类构造函数。成员初始化列表只能用于构造函数。
什么不能被继承?
构造函数和析构函数不能被继承。