C++及数据结构复习笔记(六)(类和对象2)
1.9 关于类和对象的进一步讨论
1.9.1 构造函数
类的数据成员是不能再声明类时初始化的。
如果一个类的所有数据成员是公用的,则可以在定义对象时对数据成员进行初始化,也可以用成员函数来对对象中的数据成员赋初值(如例1.8.1)。
C++使用构造函数来处理对象的初始化,构造函数必须与类同名。
构造函数在类对象进入其作用域时调用,且构造函数没有返回值(不能写成void+构造函数名),也不能被调用。构造函数仅是对当前对象中的数据成员赋值,不是在声明类时直接对数据成员赋值。即程序首先建立对象,然后对对象中的成员赋初值。
类外声明构造函数:
类名::构造函数名(类型1 形参1,类型2 形参2)
例 1.9.1使用构造函数对1.8.4小节右侧的程序进行改造。
#include<iostream>
using namespace std;
class Time
{
public:
Time();//声明构造函数
void set_time();//声明成员函数
void show_time();
private:
int hour;
int minute;
int sec;
};
Time::Time()//定义构造函数
{
hour=0;
minute=0;
sec=0;
}
int main()
{
Time t1,t2;
t1.set_time();
//调用成员函数,向t1的成员输入数据
t1.show_time();
//调用成员函数,输出t1的数据
t2.set_time();
t2.show_time();
return 0;
}
void Time::set_time()
//类外定义函数要指定作用域
{
cin>>hour>>minute>>sec;
}
void Time::show_time()
{
cout<<hour<<”:”<<minute<<”:”<<sec<<endl;
}
1.9.2 带参数的构造函数
带参数的构造函数的作用:对不同的对象赋不同的初值。
构造函数首部的一般格式为:
类中声明: 构造函数名(类型1 形参1,类型2 形参2,…);
类外定义: 类名::构造函数名(类型1 形参1,类型2 形参2,…)
在main中定义对象 类名 对象名(实参1,实参2,…);
例 1.9.2:有两个长方体,长宽高分别为:12x20x25,10x14x20,求它们的体积。编写一个基于对象的程序,在类中使用带参数的构造函数。
#include<iostream>
using namespace std;
class Box
{
public:
Box(int,int,int);//声明带参数的构造函数
int volume();
private:
int height;
int width;
int length;
};
Box::Box(int h,int w,int l)//在类外定义带参数的构造函数
{
height=h;
width=w;
length=l;
}
void Box::volume()//在类外定义成员函数
{
return(height*width*length);
}
int main()
{
Box box1(25,20,12);//建立对象,并初始化长宽高
cout<<”The volume of box1 is “<<box1.volume()<<endl;
Box box2(20,14,10);
cout<<”The volume of box2 is “<<box2.volume()<<endl;
return 0;
}
在一般的程序设计中,构造函数的声明采用参数初始化表更为简练。上例中的定义构造函数部分可以改造如下:
Box::Box(inth,int w,int l):height(h),width(w),length(l) {}
或者在构造函数声明时直接写:
Box(inth,int w,int l):height(h),width(w),length(l) {}
1.9.3 使用默认参数的构造函数
如果使用默认参数,则在建立对象时可以提供多种选择,其作用相当于好几个重载的构造函数。一个类只能有一个默认构造函数。且应在声明构造函数时就指定默认值。在一个类中定义了全部是默认参数的构造函数后,不能再定义重载构造函数。
使用默认参数的构造函数的格式:
在类中
class 类名
{
构造函数名(int x=1,int y=1,intz=1);//声明带默认参数的构造函数
};
类外
类名::构造函数名(int x,int y,int z):数据成员1(x),数据成员2(y),数据成员3(z) {} //类外定义
例 1.9.3将例1.9.2中的构造函数改为含默认参数的构造函数,长宽高均默认为10。
#include<iostream>
using namespace std;
class Box
{
public:
Box(int h=10,int w=10,int l=10);//在声明构造函数时指定默认参数
int volume();
private:
int height;
int width;
int length;
};
Box::Box(int h,int w,int l):height(h),width(w),length(l) {} //在类外定义带参数的构造函数
void Box::volume()//在类外定义成员函数
{
return(height*width*length);
}
int main()
{
Box box1;//没给实参
Box box2(15);//只给定一个实参
Box box3(15,30); //只给定2个实参
Box box4(15,30,20);//给定3个实参
return 0;
}
1.9.4 析构函数
析构函数的作用与构造函数相反,格式为:~类名。当对象的生命周期结束时,会自动执行析构函数。析构函数的作用并不是删除对象,而是在撤销对象所占用的内存之前完成一些清理工作。一个类可以有多个构造函数,但只能有一个析构函数。且析构函数不返回任何值,没有函数类型。一般是在声明类的同时调用析构函数,以指定如何完成清理工作。
调用构造函数和析构函数的顺序可参照图1.4。
图 1.4 构造函数和析构函数的调用顺序
关于它们的调用顺序,可以简单记为:先构造的后析构,后构造的先析构。它相当于一个栈,先进后出。
1.9.5 对象数组
定义:由对象组成的数组,即数组的每个元素都是同类的对象。
Eg:
class Student
{
public:
Student(int n,int a,float s):num(n),age(a),float(s){}//定义构造函数
private:
int num;
int age;
float score;
};
Student stud[50];//定义对象数组stud,有50个元素
Student stud[3]={Student(1001,18,87),Student(1002,19,76),Student(1003,18,72)};
//建立对象数组时,分别调用构造函数,对每个元素进行初始化
1.9.6 对象指针
一、指向对象数据成员的指针和指向对象的指针
对象空间的起始地址就是对象的指针。定义指向对象的指针变量的一般形式为:
类名 *对象指针名;
指向对象数据成员的指针变量的一般形式为:
数据类型名 *指针变量名;
class Time
{
public:
int hour;
int minute;
int sec;
void get_time();
|;
void Time::get_time()
{cout<<hour<<”:”<<minute<<”:”<<sec<<endl;}
int main()
{
Time *p;//定义p为指向Time类对象的指针变量
int *p1;//定义指向对象整形数据的指针变量
Time t1;
p=&t1;//把t1的起始地址赋值给p
p1=&t1.hour;//将对象t1的数据成员hour的地址赋值给p1
(*p).hour;//p指向对象中的hour成员
(*p).get_time();//调用p所指向的对象中的get_time函数
}
二、指向对象成员函数的指针
指向普通函数的指针 | 指向对象成员函数的指针 |
数据类型名 (*指针变量名)(参数列表) |
数据类型名 (类名::*指针变量名)(参数列表) 指针变量名=&类名::成员函数名 |
void (*p)(int,int,int); //p是指向void型函数的指针变量 p=fun;//将fun的入口地址赋给p (*p)();//调用fun函数 |
void (Time::*p)(); //定义p为指向Time类*用成员函数的指针变量 p=&Time::get_time;//注意不加括号 |
1.9.7 this指针
每个成员函数中都包含一个特殊的指针,这个指针的名字是固定的,称为this。它是指向本类成员对象的指针,它的值是当前被调用的成员函数所在对象的起始地址。
1.9.8 常对象
要使数据在一定范围内共享且不能被任意修改,可以使用const,即把有关的数据定义为常量。
常对象必须要有初值。且如果一个对象被声明为常对象,则不能调用该对象的非const型成员函数。
格式:
类名 const 对象名(实参列表)
Time const t1(12,34,45);//t1是常对象
t1.get_time();//错误,企图调用常对象的非const型成员函数
如果想引用常对象的成员函数,需将成员函数声明为const。常成员函数可以访问常对象中的数据成员,但不允许修改它们。
void get_time() const;//将成员函数声明为const
常数据成员 |
const int hour;//声明hour为常数据成员 Time::Time (int h):hour(h){}//使用参数初始化表对常数据成员hour进行初始化 | 常数据成员值无法修改,其初始化操作仅能采用构造函数的初始化表方式。 |
常函数成员 | void get_time() const; | 常成员函数可以引用本类中的数据成员,但不能修改他们。 |
1.9.9 指向对象的常指针
将指针变量声明为const型,这样指针值始终保持为初值,不能改变。其作用是防止误操作,增加安全性。
定义指向对象的常指针的一般形式:
类名 * const 指针变量名
Time t1,t2;
Time * const pt1;
pt1=&t1;//pt1指向对象t1,此后无法改变指向
pt1=&t2;//错误,pt无法改变指向
1.9.10 指向常对象的指针变量
指向常变量的指针变量 | 指向常对象的指针变量 |
const char *p; const 类型名 *指针变量名 |
Time * const p;//指向对象的常指针变量 const Time *p;//指向常对象的指针变量 |
如果一个变量被声明为常变量,则只能用指向常变量的指针指向它。 | 如果一个对象被声明为常对象,则只能用指向常对象的指针指向它。如果定义一个指向常对象的指针变量并使其指向一个非const的对象,则其指向的对象无法通过指针来改变。 |
const型数据的小结 | |
形式 | 含义 |
Time const t1; | t1是常对象,声明常对象t1 |
void Time::fun() const; | 声明常成员函数fun |
Time * const p; | p是指向Time类对象的常指针 |
const Time *p; | p是指向Time类常对象的指针变量 |
Time &t1=t | t1是Time类对象的引用 |
1.9.11 对象的动态建立和释放
可以用new运算符动态地建立对象,用delete运算符撤销对象。用new运算符动态地分配内存后,将返回一个指向新对象的指针。
Box *p;//定义一个指向Box类对象的指针变量
p= new Box;//在p中存放新建对象的起始地址
Box *p=new Box(12,15,18);//对新建立的对象进行初始化
用new建立的对象一般通过指针访问。只要检测返回值是否为0,就可以判断分配内存是否成功。
1.9.12 对象的赋值与复制
对象的赋值 | 对象的复制 |
一个对象的值可以赋给同类的另一个对象,通过”=”实现。且对象的赋值仅对其中的数据成员赋值,不对成员函数赋值。 | 用一个已有的对象快速复制出多个完全相同的对象。 |
对象名1=对象名2; Student stud1,stud2;//定义2个同类对象 stud2=stud1; |
类名 对象2(对象1); Box box2(box1); 或者: 类名 对象2=对象1; Box box2=box1; |
1.9.13 静态成员
实现对象中的一个或几个数据成员为所有对象所共有,实现数据共享。
一、静态数据成员
class Box
{
public:
int volume();
private:
static int height;//把height定义为静态数据成员
int width;
int length;
};
每个对象都可以引用静态数据成员,静态数据成员的值对所有对象都是一样的,静态数据成员在内存中只占一份空间。静态数据成员只能在类外进行初始化:
数据类型 类名::静态数据成员名=初值;
int Box::height=10;
Box(int h,int w,int l):height(h),width(w),length(l){}
//错误,height是静态成员,不能用参数初始化表进行初始化。
二、静态成员函数
上例中,把volume函数声明一行改为:
static int volume();
就将其变为了静态成员函数。
调用时需要加限定符::
类名::函数名;
静态成员函数的作用是为了能够处理静态数据成员,且静态成员函数无this指针,这决定了静态成员函数不能访问本类中的非静态成员。静态成员函数只能引用本类中的静态数据成员。例 1.9.4 静态成员函数的简单应用
#include<iostream>
using namespace std;
class Student
{
public:
Student(int n,int a,float s):num(n),age(a),score(s){}//参数初始化表定义构造函数
void total();
static float average();//声明静态成员函数
private:
int num;
int age;
float score;
static float sum;//声明静态数据成员
static int count;
};
void Student::total()//定义非静态成员函数
{
sum=sum+score;
count++;
}
float Student::average()//定义静态成员函数
{
return(sum/count);
}
float Student::sum=0;//对静态数据成员进行初始化
int Student::count=0;
int main()
{
Student stud[3]={Student(1001,18,70),Student(1002,19,78),Student(1005,20,98)};
//定义对象数组并初始化
int n;
cout<<”please input the number of students:”;
cin>>n;//n表示需要求前面n的学生的平均成绩
for (int i=0;i<n;i++)
stud[i].total();
cout<<”the average score of “<<n<<” students is “<<Student::average()<<endl;
//调用静态成员函数
return 0;
}
1.9.14 友元
友元可以访问与其有友好关系的类中的私有成员,其包括友元函数和友元类。
一、友元函数
在本类以外的其他地方定义了一个函数,这个函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数,在本类类体中用friend对其进行声明,此函数就称为本类的友元函数。友元函数可以访问这个类的私有成员。
将普通函数声明为友元函数 (不在任何类中的普通函数) |
友元成员函数 (另一个类中的成员函数) |
Eg:
| 下例中还包含了类的提前引用声明 |
| |
由于display函数不是Time类成员函数,不能默认引用Time类数据成员,因此必须指定要访问的对象。 |
|
二、友元类
把B类声明为A类的朋友,这是B类就是A类的友元类,B类中的所有函数都是A类的友元函数,可以访问A类的所有成员。
class A
{
friendB;
};
class B
{};