9类和对象进一步
9.1构造函数
如果一个类中所有的成员都是公用的,则可以在定义对象时对数据成员进行初始化。 但是,如果数据成员是私有的,或者类中有private或protected的成员,
就不能用这种方法初始化。
class Time
{public : //声明为公用成员
hour;
minute;
sec;
};
Time t1={14,56,30}; //将t1初始化为14:56:30
构造函数的作用
构造函数的名字必须与类名同名,构造函数没有返回值,因此也不需要在定义构造函数时声明类型, 以便编译系统能识别它并把它作为构造函数处理。
在类内定义构造函数,也可以只在类内对构造函数进行声明而在类外定义构造函数。
构造函数不需用户调用,也不能被用户调用。
带参数的构造函数
构造函数首部的一般格式为构造函数名(类型 1 形参1,类型2 形参2, …)
定义对象的一般格式为 类名 对象名(实参1,实参2, …);
#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 len) //在类外定义带参数的构造函数
{height=h;
width=w;
length=len;
}
int Box::volume( ) //定义计算体积的函数
{
return (height*width*length);
}
int main( )
{
Box box1(12,25,30); //建立对象box1,并指定box1长、宽、高的值
cout<<″The volume of box1 is ″<<box1.volume( )<<endl;
Box box2(15,30,21); //建立对象box2,并指定box2长、宽、高的值
cout<<″The volume of box2 is ″<<box2.volume( )<<endl;
return 0;
}
用参数初始化表对数据成员初始化
Box::Box(int h,int w,int len):height(h), width(w), length(len){ }
构造函数的重载
在一个类中可以定义多个构造函数,以便对类对象提供不同的初始化的方法。
这些构造函数具有相同的名字,而参数的个数或参数的类型不相同。
Box::Box( ) ;
Box::Box(int h); //有1个参数的构造函数
Box::Box(int h,int w); //有两个参数的构造函数
使用默认参数的构造函数
构造函数中参数的值如果用户不指定实参值,编译系统就使形参取默认值。
它的作用相当于好几个重载的构造函数。
它的好处是: 即使在调用构造函数时没有提供实参值,不仅不会出错,而且还确保按照默认的参数值对对象进行初始化。
9.2析构函数
它的名字是类名的前面加一个“~”符号。
析构函数是与构造函数作用相反的函数。当对象的生命期结束时,会自动执行析构函数。
析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用。
一个类可以有多个构造函数,但只能有一个析构函数。
类的设计者应当在声明类的同时定义析构函数,以指定如何完成“清理”的工作。
9.3调用构造函数与析构函数顺序
最先被调用的构造函数, 其对应的(同一对象中的)析构函数最后被调用,而最
后被调用的构造函数,其对应的析构函数最先被调用。
(1) 在全局范围中定义的对象 。它的构造函数在文件中的所有函数(包括main函数)执行之前调用。
(2) 如果定义的是局部自动对象(例如在函数中定义对象),则在建立对象
时调用其构造函数。
(3) 如果在函数中定义静态(static )局部对象, 只在main函数结束或调用exit函数结束程序时,才调用析构函数。
9.4对象数组
如果构造函数有多个参数,则不能用在定义数组时直接提供所有实参的方法。
如果构造函数有多个参数, 用花括号中分别写出构造函数并指定实参。
Student Stud[3]={ //定义对象数组
Student(1001,18,87), //调用第1个元素的构造函数,为它提供3个实参
Student(1002,19,76), //调用第2个元素的构造函数,为它提供3个实参
Student(1003,18,72) //调用第3个元素的构造函数,为它提供3个实参
};
9.5对象指针
指向对象的指针
类名 *对象指针名;
Time *pt; //定义pt为指向Time类对象的指针变量
Time t1; //定义t1为Time类对象
pt=&t1; //将t1的起始地址赋给pt
(*pt).hour
pt->hour
(*pt).get_time ( )
pt->get_time ( )
指向对象成员的指针
-
与定义指向普通变量的指针变量方法相同。
int *p1; p1=&t1.hour; cout<<*p1<<endl;
-
定义指向对象成员函数的指针变量的方法和定义指向普通函数的指针变量方法有所不同。
void (Time::*p2)( );
//定义p2
为指向Time类中公用成员函数的指针变量
数据类型名 (类名::*指针变量名)(参数表列);
使指针变量指向一个公用成员函数的一般形式为
指针变量名=&类名::成员函数名;p2=&Time::get_time;
this 指针
在每一个成员函数中都包含一个特殊的指针,这个指针的名字是固定的,称为this。 它的值是当前被调用的成员函数所在的对象的起始地址。
9.6共用数据的保护
常对象
定义常对象的一般形式为Time const t1(12,34,46); //t1是常对象
也可以把const写在最左面:const Time t1(12,34,46); //t1是常对象
如果一个对象被声明为常对象, 则不能调用该对象的非const型的成员函数 。
引用常对象中的数据成员很简单,只需将该成员函数声明为const即可。void get_time( ) const ; //将函数声明为const
mutable int count;
把count声明为可变的数据成员,这样就可以用声明为const的成员函数来修改它的值。
常对象成员
- 常数据成员其作用和用法与一般常变量相似,
const int hour; //声明hour为常数据成员
常对象的数据成员都是常数据成员,因此常对象的构造函数只能用参数初始化表对常数据成员进行初始化。
- 如果将成员函数声明为常成员函数,则只能引用本类中的数据成员,而不能修改它们。
void get_time( ) const ; //注意const的位置在函数名和括号之后
指向对象的常指针
Time t1(10,12,15),t2; //定义对象
Time * const ptr1; //const位置在指针变量名前面,规定ptr1的值是常值
指向常对象的指针变量
指向常变量的指针变量 const char *ptr;
- 如果一个对象已被声明为常对象,只能用指向常对象的指针变量指向它, 而不能用一般的(指向非const型对象的)指针变量去指向它。
- 如果定义了一个指向常对象的指针变量,并使它指向一个非const的对象,则其指向的对象是不能通过指针来改变的。
希望在调用函数时对象的值不被修改,就应当把形参定义为指向常对象的指针变量, 同时用对象的地址作实参。
如果定义了一个指向常对象的指针变量,是不能通过它改变所指向的对象的值的,但是指针变量本身的值是可以改变的。
对象的常引用
#include <iostream>
using namespace std;
class Time
{public:
Time(int,int,int);
int hour;
int minute;
int sec;
};
Time::Time(int h,int m,int s) //定义构造函数
{hour=h;
minute=m;
sec=s;
}
void fun(Time &t)
{t.hour=18;}
int main( )
{Time t1(10,13,56);
fun(t1);
cout<<t1.hour<<endl;
return 0;
}
9.7对象的动态建立与释放
可以用new运算符动态建立对象,用delete运算符撤销对象。
Box *pt; //定义一个指向Box类对象的指针变量pt
pt=new Box; //在pt中存放了新建对象的起始地址
或
Box *pt=new Box(12,15,18);
9.8对象的赋值与复制
对象的赋值
对象赋值的一般形式为对象名1 = 对象名2;
类的数据成员中不能包括动态分配的数据,否则在赋值时可能出现严重后果。
对象的复制
其一般形式为类名 对象2(对象1); Box box2(box1);
或者类名 对象名1 = 对象名2; Box box2=box1;
对象的赋值是对一个已经存在的对象赋值,因此必须先定义被赋值的对象,才能进行赋值。
而对象的复制则是从无到有地建立一个新对象,并使它与一个已有的对象完全相同(包括对象的结构和成员的值)。
9.9静态成员
静态数据成员
静态数据成员是一种特殊的数据成员。它以关键字static开头 。
每个对象都可以引用这个静态数据成员。静态数据成员的值对所有对象都是一样的。
只要在类中定义了静态数据成员,即使不定义对象,也为静态数据成员分配空间,它可以被引用。
静态数据成员是在程序编译时被分配空间的,到程序结束时才释放空间。
静态数据成员可以初始化,但只能在类体外进行初始化
数据类型类名::静态数据成员名=初值;
int Box::height=10;
静态数据成员并不是属于对象的,而是属于类的,但类的对象可以引用它。
静态成员函数成员
static int volume( );
静态成员函数是类的一部分,而不是对象的一部分 。
如果要在类外调用公用的静态成员函数,要用类名和域运算符“::”。
Box::volume( );
静态成员函数的作用不是为了对象之间的沟通,而是为了能处理静态数据成员。静态成员函数没有this指针。静态成员函数不能访问本类中的非静态成员。
如果一定要引用本类的非静态成员,应该加对象名和成员运算符“.”。
cout<<a.width<<endl;
9.10友元
在一个类中可以有公有的(public )成员和私有的(private )成员。友元可以访问与其有好友关系的类中的私有成员 。
友元包括友元函数和友元类。
友元函数
如果在本类以外的其他地方定义了一个函数,在类体中用friend对其进行声明,此函数就称为本类的友元函数。 友元函数可以访问这个类中的私有成员。
- 将普通函数声明为友元函数
- 友元成员函数friend函数不仅可以是一般函数(非成员函数),而且可以是另一个类中的成员函数
- 一个函数 可以被多个类声明为“朋友”,这样就可以引用多个类中的私有数据 。
友元类
在A类的定义体中用以下语句声明B类为其友元类: friend B;
(1) 友元的关系是单向的而不是双向的。
(2) 友元的关系不能传递。
9.11类模板
有两个或多个类,其功能是相同的,仅仅是数据类型不同,可以声明一个通用的类模板,它可以有一个或多个虚拟的类型参数。
template <class numtype> //声明一个模板,虚拟类型名为numtype,无分号
class Compare //类模板名为Compare
{public :
Compare(numtype a,numtype b)
{x=a;y=b;}
numtype max( )
{return (x>y)?x:y;}
numtype min( )
{return (x<y)?x:y;}
private :
numtype x,y;
};
(1) 声明类模板时要增加一行template <class 类型参数名>
(2) 原有的类型名int换成虚拟类型参数名numtype
。
由于类模板包含类型参数,因此又称为参数化的类 。
在类模板名之后在尖括号内指定实际的类型名,在进行编译时,编译系统就用int取代类模板中的类型参数numtype
,这样就把类模板具体化了,
Compare <int> cmp(4,7);
如果改为在类模板外定义,不能用一般定义类成员函数的形式,应当写成类模板的形式:
template <class numtype>
numtype Compare<numtype>::max( )
{{return (x>y)?x:y;}
模板的类型参数可以有一个或多个,每个类型前面都必须加class,如
template <class T1,class T2>
class someclass
{…};
10运算符重载
重载,就是重新赋予新的含义。
10.1概念
在 C++中不能在程序中直接用运算符“+”对复数进行相加运算。用户必须自己设法实现复数相加。
运算符重载的方法是定义一个重载运算符的函数,在需要执行被重载的运算符时,系统就自动调用该函数,以实现相应的运算。 使用运算符重载能使用户程序易于编写、 阅读和维护。
重载运算符的函数一般格式如下:
函数类型 operator 运算符名称 (形参表列)
{ 对运算符的重载处理 }
Complex operator+ (Complex& c1,Complex& c2);
10.2重载方法
重载运算符所实现的功能完全可以用函数实现, 使用运算符重载能使用户程序易于编写、 阅读和维护。
Complex Complex∷ operator + (Complex &c2)
{return Complex(real+c2.real, imag+c2.imag);}
10.3重载规则
(1) C++不允许用户定义新的运算符,只能对已有的 C++运算符进行重载。
(2) C++允许重载绝大部分的运算符 。不能重载的有5个
. (成员访问运算符)
.* (成员指针访问运算符)∷ (域运算符)
sizeof(长度运算符)
?: (条件运算符)
(3) 重载不能改变运算符运算对象(即操作数)的个数。
(4) 重载不能改变运算符的优先级别。
(5) 重载不能改变运算符的结合性。
(6) 用于类对象的运算符一般必须重载,但有两个例外,运算符“=”和“&”不必用户重载。
(7) 应当使重载运算符的功能类似于该运算符作用于标准类型数据时所实现的功能。
(8) 运算符重载函数可以是类的成员函数,也可以是类的友元函数,还可以是既非类的成员函数也不是友元函数的普通函数。
10.4作为类成员函数和友元函数
运算符函数是用 this 指针隐式地访问类对象的成员 。
friend Complex operator + (Complex &c1,Complex &c2);//重载函数作为友元函数
只有在极少的情况下才使用既不是类的成员函数也不是友元函数的普通函数。
将运算符重载函数作为成员函数, 必须要求运算表达式第一个参数(即运算符左侧的操作数)是一个类对象 ,而且与运算符函数的类型相同。
如果运算符左侧的操作数属于C++标准类型(如int)或是一个其他类的对象,则运算符重载函数不能作为成员函数,只能作为非成员函数。 如果函数需要访问类的私有成员,则必须声明为友元函数。
将双目运算符重载为友元函数时,在函数的形参表列中必须有两个参数,不能省略,形参的顺序任意,不要求第一个参数必须为类对象。
friend Complex operator+(int &i,Complex &c); //第一个参数可以不是
类对象
在类外定义友元函数:
Complex operator+(int &i, Complex &c)
{return Complex(i+c.real,c.imag);}
数学上的交换律在此不适用。 如果希望适用交换律,则应再重载一次运算符“+”。
有的运算符(如赋值运算符、 下标运算符、 函数调用运算符)必须定义为类的成员函数。
10.5重载双目运算符
双目运算符有两个操作数,通常在运算符的左右两侧。在重载双目运算符时,在函数中应该有两个参数。
先搭框架,逐步扩充,由简到繁,最后完善。 边编程,边调试,边扩充。
riend bool operator>(String &string1,String &string2);//类内声明
//类外重载
bool operator>(String &string1,String &string2)
{if(strcmp(string1.p,string2.p)>0)
return true;
else return false;
}
10.6重载单目运算符
单目运算符只有一个操作数,如!a,-b,&c,*p,还有最常用的++i和–i等。
运算符重载函数只有一个参数,如果运算符重载函数作为成员函数,则还可省略此参数。
Time operator++( ); //类内声明运算符重载函数
Time Time∷ operator++( ) //类外定义运算符重载函数,
{if(++sec>=60)
{sec-=60; //满 60 秒进 1 分钟
++minute;}
return *this; //返回当前对象值
}
在自增(自减)运算符重载函数中,增加一个 int 型形参,就是后置自增(自减)运算符函数。
Time operator++( );//声明前置自增运算符“++”重载函数
Time operator++(int);//声明后置自增运算符“++”重载函数
10.7重载流插入和流提取运算符
重载流插入运算符“<<”
用户定义的类型的数据,是不能直接用“<<”和“>>”来输出和输入的。 如果想用它们输出和输入自己声明的类型的数据,必须对它们重载。
对“<<”和“>>”重载的函数形式如下:
istream & operator >> (istream &,自定义类 &);
ostream & operator << (ostream &,自定义类 &);
只能将重载“>>”和“<<”的函数作为友元函数或普通的函数,而不能将它们定义为成员函数。
ostream& operator << (ostream& output,Complex& c) //定义运算符“<<”重载函数
{output<<″ (″ <<c.real<<″ +″ <<c.imag<<″ i)″ <<endl;
return output;
}
注意区分什么情况下的“<<”是标准类型数据的流插入符,什么情况下的“<<”是重载的流插入符。
cout<<c3<<5<<endl;
有下划线的是调用重载的流插入符,后面两个“<<”不是重载的流插入符,因为它的右侧不是 Complex 类对象而是标准类型的数据。
重载流提取运算符“>>”
friend istream& operator >> (istream&,Complex&); //声明重载运算符“>>”
istream& operator >> (istream& input,Complex& c) //定义重载运算符“>>”
{cout<<″ input real part and imaginary part of complex number:″ ;
input>>c.real>>c.imag;
return input;
}
10.8不同类型数据间的转换
C++编译系统自动完成的,用户不需干预,这种转换称为隐式类型转换。
标准类型数据间的转换
C++还提供显式类型转换,程序人员在程序中指定将一种指定的数据转换成另一指定的类型,其形式为
类型名(数据)
int(89.5)//将 89.5 转换为整型数 89。
对于用户自己声明的类型, 需要定义专门的函数来处理。
转换构造函数
Complex( ); //默认构造函数。
Complex(double r,double i); //用于初始化的构造函数。
Complex (Complex &c);//用于复制对象的复制构造函数,形参为引用。
Complex(double r) {real=r;imag=0;}//转换构造函数只有一个形参
其作用是将 double 型的参数 r 转换成 Complex 类的对象,将 r 作为复数的实部,虚部为 0。 在类体中,可以有转换构造函数,也可以没有转换构造函数。类名(指定类型的数据)
不仅可以将一个标准类型数据转换成类对象,也可以将另一个类的对象转换成转换构造函数所在的类对象。
Teacher(Student& s){num=s.num;strcpy(name,s.name);sex=s.sex;}
类型转换函数
用转换构造函数可以将一个指定类型的数据转换为类的对象。不能反过来将一个类的对象转换为一个其他类型的数据 。
类型转换函数的作用是将一个类的对象转换成另一类型的数据。
类型转换函数的一般形式为
operator 类型名( )
{实现转换的语句}
类型转换函数只能作为成员函数,因为转换的主体是本类的对象。 不能作为友元函数或普通函数。 也称为类型转换运算符重载函数 。
一般情况下将双目运算符函数重载为友元函数。 单目运算符则多重载为成员函数。
来源:https://blog.csdn.net/qq_28691955/article/details/100030877