-
C++教程之类(Classes)
类(Classes)
类(class)是一种将数据和函数组织在同一个结构里的逻辑方法。定义类的关键字为class ,其功能与C语言中的struct类似,不同之处是class可以包含函数,而不像struct只能包含数据元素。类定义的形式是:
class class_name { permission_label_1: member1; permission_label_2: member2; ... } object_name;其中 class_name 是类的名称 (用户自定义的类型) ,而可选项object_name 是一个或几个对象(object)标识。Class的声明体中包含成员members,成员可以是数据或函数定义,同时也可以包括允许范围标志 permission labels,范围标志可以是以下三个关键字中任意一个:private:, public: 或 protected:。它们分别代表以下含义:
- private :class的private成员,只有同一个class的其他成员或该class的“friend” class可以访问这些成员。
- protected :class的protected成员,只有同一个class的其他成员,或该class的“friend” class,或该class的子类(derived classes) 可以访问这些成员。
- public :class的public成员,任何可以看到这个class的地方都可以访问这些成员。
例如:
class CRectangle { int x, y; public: void set_values (int,int); int area (void); } rect;上面例子定义了一个class CRectangle 和该class类型的对象变量rect 。这个class 有4个成员:两个整型变量 (x 和 y) ,在private 部分 (因为private 是默认的允许范围);以及两个函数, 在 public 部分:set_values() 和 area(),这里只包含了函数的原型(prototype)。
注意class名称与对象(object)名称的不同:在上面的例子中,CRectangle 是class 名称 (即用户定义的类型名称),而rect 是一个CRectangle类型的对象名称。它们的区别就像下面例子中类型名 int和 变量名a 的区别一样:
int a;
int 是class名称 (类型名) ,而a 是对象名 object name (变量)。
在程序中,我们可以通过使用对象名后面加一点再加成员名称(同使用C structs一样),来引用对象rect 的任何public成员,就像它们只是一般的函数或变量。例如:
rect.set_value (3,4);
myarea = rect.area();
但我们不能够引用 x 或 y ,因为它们是该class的 private 成员,它们只能够在该class的其它成员中被引用。晕了吗?下面是关于class CRectangle的一个复杂的例子:
// classes example #include <iostream.h> class CRectangle { int x, y; public: void set_values (int,int); int area (void) {return (x*y);} }; void CRectangle::set_values (int a, int b) { x = a; y = b; } int main () { CRectangle rect; rect.set_values (3,4); cout << "area: " << rect.area(); } |
area: 12 |
范围操作符 (::) 声明了被定义的成员所属的class名称,并赋予被定义成员适当的范围属性,这些范围属性与在class内部定义成员的属性是一样的。例如,在上面的例子中,我们在函数set_values() 中引用了private变量x 和 y,这些变量只有在class内部和它的成员中才是可见的。
在class内部直接定义完整的函数,和只定义函数的原型而把具体实现放在class外部的唯一区别在于,在第一种情况中,编译器(compiler) 会自动将函数作为inline 考虑,而在第二种情况下,函数只是一般的class成员函数。
我们把 x 和 y 定义为private 成员 (记住,如果没有特殊声明,所有class的成员均默认为private ),原因是我们已经定义了一个设置这些变量值的函数 (set_values()) ,这样一来,在程序的其它地方就没有办法直接访问它们。也许在一个这样简单的例子中,你无法看到这样保护两个变量有什么意义,但在比较复杂的程序中,这是非常重要的,因为它使得变量不会被意外修改 (这里意外指的是从object的角度来讲的意外)。
使用class的一个更大的好处是我们可以用它来定义多个不同对象(object)。例如,接着上面class CRectangle的例子,除了对象rect之外,我们还可以定义对象rectb :
// class example #include <iostream.h> class CRectangle { int x, y; public: void set_values (int,int); int area (void) {return (x*y);} }; void CRectangle::set_values (int a, int b) { x = a; y = b; } int main () { CRectangle rect, rectb; rect.set_values (3,4); rectb.set_values (5,6); cout << "rect area: " << rect.area() << endl; cout << "rectb area: " << rectb.area() << endl; } |
rect area: 12 rectb area: 30 |
这是基于对象( object) 和 面向对象编程 (object-oriented programming)的概念的。这个概念中,数据和函数是对象(object)的属性(properties),而不是像以前在结构化编程 (structured programming) 中所认为的对象(object)是函数参数。在本节及后面的小节中,我们将讨论面向对象编程的好处。
在这个具体的例子中,我们讨论的class (object的类型)是CRectangle,有两个实例(instance),或称对象(object):rect 和 rectb,每一个有它自己的成员变量和成员函数。
构造函数和析构函数 (Constructors and destructors)
对象(object)在生成过程中通常需要初始化变量或分配动态内存,以便我们能够操作,或防止在执行过程中返回意外结果。例如,在前面的例子中,如果我们在调用函数set_values( ) 之前就调用了函数area(),将会产生什么样的结果呢?可能会是一个不确定的值,因为成员x 和 y 还没有被赋于任何值。为了避免这种情况发生,一个class 可以包含一个特殊的函数:构造函数 constructor,它可以通过声明一个与class同名的函数来定义。当且仅当要生成一个class的新的实例 (instance)的时候,也就是当且仅当声明一个新的对象,或给该class的一个对象分配内存的时候,这个构造函数将自动被调用。下面,我们将实现包含一个构造函数的CRectangle :
// class example #include <iostream.h> class CRectangle { int width, height; public: CRectangle (int,int); int area (void) {return (width*height);} }; CRectangle::CRectangle (int a, int b) { width = a; height = b; } int main () { CRectangle rect (3,4); CRectangle rectb (5,6); cout << "rect area: " << rect.area() << endl; cout << "rectb area: " << rectb.area() << endl; } |
rect area: 12 rectb area: 30 |
CRectangle rect (3,4);
CRectangle rectb (5,6);
同时你可以看到,构造函数的原型和实现中都没有返回值(return value),也没有void 类型声明。构造函数必须这样写。一个构造函数永远没有返回值,也不用声明void,就像我们在前面的例子中看到的。
析构函数Destructor 完成相反的功能。它在objects被从内存中释放的时候被自动调用。释放可能是因为它存在的范围已经结束了(例如,如果object被定义为一个函数内的本地(local)对象变量,而该函数结束了);或者是因为它是一个动态分配的对象,而被使用操作符delete释放了。
析构函数必须与class同名,加水波号tilde (~) 前缀,必须无返回值。
析构函数特别适用于当一个对象被动态分别内存空间,而在对象被销毁的时我们希望释放它所占用的空间的时候。例如:
// example on constructors and destructors #include <iostream.h> class CRectangle { int *width, *height; public: CRectangle (int,int); ~CRectangle (); int area (void) {return (*width * *height);} }; CRectangle::CRectangle (int a, int b) { width = new int; height = new int; *width = a; *height = b; } CRectangle::~CRectangle () { delete width; delete height; } int main () { CRectangle rect (3,4), rectb (5,6); cout << "rect area: " << rect.area() << endl; cout << "rectb area: " << rectb.area() << endl; return 0; } |
rect area: 12 rectb area: 30 |
构造函数重载(Overloading Constructors)
像其它函数一样,一个构造函数也可以被多次重载(overload)为同样名字的函数,但有不同的参数类型和个数。记住,编译器会调用与在调用时刻要求的参数类型和个数一样的那个函数(Section 2.3, Functions-II)。在这里则是调用与类对象被声明时一样的那个构造函数。实际上,当我们定义一个class而没有明确定义构造函数的时候,编译器会自动假设两个重载的构造函数 (默认构造函数"default constructor" 和复制构造函数"copy constructor")。例如,对以下class:
class CExample { public: int a,b,c; void multiply (int n, int m) { a=n; b=m; c=a*b; }; };没有定义构造函数,编译器自动假设它有以下constructor 成员函数:
- Empty constructor
CExample::CExample () { };
- Copy constructor
CExample::CExample (const CExample& rv) { a=rv.a; b=rv.b; c=rv.c; }必须注意:这两个默认构造函数(empty construction 和 copy constructor )只有在没有其它构造函数被明确定义的情况下才存在。如果任何其它有任意参数的构造函数被定义了,这两个构造函数就都不存在了。在这种情况下,如果你想要有empty construction 和 copy constructor ,就必需要自己定义它们。
当然,如果你也可以重载class的构造函数,定义有不同的参数或完全没有参数的构造函数,见如下例子:
// overloading class constructors #include <iostream.h> Class CRectangle { int width, height; public: CRectangle (); CRectangle (int,int); int area (void) {return (width*height);} }; CRectangle::CRectangle () { width = 5; height = 5; } CRectangle::CRectangle (int a, int b) { width = a; height = b; } int main () { CRectangle rect (3,4); CRectangle rectb; cout << "rect area: " << rect.area() << endl; cout << "rectb area: " << rectb.area() << endl; } |
rect area: 12 rectb area: 25 |
注意在我们声明一个新的object的时候,如果不想传入参数,则不需要写括号():
CRectangle rectb; // right
CRectangle rectb(); // wrong!
类的指针(Pointers to classes)
类也是可以有指针的,要定义类的指针,我们只需要认识到,类一旦被定义就成为一种有效的数据类型,因此只需要用类的名字作为指针的名字就可以了。例如:CRectangle * prect;
是一个指向class CRectangle类型的对象的指针。
就像数据机构中的情况一样,要想直接引用一个由指针指向的对象(object)中的成员,需要使用操作符 ->。这里是一个例子,显示了几种可能出现的情况:
// pointer to classes example #include <iostream.h> class CRectangle { int width, height; public: void set_values (int, int); int area (void) {return (width * height);} }; void CRectangle::set_values (int a, int b) { width = a; height = b; } int main () { CRectangle a, *b, *c; CRectangle * d = new CRectangle[2]; b= new CRectangle; c= &a; a.set_values (1,2); b->set_values (3,4); d->set_values (5,6); d[1].set_values (7,8); cout << "a area: " << a.area() << endl; cout << "*b area: " << b->area() << endl; cout << "*c area: " << c->area() << endl; cout << "d[0] area: " << d[0].area() << endl; cout << "d[1] area: " << d[1].area() << endl; return 0; } |
a area: 2 *b area: 12 *c area: 2 d[0] area: 30 d[1] area: 56 |
- *x 读作: pointed by x (由x指向的)
- &x 读作: address of x(x的地址)
- x.y 读作: member y of object x (对象x的成员y)
- (*x).y 读作: member y of object pointed by x(由x指向的对象的成员y)
- x->y 读作: member y of object pointed by x (同上一个等价)
- x[0] 读作: first object pointed by x(由x指向的第一个对象)
- x[1] 读作: second object pointed by x(由x指向的第二个对象)
- x[n] 读作: (n+1)th object pointed by x(由x指向的第n+1个对象)
由关键字struct定义的类
C++ 语言以将C 语言中的关键字struct 扩展到与C++语言中的class 关键字具有基本相同的功能,除了它所有的成员都默认为 public 而不是private。不管怎样,因为 class 和 struct 在C++中几乎具有同样的功能,struct 通常被用在只有数据的结构中,而class 则被用在有过程和成员函数的情况下。
最新更新
Objective-C语法之代码块(block)的使用
VB.NET eBook
Add-in and Automation Development In VB.NET 2003 (F
Add-in and Automation Development In VB.NET 2003 (8
Add-in and Automation Development in VB.NET 2003 (6
Add-in and Automation Development In VB.NET 2003 (5
AddIn Automation Development In VB.NET 2003 (4)
AddIn And Automation Development In VB.NET 2003 (2)
Addin and Automation Development In VB.NET 2003 (3)
AddIn And Automation Development In VB.NET 2003 (1)
2个场景实例讲解GaussDB(DWS)基表统计信息估
常用的 SQL Server 关键字及其含义
动手分析SQL Server中的事务中使用的锁
openGauss内核分析:SQL by pass & 经典执行
一招教你如何高效批量导入与更新数据
天天写SQL,这些神奇的特性你知道吗?
openGauss内核分析:执行计划生成
[IM002]Navicat ODBC驱动器管理器 未发现数据
初入Sql Server 之 存储过程的简单使用
SQL Server -- 解决存储过程传入参数作为s
武装你的WEBAPI-OData入门
武装你的WEBAPI-OData便捷查询
武装你的WEBAPI-OData分页查询
武装你的WEBAPI-OData资源更新Delta
5. 武装你的WEBAPI-OData使用Endpoint 05-09
武装你的WEBAPI-OData之API版本管理
武装你的WEBAPI-OData常见问题
武装你的WEBAPI-OData聚合查询
OData WebAPI实践-OData与EDM
OData WebAPI实践-Non-EDM模式