1.构造函数:
(1).构造函数的名字与类名同名;它不具有任何类型,不返回任何值;构造函数允许重载;构造函数自动调用(在类对象进入其作用域时就会调用构造函数),不需要用户去调用。
(2).带参数的构造函数:构造函数首部的一般格式为:构造函数名(类型1 形参1,类型2 形参2,...)
定义对象的一般格式为:类名 对象名(实参1,实参2,....)
(3).用参数初始化表对数据成员初始化
参数初始化表又称“冒号语法”或“内构的函数”
该种方法的一般形式为:Box :: Box(int h,int w,int len):height(h),width(w),length(len){}
(4).构造函数的重载
概念:构造函数具有相同的名字,而参数的个数或参数的类型不相同。
(5).默认构造函数:在调用构造函数时不必给出实参的构造函数,也称缺省构造函数,无参的构造函数属于默认构造函数。
一个类只能有一个默认构造函数,否则,系统就无法辨别应执行哪个构造函数。
示例:
#include
using namespace std;
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;
};
Box::Box()//不带参数的构造函数
{
height = 10;
width = 10;
length = 10;
}
int Box :: volume()//类外定义函数,要在函数名之前添加该函数属于的类类型,并运用作用域限定符
{
return (height * width * length);
}
int main()
{
Box box1;
cout << "the volume of box1 is" << box1.volume() << endl;
Box box2(15,30,29);//指定三个实参
cout << "the volume of box2 is" << box2.volume() << endl;
return 0;
}
class Box
{
public:
Box(int h = 10,int w = 10,int len = 10);//或Box(int = 10,int = 10,int = 10);
int volume();//在类内声明函数,在类外定义函数
private:
int height;
int width;
int length;
};
Box::Box(int h,int w,int len)//不带参数的构造函数
{
height = h;
width = w;
length = len;
}
int Box :: volume()//类外定义函数,要在函数名之前添加该函数属于的类类型,并运用作用域限定符
{
return (height * width * length);
}
int main()
{
Box box1;
cout << "the volume of box1 is" << box1.volume() << endl;
Box box2(15);//指定1个实参
cout << "the volume of box2 is" << box2.volume() << endl;
Box box3(15,30);//指定2个实参
cout << "the volume of box2 is" << box3.volume() << endl;
Box box4(15,30,29);//指定3个实参
cout << "the volume of box2 is" << box4.volume() << endl;
return 0;
}
使用默认参数的构造函数,应该在声明构造函数时指定默认值,而不能只在定义构造函数时指定默认值。
注意:一个类中定义了全部是默认参数的构造函数后,不能在定义重载构造函数。
2.析构函数
(1).析构函数也是一个特殊的函数,其作用与构造函数相反,其名字是类名的前面加一个“~”符号,该符号称为“位取反运算符”。
(2).在以下几种情况下,程序会执行析构函数:
如果在一个函数中定义了一个对象(为自动局部对象),当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数。
static局部对象在函数调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用static局部对象的析构函数。
如果定义了一个全局对象,则在程序的流程离开其作用域时,调用该全局对象的析构函数。
如果用new运算符动态的建立一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数。
(3).析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用。
(4).析构函数不返回任何值,没有数据类型,也没有函数参数,因此它不能被重载,一个类只能有一个析构函数。
(5).先构造的后析构,后构造的先析构。(有点像栈的思想。。。。。。)
3.对象数组:
对于一个类,要声明多个对象,就可以用到对象数组。如student类已经声明,定义stu数组,有50个元素就可以写成:student stu[50];如果构造函数只有一个参数,则可以在等号后面的花括号内提供实参,如student stu[3] = {60,70,78};但如果构造函数不是只有一个参数,那么就不能在定义数组时直接提供所有实参,以免弄混。
4.对象指针:
指针可以用来指向一般的变量,也可以用来指向对象。
(1.)对象的指针:对象空间的起始地址就是对象的指针,定义指向类对象的指针变量的一般形式为:
类名 *对象指针名
(2).指向对象成员的指针:存放对象初始地址的指针变量就是指向对象的指针变量;
存放对象成员地址的指针变量就是指向对象成员的指针变量。
(3)指向对象成员数据的指针:
一般的定义方法为:数据类型名 * 指针变量名,int *p1;。如:假设Time类的数据成员hour为公用的整形数据,则可以在类外同过指向对象数据成员的指针变量访问该成员,p1 = &t1.hour;cout << "*p1" << endl;
(4)指向对象成员函数的指针:
void(Time :: *p2)();//定义p2为指向Time类中公用成员函数的指针变量。
注意:在这里,前面的括号不能够省略掉,因为括号的优先级高于*,如果没有这个括号,就相当于
void Time :: *(p2()); p2 = &Time::get_time;
定义指向公用成员函数的指针变量的一般形式为:
数据类型名(类名::*指针变量名)(参数列表);
是指针变量指向一个公用成员函数的一般形式为:
指针变量名 = &类名::成员函数名;
(5).this指针:前面已经介绍过this指针,在这里补充一点,return ((*this).height + (*this).width + (*this).length);
在这里,*this两侧的括号不能省略,因为成员运算符‘.’的级别高于指针运算符‘*’。
5.共用数据的保护:
(1).常对象:在定义对象时指定对象为常对象,如time const t1(12,34,46);则对象t1中的所有数据成员的值都不能被修改。
定义常对象的一般形式为:类名 const 对象名【(实参表列)】;
或: const 类名 对象名【(实参表列)】。
如果一个对象被声明为常对象,则不能调用该对象的非const 型的成员函数(除系统自动调用的构造函数和析构函数)。为了能够引用常对象中的数据成员,要将成员函数声明为:const ,如:void get_time()const;这就使其成为了一个常成员函数,可以访问常对象中的数据成员,但不允许修改常对象中的数据成员的值。
如果一定要修改常对象中的某个数据成员的值,则可以对该数据成员声明为mutable,如mutable int count。
(2).常对象成员:
常数据成员:如果在类体中声明了常数据成员,只能通过构造函数的参数初始化表对常数据成员进行初始化,不能采用一般赋值的方法,因为常数据成员是不能被赋值的。
常成员函数:如果将成员函数声明为常成员函数,则只能引用本类中的数据成员,而不能修改他们。一般形式为:void get_time()const;const是函数类型的一部分,在声明函数和定义函数时都要有const关键字,在调用时不用加。
(3).指向对象的常指针:
将指向对象的指针变量声明为const型并将之初始化,这样指针值始终保持为其初值,不能改变,也就是说该指针的指向永远不变,但是可以改变其所指对象中的数据成员的值。
定义指向对象的常指针的一般形式为:类名 * const 指针变量名 = 对象地址;
(4).指向常对象的指针变量:
定义指向常变量的指针变量的一般形式为:const 类型名 *指针变量名;
如果一个变量已被声明为常变量,只能用指向常变量的指针变量去指向它;指向常变量的指针变量除了可以指向常变量外,还可以指向未被声明为const的变量,但是不能通过该指针变量改变该变量的值。
注意:定义了指向常变量的指针变量p,再使他指向c,并不意味着把c也声明为常变量,而只是在通过指针变量引用c时,c具有常变量的特征。
形参有const和无const的情况:
指向常对象的指针变量:
class Time
{
private:
int hour;
int minu;
public:
Time(int h = 0,int m = 0):hour(h),minu(m){}
void fun(const Time *p)const
{
// p->hour = 18;
cout << p->hour << endl << p->minu << endl;
}
};
int main()
{
const Time t1(20,30);
t1.fun(&t1);
return 0;
}
//无论t1是不是const型对象,只要形参是const型,则传参后不能对数据进行修改,只能读取。
//如果一个对象被声明为常对象,则不能调用该对象的非const成员函数
//如果对象是const型,则形参一定要是const型,否则,编译不过,因为指向非const对象的指针是不能指向const对象的
//总之:当希望在调用函数时,对象的值不被修改,就应当把形参定义为指向常对象的指针变量,同时用对象的地址作实参。
//如果要求该对象不仅在调用过程中不被改变,而且在运行过程中也不被改变,则应将对象定义为const型。
class Time
{
public:
Time(int h = 0,int m = 0):hour(h),minu(m){}
int hour;
int minu;
};
void fun(Time t)
{
t.hour = 18;
}
int main()
{
Time t1(20,30);
fun(t1);
cout << t1.hour << endl;
return 0;
}
对比上下两段代码有什么不同。
class Time
{
public:
Time(int h = 0,int m = 0):hour(h),minu(m){}
int hour;
int minu;
};
void fun(Time &t)
{
t.hour = 18;
}
int main()
{
Time t1(20,30);
fun(t1);
cout << t1.hour << endl;
return 0;
}
//上一段代码中,fun的形参是一个对象名,在函数中修改形参的值,因为在调用时建立了一个新的对象t,它是实参t1的拷贝,所以t的修改,对t1没有影响。
//在下面的代码中,形参是变量的引用名,在调用函数的过程中,把实参的地址传给形参,使引用名也指向实参变量,所以t中成员的改变会影响t1,
//如果不希望t1的值被修改,可以把引用t声明为const,即:void fun(const Time t);
用new运算符动态地创建对象,用delete运算符撤销对象。
用new运算符动态地分配内存后,将返回一个指向新对象的指针的值,即所分配的内存空间的起始地址。所以需要定义一个指向本类的对象的指针变量来存放该地址。
如:Time *pt; pt = new Time;...........delete pt;
7.对象的赋值:运用赋值运算符,‘=’,实现同属于一个类的两个对象的赋值,一般形式为:对象名1 = 对象名2;
对象的赋值,只对其中的数据成员赋值,而不对成员函数赋值。
注意:类的数据成员中不能包括动态分配的数据,否则在赋值时可能出现严重后果。
8.对象的复制:Time t1(t2);,用已有的对象t2去克隆出一个新的对象t1。
对象复制的一般形式为:类名 对象2(对象1);用对象1复制出对象2
9.静态数据成员:
以关键字static开头,如:
class Time
{
private:
static int hour;//把hour定义为静态的数据成员
int minu;
int s;
};
静态的数据成员在内存中只占一份空间。
注意:如果只声明了类而未定义对象,则类的一般数据成员是不占空间的,只有在定义对象时,才为对象的数据成员分配空间。但是,静态数据成员不属于某一个对象,所以在定义对象的时候开辟的空间也不包括该部分,它是在所有对象之外单独开辟空间。也就是说,只要声明了类,即使不定义对象,也为静态数据成员分配空间。
静态数据成员分配的空间,到程序结束时才释放空间。
静态数据成员可以初始化,但只能在类体外进行初始化,(系统默认初始为0)如:
int Time :: hour = 10;其一般形式为:数据类型 类名 :: 静态数据成员 = 初值;
注意:不能用参数初始化表对静态数据类型进行初始化
静态数据成员既可以通过对象名来引用,也可以通过类名来引用。
10.静态成员函数:
在类中声明函数的static就成了静态成员函数,如:static int volume();
静态成员函数是类的一部分,而不是对象的一部分,它与任何对象都无关,因此静态成员函数没有this指针,无法对一个对象中的非静态成员进行访问,但是可以直接引用本类的静态数据成员。如果要在类外调用公用的静态成员函数,要用类名和域运算符‘::’,如:Box :: volume();
(当然也允许通过对象名调用静态成员函数)。如果要引用本类的非静态成员,应该加对象名和成员运算符‘.’,如:cout << a.hour << endl;
作用:静态成员函数不是为了对象之间的沟通,而是为了能够处理静态数据成员。
示例:计算前n个学生的平均成绩
#include
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 coun;
};
void Student::total()
{
sum += score;
coun++;
}
float Student :: average()
{
return (sum/coun);
}
float Student::sum = 0;//对公用静态数据成员进行初始化
int Student :: coun = 0;
int main()
{
Student stud[3] =
{
Student(1001,18,70),
Student(1002,19,78),
Student(1003,20,98)
};
int n;
cin >> n;
for(int i = 0; i < n; i++)
{
stud[i].total();
}
cout << "the average score of " << n << " is" << Student :: average() << endl;
return 0;
}
//附:如果想在average函数中引用stud[1]的非静态数据成员score,有两种解决办法:
//一种是将对象数组stud定义为全局数组,增加作用域
//另外一种是将对象作为average函数的形参,在调用该函数时将一个实参对象传给该函数。
//如:
float Student :: average(Student stu)
{
cout << stu.score << endl;
return (sum / coun);
}
//average函数的形参可以写成以下几种形式:
float Student :: average(Student &stu)//函数参数为对象的引用
float Student :: average(const Student stu)
float Student :: average(const Student &stu)
11.友元:
友元包括友元函数和友元类
(1).将普通函数声明为友元函数
一个非成员函数,不属于任何类,如果想让该函数引用类中的私有成员,要在类的定义中声明该函数是类的友元函数,如示例:
class Time
{
private:
int hour;
int minu;
int ss;
public:
Time (int h,int m,int s):hour(h),minu(m),ss(s){}
friend void display(Time &);//声明该函数是这个类的友元函数
//注意:display函数并不是成员函数,如果没有声明,将不能引用
};
void display(Time &t)
{
//在引用的过程中加上对象名,因为该函数不是成员函数,没有this指针
cout << t.hour << ":" << t.minu << ":" << t.ss << endl;
}
int main()
{
Time t1(10,13,56);
display(t1);//调用普通函数
return 0;
}
(2).友元成员函数
friend函数不仅可以是一般的函数,还可以是另一个类中的成员函数。
示例:
class Date;
//这里用到了类的提前引用声明,Date类还没有定义,
//但是在该类定义之前需要先用到这个类,
//如果是先写Date类的声明,则同样要在这里先声明Time类
//但是,要知道只有在真正定义了类体之后才能够定义对象
class Time
{
private:
int hour;
int minu;
int ss;
public:
Time (int h,int m,int s):hour(h),minu(m),ss(s){}
friend void display(Date &);//声明该函数是这个类的友元函数
//注意:display函数是(Date)类的成员函数,形参是Date类对象的使用
};
class Date
{
private:
int month;
int day;
int year;
public:
Date(int m,int d,int y):month(m),day(d),year(y){}
friend void Time::display(Date &);
//在这里,要声明Time类中的display是这个类的友元函数
};
void Time :: display(Date &d)
{
//同样,display函数是Time类的成员函数,所以在使用Date类的数据成员时,要注明对象。
cout << d.month << ":" <
(3).友元类
如果B类是A类的友元类,则友元类B中的所有函数都是A类的友元函数,可以访问A类的所有成员
声明友元类的一般形式为:friend 类名
注意:
友元的关系是单向的而不是双向的。
友元的关系是不能够传递的。
12 类模板
步骤:
(1)在类声明前面加入一行,格式为:templaty
(2)用类模版定义对象时用以下形式:
类模板名<实际类型名>对象名
类模版名<实际类型名>对象名(实参列表)
(3)如果在类模板外定义成员函数,应写成类模板形式:
templaty
函数类型 类模版名 <虚拟类型参数>::成员函数名(函数形参表列){.....}
说明:
(1)类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如
templaty
class someclass
{
}
在定义对象时分别代入实际的类型名,如:
someclass t1;
(2)一个类模板可以作为基类,派生出派生类模板