类与对象(二)
时间:2010-11-16 来源:osullishuai80
(一)构造函数
(1)当定义一个类对象时,编译程序需要为对象分配存储空间,进行必要的初始化,这部分工作随着类的不同而不同.在C++中,由构造函数来完成这些工作.即构造函数用于为对象分配地址空间,完成对类的初始化.
(2)构造函数可以由用户提供,也可以由系统自动生成.
(3)构造函数的特点:
1.构造函数名必须与类名相同,否则将被当作一般的成员函数来处理.
2.定义对象时被系统自动调用
3.可以有任意类型的参数(参数也可以是类),但不能有返回值.即不能说明其返回类型的,即使是void型也不行.当然,构造函数也可以不带参数.
4.构造函数是共有的,但其它时间无法被调用.
5.每一个类都必须有一个构造函数.如果用户没有给类定义构造函数,则编译系统自动的生成一个缺省的构造函数.形如: 类名::类名(){}
6.构造函数不能像其它成员函数那样被显式的调用,它是在定义对象的同时调用的.
例程1:
#include <iostream.h>
#include <math.h>
class complex
{
private:
double real;
double imag;
public:
complex() //构造函数,且无参数
{
cout<<"In Constructor!"<<endl;
}
double abscomplex()
{ double t;
t=real*real+imag*imag;
return sqrt(t);
}
};
void main()
{
complex A; //定义类的对象A时调用构造函数complex
cout<<" abs of complex A="<<A.abscomplex()<<endl;
}
执行结果:
In Constructor!
abs of complex A=1.30899e+062
Attention!!!
由于用户已经定义了类的构造函数,因此当用户定义一对象A时必须调用该构造函数.但构造函数无参数,因此写成上述形式.
例程2:
#include <iostream.h>
#include <string.h>
class myclass1
{
public:
char name[10];
int no;
myclass1(char *s,int n)
{
cout<<"in myclass1"<<endl;
strcpy(name,s);
no=n;
}
}m("eer",54);
class myclass2
{
public:
char name[10];
int no;
}; void main()
{
myclass1 a("chen",25);
cout<<"after a"<<endl;
cout<<a.name<<' '<<a.no<<endl;
myclass2 b={"chang", 20};
cout<<b.name<<' '<<b.no<<endl;
}
执行结果:
in myclass1
in myclass1
after a
chen 25
chang 20
Attention!!!
1.用户已经定义了类的构造函数,因此当用户定义一对象A时必须调用该构造函数.而构造函数需要两个参数,因此调用构造函数时必须传递两个参数.
2.在C++中,每调用一次构造函数,都会执行一次构造函数.因此,系统会打印出两次"in myclass1".一次是对象m产生,另一次由对象a产生.
3.构造函数由于其特殊性,它只有在定义对象的同时调用,其它时刻不能再调用.比如:
myclass1 a;
a("chen",25);
这种构造函数的调用是错误的.
例程3:
#include <iostream.h>
#include <math.h>
class complex
{
private:
double real;
double imag;
public:
complex(double r=0.0,double i=0.0); //有缺省值的构造函数
double abscomplex();
}; complex::complex(double r,double i)
{
real=r;
imag=i;
} double complex::abscomplex()
{
double t;
t=real*real+imag*imag;
return sqrt(t);
} void main()
{
complex s1; //不传递参数,全部用缺省值
complex s2(1); //只传递一个参数
complex s3(2,4); //传递两个参数
cout<<"mode of s1 is:"<<s1.abscomplex()<<endl;
cout<<"mode of s2 is:"<<s2.abscomplex()<<endl;
cout<<"mode of s3 is:"<<s3.abscomplex()<<endl;
}
Attention!!!
对于带参数的构造函数,在定义对象时必须给构造函数传递参数,否则构造函数将不被执行.在实际应用中,有些构造函数的参数值通常是不变的,只有在特殊情况下才改变它的值,这时可以将其定义为带缺省参数的构造函数.
(二)重载构造函数
与一般的成员函数一样,C++允许重载构造函数,以适应不同的场合.这些构造函数之间以它们所带参数的个数或类型的不同加以区分.
例程:
//构造函数重载
#include <iostream.h>
#include <stdlib.h>
class timer
{
int seconds;
public:
timer() //无参构造函数
{
seconds=0;
}
timer(char *t) //含有一个数字串参数的构造函数
{
seconds=atoi(t);
}
timer(int t) //含一个整型参数的构造函数
{
seconds=t;
}
timer(int min,int sec) //含两个整型参数的构造函数
{
seconds=min*60+sec;
}
int gettime()
{
return seconds;
}
}; void main()
{
timer a;
cout<<"seconds1="<<a.gettime()<<endl;
timer b(10);
cout<<"seconds2="<<b.gettime()<<endl;
timer c("20");
cout<<"seconds3="<<c.gettime()<<endl;
timer d(1,10);
cout<<"seconds4="<<d.gettime()<<endl;
}
执行结果:
0
10
20
70
Attention!!!
该例程中有4个构造重载函数,它们以所带参数的个数或类型的不同加以区分.但编写程序时注意别引起二义性.比如:
1.若将构造函数 timer(int min,int sec) 改成:
timer(int min = 0,int sec = 0)
该构造函数有两个缺省参数.这样,定义对象 timer a 时,本来应该会执行第1个构造函数timer(),但现在第4个构造函数带有缺省值,因此对象a就不直到应该执行哪个构造函数,因此编译无法通过.
2.若将构造函数 timer(int min,int sec) 改成:
timer(int min,int sec = 0)
该构造函数有一个缺省参数.这样,定义对象 timer b 时,本来应该会执行第3个构造函数timer(),但现在第4个构造函数最右边的参数是缺省值,因此对象b就不知道应该执行哪个构造函数,因此编译无法通过.
3.注意,无论怎么修改第4个构造函数,都不能将其定义为:
timer(int min = 0,int sec)
(三)析构函数
析构函数也是一种特殊的成员函数,它与构造函数是动作相反的操作,通常用于执行一些清理任务,如释放分配给对象的内存空间.析构函数的特点:
1.析构函数与构造函数名字相同,但它前面必须加一个波浪号(~).
2.析构函数没有参数,也没有返回值,而且不能重载,因此一个类中只能有一个析构函数.
3.当撤销对象时,编译系统会自动的调用析构函数.
4.每个类必须有一个析构函数.若没有显式的为一个类定义析构函数,编译系统会自动的生成一个缺省的析构函数.如: ~类名(){}
5.析构函数也属于某一个类,它可以由用户提供,也可以由系统自动生成.
6.类对象构造和析构的执行顺序:先构造的后析构
例程:
//析构函数
#include <iostream.h>
#include <math.h>
class complex
{
private:
double real;
double imag;
public:
complex(double r=0.0,double i=0.0)
{
cout<<"construction…"<<endl;
real=r; imag=i;
}
~complex()
{
cout<<"destruction…"<<endl;
}
double abscomplex()
{
double t;
t=real*real+imag*imag;
return sqrt(t);
}
};
void main()
{
complex A(1.1,2.2);
cout<<"abs of complex A="<<A.abscomplex()<<endl;
}
执行结果:
construction…
abs of complex A=2.45967
destruction…
(四)拷贝构造函数
拷贝构造函数是一种特殊的构造函数.它是根据已存在的对象来创建一个新的对象.典型情况是将参数代表的对象逐域拷贝到新创建的对象中.在C++中,用户可以根据自己的需要定义拷贝构造函数,系统也可以为类产生一个缺省的拷贝构造函数.
1.自定义拷贝构造函数格式
classname(const classname &ob) //ob是用于初始化另一个对象的对象的引用
{
//拷贝构造函数的函数体
}
2.若没有编写自己定义的拷贝构造函数,C++会自动的将一个已存在的对象复制到新对象,这种按成员逐一复制的过程是由缺省的拷贝构造函数自动完成的.
3.与一般的构造函数一样,拷贝构造函数没有返回值.
例程1:系统自动添加拷贝构造函数
#include <iostream.h>
#include <stdlib.h>
class point
{
int x,y;
public:
point(int a,int b) //构造函数
{
x=a;
y=b;
}
void print()
{
cout<<x<<' '<<y<<endl;
}
}; void main()
{
point p1(30,40); //定义对象p1
point p2(p1); //显式调用拷贝构造函数,通过对象p1创建对象p2
p1.print();
p2.print();
}
执行结果:
30 40
30 40
例程2:自己定义的拷贝构造函数
#include <iostream.h>
#include <stdlib.h>
class point
{
int x,y;
public:
point(int a,int b) //构造函数
{
x=a;
y=b;
}
point(const point &p) //拷贝构造函数
{
y=p.x;
x=p.y;
} void print()
{
cout<<x<<' '<<y<<endl;
}
}; void main()
{
point p1(30,40); //定义对象p1
point p2(p1); //显式调用拷贝构造函数,通过对象p1创建对象p2
p1.print();
p2.print();
}
执行结果:
30 40
40 30
例程3:若类中有指针类型,按成员复制时可能会产生错误.该例程中,拷贝构造函数由编译器产生.
#include <iostream.h>
#include <string.h>
class string_data
{
public: char *str;
public:
string_data(char *s)
{
cout<<"构造函数"<<' '<<s<<endl;
str=new char[strlen(s)+1];
if(str!=NULL)
strcpy(str,s);
}
~string_data()
{
cout<<"析构函数"<<' '<<str<<endl;
delete str;
}
}; void main()
{
string_data x("fadfas"); //定义对象x,并执行构造函数
string_data y = x; //定义对象y,并执行拷贝构造函数,或写成 y(x);
cout<<x.str<<endl;
cout<<y.str<<endl;
cout<<x.str<<endl;
}
Attention!!!
(1)在C++中,若程序中用户已定义拷贝构造函数,则程序会执行自定义的拷贝构造函数.若程序中没有拷贝构造函数,则编译器自动补齐一个拷贝构造函数,但该函数是简单的直接赋值,在类成员有指针时容易出错.
(2)当程序执行语句 string_data x("fadfas") 后,对象x内的成员str所指向的空间内存放了字符串"fadfas".当程序执行语句 string_data y = x; 后,由于程序中没有拷贝构造函数,因此编译器会自动产生一个拷贝构造函数,该函数是简单的直接赋值,即将对象x内成员的值逐一的赋给对象y.因此,当该语句执行结束后,对象y内的成员str和对象x内的成员str指向了同一段地址空间,且该空间内存放了字符串"fadfas".
(3)当所有程序执行结束后,系统会执行析构函数,由于对象y比对象x后创建,因此系统会对对象y先析构,这样系统会delete掉对象y内的成员str指向的地址空间.当系统对对象x析构时,由于对象x的成员str所指向的空间已经delete了,因此会发生段错误.
(4)因此,当对象的成员中含有指针时,用户需要自己编写拷贝构造函数.因此,该例程可以加上下面代码即可:
string_data(const string_data &p) //自定义拷贝构造函数
{
str=new char[strlen(p.str)+1];
strcpy(str,p.str);
}
当程序执行语句 string_data y = x; 时,系统会自动执行该拷贝构造函数.对象p是对象x的别名,两者都"指向"同一个对象.在该函数内,别名p又为成员str申请了一段地址空间,并将字符串"fadfas"复制到该新申请的地址空间内.此时对象y的成员str指向了新申请的地址空间.此时,对象x的成员str指向了原申请的地址空间,因此,当程序执行结束后,先析构对象y,再析构对象x,两者互不影响. (5)对象之间赋值分析 1.第一条语句是定义一对象x,此时系统会调用构造函数,对对象x进行初始化. 2.第二条语句是定义一对象y,并将对象x所有成员的值赋给对象y,此时系统会调用拷贝构造函数.注意,第二条语句会产生两个动作,一是定义对象y,二是对对象y进行赋值,且这种赋值过程是通过"拷贝构造函数"实现的.具体如何赋值,由拷贝构造函数体决定.
(1)当定义一个类对象时,编译程序需要为对象分配存储空间,进行必要的初始化,这部分工作随着类的不同而不同.在C++中,由构造函数来完成这些工作.即构造函数用于为对象分配地址空间,完成对类的初始化.
(2)构造函数可以由用户提供,也可以由系统自动生成.
(3)构造函数的特点:
1.构造函数名必须与类名相同,否则将被当作一般的成员函数来处理.
2.定义对象时被系统自动调用
3.可以有任意类型的参数(参数也可以是类),但不能有返回值.即不能说明其返回类型的,即使是void型也不行.当然,构造函数也可以不带参数.
4.构造函数是共有的,但其它时间无法被调用.
5.每一个类都必须有一个构造函数.如果用户没有给类定义构造函数,则编译系统自动的生成一个缺省的构造函数.形如: 类名::类名(){}
6.构造函数不能像其它成员函数那样被显式的调用,它是在定义对象的同时调用的.
例程1:
#include <iostream.h>
#include <math.h>
class complex
{
private:
double real;
double imag;
public:
complex() //构造函数,且无参数
{
cout<<"In Constructor!"<<endl;
}
double abscomplex()
{ double t;
t=real*real+imag*imag;
return sqrt(t);
}
};
void main()
{
complex A; //定义类的对象A时调用构造函数complex
cout<<" abs of complex A="<<A.abscomplex()<<endl;
}
执行结果:
In Constructor!
abs of complex A=1.30899e+062
Attention!!!
由于用户已经定义了类的构造函数,因此当用户定义一对象A时必须调用该构造函数.但构造函数无参数,因此写成上述形式.
例程2:
#include <iostream.h>
#include <string.h>
class myclass1
{
public:
char name[10];
int no;
myclass1(char *s,int n)
{
cout<<"in myclass1"<<endl;
strcpy(name,s);
no=n;
}
}m("eer",54);
class myclass2
{
public:
char name[10];
int no;
}; void main()
{
myclass1 a("chen",25);
cout<<"after a"<<endl;
cout<<a.name<<' '<<a.no<<endl;
myclass2 b={"chang", 20};
cout<<b.name<<' '<<b.no<<endl;
}
执行结果:
in myclass1
in myclass1
after a
chen 25
chang 20
Attention!!!
1.用户已经定义了类的构造函数,因此当用户定义一对象A时必须调用该构造函数.而构造函数需要两个参数,因此调用构造函数时必须传递两个参数.
2.在C++中,每调用一次构造函数,都会执行一次构造函数.因此,系统会打印出两次"in myclass1".一次是对象m产生,另一次由对象a产生.
3.构造函数由于其特殊性,它只有在定义对象的同时调用,其它时刻不能再调用.比如:
myclass1 a;
a("chen",25);
这种构造函数的调用是错误的.
例程3:
#include <iostream.h>
#include <math.h>
class complex
{
private:
double real;
double imag;
public:
complex(double r=0.0,double i=0.0); //有缺省值的构造函数
double abscomplex();
}; complex::complex(double r,double i)
{
real=r;
imag=i;
} double complex::abscomplex()
{
double t;
t=real*real+imag*imag;
return sqrt(t);
} void main()
{
complex s1; //不传递参数,全部用缺省值
complex s2(1); //只传递一个参数
complex s3(2,4); //传递两个参数
cout<<"mode of s1 is:"<<s1.abscomplex()<<endl;
cout<<"mode of s2 is:"<<s2.abscomplex()<<endl;
cout<<"mode of s3 is:"<<s3.abscomplex()<<endl;
}
Attention!!!
对于带参数的构造函数,在定义对象时必须给构造函数传递参数,否则构造函数将不被执行.在实际应用中,有些构造函数的参数值通常是不变的,只有在特殊情况下才改变它的值,这时可以将其定义为带缺省参数的构造函数.
(二)重载构造函数
与一般的成员函数一样,C++允许重载构造函数,以适应不同的场合.这些构造函数之间以它们所带参数的个数或类型的不同加以区分.
例程:
//构造函数重载
#include <iostream.h>
#include <stdlib.h>
class timer
{
int seconds;
public:
timer() //无参构造函数
{
seconds=0;
}
timer(char *t) //含有一个数字串参数的构造函数
{
seconds=atoi(t);
}
timer(int t) //含一个整型参数的构造函数
{
seconds=t;
}
timer(int min,int sec) //含两个整型参数的构造函数
{
seconds=min*60+sec;
}
int gettime()
{
return seconds;
}
}; void main()
{
timer a;
cout<<"seconds1="<<a.gettime()<<endl;
timer b(10);
cout<<"seconds2="<<b.gettime()<<endl;
timer c("20");
cout<<"seconds3="<<c.gettime()<<endl;
timer d(1,10);
cout<<"seconds4="<<d.gettime()<<endl;
}
执行结果:
0
10
20
70
Attention!!!
该例程中有4个构造重载函数,它们以所带参数的个数或类型的不同加以区分.但编写程序时注意别引起二义性.比如:
1.若将构造函数 timer(int min,int sec) 改成:
timer(int min = 0,int sec = 0)
该构造函数有两个缺省参数.这样,定义对象 timer a 时,本来应该会执行第1个构造函数timer(),但现在第4个构造函数带有缺省值,因此对象a就不直到应该执行哪个构造函数,因此编译无法通过.
2.若将构造函数 timer(int min,int sec) 改成:
timer(int min,int sec = 0)
该构造函数有一个缺省参数.这样,定义对象 timer b 时,本来应该会执行第3个构造函数timer(),但现在第4个构造函数最右边的参数是缺省值,因此对象b就不知道应该执行哪个构造函数,因此编译无法通过.
3.注意,无论怎么修改第4个构造函数,都不能将其定义为:
timer(int min = 0,int sec)
(三)析构函数
析构函数也是一种特殊的成员函数,它与构造函数是动作相反的操作,通常用于执行一些清理任务,如释放分配给对象的内存空间.析构函数的特点:
1.析构函数与构造函数名字相同,但它前面必须加一个波浪号(~).
2.析构函数没有参数,也没有返回值,而且不能重载,因此一个类中只能有一个析构函数.
3.当撤销对象时,编译系统会自动的调用析构函数.
4.每个类必须有一个析构函数.若没有显式的为一个类定义析构函数,编译系统会自动的生成一个缺省的析构函数.如: ~类名(){}
5.析构函数也属于某一个类,它可以由用户提供,也可以由系统自动生成.
6.类对象构造和析构的执行顺序:先构造的后析构
例程:
//析构函数
#include <iostream.h>
#include <math.h>
class complex
{
private:
double real;
double imag;
public:
complex(double r=0.0,double i=0.0)
{
cout<<"construction…"<<endl;
real=r; imag=i;
}
~complex()
{
cout<<"destruction…"<<endl;
}
double abscomplex()
{
double t;
t=real*real+imag*imag;
return sqrt(t);
}
};
void main()
{
complex A(1.1,2.2);
cout<<"abs of complex A="<<A.abscomplex()<<endl;
}
执行结果:
construction…
abs of complex A=2.45967
destruction…
(四)拷贝构造函数
拷贝构造函数是一种特殊的构造函数.它是根据已存在的对象来创建一个新的对象.典型情况是将参数代表的对象逐域拷贝到新创建的对象中.在C++中,用户可以根据自己的需要定义拷贝构造函数,系统也可以为类产生一个缺省的拷贝构造函数.
1.自定义拷贝构造函数格式
classname(const classname &ob) //ob是用于初始化另一个对象的对象的引用
{
//拷贝构造函数的函数体
}
2.若没有编写自己定义的拷贝构造函数,C++会自动的将一个已存在的对象复制到新对象,这种按成员逐一复制的过程是由缺省的拷贝构造函数自动完成的.
3.与一般的构造函数一样,拷贝构造函数没有返回值.
例程1:系统自动添加拷贝构造函数
#include <iostream.h>
#include <stdlib.h>
class point
{
int x,y;
public:
point(int a,int b) //构造函数
{
x=a;
y=b;
}
void print()
{
cout<<x<<' '<<y<<endl;
}
}; void main()
{
point p1(30,40); //定义对象p1
point p2(p1); //显式调用拷贝构造函数,通过对象p1创建对象p2
p1.print();
p2.print();
}
执行结果:
30 40
30 40
例程2:自己定义的拷贝构造函数
#include <iostream.h>
#include <stdlib.h>
class point
{
int x,y;
public:
point(int a,int b) //构造函数
{
x=a;
y=b;
}
point(const point &p) //拷贝构造函数
{
y=p.x;
x=p.y;
} void print()
{
cout<<x<<' '<<y<<endl;
}
}; void main()
{
point p1(30,40); //定义对象p1
point p2(p1); //显式调用拷贝构造函数,通过对象p1创建对象p2
p1.print();
p2.print();
}
执行结果:
30 40
40 30
例程3:若类中有指针类型,按成员复制时可能会产生错误.该例程中,拷贝构造函数由编译器产生.
#include <iostream.h>
#include <string.h>
class string_data
{
public: char *str;
public:
string_data(char *s)
{
cout<<"构造函数"<<' '<<s<<endl;
str=new char[strlen(s)+1];
if(str!=NULL)
strcpy(str,s);
}
~string_data()
{
cout<<"析构函数"<<' '<<str<<endl;
delete str;
}
}; void main()
{
string_data x("fadfas"); //定义对象x,并执行构造函数
string_data y = x; //定义对象y,并执行拷贝构造函数,或写成 y(x);
cout<<x.str<<endl;
cout<<y.str<<endl;
cout<<x.str<<endl;
}
Attention!!!
(1)在C++中,若程序中用户已定义拷贝构造函数,则程序会执行自定义的拷贝构造函数.若程序中没有拷贝构造函数,则编译器自动补齐一个拷贝构造函数,但该函数是简单的直接赋值,在类成员有指针时容易出错.
(2)当程序执行语句 string_data x("fadfas") 后,对象x内的成员str所指向的空间内存放了字符串"fadfas".当程序执行语句 string_data y = x; 后,由于程序中没有拷贝构造函数,因此编译器会自动产生一个拷贝构造函数,该函数是简单的直接赋值,即将对象x内成员的值逐一的赋给对象y.因此,当该语句执行结束后,对象y内的成员str和对象x内的成员str指向了同一段地址空间,且该空间内存放了字符串"fadfas".
(3)当所有程序执行结束后,系统会执行析构函数,由于对象y比对象x后创建,因此系统会对对象y先析构,这样系统会delete掉对象y内的成员str指向的地址空间.当系统对对象x析构时,由于对象x的成员str所指向的空间已经delete了,因此会发生段错误.
(4)因此,当对象的成员中含有指针时,用户需要自己编写拷贝构造函数.因此,该例程可以加上下面代码即可:
string_data(const string_data &p) //自定义拷贝构造函数
{
str=new char[strlen(p.str)+1];
strcpy(str,p.str);
}
当程序执行语句 string_data y = x; 时,系统会自动执行该拷贝构造函数.对象p是对象x的别名,两者都"指向"同一个对象.在该函数内,别名p又为成员str申请了一段地址空间,并将字符串"fadfas"复制到该新申请的地址空间内.此时对象y的成员str指向了新申请的地址空间.此时,对象x的成员str指向了原申请的地址空间,因此,当程序执行结束后,先析构对象y,再析构对象x,两者互不影响. (5)对象之间赋值分析 1.第一条语句是定义一对象x,此时系统会调用构造函数,对对象x进行初始化. 2.第二条语句是定义一对象y,并将对象x所有成员的值赋给对象y,此时系统会调用拷贝构造函数.注意,第二条语句会产生两个动作,一是定义对象y,二是对对象y进行赋值,且这种赋值过程是通过"拷贝构造函数"实现的.具体如何赋值,由拷贝构造函数体决定.
相关阅读 更多 +