470 likes | 615 Views
第六章进一步使用成员函数. 静态成员 友元函数 const 对象和 volatile 对象 转换函数 指向类成员的指针 数组和类. 静态成员. 静态成员:使用关键字 static 修饰的类的数据成员和成员函数 格式: static 数据类型 数据成员名; 举例: class MyClass{ public: static int count; // 引用性说明 static void func(); int x; };. 静态数据成员. 特点:.
E N D
第六章进一步使用成员函数 静态成员 友元函数 const对象和volatile对象 转换函数 指向类成员的指针 数组和类
静态成员 • 静态成员:使用关键字static修饰的类的数据成员和成员函数 • 格式:static 数据类型 数据成员名; • 举例: • class MyClass{ public: static int count; //引用性说明 static void func(); int x; };
静态数据成员 • 特点: • 无论建立多少个类的对象,都只有一个静态数据的拷贝,对多个对象来说,静态数据成员只存储在一个地方,静态数据成员的值对每个对象都一样。如果在某个对象中对静态成员的值进行了更新,则所有对象都存取到更新后的相同的值。 • 例:Test b,c; • b.x=25; • cout<<c.x; • 在类中仅对静态数据成员进行引用性说明,必须在文件作 用域的某个地方进行定义性说明,且只能说明一次,说明时可以初始化。 • 例:int Test::x; • 静态数据成员属于类,使用 类名::静态数据成员访问
静态数据成员 • 静态数据成员可以在程序内部不依赖于任何对象被访问 • 静态数据成员不能在类中进行初始化。在main()开始前,类的声明后为它提供定义或初始化,而且在数据成员名前面不加static。数据类型 类名::静态数据成员=值 • 访问静态数据成员的方法: • 类名::静态成员名 • 对象名.静态成员名 • 对象指针->静态成员名 小结:一般统计总数、平均数用静态成员
应用举例 class Myclass{ int i; static int j; public: Myclass(); void Print(); ~Myclass(); }; int Myclass::j=0; Myclass::Myclass(){j+=10;} void Myclass::Print() { cout<<j;} void main() { Myclass first,second; first.Print(); second.Print();}
例题分析 class CStatic{ public: CStatic(){val++;} static int val; }; int CStatic::val=0; void main() { cout<< “CStatic::val=”<<CStatic::val<<endl; CStatic cs1; cout<< “cs1.val=”<<cs1.val<<endl; CStatic cs2; cout<< “cs2.val=”<<cs2.val<<endl; CStatic cs3,cs4; cout<< “cs1.val=”<<cs1.val<<endl; cout<< “cs2.val=”<<cs2.val<<endl; } Cstatic::val=0 cs1.val=1 cs2.val=2 cs1.val=4 cs2.val=4
静态成员函数 • 静态成员函数的作用在建立任何对象前处理静态数据成员 • 用关键字static声明的成员函数 • static 返回值类型 成员函数名(参数表); • 静态成员函数可以内嵌,也可在类外定义,此时不要用static前缀 • 静态成员函数不能被说明为虚函数 • 静态成员函数属于类,使用 类名::静态成员函数 访问 • 静态成员函数访问的对象 • 直接引用: • 类中的静态数据成员; • 全局数据变量; • const常量 • 间接引用:类中的非静态数据成员
静态成员函数 int ex=6; const int cx=7; class MyClass{ public: static int f_1(MyClass m); //静态成员函数 int x; //非静态数据成员 static int y; //静态数据成员 }; int MyClass::y=8; int MyClass::f_1(MyClass m) { m.x=9; return y+ex+cx+m.x; }
静态成员函数 #include <iostream.h> class X{ int member_int; public: static void func(int i,X *ptr); }; void X::func(int i,X *ptr) { ptr->member_int=i;} void main() { X obj; X::func(1,&obj); obj.func(1,&obj); }
静态成员函数 无论是公有派生和私有派生,派生类都可以使用基类的静态成员,使用时唯一的要求是,必须用“类名::成员名”代表这个静态成员。 class Base{ public: static void a(); void b();…}; class Derived:private Base{…}; class DD:public Derived{ void func();…}; void DD::func() { Base::a(); a(); b();}
例题分析 找出程序中的错误 class MyClass{ public: static void f(); private: static int Globe; }; void MyClass::f() { Globe=100;} • 没有为静态成员变量初始化
例题分析 找出程序中的错误 class MyClass{ public: static void f(MyClass my); private: int Globe; }; void MyClass::f() { cout<<Globe;} • 成员函数f的定义与实现的接口不一致 • 成员函数的实现中对私有成员Globe的引用不正确,对对象的引用采用间接方式。
友元函数 设向量类Vector和矩阵类Matrix,向量和矩阵元素都是float类型。在类中各自说明一个公有成员函数elem,用于存取向量和矩阵的私有数据元素V[i]和M[i][j] float &Vector::elem(int i) {return V[i];} float &Matrix::elem(int i,int j) {return M[i][j];} 设一个矩阵乘向量的函数,因为向量和矩阵的元素都是私有的,必须通过公有成员函数elem访问它们。 Vector multiply(Matrix &m,Vector &v) { Vector r; for(int i=0;i<5;i++) { r.elem(i)=0; for(int j=0;j<5;j++) r.elem(i)+=m.elem(i,j)*v.elem(j);} return r;}
友元函数 • 友元函数:不是当前类的成员函数,而是独立于当前类的外部函数,但它可访问该类的所有对象的成员。 class girl{ char *name; int age; public: girl(char *n,int d) { name=new char[strlen(n)+1]; strcpy(name,n); age=d; } friend void disp(girl &); ~girl( ) {delete name;}}; void disp(girl &x) { cout<<"girl\'s name is:"<<x.name<<",age:"<<x.age<<"\n";} void main() { girl e("Chen Xingwei",18); disp(e); }
友元函数 • friend 函数名 例:friend void disp(girl &) • 友元函数带有一个该类的入口参数 例:void disp(girl &) • 访问类的对象的成员时,必须使用对象名 • 不必在函数名前加“类名::” • 友元可以是不属于任何类的一般函数 • 友元可以是整个的一个类
友元函数 • 友元可以是另一个类的成员函数 class two; class one{ int a; public: void f(two &); }; class two{ int b; friend void one::f(two &); }; void f(two &r) {a=r.b;}
友元函数 • 可以把类声明成另一个类的友元 class C2{ … friend class C1; }; • 友元不具有传递性。例如:X是Y的友元,Y是Z的友元 • 友元不具有交换性。例如:X是Y的友元
友元和派生类 • 派生类的友元可以访问基类的公有和保护成员。当友元访问基类的静态保护成员时,不能使用成员名限定方法。 class one{ protected: int a; public: one(){a=20;}}; class two:public one{ int b; friend void f(two &t) {cout<<'a'<<t.a<<'b'<<t.b;} public: two(){b=10;}}; void main() { two t1; f(t1);}
友元和派生类 • 友元类的派生类可以访问基类的公有和保护成员,基类必须是派生类的直接基类。因为友元不能继承 class one{ protected: int a; public: friend class two; }; class two{ public: void func(one &s){cout<<s.a;} }; class three:public two{ public: void seta(){a=10;} };
例题分析 找出程序中的错误 class MyClass{ char my_char1; char my_char2; public: friend void SetMember(MyClass &my,char); }; void SetMember(MyClass &my,char mem1) { my.my_char1=mem1;} void SetMember(MyClass &my,char mem1,char mem2) { my.my_char1=mem1; my.my_char2=mem1;} void main() { MyClass obj; SetMember(obj,5); SetMember(obj,7,9);}
const对象 • const对象:在对象前面加上关键字const。该对象的任何数据成员都不能被修改 • const Date MyBirthday(8,13,1970); • const写在成员函数的参数表后面,该成员函数为只读函数。注意只读成员函数不仅在类定义体内说明时需要使用关键字const,在类定义体外定义时也必须使用关键字const。 • int GetMonth() const{return month;} • 一个const对象只能访问const成员函数。不能使用const对象调用非const的普通成员函数
volatile对象 • volatile对象:在对象前面加上关键字volatile。volatile变量是程序设计的进程可以随时改变的变量。例如:时钟。 • 一个volatile对象只能访问volatile成员函数。
使用带有this指针的成员函数 将关键字const写在函数头后,函数体前,说明函数是一个const成员函数,这时const不是指函数的返回值,而是指函数体中使用的this指针。 class ConstFun{ int val; public: ConstFun(int i){val=i;} int Getval() const; }; int ConstFun::GetVal() const{return val;} void main() { ConstFun s(98); cout<<s.Getval()<<endl;} const ConstFun * const this;
转换函数 • 转换函数:类中定义的一个成员函数,形式为: • class A{ • operator type( ); • }; • 说明: • type是类型名(或类名),或由类型名与类型修饰符*(指针)、[ ](数组)和( )以及const或volatile等组成的表达式。 • 该函数不能带有参数,也不能指定返回类型,其返回类型是type • 当A类的对象作为操作数用在表达式中,该表达式要求操作数应具有type类型时,该转换函数被自动调用,进行类型转换、将类的数据成员类型转换为type类型。
转换函数 例:class number{ int val; public: number(int i){val=i;} operator int( );}; number::operator int( ) { return val;} void main() { number n(15); int i=n; cout<<i<<endl; i=i+n; cout<<i<<endl; cout<<int(n)<<endl;(cout<<n<<endl;) } 被解释为i=n.operator int( ); 表达式中出现n,自动调用operator int()
转换函数 • 转换函数可以被派生类继承 class num:public number{ public: num(int i):number(i){ } }; void main( ) { num n(15); int i=n; cout<<i+n<<endl;}
转换函数 • 转换函数可以被说明成虚函数 class A{ public: virtual operator const char *( ){return “A”;}}; class B:public A{ public: virtual operator const char *( ){return “B”;}}; void main() { A *p=new B; cout<<*p<<endl;} 看到指针表达式,就会自动调用类型为指针类型的类型转换函数 <<运算符的定义中没有对对象的操作,只有: Bool、char、int、 long 、float、double、(void *)、(const char *)等故需定义类型转换函数
转换函数 • 一个类可以定义多个转换函数,只要能够从type中将它们区别开来 • 需要进行类型转换时,编译器按这些转换函数在类中说明的顺序,选择第一个遇到的匹配最好的转换函数 • 有两义性时,可以用强制类型转换显式确定一个转换函数。 • 构造函数提供的类型转换方向与转换函数的类型转换方向相反 number n(15.0):将15.0转换成val所具有的类型 operator int( ):将val转换成int
指向类成员的指针 • 指向类成员的指针:可以访问某个特定类的对象中给定类别的任何成员。指向类的成员,而不是指向该类的一个对象中该成员的一个实例。
指向类数据成员的指针 • 指向类X中类型为type的数据成员的指针的说明形式: type X::*pointer; 设类X的数据成员member的类型为type。则: pointer=&X::member;将该成员的地址存入pointer中。 • 若访问某个对象中pointer所指向的成员,使用特殊的运算符“.*”和“->*”。
指向类数据成员的指针 例:class A{ public: int a,b,c; void fa(void){ } void fb(void){ } }; void main( ) { int A::*p; p=&A::b; A x; A *px=new A; x.*p=1; p=&A::c; px->*p=2; px->b=8;} .*运算符的左操作数必须是类的对象 ->*的左操作数是指向类的对象的指针
指向成员函数的指针 type (X::pointer)(list); 指向X类中参数类型列表为list,返回类型为type的成员函数的指针 void (A::*pfan)(void); 定义一个变量pfan,指向任何无参数和无返回值的A成员函数 pfan=A::fa; 指向类A成员函数fa()的指针pfan,将该函数的地址置给指针pfan A x; A *px=new A; (x.*pfan)(); 指向类A的对象x的成员函数fa (px->*pfan)(); 指向类A对象指针的成员函数fa
指向成员函数的指针 #include <iostream.h> class A{ private: int val; public: A(int i){val=i;} int value(int a){return val+a;} }; void main() { int(A:: *pfun)(int); pfun=A::value; A obj(10); cout<<(obj.*pfun)(15)<<endl; A *pc=&obj; cout<<(pc->*pfun)(15)<<endl;}
指向成员函数的指针 • 指向类成员的指针指向一个虚函数,并且通过指向对象的指针(或引用)访问该虚函数,发生多态性。 #include <iostream.h> class base{ public: virtual void print(){cout<<“In Base”<<endl;} }; class derived:public base{ public: void print(){cout<<“In Derived”<<endl;} }; void display(base *pb,void(base::*pf)()) { (pb->*pf)();} void main() { derived d; display(&d,base::print);} base::此处的::不是成员名限定,而是指向类的成员函数的指针的一种表示方法。可以读作pf是指向base类的无参数无返回类型的成员函数。如果该成员函数是虚函数,则该指针指向基类还是派生类的同名成员函数由运行时动态决定。
指向类的静态成员的指针 #include <iostream.h> class A{ public: static int num; }; int A::num; void main() { int *p=&A::num; *p=56; cout<<A::num<<endl; cout<<*p<<endl; cout<<p<<endl; A a,b; cout<<a.num<<endl; cout<<b.num<<endl; } 静态成员不需要对象
数组和类 • 类对象数组 • 类对象指针数组 • 对象数据成员数组 • 类对象数据成员指针数组 • 类成员函数指针数组 • 静态数据成员指针数组
类对象数组 例:建立类exam的对象数组,将0~3之间的值赋给每一个元素x。 class exam{ int x; public: void set_x(int n) {x=n;} int get_x( ) {return x;} }; main() { exam ob[4]; //定义含4个元素的对象数组,每个对象属exam类 int i; for(i=0;i<4;i++) ob[i].set_x(i); for(i=0;i<4;i++) cout<<ob[i].get_x()<<; cout<<\n ; return 0; } • 每一数组元素都是对象的数组
类对象数组 • 如果类中含有用户定义的构造函数,且构造函数带有参数, 则定义对象数组时,可通过初始值表进行赋值 #include <iostream.h> class exam{ int x; public: exam(int n) {x=n;} int get_x() {return x;} }; main() { exam ob[4]={11,22,33,44}; int i; for(i=0;i<4;i++) cout<<ob[i].get_x()<<' '; cout<<'\n'; return 0; }
如果类中含有构造函数,则定义对象数组时,也可通过不带参数的构造函数或带有缺省参数的构造函数给对象数组元素赋值如果类中含有构造函数,则定义对象数组时,也可通过不带参数的构造函数或带有缺省参数的构造函数给对象数组元素赋值 调用带1个参数的构造函数 op1[0].x=1,op1[1].x=4 op1[2].x=5,op1[2].y=5 调用带2个参数的构造函数 op1[0].x=2,op1[0].y=3 op1[1].x=3,op1[1].y=4 op2[0].x=5,op2[1].y=5 class point{ int x,y; public: point() {x=5;y=5;} point(int a,int b) {x=a;y=b;} point(int a) {x=a;} int get_x() {return x;} int get_y() {return y;}}; void main() { point op(3,4); point op1[3]={1,4}; point op2[4]={point(2,3),point(3,4)}; }
类对象数组 • 完成初始化工作的构造函数不同,说明对象数组的格式有差别 • 使用缺省构造函数(无参数或全部参数采用缺省值)完成初始化工作,使得组成数组的每个对象都具有相同的初始状态 类名 数组名[数组长度]; • 使用带参数构造函数初始化数组:必须在说明对象数组的同时给出初值表 类名 数组名[长度]={(初值1…),(初值2…)…}; 注意:在初值表中没有对全部的数据成员初始化,则必须定义缺省构造函数
析构函数和对象数组 • 当对象数组的生命期结束之后,C++系统为对象数组的每个元素调用一次析构函数。 #include <iostream.h> class test{ int num; public: test(){num=0;} test(int n){num=n;} ~test(){cout<<"Destructor"<<endl;} }; void main() {test a[5];}
类对象数组 p和ob[0]同一地址 p指向ob[1] • 用对象指针引用对象数组 注意:数组名代表第一个元素的地址 class exe{ int x; public: void set_a(int a) {x=a;} void show_a() {cout<<x<<endl;} }; main() { exe ob[2],*p; ob[0].set_a(10); ob[1].set_a(20); p=ob; p->show_a(); p++; p->show_a(); return 0; }
例题分析 class base{ int x,y; public: base(int m,int n){x=m;y=n;} void print(); void print() const; }; void base::print() {cout<<x<<","<<y<<endl;} void base::print() const {cout<<x<<","<<y<<" const"<<endl;} void main() {base a(55,84); a.print(); const base b(32,25); b.print(); } 55,84 32,25 const
例题分析 class base{ const int a; static const int b; public: base(int i); void print(); const int &r;}; const int base::b=128; base::base(int i):a(i),r(a){} void base::print() {cout<<a<<","<<b<<","<<r<<endl;} void main() {base a1(125),a2(119); a1.print(); a2.print();} 常量对象的初始化 125,128,125 119,128,119
例题分析 class test{ int n; public: test(int i){n=i;cout<<"constructor is called."<<endl;} ~test(){cout<<"destructor is called."<<endl;} int getn(){return n;} void inc(){++n;} }; void main() { static test a(3); //静态类对象 for(int i=0;i<3;i++) {a.inc(); cout<<a.getn()<<endl; } cout<<"Exit main()"<<endl; } Constructor is called 4 5 6 Exit main() Destructor is called
例题分析 class test{ private: int n; public: test(int i){n=i;cout<<"constructor is called."<<endl;} ~test(){cout<<"destructor is called."<<endl;} int getn(){return n;} void inc(){++n;} }; void main() { for(int i=0;i<3;i++) {static test a(3); a.inc(); cout<<a.getn()<<endl; } cout<<"Exit main()"<<endl; } • 静态类对象的构造(析构)函数 • 构造函数在代码执行过程中,第一次遇到它的变量定义时被调用,但直到整个程序结束之前仅调用一次。 • 析构函数在整个程序退出前被调用,调用一次。 Constructor is called 4 5 6 Exit main() Destructor is called
例题分析 class test{ private: int n; public: test(int i){n=i;cout<<"constructor is called."<<endl;} ~test(){cout<<"destructor is called."<<endl;} int getn(){return n;} void inc(){++n;} }; void main() { for(int i=0;i<3;i++) { test a(3); a.inc(); cout<<a.getn()<<endl; } cout<<"Exit main()"<<endl; } constructor is called. 4 destructor is called. constructor is called. 4 destructor is called. constructor is called. 4 destructor is called. Exit main() Press any key to continue • a定义在for语句中是局部对象,只能存在循环体内,结束循环体本次循环时,需要调用一次析构函数。