1、知识提要v了解面向对象程序设计的概念v掌握类的定义及使用v掌握构造函数及析构函数的功能及定义v了解拷贝构造函数、深浅拷贝v掌握类中特殊成员的使用方法v了解友元函数及友元类p 13.1 面向对象程序设计v1.面向对象与面向过程的区别v2.面向对象的基本概念对象类抽象p 23.2 类的定义v类的定义形式p 3class 类名 /类的名字,任意合法的标识符,建议首字母大写成员访问控制符:/描述成员的访问权限,缺省为private数据成员;/描述类的属性或特征成员访问控制符:成员函数;/描述类的行为,也称为类的方法;/类声明的末尾以分号结束public(公有访问权限)private(私有访问权限)pr
2、otected(受保护访问权限)3.2 类的定义v例如,类Student定义如下:p 4class Studentpublic:/公有访问权限int GetAge()const;/GetAge()方法获得学生的年龄double AvgScore();/AvgScore()方法求学生的平均成绩private:/私有访问权限string strName;/描述学生的姓名int nAge;/描述学生的年龄double arrScore4;/4个元素的数组,分别表示4门课的 考试成绩;3.2 类的定义vC+还提供了另外一种类的定义格式:用关键字struct替换class。如,v用struct定义的类与
3、class定义类的区别是,如果在struct定义类时省略访问权限限定符,则默认的访问权限是public。p 5struct Timepublic:void SetTime();void GetTime();private:int hour,minute,second;成员的访问控制v访问控制符有三个:publicprivateprotectedp 6成员的访问控制v1.public用public修饰的成员是公有成员,具有与类外交互的能力公有类成员可以直接在类的外部访问,公有成员是不隐藏的v2.private用private修饰的成员称为私有成员,只能在类的成员函数或类的友元函数中访问,不能在类
4、的外部直接访问实现了面向对象程序设计的一个重要思想:数据封装。v3.protected用protected修饰的成员称为受保护的成员与私有成员一样,受保护的成员也只能在类的成员函数或友元函数中访问,不能在类的外部直接访问。p 7数据的封装v封装(encapsulation)是面向对象程序设计最重要的特征之一,它是指将数据和处理数据的函数封装成一个整体,以实现独立性很强的模块,避免外界直接访问对象属性而造成耦合度过高及过度依赖,同时也阻止了外界对对象内部数据的修改而可能引发的不可预知的错误。p 8成员函数的实现v成员函数的功能是通过程序代码实现的。v实现成员函数就是定义成员函数,编写代码实现相应
5、的功能。v成员函数的实现有两种方法:在类的定义中实现在类外实现p 9成员函数的实现v1.在类中实现成员函数通常,功能简单,代码较少的成员函数可以在在类内实现在类内实现的、代码简单的成员函数将自动作为内联函数使用。p 10class Studentpublic:int GetAge()const /GetAge()方法获得学生的年龄return nAge;double AvgScore();/AvgScore()方法求学生的平均成绩private:string strName;/描述学生的姓名int nAge;/描述学生的年龄double arrScore4;/存储4门课的考试成绩;成员函数的实
6、现v2.在类外实现成员函数如果成员函数的功能复杂,代码较多,则强烈建议在类外实现。在类外实现成员函数时需要在函数名前加类名及作用域解析运算符“:”,表示该函数属于哪个类。如果愿意,也可以把类外实现的成员函数声明为内联函数,只需要在函数首部的最前面加关键词inline即可。p 11double Student:AvgScore()double sumScore=0;for(int i=0;i 4;i+)sumScore+=arrScorei;return sumScore/4;3.3使用类v类是一种自定义数据类型,类一旦声明就可以像使用内置数据类型一样使用它。vC+的目标是使得使用类与使用基本的
7、内置数据类型尽可能相同。p 12创建对象v声明对象的基本格式p 13类名 对象列表;Student s1,s2;/实例化对象s1,s2创建对象v当实例化对象时,编译器需要为对象分配内存空间,存储对象的成员。v当为对象分配内存空间时,每个对象都分配独立的内存空间存储数据成员值。v所有对象的成员函数的代码都存储在同一段内存空间中。不同的对象调用同名成员函数时,实际上调用的是同一段内存代码。例3-1p 14访问对象成员v访问数据成员的语法格式v访问对象的成员函数的语法格式v在类的定义中为每个成员指定了相应的访问权限(默认访问控制符是private)。这些访问控制符限定了成员访问的“正确”位置。p 1
8、5对象名.数据成员名;对象名.成员函数名();s1.nAge /对象s1的数据成员nAgeGetAge()/调用对象s1的成员函数int main()Student s1,s2;cout s1.nAge endl;/语法错误,私有数据成员不能在类外访问cout s1.GetAge()nAge;3.4 构造函数v构造函数可以完成声明对象时为对象进行初始化的任务。v构造函数是类的一种特殊成员函数,构造函数在对象创建时被自动调用,实例化一个对象。v如果没有为类显式地提供构造函数,编译器会自动提供一个默认构造函数。p 183.4 构造函数v构造函数的格式要求:构造函数名与类名相同;构造函数没有返回值类
9、型声明;构造函数中没有return语句;p 19默认构造函数v编译器提供的默认构造函数的定义形式如下v如,类Student的默认构造函数为p 20类名:构造函数名()Student:Student()自定义无参构造函数v例3-3p 21自定义带参数的构造函数v创建对象时可以为对象传递参数,为对象的数据成员赋值,以完成对象的初始化工作。例如,v为此,需要为类Student类定义带有两个参数的构造函数。构造函数声明如下:v例3-4p 22Student s(Jason,18);Student(const string&,int);Student:Student(const string&name,
10、int age)strName=name;nAge=age;自定义带参数的构造函数v还可以用参数初始化表的形式为数据成员初始化。v基本格式:v例如,类Student的带参数的构造函数也可以用以下代码实现:p 23类名:构造函数名(参数1,参数2,参数n):数据成员1(参数1),数据成员2(参数2),数据成员n(参数n)函数体;Student:Student(const string&name,int age):strName(name),nAge(age)自定义带参数的构造函数v值得注意的是:一旦为类定义了构造函数,编译器将不再为类提供默认构造函数。v所以,为类Student定义了带参数的构造
11、函数之后,以下对象的声明是错误的:v解决上述问题的方法是再为类显式地定义不带参数的构造函数,如p 24Student s;/语法错误error:no matching function for call to Student:Student()Student:Student()自定义带参数的构造函数v可以根据需要定义多个重载的构造函数。例如,以下语句声明三个Student对象:v想要保证这条语句通过编译,就需要定义三个构造函数,它们的函数原型如下:p 25Student s1(Jason,18),s2(Jason),s3;Student:Student(const string&,int);S
12、tudent:Student(const string&);Student:Student();委托构造函数v为类Student定义多个构造函数v重载多个构造函数时代码冗余量会很大。p 26Student:Student(const string&name,int age):strName(name),nAge(age)Student:Student(const string&name):strName(name),nAge(19)委托构造函数vC+11标准引用了委托构造函数(delegating constructor),有时也称为转发构造函数(forwarding constructor)
13、,扩展了构造函数初始化数据成员的功能。v首先定义一个接收2个参数的构造函数:v然后,定义另一个构造函数时通过初始化表的形式调用已经定义的构造函数,即把数据成员初始化的工作“委托”给另一个构造函数:p 27Student:Student(const string&name,int age):strName(name),nAge(age)Student:Student(const string&name):Student(name,19)委托构造函数v使用委托构造函数时要注意以下两点:(1)不能同时用委托方式和显式初始化的方式为同一个数据成员赋值(2)委托构造函数不能在函数体中调用目标构造函数。p
14、 28class Xpublic:X(int a):data1(a)X():X(1)/正确,委托构造函数X(int)为data1赋值为1 X():data1(2)/正确,使用显式初始化的方式为data1赋值 X():X(1),data1(2)/错误,同时使用委托方式为data1赋值为1、显式初始化的方式为data1赋值为2private:int data1;上述错误提示:mem-initializer for X:data1 follows constructor delegation。class Xpublic:X(int a):data1(a)X()X(1);/数据成员data1的值不能初
15、始化为1private:int data1;含有对象成员的构造函数v如果类中的数据成员是另外一个类的对象,称这种成员为对象成员。v例如,类Date声明如下:v类Student中有一个Date类型的数据成员iBirthday,类Student声明如下v类Student的构造函数代码如下:p 29class Datepublic:Date(int y,int m,int d):nYear(y),nMonth(m),nDay(d)private:int nYear,nMonth,nDay;class Studentpublic:Student(string,int,int,int);/构造函数声明p
16、rivate:string strName;/描述学生的姓名Date iBirthday;/描述学生的年龄;Student:Student(const string&name,int year,int month,int day):strName(name),iBirthday(year,month,day)含有对象成员的构造函数v例3-5v访问成员对象的数据成员的方法是:对象名.成员对象名.数据成员名;v如,s1.iBirthday.nYear;p 30默认参数的构造函数v带默认参数的构造函数的实现形式如下:p 31类名:构造函数名(参数1=默认值1,参数2=默认值2,.,参数n=默认值n)
17、函数体;Student(const string&=,int=19);/构造函数声明Student:Student(const string&name,int age):strName(name),nAge(age)/构造函数实现默认参数的构造函数v使用默认参数的构造函数时要注意以下几点问题:(1)默认参数值在函数声明时指定,如果只有函数定义没有函数声明,则可以在定义函数时为参数指定默认值;(2)一旦为一个形参指定了默认值,这个参数右侧所有形参都必须指定默认值;(3)当重载了多个构造函数时,要避免调用的二义性;特别注意,不要被默认构造函数的隐式形式所误导。例如v例3-6p 32Student
18、s1(Jason,18);/正确,提供了实参值,声明对象s1Student s2;/正确,使用默认参数,声明对象s2Student s3;/正确,使用默认参数,声明对象s3。Student s4();/注意:该语句没有任何语法错误,但是它函数声明,声明返回值为Student类型的函数s4,不是声明对象s4。默认参数的构造函数v可以根据需要只为构造函数的部分参数提供默认值,如v构造函数也可以显式地调用,尤其是用new运算符生成一个对象时需要显式地调用构造函数v实例化对象时可以使用列表初始化方式为对象的数据成员赋初值。如,p 33Student(const string&,int=19);/构造函
19、数声明Student:Student(const string&name,int age):strName(name),nAge(age)/构造函数实现Student s1=Student(Jason,18);Student*pStu=new Student(Ailsa,20);Student*pStu=new Student;Student s1Jason,18;Student s2=Jason,18;Student*pStu=new StudentJason,18;3.5 析构函数v当对象的生命期结束时,编译器会自动调用析构函数撤消该对象。v析构函数也是类的一个特殊的成员函数,析构函数的定
20、义格式要求如下。析构函数名很特殊:在构造函数名前加“”;析构函数没有返回值类型声明,也没有return语句;析构函数没有参数,不能重载。p 343.5 析构函数v如果没有为类定义析构函数,编译器自动为类提供一个默认析构函数,这种默认的析构函数不执行任何操作。v如果在对象消亡之前希望程序做一些事情,比如清理资源、计算当前活跃对象的数目等,程序员需要显式地定义析构函数,以完成相关的操作。v如果创建对象时用new运算符为对象的数据成员申请了内存空间,当对象消亡时一定要用delete运算符回收相应的内存空间,否则将造成内存的泄露。v例3-7p 353.6 拷贝构造函数v如果已经声明了Student类型
21、的对象s1,当声明Student类型的对象s2时可以用s1为s2初始化:v当用一个对象为另一个对象赋值时,编译器将调用拷贝构造函数完成对象的赋值操作。p 36Student s1;Student s2s1;/或者写成Student s2(s1)、Student s2=s1;Student s1(Jason,18);/创建对象s1 Student s2(s1);/调用拷贝构造函数3.6 拷贝构造函数v如果没有为类自定义拷贝构造函数,编译器将为类提供默认的拷贝构造函数完成对象的赋值v有时,默认拷贝构造函数并不能完全胜任对象的拷贝工作,比如当用new运算符为对象的数据成员申请内存资源时,默认拷贝构造
22、函数往往会导致一些错误发生v拷贝构造函数的形式参数必须是类对象的引用,最好是常引用。p 37拷贝构造函数的触发时机v1.使用一个对象初始化另一个对象时。例如,v2.函数调用时,用对象作为实参传递给函数形参时。例如,v3.函数返回值是类对象,创建临时对象作为返回值时。例如,v例3-8p 38Student s1;/声明对象s1Student s2(s1);/声明对象s2,用s1初始化s2Student s1;AvgScore(s1);Student LargerAge(Student s);/参数是Student类对象,函数返回值是Student类对象s3=s1.LargerAge(s2);深拷
23、贝与浅拷贝v让我们先看一个程序,看看该程序到底发生了什么,导致这样的结果的原因是什么,如何解决这个问题。v例3-9v例3-10p 393.7对象数组v声明对象数组的基本语法格式v创建未被显式初始化的对象数组时,编译器会调用默认构造函数实例化这些对象。v如果声明数组时为数组元素赋初值,需要调用构造函数初始化数组元素。如p 40类名数组名常量表达式;Student arrStu10;/声明具有10个Student类型的对象数组arrStuStudent arrStu5=Jason,18,Kevin,19,Cora,18,Alisa,20,Eva,18;3.7对象数组v数组arrStu的声明也可以写
24、成如下形式:v这种数组的定义需要结合拷贝构造函数,并且拷贝构造函数的参数是Student类对象的常引用v也可以只对部分元素初始化。如,v例3-11p 41Student arrStu5=Student(Jason,18),Student(Kevin,19),Student(Cora,18),Student(Alisa,20),Student(Eva,18);Student arrStu5=Student(Jason,18),Student(Kevin,19),Student(Cora,18);3.8 数据共享v数据共享的意义所有对象的某个数据成员的值都是相同的如果更新了某个对象的该数据成员的值
25、,其他对象的该数据成员的值也相应地更新v数据共享的方法静态数据成员p 42静态数据成员v用static关键字声明的数据成员可以被类的所有对象共享。v静态数据成员是描述类的所有对象共同特征的数据成员,对于不同的类对象来说,静态数据成员的值是相同的。p 43静态数据成员v1.静态数据成员的声明格式v2.静态数据成员的初始化p 44static 类型说明符 对象名;如,static string teacherName;类型说明符类名:静态数据成员=初值;如,string Student:teacherName=Kevin;静态数据成员v3.静态数据成员的访问v注意:在多线程程序中,需要为静态数据成
26、员添加某种锁或设置访问规则,以避免出现资源竞争(race condition)。p 45类名:静态数据成员名;如,cout Student:teacherName endl;静态成员函数v基本格式v在类外实现静态成员函数时不需要添加static关键字v同静态数据成员一样,静态成员函数也属于整个类,由类的所有对象共享,通常用类名和域限定符“:”对静态成员函数进行调用。p 46static 类型说明符函数名(参数列表);如,static int GetStuCnt();静态成员函数v例3-12v设计静态成员函数不是为了在对象之间传递消息,仅仅是为了处理静态数据成员。v在静态成员函数中通常只访问静态
27、数据成员或其它静态成员函数。v静态成员函数没有this指针,因此不能在静态成员函数中对非静态数据成员进行默认访问。p 473.9 数据保护v关键字const也用于声明类中数据成员或成员函数,其目的是保护类中的数据不被更新。v常数据成员v静态常数据成员v常成员函数v常对象p 48常数据成员v用const声明常数据成员的格式是:v常数据成员必须通过构造函数的参数初始化表的形式进行初始化,不能在构造函数的函数体内用赋值语句初始化。v例3-13p 49const 类型说明符 数据成员;/或者类型说明符 const 数据成员;静态常数据成员v如果类的所有对象共享某个数据成员,且该成员的值不允许变化,则可
28、以把数据成员声明为静态常数据成员。vC+规定,如果类的静态常数据成员是整型或枚举型,则可以直接在类定义中为其指定初值。如,p 50class Circlepublic:Circle();private:static const double PI;/PI是静态常数据成员;const double Circle:PI=3.14;/在类外初始化class Aprivate:static const int x=1;/直接在类内初始化静态常数据成员;常成员函数v声明格式v常成员函数用于保护对象的数据成员不被修改。如果不希望在函数内部改变数据成员的值,最好把该函数定义为常成员函数,这是一个好习惯,能够
29、提高程序的质量。p 51类型说明符函数名(参数列表)const;常成员函数v使用常成员函数时要注意以下几点问题。1.const关键字是常成员函数类型的组成部分,在类中声明和类外实现常成员函数时都需要用const关键字修饰。2.在常成员函数内部不能调用非常成员函数。3.常对象只能调用常成员函数。4.const关键字可以区分函数的重载。如,5.常成员函数只保证不修改类的数据成员的值,但是可以修改其它与当前类无关的数据。p 52int getAge();int getAge()const;常对象vconst关键字也可以说明类对象,如,v常对象只能调用常成员函数。v例3-14p 53const Stu
30、dent s;3.10 类的友元v封装与数据隐藏是面向对象程序设计中一个重要的编程思想。如私有成员只能在类的成员函数中才能访问,提高了数据的安全性。vC+为类提供了一种“友元”机制,使非类的成员函数访问类的私有数据成员,方便了代码的编写、提高数据访问的效率。v类的友元可以是一个函数(友元函数),也可以是一个类(友元类)p 54友元函数v友元函数的声明形式v例3-15v一个类的成员函数也可以声明为另一个类的友元函数,直接使用类的私有数据成员。v例3-16p 55friend 类型说明符友元函数名(参数列表);友元类v如果类A声明为类B的友元类,那么类A的所有成员函数都自动成为类B的友元函数。v类
31、A声明为类B的友元类的形式如下:p 56class B.;/类B的成员声明friend class A;/声明A为B的友元.;友元类v如果类A的定义在类B之后,则需要对类A进行提前声明。v友元关系是单向的v友无关系不具有传递性。v友元可以实现数据共享,但是在一定程序上也破坏了类的封装性。所以,除非使用友元能够极大地提高效率,一般情况下不提倡使用友元。p 57小结v封装性是OOP的三大特性之一,它很好地保护了数据。v通常用私有数据成员存储信息,而公有成员函数是访问数据的唯一途径。v类是用户自定义的数据类型,用类声明对象就是用自定义类型声明变量。变量初始化通过构造函数实现。p 58小结v对象消亡时要记得回收对象占用的资源,这是析构函数的任务。v构造函数及析构函数通常定义为public属性,当然也可以定义为private属性。这样做有特定的应用场景,但也非常复杂。v友元中可以更方便地访问类的私有成员,但这也破坏了类的数据封装性。除非不得已,尽量不使用友元。p 59