类(一)
现在介绍一个C++中最重要的概念:类。
C++内存在了大量的内置类型,比如int
表示整形(整数),double
表示浮点(小数)。在了解类之前,同学们仅仅知道去用各种类型,但并不知道类的机理,也不知道C++可以自己创建类。C++中同学们可以自定义自己的类型,在类的基础上,实现数据的封装(数据隐藏)、多态、继承以及代码复用。这些特性,是自定义类的目的。
现在看下面的代码,其定义了一个非常简单的类型:
#include <iostream>
using namespace std;
class myInt
{
private:
public:
int test = 0;
};
int main()
{
myInt myClass;
cout<< myClass.test;
return 0;
}
在这个代码段中,同学们通过class
关键词自定义了一个类型为myInt
。所有类的定义是以关键字 class
为开头,后跟类的名称。类的主体包含在一对花括号{}
中。类定义后必须跟着一个分号;
。
随后的private:
和public:
关键词稍后讲解。其主要涉及类的数据隐藏和封装。
在类myInt
的花括号内,int test = 0;
声明了一个整形数据test
。在这里要注意,一些C++内置类型比如int
其只能为一个值,但同学们的自定义类型内可以包含若干的数据。其类似C++中的结构struct
。在myInt
中,还可以包含更多的数据,如:
class myInt
{
private:
public:
int test = 0;
double test2 = 3.1;
int test5 = 4;
};
其表示myInt
中包含了三个数据。
main()
主函数中则演示了如何调用myInt
的数据。首先,myInt
为一个类型,因此需要首先创建一个myInt
:myInt myClass;
(类似int myClass
)。其名为myClass
。注意myClass
为同学们的自定义myInt
类型,其中可能包含若干数据而不是一个数据。并且名字可以随便写。在调用myClass
内部的数据的时候,需要指定具体的数据名称,如:
myClass.test;
其表示调用名为myClass
的myInt
类型的内部的test
数据。.
为成员访问运算符。随后,cout
函数对其值进行输出。
上面就是一个简单的类型的范例。C++中的类不仅可以包含数据,还可以包含函数,如:
#include <iostream>
using namespace std;
class myInt
{
private:
public:
int test = 0;
void output()
{
cout<< test;
}
};
int main()
{
myInt myClass;
myClass.output();
return 0;
}
相比较于上面的代码块,本段代码在同学们自定义类型myInt
中增加了一个函数output()
,其输出myInt
类型中数据test
的值。在主函数中,myClass.output();
即调用output()
函数进行输出。
现在,我们将最开始的代码进行一下改动:
#include <iostream>
using namespace std;
class myInt
{
private:
int test = 0;
public:
//int test = 0;
};
int main()
{
myInt myClass;
cout<< myClass.test;
return 0;
}
上述代码把int test = 0;
从public:
后移入到private:
内,在尝试运行的时候,会提示:
main.cpp: In function ‘int main()’:
main.cpp:16:16: error: ‘int myInt::test’ is private
int test = 0;
数据隐藏
现在可以讨论一下private
和public
关键词。private
表示其中的数据为自定义类型的私有数据,即在自定义类型之外不能访问。也就是说,只有class内部的代码才能调用这个私有数据。public
表示其中的数据为自定义类型的公有数据,在自定义类型之外也可以访问。在上面这个例子中,main()
函数内,myClass.test;
表示调用自定义类型内的数据test
,但test
类型为私有的(private
),因此不能通过编译。
如果test
类型为私有的,如何访问呢?
可以通过类内定义的公有成员函数来进行访问,这是一种迂回的战术。如:
#include <iostream>
using namespace std;
class myInt
{
private:
int test = 0;
public:
void output()
{
cout << test;
}
};
int main()
{
myInt myClass;
myClass.output();
return 0;
}
这段代码将output()
定义在public
后,表示其为一个公有的成员函数。在自定义类型之外可以进行调用。这是一种迂回的操作,曲线救国,实现了对私有成员函数的访问。下面是一个稍微复杂的例子:
#include <iostream>
using namespace std;
class myInt
{
private:
//私有数据成员a_
int a_;
//私有数据成员b_
int b_;
//私有数据成员c_,值为99
int c_ = 99;
public:
//公有成员函数,对a_赋值
void assignA(int a)
{
a_ = a;
}
//公有成员函数,对b_赋值
void assignB(int b)
{
b_ = b;
}
//公有成员函数,对c_赋值 = a_和b_的和
void sum()
{
c_ = a_ + b_;
}
//公有成员函数,输出c_的值
void output()
{
cout << c_ << endl;
}
};
int main()
{
myInt myClass;
myClass.assignA(4);
myClass.assignB(10);
//在执行sum()之前,c_的值为99,输出99
myClass.output();
myClass.sum();
//在执行sum()之后,c_的值为14,输出14
myClass.output();
return 0;
}
接口
类的初始意图就是尽可能的对类内的数据进行隐藏(定义在private
内的私有数据)。在对类内数据隐藏之后,类外(比如main
函数)是不能调用类内隐藏的数据的。但类的公有成员函数(定义在public
内的函数)可以在类外进行调用。这些公有的成员函数也被称之为接口。
接口形象的实现类外代码访问类内隐藏的私有数据的一个途径。
封装
上面的类代码看起来比较雍容,最重要的,其还不能实现类的封装。
在某些情况下,一些闭源的代码不想让同学们获取某些计算的细节。比如上个代码中的sum()
函数,类的设计者可能并不想让代码泄露出去。这种理念就是类的封装:将公有接口(定义在public
内的函数声明)和具体的实现细节(定义在public
内的函数的代码)分开。下面的代码将对同学们起到一点启发作用:
#include <iostream>
using namespace std;
//类声明,位于main()函数之前
class myInt
{
private:
int a_;
int b_;
int c_ = 99;
public:
void assignA(int a);
void assignB(int b);
void sum();
void output();
};
int main()
{
myInt myClass;
myClass.assignA(4);
myClass.assignB(10);
myClass.output();
myClass.sum();
myClass.output();
return 0;
}
//共有成员函数代码实现
void myInt::assignA(int a)
{
a_ = a;
}
//共有成员函数代码实现
void myInt::assignB(int b)
{
b_ = b;
}
//共有成员函数代码实现
void myInt::sum()
{
c_ = a_ + b_;
}
//共有成员函数代码实现
void myInt::output()
{
cout << c_ << endl;
}
这一段代码实现的和上一段代码是完全相同的内容。但是类内公有成员函数的实现细节和声明分别定义。具体的,main()
函数之前的为类声明,其中公有函数部分也仅仅进行了声明。在main()
函数之后,所有公有函数需要定义具体的实现细节。比如,
void myInt::assignA(int a)
{
a_ = a;
}
中即为普通的函数的定义。但需要注意的是,assignA(int a)
之前需要指定其属于类的函数,这通过类名myInt
和作用于操作符::
类实现。因此void myInt::assignA(int a)
即表示返回类型为void
,myInt
类型内的assignA()
函数,传入参数为int a
。
上述将类声明和类实现分开定义的写法是实现封装的基本过程。
OpenFOAM实例
下面是摘自OpenFOAM粘度代码的一段实例,其中省略了一些还没有涉及到的内容。
/*---------------------------------------------------------------------------*\
Class Newtonian Declaration
\*---------------------------------------------------------------------------*/
//自定义类型,名称为Newtonian
class Newtonian
{
// 私有成员数据
// private:可以省略
//私有dimensionedScalar类型,名称为nu0_
dimensionedScalar nu0_;
volScalarField nu_;
public:
//略去一些尚没有介绍的公有成员函数
...
// 公有成员函数
//- nu()函数返回volScalarField类型
volScalarField nu()
{
return nu_;
}
//- 声明correct()函数
void correct();
};
现在,同学们应该可以从上段代码中看出:
这是一个类声明,类的名字为
Newtonian
(从class Newtonian
可以判断);类内的私有数据为
nu0_
和nu
,其分别为dimensionedScalar
和volScalarField
类型;这两个数据只能在类内进行访问(私有数据);类存在一个公有函数
nu()
,其直接返回类的私有数据nu_
,nu()
函数为一个接口;