540 likes | 654 Views
第 1 章 C++ 概述 第 2 章 数据类型、运算符和表达式 第 3 章 简单的输入 / 输出 第 4 章 C++ 的流程控制 第 5 章 函数 第 6 章 编译预处理 第 7 章 数组 第 8 章 结构体、共同体和枚举类型 第 9 章 指针和引用 第 10 章 类和对象 第 11 章 类和对象的其他特性 第 12 章 继承和派生 第 13 章 多态性 第 14 章 输入 / 输出流 第 15 章 模板. 目 录. 第一部分 面向过程的程序设计.
E N D
第1章 C++概述 第2章 数据类型、运算符和表达式 第3章 简单的输入/输出 第4章 C++的流程控制 第5章 函数 第6章 编译预处理 第7章 数组 第8章 结构体、共同体和枚举类型 第9章 指针和引用 第10章 类和对象 第11章 类和对象的其他特性 第12章 继承和派生 第13章 多态性 第14章 输入/输出流 第15章 模板 目 录 • 第一部分 面向过程的程序设计 • 第二部分 面向对象的程序设计
10.1 类和对象的定义 10.1.1 从结构体到类 以前,定义结构体类型,描述一个对象 : struct SPerson //只有数据成员 { char name[20]; //姓名 char sex; //性别 int age; //年龄 }; 我们把一个对象可能具有的动作,加入到对象的描述中,就形成了类。
10.1.2 类和对象的定义形式 class <类名> { [ [private:] //私有成员,缺省存取权限 <数据成员及成员函数> ] [ public: //公有成员 <数据成员及成员函数> ] [ protected: //保护成员 <数据成员及成员函数> ] }; 私有成员:只允许类内成员函数存取它 公有成员:允许类内和类外函数存取它 保护成员:允许类内和其派生类函数存取它
#include <iostream.h> #include <string.h> class Person { private:// 此处,private可缺省 char Name[20]; //姓名 char Sex;//性别,三个数据成员 int Age;//年龄 public:// 以下定义了四个成员函数 void SetData(char n[ ], char s, int a) { strcpy(Name, n);//直接访问Name Sex=s;//直接访问Sex 访问特性 Age=a;//直接访问Age } [例10.1] 定义描述一个人的类Person,源程序文件名person.h
void GetName( char *n ) // 函数 { strcpy(n, Name); } char GetSex( ) // 函数 { return Sex; } int GetAge( ) // 函数 { return Age; } };// 注意:类定义结束处的分号不能少
类是一种类型,该类型的变量称为对象(实例)类是一种类型,该类型的变量称为对象(实例) 对象的定义: <类名> <对象列表>; 如:Person a, b ; // 定义a、b两个对象 可以定义对象的指针和对象数组: 如:Person *pa, *pb, x[10]; // pa和pb是两个Person类型指针, x是对象数组 pa=&a; pa=x; pb=&b; pb=x; 那么能否这样访问对象a的成员呢? a.Name, a.Sex, a.Age 回答是:不能!
访问权限 类内访问特性 类外访问特性 10.1.3 对象成员的访问 private 可直接访问 不可直接访问 protected 可直接访问 不可直接访问 public 可直接访问 可直接访问 返回例10.1 表 10-1 类成员的访问特性 Person a, *pa=&a; //类外访问如下, a.name × pa->name × a.SetData(…) √ pa-> SetData(…) √
10.1.4 成员函数的定义 例10.1,类体内定义成员函数。例10.2 ,类体外定义成员函数。 在类体内 进行 成员函数说明 [例10.2]定义Person类,在类体外定义成员函数, #include <iostream.h> 程序文件名为person.h #include <string.h> class Person { char Name[20]; //姓名 //注意缺省 private char Sex; //性别 int Age; //年龄 public: void SetData(char [ ], char , int ); void GetName(char *); char GetSex( ); int GetAge( ); };
限定SetData( )函数 属于Person类 // 在类体外定义成员函数 void Person::SetData(char n[ ], char s, int a) { strcpy(Name, n); Sex=s; Age=a; } void Person::GetName( char *n ) { strcpy(n, Name); }
续 char Person::GetSex( ) { return Sex; } int Person::GetAge( ) { return Age; }
通过对象 访问 公有成员 例10.3 测试Person类 #include "person.h" //包含例10.1或例10.2中的头文件 void main( ) { Person a, *pa; char name[20]; a.SetData("Cheng", 'F', 20); a.GetName(name); cout << "Name: " << name <<endl; cout << " Sex: " << a.GetSex( ) << endl; cout << " Age: " << a.GetAge( ) << endl;
通过指针 访问 公有成员 // Person a, *pa; pa=&a; pa->SetData("Zhang", 'M', 18); pa->GetName(name); cout << "Name: " << name <<endl; cout << " Sex: " << pa->GetSex( ) << endl; cout << " Age: " << pa->GetAge( ) << endl; }
[例10.4] 定义并测试长方形类Crect, 长方形是由左上角坐标(left, top) 和右下角坐标(right, bottom)组成。 (left, top) ● ● (right, bottom) 见 “第10章 类和对象(例子).doc”
a pa→ b pb→ "Cheng" 'F' 20 "Zhang" 'M' 18 10.1.5 对象的存储空间 定义对象后,对象数据成员占用不同的存储空间。 Person a, b, *pa=&a, *pb=&b; a.SetData("Cheng", 'F', 20); b.SetData("Zhang", 'M', 18); 该类的所有对象的同一成员函数, 共享同一代码空间。
10.1.6 定义类和对象的有关说明 1.类中数据成员的类型可以是任意的, 但自身类的对象不可以作为成员。 class Obj { int x; double y; }; class Person { char Name[20]; char Sex; int Age; Obj obj1, obj2; // 可以 Person a, b; // 不可以 Person *pa; // 可以 ...... };
10.1.6 定义类和对象的有关说明(续) 2.若类的使用在前,定义在后,则需引用性说明。 class B; // B类说明 class A // A类的定义 { private: B *pb; // B类对象的指针pb是A类的数据成员 public: ...... }; class B // B类的定义 { ...... };
10.1.6 定义类和对象的有关说明(续) 3.定义对象的三种方法 : (1)类的定义完成后,定义对象 class A // A类的定义 { int x; float y; public: ...... }; A a1, a2, a3; //定义A类对象
10.1.6 定义类和对象的有关说明(续) (2)在定义类的同时定义对象 class A // A类的定义 { int x; float y; public: ...... } a1, a2, a3; //定义A类对象
10.1.6 定义类和对象的有关说明(续) (3)定义无类名的对象 class //无类名 { int x; float y; public: ...... } a1, a2, a3; //定义该类对象 缺点:只能定义一次该类对象, 如果以后需要再次定义该类对象就不可以了。
10.1.6 定义类和对象的有关说明(续) 4.结构体与类的区别: 结构体成员缺省的存取权限是公有的, 而类中成员缺省的存取权限是私有的。 结构体是类的特例。
10.2 初始化对象、撤消对象 “初始化是指:定义对象的同时为其赋初值”。 对结构体类型的对象,可以这样初始化: struct SPerson //定义结构体类型 { char name[20]; //姓名 char sex; //性别 int age; //年龄 }; SPerson p1={ “Cheng”, ‘F’, 20 };//初始化 √
而对“类”类型的对象的私有成员,不可以这样初始化。而对“类”类型的对象的私有成员,不可以这样初始化。 class Person //定义“类”类型 { char name[20]; //姓名 char sex; //性别 int age; //年龄 }; Person p={ “Cheng”, ‘F’, 20 };//初始化 × 原因: 结构体默认的成员访问特性是公有的, 而类的成员的访问特性是私有的。
对“类”类型的对象的公有成员,可以这样初始化。对“类”类型的对象的公有成员,可以这样初始化。 class Person //定义“类”类型 { public: char name[20]; //姓名 char sex; //性别 int age; //年龄 }; Person p={ “Cheng”, ‘F’, 20 };//初始化 √ 可以为“类”定义构造函数用于初始化对象的数据成员, 同时还可以定义析构函数用于撤销该对象时做清理工作。
10.2.1 构造函数和析构函数 1.构造函数 在类体内定义构造函数的一般格式是: ClassName(<形参列表>) // ClassName是类名, { ...... } //在此处作为构造函数名 在类体外定义构造函数的一般格式是: ClassName :: ClassName(<形参列表>) { ...... } 构造函数的调用时机:当产生新对象时,自动调用。 [例10.5] 见 “第10章 类和对象(例子).doc”
构造函数的特点如下: (1) 构造函数是成员函数,函数体可写在类体内, 也可写在类体外。 (2) 构造函数是特殊函数,该函数的名字与类名相同, 不指定返回值类型。 (3) 构造函数可以重载, 即可以定义多个参数个数不同的构造函数。 (4) 在创建对象时,系统自动调用构造函数。
2.析构函数 在类体内定义析构函数的一般格式是: ~ClassName( ) // ClassName是类名 { ...... } // 析构函数名为~ClassName 在类体外定义析构函数的一般格式是: ClassName :: ~ClassName( ) { ...... } 析构函数的调用时机:当撤销对象时,自动调用。 [例10.6]见 “第10章 类和对象(例子).doc”
析构函数的特点如下: (1) 析构函数是成员函数,可在类体内定义, 也可在类体外定义。 (2) 一般地,将析构函数定义成公有成员函数。 (3) 析构函数也是特殊函数,该函数的名字是类名前加 ~, 用来与构造函数区分,该函数不指定返回值类型, 没有参数。 (4) 一个类只能定义一个析构函数, 即析构函数不允许重载。
析构函数的特点如下(续): (5)析构函数可以被显式调用,也可以由系统自动调用。 在下面两种情况下,析构函数会被自动调用: ①当对象是系统自动创建的,则在对象的作用域结束时,系统自动调用析构函数。 ②当一个对象是使用new运算符被动态创建的,在使用delete运算符释放它时,delete将会自动调用析构函数。
3.调用构造函数和析构函数的时机(对象的生存周期) 不同存储类型的对象调用构造函数及析构函数 的时机不同。 (1)全局对象(总是静态的) —— 程序main( )开始执行时, 调用构造函数,程序main( )结束时,调用析构函数。 (2)局部动态对象 —— 当程序执行进入作用域, 在定义对象处系统自动创建对象,调用构造函数; 退出作用域时,系统在撤消对象前调用析构函数。 (3)局部静态对象 —— 当程序执行首次到达定义对象处, 系统自动创建对象,调用构造函数;程序结束时 (即main( )函数执行结束时), 系统在撤消对象前调用析构函数。
(4)动态申请的对象 —— 使用 new 产生对象时, 系统自动调用构造函数;使用 delete 撤消对象时, 系统自动调用析构函数。 [例10.7]见 “第10章 类和对象(例子).doc”
此程序运行结果是: Constructor: 2008.4.4 //调用构造函数,产生d4对象 进入 main( )函数! Constructor: 2008.1.1 // 调用构造函数,产生d1对象 进入 fun( )函数! // 第1次进入fun( )函数,产生下述 d2, d3对象 Constructor: 2008.2.2 Constructor: 2008.3.3 退出 fun( )函数! // 退出fun( )函数,撤消d3对象,不撤消d2对象 Destructor: 2008.3.3
续: 进入 fun( )函数! // 第2次进入fun( )函数,再次产生d3对象 Constructor: 2008.3.3 退出 fun( )函数! Destructor: 2008.3.3 // 退出fun( )函数,撤消d3对象 退出 main( )函数! // 退出main ( )函数,撤消d1, d2, d4对象 Destructor: 2008.1.1 Destructor: 2008.2.2 Destructor: 2008.4.4
10.2.2缺省构造函数和缺省析构函数 1. 缺省构造函数 定义类时,若没有定义构造函数,则编译系统 自动生成一个不带参数的缺省构造函数,格式如下: ClassName:: ClassName( ) { }
[例10.8]定义类时,不定义构造函数 #include <iostream.h> class Date { int Year, Month, Day; public: void ShowDate( ) { cout <<Year<<'.'<<Month<<'.'<<Day<<endl; } }; void main( ) { Date d; d.ShowDate( ); } 原因: 系统自动产生的缺省构造函数是: Date::Date( ) { } //不做任何操作 程序的运行结果是:? -858993460.-858993460.-858993460
缺省构造函数有两种形式: 没有参数的构造函数或各参数均有缺省值的构造函数, 缺省构造函数只能有一个。 Date:: Date( ) //没有参数 { Year=2003; Month=1; Day=1; } 或 Date:: Date(int y=2003, int m=1, int d=1) //均有缺省值 { Year=y; Month=m; Day=d; } 运行结果? void main( ) { Date d; d.ShowDate( ); }
注意: ①在产生对象时,若不需要对数据成员进行初始化,可以不显式地定义缺省构造函数。 ②在一个类的定义中,缺省构造函数只能有一个。 ③若已经定义了构造函数(不论它是否为缺省构造函数),则编译系统不再自动生成缺省构造函数。
格式为: ClassName :: ~ ClassName ( ) { }; 2. 缺省析构函数 若在类中没有显式定义析构函数, 则编译器自动地产生一个缺省的析构函数。 ① 在撤消对象时,若不需要做任何结束工作, 可以不定义析构函数。 ② 当类中有动态申请的数据空间时,需要显式地定义 析构函数,撤销动态数据空间。如:例10.6
格式: //注意参数是本类对象的引用 ClassName :: ClassName( [const]ClassName & Obj ) { <函数体> } 10.2.3 拷贝构造函数和缺省拷贝构造函数 拷贝构造函数:用一个已知的对象来初始化一个 被创建的同类对象。 [例10.9]见 “第10章 类和对象(例子).doc” 如果用户不定义拷贝构造函数,则系统自动产生 一个缺省的拷贝构造函数。如例10.9 : Point::Point(const Point &p) { x=p.x; y=p.y; } //各成员逐一赋值
当类中有动态申请的数据空间时, 必须定义拷贝构造函数,否则出错。 [例10.10]见 “第10章 类和对象(例子).doc” 自动产生的构造函数如下: Student::Student(const Student &s) { Name = s.Name; // 注意:直接赋地址值 Age = s.Age; }
何时必须定义拷贝构造函数呢?分两种情况: (1)若只需拷贝同类型对象的部分数据成员。 (2)或者类中的某个数据成员是使用 new 运算符 动态地申请存储空间进行赋初值时。 ★则必须在类中显式地定义一个完成拷贝功能的 构造函数,以便实现成员数据的复制。 同时应显式定义相应的析构函数,撤消动态分配的空间。
10.2.4 拷贝构造函数的调用时机 在产生新对象时,自动调用拷贝构造函数。 何时产生新对象呢? [例10.11]见 “第10章 类和对象(例子).doc” (1)明确表示由一个对象初始化另一个对象时, 例如:例 10.9 中的A、B两行 Point p3(p1); // A 调用拷贝构造函数 Point p4=p2; // B 调用拷贝构造函数 (2)当用对象作为函数参数时, 系统处理成用实参对象初始化形参对象。 (3)当函数返回对象时, 用返回值对象初始化内存临时对象。
10.2.5 利用构造函数进行类型转换 C++中不同的数据类型可以相互赋值。若定义了复数类对象 Complex c; 能否写 c = 5.0; 即希望将 5.0做为实部,0做为虚部的一个复数赋值给复数 c。 可以!!!自动类型转换 c = Complex(5.0); //利用构造函数进行类型转换 [例10.13]见 “第10章 类和对象(例子).doc”
10.3 成员函数的特性 10.3.1 内联函数和外联函数 在类体内定义的成员函数是内联函数, 在类体外定义的函数是外联函数 在类体外,也可以定义内联函数 [例10.14] 在类体外定义内联函数 class Complex { double Real, Image; public: //在类体内,给出成员函数说明 Complex(double x=0, double y=0); void Show( ); };
//在类体外定义内联函数 inline Complex::Complex(double x, double y) { Real=x; Image=y; } inline void Complex::Show( ) { cout<<'('<<Real<<','<<Image<<')'; } 内联函数的优点: 编译时在调用函数处用内联函数的代码来替换函数调用语句, 这样在程序执行时,不需要函数调用,减少时间开销。
10.3.2 成员函数的重载 构造函数可以重载,一般的成员函数也可以重载。 例10.15处理一个数组构成的线性表,动态产生线性表, 并输出线性表中的数据。 线性表:动态产生的一维整型数组,存放若干个数值, 指针 ListPtr 指向该线性表的第0个元素 。 线性表的长度 nMax 是一维数组的长度, 线性表的元素个数 nElem 是当前已有元素个数, 完成的功能:加入元素、取出元素、给元素重新赋值等。 [例10.15]见 “第10章 类和对象(例子).doc”
10.4 构造函数和对象成员 • 定义一个类时,可以把一个已定义的类的对象作为该类的成员。 • 产生新定义类的对象时,须对它的对象成员进行初始化。 • 只能通过其对象成员的构造函数来实现。 [例10.16]见 “第10章 类和对象(例子).doc” • 先调用对象成员的构造函数,再调用对象自身的构造函数。 • 析构函数的调用顺序相反。
在类的定义中,说明对象成员的一般格式为: class ClassName { ClassName_1 c1; ClassName_2 c2; ...... ClassName_n cn; public: ClassName(args) : c1 (arg_1), c2 (arg_2),...cn (arg_n) // A {......} ...... }; 其中,ClassName_1、ClassName_2、......ClassName_n 是已经定义的类名。
注意: ①args中的形参必须带有类型说明,而 arg_1 、arg_2、......、arg_n 是实参,不需要类型说明,它们可以是来自于args的变量,也可以是常数或表达式等。 ②调用对象成员构造函数的顺序与写在成员初始化列表中的顺序无关,而与对象成员的定义顺序有关,先定义的先调用。如将例10.16中的A行改写为: Line(int x1, int y1, int x2, int y2, int w, int c) : p2(x2, y2), p1(x1, y1)// A 仍然是先调用p1(x1, y1),再调用p2(x2, y2),因为p1的定义在前,p2的定义在后。
10.5 this 指针 this是一个隐含于每一个类的成员函数中的特殊指针, 它指向调用该函数的对象。 当对象调用成员函数时,自动将对象自身的地址(指针) ,传递给成员函数,在成员函数中可直接使用该指针, 指针名为this。 如有一个类: #include <iostream.h> class Sample { int x, y;