您的位置:首页 > 编程语言 > C语言/C++

C++学习笔记——类和对象(一)

2015-07-25 22:42 344 查看

构造函数

类的数据成员是不能在声明类时初始化的。如果一个类中所有的成员都是公用的,则可以在定义对象时对数据成员进行初始化。如:

class Time
{
public : //声明为公用成员
hour;
minute;
sec;
};
Time t1={14,56,30};  //将t1初始化为14:56:30


但是,如果数据成员是私有的,或者类中有private或protected的成员,就不能用这种方法初始化。

构造函数的作用

为了解决这个问题,C++提供了构造函数(constructor)来处理对象的初始化。构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在建立对象时自动执行。构造函数的名字必须与类名同名,它不具有任何类型,不返回任何值。如:

#include <iostream>
using namespace std;
class Time
{
public :
Time( )
{
hour=0;
minute=0;
sec=0;
}
void set_time( );
void show_time( );
private :
int hour;
int minute;
int sec;
};


上面是在类内定义构造函数的,也可以只在类内对构造函数进行声明而在类外定义构造函数。如:

class Time
{
public:
Time( ); //对构造函数进行声明
}

Time::Time( ) //在类外定义构造成员函数,要加上类名Time和域限定符“::”
{
hour=0;
minute=0;
sec=0;
}


带参数的构造函数

构造函数首部的一般格式为:

构造函数名(类型1  形参1, 类型2  形参2, …)


由于用户是不能调用构造函数的,因此无法采用常规的调用函数的方法给出实参。实参是在创建对象时给出的。创建对象的一般格式为:

类名  对象名(实参1, 实参2, …);


例:

class Box
{
public :
Box(int,int,int);
int volume( );
private :
int height;
int width;
int length;
};

//声明带参数的构造函数//声明计算体积的函数
Box::Box(int h,int w,int len) //在类外定义带参数的构造函数
{
height=h;
width=w;
length=len;
}

int main( )
{
...
Box box1(12,25,30); //建立对象box1,并指定box1长、宽、高的值
...
}


此外,还可以用参数初始化表对数据成员初始化,上面定义构造函数可以改用以下形式:

Box::Box(int h,int w,int len):height(h),width(w), length(len){ }


构造函数的重载

在一个类中可以定义多个构造函数,以便提供不同的初始化的方法,供用户选用。这些构造函数具有相同的名字,而参数的个数或参数的类型不相同。这称为构造函数的重载。

class Box
{
public : Box( ); //声明一个无参的构造函数
//声明一个有参的构造函数,用参数的初始化表对数据成员初始化
Box(int h,int w,int len):height(h),width(w),length(len){ }
int volume( );
private :
int height;
int width;
int length;
};


构造函数的默认参数

和普通函数一样,构造函数中参数的值既可以通过实参传递,也可以指定为某些默认值,即如果用户不指定实参值,编译系统就使形参取默认值。

class Box
{
public :
Box(int h=10,int w=10,int len=10); //在声明构造函数时指定默认参数
int volume( );
private :
int height;
int width;
int length;
};
...
int main( )
{
Box box1; //没有给实参
Box box2(15); //只给定一个实参
Box box3(15,30); //只给定2个实参
Box box4(15,30,20); //给定3个实参
}


可以看到,在构造函数中使用默认参数是方便而有效的,它提供了建立对象时的多种选择,它的作用相当于好几个重载的构造函数。

它的好处是,即使在调用构造函数时没有提供实参值,不仅不会出错,而且还确保按照默认的参数值对对象进行初始化。尤其在希望对每一个对象都有同样的初始化状况时用这种方法更为方便。

注:在一个类中定义了全部是默认参数的构造函数后,不能再定义重载构造函数。

析构函数

析构函数(destructor)也是一个特殊的成员函数,它的作用与构造函数相反,它的名字是类名的前面加一个“~”符号。当对象的生命期结束时,会自动执行析构函数。

具体地说如果出现以下几种情况,程序就会执行析构函数:

1.如果在一个函数中定义了一个对象(它是自动局部对象),当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数。

2.static局部对象在函数调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用static局部对象的析构函数。

3.如果定义了一个全局对象,则在程序的流程离开其作用域时(如main函数结束或调用exit函数) 时,调用该全局对象的析构函数。

4.如果用new运算符动态地建立了一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数。

析构函
4000
数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用。

注意:析构函数不返回任何值,没有函数类型,也没有函数参数。因此它不能被重载。一个类可以有多个构造函数,但只能有一个析构函数。

调用构造函数和析构函数的顺序

可以简记为:先构造的后析构,后构造的先析构,它相当于一个栈,先进后出。

下面归纳一下什么时候调用构造函数和析构函数:

1) 在全局范围中定义的对象(即在所有函数之外定义的对象),它的构造函数在文件中的所有函数(包括main函数)执行之前调用。但如果一个程序中有多个文件,而不同的文件中都定义了全局对象,则这些对象的构造函数的执行顺序是不确定的。当main函数执行完毕或调用exit函数时(此时程序终止),调用析构函数。

2) 如果定义的是局部自动对象(例如在函数中定义对象),则在建立对象时调用其构造函数。如果函数被多次调用,则在每次建立对象时都要调用构造函数。在函数调用结束、对象释放时先调用析构函数。

3) 如果在函数中定义静态(static )局部对象,则只在程序第一次调用此函数建立对象时调用构造函数一次,在调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数。

对象数组

如果构造函数有多个参数,在定义对象数组时应当怎样实现初始化呢?回答是: 在花括号中分别写出构造函数并指定实参。

如果构造函数有3个参数,分别代表学号、年龄、成绩。则可以这样定义对象数组:

Student Stud[3]={ //定义对象数组
Student(1001,18,87),  //调用第1个元素的构造函数,为它提供3个实参
Student(1002,19,76),  //调用第2个元素的构造函数,为它提供3个实参
Student(1003,18,72)  //调用第3个元素的构造函数,为它提供3个实参
};


指向对象的指针

对象空间的起始地址就是对象的指针。

定义指向类对象的指针变量的一般形式为:

类名 *对象指针名;


指向对象成员的指针

1) 指向对象数据成员的指针

定义指向对象数据成员的指针变量的方法和定义指向普通变量的指针变量方法相同。例如:

int *p1; //定义指向整型数据的指针变量

定义指向对象数据成员的指针变量的一般形式为:

数据类型名 *指针变量名;


2) 指向对象成员函数的指针

定义指向对象成员函数的指针变量的方法和定义指向普通函数的指针变量方法有所不同。

编译系统要求在赋值语句中,指针变量的类型必须与赋值号右侧函数的类型相匹配,要求在以下3方面都要匹配:

①函数参数的类型和参数个数;

②函数返回值的类型;

③所属的类。

那么,应该怎样定义指向成员函数的指针变量呢?应该采用下面的形式:

void (Time::*p2)( );  //定义p2为指向Time类中公用成员函数的指针变量


注意:(Time:: p2) 两侧的括号不能省略,因为()的优先级高于*。如果无此括号,就相当于:

void Time::*(p2())  //这是返回值为void型指针的函数


定义指向公用成员函数的指针变量的一般形式为:

数据类型名 (类名::*指针变量名)(参数表列);


使指针变量指向一个公用成员函数的一般形式为

指针变量名=&类名::成员函数名;


在VC++系统中,也可以不写&,以和C语言的用法一致,但建议在写C++程序时不要省略&。

this指针

在每一个成员函数中都包含一个特殊的指针,这个指针的名字是固定的,称为this指针。它是指向本类对象的指针,它的值是当前被调用的成员函数所在的对象的起始地址。

this指针是隐式使用的,它是作为参数被传递给成员函数的。

在调用该成员函数时,实际上是用以下方式调用的:

a.volume(&a);//a是一个对象


将对象a的地址传给形参this指针。然后按this的指向去引用其他成员。

常对象及其成员

既要使数据能在一定范围内共享,又要保证它不被任意修改,这时可以使用const,即把有关的数据定义为常量。

在定义对象时指定对象为常对象。常对象必须要有初值,如:

Time const t1(12,34,46); //t1是常对象

这样,在所有的场合中,对象t1中的所有成员的值都不能被修改。凡希望保证数据成员不被改变的对象,可以声明为常对象。

定义常对象的一般形式为:

类名 const 对象名[(实参表列)];


也可以把const写在最左面:

const 类名 对象名[(实参表列)];


如果一个对象被声明为常对象,则不能调用该对象的非const型的成员函数(除了由系统自动调用的隐式的构造函数和析构函数)。

引用常对象中的数据成员只需将该成员函数声明为const即可。如:

void get_time( ) const ; //将函数声明为const const在最后面


常成员函数可以访问常对象中的数据成员,但仍然不允许修改常对象中数据成员的值。有时在编程时有要求,一定要修改常对象中的某个数据成员的值,ANSI C++考虑到实际编程时的需要,对此作了特殊的处理,对该数据成员声明为mutable,如:

mutable int count;


把count声明为可变的数据成员,这样就可以用声明为const的成员函数来修改它的值。

常对象成员

1) 常数据成员

其作用和用法与一般常变量相似,用关键字const来声明常数据成员。常数据成员的值是不能改变的。

有一点要注意: 只能通过构造函数的参数初始化表对常数据成员进行初始化。

2) 常成员函数

数据成员非const成员函数const成员函数
非const的数据成员可以引用,也可以改变值可以引用,但不可以改变值
const数据成员可以引用,但不可以改变值可以引用,但不可以改变值
const对象的数据成员不允许可以引用,但不可以改变值
那么怎样利用常成员函数呢?

1.如果在一个类中,有些数据成员的值允许改变,另一些数据成员的值不允许改变,则可以将一部分数据成员声明为const,以保证其值不被改变,可以用非const的成员函数引用这些数据成员的值,并修改非const数据成员的值。

2.如果要求所有的数据成员的值都不允许改变,则可以将所有的数据成员声明为const,或将对象声明为const(常对象),然后用const成员函数引用数据成员

3.如果已定义了一个常对象,只能调用其中的const成员函数,而不能调用非const成员函数(不论这些函数是否会修改对象中的数据)。这是为了保证数据的安全。如果需要访问对象中的数据成员,可将常对象中所有成员函数都声明为const成员函数,但应确保在函数中不修改对象中的数据成员。

4.不要误认为常对象中的成员函数都是常成员函数。常对象只保证其数据成员是常数据成员,其值不被修改。如果在常对象中的成员函数未加const声明,编译系统把它作为非const成员函数处理。

5.常成员函数不能调用另一个非const成员函数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c++