1.31k likes | 1.5k Views
课程设计安排. 时间 :2014年1月15-19日 下午13:00-18:00 地点:综合楼315. 第四章 类与对象. C++: C with Classes. 封装( Encapsulation ) 是面向对象程序设计最基本的特性,也就是把数据(属性)和函数(操作)合成一个整体,这是用类与对象实现的。接口(类设计)和实现(编程)分离。 本章重点: 1. 引入 C++ 的类( class )和对象( object )的概念,建立 “ 函数也可以是数据类型的成员 ” 的思想。 2. 运算符重载。 . 第四章 类与对象. 4.1 类与对象.
E N D
课程设计安排 • 时间 :2014年1月15-19日下午13:00-18:00 • 地点:综合楼315
第四章 类与对象 C++: C with Classes 封装(Encapsulation)是面向对象程序设计最基本的特性,也就是把数据(属性)和函数(操作)合成一个整体,这是用类与对象实现的。接口(类设计)和实现(编程)分离。 本章重点: 1.引入C++的类(class)和对象(object)的概念,建立“函数也可以是数据类型的成员”的思想。 2.运算符重载。
第四章 类与对象 4.1 类与对象 4.6 友元 4.2 从面向过程到面向对象 4.7 静态成员 4.3 构造函数和析构函数 4.8 结构 4.4 引用与复制构造函数 4.9 名字空间域和类域(选读) 4.5 运算符的重载 4.10面向对象的程序设计和Windows编程
4.1类与对象 4.1.1 C++类的定义 4.1.2 成员函数的定义 4.1.3对象的创建与使用
4.1.1 C++类的定义 类的引入: 类是一种数据类型。 描述客观事物必须用不同的数据类型来描述不同的方面。如商品: 商品名称(用字符串描述),该商品数量(用整型数描述),该商品单价(用浮点数描述),该商品总价(用浮点数描述)。 这里用了属于三种不同数据类型的四个数据成员(data member)来描述一种商品。
4.1.1 C++类的定义 类的表述: class CGoods{ public: char Name[21] ; int Amount ; float Price ; float Total_value ; };//最后的分号不可少,这是一条说明语句 关键字class是数据类型说明符,指出下面说明的是类。标识符CGoods是商品这个类的类型名。花括号中是构成类体的系列成员,关键字public是一种访问限定符。
4.1.1 C++类的定义 访问限定符(access specifier): public(公共的)说明的成员能从外部(类代码外面)进行访问。 private(私有的)和protected(保护的)说明的成员不能从外部进行访问。 每种说明符可在类体中使用多次。 访问限定符的作用域是从该说明符出现开始到下一个说明符之前或类体结束之前结束。 如果在类体起始点无访问说明符,系统默认定义为私有(private用关键字class定义类)。 访问限定符private(私有的)和protected(保护的)体现了类具有封装性(Encapsulation)。
4.1.1 C++类的定义 类的定义: class类名{ 《《private:》 成员表1;》 《public: 成员表2;》 《protected: 成员表3;》 }; //注意:所有说明都以分号结束 其中“class 类名”称为类头(class head)。花括号中的部分称为类体(class body),类体中定义了类成员表(class member list),包含数据和函数。
4.1.1 C++类的定义 成员函数(member function): class(struct) CGoods{ private : char Name[21] ; int Amount ; float Price ; float Total_value ; public : //声明是必须的,定义可选(放到类内部也可以放到类外部) void RegisterGoods(char[],int,float); //输入数据 void CountTotal(void) ; //计算商品总价值 void GetName(char[]) ; //读取商品名 int GetAmount(void) ; //读取商品数量 float GetPrice(void) ; //读取商品单价 float GetTotal_value(void) ; };//读取商品总价值 课下查找:利用关键字class和struct定义类的区别?
4.1.1 C++类的定义 封装: 类把数据(事物的属性)和函数(事物的行为——操作)封装为一个整体。 接口: 通常数据成员被说明成私有的,函数成员被说明成公有的;从外部对数据成员进行操作,只能通过公有函数来完成,数据受到了良好的保护,不易受副作用的影响。公有函数集定义了类的接口(interface)。 成员函数可以直接使用类定义中的任一成员,可以处理数据成员,也可调用函数成员。
注意: 类是一种数据类型,定义时系统不为类分配存储空间,所以不能对类的数据成员初始化。类中的任何数据成员也不能使用关键字extern、auto或register限定其存储类型。 class CGoods; //类声明,未定义之前
4.1.2 成员函数的定义 函数定义: 通常在类定义中,成员函数仅作声明。函数定义通常在类的定义之后进行,其格式如下: 返回值类型类名::函数名(参数表) {……}//函数体 其中运算符“::”称为作用域解析运算符(scope resolution operator),它指出该函数是属于哪一个类的成员函数。 类CGoods的函数定义
成员函数 inline声明 class CGoods{ float GetPrice(void) {//类内部定义,默认inline return price; } }; 或者 inlinefloat Cgoods:: GetPrice(){//建议此声明方式 return price; }
成员函数 const 函数声明: 数据成员只可以读取不可以修改 class CGoods{ float GetPrice(void) const; }; 而且 float Cgoods:: GetPrice() const{ return price; }
4.1.3对象的创建与使用 定义对象: 对象是类的实例(instance)。定义一种数据类型只是告诉编译系统该数据类型的构造,并没有预定内存。类只是一个样板,以此样板可以在内存中开辟出同样结构的实例——对象。格式如下: CGoods Car; 这个定义创建了CGoods类的一个对象Car,同时为它分配了属于它自己的存储块,用来存放数据和对这些数据实施操作的成员函数(代码)。对象只在定义它的域中有效。
对象1 对象n 对象2 数据区 代码区 数据区 代码区 数据区 代码区 ...... 对象存储: 图4.1 各对象完全独立地安排内存的方案 图4.1是系统为每一个对象分配了全套的内存。数据区安放成员数据,代码区安放成员函数。 注意:区别同一个类的各个不同的对象的属性是由数据成员决定的,不同对象的数据成员的内容是不一样的;而行为(操作)是用函数来描述的,这些操作的代码对所有对象都是一样的。
对象1 对象n 对象2 数据区 数据区 数据区 ...... 公共代码区 图4.2 各对象的代码区共用的方案 图4.2仅为每个对象分配一个数据区,代码区(放成员函数的区域)为各对象类共用。 图4.1对应的是在类说明中定义函数,而图4.2对应的是在类说明外部定义函数 。
4.1.3 对象的创建与使用 内联函数: 使用关键字inline,系统自动采用内联扩展方法实现,每个对象都有该函数一份独立的代码。 如RegisterGoods()函数可定义为: inlinevoid CGoods::RegisterGoods(char name[] , int amount , float price){ strcpy(Name,name) ; Amount=amount ; Price=price ; } 则每个对象都有RegisterGoods()函数一份独立的代码。
4.1.3 对象的创建与使用 【例4.1】商品类对象应用实例 对象使用规则: 只要在对象名后加点号(点操作符,成员访问运算符(member access oprator)之一),再加成员数据或成员函数名就可以了。但是这些成员必须是公有的成员,只有公有成员才能在对象的外面对它进行访问。 【例4.1】中对象car的4个数据成员全是私有的,如写: car.Name;car. Amount; car.Price;car.Total_value; 是错误的,必须用对象car所带的公有函数进行访问。
4.2 从面向过程到面向对象 (阅读) 结构化程序设计特点: 采用的是“自顶向下,逐步细化(divide and conquer,stepwise refinement)”的思想。具体操作方法是模块化。模块是按功能来分的,所以也称功能块。在C++中称为一个函数,一个函数解决一个问题,即实现一个功能或一个操作。 在模块化的思想中已经出现了封装的概念,这个封装是把数据封装到模块中,即局部变量。这是很不彻底的,因为模块是功能的抽象,而数据则是具有其个性的,一但发生变化,抽象的功能模块就不再适用了。可维护性差成了制约结构化程序设计的瓶颈。 面向过程程序设计缺点的根源在于数据与数据处理分离。
4.2 从面向过程到面向对象(阅读) 结构化程序设计弱点: 当软件规模过大,采用结构化程序设计,其开发和维护就越来越难控制。其根本的原因就在于面向过程的结构化程序设计的方法与现实世界(包括主观世界和客观世界)往往都不一致,结构化程序设计的思想往往很难贯彻到底。 对象概念: 对象的概念是面向对象技术的核心所在。面向对象技术中的对象就是现实世界中某个具体的物理实体在计算机逻辑中的映射和体现。
4.2 从面向过程到面向对象(阅读) 现实世界中的实体可以抽象出类别的概念。对应于计算机世界就有一个类(class)的概念。面向对象是计算机世界模拟现实世界。图4.3表达了计算机世界与现实世界之间的对应关系。 现实世界 客观世界 计算机世 界 对象 实体 实例化 映射 抽象 抽象 主观世界 抽象类别 类 图4.3对象、实体与类
4.2 从面向过程到面向对象(阅读) • 对象、类与消息: • 面向对象程序设计模拟自然界认识和处理事物的方法,将数据和对数据的操作方法放在一起,形成一个相对独立的整体——对象(object),同类对象还可抽象出共性,形成类(class )。一个类中的数据通常只能通过本类提供的方法进行处理,这些方法成为该类与外部的接口。对象之间通过消息(message)进行通讯。
表针 旋钮 其他机械机构 属性 行为 4.2 从面向过程到面向对象(阅读) 对 象 调节旋钮
类的一个具体实现,称为实例 类 对象 描述这类对象共有的、本质的属性和行为 具体到一只圆形的或方形的手表 手表 一块手表 手表共有的属性(表针、旋钮、内部结构) 和行为(调节旋钮) 4.2 从面向过程到面向对象(阅读) 类 是一个抽象的概念,用来描述某一类对象所共有的、本质的属性和类行为。
发送消息 接收并响应消息 4.2 从面向过程到面向对象(阅读) 消 息 我们把对象之间产生相互作用所传递的信息称做消息。 启 动 转 向
内 外 机械零件 动作 调节旋钮 读表盘 4.2 从面向过程到面向对象(阅读) 面向对象程序设计的特点: 封装性 对象是一个封装体,在其中封装了该对象的属性和操作。通过限制对属性和操作的访问权限,可以将属性“隐藏”在对象内部,对外提供一定的接口,在对象之外只能通过接口对对象进行操作。 C++通过建立数据类型——类来支持封装和数据隐藏。封装性增加了对象的独立性,从而保证了数据的可靠性。一个定义完好的类可以作为独立模块使用。
汽车 载人 载货 客车 货车 小,速度快 大,速度慢 小轿车 大客车 4.2 从面向过程到面向对象(阅读) 继承与派生 以汽车为例看客观世界描述事物的方式: 面向对象程序设计提供了类似的机制: 当定义了一个类后,又需定义一个新类,这个新类与原来的类相比,只是增加或修改了部分属性和操作,这时可以用原来的类派生出新类,新类中只需描述自己所特有的属性和操作。 新类称为子类或派生类,原来的类称为基类。派生可以一直进行下去,形成一个派生树。 继承性大大简化了对问题的描述,大大提高了程序的可重用性,从而提高了程序设计、修改、扩充的效率。
4.2 从面向过程到面向对象(阅读) 多态性 多态性指,同一个消息被不同对象接收时,产生不同结果,即实现同一接口,不同方法。 高中生 大学生 计 算平均成绩 语文、数学、英语、政治、物理、化学、生物 高数、英语、计算机、线性代数
4.2 从面向过程到面向对象(阅读) 继承和多态性组合,可以生成很多相似但又独一无二的对象。继承性使得这些对象可以共享许多相似特性,而多态又使同一个操作对不同对象产生不同表现形式。这样不仅提高了程序设计的灵活性,而且减轻了分别设计的负担。
4.3 构造函数和析构函数 特殊的成员函数,只要创建类类型的对象,都要执行构造函数。功能:为数据成员分配存储空间并初始化每个对象的数据成员。 构造函数(constructor) 4.3.1 构造函数的定义与使用 4.3.2 析构函数的定义
4.3.1 构造函数的定义与使用 构造函数特征: 1.函数名与类名相同。 class CGoods{ public: CGoods (char* name , int amount , float price); CGoods (int amount , float price) const; //error }; 2.构造函数无函数返回类型说明。注意是什么也不写,也不可写void!形式参数可以有也可以没有. 不能声明为const
3.在程序运行时,当新的对象被建立,该对象所属的类的构造函数自动被调用,在该对象生存期中也只调用这一次。3.在程序运行时,当新的对象被建立,该对象所属的类的构造函数自动被调用,在该对象生存期中也只调用这一次。 CGoods book1; 4.构造函数可以重载。说明中可以有多个构造函数,它们由不同的参数表区分; class CGoods{ public: CGoods (char* name , int amount , float price); CGoods (char* name , float price); CGoods(); }; 思考:多个构造函数,调用时选择哪一个?重载函数调用规则来思考。
4.3.1构造函数的定义与使用 5.构造函数可以在类中定义,也可以在类外定义。 class CGoods{ public: CGoods (char* name , int amount , float price){//类内部定义 strcpy(Name,name) ;Amount=amount ;Price=price ; Total_value=price*amount ;} }; 或者 class CGoods{ public: CGoods (char* name , int amount , float price);//类内部声明 }; CGoods::CGoods (char* name , int amount , float price){//类外部定义 strcpy(Name,name) ;Amount=amount ;Price=price ; Total_value=price*amount ; }
6. 如果类说明中没有给出构造函数,则C++编译器自动创建一个默认的构造函数: 类名(void) {} 注意:只要我们定义了一个构造函数,系统就不会自动生成默认的构造函数。 只要构造函数是无参的或各参数均有默认值的,C++编译器都认为是默认的构造函数,并且默认的构造函数只能有一个 。
4.3.1构造函数的定义与使用 CGoods的构造函数: 三参数: CGoods (char* name , int amount , float price){ strcpy(Name,name) ;Amount=amount ;Price=price ; Total_value=price*amount ;} 两参数:货名和单价, CGoods (char* name , float price){ strcpy(Name,name) ;Price=price;Amount=0 ; Total_value=0.0 ;} 默认的构造函数: CGoods(){ Name[0]=‘\0’ ; Price=0.0 ; Amount=0 ; Total_value=0.0 ;} 这三个构造函数同时被说明(重载)。
4.3.1构造函数的定义与使用 实参决定调用哪个构造函数: CGoods Car1(“夏利2000”,30,98000.0); 调用了CGoods中的第一个构造函数,等效于: CGoods Car1= CGoods(“夏利2000”,30,98000.0); CGoods Car2(“桑塔那2000”,164000.0) ; 调用的是第二个构造函数,参数为两个。 CGoods Car3; 定义时调用不带参数的构造函数 但是定义对象时不能加括号。例如:CGoods Car4(); Car4()是不带参数的函数,它的返回值是类CGoods的对象。 【例4.1_1】完整商品类对象应用实例
练习 • 选择一下一个抽象,确定类中需要什么数据,并提供适当的构造函数。并解释你的决定。 1. Book 2. Date 3. Student 4. Vehicle 5. Tree 6. Computer 7. Program
构造函数初始化式 • 与普通函数一样,构造函数具有名字,形参列表和函数体。不同:定义时可以包含一个初始化列表(不能在声明处指出) CGoods ::CGoods (char* name , int amount , float price):Amount(amont),Price(price){ strcpy(Name,name); } 注意:const,没有默认构造函数的类类型的成员或引用数据成员 class A{ private: const int i; public: A(int var){var=i;} //error A(int var):i(var){ } };
成员初始化的次序: 成员被定义的次序决定了初始化的次序.初始化列表只是仅初始化成员的值,而不指定初始化的顺序。 class X{ int i; int j; public: //run-time error: i is initialized before j X(int val):j(val),i(j){ } };
4.3.2 析构函数的定义 析构函数(destructor)特征: 当一个对象的生命周期结束时,C++会自动调用析构函数(destructor)对该对象并进行善后工作(不严谨)。 1.构函数名与类名相同,但在前面加上字符‘~’,如 ~CGoods()。 2.析构函数无函数返回类型,与构造函数在这方面是一样的。但析构函数不带任何参数。 3. 一个类有一个也只有一个析构函数,这与构造函数不同。析构函数可以默认。 4. 对象注销时,系统自动调用析构函数,按成员在类中声明的次序逆序撤销成员(释放存储空间)。 想一想:什么工作? 有些情况,不需要显示定义 析构函数。查找资料,什么 情况下需要什么情况下不需要? 能否举例说明 【例4.2】定义一个矩形类
4.4引用与复制构造函数 4.4.1 引用 4.4.2 复制构造函数 4.4.3 成员对象与构造函数
4. 4.1引用 引用的导入: 参数传递的传值方式在函数域中为参数重新分配内存,而把实参的数值传递到新分配的内存中。它的优点是有效避免函数的副作用。 问题:如果要求改变实参的值,怎么办呢?如果实参是一个复杂的对象,重新分配内存会引起程序执行效率大大下降,怎么办呢? 有一种导出型数据类型—引用(reference)可以解决上面的难题。引用又称别名(alias)。
4.4.1引用 引用的定义: 引用是给一个已经定义的对象/变量重新起一个别名,而不是定义一个新的变量,定义的格式为: 类型 &引用变量名=已定义过的变量名; 例如: double number ; double &newnum=number ; newnum是新定义的引用类型变量,它是变量number的别名。 引用主要用于函数之间的数据传递。
4.4.1引用 newnum是变量number的别名,C++系统不为引用类型变量分配内存空间。内存分配见下图: number称为引用newnum的关联变量。“&”(读作ampersand)在这里是引用的说明符。必须注意number和newnum都是double类型。如在程序中修改了newnum也就是修改了number,两位一体。 注意:对数组只能引用数组元素,不能引用数组(数组名本身为地址)。
const 引用(不严谨):指向const 对象的引用 const int val=1024; const int &refval=val; int &ref2=val; // error: nonconst reference to a const object 课下查找,以下语句是否合法,并解释: int i=34; double j=65.14; const int &ref1=34; int &ref3=24; const int &ref2=ref1+i; int &ref4=ref1+i; const int &ref5=j; 可参考课本 p.133
4.4.1引用 【例4.3】引用作为函数的参数。 采用引用调用时,将对实参进行操作。 【例4.4】引用作为函数的返回值 一般函数返回值时,要生成一个临时变量作为返回值的副本,而用引用作为返回值时,不生成值的副本。 注意:采用引用返回方式时,返回的不能是函数中的局部变量,这时返回的局部变量地址已经失效。引用方式返回最常用的是由引用参数传递过来的变量(见例4.5),其次是全局变量,这样返回的变量地址是有效的。 【例4.5】返回值为引用的函数作为左值(选读)
4.4.2 复制构造函数 复制构造函数引入: 同一个类的对象在内存中有完全相同的结构,如果作为一个整体进行复制是完全可行的。这个复制过程只需要复制数据成员,而函数成员是共用的(只有一份代码)。在建立对象时可用同一类的另一个对象来初始化该对象,这时所用的构造函数称为复制构造函数(Copy Constructor)。 CGoods类,复制构造函数为: CGoods (CGoods & cgd){ Strcpy (Name , cgd.Name); Price= cgd.price; Amount=cgd.Amount; Total_value=cgd.Total_value;} 复制构造函数形参通常声明为const: CGoods (const CGoods & cgd) {....} 思考: 为什么?
4.4.2 复制构造函数 复制构造函数特征: 1.复制构造函数的参数必须采用引用。 * 在C++中按值传递一个参数时,会在函数中重新分配一块内存建立与参数同类型的变量或对象,再把参数的数据成员赋给新的变量或对象。在建立这个对象时,编译器就会自动为这个对象调用复制构造函数。如果其参数是真实的对象而不是引用,则又会引入新的一轮调用复制构造函数的过程,出现了无穷递归。 CGoods (CGoods cgd){ ... ... }
4.4.2 复制构造函数 2.系统会自动提供称为默认的复制构造函数(无显式定义),亦称为默认的按成员初始化。按成员作复制是通过依次复制每个数据成员实现的。 4.在某些情况下,它对类与对象的安全性和处理的正确性还不够(有指针数据成员或为成员分配资源?),这时就要求提供显式的复制构造函数和复制赋值操作符的定义。 3. 赋值运算符“=”称默认的按成员复制赋值操作符(Overloaded Assignment Operator),同类对象之间可以用“=”直接复制 。