第9讲 类与对象 9.1 面向对象程序设计的基本概念 9.2 对象与类的定义 9.3 内联函数 9.4 对象的使用
学习目标 理解面向对象程序设计的概念和特点; 熟练掌握类和对象的定义和使用; 掌握类中不同类型成员数据和成员函数的定义 和使用权限; 掌握类中不同类型成员数据和成员函数的定义 和使用权限; 掌握调用类中不同类型数据的方法
第8讲 类与对象 9.1 面向对象程序设计的基本概念 9.2 对象与类的定义 9.3 内联函数 9.4 对象的使用
面向对象技术基本概念 传统的软件开发技术(如结构化技术)是采用自顶向下的思想指导程序设计,即将目标划分为若干子目标,子目标再进一步划分下去,直到目标能被程序设计实现为止。 面向对象技术根据现实生活中的具体实体,将程序的实现分解为一个一个具体对象的实现,这样的程序设计更符合人们的思维方式。 面向对象设计方法追求的是现实问题空间与软件系统解空间的近似和直接模拟。它希望用户用最小的气力,最大限度地利用软件系统来求解问题。
面向对象技术基本概念
面向过程设计 面向对象设计 莱特兄弟 飞行者1号 试飞 航空理论 引擎 机翼 机械结构 控制装置 动力系统 试飞 空中客车 总装—法国 发动机—英国 起落架—英国 刹车系统—西班牙 机翼—法国 机舱材料—意大利 控制系统—德国
面向对象设计方法的基本特点 模块性 封装功能 代码共享 对象是一个功能和数据独立的单元,互相之间只能通过 对象认可的途径进行通信。 对象是一个功能和数据独立的单元,互相之间只能通过 对象认可的途径进行通信。 封装功能 用户不必清楚内部细节,只要了解其功能描述就可以使 用。 代码共享 继承性提供了一种代码共享的手段,可以避免重复的代 码设计。
面向对象设计方法的基本特点 灵活性 易维护性 增量型设计 对象的功能执行是在消息传递时确定的,支持对象的主 体特征,对象可以根据自身特点进行功能实现。 易维护性 对象实现了抽象和封装,使其中可能出现的错误限制在 自身,不会向外传播,易于检错和修改。 增量型设计 面向对象系统可以通过继承机制不断扩充功能,而不影 响原有软件的运行。
面向对象程序设计 面向过程 面向对象 面向对象程序设计关键机制—类与对象 数据与操作的分离 可重用性差 维护代价高 数据和操作的封装 可重用性极大提高 维护代价降低
面向对象程序设计 面向对象程序设计的核心 类与对象促进了编程技术的飞跃 设计所需的类和对象 考虑怎样调用对象 把什么数据和函数封装 如何向对象发送消息 类与对象促进了编程技术的飞跃 面向过程程序设计 面向对象程序设计 类=算法+数据结构 程序=算法+数据结构 程序=(对象+对象+…)+ 消息
第8讲 类与对象 9.1 面向对象程序设计的基本概念 9.2 对象与类的定义 9.3 内联函数 9.4 对象的使用
对象 对象 是描述客观事物的实体 是构成系统的基本单位 能根据外界信息进行相应的操作 具有静态的属性和动态的行为
对象举例 按钮对象: 按钮的内容: 大小,按钮的字体、图案等,称为属性。 针对按钮的各种操作: 创建、单击、双击、拖动等,称为行为。
对象 如所属院系、专 业、人数等 静态属性 班级对象 如上课、开会、 体育比赛等 动态行为 抽象的对象 VC程序中的对象 静态属性 成员数据 1 静态属性 班级对象 如上课、开会、 体育比赛等 2 动态行为 抽象的对象 VC程序中的对象 静态属性 成员数据 动态行为 成员函数
类 类(Class) 本质是一种复杂的数据类型 将不同类型的数据和与这些数据相关的操作封装在 一起 类代表了某一批对象的共性和特征
类的封装 类是把多种特征的数据和函数封装成一个整体。 封装是面向对象编程思想中的重要特性之一,其 作用在于定义对象和操作,只提供抽象的接口, 并隐藏其具体实现。封装的结果是把有相似属性 (成员变量)、操作(成员函数)的事物绑在一 起处理(即形成一个类)。 例如,为了方便管理公司职工,可以给职工写一 个类,该类的属性可以有年龄、性别、姓名、入 职日期等,操作可以有开除、调动等,这就实现 了封装。
类的举例
类 类与对象的关系 类是对象的抽象 对象是类的实例 1 计算机1班 班级类 信息工程2班 2
类的定义 类的定义格式: class 类名 { public: 公有成员数据; 公有成员函数; private: 私有成员数据;私有成员函数; protected: 保护成员数据;保护成员函数; }; 关键字 类名 公有 能在类内和类外调用 私有 仅能在本类中调用 保护 仅能在本类及该类的派生类中调用
例: 定义一个学生类: 公有成员数据 公有成员函数 私有成员数据 私有成员函数 保护成员数据 保护成员函数 class Student {public : float average; void SetName(char *name); void SetMath(float math); private : char Name[20]; float Math; float calcul(float score); protected: float sum; void SetChinese(float ch); float GetAverage(void); }; 公有成员数据 公有成员函数 私有成员数据 私有成员函数 保护成员数据 保护成员函数
类的定义 用关键字private限定的成员称为私有成员,对私有成 员限定在该类的内部使用,即只允许该类中的成员函 数使用私有的成员数据,对于私有的成员函数,只能 被该类内的成员函数调用;类就相当于私有成员的作 用域。 用关键字public限定的成员称为公有成员,公有成员 的数据或函数不受类的限制,可以在类内或类外自由 使用;对类而言是透明的。 而用关键字protected所限定的成员称为保护成员,只 允许在类内及该类的派生类中使用保护的数据或函数。 即保护成员的作用域是该类及该类的派生类。
类的定义 私有数据 公有数据 保护数据 类内函数 类外函数 可以调用 可以调用 可以调用 不可调用 可以调用 不可调用 私有函数 公有函数 保护函数 类内函数 类外函数 可以调用 可以调用 可以调用 不可调用 可以调用 不可调用
注意 每一个限制词(private等)在类体中可使用多次。 一旦使用了限制词,该限制词一直有效,直到 下一个限制词开始为止。
class Student { char Name[20]; float Math; float Chiese; public : float average; void SetName(char *name); void SetMath(float math); void SetChinese(float ch); float GetAverage(void); }; 均为私有权限 均为公有权限
成员函数与成员数据的定义不分先后,可以先 说明函数原型,再在类体外定义函数体。 成员函数与成员数据的定义不分先后,可以先 说明函数原型,再在类体外定义函数体。 class A { float x, y; public: void Setxy(float a,float b) { x=a; y=b; } void Print(void) { cout<<x<<‘\t’<<y<<endl; } }; 在类体内定义成员函数
class A { float x, y; public: void Setxy(float a,float b); void Print(void); }; 在类体内说明成员函数原型 void A::Setxy(float a,float b) { x=a; y=b; } void A::Print(void) { cout<<x<<‘\t’<<y<<endl; } 在类体外定义成员函数
void A::Setxy(float a, float b) { x=a; y=b; } 在类体外定义成员函数的格式: <type> < class_name > :: < func_name > (<参数表>) { ...... //函数体 } 类名 函数类型 函数名 形参列表 void A::Setxy(float a, float b) { x=a; y=b; } 函数体
定义类时应注意: 类具有封装性,并且类只是定义了一种结构(样 板),所以类中的任何成员数据均不能使用关键 字extern,auto或register限定其存储类型。 在定义类时,只是定义了一种导出的数据类型, 并不为类分配存储空间,所以,在定义类中的数 据成员时,不能对其初始化。如: class Test { int x=5,y=6; extern float x; }; //是不允许的 //是不允许的
思考: 类与结构体有什么相同点和不同点? struct student { int num; char name[20]; char sex; int age; float score; }; class student { private: int num; char name[20]; public: get_score() {…}; print_score(){…} }; 结构体 类 网络课程平台http://course.cn/G2S/Template/View.aspx?action=view&courseType=0&courseId=2272
类与结构体的比较 在C++语言中,结构体类型只是类的一个特 例。 结构体类型与类最主要的区别在于: 在类中,其成员的缺省的存取权限是私有的; 而在结构体类型中,其成员缺省的存取权限是 公有的。
第8讲 类与对象 9.1 面向对象程序设计的基本概念 9.2 类的定义 9.3 内联函数 9.4 对象的使用
内联函数的引出 为什么需要内联函数 函数调用是将程序的执行顺序转移到函数存放在内存中的某个地址,在函数执行完毕后,在返回到调用该函数的位置。这种转移操作要求转移前保护现场并记住转移点的地址,返回值还要求保护现场,并按原来保存的地址继续执行主程序。 因此,函数调用需要一定的时间和空间的开销,将影响程序的执行效率。 对于一些函数体代码较少,但又频繁被调用的函数,解决其效率问题更为重要。 引入内联函数的目的是为了解决函数调用的效率问题。
内联函数 内联函数是一类特殊的函数 内联函数在调用时不是像一般的函数那样要转去执行被调用函数的函数体,执行完成后再转回调用函数中,执行其后语句,而是在调用函数处用内联函数体的代码来替换,这样将会节省调用开销,提高运行速度。 内联函数与前面讲过的带参数的宏的代码效率是一样的,但是内联函数要优于宏,因为内联函数遵循函数的类型和作用域规则,它与一般函数更相近,在一些编译器中,一旦遇上内联扩展,将与一般函数一样进行调用,调试比较方便。
内联成员函数 当我们定义一个类时,可以在类中直接定义函数 体。这时成员函数在编译时是作为内联函数来实 现的。 当我们定义一个类时,可以在类中直接定义函数 体。这时成员函数在编译时是作为内联函数来实 现的。 当类中函数体放在类外定义的时候,函数为普通 的非内联函数。 若要将类体外定义的普通成员函数变为内联成员 函数,需要在类外定义的成员函数前面加上关键 字inline。
内联函数 说明该成员函数为内联 class A { float x, y; public: void Setxy(float a,float b); void Print(void); }; 说明该成员函数为内联 inline void A::Setxy(float a,float b) { x=a; y=b; } inline void A::Print(void) { cout<<x<<‘\t’<<y<<endl; }
内联函数的几点说明 内联函数的限制: 在内联函数中,不能含有复杂的控制语句,例如开 关、循环语句等 内联函数的函数体不宜过大 在内联函数中,不能含有复杂的控制语句,例如开 关、循环语句等 内联函数的函数体不宜过大 递归函数不能被说明为内联函数
第8讲 类与对象 9.1 面向对象程序设计的基本概念 9.2 类的定义 9.3 内联函数 9.4 对象的使用
对象 定义了一个类后,不能对类的成员进行操作。要 使用类必须先声明类的对象。 类的对象是具有该类类型的某一实体。 定义了一个类后,不能对类的成员进行操作。要 使用类必须先声明类的对象。 类的对象是具有该类类型的某一实体。 如果将类看作是自定义的类型,那么类的对象可 看成是该类型的变量。
对象的声明 例: 类的对象的声明格式: <类名> <对象名表> Time t1, t2, *t3, t4[10]; 普通对象 对象指针 对象数组
对象的使用 对象是类的实例化。在声明一个类时,并 没有为其分配内存空间,只有在实例化一 个对象时,才为对象分配空间。声明一个 对象和声明一个整型变量类似。 一个对象的成员就是该对象的类所定义的 成员,有成员数据和成员函数,引用时同 结构体变量类似,用“.”运算符。
访问对象公有成员 例: 例: 访问对象的公有成员数据: <对象名> .<公有成员数据> t1.math; 访问对象的公有成员函数: <对象名> .<公有成员函数(参数列表)> 例: t2.PrintStandand(3.5, 6.7); 对象名 公有成员函数 参数列表
例:访问对象公有成员 直接调用 class A { float x,y; public: float m,n; void Setxy( float a, float b ){ x=a; y=b; } void Print(void) { cout<<x<<‘\t’<<y<<endl; } }; 访问对象的公有成员数据 void main(void) { A a1,a2; a1.m=20; a1.n=10; a1.Setxy(2.0, 5.0); a1.Print(); } 直接调用 访问对象的公有成员函数
类的对象 用成员选择运算符“.”只能访问对象的公有成员,而不能访问对象的私有成员或保护成员。若要访问对象的私有的数据成员,只能通过对象的公有成员函数来获取。 class CDog { //私有成员 unsigned int m_Age; protected: //保护成员 unsigned int m_Weight; }; int main(int argc, char* argv[]) CDog mydog; mydog.m_Age = 2; mydog.m_Weight = 10; return 0; } 错误,不能访问保护成员 错误,不能访问保护成员
同类型的对象之间可以整体赋值,这种赋值与对象的成员的访问权限无关。 class A { float x,y; public: float m,n; void Setxy( float a, float b ){ x=a; y=b; } void Print(void) { cout<<x<<‘\t’<<y<<endl; } }; void main(void) { A a1,a2; a1.m=10; a1.n=20; //为公有成员数据赋值 a1.Setxy(2.0,5.0); a2=a1; a1.Print(); a2.Print(); } 同类型的对象之间可以整体赋值 相当于成员数据间相互赋值
访问对象私有成员 如何访问对象的私有成员? 利用公有函数访问私有成员 利用指针访问私有成员 利用引用访问私有成员
1. 利用公有函数访问私有成员 为私有成员数据赋值 返回私有数据成员 调用公有函数 调用公有函数,获得私有数据成员 class Test{ int x,y; public: void Setxy(int a, int b) {x=a; y=b;} int Getx(void) { return x;} int Gety(void) { return y;} void Printxy(void){cout<<"x="<<x<<'\t'<<"y="<<y<<endl; } }; void main(void) { Test p1,p2; p1.Setxy(3,5); int a,b; a=p1.Getx( ); b=p1.Gety(); //将 a=x, b=y cout<<a<<'\t'<<b<<endl; } 为私有成员数据赋值 返回私有数据成员 调用公有函数 调用公有函数,获得私有数据成员
2. 利用指针访问私有数据成员 输出: 3 5 x y Setxy() Getxy() Printxy() 形参为指针 提取x, y值 a class Test{ int x,y; public: void Setxy(int a, int b) {x=a; y=b;} void Getxy(int *px, int *py) {*px=x;*py=y;} void Printxy(void){cout<<"x="<<x<<'\t'<<"y="<<y<<endl; } }; void main(void) { Test p1,p2; p1.Setxy(3,5); int a,b; p1.Getxy(&a, &b); cout<<a<<'\t'<<b<<endl; } 形参为指针 提取x, y值 a Setxy() Getxy() Printxy() 3 x y 3 &a Px, Py 5 b 5 &b 将 a=x, b=y 输出: 3 5
3. 利用引用访问私有数据成员 输出: 3 5 形参为引用 提取x, y值 将 a=x, b=y class Test{ int x,y; public: void Setxy(int a, int b){x=a; y=b;} void Getxy(int &px, int &py) { px=x; py=y;} void Printxy(void){cout<<"x="<<x<<'\t'<<"y="<<y<<endl; } }; void main(void) { Test p1,p2; p1.Setxy(3,5); int a,b; p1.Getxy(a, b); cout<<a<<'\t'<<b<<endl; } 形参为引用 提取x, y值 输出: 3 5 将 a=x, b=y
4. 定义类的指针及用指针来调用对象 x y Setxy() Getxy() Printxy() a1 2.0 p 3.0 class A{ float x,y; public: float Sum(void) { return x+y; } void Set(float a,float b) { x=a; y=b;} void Print(void) { cout<<"x="<<x<<'\t'<<"y="<<y<<endl; } }; void main(void) { A a1,a2; A *p; //定义类的指针 p=&a1; //给指针赋值 p->Set(2.0, 3.0); //通过指针引用对象的成员函数 p->Print(); cout<<p->Sum()<<endl; a2.Set(10.0, 20.0); a2.Print(); } a1 Setxy() Getxy() Printxy() 2.0 x y p 3.0
类作用域 类体的区域称为类作用域。 类的成员函数与成员数据仅在该类的范围内有 效。 类的成员函数与成员数据仅在该类的范围内有 效。 不能在主函数中直接通过函数名和成员名来调 用类的成员函数。
class A { float x,y; public: float m,n; void Setxy( float a, float b ){ x=a; y=b; } void Print(void) { cout<<x<<‘\t’<<y<<endl; } }; void main(void) { A a1,a2; m=20; n=10; Setxy(2.0, 5.0); Print(); } void main(void) { A a1,a2; a1.m=20; a1.n=10; a1.Setxy(2.0, 5.0); a1.Print(); } 不能直接调用 用对象名调用
类类型的作用域和对象的作用域 在函数体外定义的类,其类名的作用域为 文件作用域;而在函数体内定义的类,其 类名的作用域为块作用域 。 在函数体外定义的类,其类名的作用域为 文件作用域;而在函数体内定义的类,其 类名的作用域为块作用域 。 对象的作用域与变量作用域完全相同 , 全局 对象、局部对象等。
class A { float x,y; public: float m,n; void Setxy( float a, float b ){ x=a; y=b; } void Print(void) { cout<<x<<‘\t’<<y<<endl; } }a3,a4; 类A:文件作用域,在整个文件中有效 全局对象 void main(void) { A a1,a2; class B{ int i,j; public : void Setij(int m, int n){ i=m; j=n; } }; B b1,b2; a1.Setxy(2.0, 5.0); b1.Setij(1,2); } 局部对象 类B:块作用域,在函数内有效。
类的嵌套 在定义一个类时, 在其类体中又包含了一个类 的完整定义,称为类的嵌套 。 类是允许嵌套定义的
类B包含在类A中,为嵌套定义 嵌套类的对象 class A { class B{ int i,j; public : void Setij(int m, int n){ i=m; j=n; } }; float x,y; public: B b1,b2; void Setxy( float a, float b ){ x=a; y=b; } void Print(void) { cout<<x<<‘\t’<<y<<endl; } 类B包含在类A中,为嵌套定义 在类A的定义中,并不为b1,b2分配空间,只有在定义类A的对象时,才为嵌套类的对象分配空间。嵌套类的作用域在类A的定义结束时结束。 嵌套类的对象
成员函数的重载 类中的成员函数与前面介绍的普通函数一样,成员函数可以带有缺省的参数,也可以重载成员函数。 重载时,函数的形参必须在类型或数目上不同。 可以有缺省参数的成员函数,若形参不完全缺省,则必须从形参的右边开始缺省。
举例 class Test{ int x , y; int m, n; 输出:x=3 y=5 public: void Setxy(int a, int b){x=a; y=b;} void Setxy(int a,int b,int c,int d){ x=a;y=b;m=c;n=d;} void Printxy(int x){cout<< “m="<<m<<'\t'<<“n="<<n<<endl;} void Printxy(void) {cout<<"x="<<x<<'\t'<<"y="<<y<<endl;} } ; void main(void) { Test p1,p2; p1.Setxy(3, 5); p2.Setxy(10,20,30,40);//参数数目不同 p1.Printxy(); p2.Printxy(); p2.Printxy(2);//参数类型不同 } 输出:x=3 y=5 x=10 y=20 m=30 n=40
例:类的调用 三角形类:三角形的三边及与三边相关的运算 class Triangle{ private: float a,b,c; //三边为私有成员数据 public: void Setabc(float x, float y, float z);//置三边的值 void Getabc(float &x, float &y, float &z);//取三边的值 float Perimeter(void);//计算三角形的周长 float Area(void);//计算三角形的面积 void Print(void);//打印相关信息 };
void Triangle::Setabc (float x,float y,float z) {a =x; b=y; c=z; } //置三边的值 void Triangle::Getabc (float &x,float &y,float &z) //取三边的值 {x=a; y=b; z=c;} float Triangle::Perimeter (void) {return (a+b+c)/2;} //计算三角形的周长 float Triangle::Area (void) //计算三角形的面积 {float area,p; p= Perimeter(); area=sqrt((p-a)*(p-b)*(p-c)*p); return area; } void Triangle::Print(void) //打印相关信息 {cout<<"Peri="<<Perimeter()<<'\t'<<"Area="<<Area()<<endl;}
void main(void) { Triangle Tri1; //定义三角形类的一个实例(对象) Tri1.Setabc (4,5,6); //为三边置初值 float x,y,z; Tri1.Getabc (x,y,z); //将三边的值为x,y,z赋值 cout<<x<<'\t'<<y<<'\t'<<z<<endl; cout<<“s=”<<Tri1.Perimeter ()<<endl;//求三角形的周长 cout<<“Area=”<<Tri1.Area ()<<endl;//求三角形的面积 cout<<"Tri1:"<<endl; Tri1.Print ();//打印有关信息 }
例:对象使用综合练习 利用类与对象编写程序,完成学生的姓名成绩及 相关的运算 class Stu 利用类与对象编写程序,完成学生的姓名成绩及 相关的运算 class Stu { char Name[20]; //学生姓名 float Chinese; //语文成绩 float Math; //数学成绩 public: float Average(void); //计算平均成绩 float Sum(void); //计算总分 void Show(void); //打印信息 void SetStudent(char*, float, float);//为对象置姓名、成绩 void SetName(char *); //为对象置姓名 char *GetName(void); //取得学生姓名 };
例:对象使用综合练习 float Stu::Average(void){ return (Chinese+Math)/2;}//平均成绩 float Stu::Sum(void){ return Chinese+Math; }//总分 void Stu::Show(void) //打印信息 { cout<<"Name: "<<Name<<endl<<"Score: "<<Chinese<<'\t'<<Math<<'\t'<<"average: "<<Average()<<'\t'<<"Sum: "<<Sum()<<endl; } void Stu::SetStudent(char *name,float chinese,float math) { strcpy(Name,name); //置姓名 Chinese=chinese; //置语文成绩 Math=math; //置数学成绩 char * Stu::GetName(void) { return Name;} //返回姓名
例:对象使用综合练习 void main(void) { Stu p1,p2; p1.SetStudent(“Li qing”,98,96);//对象置初值 p2.SetStudent("Wang Gang",90,88); //对象置初值 p1.Show();//打印信息 p2.Show();//打印信息 p1.SetName (“Zhao jian”);//重新置p1对象的名字 p1.Show (); cout<<“p1.Name: ”<<p1.GetName ()<<endl;//打印对象的名字 cout<<“p1.average: ”<<p1.Average ()<<endl;//打印对象的成绩 }
总结 对象的使用 访问对象的公有成员 直接调用 访问对象的私有成员 利用公有函数访问 访问对象的保护成员 利用指针访问 利用引用访问 与访问私有成员相同
例:设计图像处理软件 程序界面 导入任意图像
设计类与对象 设计类 设计对象
设计结果 图像翻转 边缘检测 直方图均衡+放大2倍 直方图均衡+放大2倍+旋转60度
第8讲 类与对象 9.1 面向对象程序设计的基本概念 9.2 对象与类的定义 9.3 内联函数 9.4 对象的使用
作业题1: 用面向对象方法编写一个学生成绩录入程序,要求: 用户可以根据需要输入任意条的学生成绩信息,当遇到某个 自定义字符时结束录入。例如输入(至少输入3个学生): 计算每位学生的总分,并将学生按照总分由大到小排序打印 出来,如: Name Age Sex English Math Program John 21 M 78.9 95.2 89.3 Smith 24 F 91.2 76.2 85.5 Name Age Sex English Math Program Total John 21 M 78.9 95.2 89.3 263.4 Smith 24 F 91.2 76.2 85.5 252.9 网络课程平台http://course.sdu.edu.cn/G2S/Template/View.aspx?action=view&courseType=0&courseId=2272
作业题2: 课本P211 习题 8.5 定义复数类,完成以下功能:输入两个复数,可以 选择进行复数的加、减、乘或除运算,并输出相应 的运行结果。