《第2章C面向对象程序设计》由会员分享,可在线阅读,更多相关《第2章C面向对象程序设计(80页珍藏版)》请在金锄头文库上搜索。
1、第 2章 C+面向对象程序设计,2 . 1 类和对象,2 . 2 数据共享和成员特性,2 . 3 继承和派生,2 . 4 多态和虚函数,2 . 5 运算符重载,2 . 6 输入/输出流,2 . 1 类和对象,2.1.1 类的定义 C+中定义类的一般格式如下: class private: public: protected: ; ,2.1.1 类的定义,当类的成员函数的函数体在类的外部定义时,必须由作用域运算符“:”来通知编译系统该函数所属的类。例如: class CMeter public: double m_nPercent; / 声明一个公有数据成员 void StepIt(); / 声
2、明一个公有成员函数 void SetPos(int nPos); / 声明一个公有成员函数 int GetPos() return m_nPos; / 声明一个公有成员函数并定义 private: int m_nPos; / 声明一个私有数据成员 ; / 注意分号不能省略 void CMeter:StepIt() m_nPos+; void CMeter:SetPos(int nPos) m_nPos = nPos; ,2.1.2 对象的定义,与结构类型一样,它也有三种定义方式:声明之后定义、声明之时定义和一次性定义。但由于“类”比任何数据类型都要复杂得多,为了提高程序的可读性,真正将“类”当
3、成一个密闭、“封装”的盒子(接口),在程序中应尽量在对象的声明之后定义方式,并按下列格式进行: 一个对象的成员就是该对象的类所定义的数据成员(成员变量)和成员函数。访问对象的成员变量和成员函数和访问变量和函数的方法是一样的,只不过要在成员前面加上对象名和成员运算符“.”,其表示方式如下: . .() 例如: myMeter.m_nPercent, myMeter.SetPos(2), Meters0.StepIt();,2.1.2 对象的定义,若对象是一个指针,则对象的成员访问形式如下: - -() “-”是一个表示成员的运算符,它与“.”运算符的区别是:“-”用来表示指向对象的指针的成员,而
4、“.”用来表示一般对象的成员。,2.1.3 类作用域和成员访问权限,1类名的作用域 如果在类声明之前就需要使用该类名定义对象,则必须用下列格式在使用前进行提前声明(注意,类的这种形式的声明可以在相同作用域中出现多次): class ; 例如: class COne; / 将类COne提前声明 class COne; / 可以声明多次 class CTwo / private: COne a; / 数据成员a是已定义的COne类对象 ; class COne / ;,2.1.3 类作用域和成员访问权限,2类中成员的可见性 (1)在类中使用成员时,成员声明的前后不会影响该成员在类中的使用,这是类作
5、用域的特殊性。例如: class A void f1() f2(); / 调用类中的成员函数f2 coutaendl; / 使用类中的成员变量a void f2() int a; ;,2.1.3 类作用域和成员访问权限,(2)由于类的成员函数可以在类体外定义,因而此时由“类名:”指定开始一直到函数体最后一个花括号为止的范围也是该类作用域的范围。例如: class A void f1(); / ; void A:f1() / 则从A:开始一直到f1函数体最后一个花括号为止的范围都是属于类A的作用域。 (3)在同一个类的作用域中,不管成员具有怎样的访问权限,都可在类作用域中使用,而在类作用域外却不
6、可使用。例如: class A public: int a; / ; a = 10; / 错误,不能在A作用域外直接使用类中的成员,2.1.3 类作用域和成员访问权限,3类外对象成员的可见性 对于访问权限public、private和protected来说,只有在子类中或用对象来访问成员时,它们才会起作用。在用类外对象来访问成员时,只能访问public成员,而对private和protected均不能访问。,2.1.4 构造函数和析构函数,1构造函数 C+规定,一个类的构造函数必须与相应的类同名,它可以带参数,也可以不带参数,与一般的成员函数定义相同,可以重载,也可以有默认的形参值。 例如:
7、class CMeter public: CMeter(int nPos ) / 带参数的构造函数 m_nPos = nPos; / 这样若有: CMeter oMeter(10), oTick(20);,2.1.4 构造函数和析构函数,2对构造函数的几点说明 (1)构造函数的约定使系统在生成类的对象时自动调用。同时,指定对象括号里的参数就是构造函数的实参,例如,oMeter(10)就是oMeter. CMeter(10)。故当构造函数重载及设定构造函数默认形参值时,要避免出现二义。 CPerson(char *str, float h = 170, float w = 130) / A st
8、rcpy(name, str); height = h; weight = w; CPerson(char *str) / B strcpy(name, str); ,2.1.4 构造函数和析构函数,(2)定义的构造函数不能指定其返回值的类型,也不能指定为void 类型。事实上,由于构造函数主要用于对象数据成员的初始化,因而无须返回函数值,也就无须有返回类型。 (3)若要用类定义对象,则构造函数必须是公有型成员函数,否则类无法实例化(即无法定义对象)。若类仅用于派生其他类,则构造函数可定义为保护型成员函数。,2.1.4 构造函数和析构函数,3默认构造函数 实际上,在类定义时,如果没有定义任何构
9、造函数,则编译自动为类隐式生成一个不带任何参数的默认构造函数,由于函数体是空块,因此默认构造函数不进行任何操作,仅仅为了满足对象创建时的语法需要。其形式如下: () 例如,对于CMeter类来说,默认构造函数的形式如下: CMeter( ) / 默认构造函数的形式 默认构造函数的目的是使下列对象定义形式合法: CMeter one; / one.CMeter(); 会自动调用默认构造函数,2.1.4 构造函数和析构函数,4析构函数 与构造函数相对应的是析构函数。析构函数是另一个特殊的C+成员函数,它只是在类名称前面加上一个“”符号(逻辑非),以示与构造函数功能相反。每一个类只有一个析构函数,没
10、有任何参数,也不返回任何值。例如: class CMeter public: / CMeter( ) / 析构函数 / 析构函数只有在下列两种情况下才会被自动调用: (1)当对象定义在一个函数体中,该函数调用结束后,析构函数被自动调用。 (2)用new为对象分配动态内存,当使用delete释放对象时,析构函数被自动调用。,2.1.4 构造函数和析构函数,5应用示例 类的构造函数和析构函数的一个典型应用是在构造函数中用new为指针成员开辟独立的动态内存空间,而在析构函数中用delete释放它们。 【例Ex_Name】 使用构造函数和析构函数 由于“CName one(p);”调用的是B重载构造函
11、数,从而使得私有指针成员strName的指向等于p的指向。而p指向new开辟的内存空间,其内容为“DING”,一旦p指向的内存空间删除后,p的指向就变得不确定了,此时strName指向也不确定,所以此时运行结果为: 葺葺葺葺?,2.1.4 构造函数和析构函数,显然,输出的是一个无效的字符串。因此,为了保证类的封装性,类中的指针成员所指向的内存空间必须在类中自行独立开辟和释放。因此,类CName应改成下列代码。 这样,主函数中的代码才会有正确的运行结果: DING,2.1.5 对象赋值和拷贝,1赋值 在C+中,一个类的对象的初值设定可以有多种形式。例如,对于前面的类CName来说,则可有下列对象
12、的定义方式: CName o1; / 通过A显式默认构造函数设定初值 CName o2(“DING“); / 通过B重载构造函数设定初值 等都是合法有效的。但是若有: o1 = o2; / 通过赋值语句设定初值 C+还常用下列形式的初始化来将另一个对象作为对象的初值: () 例如: CName o2(“DING“); / A:通过构造函数设定初值 CName o3(o2); / B:通过指定对象设定初值,2.1.5 对象赋值和拷贝,每一个类总有一个默认拷贝构造函数,其目的是保证B语句中对象初始化形式的合法性,其功能就等价于“CName o3 = o2;”。但语句“CName o3(o2);”与
13、语句“o1 = o2;”一样,也会出现程序终止的情况,其原因和“o1 = o2;”的原因一样。但是,若有类CData: class CData public: CData( int data = 0) m_nData = data; CData() int getData() return m_nData; private: int m_nData; ;,2.1.5 对象赋值和拷贝,则下列初始化形式却都是合法有效的: CData a(3); / 通过重载构造函数设定初值 CData b(a); / 通过默认拷贝构造函数设定初值 / 等价于 CData b = a; cout a.getData
14、()endl; / 输出 3 cout b.getData()endl; / 输出 3,2.1.5 对象赋值和拷贝,2浅拷贝和深拷贝 每一个C+类都有一个隐式的默认拷贝构造函数,其目的是保证对象初始化方式的合法性,其功能是将一个已定义的对象所在的内存空间的内容依次拷贝到被初始化对象的内存空间中。这种仅仅将内存空间的内容拷贝的方式称为浅拷贝。 3深拷贝构造函数 拷贝构造函数是一种比较特殊的构造函数,除遵循构造函数的声明和实现规则外,还应按下列格式进行定义。 (参数表) 对于CName的拷贝构造函数,可有下列合法的函数原型: CName( CName ,2.1.5 对象赋值和拷贝,【例Ex_Cop
15、yCon】 使用拷贝构造函数 程序运行结果如下: DING DING YOU HE,2.1.6 对象成员的初始化,为提高对象初始化效率,增强程序的可读性,C+允许在构造函数的函数头后面跟一个由冒号“:”来引导的对象成员初始化列表,列表中包含类中对象成员或数据成员的拷贝初始化代码,各对象初始化之间用逗号分隔,如下列格式: :(形参表):对象1(参数表), 对象2(参数表), 对象n(参数表) ,对象成员初始化列表,2.1.6 对象成员的初始化,先来看看第一种方式(函数构造方式),例如。 但若使用第二种方式(对象成员列表方式),即使用由冒号“:”来引导的对象成员初始化列表的形式,如下面的代码: class CPoint / ; class CRect public: CRect( int x1, int y1, int x2, int y2) : m_ptLT(x1, y1), m_ptRB(x2, y2) private: CPoint m_ptLT, m_ptRB; ; int main() CRect rc(10, 100, 80, 250); return 0; ,2 . 2 数据共享和成员特性,1静态数据成员 与静态变量相似,静态数据成员是静态存储(static)的,但定义一个静