现在的位置: 首页 > 综合 > 正文

C++语言指南(十八)——类 (I)

2013年08月29日 ⁄ 综合 ⁄ 共 7627字 ⁄ 字号 评论关闭
*********************************************************
原文:http://www.cplusplus.com/doc/tutorial/
 *********************************************************
*********************************************************
声明:
    欢迎转载,但请保持版权信息的完整。
翻译:云淡风轻
http://blog.csdn.net/dragonxie1983
http://www.cfannet.com
********************************************************
是对数据结构体的概念的扩展:不再只拥有数据,它可以数据和函数都拥有。
对象是类的实例化。以变量的观点来看,类就是一个类型,而对象就是它的变量。
类通常使用关键字class来声明,下面是它的声明形式

class class_name {
 access_specifier_1:
    member1;
 access_specifier_2:
    member2;
 ...
 } object_names;

其中class_name是类的一个有效标识符,object_names是这个类的对象名的可选列表。声明体可以包含成员,它可以是数据一个可以是函数声明,和可选的访问说明符。
除了我们现在也能够把函数和成员同时包含在其中,也引入了叫做访问权限修饰符access specifier)的新的东西,其他的同在数据结构体上的声明十分类似。一个访问权限修饰符是下面三个关键字中的一个:privatepublicprotected。这些修饰符限制跟在它们后面的成员获得的访问权限:

  • private成员只对与其同一类的其他成员或这个类的友员可访问。
  • protected成员可以被它同一个类的其他成员或它们的友员访问,但也可以被他的继承类的成员访问。
  • 最后, public 成员可以被从这个对象可见的任何地方访问。
默认情况下,用class关键字声明的一个类的所有成员都是private的。因此,任何在任意其他类修饰符前声明的成员都自动拥有private访问权限。例如:
class CRectangle {
    int x, y;
 public:
    void set_values (int,int);
    int area (void);
 } rect;
声明一个类(例如,一个类型)叫做CRectangle和这个类的一个对象(例如,一个变量)叫做rect。这个类拥有4个成员:两个int型的数据成员(成员x和成员y),它们是private访问权限的(因为private是默认访问级别)和两个有public访问权限的成员函数:set_values()area(),对于这两个成员函数我们仅包含了他们的声明,而没有他们的定义。
注意类名和对象名之间的不同:在上面的例子中,CRectangle是类的名字(就像,类型),而rect是类型CRectangle的一个对象。这和下面声明中inta的关系是一样的:
int a;
其中int是类型名(类),而a是变量名(对象)。
在提前定义了CRectanglerect后,我们可以在程序体的任何地方像使用普通函数或通常的变量那样使用对象rect的任何公共(public)成员,只是需要在对象名后跟一个点(.)然后再是成员的名字。所有都和我们在前面对数据结构体做的一样。例如:
rect.set_values (3,4);
myarea = rect.area();

从我们在类外的程序体中我们唯一不能访问的rect的成员是xy,因为它们有私有访问权限(private access),并且它们只能从与它们同一个类中的其他成员访问。

这是类CRectangle的一个完整的例子:
// classes example
#include <iostream>
using namespace std;
 
class CRectangle {
    int x, y;
 public:
    void set_values (int,int);
    int area () {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();
 return 0;
}
area: 12
这段代码中最重要的一个新东西就是包含在set_values()的定义中的域(scope)操作符(::,两个冒号)。它被用来从一个类的自身声明之外来定义它的一个成员。
你或许已经注意到了,成员函数area()的定义被十分简单的直接包含在类CRectangle的定义中,而set_values()在类中只有它的原型声明,但它的定义却在类声明的外面。在这个外声明中,我们必须使用域操作符(::)来指明我们正在定义的函数是类CRectangle的一个成员,而不是一个常规的全局函数。
域操作符(::)用来指明被定义的是属于哪个类的成员,并得到完全相同的域属性,就像这个函数被直接包含在类定义中一样。例如,在前面的代码中的函数set_values()中,我们可以使用变量xy,而它们是类CRectangle的只能被它们类中的其他成员说访问的私有成员。
完全在类中定义一个类成员函数,和仅在类中包含一个原型但在以后定义它的唯一区别是:在第一种情况中,这个函数将被编译器自动的认为是一个inline成员函数,而第二种则被认为是一个普通的(非inline)类成员函数,事实上在行为上它们没有任何区别。
成员xy拥有私有访问权限(记住如果什么其他的也没说,用关键字class定义的一个类的所有成员都是私有访问权限的)。通过声明他们是私有的我们拒绝从类外的任何地方对它们的访问。这是有意义的,因为我们已经定义了一个成员函数来设置对象中的这些成员:成员函数set_values()。因此,在程序的其他地方不再需要直接的访问他们。或许在像这样一个简单的例子中,很难看出保护这两个变量的实用性,但是在一个更大的工程中值不能被用一种不可意料的方式修改是非常重要的了(从对象的角度来看是不可意料的)。
一个类的一个更大的优点是,就像任何其他类型,我们能够声明它的几个对象。例如,在前面类CRectangle的例子之后,我们可以声明对象rectb来对对象rect作一个添加:
// example: one class, two objects
#include <iostream>
using namespace std;
 
class CRectangle {
    int x, y;
 public:
    void set_values (int,int);
    int area () {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;
 return 0;
}
rect area: 12
rectb area: 30 
在这个具体例子中,类(对象的类型)这里我们谈论当然是CRectangle了,有两个实例或对象:rectrectb。它们的每一个都有自己的成员变量和成员函数。
注意对rect.area()的调用没有给出和对rectb.area()的调用相同的结果。这是因为每个类CRectangle的对象都有它自己的变量xy,同样的,就像这样,也有它们自己的函数成员set_value()area(),这些函数操作它的对象自己的变量。
那就是基本的面向对象编程object-oriented programming)的观点:数据和函数都是对象的成员。我们不再使用全局变量集合来作为我们从一个函数向另一个函数传递的参数,但是作为替代我们操作对象,它们有内嵌的作为成员的自己的数据和函数。注意,我们不需要在任何对rect.arearectb.area的调用中给出任何参数。这些成员函数直接使用它们各自对象rectrectb的数据成员。
构造函数和析构函数
对象通常在它们被创建的过程中需要初始化变量或分配动态内存,以使它们可以运转并且避免在它们运行期间返回以外值。例如,在我上面的例子中我们在调用函数set_values()之前调用成员函数area()会发生什么呢?或许我们会到一个不确定的结果,因为成员xy还没有被赋予一个值。
为了避免那样,一个类可以包含一个叫做构造函数(constructor)的特殊函数,一旦这个类的一个新的对象被创建这个函数会被自动的调用。构造函数必须同它所在的类有完全相同的名字,并且没有任何返回类型,甚至是void
我们将实现一个包含一个构造函数的CRectangle
// example: class constructor
#include <iostream>
using namespace std;
 
class CRectangle {
    int width, height;
 public:
    CRectangle (int,int);

    int area () {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;
 return 0;
}
rect area: 12
rectb area: 30 
正如你看到的,这个例子的结果和前一个是相同的。但是现在我们去掉了成员函数set_values(),并且作为替代包含了一个构造函数,这个构造函数完成相似的动作:它用传递给它的参数来初始化变量xy的值。
注意这些参数是如何在这个类的对象被创建时传递给构造函数的:
CRectangle rect (3,4);
CRectangle rectb (5,6);
构造函数不能被像其他普通成员函数那样显式的调用。它们仅在某个类的一个新对象被创建时被执行。
我们也看到无论是原型还是随后的构造函数的实现都不包含一个返回值,甚至是void
析构函数用来完成相反的功能。当一个对象被销毁,或它的生存期已经结束了(例如,如果它被定义为一个函数中的局部变量并且这个函数结束了),再或是它是一个动态分配的对象并且它被使用操作符delete释放掉的时候,析构函数被调用。
析构函数必须与类同名,但是在前面要加一个浪线(颚化符号)(~)并且它也必须不返回任何值。
析构函数特别适合用在,一个对象在它的生存期中分配了动态内存,并且在它被销毁的时候我们希望释放分配给这个对象的那些内存时。

// example on constructors and destructors

#include <iostream>
using namespace std;
 
class CRectangle {

    int *width, *height;

 public:

    CRectangle (int,int);

    ~CRectangle ();

    int area () {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 class constructors
#include <iostream>
using namespace std;
 
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;
 return 0;
}
rect area: 12
rectb area: 25 
在这个例子中,rectb的声明没有带任何实参,因此它被用没有形参的构造函数初始化,这个构造函数把widthheight都初始化为值5
重要:注意如果我们声明一个新的对象并且我们想要使用它的缺省构造函数(就是那个不带形参的),我们不带括号():
CRectangle rectb;   // right(正确)
CRectangle rectb(); // wrong!(错误)
缺省构造函数
如果你在一个类的定义中没有声明任何构造函数,编译器将假设这个类有一个缺省的不带任何参数的构造函数。因此,在声明了一个像这样的类之后:
class CExample {
 public:
    int a,b,c;

    void multiply (int n, int m) { a=n; b=m; c=a*b; };

 };
编译器假设CExample有一个缺省的构造函数,因此你可以通过简单的不带任何参数的方式,来声明这个类的对象:
CExample ex;
但是一旦你为一个类声明了你自己的构造函数,编译器不再提供一个隐式的缺省构造函数。所以,你必须根据你为这个类定义的构造函数的原型,来声明这个类的所有对象:
class CExample {
 public:
    int a,b,c;

    CExample (int n, int m) { a=n; b=m; };

    void multiply () { c=a*b; };
 };
这我们声明了一个带有两个int型参数的构造函数。因此下面的对象声明是正确的“
CExample ex (2,3);
但是,
CExample ex;
将是正确的,因为我们已经声明这个类有一个明确的构造函数,这取代了缺省构造函数。
但是如果你没有指明你自己的,编译器不仅仅为你创建了一个缺省构造函数。如果你没有声明你自己的,它还一共提供三个特殊的成员函数。它们是拷贝构造函数拷贝赋值操作符缺省构造函数
拷贝构造函数拷贝赋值操作符把另一个对象中包含的全部数据拷贝给当前对象的数据成员。对于CExample,被编译器声明的隐式的拷贝构造函数可能同下面的代码做的事情有些相似:
CExample::CExample (const CExample& rv) {
 a=rv.a; b=rv.b; c=rv.c;
 }
因此,下面两个对象的声明将是正确的:
CExample ex (2,3);
CExample ex2 (ex);   // copy constructor (data copied from ex)
指向类的指针
创建指向类的指针是非常正当的。我们只需要简单的认为:一旦被声明,一个类就成为了一个有效的类型,所以我们可以使用类名来作为指针的类型。例如:
CRectangle * prect;
是一个指向类CRectangle的某个对象的指针。
就像在数据结构体上发生的一样,为了直接引用被一指针指向的对象的一个成员,我们使用间接操作的箭头操作符(->)。这是一个带有一些肯能结合的例子:
// pointer to classes example
#include <iostream>
using namespace std;
 
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
下面你有一个如何读在上面例子中出现的一些指针和类操作符(*&.->[])的总结:
表达式

抱歉!评论已关闭.