340 likes | 422 Views
第六章 类. 类的引入 类的具体语法形式 构造函数和析构函数 this 指针、静态成员和常量成员 友元和嵌套类 对象数组和指针间的关系. C 是 C++ 的子集, C++ 具备了封装、继承和多态性等主要面向对象特征。 当退化为 C 时,处理要求高效率的地方和 C 完全相同;强调面向对象特性时,一般比 C 慢百分之十几。. C++ 更好的 C: C++ 简洁的单行注释; 严格的参数检查; 用引用传递参数; 灵活的局部变量说明( C 是在任何执行语句前); 内联函数(凡是有循环语句或 switch 语句的函数不能成为内联函数);
E N D
第六章 类 类的引入 类的具体语法形式 构造函数和析构函数 this指针、静态成员和常量成员 友元和嵌套类 对象数组和指针间的关系
C是C++的子集,C++具备了封装、继承和多态性等主要面向对象特征。C是C++的子集,C++具备了封装、继承和多态性等主要面向对象特征。 当退化为C时,处理要求高效率的地方和C完全相同;强调面向对象特性时,一般比C慢百分之十几。 C++更好的C: C++简洁的单行注释; 严格的参数检查; 用引用传递参数; 灵活的局部变量说明(C是在任何执行语句前); 内联函数(凡是有循环语句或switch语句的函数不能成为内联函数); 常量修饰,包括几种常量指针; 空间申请和释放; 数据的抽象和封装: 传统的程序设计语言强调“功能”(函数、操作),数据的功能不如功能大,只提供几种内部数据类型; C++提升了数据的重要性,建立新的数据类型和表示出这些数据类型的对象之间的交互性是重要的。
数据量大时,效率低 查错困难 局部变量隐藏在函数体内;通过函数的参数传递共享变量。 使用全局变量,程序中每个函数都可以访问。 6.1 类的引入 类是结构的扩展,可用来对外部函数访问的限制。 使用静态全局变量,局限于文件。 stack.h void push(char); char pop(); stack.cpp #include”stack.h” static char v[100]; static char *p=v; void push(char c) { //…… } char pop() { //…… }
函数 pop v 函数 p push 公共接口 私有数据 使用多个堆栈要重新定义 other.cpp include <iostream.h> include “stack.h” void some_function() { push(‘c’); char c=pop( ); } 外部函数通过公共接口访问数据,而不必关心数据的存储形式。但 存放的 是具体的数据,而不是数据的描述。 结构是对数据描述,可创建多个对象。
破坏了数据的封装特性 堆栈的结构版本: struct stack { char v[100]; char *p; void push(char c) { //…… } char pop() { //…… } }; 堆栈的使用: stack sta1, sta2; sta1.p=sta1.v; sta2.p=sta2.v; sta1.push(‘c’); 外部函数可直接访问结构的成员。 C++引入类解决上述问题(限制全局变量使用和构成新类型)
除该类的成员函数外,谁都不得访问 程序中所有函数都可访问 可以省去 6.2 类定义 与结构不同之处是成员访问说明符。 堆栈的类版本: class stack { private: char v[100]; char *p; public: void push(char c) { //…… } char pop() { //…… } }; 类的实例也就是所谓的对象: stack sta1; stack *pstack=new stack;
类的定义,初始化无效 类的使用: void main( ) { stack sta; sta.p=sta.v; sta.push(‘a’); char ch=sta.pop(); } 一个类的普通成员数据,在不同的对象中占用不同的空间;但成员函数不管在哪个实例中调用,都占用同一空间。 可用一特殊成员函数(构造函数)初始化,每当创建对象时系统自动调用 此函数。 名字与类相同,无返回值,公有成员函数 6.3 构造函数和析构函数 class c { int n=0; int rint=n; //…. }
1、为sta分配空间 2、调用构造函数 无参,缺省构造函数 类外定义函数,要用:: class stack { private: char v[100]; char *p; public: stack( ) { p=v; } void push(char c); void pop(); }; 类的使用: void main( ) { //…… stack sta; sta.push(‘a’); char ch=sta.pop(); }
还可以给一缺省值 构造函数可以重载。例如:即可以有缺省构造函数,同时又有一个带参构造函数,这时要注意二义性。 v数组是动态分配的,出作用域并不自动释放。析构函数 类名前加~,无返回值,无参数(不能重载) 构造函数可带参数: class stack { private: char *v; char *p; int size; public: stack( ) { p=v; } stack( int sz ) { v=new char[size=sz] ; p=v; } //…… }; 例如: ~stack( ) { delete [ ] v; }
构造函数和析构函数的调用时间: 6.4 类成员 6.4.1 this 指针 在创建类对象后,用 . 和访问数据成员和成员函数。例如: CTest test1; CTest *ptest2=new CTest; //…… cout<<test1.n<<endl; cout<<ptest2->n<<endl;
C++用关键字this可访问这个指针。this 的定义形式为: T * const this; 因此, getn( )中的return n; return this->n; 编译器引用的是哪个实例对象的n? 而在成员函数内: class CTest { public: int n; int getn( ) { return n; } }; 实际上编译器给成员函数传递一个隐藏的对象指针,指向函数调用所要引用的对象。 函数隐式地使用这个指针。例如: CTest test; test.getn( ); 编译器传给getn( )一个隐藏的指向test对象指针, getn( )使用这个指针访问test的成员n。
冒号,一个或多个用逗号分开的成员初始化符 初始化符表 数据成员名和括起来的初始值 成员初始化符 6.4.2 成员对象和成员对象指针 一个数据成员定义为另一个类的对象——成员对象 创建类对象时,也要创建成员对象;用构造函数初始化嵌套对象时,也要用成员对 象的构造函数初始化成员对象。用初始化符表传递成员对象构造函数所需的参数。 class CEmbedded { public: CEmbedded( int Param1, int Param2){ //…… } }; class CContainer { private: CEmbedded embed; public: CContainer( int p1, int p2, int p3):embed(p1,p2){ //…… } };
用成员初始化符表初始化其它数据类型 ——常量和引用不能在构造函数中用赋值初始化。 class C { private: int n; const int cInt; int & rInt; public: C(int param):n(param), cInt(5), rInt(n) { //…… } }; 语句:C cobject(0); 使cobject的n和cInt被初始化为0和5,数据成员rInt初始化为n的别名。
new不仅有类型,还要跟上初始化类型对象的参数 . 初始化,用函数调用形式. 一个数据成员定义为另一个类的对象的指针——成员对象指针: class table { public: table( ) { //… } table(int size) { //…… } }; class classdef { table *members; table * friends; int no_of_members; public: classdef(int size); ~classdef( ); }; 生成类对象时并不一定要生成所指对象,不必用成员初始化符表,而是在生成它所指对象时再初始化。 classdef::classdef(int size) { members=new table(size); friends=new table; no_of_members=size; } classdef::~classdef( ) { delete members; delete friends; }
类中声明 类外定义和初始化 无论CTest创建多少个实例,count将严格只存放一个拷贝。 6.4.3 静态类成员 class CTest { public: static int count; //…… }; int CTest::count=0; //…… 由于这种静态类成员独立于任何类对象存在,用::定义,而无需引用类实例。Static 成员和整个程序作业一样持久,但作用域仅限于此类。另外,其它访 问也是受控的(公有、私有、保护的)。 静态成员函数: class CTest { public: static int getCount( ) { //…… } //…… }; void main( ) { int count=CTest::getCount( ); //…… }
因为无this指针,企图访问非静态成员,编译器无法判定它所要访问的数据成员是属于哪个类的。因为无this指针,企图访问非静态成员,编译器无法判定它所要访问的数据成员是属于哪个类的。 静态成员函数只可以引用属于该类的静态数据成员或静态成员函数。 #include<iostream.h> class CTest { private: static int count; public: CTest( ) { ++count; } ~CTeat( ) { --count; } static int getCount( ) { return count; } }; int CTest::count=0; 类外代码用类名和作用域分辨符调用,无需引用具体实例,甚至类实例可以不存在。 void main( ) { cout<<CTest::getCount( )<<“object exist\n”; CTest test1; CTest *ptest2=new CTest; cout<<CTest::getCount( ) <<“object exist\n”; delete ptest2; cout<<CTest::getCount( ) <<“object exist\n”; }
6.4.3 常量类成员 class con { private: int i; const int c1; public: const int c2; con(int i1, int i2) :c1(i1),c2(i2) { c1=1; i=c1; } }; void some_fun( ) { con c(1,10); int i=c.c2; c.c2=i; i=c.c1; }
成员函数也可声明为常量类型(在函数声明的末尾加const)——常量成员函数成员函数也可声明为常量类型(在函数声明的末尾加const)——常量成员函数 在常量成员函数中不能有修改类中成员的语句。可修改局部变量、全局变量或其它类对象的值。 class X { int m; public: int readme( ) const { int i; i=m; m++; return m; } void writeme(int i) { m=i; } }; 为了保证常量对象的数据成员不被非法修改,常量对象应只调用常量成员函数: void f( X& mutable, const X& constant) { mutable.readme( )i; mutable.writeme(7); constant.readme( ); constant.writeme( 7); }
常量成员函数不能修改引用它的对象是由于this指针为const * const类型。若对其强制类型转换,则可修改成员的值。如: int readme( ) const { ((X *)this)->m++; return m; }
6.5 友员、嵌套类和结构 6.5.1 友员 友员给予别的类或非成员函数访问私有成员权利。 在此类中用关键字friend声明,公有或私有区声明都行。 函数作为友员: class t1 { private: int data; friend void friend_t1( ti fri ); public: t1( ) { data=12; } }; void friend_t1( ti fri ) //外部函数 { fri.data=10; }
void t1::use_friend( ) { t1 fri; this->friend_t1(fri); ::friend_t1(fri); } void main( ) { t1 fri, fri1; fri.friend_t1( fri ); friend_t1(fri); } 友员不是成员—— 不能用this指针访问它。
另一类的成员作为友元: class x { public: void f( ); private: int i; }; class y { friend void x::f( ); int i; public: //…… }; void x::f( ) { y yi; yi.i=10; }
函数 pop v 函数 p push 公共接口 私有数据 友元 整个类作为另一个类的友元: class x { friend class y; //…… };
函数elem( )调用效率低 将multiply定义为matrix和vector的友员 举例:向量和矩阵的乘积 class vector { public: float elem(int i); }; class matrix { public: float elem(int i, int j); }; vector multiply(const matrix &m, const vector &v) { vector r; for(int i=0; i<=3; i++) { r.elem(i)=0; for(int j=0; j<=3;j++) r.elem(i)+=m.elem(i, j)*v.elem(j); } return r; }
6.6 对象、指针和数组 6.6.1 对象数组 对象数组存放一组对象。在创建时,数组的每一个对象都调用缺省构造函数进行 初始化。若此类只有带参构造函数,使用初始化符表。 class ant { public: ant(char *name){ //…… } }; int main( ) { ant colony_number[4]={ ant(“ant1”), ant(“ant2”), ant(“ant3”), ant(“ant4”)}; //…… }
所指成员的类型 所指成员所属的类 指向数据成员的指针一般形式: 类型 类名:: *指针名; 6.6.2 指向数据成员的指针 class A { int a; public: int b; int c; }; int A:: *p_A_int=&A::a; int A:: *p_A=&A::b; A a1; a1.*p_A=2; p_A_int=&A::c; a1.*p_A_int=3; A *pA=&a1; pA->*p_A_int=4;
指向成员函数的指针一般形式: 返回类型 (类名:: *指针名)(参数类型) class X { public: int mf1(char*); int mf2(int); }; class Y { public: int mf3(char*); }; int (X:: *pmf_int_cp) (char*); pmf_int_cp=&X::mf1; X x1; int p1=(x1.*pmf_int_cp) (“string”); pmf_int_cp=&X::mf2; pmf_int_cp=&Y::mf3;
6.7 一个完整的类 //CRECT.h #ifndef _CRECT_HPP #define_CRECT_HPP class CRectangle { private: int Left; int Top; int Right; int Bottom; public: CRectangle( ) {Left=Top=Right=Bottom;} CRectangle(int L, int T, int R, int B) { setcoord(L,T,R,B); } void draw(void);
void getcoord(int *L, int *T, int *R, int *B) { *L=Left; *T=Top; *R=Right; *B=Bottom; } void setcoord(int L, int T, int R, int B); }; inline int max(int p1,int p2) { return p1>p2?p1:p2; } inline int min(int p1,int p2) { return p1<p2?p1:p2; } #endif
// CRECT.CC #include”CRECT.h” extern “C” { #include <stdlib.h> } void Line( int X1, int Y1, int X2, int Y2); void CRectangle::draw(void) { Line(Left, Top, Right, Top); Line(Right, Top, Right, Bottom); Line(Right, Bottom, Left, Bottom); Line(Left, Bottom, Left, Top); } void CRectangle::setcoord(int L, int T, int R, int B) { L=min(max(0,L), 80); T=min(max(0,T), 25); R=min(max(0,R), 80); B=min(max(0,B), 25); R=max(R, L); B=max(B, T); Left=L; Top=T; Right=R; Bottom=B; }
练习题1:给出程序运行结果 #include<iostream.h> class Sm { public: static int n; }; int Sm::n=2; void main( void ) { Sm ie1, ie2; cout<<ie1.n<<“ “; ie2.n=3; ie1.n=5; cout<<ie2.n; } 练习题2:改正错误 #include<iostream.h> class A { public: int i; void set( int ); void diap( ) { cout<<i<<ednl; } };
void set (int si) { i=si; } void main ( ) { A ob; ob.set(2); i=1; ob.disp( ); } 练习题3:改正错误 class A { int x; const int y; static int z; public: A( int a, int b ) { x=a; y=b; } }; void f ( ) { A::z=10; //…… }
练习题4:改正错误 class A { private: int n; friend void fri_A( A b); public: A( int c=0) { n=c; } void use_fri( ) { A a1; this->fri_A(a1); } }; friend void fri_A(A b) { b.n=10; } void main( ) { A a2, a3; a2.fri_A(a3); use_fri( ); } 练习题5:改正错误 const int a=78; char *const pc=“abcd”; const int *pi=&a; *pi=68; *(pc+1)=‘c’; *pc++=‘y’;
6.5.2 嵌套类 一个类的内部给出另一个类的定义: class X { class M1 { int m; }; public: class M2 { int m; }; M1 f(M2); }; M2公有嵌套类,外部函数用::指明作用域后才能使用。 void f ( ) { M1 a; M2 b; X::M1 c; X::M2 d; }