670 likes | 807 Views
Object-Oriented Programming in C++ 第三章 再论类和对象. 中国科大学继续教育学院 李艺 leeyi@ustc.edu.cn. 第一 章 C++ 的初步知识 第二章 类和对象 第三章 再论类和对象 第四章 运算符重载 第五章 继承与派生 第六章 多态性与虚函数 第七章 输入输出流 第八章 C++ 工具. 3.1 构造函数 3.2 析构函数 3.3 调用构造函数和析构函数的顺序 3.4 对象数组 3.5 对象指针 3.6 共用数据的保护 3.7 对象的动态建立和释放
E N D
Object-OrientedProgramming in C++第三章 再论类和对象 中国科大学继续教育学院 李艺 leeyi@ustc.edu.cn
第一章 C++的初步知识 第二章 类和对象 第三章 再论类和对象 第四章 运算符重载 第五章 继承与派生 第六章 多态性与虚函数 第七章 输入输出流 第八章 C++工具
3.1 构造函数 3.2 析构函数 3.3 调用构造函数和析构函数的顺序 3.4 对象数组 3.5 对象指针 3.6 共用数据的保护 3.7 对象的动态建立和释放 3.8 对象的赋值和复制 3.9 静态成员 3.10 友元
3.1 构造函数 • 对象的初始化 和普通变量一样,我们定义一个变量,往往同时进行初始化: int a = 3; 而声明类时,数据成员不能进行初始化: class time { hour = 0; minute = 0; second = 0; } 因为类不是实体,不占分配存储空间,显然无法容纳数据。
3.1 构造函数 如果一个类的所有数据才成员都是公用的,我们可以想结构体变量那样,在定义对象时(而不是声明类时)进行初始化: class time { public: hour = 0; minute = 0; second = 0; }; time t1 = {13,30,20}; 但在类的声明中,数据成员往往都是私有的,不能这样初始化。就需要一个公有成员函数来完成,而且应该是自动调用地完成。这就是构造函数。
3.1 构造函数 • 构造函数的作用 C++提供了构造函数( constructor )来处理对象的初始化。构造函数是一个由用户定义的特殊的成员函数。与其他成员函数不同之处在于: • 用户不能调用它,而是在定义对象时,有系统自动调用构造函数。 • 构造函数的名字必须与类名一致,不能是其他名字。 • 构造函数不能有任何返回类型。 • 用户如果没有定义构造函数,系统会自动生成一个构造函数,只不过函数体中没有任何语句。
3.1 构造函数 • 例3.1 将前例的时间类定义构造函数。在构造函数中加入输出语句,看看运行效果。 class time { private: int hour, minute, second; public: time ( ) //time 类的构造函数 { hour = 0; minute = 0; second = 0; } void setTime ( ); void showTime ( ) { cout << hour <<“:” <<minute <<“:” <<second <<endl; } }; void time::setTime ( ) { cout<<“hour = “; cin>>hour; cout<<“minute=“; cin>>minute; cout<<“second=“; cin>>second; } void main ( ) { time t1; // 定义time类对象t1,调用构造函数tume ( ) t1.setTime( ); t1.showTime ( ); time t2; // 定义time类对象t2,调用构造函数tume ( ) t2.setTime ( ); t2.showTime ( ); }
3.1 构造函数 • 带参数的构造函数 前一个示例中,没产生一个time了的对象,其初值都初始化为0。如果用户初始化时,不想将对象初值置为0而是其它值,就需要用带参数的构造函数来实现。 • 声明一个构造函数的一般格式为: 构造函数名 ( 类型1 形参1,类型2 形参2,…); • 定义一个对象的一般格式为: 类名 对象名 ( 实参1,实参2,…); 下面举例说明用法。
3.1 构造函数 • 例3.2 :有两个长方体,长宽高分别为(1,2,3)和(4,5,6)。试编写一基于对象的程序,分别求他们的体积,并且要求用带参数的构造函数初始化他们。 void main ( ) { box box1 (1,2,3); cout <<“box1的体积为” <<box1.volume( ) << endl; box box2 (4,5,6); cout <<“box2的体积为” <<box2.volume( ) << endl; } #include <iostream.h> class box { private: int height, width, length; public: box ( int h, int w, int len ) { height = h; width = w; length = len; } int volume ( ) { return height * width * length; } };
3.1 构造函数 • 用参数初始化表对数据成员初始化 C++提供另一种初始化数据成员的方法:参数初始化表来实现对数据成员的初始化。这种方法不在函数体内初始化数据成员,而是在函数首部实现。例3.2 我们改写成如下形式: #include <iostream.h> class box { private: int height, width, length; public: box(int h,int w,int len):height(h),width(w),length(len) { } int volume ( ) { return height * width * length; } }; void main ( ) { box box1 (1,2,3); cout <<“box1的体积为” <<box1.volume( ) << endl; box box2 (4,5,6); cout <<“box2的体积为”<<box2.volume( ) << endl; }
3.1 构造函数 示例中的初始化表表示,用形参h的值初始化数据成员height,用w值初始化width,用len值初始化 length。这种初始化方法比较简练,可以直接在类体中定义构造函数。
3.1 构造函数 • 构造函数的重载 一个类中,可以有多个构造函数,只要他们的参数表不同。以方便同类对象不同初始化的需要。见下例3.3 : #include <iostream.h> class circle { private: float radius; public: circle( );// 声明一个无参数构造函数 circle( float r ): radius (r) { } // 声明一个带参数构造函数 float area ( ) { return radius * radius * 3.14159; } }; circle::circle ( ) { radius=10.0; } void main ( ) { circle c1 ( 1.0 ), c2; cout <<“c1的面积为” <<c1.area( ) << endl; cout <<“c2的面积为” <<c2.area( ) << endl; }
3.1 构造函数 • 说明 • 参数表为空的构造函数叫默认构造函数,一个类中只能有一个默认函数。 • 定义对象时,如果想用默认构造函数初始化它,正确的定义形式为: circle c2; 而不是: circle c2 ( ); • 一个类尽管定义了多个构造函数,一个对象只能用其中一个来初始化。
3.1 构造函数 • 使用默认参数的构造函数 构造函数的参数既可以通过实参传送,也可以指定为某些默认值。当用户不指定实参值时,编译系统便将默认值为形参值。 在实际生活中,常有一些这样的默认值。如计数器的默认值为0;战士的性别默认值为“男”;男性职工退休年龄默认值为60岁等,如果实际情况不是默认值,则由用户另外指定。采用默认值,可以减少用户的输入量。 下面举例说明。
3.1 构造函数 例3.4 试将例3.3 的构造函数改用默认值的参数,半径值默认为1.0。 #include <iostream.h> class circle { private: float radius; public: circle( float r = 1.0 );// 声明构造函数是指定默认参数 float area ( ) { return radius * radius * 3.14159; } }; circle::circle ( float r ) //定义函数时,可不再指定默认参数 { radius = r; } void main ( ) { circle c1 (10.0), c2; cout <<“c1的面积为” <<c1.area( ) << endl; cout <<“c2的面积为” <<c2.area( ) << endl; }
3.1 构造函数 • 构造函数中使用默认参数的好处 • 提供建立对象时的多种选择,相当于好几个重载的构造函数。 • 即使在调用构造时不提供参数也不会出错,因为有默认参数值参与对象初始化。 • 当每一个对象都是相同的初始值时,非常方便,用户不需要输入数据。
3.1 构造函数 • 默认参数值的构造函数使用注意 • 何处指定默认参数值?构造函数的声明处还是定义处?应该在构造函数的声明处指定默认参数值。因为类的声明在头文件中,用户是看得见的,而防在函数的定义处,用户不一定看得见。 • 一个类定义了全部是默认参数的构造函数后,不能再定义重载的构造函数。否则会产生多义性,系统不知道调用哪一个。例如一个类有右边形式 的三个重载构造函数,若定义了如 下对象: circle circle1( ); 它调用哪一个构造函数呢?系统不 能确定,从而引起混乱。 circle ( float r=2.3 ); circle ( ); circle ( float );
3.2 析构函数 • 什么是析构函数? 析构函数( destructor ) 也是一个特殊函数,它的作用与构造函数相反,是在撤消对象占用的内存前进行一些清理工作。它还可以被用来执行“用户希望在最后依次使用对象之后所执行的任何操作”。 析构函数的名称是类名的前面加一个取反符号“~”。我们在类的声明中定义析构函数。如果用户不定义析构函数,系统便自动生成一个空的析构函数。 • 析构函数特点: • 没有返回类型; • 没有函数参数; • 不能被重载。
3.2 析构函数 • 什么时候运行析构函数? 当对象的生命结束时,会自动执行它的析构函数。具体而言,当出现以下几种情况,析构函数就会被执行。 • 如果在函数中定义了一个对象,当函数调用结束时,释放对象,释放对象前自动执行析构函数。 • static 局部对象在函数调用结束时,包含的对象不会被释放,只在main函数结束或调用exit函数时,才调用static局部对象的析构函数。 • 如果定义了一个全局对象,,则在程序的流程离开其作用域时(如main函数结束,或exit语句),调用该全局对象的析构函数。 • 如果用new运算符动态地建立了一个对象,当用delete 运算符释放对象时,先调用该全局对象的析构函数。
3.2 析构函数 例3.5 析构函数举例。 #include <iostream.h> class box { private: int height, width, length; public: box ( int h, int w, int len ) { height = h; width = w; length = len; } ~box ( ) // 析构函数 { cout <<“Destructing a object” << endl; } int volume ( ) { return height * width * length; } }; void main ( ) { box box1 (1,2,3); cout <<“box1的体积为” <<box1.volume( ) << endl; box box2 (4,5,6); cout <<“box2的体积为” <<box2.volume( ) << endl; }
3.3 调用构造函数和析构函数的顺序 • 调用构造函数和析构函数的顺序 先构造的后析构,后构造的先析构。 #include <iostream.h> class box { private: int height, width, length; public: box ( int h, int w, int len ) { height = h; width = w; length = len; cout <<“Constructor a object” << endl; } ~box ( ) // 析构函数 { cout <<“Destructoring a object” << endl; } int vol ( ) { return height * width * length; } }; void fn ( ) { box b1( 2,2,2 ); cout<<“b1=”<<b1.vol( ) << endl; static box b2( 3,3,3 ); // fn结束不析构b2,直到main结束 cout<<“b2=”<<b2.vol( ) << endl; } void main ( ) { fn( ); box b3 (1,2,3); cout<<“b3=”<<b3.vol( )<< endl; box b4 (4,5,6); cout <<“b4=”<<b4.vol( )<<endl; }
3.4 对象数组 数组不仅可以由简单变量组成,也可以由对象组成,即每一个数组元素都是同类的对象。 例如,一个班有30人,每个学生的属性包括学号、姓名、性别。我们建立起学生类后,为每个学生建立一个对象,需要分别取30个对象名,很不方便。较好的做法是,定义一个“学生类”,的对象数组,每一个数组元素是一个“学生类”的对象。 请看下面示例程序。
3.5 对象指针 • 指向对象的指针 创建一个类的对象时,系统会为没一个对象分配一定的存储空间,以存放成员。对象空间的起始地址就是对象的指针。可以定义一个指针,用来存放对象的指针: 访问成员的方法:( *pt ).hour, (*pt).put ( ) 或者:pt->hour, pt->put( ) class Time {public: int hour, minute, sec; void put ( ) { hour = 12; minute = 0; sec = 0; } }; void main ( ) {Time *pt, t1; pt = &t1; pt.put ( ); cout <<pt->hour<<“:”<<pt->minute <<“:”<<sec<<endl; cout <<(*pt).hour<<“:”<<(*pt).minute <<“:”<<(*pt).sec<<endl; }
3.5 对象指针 • 指向对象数据成员的指针 定义一个指向对象数据成员的指针变量的方法和前面介绍的定义指向普通变量的指针变量方法相同。 定义格式:数据类型名 * 指针变量名; 例如: int *pl; // 定义指向整形数据的指针变量 pl=&t1.hour; // 将t1的数据成员hour地址赋给指针pl,使其指向t1.hour cout << *pl <<endl; // 输出t1.hour的值
3.5 对象指针 • 指向对象成员函数的指针 定义指向对象成员函数的指针变量的方法和定义指向普通函数的指针变量的方法有所不同。 • 指向普通函数的指针变量的定义方法: 返回数据类型 (*指针变量名) (参数表列); 例:void (*p) ( ); // p是指向void 类型函数的指针变量 p = fun; // 将fun函数的入口地址赋给指针变量p (*p)( ); // 调用fun函数 而定义一个指向对象成员函数的指针变量则不能这样: p = t1.put; // 出现编译错误,不知道t1.put所属的类
3.5 对象指针 • 定义指向公用成员函数的指针变量方法: 返回数据类型 ( 类名:: *指针变量名) (参数表列); 例:void (Time::*p2)( ); // Time::*p2的括号必须加上 • 令指针变量指向一个公用成员函数的方法: 指针变量名 = & 类名::成员函数名; 例:p2 = & Time::put;
3.5 对象指针 #include <iostream.h> class Time {public: int hour, minute, sec; Time ( int h, int m, int s ) { hour = h; minute = m; sec = s; } void get_Time( ) { cout << hour<<“:” << minute<<“:” << sec<<endl; } }; void main ( ) {Time t1( 10,12,56 ); int *pl = &t1.hour; // 定义指向整形数据的指针,指向t1.hour cout << *p1 <<endl; t1.get_Time( ); // 调用t1的成员函数 Time *p2 = &t1; // 定义指向Time类对象的指针变量t2,并指向t1 p2->get_Time( ); //调用p2所指对象的成员函数 void (Time::*p3)( ); // 定义指向Time类公用成员函数的指针变量p3 p3 = &Time::get_Time; // 使p3指向Time类公用成员函数get_Time( ) (t1.*p3) ( ); //调用p3所指的成员函数t1.get)Time( ) }
3.5 对象指针 • this 指针 通过第二章的学习我们知道,多个同类对象在内存中是共享成员函数的,以节省内存空间。那么,不同对象的成员函数引用数据成员时,怎么找到自己的数据成员呢? C++在每一个成员函数中都包含一个特殊的指针,这个指针的名字是固定的,成为“this”。它是指向本对象的指针,它的值是当前被调用的成员函数所在对象的起始地址。 例如,当a的成员函数调用数据成员a.volume时,编译系统就把对象a的起始地址赋给 this 指针,于是在成员函数引用数据成员时,就按照 this 的指向找到对象a 的数据成员。
3.5 对象指针 比如下列涉及数据成员的运算的返回语句: return length*width*height ; 实际上C++处理为: return ( this ->length)*(this->width)*(this->height); 也可以写成如下形式: return ((*this).length*(*this).width*(*this).height); 但不能写成: return ((*this.length) *(*this.width)*(*this.height)); //错误 因为“.”操作符的优先级高于指针运算符“*”,(*this.length)就相当于*(this.length),而 this.length 是不合法的,编译出错(应该是this->length)。
3.6 共用数据的保护 C++采用了不少的数据保护措施。最常用的,是将数据成员设置成私有数据( private ),以增加数据的安全性和私密性。 但是,有时候要求数据在能共享的前提下能不能被篡改,我们就需要借助其他手段了。 什么手段呢?可以采用const,即把要保护的数据定义为常量。
3.6 共用数据的保护 • 常对象 在定义对象时指定对象为常对象。常对象中的数据成员为常变量,并且必须要有初值: Time const t1(12,34,56); 这样,在所有的场合中,对象t1 的所有数据成员都被保护了,不能被修改。因此,凡是希望数据成员不能被改变的对象,可以声明为常对象。其声明格式为: 类名 const 对象名(参数表列); 或者: const 类名 对象名(参数表列); 两者等价。
3.6 共用数据的保护 如果一个对象被定义成常对象,那么不能该对象的非const 型成员函数,当然,系统隐含调用的构造函数和析构函数除外。比如: Time const t1( 12,34,56); // 定义t1为常对象 t1.get_Time( ); // 错误,get_Tiem( )不是const 型,不能调用 为什么会这样?因为成员函数有可能修改数据成员,而成员函数的定义可能和成员函数的声明不在同一文件,系统没法检测。所以,系统只能统一拦截,不让用户调用常对象的成员函数,除非该成员函数被声明成 const 类型。
3.6 共用数据的保护 怎样才能引用常对象的成员函数呢?很简单,只需要把该成员函数的声明为 const 类型,即常成员函数就行了。方法为: 函数返回类型 函数名(参数表列)const; 比如:void get_Time( ) const; // 将函数声明成const 类型 常成员函数可以访问常对象中的数据成员,但仍然不准修改它们。 有时候编程要求必须修改某个常对象中的数据成员,如某个计数器count ,ANSI C++对此做了特殊的处理,将该数据成员 声明为 mustable,如: mustable int count; 这样,常对象的数据成员count,就可以用常成员函数来访问和修改了。
3.6 共用数据的保护 • 常对象成员 • 常数据成员:其作用和用法与一般常变量相似,在类的声明中,用关键词 const 来声明常数据成员,例如: const int hour; 注意:常数据成员的初始化,不能采用在构造函数中对常数据成员赋予初值的方法,只能通过构造函数的参数初始化表对常数据成员进行初始化。 在类外定义构造函数,初始化形式为: Time::Time ( int h ): hour ( h ) { } 在类中声明了某一个数据成员为常数据成员后,该类的所有对象中的该数据成员的值是不可改变的,但可以是不同的(由每个对象的参数初始化表决定)。
3.6 共用数据的保护 • 常成员函数 一般的成员函数可以引用本对象中的非const 数据成员,也可以修改它们。但如果成员函数声明为常成员函数,则只能引用本对象中的数据成员,而不能修改它们。如 void get_Time( ) const; // const位置在最后 const 是函数类型的一部分,在声明函数和定义函数时都要用到 const 关键字。 常成员函数可以引用常数据成员,也可以引用非const数据成员;而常数据成员可以被常成员函数引用,也可以被非 const 成员函数引用。够乱的,见90页表总结。
3.6 共用数据的保护 • 怎样使用常成员函数呢? • 类中如果一些数据成员需要保护,另一些数据成员不需保护,我们就将需要保护的数据成员声明为 const ,以保证其值不被改变。 • 类中如果所有数据成员都需保护,可以将所有数据成员都声明成 const ,本对象的任何成员函数,都只能引用,不能改变它们。或者将这些数据成员所属的对象声明成const,只让本对象的const 成员函数可引用,但不能改变。 • 如果一个对象被定义成了常对象,只能调用其中的const成员函数,不能调用非const成员函数。 • 常成员函数不能调用另一个非const成员函数
3.6 共用数据的保护 • 指向对象的常指针 • 什么时候需要常指针让其指向对象?如果想将一个指针变量与一个对象固定地联系在一起,以防止误操作;往往用常指针作为函数的形参,目的是不允许在函数执行过程中改变指针值,使其始终指向原来的对象。 将指向对象的指针变量声明为const ,并将之初始化,可以使指针值恒定为初始值,即其指向始终不变。如 Time t1(10,12,14), t2; //定义对象 Time * const ptr = &t1; // 定义常指针,恒定指向t1对象 ptr = &t2; // 错误,常指针不能改变指向 • 定义一个指向对象的常指针的格式为: 类名 * const 指针变量名 = 对象地址; • 指向对象的常指针,这个变量的值是一个对象的地址,他不允许被改变,但所指对象的内容可以改变。
3.6 共用数据的保护 • 指向常对象的指针 • 定义指向常对象的指针格式: const 类名 * 指针名; 这和C语言中指向常变量的指针是一样的。只需将上述格式中的类名换成类型名就行了。我们不妨在一起作如下讨论: • 如果一个变量被声明为常对象(/ 常变量),只能用指向常对象(/ 常变量)的指针指向它。如: const char c[ ] = “boy”; // 定义const型的常字符数组 const char * p1; // p1为指向常字符变量的指针 p1 = c; // 合法,p1指向常变量 char * p2 = c // 非法,p2不是指向常变量的指针 • 指向常变量的指针如果指向了非const对象,其指向的对象值不能用指针改变,可以用非指针方式改变。 Time t1(10,12,14); // 定义Time类对象t1,它不是常对象 const Time *p = &t1; // p是指向常对象t1 的指针 t1.hour = 18; // 合法,t1不是常量 (*p).hour = 18; // 非法,不能通过指针改变t1的值
3.6 共用数据的保护 • 用指针变量作形参时,形参和实参的对应关系见下表
3.6 共用数据的保护 • 例如: const char str[ ] = “boy”; // str是常数组名 void func(char * ptr); // 函数形参为指向非const变量的指针 func (str); // 非法,实参是常变量指针,形参是非const变量指针 • 出错原因: 形参是非指向非const型变量的指针,在函数中可以也可能改变指针所指变量值,而实参是指向常变量的指针,不能被改变,发生矛盾。
3.6 共用数据的保护 • 指向常对象的指针最常用于函数的形参,目的是保护形参指针所指的对象,使它在函数执行过程中不被修改。如 void main( ) { void func(const Time * p); // func的形参是指向常对象的指针 Time t1(10,12,14); func(&t1); //合法,实参是 非const 对象t1 的地址 return; } void func( const Time * P) { p->hour = 18; // 错误,指向的常对象被修改了 cout<<p->hour<<endl; } 如果func形参不是指向const 型的Time对象的指针,即没有const限定词,则t1的hour就可以被修改。
3.6 共用数据的保护 有人可能会问,我不将func函数的形参定义为const型对象,只将 t1 定义为const 型对象,不是也能保护 t1 在func中不被篡改吗? 不行,会出现编译错误。因为指向非const 对象的指针不能指向const 对象;同理,指向非const 变量的指针不能指向const 变量。 • 不能通过指向常对象的指针改变所指对象的值,但指针变量本身的值可以改变。
3.6 共用数据的保护 • 何时使用指向常对象的指针?何时使用常对象? • 当希望在调用函数时对象的值不能被修改,就应该把形参定义为指向常对象的指针,同时用对象的地址做实参,而实参对象可以是const型,也可以是非 const型。 • 如果要求对象不仅在函数调用过程中不被修改,而且在整个程序运行时不被修改,就应该把该对象定义成常对象。
3.6 共用数据的保护 • 对象的常引用 前面讲过,引用主要是用于函数调用,将改变后的变量值带回到被调用的函数外。 但如果不希望在函数中修改参数,可以把引用型形参定义成 const 型: 函数返回类型 函数名( const 形参类型 & 形参名); 则在函数中不能改变形参值,也就不能改变对应的实参值。 • 什么时候使用常指针和长引用?使用常指针或常引用作为函数参数,既能保证数据安全,不被修改;调用函数时又能不必建立实参的拷贝,提高了程序运行效率,节省了内存空间。
3.6 共用数据的保护 • const 数据小结
3.7 对象的动态建立和释放 • 问题的提出:前面我们所学创建对象的方法都是静态的,它们在程序 运行过程中所占用的内存空间不能被释放。比如,在一个函数中定义了一个对象,只有在函数结束时,该对象才能被释放。 有时候,人们希望对象的创建和释放是在程序运行时,由运行程序的人决定,比如,链表结构的建立和删除,这就需要动态建立和释放对象。 C++语言用new、delete 这两个运算符来实现。内存的分配和释放,也用来实现对象的建立与撤消。
3.7 对象的动态建立和释放 • 动态建立对象的方法: 如果已经定义了一个 Box 类,可以用new 运算符动态地创建一个 Box 对象: new Box; 系统执行此语句时,首先开辟一段内存空间,并在此空间中存放一个 Box 类对象,同时运行该类的构造函数,以初始化该对象,然后返回一个指向该对象的指针值。 但此时用户还不能访问这个对象,因为它既没有对象名,用户也不知道它的地址。这种对象称为“无名对象”。我们应该这样做: Box *pt; // 定义一个指向Box类对象的指针pt pt = new Box; // 创建一个Box对象,将新对象的指针值赋给pt cout << pt->height <<endl;
3.7 对象的动态建立和释放 用new 动态创建的对象一般没有对象名,只能通过指针访问。 在执行new 运算时,如果内存不足,则创建失败。大多数C++编译系统都让 new 返回一个0指针,表示内存不足,操作失败。 • 动态撤消对象的方法:由new 创建的对象不再需要时,可以由delete运算符释放。上例pt的释放方法为: delete pt; 这样就撤消了pt指向的对象。 在执行 delete运算符时,在释放空间以前,系统自动调用析构函数,完成有关善后清理工作。
3.8 对象的赋值与复制 • 对象的赋值 • 对象之间的赋值是通过运算符“=”进行的,其过程是将一个对象的成员复制给另一同类对象的对应成员。 • 对象赋值格式: 对象名1=对象名2 • 对象的复制 • C++可以根据一个已知的对象快速地复制出多个完全相同的对象。比如: Box box2(box1); 其作用就是对象的克隆,即用一个已知的对象 box1复制出一个完全相同的新对象 box2。 • 对象复制的格式: 类名 被克隆出来的新对象名(已有的对象名);