}
一个try语句后可接多个catch语句,catch语句按先后顺序执行,如果前一个catch语句没有捕捉到错误,会继续向后执行,只要找到一个匹配的类型,后面的异常处理都将被忽略。
1.3.3异常规格说明
voidf()throw(int,long,float,char*);员名
如:
ArrayName[0].OpenWindow();
1.3.4对象数组的初始化
首先在类中定义出对应的初始化的构造函数(因为对象数组的初始化其实就是数组中的每个元素对象的初始化),然后通过初始化列表的方式一一为各个元素对象赋初始值。
【实例练习】
classWindow
{public:
Window(intx,inty,inth,intw)
{X=x,Y=y,H=h,W=w;
}
private:
intX,Y,H,W;
};
voidmain()
{WindowWinArray[2]={Window(10,10,200,200),Window(20,20,300,300)};
for(inti=0;i<2;i++)
{WinArray[i].MoveWindow(10*i,20*i);
}
}
1.3.5对象数组编程时的构造函数设计
数组中的每一个元素对象被创建时,系统都要调用构造函数来初始化各元素对象,用户可根据应用需要,将构造函数设计为:
❑不定义出构造函数---采用缺省构造函数,各元素对象的初值要求为0时;
❑定义出具有缺省形参的构造函数---各元素对象的初值要求为相同的值时;
❑定义出带形参的构造函数---各元素对象的初值要求有不相同的初值时。
1.3.6对象数组编程时的析构函数设计
当数组中的每一个元素对象被删除时,系统都需要调用一次析构函数,将导致析构函数被多次调用。
因此释放资源时应先识别指针是否为NULL,避免多次删除。
【实例练习】
classWindow
{public:
voidOpenWindow(intx,inty,inth,intw)
{Title=newchar[20];
strcpy(Title,"WindowTitle");
rectangle(x,y,x+w,y+h);
}
~Window()
{if(Title!
=NULL)penWindow(10,20,200,200);
WinArray[1].MoveWindow(100,200);
}
1.4对象指针
1.4.1C/C++语言中的指针
它是一种特殊的变量,其值代表另一个变量的存储地址(可以给出内存存储图来说明)。
1、定义语法:
数据类型*指针变量名;
如:
int*ptr;
2、初始化:
通过对指针进行初始化来指示其所指向的目标变量单元,否则为空指针。
intX=1;
int*ptr=&X;penWindow();penWindow();penWindow();
ptr[1].OpenWindow();
delete[]ptr;
}
1.5对象可以作为函数的形参和返回值的定义
【蓝梦提示】
□在此种应用中需要注意拷贝构造函数的设计
voidShowArea(Windowwin)员名”)。
2、静态成员函数(系统自动将它看作为内联函数):
采用“类名:
:
成员函数名(实参)”访问它。
voidmain()
{ChineseLi,Zhang;
();员名”格式,一般不采用此方式
Chinese:
:
getSkinColor();员名”(因为静态成员函数中无this指针定义,不能隐含指定成员数据所属的对象)。
3、非静态成员函数可以直接访问类中的静态成员数据和非静态成员数据。
【实例练习】
#include
#include
usingnamespacestd;
classChinese
{
public:
Chinese(stringna){Name=na;}
public:
staticintgetSkinColor(ChinesePerson)员名"
❑returnSkinColor;员名”方式,而是直接调用它。
❑由于友员函数不是成员函数,因而无this指针,在函数体中访问对象中的成员时必须通过对象名(可将形参定义为引用),而在成员函数体内可以直接访问成员数据。
1.5.1友员函数与一般函数(非成员函数)在编程方面的不同点
❑友员函数在类体内应该采用friend加以限定,但非成员函数不需要在类中声明。
❑在友员函数体内可以利用对象访问类中的private成员,但非成员函数则不能。
1.6友员类:
1.6.1含义
一个类为另一个类的友员类---此类的所有成员函数都能访问对方类的私有成员。
1.6.2友员类的定义
将此类名在另一个类中使用friend加以修饰说明。
classMyClass;员分隔符,:
:
作用域限定符,:
条件运算符,sizeof运算符,
成员指针选择符”.*”
5、重载的要求:
一般将一元运算符重载为类成员函数(“=”也必须重载为类运算符函数,请见前面的应用例),而将二元运算符重载为友员运算符函数(当然也可重载为类运算符函数)。
【本章总结】
本章主要讲解了友元函数及运算符重载的应用,其中应熟练掌握的是运算符重载的用法。
第2章C++类的继承与派生
【本章中的知识要点】
□类的继承与派生目的,派生类的正确定义,子类中的成员正确设计
□继承方式的合理选用,多重继承的编程
【重点难点】
□继承中类之间的访问属性、继承方式的合理选用
2.1入门知识
保持已有类的特性而构造新类的过程称为继承;
派生
在已有类的基础上新增自己的特性而产生新类的过程称为派生。
❑类的定义和设计为实现数据隐藏、封装提供了途径,但如何有效地创建类
❑如果能在已有的资源基础上设计新类,则能加快编程速度
❑为什么要继承:
新的问题与已解决过的问题有相似性。
❑为什么要派生改造:
新的问题与已解决过的问题又有差异。
实现代码重用,在原有程序的基础上快速编程新的应用。
由于自然界的各个实体(对象)具有自然的相关性,这在纵向上主要体现为继承与派生的特性(参见上述例)。
而C++中的类是对自然界中的实体描述,当然在编程时可以借用实体自然的相关性来组织程序的各个模块,以减少重复编程。
2.2如何实现继承与派生
2.2.1基类和派生类
被继承特性的类称为基类(或父类);而新增特性从而派生出的类称为派生类(或子类)
Point-->Rectangle-->Window-->Dialog-->CommonDialog
【蓝梦提示】
□基类和派生类只是相对的问题层次关系,而不是绝对的问题层次关系
□具体谁是基类和派生类这要取决于所解决的问题层次
2.2.2派生类的定义语法
class派生类名:
继承方式基类名1,继承方式基类名2
{成员定义;
};
如:
classDialog:
publicWindow.其它成员定义
private:
intRadius;
};
2、private私有继承方式则主要着重于实现代码重用,应用它的主要目的是为了继承父类的接口或父类的数据结构、重用基类中的某些成员函数等。
在概念上不必非要具有泛化与特化的子类型关系。
如表单类Table从点Point类继承,程序员的主要目的是要重用基类中的GetX()、GetY()等函数,同时也可以将点的X、Y坐标看成为表格的行列单元格位置,从而可以重用Point类的数据结构。
此时应采用private继承方式,而不宜使用public继承方式。
classTable:
privatePoint.其它成员定义
};
3、protected保护型继承方式则是前二种的折中,即派生类与基类之间有一定的概念相关性但是又不希望派生类的对象访问基类中成员但允许派生类中成员使用基类中成员。
例如点--->圆,具有概念的相关性但是又不希望直接通过圆对象来访问点中成员而只允许圆类中成员访问基类点中成员,不能完全设计为public继承方式,但如定义为private继承方式则表明派生类与基类无任何关系,但从逻辑上来说圆是点的特化。
此时可以采用protected保护型继承方式。
classCircle:
protectedPoint.其它成员定义
private:
intRadius;
};
voidmain()
{CirclemyCircle(10,10,100);
cout<<();..继承的方式基类名n
{成员定义;
};
如:
classMaster:
publicStudent,publicPerson.其它成员定义
private:
intRadius;
};
②多重继承时
派生类构造函数名(基类1的形参定义,...,基类n的形参定义,派生类的形参定义)
:
基类1的构造函数名(形参名),...,基类n的构造函数名(形参名)
{
派生类的成员赋值
}
如:
classMaster:
publicStudent,publicPerson
{public:
Master(char*Name,intID,charDepartment,intage):
Student(Department),
Person(Name,ID)
{MasterAge=age;
}
private:
intMasterAge;
};
③多重继承并且内嵌有对象数据成员时
派生类构造函数名(基类1的形参定义,...,基类n的形参定义,对象数据成员所需参数定义,派生类自己的形参定义):
基类1的构造函数名(形参名),...,基类n的构造函数名(形参名),对象数据成员的初始化
{
派生类的成员赋值
}
classDialog:
publicWindowClass
{public:
Dialog(intX,intY,intH,intW,char*Title):
WindowClass(X,Y,H,W),\
OKButton(X+W/3,Y+2*H/3,20,10),CancelButton(X+2*W/3,Y+2*H/3,20,10)
{TitleText=newchar[100];//申请内存空间
strcpy(TitleText,Title,);
}
~Dialog()//不需显示调用直接基类WindowClass类的析构函数
{
delete[]TitleText;//必须释放所申请的内存空间
}
voidOpenWindow()
{WindowClass:
:
OpenWindow();//调用基类中的OpenWindow()以画出窗口
();//画出按钮
();
outtextxy(X+W/3,Y+H/3,TitleText);//显示出对话框的标题文字
}
voidMoveWindow(intNewX,intNewY)
{CloseWindow();
SetX(NewX);
SetY(NewY);//也可以在基类中将X,Y设置为protected成员
OpenWindow();
}
private:
char*TitleText;
ButtonOKButton,CancelButton;
};
【蓝梦提示】
□一旦基类中定义出构造函数和析构函数,派生类也必需定义出它们,否则全部采用缺省形式。
□派生类只需为它的直接基类定义构造函数的形参并调用它,而不需为它的上基类(爷爷类)定义形参(以区别于虚基类构造函数的设计格式)。
2.3继承时的析构函数
派生类没有继承基类的析构函数,为了实现在派生类的对象删除时能完成指定的任务,必需在派生类中自行定义出自己的析构函数。
2.3.1设计原则
只需要为本层次的类定义出析构函数,不需显示调用直接基类的析构函数(这将由系统隐式地调用)。
2.3.2定义格式
类同于一般的析构函数定义。
类名:
:
~类名()
{
}
2.3.3原因
因为一个类中只有一个析构函数(无重载形式),能由系统准确识别并且自动隐式调用。
2.3.4调用次序
❑构造函数的调用次序---首先基类,后内嵌的对象数据成员,再派生类;
❑而析构函数则相反---首先派生类,后内嵌的对象数据成员,再基类。
classBase
{public:
Base(intb)
{B=b;
cout<<"BaseConstructorCalled"<}
~Base()
{cout<<"BaseDestructorCalled"<}
voidprint()
{cout<
}
private:
intB;
};
classDerived:
publicBase
{public:
Derived(intb,intd):
Base(b)
{D=d;
cout<<"DerivedConstructorCalled"<}
~Derived()
{cout<<"DerivedDestructorCalled"<}
voidprint()
{Base:
:
print();
cout<}
private:
intD;
};
voidmain()
{DerivedObj(1,2);
();
}
程序的输出结果为:
BaseConstructorCalled
DerivedConstructorCalled
1
2
DerivedDestructorCalled
BaseDestructorCalled
【本章总结】
本章主要讲解了继承和派生的特点以及子类和父类之间的访问属性。
需要熟练掌握的是子类继承父类后其构造函数的设计以及他们之间的访问特点。
第3章多重多级继承及虚基类
【本章中的知识要点】
□继承时的二义性产生的时机,同名支配原则
□虚基类及派生类时的构造函数设计规则
【重点难点】
□虚基类的应用,派生类构造函数的设计
3.1多重多级继承时访问二义性
3.1.1访问时的二义性
对某一函数调用时将有多种可能的形式出现。
如:
classWindow
{public:
Window();
Window(intx=0,inty=0,inth=600,intw=800);
};
voidmain()
{WindowwinA;//系统不知将调用那一种形式的构造函数来实现初始化,这是典型的访问时的//二义性问题。
但在多重多级继承时还会产生另一种形式的访问时的二义性问题。
}
3.1.2在多重继承时的二义性
在多重继承时,基类与派生类之间或基类之间出现同名成员时,将出现访问时的二义性。
这可采用类名指定或支配原则来解决访问时的二义性问题。
classPerson
{public:
OutobjEat(Food&obj);//每个人都有“吃”的行为(方法)
};
classTeacher
{public:
OutobjEat(Food&obj);
//每个老师也应该都有“吃”的行为(方法),但与一般的人的吃法有差别
voidTeachMethod(Tool&teachTool);//每个老师都应该有自己的教学方法
};
classTrainTeacher:
publicPerson,publicTeacher
{public:
voidTeachMethod(Tool&teachTool);
};
//每个职业技能培训的老师也都应该有自己的教学方法,但与一般学校的老师的教学方法有//差别,故重写此方法。
voidmain()
{TrainTeacherzhang;
(Computer);
//调用时将采用同名覆盖原则来决定调用TrainTeacher类中的TeachMethod()函数
(apple);//调用时将出现访问时的二义性(到底是执行Person:
:
Eat()还是Teacher:
:
Eat())
}
3.1.3同名支配(覆盖)原则
派生类中的成员名覆盖基类中的同名成员;调用时未强行指明时则为派生类中的同名成员;如访问被覆盖的同名基类成员,应使用基类名加以限定。
voidmain()
{TrainTeacherzhang;
(Computer);
//调用时将采用同名覆盖原则来决定调用TrainTeacher类中的TeachMethod()函数
:
:
TeachMethod(Blackboard);
//调用时由于采用了基类名加以限定,从而调用Teacher类中的TeachMethod()函数
:
:
Eat(apple);//必须采用基类名加以限定否则将出现访问时的二义性
:
:
Eat(apple);//必须采用基类名加以限定否则将出现访问时的二义性
}
3.1.4在多级继承时的二义性
派生类从多个基类继承派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性(类似生物学中的“近亲繁殖”)---这可以通过采用虚基类的机制来解决。
例如:
classA{
public:
voiddis(){cout<<"HelloA"<};
classB:
publicA{
};
classC:
publicA{
};
classD:
publicB,publicC{
};
voidmain(){
Dd;
();//编译器无法判断该使用从B类继承来的dis()还是从C类继承来的dis()。
}
3.2虚基类
在派生类的定义时以virtual加以修饰的基类
3.2.1定义语法
各个虚基类的说明位置无先后次序要求;每个virtual只对其后的基类名起作用,参见上述实例和如下的Circle类的定义。
3.2.2作用
它主要用来解决多重与多级继承时可能发生的对同一基类继承多次而产生访问的二义性问题,为最远的派生类提供一份基类的成员而不重复对它产生多次拷贝,因为此时编译系统将采用优化的编译方式来处理各级派生类的成员。
3.2.3如何判断是否为虚基类的问题
画出多重与多级继承时的关系链,观察是否有从某一个共同的起点出发,经过不同的途径,最后又汇合在一处的结点;此共同的起点(基类)应为虚基类。
3.2.4处理的方法
应该将此共同的起点基类设计为虚基类。
3.2.5带有虚基类的最远的派生类的构造函数
1、特点:
虚基类和一般基类的最大的差别在于派生类的构造函数定义(一般的基类方式继承时只需要初始化其直接基类)。
2、规则:
最远的派生类的构造函数不仅需要分别对它的直接基类初始化,也需要对共同基类(虚基类)初始化并且调用其构造函数。
【实例练习】
#include
#include
usingnamespacestd;
classA
{
public:
A(inti=0){x=i;cout<<"ContructorA"<voiddis(){cout<<"HelloA"<private:
intx;
};
classB:
virtualpublicA
{
public:
B():
A(10){cout<<"ConstrutorB"<};
classC:
virtualpublicA
{
public:
C():
A(32){cout<<"ConstructorC"<};
classD:
publicB,publicC
{
public:
D():
A(100){}
};
voidmain()
{
Dd;
();
}
【蓝梦提示】
□一旦基类中定义出构造函数,派生类也必须定义出自己的构造函数,否则将全部采用缺省形式
【本章总结】
本章主要讲解了多重继承的特点及虚基类的应用。
需要熟练掌握的是在多重继承中派生类构造函数的设计
第4章
多态性和虚函数
【本章中的知识要点】
□多态性(静态及动态)
□赋值兼容原则
□虚函数及纯虚函数和抽象基类等的编程
【重点难点】
□虚函数及纯虚函数和抽象基类应用
4.1多态性
4.1.1多态性
同一名称,不同的功能实现方式(允许在派生类中对原有基类的成员加以重写并重定义出新的功能实现方式);子类的对象共享父类的行为。
classWindow
{public:
voidOpenWindow()
{功能实现为画出窗口;
}
};
classDialog:
publicWindow
{public:
voidOpenWindow()//重写原有基类的成员
{功能实现为画出对话框;
}
};
4.1.2作用