1、面向对象的基本概念
1、面向对象的概念
面向对象编程是一种程序设计的规范,他将对象作为程序的基本单位,将程序和数据封装在其中,以提高软件的重用性、灵活性和扩展性。对象指的是类的实例。面向对象的编程语言使程序能够较直观的反映客观世界的本来面目。所有面向对象的程序设计语言都支持对象、类、消息、封装、继承、多态等诸多概念。面向对象程序设计可以看作是种在程序中博阿寒各种独立而又互相调用的对象的思想。传统程序设计主张将程序看作是一系列函数的集合,或者一系列直接对计算机下达的指令。面向对象程序设计中的每一个小对象都应该能够接收数据、处理数据并将数据传达给其他对象。
1、类
类定义了意见事情的抽象特点、定义了事物的属性和它可以完成的动作(它的行为)。面向对象程序设计就是通过数据抽象,将许多实例中共性的数据和为操作这些数据所需要的算法抽取出来,并进行封装和数据隐藏,形成一个新的数据类型——“类”类型。
2、对象
对象是对问题领域中某个实体的抽象,是构成程序的基本单位和运行实体,是应用程序的组装模块,它可以是任何的具体事物。不同对象具有不同的特征和行为。在面向对象程序设计中,对象作为一个变量,包括数据和用来处理这些数据的方法和工具。
·· 类和对象就好比是“整数”和“123”,“整数”是一种数据的类型,而123是一个真正的“整数”(及对象)。所有的“整数”都具有“整数”所描述的属性,如“整数的大小”,系统则将内存分配个“整数“存储具体的数值。对象是程序可以控制的实体,包括属性、事件和方法。
3、属性
属性就是对象的特性,是用来描述对象特征的参数。属性是属于某一个类的,不能独立于类而存在。
4、方法
方法是指对象本省所具有的、反映该对象功能的内部函数或过程,亦即对象的动作。通俗的说,方法就是一个对象所执行的某些特定动作,如对象的移动、显示和打印等。其组织行为表现为过程和函数。
5、事件
事件泛指能被对象识别的用户操作动作或对象状态的变化发出的信息,亦即对象的响应。例如,在某个命令按钮上单击鼠标就会出现相应的程序代码,实现相应的功能,则单击鼠标就是一种事件。
2、面向对象的基本概念
1、封装性
封装是面向对象程序设计特征之一,是对象和类概念的主要特征。封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。封装保证了模块具有较好的独立性,使得程序维护修改较为容易。对应用程序的修改仅限于类的内部,因此可以将应用程序修改
带来的影响减少到最低限度。
2、继承
面向对象程序设计的最大优点是允许”继承“,即在某个类的基础上可以派生出来新类。一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特征,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以在它的基类那里继承方法和实例变量,而且类可以修改或者增加新的方法使之更适合特殊的需要。目前的面向对象程序设计开发工具都提供了大量的类,用户可以直接使用这些类,或通过对这些类的扩充和重用形成新的类。
3、多态性
多态性允许不同类的对象对同一个消息做出不同的响应。最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类不同而实现不同的方法。具体实现有静态多态性和动态多态性两种表现形式。静态多态性是通过一般的函数重载来实现的;动态多态性是通过虚函数来实现的。
2、类与对象
1、类的声明和对象的定义
类是具有相同特性对象的集合,类规定并提供了对象具有的属性特征和行为规则。对象通过类来产生,对象是类的实例。在C++语言中,类和对象的关系就是数据类型和具体变量的关系,是进行程序设计的基础。
1、类的定义
类定义了事物的属性和它可以完成的动作(它的行为)。类包括描述其属性的数据和对这些数据进行操作的函数。定义方法如下:
class 类名 { public 数据成员; 成员函数; private 数据成员; 成员函数; protected 数据成员; 成员函数; };
例如:下面程序定义了一个时间类。
class CTime { private: int hour, minute, second; public: void print() { cout << hour << ":" << minute << ":" << second << endl; } void SetTime(int h, int m, int s) { hour = h; minute = m; second = s; } };
该时间类包括三个私有数据成员,分别表示时、分、秒,两个公有成员函数用域设置时间、输出时间。
关于类的定义需要注意以下几点:
- class是定义类的关键字,class后的类名是用户根据程序需要自己定义的,只要复合标识符命名规则就可以。按照通常做法,一般以大写的class字头C开头,C后首字母大写。类名后的{}括起来的部分为类体,需要注意的是类体后面的 ”;"不能省略。
- 类体内的数据成员。数据成员一般是一系列变量的声明,但要注意的是不能在声明变量时就初始化。因为类只是相当于数据类型,不是具体变量,所以无法赋值。
- 类的数据成员和成员函数前的三个关键字public、private和protect表示三种不同的访问权限:
- public(公有的):公有的数据成员和成员函数可以被程序中任何函数或语句调用。
- protected(保护的):保护成员不能被类外的函数或语句调用,只有该类的成员函数才能存取保护成员。但是如果该类派生了子类,则子类也可以存取保护成员。
- private(私有的):私有成员就只能被类中函数访问,类外的函数或语句无法存取。
- 如果定义类时,数据成员或成员函数前没有写任何关键字,则默认的访问权限是private。由于私类成员在类外不可见,很好的体现了类的封装性,公有成员则是类与外部程序的接口。
- 在类体内,三个表示访问权限的关键字不必同时出现,即使同时出现,先后顺序也可以随意放置,并且同意关键字可以出现多次。
- 类的定义域struct定义结构体类型时的格式相同,只是结构体只有数据成员,没有成员函数。结构体成员前也可以用public、private和protected限定访问权限,但一般情况下都没有写,采用默认方式(public)。所以结构体也可以看作是类的一种。
2、对象的定义
类相当于一种数据类型,可以用来定义具体变量,即进行类的实例化,创建对象。具体格式如下:
(1)先定义类,再定义对象,格式为: <类名> <对象名表> (2)定义类的同时定义对象,格式为: class <类名> { 类的成员 }<对象列表>;
定义对象时,需要注意以下几点:
- 对象名表可以是一个对象名,也可以是多个对象名,各个对象名之间用逗号隔开即可。例如:
CTime t1,t2;
- 对象不仅可以是一般对象,还可以是数组、指针或引用名。例如
CTime t[24],pt,&rt=t1;(t1是已经定义过的同类对象)
- 对象不仅可以是一般对象,还可以省略类名,即用无名类直接定义对象,只是没有类名,在后续程序中能再继续使用。
2、类的成员函数
再类体内,成员函数表示的是类的行为,是类与外部程序的接口。成员函数的定义可以放在类体内,也可以放在类体外。当成员函数放在类体内是,即使没有关键字inline修饰,该函数也为内联函数。如果成员函数的定义放在类体外,在类体内需要加函数声明,在类体外定义函数时,函数名前要加强类名和作用域运算符::。如时间类可以改为如下格式:
class CTime { private: int hour, minute, second; public: void print() { cout << hour << ":" << minute << ":" << second << endl; } void SetTime(int h, int m, int s);//类体内的函数声明 }; void CTime::SetTime(int h, int m, int s)//类体外的函数定义即函数实现 { hour = h; minute = m; second = s; }
此时,成员函数不是内联函数,乳沟要将定义在类体外的成员函数设置为内联函数,必须用关键字inline进行显示声明,即在返回值类型前加上inline。
3、对象的使用以及对成员的访问
用类定义了对象后,对象就具有与类相同的属性和行为,类有哪些成员,对象就有哪些成员,既包括数据成员也包括成员函数。对象成员的表示方法为:
<对象名>.<数据成员> <对象名>.<成员函数>(<参数表>)
例如,用时间类定义对象t1,并表示t1的所有成员。
CTime t1; t1.hour,t1.minute,t1.second,t1.SetTime(8,0,0),t1.print()
如果对象是对象指针,则将成员运算符.换成->,即:
<对象指针名>-><数据成员> <对象指针名>-><成员函数>(<参数表>)
例如:
CTime *pt=t1; pt->hour,pt->minute,pt->second,pt->SetTime(8,0,0),pt->print()
示例:编程,用时间类的对象输出时间
方法1:使用一般对象
#include "stdafx.h" #include<iostream> using namespace std; class CTime { private: int hour, minute, second; public: void print() { cout << hour << ":" << minute << ":" << second << endl; } void SetTime(int h, int m, int s);//类体内的函数声明 }; void CTime::SetTime(int h, int m, int s)//类体外的函数定义即函数实现 { hour = h; minute = m; second = s; } int main() { CTime t1; t1.SetTime(11, 0, 0); t1.print(); return 0; }
运行结果如下:
方法2:使用对象指针
#include "stdafx.h" #include<iostream> using namespace std; class CTime { private: int hour, minute, second; public: void print() { cout << hour << ":" << minute << ":" << second << endl; } void SetTime(int h, int m, int s);//类体内的函数声明 }; void CTime::SetTime(int h, int m, int s)//类体外的函数定义即函数实现 { hour = h; minute = m; second = s; } int main() { CTime t1,* pt = &t1; //pt为对象指针 pt->SetTime(11, 0, 0); pt->print(); return 0; }
方法3:使用对象引用
int main() { CTime t1, &rt=t1; //rt为对象引用 rt.SetTime(11, 0, 0); rt.print(); return 0; }
4、构造函数与析构函数
在C++中,构造函数和析构函数是类的两个特殊的成员函数,特殊之处在于,构造函数和析构函数都与类同名,只是析构函数名要在类名前加上符号~。两个函数都是公有的,由系统自动调用。在创建对象时系统会自动调用相应的构造函数进行对象的初始化;当一个对象的生存期即将结束时会调用析构函数做释放内存等清理工作。
1、构造函数
构造函数作为类的成员函数,是在类体内定义的,函数名与类名相同,与一般函数不同,他不能指定任何返回类型(包括void)。而且C++允许构造函数的重载。作为类的成员函数,构造函数可以直接访问类的所有数据成员,根据构造函数参数的具体情况,将构造函数分为以下几种:
- 默认构造函数(不带参数)
在类定义时,如果没有定义构造函数,系统会自动生成一个默认构造函数,而且不带任何参数,函数体是空的,形式为:
<类名>(){}
由于默认构造函数不带参数,为对象初始化时不被指定任何具体值。此时系统会按照对象的存储类型不同区别对待,如果对象是auto存储类型,则数据成员的额初始值是无效的,如果是extern或static存储类型,则数据成员初始值为其默认值(0或空)。上例中都没有给出构造函数的定义,但实际上都采用了默认构造函数,具体构造函数形式为:
CTime(){}
- 带参数的构造函数
一般的,构造函数都带有参数,创建构造对象时给出实参,系统会根据实参个数、类型调用相应的构造函数为对象进行初始化。其一般形式为:
<类型>(函数名1 参数1,类型名2 参数2,...) { 函数体 }
在上例中,函数SetTime()的作用是为了给数据成员赋值,那么可以用构造函数取代它,程序可修改为如下形式。
#include "stdafx.h" #include<iostream> using namespace std; class CTime { private: int hour, minute, second; public: void print() { cout << hour << ":" << minute << ":" << second << endl; } CTime(int hh, int mm, int ss)//带参数的构造函数 { hour = hh; minute = mm; second = ss; } }; int main() { CTime t1(11, 30, 20);//创建对象时以参数的形式为对象初始化 t1.print(); return 0; }
可以看出,虽然实现的功能相同,但两个主函数是不同的。前面的例子只是调用默认的构造函数,所以定义对象后,,调用成员函数SetTime(11,0,0)设置时间。这里因为使用的是带参数的构造函数,所以创建对象时就以参数的形式初始化对象。
另外,构造函数的参数也可以有默认值,例如:
CTime(int hh, int mm=10, int ss=20)//带默认参数的构造函数 { hour = hh; minute = mm; second = ss; }
创建对象时,可以写为:CTime t1(9),t2(5,33),t3(12,22,32).那么对象t1初始值为:9:10:20,t2的初始值为5:33:20,t3的初始值为:12:22:32.
- 构造函数初始化列表
C++类构造函数初始化列表是在函数后加一个冒号“:”,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化表达式。例如,类CExample有两个公有数据成员a和b,如果采用构造函数初始化列表方式进行初始化,代码可以写成如下列形式。
class CExample { public: int a; int b; CExample() :a(10), b(20) {} };
当然,也可以在构造函数的函数体内对a和b赋值,构造函数可以改写为下列形式。
CExample() { a = 10; b = 20; }
两个构造函数的结果是一样的。使用初始化列表的构造函数可显示的初始化类的成员;而没使用初始化列表的构造函数是对类的成员赋值,并没有进行显式的初始化。两种形式的构造函数目的都是对数据成员初始化,对C++的内置数据类型和复合类型(指针,引用)来说,两种格式的初始化在性能和结果上都是一样的。但对于自定义类型(例如类)数据成员来说,虽然结果相同,但是性能上存在很大的差别。因为“类”类型的数据成员对象在进入函数体前已经构造完成,也就是说在成员初始化列表处进行构造对象的的工作,调用构造函数,在进入函数体之后,对已经构造好的类对象赋值,需要在调用拷贝赋值操作符。
所以,内置“类”类型成员变量,为了避免两次构造,推荐使用类的构造函数初始化列表。但是对于以下两种情况则必须用带有初始化列表的构造函数。
(1)成员类型是没有默认构造函数的类。若没有进行显示的初始化,则编译器隐式的使用该成员类型所属类的默认构造函数,如果该类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
(2)成员是const成员或对象引用,由于const对象或引用只能初始化,不能对它们赋值。
初始化列表中成员一般不是一个,C++初始化类成员时,是按照声明的顺序,而不是按照出现在初始化列表中的顺序进行初始化的。例如,即使上例中的构造函数列表改为
CExample():b(20),a(10)
其初始化时仍然按照先a后b的顺序,因为在类中是按先a后b的顺序声明的。
- 拷贝构造函数
如果构造函数的参数是一个已经定义的同类对象的而引用就称为拷贝构造函数。相当于用一个已知对象初始化另一个对象。例如,类CTime的拷贝构造函数可以写为下列形式。
CTime(CTime &t) { hour = t.hour; minute = t.minute; second = t.second; }
下列这段代码是前面例子的CTime类的构造函数改写为带参数的构造函数和拷贝构造函数的形式。
#include "stdafx.h" #include<iostream> using namespace std; class CTime { private: int hour, minute, second; public: void print() { cout << hour << ":" << minute << ":" << second << endl; } CTime(int hh, int mm=10, int ss=20)//带三个参数的构造函数 { hour = hh; minute = mm; second = ss; } CTime(CTime &t) //拷贝构造函数,参数为同类对象的引用 { hour = t.hour; minute = t.minute; second = t.second; } }; int main() { CTime t1(8, 30, 20); //调用带三个参数的构造函数为对象t1初始化 CTime t2(t1); //调用拷贝构造函数为对象t2初始化 t1.print(); t2.print(); return 0; }
程序运行结果如下:
本程序中包含两个构造函数,是构造函数的重载,系统会根据创建对象时的实参来确定调用哪个构造函数。上例中忽略拷贝构造函数,程序结果仍然不变,这是因为如果没有编写拷贝构造函数,编译器会自动产生一个拷贝构造函数。
2、析构函数
析构函数与构造函数类似,函数也与类名相同,只是在函数名前面加一个符号~。以区别于构造函。例如,CTime类的析构函数为~CTime(),它不能带任何参数,也没有返回值(包括void类型)。析构函数通常用于释放内存资源,定义析构函数时需注意只能有一个析构函数,不能重载。
如果用户没有编写析构函数,编译系统会自动生成一个默认的析构函数,函数体为空,即不执行任何操作。所以许多简单的类中没有用显式的析构函数。
如果用new为一个类对象分配了动态内存,最后要用delete释放对象时,析构函数也会自动调用。
5、const对象和const成员函数
1、常数据成员
定义类时,如果数据对象的定义语句前用关键字const加以修饰,就是常数据成员。其格式为:
const <类型> <常数据成员名>
常数据成员的值是不能被改变的,只能在定义时有构造函数的成员初始化列表赋值,具体格式是:
<构造函数名>(<参数表>):<成员初始化列表> {<函数体>}
例如,类A的定义如下:
class A { public: A(int i,int j):a(i) { b = j; } . . . private: const int a; int b; };
在构造函数列表中将常数据成员a初始化为i的值。
2、常成员函数
用const关键词说明的函数叫做常成员函数。其格式如下:
<类型><函数名>(<参数表>)const { <函数体>}
常成员函数不能更新对象的数据,也不能调用非const修饰的其他成员函数,但可以被其他成员函数调用。常成员函数可以与同名函数重载,即使两者参数相同。例如,如果类A有两个成员变量
int A:func(){...} 与 int A:func() const{...}
两者就是重载函数。
3、常对象
在声明对象时,如果用const关键字修饰,说明该对象为常对象,在程序中不能修改。
格式为:
<类名>const<对象名>
或者
const<类名><对象名>
声明常对象的同时必须初始化,因为常对象的数据成员在程序中不能被修改,常对象只能调用常成员函数。
例:分析下类程序执行系欸过,熟悉常对象的使用方法。
#include "stdafx.h" #include<iostream> using namespace std; class point { int x, y; public: point(int a, int b)//构造函数 { x = a; y = b; } void movepoint(int a, int b)//一般成员函数 { x += a; y += b; } void print()const//常成员函数 { cout << "x=" << x << " y=" << y << endl; } }; int main() { point point1(1, 1); //point1作为普通对象 const point point2(2, 2);//point2为常对象 point1.print();//普通对象可以调用常成员函数 point2.print();//常对象调用常成员函数 return 0; }
运行结果如下:
6、对象的动态建立和释放
C++允许用new和delete为用户自定义类(用户自定义的数据类型)进行动态内存分配和释放,即进行对象的动态建立和释放。如果系统为对象成功开辟内存空间,则返回内存空间的首地址,通常把改地址赋值给一个指针。用new建立的动态对象一般不适用对象名,而是通过指针进行访问。创建动态对象的语句格式有如下两种/
类名 *pc= new 类名;//为指定的类动态分配内存,将首地址存入指针变量。 类名 *pc=new 类名(初值);//未指定的类的对象动态分配内存并进行对象的初始化
如果要创建动态对象数组,语句格式为:
类名 *pc=new 类名 [数组大小]
用new建立的对象,使用完毕要用delete运算符予以释放,格式为:
delete 指针名;//用于释放单个对象 delete []指针名;//用于释放对象数组
假如已经定义了某个类,如果想用new进行对象的动态内存分配,就要知道该类有几个数据成员,赋值时需要提供几个数据。由于对象的初始化通常都是由构造函数来完成的,首先需要了解类的构造函数是如何定义的,这样才能正确的使用new创建对象空间和初始化。
例如,类A的定义中,构造函数带有三个int型参数,则动态对象创建可以用如下语句实现。
A *po; po=new A(6,7,8) delete po;
第一条语句定义了指向类A类型的指针po,第二条语句将动态分配的类A的对象地址存入po,最后一条语句释放对象po。上述三条语句与A *po=new A(6,7,8)等效。
下面两条语句用于创建动态对象数组。
A *par; par=new A[10];
第一条语句定义了指向类A类型的指针par,第二条语句将动态分配的类A的对象数组的首地址存入指针par,该对象数组包含10个元素。上述两条语句可以改写为:
A *par=new A[10]
例:分析下列程序执行结果,了解对象的动态建立与释放
#include "stdafx.h" #include<iostream> #include<string> using namespace std; class A { private: string name; public: A(); A(string); ~A(); }; A::A() { cout << "默认构造函数正在执行,创建无名对象" << endl; } A::A(string s) :name(s) { cout << "带参数构造函数A()正在执行,创建对象:" << name << endl; } A::~A() { cout << "~A()正在执行,析构对象:" << name << endl; } int main() { A *pt1 = new A("aaa"); A *pt2 = new A(); delete pt2; A *ptr = &A(); delete pt1; return 0; }
程序运行结果如下:
7、this指针
this指针是类的一个自动生成、自动隐藏的私有成员,它存在于类的每一个非静态成员函数中,作为成员函数的一个隐藏的形参,指向被调用函数所在的对象。
当调用一个对象的成员函数时,编译程序先将对象的地址赋给this指针,this形参不能由成员函数定义,而是由编译隐含的定义的。
在类的一般成员函数中,this指针被隐含的声明,格式为:
类名 * const this
this是一个指向const类型对象的const指针,既不能改变this所指向的对象的值。也就是说不仅不能给this指针赋值,this指针所指向的对象也是不可修改的,不能为这种对象的数据成员进行赋值操作。
例:定义一个point类,用x,y两个私有成员表示点的做i包,分析下列称程序运行结果,熟悉this指针用法。
#include "stdafx.h" #include<iostream> using namespace std; class point { private: int x, y; public: point(int a, int b) { x = a; y = b; } void movepoint(int a, int b) { this->x += a; this->y += b; } void print() { cout << "x=" << x << "y=" << y << endl; } }; int main() { point point1(10, 10);//创建point1并初始化 point1.movepoint(1, 2); point1.print(); return 0; }
当对象point1调用movepoint(2,2)函数时,即将point1对象的地址传递给this指针。所以movepoint函数体中用this->x,this->y调用对象的私有成员。movepoint函数应该还包括一个隐含参数——this指针,函数的原型应该是void movepoint(point *this,int a,int b),这样point1的地址传递给了this,,所以movepoint函数中便显示的写成:
this->x += a; this->y += b;
7、友元函数和友元类
采用类的级之后实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,成员函数一般定义为公有的,这些成员函数提供了类与外界间的通信接口。但是,有时需要定义一些函数,这些函数不是类的一部分,但又需要频繁的访问类的数据成员,这时可以将这些函数定义为类的友元函数。例如,如果类A中的函数fun()要访问类B中的成员,那么函数fun()就应该定义为类B的友元函数。友元函数的使用可以使一个类外的成员函数直接访问另一个类的私有成员。
使用友元函数虽然提高了程序了运行效率(减少了类型检查和安全性检查等),但它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。
如果友元函数是一个外部函数,不属于任何类,该函数称为友元外部函数,如果该函数是另一个类的成员函数,则称为友元成员函数。一般的,将友元外部函数称为友元函数。
1、友元函数
友元函数是可以直接访问类的私有成员的非成员函数。它是定义在类外的普通函数,不属于任何类,但需要在类的定义中加以声明,声明时只需在函数类型名称前加上关键字friend,其格式如下:
friend 类型 函数名(形式参数);
友元函数的声明可以放在类的私有部分,也可以放在公有部分,他们是没有区别的,都说明函数是该类的一个友元函数。在友元函数的函数体中可以通过对象名访问本类的所有成员,包括私有成员和保护成员。下面是一个简单的友元函数实例:
#include "stdafx.h" #include<iostream> using namespace std; class A { public : A(int i) //构造函数 { x = i; } private: int x; friend int func(A &a, int i); //声明友元函数func() }; int func(A &a, int i) { return a.x + i; } int main() { A a(3); cout << func(a, 6)<<endl; //友元函数不属于类,直接调用,不要用a.func() return 0; }
在主函数中,实在类外,不能通过类A的对象a来访问类的私有成员x。
使用友元函数需注意以下几点:
- 友元函数不属于类,没有this指针。
- 因为友元函数是类外的函数,所以它的声明可以放在类的私有段或公有段且没有区别,一般放在私有段。
- 友元函数定义在类外部,函数类型前不加关键字friend。
- 一个函数可以是多个类的友元函数,只要在各个类中分别声明即可。
2、友元成员函数
如果类A的一个成员函数为类B的友元函数,则该函数称为友元成员函数。这样,在类A的这个成员函数中,借助参数类B的对象可以直接访问B的私有变量。
在类B中,要在public段声明友元成员函数,格式为:
friend 类型 函数名(形式参数)
调用是,应该先定义B的对象b——使用b调用自己的成员函数——自己的成员函数中使用了友元机制。下面是一个友元成员函数的使用实例。
#include "stdafx.h" #include<iostream> using namespace std; class B; class A { private: int x; public: A() { this->x = 0;//等价于x=0 } void display(B &v);//成员函数声明 }; class B { private: int y; int z; public: B(); //类B构造函数声明,无参数 B(int, int); //类B构造函数声明,含两个参数 friend void A::display(B &); //类A的成员函数display()被声明为类B友元函数 }; void A::display(B &v) { cout << "v.y=" << v.y << endl; cout << "v.z=" << v.z << endl; x = v.y + v.z; cout << "x=" << x << endl;//类B的私有成员y,z为初始值为0 } B::B() { this->y = 0; this->z = 0; } B::B(int y, int z) { this->y = y; this->z = z;//类B的私有成员y,z初始化为参数值 } int main() { A a; B b(2, 3); a.display(b); return 0; }
运行结果如下:
本例中,类A需先定义,这样才能将其成员函数display()定义为类B的友元函数。为了正确的构造类,需要注意友元声明与友元定义之间的互相依赖。
3、友元类
如果一个类X的所有成员函数都是另一个类Y的友元函数,则类X就是类Y的友元类,类X的成员都可以访问类Y中的成员,包括私有成员和保护成员。
定义友元类的语句格式入下:
friend class 类名;
其中,friend和class是关键字,类名必须是程序中的一个已定义过的类。
例如,以下语句说明类B是类A的友元类。
class {...}; class A { ... public: friend class B; ... };
上例中,类B的所有成员函数都是类A的友元函数,能存取类A的私有成员和保护成员。下面是一个友元类的使用示例:
#include "stdafx.h" #include<iostream> using namespace std; class A { public: friend class B;//声明类B是A的友元类 void display() { cout << x << endl; } private: int x; }; class B { private: A a; public: B(int i) { a.x = i; } void display() { cout << "a.x=" << a.x << endl; } void display1() { cout << "a.display()="; a.display(); } }; void main() { B b(10); b.display(); b.display1(); }
程序运行结果如下:
使用友元类时需要注意以下几点:
- 友元关系不能被继承,如果类B是类A的友元,类B的派生并不会自动称为A的友元。
- 友元关系是单向的,不具有交换性。若类B是类A的友元,B类的成员函数就可以访问类A的私有和保护函数,但类A的成员函数却不能访问类B的私有和保护数据。类A不一定是类b的友元,要看在B中是否又相应的声明。
- 友元关系不具有传递性。若B是类A的友元,类C是类B的友元,类C和类A之间,如果没有声明,就不存在友元关系。
8、类的静态成员和静态成员函数
定义类时,在其数据成员和成员函数前加上static关键字,这些成员就变成静态数据成员和静态成员函数。
1、静态数据成员
在进行类的定义时,不论private、protected还是public访问权限的数据成员,只要在数据类型前加上关键字static,该成员变量就是类的静态数据成员。静态数据成员是特殊的数据成员,表现在以下几点:
- 当用同一个类定义了多个对象时,所有对象的静态数据成员占用同一个空间,即不同对象的静态数据成员实际上是同一个变量。它保存在内存的全局数据区。利用这一特性可以实现数据在各个对象间的共享和消息传递,它的值可以更新。
- 静态数据成员是属于类的,它的初始化与一般数据成员初始化不同,不是通过构造函数进行。要在类体外初始化,但在类体内要进行声明。
静态数据成员初始化的格式为:
<数据类型> <类名>::<静态数据成员名>=<值>
- 类的静态数据成员有两种访问形式
<类对象名>.<静态数据成员名> 或 <类类型名>::<静态数据成员名>
例如,类A的静态成员i,用A定义了a,b两个对象,若访问i可以写为:
a.i,b.i, 或 A::i
-
静态数据成员虽然存储在全局数据区,但它不是全局变量。不存在与程序中其他全局变量冲突的可能性;静态数据成员可以说private成员,而全局变量则不能。
下面是一个类的静态数据成员用法
#include "stdafx.h" #include<iostream> using namespace std; class Myclass { public: Myclass(int i, int j, int k);//构造函数的声明 void GetSum(); private: int a, b, c; static int Sum; //声明类的静态数据成员Sum }; int Myclass::Sum = 0; //定义并初始化静态数据成员 Myclass::Myclass(int i, int j, int k)//构造函数定义 { this->a = i; this->b = j; this->c = k; Sum += i + j + k; } void Myclass::GetSum() //成员GetSum定义,用于输出Sum的值。 { cout << "Sum=" << Sum << endl; } void main() { Myclass M(1, 2, 3); M.GetSum(); Myclass N(4, 5, 6); N.GetSum(); //输出静态数据成员Sum的值 }
2、静态成员函数
在类的定义中,在成员函数定义或声明时,函数类型前加上关键字static,该函数就是一个静态成员函数,它是属于类而不是属于具体某个对象。使用静态成员函数时需要注意以下几点。
- 普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,通常情况下,this是省略的。如函数fn()实际上是this->fn()。但是,静态成员函数由于不与任何的对象相联系,因此它不具有this指针。
- 静态成员函数不能字节访问类的非静态数据成员,也无法直接访问非静态成员函数,它只能直接访问静态成员和静态成员函数。但它可以通过类的对象字节访问非静态数据成员和非静态成员函数。即
对象名.非静态数据成员 或 对象名.非静态成员函数
- 如果在类体内只是静态成员函数的声明,在类体外进行函数定义时不能再指向关键字static。
下面是一个静态成员的用法
#include "stdafx.h" #include<iostream> #include<string> using namespace std; class Student { static int number; string name; public: void set(string str) { name = str; number++; } void print() { cout << "1:The number of the student is " << number << " numbers" << endl; } static void print2(Student &s) { cout << "2:The number of the student is " << number << " numbers" << endl; cout <<s.name<< endl; s.print(); } }; int Student::number = 0; int main() { Student s; s.set("smith"); s.print2(s); return 0; }