第一章 类和对象
类和对象是面向对象程序设计的两个最基本、最重要的概念。所谓对象就是客观事务在计算机中的抽象描述;而所谓类则是对具有相似属性和行为的一组对象的统一描述。从程序设计语言的角度来说,类是一种数据类型,而对象是具有这种类型的变量。
1.类的定义
类定义的一般形式为:
class 类名
{
private:
数据成员或成员函数
protected:
数据成员或成员函数
public:
数据成员或成员函数
};
<各成员函数的实现代码>
其中,class是定义类的关键字。类名是一个有效的标志符,且一般首字母大写。大括号括起来的部分是类说明部分,它声明了类的所有成员(包括数据成员和函数成员),这些成员从访问权限上分成三类,即私有(private)、公有(public)和保护(protected),其中默认权限为private.
类的private部分说明的成员,在类之外是不能存取的,只有类中的成员函数才能存取private的数据成员和成员函数。类的public部分说明的成员,可被程序中的任何函数或语句存取,public成员多为成员函数,用来提供一个与外界的接口,外界只有通过这个接口才可以实现对private成员的存取。类的protected部分说明的成员,不能在类之外存取,只有类的成员函数及其子类(派生类)可以存取protected的成员。
2.成员函数的定义
类中的成员函数可以在两处定义:一是将成员函数的定义直接写在类中,一般适合于成员函数规模较小的情况;二是在类的定义体中只写出成员函数的原型说明,而成员函数的定义写在类的定义之外,这种情况比较适合于成员函数体较大的情况,其定义格式为:
返回值类型 类名::成员函数名(参数说明)
{
函数体;
}
此处的“::”符号称为作用域运算法(名空间分隔符),它是用来指明哪个函数属于哪个类或哪个数据属于哪个类。
需要说明的是,成员函数既可以是有参函数,也可以像普通函数一样,给形参指定默认值。
3.对象及指向对象的指针
对象是类的实例,定义对象之前一定要说明该对象的类。定义对象的一般格式为:
类名 对象名表;
其中,对象名表中可以有一个或多个对象名,多个对象名之间用逗号分隔。
另外,也可以定义指向类类型的指针,其定义格式为:
类名 *指针变量名;
4.访问对象的成员
访问对象的成员有以下两种格式:
(1) 对象名。成员名
(2) 指针变量名->成员名
第一种格式是利用对象和运算符“。”访问成员;而第二种格式是采用指向对象的指针和运算符“->”来访问成员,并且第二种格式中的指针变量必须已指向某个对象。
5.类成员指针
在C++语言中,除了可以定义指针对象外,还可以定义类成员指针。
类数据成员指针就是程序中定义的用于指向类中数据成员的指针变量,借助该指针变量可以访问它所指向的类中的数据成员(该数据成员必须具有public访问权限),但该指针变量不是类的成员,它只是程序中的一个指针变量而已。
类数据成员指针的定义格式如下:
类型 类名::*指针变量名;
此处的“类型”要与类中数据成员的类型保持一致。
要使已经定义的类数据成员的指针变量指向类中某个数据成员时,可以通过以下语句:
类数据成员指针变量名=&类名::类数据成员变量名;
当类数据成员指针变量已经指向类中某个数据成员时,可以通过以下语句访问类中数据成员:
对象名。*类数据成员指针变量名;
6.this指针
在每一个类成员函数的形参表中都有一个隐含的指针变量this,该指针变量的类型就是成员函数所属类的类型。当程序中调用成员函数时,this指针变量被自动初始化为发出函数调用的对象的地址。
尽管我们在定义成员函数时没有看到this指针变量,也没有定义this指针变量,但是在成员函数的函数体内我们可以使用this指针变量,因为该指针变量使系统隐含给出的,我们不需要也不能在成员函数的形参表中对this指针变量进行显示说明。
类中成员函数有了隐含的指针变量this后,就可以保证用不同的对象调用成员函数是对不同对象的操作。
7.对象赋值语句
对于同一个类生成的两个对象,可以进行赋值,其功能是将一个对象的数据成员赋值到另一个对象中去,赋值语句的左右两边各是一个对象名。
如,已知一个类Example,则:
Example obj1,obj2;
obj2=obj1;//将对象obj1的数据成员赋给对象obj2
8.对象的作用域和生存期
在不同的位置以不同的方式定义对象时,其作用域和生存期是不同的,其原理与普通变量相同。可分为局部对象(不包括局部静态对象)、静态对象(局部静态对象和全局静态对象)、全局对象等。
9.堆对象
所谓堆对象是指在程序运行过程中,根据需要随时可以建立和删除的对象。堆对象被创建在内存中一些空闲的存储单元中,这些存储单元被称为堆。堆可以被创建的对象占有,可以通过删除堆对象而获得释放。
需要利用new运算符创建堆对象,利用delete运算符删除堆对象。堆对象的生存期是整个程序的生命期。如:
Example *p;//p为指向类Example对象的一个指针
p=new Example(); //使用new给p分配内存空间
delete p; //使用delete释放p所指向的空间
10.对象数组
当一个数组的类型为类类型时,该数组中的每个元素都是该类中的一个对象,则这种数组就是对象数组。对象数组的定义格式为:
类名 数组名 [数组大小];
如:
Example array[10];
表明array数组是一个一维对象数组,该数组有10个元素,从array[0]到array[9],其中每个元素都是类Example的对象。
C++学习摘要之二:构造函数和析构函数
构造函数和析构函数是类的两个特殊的成员函数
1.构造函数
构造函数(constructor)是类的一个特殊的成员函数,它与类名同名。当定义该类的对象时,构造函数将被系统自动调用用以实现对该对象的初始化。
构造函数不能有返回值,因而不能指定包括void在内的任何返回值类型。
构造函数的定义与其他成员函数的定义一样可以放在类内或类外。
构造函数的定义格式为:
类名(形参说明)
{函数体}
构造函数既可以定义成有参函数,也可以定义成无参函数,要根据问题的需要来定。
注意:程序中不能直接调用构造函数,构造函数是在创建对象时由系统直接调用的,因此,在构造函数中一般完成初始化类成员变量的操作。
2.构造函数的重载
一个类中出现了两个以上的同名的成员函数时,称为类的成员函数的重载。
在类的成员函数的重载中,比较常见形式是构造函数的重载,当类中出现了重载构造函数时,C++语言将根据构造函数中的参数个数和类型选择合适的构造函数来完成对象的构造。
3.默认构造函数与缺省参数的构造函数
如果在类中没有显示定义构造函数,则编译系统会为该类提供一个默认的构造函数,该默认构造函数是一个无参函数,函数体为空,它仅仅负责创建对象,而不做任何初始化工作(即不给相应的数据成员赋初值),所以在该类的对象创建时不能保证有一个确定的初始状态。
良好的编程习惯应该是给类提供合适的完成初始化工作的构造函数。
但是,只要一个类定义了一个构造函数(不一定是无参构造函数),编译系统就不再提供默认的构造函数。
当构造函数具有缺省参数时,称为具有缺省参数的构造函数,在使用具有缺省参数的构造函数时,要防止二义性。
4.拷贝构造函数
拷贝构造函数是一种特殊的构造函数。定义拷贝构造函数的一般格式为:
类名::类名(const 类名 &形式参数)
{ 函数体 }
拷贝构造函数的函数名与类名同名。该函数也没有返回值。
拷贝构造函数的功能是通过将一个同类对象的值拷贝给一个新对象,来完成对新对象的初始化,即用一个对象去构造另外一个对象。
如果在类的定义中没有定义拷贝构造函数,则编译系统将自动生成一个具有上述形式的默认的拷贝构造函数,作为该类的公有成员。
5.析构函数
与构造函数对应的是析构函数。当一个对象被定义时,系统会自动调用构造函数为该对象分配相应的资源,当对象使用完毕后且在对象消失前,系统会自动调用类的析构函数来释放这些系统资源。
析构函数也是类的一个特殊的成员函数,其函数名称是在类名的前面加上“~”;它没有返回值,也没有参数。一个类中只能拥有一个析构函数,所以析构函数不能重载。
析构函数的定义方式为:
~类名()
{ 函数体 }
如果程序员在定义类时没有为类提供析构函数,则系统会自动创建一个默认的析构函数,其形式为:
~类名()
{ }
对象被析构的顺序与其创建时的顺序正好相反,即最后构造的对象最先被析构。
如果一个对象是被new运算符动态创建的,当使用delete运算符释放它时,delete将会自动调用析构函数。
6.一个类的对象作为另一个类的数据成员
当一个类中的数据成员是某一个类的对象时,可称这种成员是新建类的子对象或对象成员,则新类的定义格式可表示为:
calss X
{
类名1 成员名1;
类名2 成员名2;
类名3 成员名3;
……………
类名n 成员名n;
….………… //其他成员
};
其中,X为新建类的类名,类名1、类名2、……、类名n必须是已定义过的类。如:
class A {};
class B
{
A a;
};
则在创建类B的对象(调用类B的构造函数)时,会自动调用类A的构造函数。如果类A的构造函数为有参函数时,通常采用初始化表的方式来调用构造函数。
新类的构造函数的一般定义格式为:
新类(参数表0):成员1(参数表1),成员2(参数表2),…,成员n(参数表n)
{ …….. }
其中,成员1、成员2、……、成员n是新类中的对象成员;参数表1提供初始化成员1所需的参数,参数表2提供初始化成员2所需的参数,依此类推,并且这几个参数表中的参数均来自参数表0.另外,初始化新类的非对象成员所需的参数,也由参数表0提供。
7.常对象与常对象成员
(1)常对象
常对象是指对象常量,其定义格式为:
const 类名 对象名;
从格式中可以看出,常对象的定义与一般对象的定义相比,在类名前必须加const关键字。
常对象具有以下特点:
l 常对象在定义时必须进行初始化,而且在程序中不能再对其进行更新。
l 通过常对象只能调用类中的常成员函数,而不能调用类中的其他成员函数。
(2)常对象成员
常对象成员分为常成员函数和常数据成员。
1)常成员函数
在类中,使用关键字const说明的成员函数成为常成员函数,常成员函数的说明格式为:
类型 函数名(形参表) const;
类中的常成员函数与普通成员函数相比,具有以下特点:
l 常成员函数为类的只读函数,这种成员函数可以读取数据成员的值,但不可以更新数据成员的值,它也不能调用该类中没有const修饰的其他成员函数。
l 常成员函数定义中的const关键字是函数类型的一部分,因此在其实现部分中也要带上const关键字。
l 常成员函数定义中的const关键字可以参与区分重载函数。
例如:
#include <iostream.h>
class Test_const{
private:
int m;
public:
Test_const(int arg1) //构造函数
{
m=arg1;
}
void setvalue(int newvalue);
void showvalue();
void showvalue() const; //常成员函数
};
void Test_const::setvalue(int newvalue)
{
m=newvalue;
}
void Test_const::showvalue()
{
cout<<"m="<<m<<endl;
}
void Test_const::showvalue() const //此处的const关键字不可少
{
cout<<"const example m="<<m<<endl;
}
void main()
{
Test_const c1(100); //定义对象c1
const Test_const c2(100); //定义常对象c2
c1.setvalue(200);
c1.showvalue(); //此处调用的是函数void showvalue();
c2.showvalue(); //此处调用的是函数void showvalue() const;
//不能执行语句c2.setvalue(200);因为常对象c2只能调用常成员函数
}
2)常数据成员
类中定义的数据成员,除了可以为一般变量外,还可以为const常量,这种数据成员称为常数据成员。
构造函数可以对对象的数据成员进行初始化,但如果数据成员为常量成员或引用成员时,则不能在构造函数中直接用赋值语句为其进行赋值。需要利用构造函数所附带的初始化表进行初始化,即在构造函数的括号后面加上“:”和初始化表,其格式为:
类名::类名(形参表):常数据成员名1(值1),常数据成员名2(值2),……
{
//构造函数的函数体
}
可以看出,当有多个数据成员时,初始化表中的初始化项有多个,且需要用逗号隔开。
8.类作用域
类作用域又可称为类域,它是指在类定义中用一对大括号开括起来的范围。
不同的类的成员函数可以具有相同的名字,因此,需要用作用域运算符“::”来指明该成员函数所属的类。
在类的成员函数中可以直接引用类的数据成员。但是,如果在成员函数中定义了同名的局部变量时,则必须用作用域运算符“::”来指定,以免混乱。例如:
#include <iostream.h>
class Region{
private:
int x;
int y;
public:
Region(int x,int y)
{
Region::x=x;
Region::y=y;
}
void print()
{
cout<<"x="<<x<<",y="<<y<<endl;
}
};
void main()
{
Region region(5,10);
Region *p;
p=®ion;
region.print();
p->print();
}
如果要从类外访问类的成员,则必须通过对象名和圆点成员选择符“.”或指向对象的指针和箭头成员选择符“->”。如上例中的语句region.print()和语句p->print();是等价的。