1.构造函数
类的数据成员不能在声明类时初始化,为了解决这个问题,C++提供了构造函数(constructor)来处理对象的初始化。
构造函数特点:
1->函数名和类名相同。
2->无返回值,没有类型。(实际返回当前对象的引用)
3->构造函数不需要用户调用,也不能被用户调用,只会在新的对象被创建时,由编译器自动调用,且在对象的生命周期内仅调用一次,以保证每个数据成员都有一个合适的初始值。
4->有初始化列表(可以不用,只有构造函数有初始化列表)。
5->构造函数可以重载,实参决定调用哪个构造函数。
6->如果没有显示定义,系统会提供一个默认的构造函数。
7->无参构造函数和带有缺省值的构造函数都被认为是缺省构造函数,而且缺省构造函数只能有一个。
8->构造函数不能用const和volatile来修饰。
eg:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Data
{
public: // 类内定义构造函数
Data() //定义构造成员函数,函数名与类名相同
{
year = 0; //利用构造函数对对象中的数据成员赋初值
month = 0;
day = 0;
}
void set_data(); //函数声明
void show_data();
private:
int year;
int month;
int day;
};
void Data::set_data()
{
cin >> year;
cin >> month;
cin >> day;
}
void Data::show_data()
{
cout << year << ":" << month << ":" << day << endl;
}
int main()
{
Data d1;
d1.set_data(); //对d1的数据成员赋值
d1.show_data(); //显示d1数据成员值
Data d2;
d2.show_data(); //显示d2数据成员值
return 0;
}
构造函数在类内的定义,也可以在类外定义。
在类外定义:
class Data
{
public: // 类内定义构造函数
Data();
void set_data(); //函数声明
void show_data();
private:
int year;
int month;
int day;
};
Data :: Data()
{
year = 0;
month = 0;
day = 0;
}
构造函数小结:
1->构造函数不能用const修饰的原因:构造函数的作用是初始化对象,如果用const修饰构造函数,之后不能对该对象重新赋值。
2->有两个类,A和B,B类中含有A类的对象,A中显式定义了构造函数,B中无构造函数,此时,系统会在B类中合成一个默认构造函数。
3->成员初始化列表只提供该类数据化成员的初始化,而在构造函数体内对数据成员设置值失忆个赋值操作。
2.拷贝构造函数
拷贝构造函数:
一种特殊的构造函数,只有一个形参,而且该形参是对本类类型对象的引用,这样的构造函数称为拷贝构造函数。
拷贝构造函数特征:
1、它是构造函数的重载。
2、只有一个参数。
3、由普通构造函数和赋值操作符共同实现。
4、它的参数必须使用同类型对象的引用传递。
5、如果没有显式定义,系统会自动合成一个默认的拷贝构造函数。默认的拷贝构造函数会依次拷贝类的数据成员完成初始化。
拷贝构造函数的使用:
在C++中,下面三种对象需要调用拷贝构造函数(有时也称“复制构造函数”):
1) 一个对象作为函数参数,以值传递的方式传入函数体;
2) 一个对象作为函数返回值,以值传递的方式从函数返回;
3) 一个对象用于给另外一个对象进行初始化(常称为赋值初始化)。
eg:
class Point
{
public:
Point(int xx,int yy){X=xx;Y=yy;}
Point(const Point& p);
int getX(){returnX;}
int getY(){returnY;}
private:
intX,Y;
};
Point::Point(const Point& p)
{
X=p.X;
Y=p.Y;
std::cout<<"拷贝构造函数调用"<<std::endl;
}
3.析构函数
析构函数:
析构函数(destructor) 与构造函数相反,当对象结束其生命周期时(例如对象所在的函数已调用完毕),系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,应在退出前在析构函数中用delete释放)。
C++中的析构函数格式:
class <类名>
{
public:
~<类名>();
};
<类名>::~<类名>()
{
//函数体
};
eg:
#include <string.h>
#include <iostream>
using namespace std;
class stud
private:
int num;
char name[10];
char sex;
public:
stud(int n,const char nam[],char s)
{
num = n;
strcpy(name, nam);
sex = s;
}
~stud() //析构函数
{
cout<<"stud has been destructed!"<<endl;//通过输出提示告诉我们析构函数确实被调用了
}
void display()
{
cout<<"num:"<<num<<endl;
cout<<"name:"<<name<<endl;
cout<<"sex:"<<sex<<endl;
}
};
int main()
{
stud stud1(10010, "Wang-li", 'f'), stud2(10011, "Zhang-fun", 'm');
stud1.display();//输出学生1的数据
stud2.display();//输出学生2的数据
return 0;
}
当主函数结束时调用析构函数,输出stud has been destructe!。
4.赋值预算符重载
重载操作符是具有特殊函数名的函数,关键字operator后面接需要定义的操作符符号。操作符重载也是一个函数,具有返回值和形参表。它的形参数目与操作符的操作数目相同,函数调用操作符可以接受任意数目的操作数。使用运算符重载可以提高代码的可读性。返回类型 operate 操作符(参数列表)。
eg:
class A
{
public:
A()
{}
A(int id,char *t_name)
{
_id=id;
name=new char[strlen(t_name)+1];
strcpy(name,t_name);
}
A& operator =(A& a) //注意:此处一定要返回对象的引用,否则返回后其值立即消失!
{
if(name!=NULL)
delete name;
this->_id=a._id;
int len=strlen(a.name);
name=new char[len+1];
strcpy(name,a.name);
return *this;
}
~A()
{
cout<<"~destructor"<<endl;
delete name;
}
int _id;
char *name;
};
int main()
{
A a(1,"helloworld");
A b;
b=a;
}
小结:
使用重载操作符,可以令程序更自然、更直观,而滥用操作符重载会使得类难以理解,在实践中很少发生明显的操作符重载滥用。但有些程序员会定义operator+来执行减法操作,当一个重载操作符不明确时,给操作符取一个名字更好,对于很少用的操作,使用命名函数通常比用操作符好,如果不是普通操作,没有必要为简洁而用操作符。