http://www.xs360.cn 《IOS应用开发教程》 QQ学习群:262779381
第三章 Objective-C面向对象方法实现 http://www.xs360.cn 第三章 Objective-C面向对象方法实现 QQ学习群:262779381
1、通过本章学习,让读者了解Objective-C语言面向对象编程的思想及方法实现。 2、掌握类、对象和方法的概念及方法定义。 教学目标: 1、通过本章学习,让读者了解Objective-C语言面向对象编程的思想及方法实现。 2、掌握类、对象和方法的概念及方法定义。 3、掌握Objective-C面向对象编程特征—继承、多态。 4、了解动态类型和动态绑定的概念及在编程中的运用。 QQ学习群:262779381
3.1对象、类、方法介绍 对象就是一个实体,我们能够感受到的事物,一辆汽车,一间房子, 一个小动物,这些都是对象。 类则是对对象的抽象,简单来说,就是对这个类的一个概括。 将这些生活中的动作作为方法来进行定义和使用 QQ学习群:262779381
在OC中,类的定义分为两个部分: · 接口部分(interface):接口中声明了类和父类的名称,还声明了一些实例变量 和方法。接口部分中的声明应在@interface和@end中间。 该部分一般的格式为: @interface NewClassName : ParentClassName { memberDeclarations; } methodDeclarations; @end 波峰波谷命名法 QQ学习群:262779381
·类和实例方法 实例变量我们在前面的章节中有过介绍,这里我们介绍下类和实例方法, OC中方法的声明格式为: 方法类型 (返回值) 方法名称 (参数类型)参数名 -(void)setNumber:(int)n; OC中有两种方法类型,开头是符号“-”代表该方法是一个实例方法,而 开头是符号“+”则代表该方法是类方法 ·返回值 在声明方法的时候,还要声明方法是否有返回值,若有,是整型还是实型。 如果有返回值,那么在方法实现的最后,要加一条return语句来返回相 对应类型的值,如果无返回值,则不需要写这条语句。 QQ学习群:262779381
·参数 上文中的setNumber方法就带有一个整型参数,这样就能指定向该方法中传递 一个整型的参数。方法中也可以带多个参数,例如, -(void)intWithNumber:(int)n andAge:(int)a; 这样就在方法中带了两个参数,但是这个放的名称是什么呢?这也是OC中特有 的一种命名方式,它的方法名是intWithNumberandAge,通过冒号“: “来接受一个参数,也便于区分。 · 实现部分(implementation):实现部分则包含了在接口中声明的方法的方 法的具体实现。 QQ学习群:262779381
3.2继承 3.2.1@property属性和点语法 要通过对象调用方法来使用实例变量必须要声明并实现set方法和get方 法,也就是常说的设置器和访问器,在上面的例子中因为只有一个实例 变量,所以只须定义和实现一个set方法和一个get方法,在set方法中, 还需要调用父类的init方法,也是一个较好的编程习惯。但是如果类中有 很多实例变量呢?那不是定义很多的set和get方法吗?正是出于这点的 考虑,在OBC 2.0中,系统提供了@property方法来自动帮我们生成 set和get方法,现在我们来一起学习如何使用@property方法来简化我 们的代码。 QQ学习群:262779381
#import <Foundation/Foundation.h> @interface number : NSObject { int Mynumber1; float Mynumber2; } @property(nonatomic) int Mynumber1; @property(nonatomic) float Mynumber2; - (void)print; @end QQ学习群:262779381
@implementation number @synthesize Mynumber1,Mynumber2; - (void)print #import "number.h" @implementation number @synthesize Mynumber1,Mynumber2; - (void)print { NSLog(@"Mynumber1 is %d,Mynumber2 is %.1f",Mynumber1,Mynumber2); } @end QQ学习群:262779381
#import <Foundation/Foundation.h> #import "number.h" int main(int argc, const char * argv[]) { @autoreleasepool { number *intNumber = [[number alloc]init]; intNumber.Mynumber1 = 10; intNumber.Mynumber2 = 20.5; [intNumber print]; } return 0; QQ学习群:262779381
在Objective-C中通过对象调用方法有两种方法,一种是我们在以前的例 子中运用到的中括号的方法,另一种就是在@property方法中的点语法 方法。声明@property属性之后,在.m实现文件中要使用 @synthesize方法来完成这个方法。这样在调用实例变量的时候就会方 便很多。 其实在@property属性中有很多的参数可供选择,下面列出几种供大家参 考。 readonly:只产生简单的getter方法,没有setter方法。 retain: setter方法对参数进行release旧值,再retain新值。 nonatomic:禁止多线程,保护变量。 assign: 默认类型,setter方法直接赋值,而不进行retain操作。 QQ学习群:262779381
3.2.2类的继承 类的继承知识点中就要引入父类和子类的概念, NSObject类是所有类的父类,子类能够继承父类的实例 变量和方法,子类可以直接访问这些方法和实例变量,就 像直接在类中定义了一样。 QQ学习群:262779381
#import <Foundation/Foundation.h> @interface Class_A : NSObject { int x; } - (void)initX; @end #import "Class A.h" @implementation Class_A - (void)initX x = 10; QQ学习群:262779381
@interface Class_B : Class_A - (void)print; @end #import "Class B.h" #import "Class A.h" @interface Class_B : Class_A - (void)print; @end #import "Class B.h" @implementation Class_B - (void)print { NSLog(@"x = %d",x); } QQ学习群:262779381
#import <Foundation/Foundation.h> #import "Class A.h" #import "Class B.h" int main(int argc, const char * argv[]) { @autoreleasepool { ClassB *number = [[ClassB alloc]init]; [number initX]; [number print]; } return 0; QQ学习群:262779381
我们有时候不想让其他的类使用自己的成员变量,就可以将它定义为 private私有变量,比如在上面的例子中,我们将实例变量x定义为 private,那么在编译的时候就会报错,ClassB就没有权限去使用这 个私有实例变量,该私有变量只能在本类中使用。而@protected定 义的实例变量(默认情况)可被该类及任何子类中定义的方法直接访 问,@public定义的实例变量不仅可以使本类的方法使用,还可以被 其他的类和模块中定义的方法直接访问。 QQ学习群:262779381
3.3多态、动态类型和动态绑定 3.3.1多态 多态使得在程序中来自不同类的对象可以定义相同名称的 方法,简单的来说,就是相同的名称,不同的类。 QQ学习群:262779381
#import "ball.h" @interface basketball : ball - (void)player:(int)f; - (void)play; @end #import "basketball.h" @implementation basketball - (void)player:(int)b { players = b; } - (void)play NSLog(@"篮球是%d个人的运动",players); NSLog(@"篮球比赛开始了"); QQ学习群:262779381
#import "ball.h" @interface football : ball - (void)player:(int)f; - (void)play; @end #import "football.h" @implementation football - (void)player:(int)f { players = f; } - (void)play NSLog(@"足球是%d个人的运动",players); NSLog(@"足球比赛开始了"); QQ学习群:262779381
QQ学习群:262779381 #import <Foundation/Foundation.h> @interface ball : NSObject { int players; } - (void)player:(int)b; - (void)play; @end #import "ball.h" @implementation ball - (void)player:(int)b { players= b; - (void)play NSLog(@"比赛开始了!"); NSLog(@"球类比赛不是%d个人的比赛",players); QQ学习群:262779381
#import <Foundation/Foundation. h> #import "basketball #import <Foundation/Foundation.h> #import "basketball.h" #import "football.h" int main(int argc, const char * argv[]) { @autoreleasepool { ball *ballgame = [[ball alloc]init]; ball *basketballgame = [[basketball alloc]init]; ball *footballgame = [[football alloc]init]; [ballgame player:1]; [basketballgame player:5]; [footballgame player:11]; [ballgame play]; [basketballgame play]; [footballgame play]; } return 0; QQ学习群:262779381
我们定义了三个类,ball类的父类是NSObject类,而 basketball类和football类是ball类的子类,我们可以看 到这三个类中都有play这个方法,但是它们属于不同的 类,这就是多态的应用,能使得同一个函数有不同的表达 方式。两个子类中还有player方法也是多态的运用。 QQ学习群:262779381
其实如果要算是完整的一个多态的表现,还要满足下面三 个条件:有继承关系,上述例子中basketball类和 football类就是继承了ball类,所有有继承关系;有方法 重写,在两个子类中都分别重写了player方法和play方 法;父类的声明变量指向子类对象,在主函数中,我们声 明对象的时候都是用的两个子类的父类ball类。所以满足 多态的三个条件,读者在使用多态的时候,也要注意完整 定义这三个条件。 QQ学习群:262779381
3.3.2动态类型 在Objective-C中,除了基本的数据类型外,还有一种特殊的数据类型,那就是动态类型,id类型。 id类型可以存储任何类型的对象,换句话说,我们也可以将它划分到基础数据类型中。下面我们声明一个id类型的变量。 id Numbers 在声明了id类型的变量后,Numbers可以存储任何类型的对象,那么我们可以声明一个具有id类型返回值的方法,用于创建实例。 -(id) NewNumbers:(int)number; 我们发现,id类型不仅仅可以定义变量,可以定义方法,让方法的返回值为动态的,这就可以使编程中具有更好的代码灵活性。 QQ学习群:262779381
3.3.3动态绑定 QQ学习群:262779381 #import <Foundation/Foundation.h> @interface intNumber : NSObject @property int A,B; - (void)setA:(int)a andB:(int)b; - (int)add; @end #import "intNumber.h" @implementation intNumber @synthesize A,B; - (void)setA:(int)a andB:(int)b { A = a; B = b; } - (int)add int result; result = A + B; NSLog(@"%d + %d =%d",A,B,result); return result; QQ学习群:262779381
QQ学习群:262779381 #import <Foundation/Foundation.h> @interface floatNumber : NSObject @property float A,B; - (void)setA:(float)a andB:(float)b; - (float)add; @end #import "floatNumber.h" @implementation floatNumber @synthesize A,B; - (void)setA:(float)a andB:(float)b { A = a; B = b; } - (float)add float result; result = A + B; NSLog(@"%f + %f =%f",A,B,result); return result; QQ学习群:262779381
#import <Foundation/Foundation.h> #import "intNumber.h" #import "floatNumber.h" int main(int argc, const char * argv[]) { @autoreleasepool { id dataValue; //动态类型变量 intNumber *numberInt = [[intNumber alloc]init]; floatNumber *numberFloat = [[floatNumber alloc]init]; [numberInt setA:10 andB:20]; [numberFloat setA:12.57 andB:32.31]; dataValue = numberInt; //第一个dataValue [dataValue add]; dataValue = numberFloat; //第二个dataValue } return 0; QQ学习群:262779381
Objective-C语言的系统总是跟踪对象所属的类,运行时先判定对象所属的类,然后在运行时确定需要动态调用的方法,而不是在编译的时候。因此,在程序执行的时候,当系统准备将add消息发送给dataValue时,它会检查dataValue中存储的对象所属的类,比如这里我们检查到dataValue变量保存了一个intNumber对象,此时,系统将intNumber类中定义的add方法发送给dataValue,这就是动态绑定的概念。 QQ学习群:262779381
3.4对象的复制 3.4.1系统类的复制 和C++和Java等面向对象编程语言类似,Objective-C中也有对象复制(拷贝)的概念。那么对象复制是什么?什么时候会用到呢?下面我们通过一个场景对其进行分析。 我们假设一个对象中拥有一个数组对象,现在我们又需要生成一个对象,同时将现有的对象赋值给这个新对象,那么问题出现了,这两个对象中的数组对象是同一个,也就是说,当我在一个对象中对数组对象进行修改,那么另一个对象中的数组对象也会同时修改,也就是说这两个对象中的数组对象是共享的。有时我们也需要不同时改变这两个数组对象,所以对象复制的概念由此而生。 QQ学习群:262779381
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { NSMutableArray *array1 = [NSMutableArray arrayWithObjects:@"one",@"two",nil]; NSMutableArray *array2 = [array1 retain]; //retain只是引用计数+1,没有创建新的对象 //array1与array2指针相同,指向同一个对象 if(array1 == array2){ NSLog(@"array1 == array2"); NSLog(@"array1的引用计数:%ld",array1.retainCount); } return 0; QQ学习群:262779381
在讲解对象复制前,我们引出一个retain的概念,和copy类似,retain也可以快速的创建对象。但是,retain只是让原对象的引用计数+1,并没有创建新的对象,这两个对象的指针相同,指向同一个对象。所以我们在控制台中看到程序运行的结果,array1的引用计数为2。 QQ学习群:262779381
下面我们对程序清单copy进行修改,加入对象复制。 NSMutableArray *array1 = [NSMutableArrayarrayWithObjects:@"one",@"two",nil]; NSMutableArray *array2 = [array1 copy]; if(array1 != array2){ NSLog(@"array1 != array2"); NSLog(@"array1的引用计数:%ld",array1.retainCount); NSLog(@"array2的引用计数:%ld",array2.retainCount); } 如代码清单所示,我们通过copy方法,快速创建了一个数组array2,copy方法用于不可变数组,mutableCopy方法用于可变数组。我们看到控制台的结果,array1和array2的引用计数都是1,这就说明copy方法是新建一个对象,和retain方法有所区分,并且通过代码我们可以看出,array1并不等于array2。 QQ学习群:262779381
3.4.2深拷贝和浅拷贝 深拷贝:拷贝属性对象的所有内容 浅拷贝:只拷贝所有属性对象的指针 QQ学习群:262779381
QQ学习群:262779381 #import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { NSMutableArray *dataArray = [NSMutableArray arrayWithObjects: [NSMutableString stringWithString:@"1"], [NSMutableString stringWithString:@"2"], [NSMutableString stringWithString:@"3"], nil]; NSMutableArray *dataArray2; NSMutableString *msg; NSLog(@"dataArray: "); for (NSString *string in dataArray) { NSLog(@" %@",string); } dataArray2 = [dataArray mutableCopy]; msg = [dataArray objectAtIndex:0]; [msg appendString:@"0"]; NSLog(@"dataArray2: "); for (NSString *string in dataArray2) { return 0; QQ学习群:262779381
在代码中,我们首先创建了一个数组,并对其赋予了字符串值,然后复制一份,并对数组下标为0的元素进行修改,通过objectAtIndext方法找到下标为0的元素,然后通过appendString方法在字符串后面添加内容。注意到,修改后,dataArray和dataArray2的下标为0的元素的值都改变了,这是为什么呢?这是因为在dataArray调用objectAtIndex方法时,返回的对象与dataArray中的第一个元素都指向内存中的同一个对象,虽然说mutableCopy方法为新对象创建了一个新的内存空间,但是它的指针指向的是同一块地址,这就是浅拷贝的概念,只是对属性对象的指针。 QQ学习群:262779381
3.5IOS中的内存管理 在Java和.net等其他语言当中,都为用户提供了自动内存管理的机制,我们不需要去关心内存会不会泄露等问题。但是在iPhone开发中需要开发者手动管理内存,所以我们需要了解iPhone开发中内存管理的相关知识,下面就带大家了解Objective-C中内存管理机制和一些特性。 QQ学习群:262779381
3.5.1内存管理基础知识 我们知道IOS设备的RAM(random access memory)大小是有限的,所以要对RAM进行实时的管理。当应用程序运行时占用的内存都是取于RAM。操作系统启动应用时,会为应用保留一部分空闲的RAM,称为堆区。我们在创建实例时,会从堆区中取出一小块供其使用,当我们不需要使用对应的对象时,应该及时的释放分配给它的内存。 在前面学习的知识中我们了解到OC中是使用alloc方法为所有Objective-C类创建实例,但是当我们为实例分配内存之后,还不能使用,要通过init方法对实例化对象进行初始化。init方法是一个实例方法,一个类中会含有多个init方法,这些方法都是以init开头。 QQ学习群:262779381
#import <Foundation/Foundation.h> #import "person.h" int main(int argc, const char * argv[]) { @autoreleasepool { person *Person1 = [person alloc]; [Person initWithName:@"jack" AndAge:20]; } return 0; 我们创建了一个person类对象,并通过初始化方法给对象初始化了相应的变量。在Objective-C中,局部变量是放在栈区中的,而alloc出来内存区域是在堆区中的。 Person对象在栈区中有一块区域,而这个区域又指向堆区属于自己内存区域。 QQ学习群:262779381
- (id)init { self = [super init]; if(self){ //初始化代码 } return self; 我们一般在重写init方法的时候,会调用父类的初始化方法,通过[super init],init方法返回的值是id类型,描述了被初始化的对象。如果父类初始化失败,则会返回nil,就无法进行当前类的初始化工作。我们也可以这样理解上述代码。Init方法完成对子类对象的初始化,可以将此工作分为两个部分:继承父类的对象的初始化,子对象本身对象的初始化,所以[super init]其实就是父类对象进行初始化,而在中括号内才是对子类自身的对象进行初始化。 在分配内存之后如果想将该内存还给堆区,可以通过dealloc方法来实现,当对象收到dealloc消息时,会将其占用的内存还给堆。 QQ学习群:262779381
3.5.2引用计数 Cocoa Touch框架采用手动引用计数(mrc)来管理内存,对象不知道具体的拥有方,只是知道拥有方的个数,当拥有方(引用计数)为零时,就调用dealloc方法来释放内存。我们举个简单的例子来理解帮助读者理解引用计数这个概念。小明家有一条小狗,家人吃晚饭带这这条小狗去遛弯,爸爸妈妈和小明都牵着小狗,当3个人都将手中的绳子松开时,小狗就“自由”了,没有拥有方,就会“释放”自己。 ·retain计数和release管理内存 对象通过retain计数知道拥有方的个数。当用户创建一个实例后,对象将得到一个拥有方,此时的retain计数值为1,如果又有一个拥有方加入,则retain计数加1,当对象失去一个拥有方时,会受到release消息,此时的retain计数减1。当retain计数为0时,对象就会向自己发送一个dealloc消息,释放相应的内存。还有重要的一点,当引用计数为0时,对象不能在使用release和其他方法,不然系统会出现内存方面的错误。 QQ学习群:262779381
#import <Foundation/Foundation.h> #import "person.h" int main(int argc, const char * argv[]) { @autoreleasepool { person *Person1 = [person alloc]; [Person1 initWithName:@"jack" AndAge:20]; person *Person2 = Person1; [Person2 retain]; [Person1 release]; [Person2 release]; } return 0; 我们也可以来总结一下其中的规则,例如谁使用,谁先retain;用完记得release。这样就不会出现内存泄露。 QQ学习群:262779381
·实例变量的属性 我们在定义变量的getter和setter方法时,需要设置实例变量的属性,如 @property(nonatomic,retain)NSString *name; 那么,括号中的两个属性又是什么意思呢?接下来我们就来了解实例变量的属性。 常见的属性有assign,copy,retain,readonly和nonatomic等。 assign:对基础数据类型(NSInteger)和C类型数据(int,float,char等),使用assign属性只是简单的赋值,不改变引用计数。 copy:copy属性用于NSString对象,它将创建一个引用计数为1的对象,然后释放原先的旧对象。 retain:用于其他NSObject和NSObject子类。当添加retain属性时,将释放旧的的对象,将旧对象的值赋予输入对象,再提高输入对象的引用计数为1。 QQ学习群:262779381
NSString *myString = [[NSString alloc]initWithString:@"hello"]; 这段代码执行时会完成两个操作,因为alloc了一块内存区域,所以会在堆上分配一段内存来存储字符串,比如内存地址为0X1234,内容为“hello”,然后会在栈上分配一段内存来从存储字符串对象myString,假设地址为0XABCD,内容为堆地址0X1234。 1.当使用assign属性时,例如NSString *myString2 = [myString assign]; 此时myString2和myString完全相同,地址都是0XABCD,内容为0X1234,那么对这2者任何一个进行操作相当于对另一进行操作,所以引用计数不改变。 2.当使用retain属性时,例如NSString *myString2 = [myString retain];此时myString2的地址不再和myString一样,但是内容还是一样,为0X1234。因此myString2和myString都可以来管理@“hello”所在的内存,都是拥有者,因此运用此属性时引用计数要加1. 3.当使用copy属性时,例如NSString *myString2 = [myString copy];此时会在堆上重新开辟一段内存存放字符串@“hello“,比如内存为0X1222,内容为@”hello“,同时会在栈上为myString2分配空间,比如地址为0XBBCD,内容为0X1222,因此引用计数要加1。 QQ学习群:262779381
3.5.3自动释放池和ARC ·autoreleasepool autorelease提供了一中延迟释放的功能,实际上是把对release的调用延迟了,对于每一个autorelease,系统只是把该对象放入了当前的autoreleasepool中,当自动释放池被释放时,该pool种的所有对象会调用release。那有的读者就会问,自己手动的release管理内存不行么,这种autorelease有什么好处呢?我的理解它有一个作用就是可以做到每个函数对自己申请的对象负责,自己申请,自己释放,该函数的调用者不需要关心它内部申请对象的管理。 要使用autorelease命令,首先要手动创建一个自动释放池。 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init]; QQ学习群:262779381
QQ学习群:262779381 #import <Foundation/Foundation.h> #import "person.h" int main(int argc, const char * argv[]) { @autoreleasepool { person *Person1 = [[[person alloc]init]autorelease]; [Person1 setAge:20]; [Person1 setName:@"jack"]; person *Person2 = Person1; [Person2 retain]; //[Person1 release]; [Person2 release]; } return 0; QQ学习群:262779381
我们将新创建的对象设置了autorelease,我们就不需要再对Person1进行 release。当程序运行到最后时,该对象的引用计数还是1,那么该对象什么 时候才会被释放呢?其实,autoreleaspool就是一个NSMutableArray。 当autoreleasepool销毁时,会遍历其内部release数组中的每一个成员。 如果数组中某元素的引用计数为1,那么经过[pool drain]后值变为0,则该 元素被销毁。 Tips: 在新版本的Xcode中,用@autoreleasepool{}代替了原先定义自动释放 池的方法。效果是一些样的。 所以,在代码中被标记autorelease的对象只有在程序结束时才会被销毁, 它体现了延迟释放的特点, QQ学习群:262779381
正是因为autoreleasepool是当程序结束时才会被销毁,那如果在程序运行的过程中创 建很多标记了autorelease的实例,那么这些对象只有在程序运行结束后才会被销毁,这 样的话,内存得不到及时的释放,可用的内存越来越少,就会造成在应用程序运行当中 内存不足的情况。那么你可以自己手动创建一个自动释放池,例如: #import <Foundation/Foundation.h> #import "person.h" int main(int argc, const char * argv[]) { @autoreleasepool { person *Person1 = [[[person alloc]init]autorelease]; person *Person2 = Person1; [Person2 retain]; [Person2 release]; NSAutoreleasePool *objectpool = [[NSAutoreleasePool alloc]init]; person *a = [[[person alloc]init]autorelease]; person *b = [[[person alloc]init]autorelease]; person *c = [[[person alloc]init]autorelease]; [objectpool drain]; } return 0; QQ学习群:262779381
在最新版本的XCode(6.0)中,新建之后的项目都是使 用ARC,如果不需要使用ARC机制,需要在项目的Build Setting中去除ARC选项 QQ学习群:262779381
ARC机制能让你的代码量减少不少,因为你不需要考虑令人头痛的内存管理 问题,只需要将全部的精力放到产品的设计和实现上,我们可以看一下使用 ARC和不使用ARC代码量的情况, QQ学习群:262779381
可以看出,使用ARC之后,工作量减少了不少。但是大家肯定又会有 疑问,有ARC机制为什么还要学习前面的内存管理呢?确实ARC机制 可以免去自己管理内存的麻烦,但是ARC机制并不是那样的完美,肯定 会出现这样或那样的问题。有很多的工作人员就抱怨使用ARC之后也还 会有内存的问题,是因为存在ARC代码和非ARC代码混用的问题,如 果你不是非常熟悉Objective-C中的内存管理机制,还是尽量不要使用 ARC机制,手动管理引用计数虽然比较麻烦,但是它很直观,便于后期 的维护。如果使用的时ARC,那么它自动的release消息是看不到的, 这样不利于修改和维护。 QQ学习群:262779381
本章小结 在本章中,我们了解了Objective-C面向对象的相关知识,了解了IOS中面向对象的编程思想,其中动态绑定是Objective-C的特点,掌握其用法,能够让自己的代码具有更强的灵活性和可读性。内存管理也是IOS开发中的重中之重,因为移动设备的内存有限,如何合理的管理内存,就成为了IOS开发中的关键点。 QQ学习群:262779381
课后习题 选取身边的事物为一个类,并定义相应的实例变量,定义至少5个方法,并实现它。通过相应的方法将结果输出在控制台上。 在第一题的基础上,定义一个类的子类,并重写父类中的方法,通过对象分别调用相应的方法。 编写一个程序,能够分别计算矩形,正方形和三角形的周长和面积。用面向对象的知识完成。 定义两个类,一个为父类另一个为子类,在子类中分别用深拷贝和浅拷贝去初始化对象, 并实现父类的方法。 QQ学习群:262779381