2019独角兽企业重金招聘Python工程师标准>>>
一. 静态成员
在一个类中还可以定义静态成员,但静态成员是所有对象公有的。静态成员分为静态数据成员和静态成员函数。1.静态数据成员
在类中定义静态数据成员的方法就是在该成员的前面加上关键字static.
定义静态数据成员的语句格式如下:
class 类名
{
……
static 类型说明符 成员名;
……
};
静态数据成员是类的所有对象共享的成员。静态数据成员所占的空间不会随着对象的产生而分配,也不会随着对象的消失而回收。对静态数据成员的操作和类中一般数据成员的操作是不一样的,定义为私有的静态数据成员不能被外界所访问。静态数据成员可由任意访问权限许可的函数所访问。
由于静态数据成员是类的所有对象共享的,而不从属于任何一个具体对象,所以必须对类的静态数据成员进行初始化,但对它的初始化不能在类的构造函数中进行,其初始化语句应当写在程序的全局区域中,并且必须指明其数据类型与所属的类名,其初始化格式如下:
类型 类名::变量名=值;
对于在类的public部分说明的静态数据成员,在类的外部可以不使用成员函数而直接访问,但在使用时必须用类名指明所属的类,其访问格式为:
类名::静态数据成员名
对于在类的非public部分说明的静态数据成员,则只能由类的成员函数访问,其访问方法与访问类中普通数据成员的访问方法完全一样,但在类的外部不能访问。
2.静态成员函数
静态成员函数的定义与一般成员函数的定义相同,只是在其前面冠以static关键字,其定义格式如下:
class 类名
{
…
static 类型 函数名(形参)
{ 函数体 }
…
};
说明:
(1)类的静态成员函数只能访问类的静态数据成员,而不能访问类中的普通函数成员(非静态数据成员),因为普通数据成员只有类的对象存在时才有意义。
(2)静态成员函数与类相联系,而不与类的对象相联系,所以,在类的外部调用类中的公有静态成员函数,必须在其左面加上“类名::”,而不是通过对象名调用公有静态成员函数。在类的外部不能调用类中的私有静态成员函数。
示例:
#include
using namespace std;
class A {
public:
int c;// ISO C++ 不允许成员‘c’的初始化
static int a;// ISO C++ 不允许在类内初始化非常量静态成员‘a’
static string b;
static void hello(){
cout << "HelloWorld" << endl;
}
};
int A::a = 20;
string A::b = "jintao";
int main() {
cout << A::a << "\n" << A::b << endl;
A :: hello();
}
运行结果:
20
jintao
HelloWorld
二. 友元函数
1.1为什么要使用友元函数在实现类之间数据共享时,减少系统开销,提高效率。如果类A中的函数要访问类B中的成员(例如:智能指针类的实现),那么类A中该函数要是类B的友元函数。具体来说:为了使其他类的成员函数直接访问该类的私有变量。即:允许外面的类或函数去访问类的私有变量和保护变量,从而使两个类共享同一函数。
实际上具体大概有下面两种情况需要使用友元函数:(1)运算符重载的某些场合需要使用友元。(2)两个类要共享数据的时候。
1.2使用友元函数的优缺点
1.2.1优点:能够提高效率,表达简单、清晰。
1.2.2缺点:友元函数破环了封装机制,尽量不使用成员函数,除非不得已的情况下才使用友元函数。
2.友元函数的使用
2.1友元函数的参数:
因为友元函数没有this指针,则参数要有三种情况:
2.1.1 要访问非static成员时,需要对象做参数;
2.1.2 要访问static成员或全局变量时,则不需要对象做参数;
2.1.3 如果做参数的对象是全局对象,则不需要对象做参数;
2.2友元函数的位置
因为友元函数是类外的函数,所以它的声明可以放在类的私有段或公有段且没有区别。
2.3友元函数的调用
可以直接调用友元函数,不需要通过对象或指针
2.4友元函数的分类:
根据这个函数的来源不同,可以分为三种方法:
2.4.1普通函数友元函数
2.4.1.1 目的:使普通函数能够访问类的友元
2.4.1.2 语法:
声明: friend + 普通函数声明
实现位置:可以在类外或类中
实现代码:与普通函数相同
调用:类似普通函数,直接调用
2.4.1.3代码:
#include
using namespace std;
class student {
public:
void get();
friend void mm(student &t);
private:
int num;
};
void student::get() {
cin >> num;
}
void mm(student &t) {
cout << t.num << endl;
}
int main() {
student st1;
st1.get();
mm(st1);
return 0;
}
2.4.2类Y的所有成员函数都为类X友元函数—友元类
2.4.2.1目的:使用单个声明使Y类的所有函数成为类X的友元,它提供一种类之间合作的一种方式,使类Y的对象可以具有类X和类Y的功能。
2.4.2.2语法:
声明位置:公有私有均可,常写为私有(把类看成一个变量)
声明: friend + 类名(不是对象哦)
2.4.2.3代码:
#include
using namespace std;
class CApple {
friend class COrange; //声明友元类为COrange
public:
CApple() {
m_x = 0;
m_y = 0;
} //默认构造函数
CApple(int x, int y);
void disp();
private:
int m_x; //数据成员
int m_y;
};
CApple::CApple(int x, int y) //构造函数
{
m_x = x;
m_y = y;
}
void CApple::disp() {
cout << m_x << ";" << m_y << endl;
}
class COrange //友元类COrange的声明
{
public:
COrange(int x, int y); //构造函数
void disp();
private:
CApple m_apple; //声明数据成员为CApple类的对象
};
COrange::COrange(int x, int y) {
m_apple.m_x = x; //访问对象m_ apple的私有成员m_x
m_apple.m_y = y; //访问对象m_ apple的私有成员m_y
}
void COrange::disp() {
cout << m_apple.m_x << ";" << m_apple.m_y << endl;
}
int main() {
CApple apple(55, 88); //创建CApple类对象apple
apple.disp();
COrange orange(10, 100); //创建COrange类对象orange
orange.disp();
}
运行结果:
55;88
10;100
2.4.3类Y的一个成员函数为类X的友元函数
2.4.3.1目的:使类Y的一个成员函数成为类X的友元,具体而言:在类Y的这个成员函数中,借助参数X,可以直接以X的私有变量
2.4.3.2语法:
声明位置:声明在公有中 (本身为函数)
声明:friend + 成员函数的声明
调用:先定义Y的对象y---使用y调用自己的成员函数---自己的成员函数中使用了友元机制
2.4.3.3代码:
实现代码和2.4.2.3中的实现及其相似只是设置友元的时候变为friend void boy::disp(girl &);自己解决喽……
小结:其实一些操作符的重载实现也是要在类外实现的,那么通常这样的话,声明为类的友元是必须滴。
4.友元函数和类的成员函数的区别
4.1 成员函数有this指针,而友元函数没有this指针。
4.2 友元函数是不能被继承的,就像父亲的朋友未必是儿子的朋友。
三 .运算符重载
C++语言中预定义的运算符的操作对象只能是基本数据类型,但是,在实际应用中,对于很多用户自定义数据类型(如类)也需要类似的功能,这就需要对已有的运算符赋予多种含义,使同一个运算符具有作用于不同类性的数据导致不同类型的行为,这就是运算符重载。因此,运算符重载的目的是设置C++语言中的某一运算符,让它们之间并不冲突,C++语言会根据运算符的位置辨别应使用哪一种功能进行运算。可见,运算符重载的优点是允许改变使用于系统内部的运算符的操作方式,以适应用户新定义类型的类似运算。运算符重载的实质是函数重载。事实上,C++语言中的每一个运算符对应着一个运算符函数,在实现过程中,把指定的运算表达式中的运算符转化为对运算符函数的调用,而表达式中的运算对象转化为运算符函数的实参,这个过程是在编译阶段完成的。例如:
int a=1,b=2;
a+b;
表达式“a+b”在编译前,将被解释为函数调用形式:operator+(a,b)。
其中,operator是一个关键字,它与后面的“+”共同组成了该运算符函数的函数名。
运算符重载是一种特殊的函数重载。在类中可以采用下述两种方法对运算符函数进行重载。
1.重载为类的成员函数
将运算符函数重载为类的成员函数是指在类中定义一个同名的运算符函数,其语句格式为:
TYPE X::operator@(形参表)
{
//函数体
//重新定义运算符@在指定类X中的功能
}
其中,operator是关键字,@是需要被重载的运算符,X是需要重载该运算符的类名,TYPE是该运算符函数的返回值类型。关键字operator与后面的运算符@共同组成了该运算符函数的函数名。
2.重载为类的友元函数
可以定义一个与某一运算符函数同名的全局函数,然后再将该全局函数声明为类的友元函数,从而实现运算符的重载。其语法格式为:
friend TYPE operator@(形参表);
下面介绍运算符重载的几个问题:
以上两种重载形式均可访问类的私有成员;
几乎所有的运算符都可以被重载,但下列运算符不允许重载:
“。”、“。*”、“::”、“?:”
运算符重载后,既不会改变原运算符的优先级和结合特性也不会改变使用运算符的语法和参数个数;
“=”、“()”、“[]”和“->”等运算符不能重载为友元函数;
当运算符重载为类的成员函数时,函数的参数个数比原来的运算对象少一个(右++和右——除外);当重载为类的友元函数时,参数个数与原运算符的运算个数相同;单目运算符最好重载为类的成员函数,而双目运算符则最好重载为类的友元函数。
示例:
#include
using namespace std;
class CComplex //定义复数类
{
float real, imag; //复数的实部和虚部
public:
CComplex(float r, float i) {
real = r;
imag = i;
} //带参数的构造函数
CComplex() {
real = 0;
imag = 0;
} //不带参数的构造函数
void Print(); //复数的输出函数
friend CComplex operator+(CComplex a, CComplex b);
//用友元函数重载复数相加运算符
friend CComplex operator-(CComplex a, CComplex b);
//重载复数相减运算符
friend CComplex operator*(CComplex a, CComplex b);
//重载复数相乘运算符
friend CComplex operator/(CComplex a, CComplex b);
//重载复数相除运算符
};
void CComplex::Print() //定义复数的输出函数
{
cout << real;
if (imag > 0)
cout << "+";
if (imag != 0)
cout << imag << "i\n";
}
CComplex operator +(CComplex a, CComplex b) {
CComplex temp;
temp.real = a.real + b.real;
temp.imag = a.imag + b.imag;
return temp;
}
CComplex operator -(CComplex a, CComplex b) {
CComplex temp;
temp.real = a.real - b.real;
temp.imag = a.imag - b.imag;
return temp;
}
CComplex operator *(CComplex a, CComplex b) {
CComplex temp;
temp.real = a.real * b.real - a.imag * b.imag;
temp.imag = a.real * b.imag + a.imag * b.real;
return temp;
}
CComplex operator /(CComplex a, CComplex b) {
CComplex temp;
float tt;
tt = 1 / (b.real * b.real + b.imag * b.imag);
temp.real = (a.real * b.real + a.imag * b.imag) * tt;
temp.imag = (b.real * a.imag - b.imag * a.real) * tt;
return temp;
}
int main() {
CComplex c1(2.3, 4.6), c2(3.6, 2.8), c3; //定义三个复数对象
c1.Print(); //输出复数c1和c2
c2.Print();
c3 = c1 + c2; //复数c1,c2相加,结果给c3
c3.Print(); //输出相加结果
c3 = c2 - c1; //复数c2,c1相减,结果给c3
c3.Print(); //输出相减结果
c3 = c2 * c1; //复数c1,c2相乘,结果给c3
c3.Print(); //输出相乘结果
c3 = c1 / c2; //复数c1,c2相除,结果给c3
c3.Print(); //输出相除结果
}
运行结果:
2.3+4.6i
3.6+2.8i
5.9+7.4i
1.3-1.8i
-4.6+23i
1.01731+0.486538i
四,模板
模板(template)利用一种完全通用的方法来设计函数或类而不必预先说明将被使用的每个对象的类型,利用模板功能可以构造相关的函数或类的系列,因此模板也可称为参数化的类型。在C++语言中,模板可分为类模板(class template)和函数模板(function template)。在程序中说明了一个函数模板后,编译系统发现有一个相应的函数调用时,将根据实参中的类型来确认是否匹配函数模板中对应的形参,然后生成一个重载函数。该重载函数的定义体与函数模板的函数定义体相同,称之为模板函数(template function)。
函数模板与模板函数的区别是:函数模板是模板的定义,定义中用到通用类型参数。模版函数是实实在在的函数定义,它由编译系统在遇到具体函数调用时所产生,具有程序代码。
同样,在说明了一个类模板之后,可以创建类模板的实例,即生成模板类。
类模板与摸板类的区别是:类模板是模板的定义,不是一个实实在在的类,定义中用到通用类型参数;模板类是实实在在的类定义,是类模板的实例。
1.函数模板
通过前面知识的学习可知,在所定义的函数中,函数形参的类型是固定的,当调用函数时,实参的类型要与被调函数的形参类型保持一致,否则会出现类型不一致的错误。因此,对于功能相同而只是参数的类型不同的情况,也必须定义不同的函数来分别完成相应的功能,这显然是很不灵活的。
C++语言中提供的函数模板功能就是为解决以上问题而提出的。C++语言提供的函数模板可以定义一个对任何类型变量都可进行操作的函数,从而大大增强了函数设计的通用性。因为普通函数只能传递变量参数,而函数模板却提供了传递类型的机制。
在C++语言中,使用函数模板的方法是先说明函数模板,然后实例化成相应的模板函数进行调用执行。
函数模板的一般说明形式如下:
template <类型形参表>
返回值类型 函数名(形参表)
{
//函数定义体
}
在上面的定义形式中,<参数形参表>可以有一到若干个形参,各形参前必须加上class关键字,表示传递类型,当有多个形参时,各形参间用逗号分隔。从中可以看出,<类型形参表>中的每个形参就表示了一种数据类型。“形参表”中至少有一个形参的类型必须用<类型形参表>中的形参来定义。
函数模板只是说明,不能直接执行,需要实例化为模板函数后才能执行。当编译系统发现有一个函数调用:函数名(实参表);时,将根据“实参表”中的实参的类型和已定义的函数模板生成一个重载函数即模板函数。该模板函数的定义体与函数模板的定义体相同,而“形参表”中的类型则以“实参表”中的实际类型为依据。
2.类模板
类模板实际上就是函数模板的推广。
说明类模板的一般格式为:
template <类型形参表>
class 类模板名
{
private:
私有成员定义
protected:
保护成员定义
public:
公有成员定义
};
(1)<类型形参表>中可以包括一到若干个形参,这些形参既可以是“类型形参”,也可以是“表达式形参”。每个类型形参前必须加class关键字,表示对类模板进行实例化时代表某种数据类型,也就是说,类型形参是在类模板实例化时传递数据类型用的;表达式形参的类型是某种具体的数据类型,当对类模板进行实例化时,给这些参数提供的是具体的数据,也就是说,表达式形参是用来传递具体数据的。当<类型形参表>中的参数有多个时,需用逗号隔开。如:
template
class myclass
{
//类的定义体
};
此处定义的类模板名是myclass,它有三个参数arg1、arg2和arg3,其中arg1和arg3是类型形参,在类模板实例化时用于传递数据类型,arg2是表达式形参,用于在类模板实例化时传递具体数据。
(2)类模板中成员函数可以放在类模板的定义体中(此时与类中的成员函数的定义方法一致)定义,也可以放在类模板的外部来定义,此时成员函数的定义格式如下:
template <类型形参表>
函数值的返回来性 类模板名<类型名表>::成员函数(形参)
{ 函数体 }
其中:类模板名即是类模板中定义的名称;
类型名表即是类模板定义中的<类型形参表>中的形参名。
(3)类模板定义只是对类的描述,它本身还不是一个实实在在的类,是类模板。
(4)类模板不能直接使用,必须先实例化为相应的模板类,定义模板类的对象(即实例)后,才可使用。可以用以下方式创建类模板的实例。
类模板名<类型实参表> 对象名表;
此处的<类型实参表>要与该模板中的<类型形参表>匹配,也就是说,实例化中所用的实参必须和类模板中定义的形参具有同样的顺序和类型,否则会产生错误。
五,流和文件流:
在程序设计中,数据输入/输出(I/O)操作是必不可少的,C++语言的数据输入/输出操作是通过I/O流库来实现的。C++中把数据之间的传输操作称为流,流既可以表示数据从内存传送到某个载体或设备中,即输出流,也可以表示数据从某个载在程序设计中,数据输入/输出(I/O)操作是必不可少的,C++语言的数据输入/输出操作是通过I/O流库来实现的。C++中把数据之间的传输操作称为流,流既可以表示数据从内存传送到某个载体或设备中,即输出流,也可以表示数据从某个载体或设备传送到内存缓冲区变量中,即输入流。在进行I/O操作时,首先是打开操作,使流和文件发生联系,建立联系后的文件才允许数据流入和流出,输入或输出结束后,使用关闭操作使文件与流断开联系。
标准输入输出流(cout、cin)在使用过程中,只要在程序的开头嵌入相应的头文件“iostream.h”即可。
文件的打开和关闭是通过使用fstream类的成员函数open和close来实现的,fstream类用来对文件流进行操作,fstream类的头文件是fstream.h.
1.数据的输出cout
cout是标准输出设备即显示器(默认设备)连接的预定义输出流。C++语言的插入运算符“<<”向输出流发送字符,cout是数据的目的地,插入运算符“<<”把数据传送到cout.
输出流对象cout输出数据的语句格式为:
cout<<数据1<<数据2<<……<<数据n;
其中,“<<”是输出操作符,用于向cout输出流中插入数据。
在cout中还可以使用流控制符控制数据的输出格式,但要注意使用这些流控制符时,要在程序的开始部分嵌入头文件“iomanip.h”。
常用的流控制符及其功能如下表所示:
表1 I/O流的常用控制符
2.数据的输入cin
cin是与标准输入设备即键盘(默认设备)连接的预定义输入流。它从输入流中取出数据,数据从输入提起运算符“>>”处流进程序。
输入流对象cin输入数据的语句格式为:
cin>>变量名1>>变量名2>>……>>变量名n;
其中,“>>”是输入操作符,用于从cin输入流中取得数据,并将取得的数据传送给其后的变量,从而完成输入数据的功能。
注意:“>>”操作符后除了变量名外不得有其他数字、字符串,否则系统会报错。
3.文件流及其有关的类
文件是存储在磁盘、磁带等外部设备上的数据的集合,每一个文件都必须有一个唯一的文件名称。在使用文件前必须首先打开文件,使用完毕后必须关闭文件。对文件的操作是由文件流类完成的。文件流类在流与文件之间建立连接。下图是与文件操作相关的类及其继承关系。
图1 几个与文件处理相关的类及其继承关系结构图
下表是常用的I/O流类库的说明,以及在编程中需要包含到程序中的头文件。
表2 常用I/O流类库说明
4.文件的打开与关闭
对文件的操作是由文件流类完成的。文件流类在流与文件间建立连接。由于文件流分为三种:文件输入流、文件输出流、文件输入/输出流,所以相应的必须将文件流说明为ifstream、ofstream和fstream类的对象,然后利用文件流的对象对文件进行操作。
对文件的操作过程可按照一下四步进行:即定义文件流类的对象、打开文件、堆文件进行读写操作、关闭文件,下面分别进行介绍。
(1)定义文件流对象
利用文件流类可以定义文件流类对象,方法是:
文件流类 对象名;
如:
ifstream ifile; //定义一个文件输入流对象
ofstream ofile; //定义一个文件输出流对象
fstream iofile; //定义一个文件输出/输入流对象
(2)打开文件
定义了文件流对象后,就可以利用其成员函数open()打开需要操作的文件,该成员函数的函数原型为:
void open(const unsigned char *filename,int mode,int access=filebuf:openprot);
其中:filename是一个字符型指针,指定了要打开的文件名;mode指定了文件的打开方式,其值如下表所示;access指定了文件的系统属性,其取值为:
0 一般文件
1 只读文件
2 隐藏文件
3 系统文件
表3 在ios类中定义的文件打开方式
说明:
1)在实际使用过程中,可以根据需要将以上打开文件的方式用“|”组合起来。如:
ios::in|ios::out 表示以读/写方式打开文件
ios::in|ios:: binary 表示以二进制读方式打开文件
在程序设计中,数据输入/输出(I/O)操作是必不可少的,C++语言的数据输入/输出操作是通过I/O流库来实现的。C++中把数据之间的传输操作称为流,流既可以表示数据从内存传送到某个载体或设备中,即输出流,也可以表示数据从某个载
ios::out|ios:: binary 表示以二进制写方式打开文件
ios::in|ios::out|ios::binary 表示以二进制读/写方式打开文件
2)如果未指明以二进制方式打开文件,则默认是以文本方式打开文件。
3)对于ifstream流,mode参数的默认值为ios::in,对于ofstream流,mode的默认值为ios::out.
(3)文件的关闭
在文件操作结束(即读、写完毕)时应及时调用成员函数close()来关闭文件。该函数比较简单,没有参数和返回值。
利用对象和相应的成员函数对文件进行读写操作,我们将单独介绍。
5.文件的读写操作
在打开文件后就可以对文件进行读写操作了。从一个文件中读出数据,可以使用文件流类的get、getline、read成员函数以及运算符“>>”;而向一个文件写入数据,可以使用其put、write函数以及插入符“<<”,如下表所示:
表4 文件流类的文件操作成员函数
顺序文件操作:这种操作方式只能从文件的开始处依次顺序读写文件内容,而不能任意读写文件内容。
从一个文件中读出数据,可以使用get、getline、read成员函数以及运算符“>>”;而向一个文件写入数据,可以使用put、write成员函数以及插入符“<<”。