chapt3
2020-03-01 124浏览
- 1.第 3 章 类与对象(续)
- 2.3.1 构造函数 (constructor) 19/5/14 2
- 3.3.1.1 对象的初始化 对象初始化的问题 类声明时不能赋初值 why 定义对象时赋初值 , 私有 / 受保护成员变量 怎么办? 程序员定义对象时忘了赋初值怎么办? 程序员会不会多次调用赋初值函数,导致多 次初始化? 19/5/14 3
- 4.3.1.2 构造函数 C++ 语言的构造函数是一种特殊的成员函数 , 其特 征是 : 构造函数的名字必须与类名字相同。 构造函数不能指定返回类型,即使是 void 类型也不允许。 一个类可以由多个构造函数,即:可以通过重载使一个类 具有多种初始化方法。 当程序中声明一个新对象时,程序会自动(隐式)调用默 认的构造函数来初始化这个对象的状态。 如果我们设计的类没有定义构造函数, C++ 编译程序会 自动为该类建立一个缺省的构造函数。这个缺省构造函数 没有任何形式参数,并且函数体为空。 19/5/14 4
- 5.3.1.3 构造函数的调用时机 全局对象的构造函数在 main( ) 函数执行之前就被 调用。 全局静态对象的构造函数在 main( ) 函数执行之前 被调用。 局部静态对象的构造函数是当程序第一次执行到相 应的声明语句时才被调用。 应出 一个外部 应应象的 应引用性声明时,并不调用 相应 应 的 构造函数,因为这个外部对象只是引用在其他地方 声明的对象,并没有真正地创建一个对象。 动态创建对象时(后面讲)。 定义对象数组时(后面讲) 。 19/5/14 5
- 6.3.1.4 构造函数的重载 当一个类有多个构造函数时,必须构成重载 应 系 ,主要有: 无参数构造函数 应参 数构造函数 缺省参数构造函数 19/5/14 6
- 7.3.1.5 初始化列表 类的引用成员如何初始化? 基类的带参数构造函数,如何调用? ( 后面 讲) 没有无参数构造函数的对象型数据成员如何 初始化 ? 19/5/14 7
- 8.3.1.5 初始化列表 初始化列表的参数表一般来自“参数表 0” , 也可以使用任意复杂的表达式或函数调用。 参数为空则可以省略。 初始化列表也用来对一般的数据成员进行初 始化 : 无参数的对象成员 , 简单类型的数据成 员。 有参数的对象成员的初始化,初始化引用成 员 , 初始化常量对象 , 显式调用基类的构造 函数则必须用初始化列表来完成 . 19/5/14 8
- 9.3.1.5 初始化列表 对象成员构造函数的调用次序取决于这些成员 类中声 在 明的次序,与它们在初始化列表中出现的次序无 关。 创建类 C 的一个新对象时,程序将先完成初始化列 表的初始化工作后 ( 包括初始化列表中的对象成员的 构造函数 ) 后 , 再对初始化列表中没有列出的数据 成员进行初始化,最后执行类 C 的构造函数体内的 应 句 。 对象成员析构函数的调用次序与构造函数刚好相反 。 19/5/14 9
- 10.3.1.6 什么时候必须定义无参数构造 函数 前提:类定义了带参数的构造函数 ( 注意缺 省值情况 ) ,则在以下情况必须定义务参数 构造函数: 定义对象数组时。 作为基类且派生类没有显式调用这个基类的 构造函数。 定义对象且没有显式初始化时。 19/5/14 10
- 11.3.2 析构函数 C++ 语言的析构函数是一种特殊的成员函数,用于 在对象撤销时执行一些清理任务,并释放出构造函 数分配的内存等,其特征是: 析构函数名字必须是在类名 前面加上一个波浪号“ ~” 。 析构函数不能指定返回类型,即使是 void 应型 也不允 应应。 一个类只能有一个析构函数。 析构函数不可指定任何形式参数。因为创建对象是由程 序员指定的,而撤销对象是在对象生命期结束时由程序 自动完成的,因此程序员无法提供析构函数的实际参数 。 19/5/14 11
- 12.3.2.1 析构函数的作用 用户希望在对象撤销前执行的操作,如: 1. 对相同类的对象个数进行记帐 2. 释放对象占用的资源,主要是堆内存 ( 用 n ew 申请的 ) ,文件句柄等 3. 其他任何希望做的事情 19/5/14 12
- 13.3.2.2 析构函数的调用时机 程序员手工释放动态生成的对象 对象的生存期结束前 19/5/14 13
- 14.3.3 调用构造函数和析构函数的顺序 完全相反 19/5/14 14
- 15.3.4 应 象 数 应 CStudent students[10]; CStudent students[3] ={60,70,80}; ***P82*** CStudent students[2]={ CStudent(1,2,3), CStudent(4,5,6), CStudent(7,8,9), }; 19/5/14 15
- 16.3.5 对象指针 对象指针就是指针的基类型为类的指针,如 : 类名 * pObject = NULL; 对象的指针指向对象空间的起始地址 对象的起始地址可以作为对象指针使用 19/5/14 16
- 17.3.5.1 指向对象的指针 类名 * 指针名 = NULL ; 如: CStudent * pStudent = NULL; CStudent tom; pStudent = &tom cout
- 18.3.5.1 指向对象的指针——特例 this 只能在非静态成员函数中使用 ( 非静态 ) ,tom.this 是没有意 义的。 this 如何应 应 第 的,作应 一 函数的 ” 个参数 ”. 指向的是调用这个函数的对象 , 即在在调用成员函数时,把对 象的地址赋值给 this 。 对于构造函数和析构函数,指向的是正在构造或析构的对象 。 在构造函数中,不能用 this 指针调用派生类重定义的虚函 数,因为在基类的构造函数执行期间, “对象还不是一个 派生类的对象 ”。 类的成员函数返回对象自身或对象自身的指针,必须使用 thi s。 如果成员函数的形参和成员变量同名,则需要使用 this 19/5/14 18
- 19.3.5.1 指向对象的指针——特例 this 成员函数的调用机理: CStudent tom; tom.getage(); CStudent::getage(&tom);Demo:tom.getage() ; const member funct ion; static member function call non static fu cntion 19/5/14 19
- 20.3.5.1 指向对象的指针——特例 this 可以这样理解: “this 在对象创建时 / 初创建,放在栈上”,不 占用对象空间,对象生命周期只有一个 this 指针。 “ 成员函数调用前创建,调用后撤销”。 VC6.0 中对象地址放在 ecx 寄存器中,在函 数调用时,写入 ebp-4 中,即 this 的位置。 19/5/14 20
- 21.3.5.2 指向对象的数据成员的指针 CStudent{public:int m _age; float m_weight;} ; 成员变量的类型 * point2membervarible; CStudent tom; int * p_m_age; float * p_m_weight; p_m_age = &tom.m_age; p_m_weight = &tom. m_weight; 与指向普通变量的指针相同。 19/5/14 21
- 22.3.5.3 指向成员函数的指针 指向普通函数 的指应应 : 函数类型, 形参 int (*pFunc)(); int * (*pFunc2)(double, int); int hello(); int * power(double, int); pFunc = hello; pFunc = &hello pFunc2 = power; pFunc2 = &power 19/5/14 22
- 23.3.5.3 指向成员函数的指针 CStudent{public:int m _age; void setage(int age){m_age = age; } void (CStudent::*pFunc3)(int);CStudent john; pFunc3 = & john.setage; pFunc3 = & CStudent ::setage; (john.*pFunc3)(17); 19/5/14 23
- 24.3.6 const 在类与对象中的应用 常对象 const 类名 对象 [( 实参列表 )] ; 类名 const 对象 [( 实参列表 )] ; 常成员函数 / 成员变量 const 类型 成员变量; 类声明时,函数后加 const : int getage() const; 常对象调用常成员函数 / 变量,不需要进行任何转换 。 普通对象调用常成员函数 / 变量,需要进行转换。 19/5/14 24
- 25.3.6 const 在类与对象中的应用 指向常对象的指针变量: const 类名 * pPoint2ConstObj; 理解为: const 类名 (* pPoint2ConstObj); const CStudent tom, alice; const CStudent *pStudent = &tom pStudent = &alice //ok pStudent->m_temp =100; //error 如果一个对象为常对象,只能用指向常对象 的指针变量指向它。 19/5/14 25
- 26.3.6 const 在类与对象中的应用 指向对象的常指针: 类名 * const pConst_ObjPoint; 理解为:类名 * (const pConst_ObjPoint); CStudent tom, alice; CStudent * const pStudent = &tom pStudent = &alice //error pStudent->m_temp =1980;//ok 19/5/14 26
- 27.3.6 const 在类与对象中的应用 指向常对象的常指针 const 类名 * const pConst_ObjPoint; 理解为: const 类名 * (const pConst_ObjPoin t); const CStudent tom, alice; const CStudent * const pStudent = &tom pStudent = &alice //error pStudent->m_temp =1980;//error 19/5/14 27
- 28.3.6 const 在类与对象中的应用 对象的常引用 const 类名 &rObject= object; rObject.m_member= value; //error 多用于形参 19/5/14 28
- 29.3.7 对象的动态创建与释放 类名 *pObject = new 类名 [<( 实参 )>] ; delete pObject; 类名 *pObjectArray = new 类名 [int] ; // 必须有无参数构造函数 delete [] pOjectArray; 19/5/14 29
- 30.3.8 拷贝构造函数与对象的复制 拷贝构造函数 (copy constructor) 是一种特殊的构造 函数,其形参为本类对象的引用 ( 为什么必须是引用 呢? ) ,形如: class 类名 { public : 类名 ( 形参 ) ; // 构造函数 类名 (const 类名 & 对象名); // 拷贝构造函数 }; 类名 :: 类名 (const 类名 & 对象名 )// 拷贝构造函数的 实现 { 19/5/14 函数体 } 30
- 31.3.8 拷贝构造函数与对象的复制 缺省拷贝构造函数 (default copy construct or): 所有类都必须有一个拷贝构造函数。如果程序员 没有为一个类定义拷贝构造函数,那么编译器将 自动产生一个公有的缺省拷贝构造函数。 如果程序员为一个类定义拷贝构造函数,那么编 译器不再生成缺省拷贝构造函数。 19/5/14 31
- 32.3.8 拷贝构造函数与对象的复制 什么情况下调用拷贝构造函数 ? 形如 classname(const classname& obj) 的拷 贝构造函数表示用相同类型的对象 obj 来初 始化一个新创建的对象实例,主要在以下三 种情况下起初始化作用: 19/5/14 32
- 33.3.8 拷贝构造函数与对象的复制 1. 在声明语句中用一个对象初始化另一个对象; CPerson q("Mickey"); // constructor is used to bui ld q. CPerson r(q); // copy constructor is used to build r. CPerson p = q; // copy constructor is used to in itialize //in declaration. p = q; // Assignment operator, no constructor or copy constructor. 19/5/14 33
- 34.3.8 拷贝构造函数与对象的复制 2. 将一个对象作为参数按值调用方式传递给另 一个对象时生成对象副本; f(object); // copy constructor initializes for mal value parameter 3. 生成一个临时对象作为函数的返回结果. 19/5/14 34
- 35.3.8 拷贝构造函数与对象的复制 为什 么需要拷构造函数? 贝 没有不行吗? 应 例 19/5/14 35
- 36.3.8 拷贝构造函数与对象的复制 深复制、浅复制 浅 obj1 复 制 obj2 19/5/14 X Y pInt 堆对象 X Y pInt 36
- 37.3.8 拷贝构造函数与对象的复制 因为缺省拷贝构造函数使用浅复制方式 (sha dow copy 逐位复制 ) 利用另一个对象来初始 化新创建对象的状态,如果我们需要另外的 复制策略就需要自己设计一个拷贝构造函数 。 深拷贝 (deep copy) :应 每个 应 应应 应 象独立分配 应应应 应 一个堆对象,称深拷贝。即:先拷贝对象主 体,再应 obj2 分配一个堆对象,最后用 obj1 的堆对象拷贝 obj2 的堆对象。图示如下 19/5/14 37
- 38.3.8 拷贝构造函数与对象的复制 拷贝前 深 obj1 复 制 obj2 19/5/14 X Y pInt 堆对象 X Y pInt 堆对象 38
- 39.3.8 拷贝构造函数与对象的复制 拷贝构造函数的参数为什么必须是引用类 型? 拷贝构造函数为空,会发生什么,会执行 浅拷贝吗?? 含有常量成员和引用成员的类为什么必须 显式声明拷贝构造函数? 19/5/14 39
- 40.3.8’ 赋值运算符重载与对象赋值 CStudent s1, s2; … S1=s2; 赋值只对数据成员进行!函数成员不进行 赋值。 会不会存在类似对象赋值中的问题? 19/5/14 40
- 41.3.8’ 赋值运算符重载与对象赋值 在未对赋值运算符 ” =” 重载的情况下 ( 下一 章讲 ) ,运用对象赋值时:类不能含有指向 动态分配内存的数据成员。如果有这样的 成员,请使用对象复制。 19/5/14 41
- 42.3.9 静态成员 静态成员变量、成员函数的声明 class CStudent{public:CStudent(){m_s_count++;} ~CStudent(){m_s_count--;} static int getobjectcount(){return m_s_count;}private:static int m_s_count; } 19/5/14 42
- 43.3.9 静态成员 1. 静态成员变量的初始化:类的外面 2. 3. 4. 5. intCStudent::m_s_count=0; 静态成员函数只能使用静态成员变量 静态成员变量和成员函数可以在没有任何对 象的情况下使用 类名 :: 静态成员;对象 . 静态成员 ; 静态成员函数中不可以使用 this 指针 普通成员函数可以使用静态成员 19/5/14 43
- 44.3.10 友元 友元函数 一般情况下,类中私有和保护的成员在类外不能直 接被访问。 友元函数是一种定义在类外部的普通函数,其特点 是能够访问类中私有成员和保护成员,即类的访问 权限的限制对其不起作用。 友元函数很简单:只需声明时在函数前面加 friend 关键字,通常有一个形参为声明这个函数为友元的 类类型。 19/5/14 44
- 45.3.10 友元 class A{ int m_a; public: friend int Afriend(A& obj); }; int Afriend(A& obj){return obj.m_a;} 19/5/14 45
- 46.3.10 友元 友元函数不是成员函数,用法也与普通的函数完全 一致,只不应 它能 应应应应应应 中所有的数据。友元函数 应应应应应应应应应应 破坏了 类的 封装性和蔽性,使得非成 隐 员函数可以 访问类的 私有成 员 。 一个类的友元可以自由地使用该类中的所有成员。 友元函数近似于普通的函数,它不带有 this 指针 ,因此必须将对象名或对象的引用作为友元函数的 参数,这样才能访问到对象的成员。 友元函数可以是一般函数,也可以是类的成员函数 。 19/5/14 46
- 47.3.10 友元 友元函数不受类中访问权限关键字的限制, 可以把它放在类的私有部分,放在类的公有 部分或放在类的保护部分,其作用都是一样 的。换言之,在类中对友元函数指定访问权 限是不起作用的。 通常使用友元函数来获取对象的数据成员的 值,而不修改对象的成员的值,则肯定是安 全的。 19/5/14 47
- 48.3.10 友元 class A{ float x,y;public:A(float a, float b){ x=a; y=b;} float Sum(){ return x+y; } friend float Sum(A &a){ return a.x+a.y; }; void main(void) { A t1(4,5),t2(10,20); cout<49.3.10 友元 class B ; // 定义类 A 前,则首先对类 B 作引用性说明 class A{ ...... // 类 A 的成员定义public:void fun( B & );// 函数的原型说明 }; class B{ ...... Int m_private; friend voidA::fun('>A::fun(
- 49.3.10 友元 class B ; // 定义类 A 前,则首先对类 B 作引用性说明 class A{ ...... // 类 A 的成员定义public:void fun( B & );// 函数的原型说明 }; class B{ ...... Int m_private; friend voidA::fun('>A::fun(