640 likes | 802 Views
9.3 友元函数与友元类. 9.3.1 友元函数 C++ 提供一种允许外部类和函数存取类的私有成员和保护成员的辅助方法,即将它们声明为一个给定类的友元(或友元函数),使其具有类成员函数的访问权限。但友元本身不是类的成员,它不属于任何类。 对于使用友元函数,有以下几点 说明: (1) 为了在类的定义中对友元加以声明,只需在友元的名称前加上关键字 friend 即可。 (2) 友元函数是能访问类的所有成员的普通函数,一个函数可以是多个类的友元函数,只需在各个类中分别声明。. 9.3 友元函数与友元类.
E N D
9.3 友元函数与友元类 • 9.3.1 友元函数 • C++提供一种允许外部类和函数存取类的私有成员和保护成员的辅助方法,即将它们声明为一个给定类的友元(或友元函数),使其具有类成员函数的访问权限。但友元本身不是类的成员,它不属于任何类。 • 对于使用友元函数,有以下几点说明: • (1) 为了在类的定义中对友元加以声明,只需在友元的名称前加上关键字friend即可。 • (2) 友元函数是能访问类的所有成员的普通函数,一个函数可以是多个类的友元函数,只需在各个类中分别声明。
9.3友元函数与友元类 • (3) 将一个函数声明为某个类的友元函数的方法是在该类定义里提供一个以关键字friend开头的函数原型,友元函数的定义,可以在类的内部或外部,友元函数虽然是在类内进行声明,但它不是该类的成员函数,不属于任何类,在类外定义友元函数时,与普通函数的定义一样,不应在函数名前用类名加以限制,因此,友元函数不象成员函数那样在调用时使用对象名,友元函数要对类的成员进行访问,必须在参数表中显式指明要访问的对象。 • (4) 一个类的友员函数与该类的类内成员函数一样,享有对该类一切成员的访问权。 • (5) 友元函数的调用与一般函数的调用方式和原理一致。 • (6) C++不允许将构造函数、析构函数和虚函数声明为友元函数。
9.3友元函数与友元类 • 【例3-18】友元函数的定义和使用方法。 • #include “iostream.h” • class X1() • { private: • int x; • public: • X1(int i) { x=i; } • int getx(); • friend void sum(X1 &a, X2 &b); //声明友元函数 • }; • int X1::getx() • { return x;}
9.3友元函数与友元类 • void sum(X1 &a,X1 &b) //定义函数 • {cout<<"用友元函数求各和:"<<a.x+b.x<<endl; • //可以访问类的私有成员} • void sum1(X1 &a,X1 &b) //定义普通函数 • {cout<< "用普通函数调用类公共接口函数求和:" • <<a.getx()+b.getx()<<endl; • } • void main() • { X1 m(1); • X1 n(2); • sum(m,n); • sum1(m,n); • }
9.3友元函数与友元类 • 程序的执行结果为: • 用友元函数求各和:3 • 用普通函数调用类公共接口函数求和:3
9.3友元函数与友元类 • 3.6.2 友元类 • 友元类是在多个类之间建立一种访问机制,当程序中定义了两个或两个以上的类时,如果一个类将自己说明为另一个类的友员类,其成员就可以被该类使用。 • 定义友元类的语句格式为: • friend class 类名; • 其中:friend 和class 是关键字,类名必须是程序中的一个已定义过的类。 • 当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元,友元类的所有成员函数都可视为该类的友元函数,能存取该类的私有成员和保护成员。
9.3友元函数与友元类 • 如:将类B声明为类A的友元类的方法是在A类的定义中加上说明: • friend class B; • 注意:友元关系不具有对称性。此外,友元关系不具有传递性,如果类B是类A的友元类,类C是类B的友元类,这并不隐含类C是类A的友元类。 • 【例3-8】有两个类Cla_1和Cla_2。在类Cla_1的定义中说明了友员类Cla_2。主函数中用它们生成了两个对象,然后对这两个对象的私有成员进行访问。
9.3友元函数与友元类 • #include <iostream.h> • #include <windows.h> • #include <stdio.h> • //定义类Cla_1 • class Cla_1 • { private: • friend class Cla_2; //说明友员类Cla_2 • char *name; • int age; • public: • Cla_1( char *str, int i ); • };
9.3友元函数与友元类 • //Cla_1的构造函数定义 • Cla_1::Cla_1(char *str, int i) • { name=str; //为类Cla_1的成员赋值 • age=i; } • //定义类Cla_2 • class Cla_2 //类Cla_2中并没有说明Cla_1友员类 • { public: • void show(Cla_1 x); • } ;
9.3友元函数与友元类 • // Cla_2的show函数定义 • void Cla_2::show(Cla_1 x) • { cout<<"\n\n 姓名:"<<x.name<<endl; • cout<<"\n 年龄:"<<x.age<<endl<<endl; } • // 主函数 • void main() • { //生成对象obj1,定义友员类Cla_2 • Cla_1 obj1("李小丽",30); • Cla_2 obj2; //生成对象obj2 • obj2.show(obj1); //调用obj2.show,去访问obj1的成员 • }
9.4 堆对象和对象数组 • 9.4.1 堆对象 • 我们知道,用new和delete可以动态地分配堆内存或释放堆内存。在此可利用new建立对象(会自动调用构造函数),利用delete可删除对象(会自动调用析构函数)。class Tdate • {public: • Tdate(int m, int d, int y); • protected: • int month; • int day; • int year; • };
9.4堆对象和对象数组 • Tdate::Tdate() • {if (m>0 && m<13) • month=m; • if (d>1 && d<32) • day=d; • if (y>0 && y<3000) • year=y; } • void main() • { Tdate *pd; • pd=new Tdate(1,1,1998); • //…… • delete(pd);}
9.4堆对象和对象数组 • 说明: • (1)堆对象的生存期是整个程序的生命期,所以只有程序运行结束时,堆对象才被删除。这一点与一般的局部对象的生命期不同,局部对象的生存期开始于函数体的执行,而终止于函数体执行结束。 • (2)堆对象用delete来释放。
9.4堆对象和对象数组 • 9.4.2 对象数组 • 一个数组的类型除了可以为基本的数据类型外,还可以为类类型,则这时,该数组中的每个元素都是该类中的一个对象,则这种数组就是对象数组。 • 对象数组的定义方式: • 类名 数组名[数组大小]; • 【例3-9】类person是说明人员的信息结构。用person生成一个对象数组emp[5],通过assignment将人员信息填入,然后将它们显示出来。 • # include <windows.h> • # include <iostream.h>
9.4堆对象和对象数组 • class person • {private: • char *name; //name指向信息串 • int age; //年龄 • public: • person(); // 构造函数说明 • ~person(); // 析构函数说明 • void assignment(char *a, int b); // assignment函数说明 • void show (); // show函数说明 • }; • // 构造函数定义 • person :: person()
9.4堆对象和对象数组 • { name=new char('\0');// 为name申请存储空间,置为空 • age=-1; // 为age 赋初始值 • } • // 析构函数定义 • person :: ~person() • { delete [ ] name; // 回收name空间 } • // assignment函数定义 • void person :: assignment(char *a, int b) • { name = new char[strlen(a) + 1]; • strcpy(name,a); // 用参数a的值修改name • age=b; • };
9.4堆对象和对象数组 • // show函数定义 • void person :: show () • { cout << "\n 姓名:" << name << " 年龄:" << age ; }; • void main () • { // 生成对象数组emp[5] • person emp[5]; • // 给对象数组赋值 • emp[0].assignment("张立三",25); • emp[1].assignment("王冠之",28); • emp[2].assignment("王大成",35); • emp[3].assignment("英乐乐",21); • emp[4].assignment(“胡忠厚”,26);
9.4堆对象和对象数组 • // 显示emp • int i; • for (i=0; i<5; i++) • { emp[i].show(); } • cout <<endl<<endl; • }
9.5 运算符的重载 • C++中预定义的运算符的操作对象只能是基本数据类型,实际上,对于很多用户自定义类型(比如类),也需要有类似的运算操作。例如,下面的程序段声明了一个复数类: • class complex //复数类声明 • { • public: • complex(double r=0.0,double i=0.0) • {real=r;imag=i;} • void display(); //显示复数的值 • private: • double real; • double imag; • };
9.5运算符的重载 • 于是我们可以这样声明复数类的对象: • complex a(10,20),b(5,8); • 接下来,如果需要对a和b进行加法运算,该如何实现呢?我们当然希望能使用“+”运算符,写出表达式“a+b”,但是编译的时候却会出错,因为编译器不知道该如何完成这个加法。这时候就需要我们自己编写程序来说明“+”在作用于complex类对象时,该实现什么样的功能,这就是运算符重载。运算符重载是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据导致不同类型的行为。 • 运算符重载的实质就是函数重载。在实现过程中,首先把指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参,然后根据实参的类型来确定需要调用的函数,这个过程是在编译过程中完成的。
9.5运算符的重载 • 运算符重载的规则如下: • ①C++中的运算符除了少数几个之外,全部可以重载,而且只能重载C++中已有的运算符。 • ②重载之后运算符的优先级和结合性都不会改变。 • ③运算符重载是针对新类型数据的实际需要,对原有运算符进行适当的改造。一般来讲,重载的功能应当与原有功能相类似,不能改变原运算符的操作对象个数,同时至少要有一个操作对象是自定义类型。
9.5运算符的重载 • 不能重载的运算符只有五个,它们是类属关系运算符“.”、成员指针运算符“*”、作用域分辨符“::”、sizeof运算符和三目运算符“?:”。前面两个运算符保证了C++中访问成员功能的含义不被改变。作用域分辨符和sizeof运算符的操作数是类型,而不是普通的表达式,也不具备重载的特征。
9.5运算符的重载 • 运算符的重载形式有两种,重载为类的成员函数和重载为类的友元函数。运算符重载为类的成员函数的一般语法形式为: • 函数类型 operator运算符(形参表) • { • 函数体; • } • 运算符重载为类的友元函数的一般语法形式为: • Friend 函数类型 operator运算符(形参表) • { • 函数体; • }
9.5运算符的重载 • 函数类型指定了重载运算符的返回值类型,也就是运算结果类型;operator是定义运算符重载函数的关键字;运算符即是要重载的运算符名称,必须是C++中可重载的运算符,比如要重载加法运算符,这里就写“+”;形参表中给出重载运算符所需要的参数和类型;对于运算符重载为友元函数的情况,还要在函数类型说明之前使用friend关键字来声明。
9.5运算符的重载 • 当运算符重载为类的成员函数时,函数的参数个数比原来的操作数个数要少一个(后置“++”、“- -”除外);当重载为类的友元函数时,参数个数与原操作数个数相同。原因是重载为类的成员函数时,如果某个对象使用重载了的成员函数,自身的数据可以直接访问,就不需要再放在参数表中进行传递,少了的操作数就是该对象本身。而重载为友元函数时,友元函数对某个对象的数据进行操作,就必须通过该对象的名称来进行,因此使用到的参数都要进行传递,操作数的个数就不会有变化。 • 运算符重载的主要优点就是允许改变使用于系统内部的运算符的操作方式,以适应用户自定义类型的类似运算。
9.5运算符的重载 • 运算符也可以重载为类的友元函数,这样,它就可以自由地访问该类的任何数据成员。这时,运算所需要的操作数都需要通过函数的形参表来传递,在参数表中形参从左到右的顺序就是运算符操作数的顺序。 • 对于双目运算符B,如果它的一个操作数为A类的对象,就可以将B重载为A类的友元函数,该函数有两个形参,其中一个形参的类型是A类。经过重载之后,表达式oprdl B oprd2就相当于函数调用operator B(oprd1,oprd2)。
9.5运算符的重载 • 对于前置单目运算符U,如“-”(负号)等,如果要实现表达式U oprd,其中oprd为A类的对象,则U可以重载为A类的友元函数,函数的形参为A类的对象oprd。经过重载之后,表达式U oprd相当于函数调用operator U(oprd)。 • 对于后置运算符“++”和“--”,如果要实现表达式oprd++或oprd-- ,其中oprd为A类的对象,那么运算符就可以重载为A类的友元函数,这时函数的形参有两个,一个是A类对象oprd,另一个是整型(int)形参。第二个参数是用于与前置运算符函数相区别的。重载之后,表达式oprd++和oprd--就相当于函数调用operator++(oprd,0)和operator--(oprd,0)。
9.5运算符的重载 • 例 复数类加减法运算重载——友元函数形式。 • 本例利用将运算符重载为友元函数的形式,重载运算符“+”、“-”,使之实现复数加减法。本例所针对的问题和例8-1完全相同,运算符的两个操作数都是复数类的对象,因此重载函数有两个复数对象作为形参。
#include<iostream.h> • class complex //复数类声明 • { • public: //外部接口 • complex(double r=0.0,double i=0.0) • {real=r;imag=i;} • //运算符“+”重载友元函数 • friend complex operator+(complex c1,complex c2); • //运算符“-”重载友元函数 • friend complex operator-(complex c1,complex c2); • void display(); //显示复数的值 • private: //私有数据成员 • double real; • double imag; • };
void complex::display() //显示函数实现 • { cout<<”(”<<real<<”,”<<imag<<”)”<<endl;} • //运算符重载友元函数实现 • complex operator + (complex c1,complex c2) • { return complex(c2.real+c1.real,c2.imag+c1.imag);} • //运算符重载友元函数实现 • complex operator - (complex c1,complex c2) • { return complex(c1.real-c2.real,c1.imag-c2.imag);}
void main() //主函数 • { • complex c1(5,4),c2(2,10),c3; • cout<<"cl=”;c1.display(); • cout<<”c2=”;c2.display(); • c3=c1-c2; //使用重载运算符 • cout<<”c3=c1-c2;”; • c3.display(); • c3=cl+c2; //使用重载运算符 • cout<<"c3=cl+c2=”; • c3.display(); • }
9.5运算符的重载 • 将运算符重载为类的友元函数,就必须把操作数全部通过形参的方式传递给运算符重载函数。和例8-1相比,主函数根本没有做任何改动,主要的变化在复数类的成员。程序运行的结果也完全相同。 • 这里,只介绍了几个简单运算符的重载,还有一些运算符,如“[]”,“:”,类型转换等,进行重载时有一些与众不同的情况。考虑到对于初学者而言,难以一下子接受太多新内容,所以暂且不讲。
9.5运算符的重载 • 3.8 运算符的重载 • 运算符的重载与函数重载的方法一致,是一种特殊的函数重载。 • 使用运算符重载的一般格式为: • 类型名operator@(形参表) • 其中,operator是关键字,@是运算符。
9.5运算符的重载 • 【例】 运算符重载举例。 • class Cint • {public: • friend Cint operator+(int a,int b);// “+”(加号)被重 • //载为友元函数 • Cint operator-(); //“-”(负号)被重载为成员函数” • } • Cint Cint::operator+(int a,int b) • { Cint t; • t=a+b; • return t;}
9.5运算符的重载 • Cint Cint::operator-() • { Cint t; • t= -t; • return t; • } • 说明: • (1)在C++中几乎所有的运算符(除“.”、“.*”、“::”、“?:”外)都可以被重载。 • (2) 运算符的重载既不会改变原运算符的优先级和结合性,也不会改变使用运算符的语法和参数个数。 • (3) 由于重载后的运算符函数经常需要访问类的私有成员,因此运算符函数operator@()通常被声明为类的成员函数或友元函数。其等价的函数调用形式,如表3.2所示。
表达式 友元函数调用 成员函数调用 a+b operator+(a,b) a.operator+(b) a++ operator++(a,0) a.operator++(0) -a operator-(a) a.operator-( ) 9.5运算符的重载 • 表3.2运算符表达式及其等价的函数调用形式 • (4) 当重载为类的成员函数时,运算符重载函数的形参个数要比运算符操作数个数少一个;若重载为友元函数,则参数个数与操作数个数相同。 • (5) 当重载为友元函数时,“=”、“()”、“[]”、“->”等运算符不能重载。
9.6 模板与使用 • 模板是C++语言中相对较新的一个重要特征。模板的使用可以使程序员快速建立安全的类库集合和函数集合。它的实现方便了更大规模的软件开发。 • 模板的概念 • 如果一个程序的功能是对某种特定的数据类型进行处理,则将所处理的数据类型说明为参数,就可以把这个程序改写为模板,在模板中定义对数据的的操作方式。模板使程序可以对其他数据类型进行同样方式的操作。
9.6模板与使用 • 在写程序的时候,如果一个参与运算的数的具体数值会随着实际情况的不同而改变的话,便会很自然的想到使用变量来代表这个数。同样的道理,当被处理的数据的类型会随着实际情况的不同而改变时,也会自然的想到把数据类型作为一个可变化的成分从程序中提取出来。等出现真实的函数调用时,再用具体的数据类型。 • 模板就是使程序能够对不同类型的数据进行相同方式的处理。 • 由于C++语言中对数据进行处理的主体可以是函数也可以是类,因此C++中模板也分成函数模板和类模板。
9.6模板与使用 • 3.9.1 类模板 • 说明类模板的一般格式为: • template <类型参数表> class 类模板名 • { private: • 私有成员定义 • protected: • 保护成员定义 • public: • 公有成员定义 }; • 其中:(1)类型形式参数表可以包含基本数据类型,也可
9.6模板与使用 • 以包含类类型,如果是类类型,则须加前缀class。当参数有多个时,需用逗号隔开。 • (2)类模板中的成员函数的定义,可以放在类模板的定义体中(此时与类中的成员函数的定义方法一致),也可以放在类模板的外部定义成员函数,此时成员函数的定义格式如下: • template <类型形式参数表> • 函数值的返回类型 类模板名<类型名表>::成员函数(形参) • { 函数体} • 其中:类模板名即是类模板中定义的名称;类型名表即是类模板定义中的类型形式参数表中的参数名。
9.6模板与使用 • (3)利用类模板定义的只是对类的描述,它本身还不是一个实实在在的类。因此是类模板。 • (4)要定义类模板的对象(即实例),需要用下列格式的语句: • 类模板名 <类型实际参数表> 对象名; • 【例3-22】定义类模板ABC,内含成员函数set和get。用ABC生成对象abc1和abc2。它们的数组元素数不同,显示的结果也不同。 • # include <iostream.h> • // 定义类模板ABC • template <class T, int I> class ABC • { private: • T array [I] ; // 定义数组array
9.6模板与使用 • public: • void set (int x) // 定义成员函数set • { int i; • for (i=0; i<I; i++) //循环I次 • array[i]=x+i; //数组元素赋值 • } • void get () //定义成员函数get • { cout <<"\n 数组元素总数为:"<< I<<endl; • cout <<" array["<<I-1<<"]="<<array[I-1]<<endl; } • }; • void main() • { //由模板ABC生成对象abc1
9.6模板与使用 • ABC <int,50> abc1; • abc1.set(0); //调用对象abc1.set • abc1.get(); //调用对象abc1.get • //由模板ABC生成对象abc2 • ABC <int,100> abc2; • abc2.set(10); //调用对象abc2.set • abc2.get(); //调用对象abc2.get • } • 以下是对上例中的成员函数定义体放于类模板外部定义的示例。 • 【例3-23】定义模板ABC,内含成员函数set和get。用ABC生成对象abc1和abc2。它们的数组元素数不同,显示的结果也不同。
9.6模板与使用 • #include <iostream.h> • // 定义类模板ABC • template <class T, int I> class ABC • { private: • T array [I] ; // 定义数组array • public: • void set (int x); // 定义成员函数set • void get () ; //定义成员函数get • }; • template <class T,int I> • void ABC<T,I> ::set (int x) // 定义成员函数set • { int i;
9.6模板与使用 • for (i=0; i<I; i++) //循环I次 • { array[i]=x+i; } //数组元素赋值 • } • template <class T,int I> • void ABC<T,I>::get() • { cout <<"\n 数组元素总数为:"<< I<<endl; • cout <<" array["<<I-1<<"]="<<array[I-1]<<endl; } • void main() • { //由模板ABC生成对象abc1 • ABC <int,50> abc1; • abc1.set(0); //调用对象abc1.set • abc1.get(); //调用对象abc1.get
9.6模板与使用 • //由模板ABC生成对象abc2 • ABC <int,100> abc2; • abc2.set(10); //调用对象abc2.set • abc2.get(); //调用对象abc2.get • } • 类模板的使用方法可以总结为: • (1) 给出类模板的定义体。 • (2)在适当的位置创建一个类模板的实例,即一个实实在在的类定义,同时创建该模板类的对象。 • (3)有了对象名,以后的使用就和普通类的对象是一致的。
9.6模板与使用 • 3.9.2 函数模板 • 函数模板是函数的一种抽象形式。由于 C++中的数据类型很多,针对某种类型设计的函数显然不能用于其它类型,因此对于同一个功能的要求,不得不编制多个相似的程序,比如求两个量中最小值的函数,相似的函数可能有: • (1)求两个整型数据的最小值:int min(int x,int y) • (2)求两个单精度数据的最小值:float min(float x,float y) • (3)求两个双精度数据的最小值:double min(double x, double y) • 为了解决以上的麻烦,C++引入了函数模板的概念。函数模板就是用来解决一个模板生成多个函数的问题。
9.6模板与使用 • 定义函数模板的格式为: • template <类型形式参数表> • 函数返回值类型名 函数模板名(函数形参及类型) • {函数体}
如:求两个数最小值的函数模板定义如下: • template <class T> • T min( T x, T y) • { return x<y?x:y; } • 有了以上函数模板,则在主程序中使用函数模板生成合适的模板函数。 • int main() • { float b; • char c; • cout <<min(20,10)<<endl; // 生成重载函数int min(int,int) • b= min(-5.43, 50.23); //生成重载函数float min(float,float) • c= min(‘A’, ‘a’); //生成重载函数char min(char,char) • cout<<“b=“<<b<<“, c=“<<c<<endl; • }
文件及其操作 • 流的概念 • C++语言中没有输入/输出语句。但C++编译系统带有一个面向对象的I/O软件包。它就是I/O流类库,流是I/O流类的中心概念。流是一种抽象,它负责在数据的生产者和数据的消费者之间建立联系,并管理数据的流动,程序将流动对象看作是文件对象的化身。