本文是谭浩强老师c++程序设计第九章的内容小结。
1.构造函数与类对象的初始化
(1)对类错误的初始化
对于一般数据,通常可以在声明的同时初始化,但是类不同,因为类不是一个实体,而是一个抽象类型,并不占用内存,因此没地方存放数据,所以不能在声明中初始化,即下述形式是错误的:
class Time
{
hour = 0;
minute = 0;
sec = 0;
}; //是错误的
(2)用构造函数初始化数据成员
构造函数是一种特殊的成员函数,与其他成员函数不同,它无需用户调用,在建立对象时会自动执行调用过程,这一点与在类中直接赋值的不同点是,只有在函数调用时才会分配存储空间,对当前对象赋值,不调用时不占内存。
构造函数的名字必须与类名相同,不能随意命名,因此它的形式如下例:
class Time
{public
Time() //定义构造函数
{
hour = 0;
minute = 0;
sec = 0;
}
void set_time();
void show_time();
private:
int hour;
int minute;
int sec;
};
对于构造函数注意以下几点:
a. 构造函数可以定义在类内或类外,类内形式如上例,类外要写上类名,并用作用域限定符连接,如:
Time::Time()
b. 构造函数的调用发生在建立对象时,调用时才为数据分配存储单元;
c. 构造函数没有返回值,也没有类型,因此定义时不声明类型,它的作用只是初始化对象;
d. 构造函数不需要被用户调用,也不能被调用;如果用户没定义构造函数,系统会自己生成,不过此时构造函数为空,也不起初始化作用;
e. 可以用一个类初始化另一个类,如:
Time t1;
Time t2 = t1;
f. 在构造函数中不仅可以包含数据成员赋值语句,还可以包含其他语句,如cout;
(3)带参数的构造函数
定义形式:
构造函数名(类型1,形参1,类型2,形参2,...)
由于构造函数不能被用户调用,因此不能写成fun(a,b)的形式调用,它只能在建立对象时由系统调用,因此建立对象时应该写成:
类名 对象名(实参1,实参2...)
(4)用参数初始化表初始化数据成员
该方法不在函数体内对数据成员初始化,而是在函数部首实现,形式如下:
类名::构造函数名(参数列表):初始化成员表
{
构造函数体
}
如下所示:
Box::Box(int h, int w, int len):height(h),width(w),length(len){}
注意:若成员是数组,不能在初始化参数表中赋值,要在构造函数体中赋值,如:
class Student
{
public:
Student(int n, char s, nam[]):num[n],sex(s)
{strcpy(name,mam);} //函数体
private:
int num;
char sex;
char name[20];
};
//可以如下定义对象
Student stud1(10101, 'm', "wang_li");
(5)构造函数的重载
在一个类中可以定义多个构造函数,这些构造函数可以有相同的名字,而他们的参数个数或类型不相同,这就是重载,这种情况下,系统会按照调用时参数的形式来确定调用的是哪个构造函数。
(6)使用默认参数的构造函数
即在声明构造函数时直接指定默认参数的方法,这是在定义时可以不指定默认参数,如下例:
class Box
{
public:
Box(int h=10;int w=10;int len=10);//声明构造函数
private:
int height;
int width;
int length;
};
//定义构造函数
Box::Box(int h, int w, int len)//不指定默认参数
{
height = h;
width = w;
length = len;
}
注意:类中定义了全部是默认参数的构造函数后,不能再定义重载函数,否则出现歧义,系统不知道调用谁。
2. 析构函数
析构函数是一个特殊的成员函数,它是与构造函数作用相反的函数,它不是为了删除对象,二是在撤销对象占用的内存之前完成一些清理工作。如释放资源,同时还可以用来执行“用户希望在最后一次使用对象之后所执行的任何操作”,如输出相关信息等。一般情况下,类的设计者应该在声明类的同时定义析构函数,以指定如何完成清理工作,若用户没有定义析构函数,系统也会自动生成,但是它只是徒有析构函数的名称和形式,其实什么操作都不执行,要想让析构函数完成任何工作,都需要在定义的析构函数中指定。
函数形式:
在类名前加一个取反符号~
如:
Student(int n, string nam, char s)
{
num=n;
name = nam;
sex =s;
}//定义有参数的构造函数
~Student() //定义析构函数
3. 调用构造函数和析构函数的顺序
一般情况下,调用析构函数的次序和调用构造函数的次序相反,即最先被调用的构造函数,其对应的(同一对象中的)析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用。也就是,先构造的后析构,后构造的先析构。它相当于一个栈,先进后出。
4. 对象数组
对象数组即数组中每个元素都是同类的对象。
如:Student stud[50]; //假设已经声明了Student类,定义stud数组,有50个元素。
在建立数组时,同样要调用构造函数,若有50个元素,就需要调用50次构造函数。如果构造函数只有一个参数,在定义数组时可以直接在等号后边的花括号内提供实参,如:
Student stud[3]={60,70,78};
如果构造函数有多个参数,则不能用在定义数组时直接提供所有实参的方法,因为系统会分不清给谁,此时应该用如下形式:
Student stud[3]={
Student(1001,18,87);
Student(1002,19,76);
Student(1003,18,72);
};
5. 对象指针
(1)指向对象的指针
对象的指针是对象空间的起始地址,其定义的一般形式为:
类名 *对象指针名;
如:
Time *pt; //定义pt为指向Time类对象的指针变量
Time t1; // 定义t1为Time类对象
pt = &t1; // 将t1的起始地址赋给pt
可以通过对象指针来访问对象和对象的成员,形式如下:
访问数据:(*pt).time或pt->time;
访问函数:(*pt).get_time()或pt->get_time();
(2)指向对象成员的指针
a. 指向对象数据成员的指针:
数据类型名 *指针变量名;
如:
int *p1;
p1 = &t1.hour;
b. 指向对象成员函数的指针:
指向普通函数的指针:类型名 (*指针变量名)(参数列表);如:void (*p) ();
而指向成员函数的指针不同,因为成员函数是类中的成员,指向它的指针也必须指明它所属的类,因此形式如下:
数据类型名 (类名:: *指针变量名)(参数列表);
如:
void(Time:: *p2)();
p2 = &Time::get_time;
(3)this指针
this指针是指向本类对象的指针,它的值是当前被调用的成员函数所在的对象的起始地址。
this指针是隐式使用的,它是作为参数被传递给成员函数的;也就是说这些是编译系统自动实现的,不需要人为加入this指针,也不必将对象的地址传给this指针。
如:return (*height)相当于return((*this).height)
6. 共用数据的保护
(1)常对象
定义对象时加关键字const,可以指定对象为常对象,常对象必须有初值,定义形式如下:
类名 const 对象名[(实参表)];或者:const 类名 对象名[(实参表)];
如:Time const t1(12,34,46); //定义t1是常对象
这样在t1的生命周期中,对象t1所有的数据成员的值都不能被修改。
同时,通过该对象只能调用类中的常成员函数,不能调用普通的函数,若想调用普通函数,必须将该函数变为常函数(如果该函数不改变对象中数据成员的值),将函数变为常函数的形式是:
void get_time() const;
若想修该常对象中某个数据成员的值,需要将数据成员声明为mutable,如:
mutable int count;
也就是把count声明为可变类型。
(2)常对象成员
a. 常数据成员
用const来声明常数据成员:const int hour;
常数据成员只能通过构造函数的参数初始化表对其赋初值,其他情况都不可以。
b. 常成员函数
类型名 函数名(参数表)const;
如:void get_time() const;
如果将成员函数声明为常函数,则只能引用本类的数据成员,而不能修改它们。
常成员函数不能调用另一个非常成员函数
c. 指向对象的常指针
类名 *const 指针变量;
如:Time *const p;
指向对象的常指针的值不能改变,也就是说它所指向的对象不能改变,但是对象的值可以被改变;
d. 指向常对象的指针变量
const 类型名 *指针变量名;
如:const Time *p;
若一个变量已经被声明为常变量,那就只能用指向常变量的指针指向它;而指向常变量的指针既可以指向常变量,也可以指向非常变量,此时不能通过改变指针来改变变量的值,也就是说只有通过指针访问,该变量具有常变量属性,若直接访问该变量,可以改变该变量的值。
e. 对象的常引用
一个变量的引用即是这个变量的别名,常引用即是声明为const的引用。
如: void fun(const Time &t);此时函数不会修改其对应实参的值。
7. 对象的动态建立和释放
与结构体相同,仍是用new和delete来动态建立对象或撤销对象。
详见:https://blog.csdn.net/zl3090/article/details/86619016
8. 对象赋值和复制
(1)对象的赋值:
对象名1 = 对象名2;
注意:二者类型相同,且是对数据成员的赋值;
(2)对象的复制:
类名 对象2(对象1);
用已有的对象1复制出一模一样的对象2。
赋值是对一个已经存在的对象赋值,也就是赋值之前需要先声明;
而复制是从无到有建立一个新的对象。
9. 静态成员
(1)静态数据成员
如果希望各个对象中数据成员的值是一样的,就可以把它定义为静态数据成员,关键字是static,如:
static int height;
静态数据成员可以初始化,但必须在类体外初始化,同时不能用参数初始化列表进行初始化,如:
int Box::height = 10;
静态数据成员在内存中只占一份数据,它的值可以被改变,一旦被改变,对象中所有的该成员的值都会被改变。
(2)静态成员函数
静态成员函数与静态成员数据都是类的一部分,而不是对象的一部分,如果在类外调用,必须加类名和域运算符::。
静态成员函数形式如下:
static int volunm;
其主要用来访问静态数据成员,而不能访问非静态成员。
10. 友元
(1)友元函数
类的友元函数在类体中用friend进行声明,它可以访问其对应函数的私有成员。
友元函数既可以将普通函数声明为友元函数,也可以将类中其他成员函数声明为友元函数。
如:friend void display( Time &);//声明display函数为Time类的友元函数;
friend void Time::display(Date &);//声明Time中的display函数为本类的友元成员函数。
(2)友元类
不仅可以将一个函数声明为类的友元函数,还可以将一个类声明为另一个类的友元类,形式如:
friend 类名;
在A类的定义体中用friend B声明B类为其友元类。
友元关系是单向的,B是A的友元类,说明B可以访问A,但是A不是B的友元类,A不能访问B的私有数据;
同时,友元关系不能传递,B是A的友元类,C是B的友元类,C并不是A的友元类。
11.类模板
类模板解决的是若干个类功能相同而数据类型不同的情况。
声明类模板时,在类之前要加一行:
template <class 类型参数名>
对于类模板的一般形式为:
类模板 <实际类型名> 对象名(参数表);
例子如下: