2009-7-28 1
第 12章类的其他特性
2009-7-28 2
友元函数为什么要定义友员? 类中的私有数据不能被外部任意访问修改,只能由类本身的函数访问修改。
友员的作用? 被定义为友员的类或函数可以访问修改类的私有数据。
友员给予 别的类 或 非成员函数访问私有成员的权利。
2009-7-28 3
友元函数的说明及使用例 1,用友元函数的方法求圆柱体的体积。
1201
什么是友员函数? 将一个外部函数(不是类全部,也不是部分成员函数)声明为一个类的友员,
使这个函数能访问类的私有和保护成员。
声明形式?
friend 返回类型 函数名 (形参 );
例如,friend void show(st_n &st);
12001
2009-7-28 4
注:
1.友元函数不是类的成员函数,函数体中要用运算符
,.”来访问对象的成员,不能用 this指针。
区别,友元函数必须在类的定义中说明,函数体可在类内也可在类外定义;友元函数可访问类中的所有成员。
2.类中对友元函数指定访问权限无效。
3.友元函数的作用域与一般函数的作用域相同。
4.使用友元函数的目的是提高程序的运行效率。
5.友元函数破坏了类的封装性,因而应谨慎使用。
2009-7-28 5
成员函数用作友元什么是友员成员函数? 将一个类的成员函数(不是类全部)声明为另一个类的友员,使这个函数能访问该类的私有和保护成员。
声明形式?
friend 返回类型 类名,:函数名例如,friend void sco::show( );
2009-7-28 6
将类 C的一个成员函数说明为类 D的友元函数的一般格式:
class D; //对类 D作引用性说明
class C{
… //类 C的成员定义
public:
void fun(D &);
//只能给出函数的原型说明,因类 D没有定义
};
class D{

friend void C::fun(D &); //定义友元函数
};
void C::fun(D &d) //开始定义成员函数
{…}
2009-7-28 7
将一个类 M中所有成员说明成另一个类 N的友元的一般格式:
class N{
… //成员定义
friend class M; //说明类 M是类 N的友元
};
class M{
… //成员定义
};
类 M中的所有成员可使用类 N中的全部成员,称 类
M为类 N的友元 。
友元关系不传递,
不具有交换性,也不具有继承性。
2009-7-28 8
例 2,将一个类的成员函数用作另一个类的友元函数。
1202执行后输出:
a1.x=25 a1.y=40
b1.c=55 b1.d=66
a1.x=55 a1.y=66
例 2 有关内容总结
1,定义了类 A中的函数 Setxy( ) 是类 B
的友员,建立了类和类的成员函数之间的联系。使编程显得更便利。
2009-7-28 9
2,友员成员函数可以访问并修改类的私有或保护数据,调用类的私有或保护函数使类不至于由于封装而增加了编程的复杂性。
总的目标,使外部类的成员函数能访问修改类的私有或保护数据、能调用类的私有或保护函数。 与友员类的区别 是仅将部分成员函数声明为友员,而不是整个类。
例 3,把类作为友元。
1203执行后输出:
x=500 y=500
GlobalF->var=1000
2009-7-28 10
例 3有关内容总结
1,定义了类 F2是类 F 的友员,建立了类之间的联系。使编程显得更便利。
2,友员可以访问并修改类的私有和保护数据,调用类的私有和保护函数,使类不至于由于封装而增加了编程的复杂性。
总的目标,使外部类能访问修改类的私有或保护数据、能调用类的私有或保护函数。
2009-7-28 11
虚函数多态性 是 OOP的关键技术之一。它指在一般类中定义的操作在被继承类继承后可以有不同的表现形式。
例如:图形的求面积类,shape
操作:求面积类:椭圆操作:求面积类:长方形操作:求面积解决的方法:用虚函数实现动态联编
2009-7-28 12
例 4,没有用多态性的例题
#include<iostream.h>
class A {
public:
void print( ) { cout << "A "; } };
class B:public A { //定义派生类
public:
void print( ) { cout << " B "; } }; //重新定义函数
void main( ) {
A a,*p;
B b;
p= &a; p->print( ); //访问基类函数
p= &b; p->print( ); //希望访问派生类函数
}
结果,AA
2009-7-28 13
虚函数的定义和使用在基类中,在要被定义成虚函数的函数前加关键字 virtual
例如,virtual void print( );
注意,1,关键字是 virtual
2,放在最前面,函数的类型不能忘,
如 virtual void print( );中的 void
3,可只在基类中的函数加 virtual,在派生类中不加,但为了便于阅读,可在所有要声明为虚函数的前面都加 virtual
4,只需要在类定义文件,即头文件中的虚函数加 virtual,在 cpp 文件中就不要再加 virtual
2009-7-28 14
例 5,用多态性解决例 1中的问题。
#include<iostream.h>
class base {
public:
virtual void print( ) { cout <<,base,; } }; //定义了虚函数
class first:public base { //定义派生类
public:
void print( ) { cout <<,first,; } }; //重新定义 print( )
class second:public base { //定义派生类
public:
void print( ) { cout << " second "; } }; //重新定义 print( )
2009-7-28 15
void main( ) {
base ob1,*p; //定义基类的对象
first ob2; //定义派生类的对象
second ob3; //定义派生类的对象
p= &ob1; //p 指向基类对象 ob1
p->print( ); // 调用基类成员函数
p= &ob2; // p 指向子类对象 ob2
p->print( ); // 动态联编,调用子类成员函数
p= &ob3; // p 指向子类对象 ob3
p->print( ); // 动态联编,调用子类成员函数
}
结果,base
first
second
2009-7-28 16
例 5的相关内容总结
1,基类中的虚函数是由 virtual 定义的:
virtual void print( );
2,在定义了虚函数之后,只要定义一个基类的指针,就可以调用子类的函数:
p= &ob2; // p 指向子类对象 ob2
p->print( ); // 动态联编,调用子类成员函数
3,要实现动态联编,子类中的函数必需与基类的函数形式一致,若在子类中对虚函数的形式进行了改变,则子类中的函数将失去多态性。
4.C++有两种多态性:
静态多态性,操作的对象编译时确定的 重载实现;
动态多态性,操作的对象依赖运行的进程 虚函数;
2009-7-28 17
例 6,使用虚函数。
1204执行后输出:
x=1000 y=2000 z=3000
x=1000 y=2000 z=3000
第一行输出是调用三个不同对象的成员函数,
而第二行的输出是将三个不同类型的对象起始地址赋给基类的指针变量 pa,当 pa指向不同对象时
,调用不同对象中的虚函数,这就是运行的多态性。
2009-7-28 18
纯虚函数基类中虚函数没有定义,用初始化符 =0代替函数定义,这时的虚函数叫 纯虚函数 。
纯虚函数定义的一般格式:
virtual 类型 函数名() =0
如,class X {
//……
public:
virtual void print( )=0;
//……
};
含有纯虚函数的类叫做 抽象基类 。
2009-7-28 19
class Y {
public:
virtual void print( ) =0;
//……
};
X x1; //错误
Y y1; //错误只能作为别的类的基类,不能生成抽象基类的对象用这种基类指针指向派生类的对象时,必须在派生类中重载纯虚函数,否则出错。如前面例 6改为:
12041执行后输出:
y=2000
z=3000
抽象基类的用途是为派生类提供基类,纯虚函数的作用是作为派生类中的成员函数的基础,并实现动态多态性。
2009-7-28 20
例 7,建立一个双向链表,要完成插入一个结点、删除一个结点、查找某一个结点操作,并输出链表上的各个结点值。为了简化结点上包含的信息,设结点只包含一个整数

分析,链表的插入、删除、查找等操作相同,只是结点上包含的信息随不同的应用不同,故可将实现链表操作部分设计成通用程序。
用两个类来表示一个结点的数据,类 IntObj的成员数据描述结点信息,成员函数完成结点的比较、输出结点数据等。 类 Node的成员数据构成双向链表的后向指针 Next和前向指针 Prev以及描述结点数据的指针 Info。再定义一个
Node的 友元类 List,成员数据包括指针链表的首指针 Head
,操作链表的尾指针 Tail,成员函数实现链表的各种操作,
如插入、删除结点等。
2009-7-28 21
描述结点的信息描述结点的信息指针前向指针后向指针指针前向指针后向指针
IntObj IntObj
Node Node 1207
2009-7-28 22
程序执行后输出:
data=101 data=102 data=103 data=104
data=101 data=103 data=104
data=101 data=103 data=104 data=102
例 8,处理字符串的双向链表。
1208
分析,设每个结点上的数据是一个指向字符串的指针,
链表的基本操作与上例相同,故只要包含头文件
Ex12_7.h即可。需要做的工作是从抽象类 Object派生出描述结点数据的类 StrObj,根据结点数据的特点,增加构造函数和析构函数,重新定义比较两结点是否相等的虚函数和输出结点上数据的虚函数,根据需要再设计相应的主函数。
2009-7-28 23
静态成员为什么引入静态成员? 创建了一个对象,则这个对象将拥有所有类中的成员,如果某个数据对所有对象都一样,则这个数据只要有一个拷贝,而不是每个对象都重复定义这个数据,形成内存浪费。简言之:一个数据拷贝,所有对象共享。
2009-7-28 24
例如有类:
class student {
char *name,*num;
int total; //学生总人数
public,…… };
应用程序中声明对象为:
student a,b,c;
则对象 a,b,c 中都有数据 total,而学生总数是一样的,所以 total 被重复定义了两次。若把 total 声明为静态成员,就可避免这个问题。
2009-7-28 25
静态数据成员类定义中用关键字 static修饰的成员数据称为 静态成员数据 。
定义形式?在所要定义的数据成员前加关键字 static,如,
class student {
char *name,*num;
static int total;
public,…… };
将学生总人数 total 声明为静态成员 。
2009-7-28 26
例 9,静态成员数据的说明与使用。
1209执行后输出:
i=2 j=3 x=4 y=5
i=100 j=200 x=300 y=400
i=2 j=3 x=300 y=400
同一个类的不同对象的静态成员数据使用相同的存储空间
2009-7-28 27
注:
1.类的静态成员数据是静态分配存储空间的,而其他成员是动态分配存储空间的(全局变量除外)。
2.必须在文件的作用域中,对静态成员数据作一次且只能作一次定义性说明。故可通过静态成员数据名前加上类名和作用域运算符直接使用静态成员数据。如:
12091执行后输出:
A::x=0
3.静态成员数据具有全局变量和局部变量的一些特性。
4.通常在构造函数中不给静态成员数据置初值,而是在对静态成员数据的定义性说明时指定初值。
2009-7-28 28
例 10,利用静态成员数据作为产生对象的计数器。
1210
执行后输出:
Number of Objects=1
Number of Objects=2
Number of Objects=3
Number of Objects=2
Number of Objects=1
Number of Objects=0
2009-7-28 29
静态成员函数用关键字 static可将类的成员函数定义为静态成员函数。
注意点:
只能访问静态数据,不能访问其它成员。
可以没有对象而进行函数调用。
目的,对静态数据成员进行访问修改。
形式,在一般函数声明前加 static
2009-7-28 30
例 11,定义和使用静态的成员函数。
1211
与例 11相关的说明
1.在类外的程序代码中,通过类名加上作用域操作符可直接调用静态成员函数。如:
A::Show(a1)
执行后输出:
Number of Objects=1
i=100
count=1
i=300
count=1
Number of Objects=2
Number of Objects=3
Number of Objects=2
Number of Objects=1
Number of Objects=0
2009-7-28 31
2.静态成员函数只能直接使用本类的静态成员数据或静态成员函数,但不能直接使用非静态的成员数据。
3.静态成员函数的实现部分在类定义之外定义时,
前面不能加修饰词 static。
4.不能将静态成员函数定义为虚函数。
5.可将静态成员函数定义为内联的 (inline),定义方法与非静态成员函数相同。
6.一般情况不必定义静态函数,因在使用上静态函数没有非静态成员函数方便,故只有一些特殊的场合才使用静态成员函数。
2009-7-28 32
1,静态成员有,
静态数据 和 静态函数 。
2,静态成员可以通过 对象调用,
也可以通过 类属直接调用,如,
cout << a.getTotal( );
//对象调用
cout << st_n::getTotal( );
// 类属调用
3,不管创建多少对象,静态成员只有一个拷贝,所有对象 共享这个静态成员。
4,静态数据必须初始化,静态函数只能对静态数据进行操作。
课堂小结
2009-7-28 33
作 业一,填空
1,一个非成员函数必须声明为类的,
才能访问类的私有数据,
2,类的友员可以访问类的 和 成员,
3,一个无返回值的友员函数可声明为,
add(int,char*);
二,友员函数的作用是什么? 说明友员类、
友员成员函数和友员函数的区别。
2009-7-28 34
三,改错并说明理由,
include <iostream.h>;
void class example ( )
{
private
friend show( ):
int date=( )
public;
void eample { int y= 10} ( date=y);
int print { }
( cout >>?date=? >> date; )
}
2009-7-28 35
6.7 一个完整的类屏幕上画矩形:
Left Right
Top
Bottom矩形类 Crectangle:用对角坐标定义矩形 ;
构造函数;
析构函数;
屏幕上画矩形函数;
class CRectangle {
private:
int Left;
int Top;
int Right;
int Bottom;
public:
CRectangle( );
CRectangle(int L,int T,int R,int B);
void draw(void);
void getcoord(int *L,int *T,int *R,int *B);
void setcoord(int L,int T,int R,int B);
};
调用 setcoord设定坐标调用 Line画图 将坐标值传给参数
2009-7-28 36
//CRECT.h
class CRectangle {
private:
int Left;
int Top;
int Right;
int Bottom;
public:
CRectangle( )
{
Left=Top=Right=Bottom=0;
}
CRectangle(int L,int T,int R,int B)
{
setcoord(L,T,R,B);
}
void draw(void);
2009-7-28 37
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; }
2009-7-28 38
// 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;
}
2009-7-28 39
练习题 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 5
2009-7-28 40
练习题 2:改正错误
#include<iostream.h>
class A {
public:
int i;
void set( int );
void diap( ) { cout<<i<<ednl; } //cout<<i<<endl;
};
void set (int si) { i=si; } //void A::set(int si){i=si;}
void main ( )
{
A ob;
ob.set(2);
i=1; //ob.i=1;
ob.disp( ); //ob.diap();
}
2009-7-28 41
练习题 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;
//……
}
2009-7-28 42
练习题 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( );
}
2009-7-28 43
练习题 5:改正错误
const int a=78;
char *const pc=“abcd”;
const int *pi=&a;
*pi=68;
*(pc+1)=?c?;
*pc++=?y?;
2009-7-28 44
练习题:
1、写出程序运行结果
#include<iostream.h>
class Base {
public:
Base(void){ };
virtual ~Base( )
{
cout<<“Base::~Base is called”<<endl;
}
};
class Derived,public Base {
public:
Derived(void) { }
~Derived(void)
{
cout<<“Derived,~Derived is called”<<endl;
}
};
void main( void)
{
Base *bp=new Derived;
delete bp;
}
2009-7-28 45
2、下面类是定义用于求圆和圆外接矩形面积,请填空:
#include<iostream.h>
class shape {
protected:
double r;
public:
shap(double x)
{ ;}
virtual void area( ) ;
};
class circle,{
public:
circle(double x),{ }
void area( )
{
cout<<“The circle?s area is:”;
cout<<3.13*r*r<<endl;
}
};
class ex_square,{
public:
ex_square( ),{ }
void ( )
{
cout<<“The external square?s area is:”;
cout<<4*r*r<<endl;
}
};
2009-7-28 46
一,判断题 (说明理由 )
1,静态数据只可以由静态函数访问和修改。
2,静态数据为所有对象共享,
所以可认为静态数据是属于类的,而不是属于对象的。
3,静态数据和函数只能声明为公有类型。
二,应用静态数据和静态函数的作用是什么?
静态数据与普通数据有何区别?