拷贝构造函数与析构函数
字符串函数
例4.1:类中数据成员是字符串
#include <iostream>
#include <cstring>//字符串函数声明所在的头文件
using namespace std;
class HelloWorld
{
private:
char msg[10];//问候信息
public:
void Show()
{cout<<"Hello,"<<msg<<endl;}
HelloWorld(char s[])//构造函数参数是数组名
{ strcpy(msg,s); }
HelloWorld()//无参构造函数
//使用strcpy函数给msg赋值,比较繁琐
{ strcpy(msg, "jsj16"); }
};
int main()
{
char s[10];
cin>>s;
HelloWorld a(s),b;
//需要带参数构造函数和无参构造函数,或者二合一:带默认值的构造函数
a.Show();
b.Show();
return 0;
}
string类是C++提供的字符串类,其主要功能是对字符串进行操作。
string类定义的变量称为字符串对象,该对象可以直接用字符串常量赋值,也可调用string类中定义的成员函数。
例4.2:连接字符串实例
#include <iostream>
#include <string>//字符串函数声明所在的头文件,devcpp和moodle平台都可以省略
using namespace std;
int main()
{
string m_str1="abc";//用一个字符串常量给一个字符串对象赋值
string m_str2=m_str1;//用一个已经有的对象m_str1给新定义的m_str2对象初始化
string m_str3=m_str1+m_str2;//执行第三行后,m_str3的值应该是“abcabc”。
//使用+运算符连接2个字符串
if(m_str1==m_str2)//使用==运算符比较2个字符串
cout<<"相等"<<endl;
else
cout<<"不等"<<endl;
cout<<m_str3<<endl;
m_str3=m_str1; //用m_str1对象给m_str3对象赋值
cout<<m_str3<<endl;
return 0;
}
例4.3: string类作函数参数实例
#include<iostream>
#include <string>
using namespace std;
void seta(string s)
{ cout<<s<<" of China"<<endl;}
void setb(const string & s)//在传递的"Liuxiang"这样一个字符串常量时,setb函数的形参一定要用const修饰
//如果string t=("Liuxiang"); setb(t);则setb的函数形参可以不用const修饰
{ cout<<s<<" of China"<<endl;}
int main()
{ seta("Wangbo");
setb("Liuxiang");
cout<<"See how they run"<<endl;
return 0;
}
常类型与const修饰符
常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。
C++中,引入const 的目的是为了取代宏这个预编译指令,消除它不能进行语法检查的缺点,同时继承宏替换的优点。C++中定义const常量,具有不可变性。const可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。
题4.1: 访问类中数据成员是字符串对象
#include <iostream>
#include <string>//使用string类,声明所在的头文件
using namespace std;
class HelloWorld
{
private:
string msg;//访问信息
public:
void Show()
{cout<<"Hello,"<<msg<<endl;}
HelloWorld(const string &s)//构造函数参数是对象引用
{ msg=s;//使用=直接赋值
}
HelloWorld()//无参构造函数
{ msg= "LiMing";//使用=直接赋值
}
};
int main()
{
string s;
cin>>s;
HelloWorld a(s),b;
a.Show();
b.Show();
return 0;
}
例4.4:日期类中各种构造函数调用的实例
#include <iostream>
using namespace std;
class Date
{
private:
int year;
int month;
int day;
public:
void Print()//输出
{cout<<"日期是"<<year<<"-"<<month<<"-"<<day<<endl;}
Date(int y,int m,int d)//带普通参数的构造函数
{year=y; month=m; day=d;
cout<<"调用了带普通参数的构造函数"<<endl;
}
Date()//无参构造函数
{year=0; month=0;day=0;
cout<<"调用了无参构造函数"<<endl;
}
};//类的定义结束
int main()
{ Date a1(2012,2,27);//调用带普通参数的构造函数
a1.Print();
Date a2;//调用无参构造函数
a2.Print();
Date a3(a1);
a3.Print();
return 0;
}//调用默认拷贝构造函数
运行结果:
调用了带普通参数的构造函数
日期是2012-2-27
调用了无参构造函数
日期是0-0-0
日期是2012-2-27
拷贝构造函数
用一个对象值创建并初始化另一个对象。
日期类的设计与实现中
Date a1(2012,2,27);//调用带普通参数的构造函数
Date a3(a1);//调用默认拷贝构造函数 此句与Date a3=a1;等价
就是用a1 的值初始化新创建的对象a3
拷贝构造函数的特点:
1.拷贝构造函数名字与类同名,没有返回类型;//不是必须的,通常默认拷贝构造函数即可;
2.拷贝构造函数只有一个形参数,该参数是该类的对象的引用;
如果一个类中没有定义拷贝构造函数,则系统自动生成一个缺省拷贝构造函数,其功能是将已知对象的所有数据成员的值拷贝给对应对象的数据成员。
定义
拷贝构造函数的格式如下:
<类名>::<拷贝构造函数名>(<类名>&<引用名>)
{<函数体>}
其中, <拷贝构造函数名>与该类名相同;
拷贝构造函数的用处
- 拷贝构造函数用于使用已知对象的值创建一个同类的新对象
- 把对象作为实参数进行函数调用时,系统自动调用拷贝构造函数实现把对象值传递给形参对象;
- 函数的返回值为对象时,系统自动调用拷贝构造函数对返回对象值创建一个临时对象,然后再将这个临时对象值赋给接收函数返回值的对象。
- 拷贝构造函数需要定义的情况是数据成员是指针。
学生类拷贝构造函数使用实例
#include <iostream>
#include <cstring>
using namespace std;
class CStuScore
{
public:// 公有类型声明
void Show()
{cout<<strName<<"的平均成绩为:"<<GetAverage()<<endl;}
CStuScore(char *pName,int no,float s0, float s1,float s2)
{strName=pName;//strName=pName;使数据成员指向外部(例如:main函数)地址,带来不可知结果
iStuNO=no;
fScore[0] = s0;
fScore[1] = s1;
fScore[2] = s2;
}
private: // 私有类型声明
float fScore[3]; // 三门课程成绩
char *strName;// 姓名
int iStuNO;// 学号
float GetAverage();//计算平均成绩
};
float CStuScore::GetAverage()//计算均值
{
return (float)((fScore[0] + fScore[1] + fScore[2])/3.0);
}
int main()
{
CStuScore oOne("LiMing",21020501,80,90,65),b(oOne);
oOne.Show();
b.Show();
return 0;
}
//CStuScore b(oOne);
//调用缺省拷贝构造函数,只是指针值的复制(浅拷贝),即存在oOne和b对象的指针成员指向同一个内存空间的问题
分析
1. strName=pName;使数据成员指向外部c数组(例如:main函数)地址,也会被改,无法封装
int main()
{
CStuScore oOne("LiMing",21020501,80,90,65),b(oOne);
oOne.Show();
b.Show();
return 0;
}
int main()
{
char c[10]="LiMing";
CStuScore oOne(c,21020501,80,90,65),b(oOne);
oOne.Show();
b.Show();
strcpy(c,"wangbo"); //修改数组c的内容
oOne.Show();
return 0;
}
2. strName=pName;使数据成员指向外部c数组(例如:main函数)地址,c被释放,strName成为野指针
int main()
{
char *c=new char[10];
strcpy(c,"LiMing");
CStuScore oOne(c,21020501,80,90,65),b(oOne);
oOne.Show();
b.Show();
delete []c; //释放动态数组c
oOne.Show(); //发生内存错误
return 0;
}
析构函数
不是必须的,通常默认析构函数即可(无参,函数体为空)
必须定义析构函数的情况:
1.构造函数打开一个文件,使用完文件时,需要关闭文件。
2.从堆中分配了动态内存区,在对象消失之前必须释放。
特点
- 无返回类型,但是不要加void。
- 无参数,因此不存在析构函数重载,只有1个析构函数。
在对象释放时由系统自动调用。 - 如果程序中未声明,则系统自动产生出一个缺省形式的析构函数。
- 析构函数与构造函数的功能相对应,所以析构函数名是构造函数名前加一个逻辑反运算符“~”
- 析构函数以调用构造函数相反的顺序被调用。
学生类中析构函数定义的实例
该类定义的构造函数在对象之外分配一段堆内存空间,撤销时,由析构函数收回堆内存。通过动态数组申请和释放,解决了指针成员指向外部(main函数中)地址的问题。
#include <iostream>
#include <cstring>
using namespace std;
class CStuScore
{
public:// 公有类型声明
void Show()
{cout<<strName<<"的平均成绩为:"<<GetAverage()<<endl;}
CStuScore(char *pName,int no,float s0, float s1,float s2)
{ strName= new char[12];//动态数组申请
strcpy(strName,pName);
iStuNO=no;
fScore[0] = s0;
fScore[1] = s1;
fScore[2] = s2;
}
~CStuScore()
{delete []strName;//释放动态申请的内存
} //动态数组释放
private: // 私有类型声明
float fScore[3]; // 三门课程成绩
char *strName;// 姓名
int iStuNO;// 学号
float GetAverage();//计算平均成绩
};
float CStuScore::GetAverage()//计算均值
{ return (float)((fScore[0] + fScore[1] + fScore[2])/3.0); }
int main()
{ CStuScore oOne("LiMing",21020501,80,90,65);//,b(oOne);
oOne.Show();
//b.Show();
return 0;
}
拷贝构造函数的深浅拷贝问题
strName=r.strName;
//浅拷贝:对逐个成员的指针值的依次复制
//存在2个或2个以上对象姓名成员指向同一个内存空间的问题(即2个或2个以上对象共用一个姓名),如果释放其中一个对象,将导致另外的对象可能没有地方存姓名。这就发生内存错误。
CStuScore(CStuScore &r){strName= new char[12];
strcpy(strName,r.strName);}
//深拷贝
//重新定义拷贝构造函数。学生类中姓名是一个指针,指向各自由new创建存放姓名字符串的内存空间首地址。
构造函数与析构函数实例
#include <iostream>
using namespace std;
class point
{
public:
point(int xp,int yp)
{ x=xp; y=yp; }
point(point& p);
~point() {cout<<"析构函数被调用"<<endl;}
int getx() {return x;}
int gety() {return y;}
private:
int x,y;
};
point::point(point& p)
{ x=p.x; y=p.y;
cout<<"拷贝构造函数被调用"<<endl;
}
point fun(point q);//函数声明
//不要在返回指向局部变量或者临时对象的引用。
函数执行完毕之后,局部变量和临时对象将消失,
引用将指向不存在的数据。
int main()
{
point M(12,20),P(0,0),S(0,0);//调用3次带参数的构造函数创建对象M、P、S
point N(M);//调用拷贝构造函数创建对象N
P=fun(N);//函数调用完成,调用析构函数2次,释放局部对象R以及形参对象q
S=M;
cout<<"P="<<P.getx()<<","<<P.gety()<<endl;
cout<<"S="<<S.getx()<<","<<S.gety()<<endl;
return 0;
}//程序结束前调用4次析构函数,释放对象N、S、P、M
point fun(point q)//形参是对象,参数传递时调用拷贝构造函数创建对象q
{
cout<<"OK"<<endl;
int x=q.getx()+10;
int y=q.gety()+15;
point R(x,y);//创建对象R,调用带参数的构造函数
return R;
}
运行结果:
拷贝构造函数被调用
拷贝构造函数被调用
OK
析构函数被调用
析构函数被调用
P=22,35
S=12,20
析构函数被调用
析构函数被调用
析构函数被调用
析构函数被调用
注意,析构函数以调用构造函数相反的顺序被调用。