class的构造函数
1、在创建对象时自动调用的函数,在整个对象的生命周期中一定会被调用一次,且只能被调用一次。
2、在构造函数中负责对成员变量的初始化、分配资源、设置对象的初始状态。
3、构造函数可以有多个版本,这些不同的版本之间会构造重载,创建对象时的方式不同、给的参数不同会调用相应的构造函数,如果调用的构造函数不存在可能会造成编译错误。
// 无参构造
Student stu <=> Student* stup = new Student;
Student stu(参数列表) <=> Student* stup = new Student(参数列表);
4、如果类中没有定义构造函数,编译器会自动生成一个无参构造。
一旦定义了其它版本的构造函数,无参构造就不会再生成了,因此为了防止无参方式创建对象出错,在定构造函数时,至少要实现两个。
5、无参构造未必无参,在C++中函数可以有默认参数,如果有参构造全部设置了默认参数,就会和无参数构造有冲突,它们两个只能有一个存在。
6、所谓的"编译器生成的某某函数"
"编译器生成的某某函数",不是真正意义上的函数,编译作为指令的生成者,只要生成具有某些函数功能的指令即,没有必须生成高级语言的主义上的函数。
7、什么时候调用无参构造
a、Student stu <=> Student* stup = new Student;
b、创建对象数组,每个对象都会调用一次无参构造。
c、如果类A中有成员是类B,当执行类A的构造函数前会自动调用类B的无参构造。
d、在类A中如何调用类B的有参构造
类A(参数列表):成员类B(参数列表)
{
...
}
8、类型转换构造函数
用一个数据给对象初始化,默认会自动调用构造函数,达到类型转换的效果。
这种方式虽然使用方便,但也会包容一些错误存在,如果想让代码检查更为严格可以使用explicit关键字禁止隐式转换的方式调用构造函数。
9、也可以实现自动类型转换构造函数(默认)。
下面来看一个例子:
定义一个Date类: year,month,day,在类里提供
1、年月日是否有效,返回bool类型
2、是否是闰年,返回bool类型
3、求nextday() ,返回下一天的日期
4、写一个nextday(int n)表示n表后的日期,注意n可正可负
#include
#include
#include
using namespace std;
class MyDate
{
public:
MyDate(int y, int m, int d);
// MyDate ();
void input();
void judge(int x);//运算
void output(int x);
// int get_year();
// int get_month();
// int get_day();
private:
void check();
int year;
int month;
int day;
int a;
int sum;
};
void MyDate::input ()
{
cout << " 请输入当前的日期\n";
cout << "年\n";
cin >> year ;
cout << " 月\n";
cin >> month;//year XXX;
cout << "日\n";
cin >> day;
check();
}
void MyDate::judge(int x)
{
int i = month -1;
if(year%4 ==0&&year%100!=0)//一年中就有366天
{
int mon[12] = {30,29,31,30,31,30,31,31,30,31,30,31};//声明并初始化数组
sum = mon[i] - day;
if(sum12)
{
month=month-12;
year++;
}
}
else
{
a=0;
day = day + x;
}
if(a)
{
day = sum - x;
}
}
else//一年中有365天
{
int mon[12] = {30,28,31,30,31,30,31,31,30,31,30,31};
sum = mon[i] - day;
if(sum12)
{month=month-12;
year++;}
}
else
{
a=0;
day= day+ x;}
if(a)
{
day = sum- x;
}
}
}
/*
int MyDate::get_year()
{
return year;
}
int MyDate::get_month()
{
return month;
}
int MyDate::get_day()
{
return day;
}
*/
void MyDate::check()
{
if(month < 0 || month > 12)
{
cout << " error.\n";
exit(1);
}
else
{
if(month==4 ||month==6 ||month==9 ||month==11)
{
if(day <0 || day>30 )
{
cout << " error.\n";
exit(1);
}
}
if(month==2 && day>29)
{
cout << " error.\n";
exit(1);
}
if(month==3 ||month==5 ||month==7 || month==8 ||month==10||month==12)
{
if(day>31 ||day<0)
{
cout << " error.\n";
exit(1);
}
}
}
}
MyDate::MyDate(int y, int m, int d)
{
year = y;
month = m;
day = d;
}
/*
MyDate::MyDate()
{
//空函数;
}
*/
void MyDate::output (int x)
{
cout << "从今天开始到" << x << " 天后是" << year << "年" << month << "月" << day<< "日";
cout << endl;
}
int main ()
{
MyDate date(2018,8,7);
//date.input();
int X;
cout << " 这个程序用于计算日期.\n";
//date.input();
cout << " 请输入你想要知道多少天后的日期.\n";
cin >> X;
date.judge(X);
date.output (X);
return 0;
}
这里的构造函数有两个,它们构成了重载!我们继续来看两个更为重要的构造函数
一、拷贝构造函数
1、是一种特殊的构造函数,就是用一个已有的对象去构造其同类的副本对象,即对象克隆。
class 类名
{
类名(类名& that)
{
对类成员挨个赋值
...
}
}
2、编译器会默认生成一个拷贝构造函数
编译生成的拷贝构造函数默认会逐字节复制类中的每一个成员。
如果在类A中有类B成员,会在类A的拷贝构造中自动调用类B的拷贝构造。
3、程序员可以自定义拷贝构造来取代默认的拷贝构造。
a、拷贝构造只能有一个,不能重载。
b、一旦程序员自定义的拷贝构造,编译器就不再生成。
c、在自定义的拷贝构造中能通过编码来实现成员的复制。
4、一般情况下编译器生成的拷贝构造完全够用,不要轻易自定义构造。
5、什么情况下调用拷贝构造:
a、对象与对象赋值
b、用对象与函数传参
c、用对象当作返回值
再来看一个例子:已知有一个学生类
class Student
{
char* name;
char sex;
short age;
};
Student stu1;
Student stu2 = stu1;
stu.name = new char[20];
此类在使用过程中偶尔会发生段错误、数据丢失、内存泄漏,如何改进此类。
#include
#include
using namespace std;
class Student
{
char* name;
char sex;
int age;
public:
Student(){}
Student(const char* _name,char _sex,char _age)
{
name = new char[strlen(_name)+1];
strcpy(name,_name);
sex=_sex;
age=_age;
}
Student(Student& stu)
{
//Student newstu=new Student;
cout<<"I'm copy"<name=new char;
//("haha",'m',15);
stu->show();
//Student* stu2=new Student("hahaaaa",'m',15);
Student stu1=*stu;
stu1.show();
}
在类中,如果成员变量中有指针变量,我们应该使用new和delete来管理内存;
二、赋值构造函数
赋值构造
1、赋值构造就是一个对象给另一个对象赋值的时候调用的函数。
stu2 = stu1; // 赋值构造
Student stu2 = stu1;// 拷贝构造
void func(Student stu);// 拷贝构造
func(stu1);
Student func(void) // 拷贝构造
{
return *this;
}
Student stu = func()
2、赋值构造函数的格式
void operator = (Student& that)
{
}
// 可以与其它对象进行交互
Student& operator = (Student& that)
{
}
3、编译器会默认生赋值构造,它的功能与拷贝构造的功能一样,把对象A完全拷贝给对象B。
4、赋值构造也拷贝构造的区别
拷贝构造:使用对象A去创建出对象B(调用时对象B还末生成)。
赋值构造:对象A与对象B都已经构造完成,此时B = A;
如果对象中有常成员拷贝构造可以成功调用,但赋值构造不行。
5、一般情况下默认的赋值构造基本够用的,除非有成员是指针,指向了额外的内存空间,这种情况下才需要自定义拷贝构造、赋值构造。
6、自定义赋值构造
a、确定赋值构造的格式
b、防止自赋值
int num = 1;
num = num;
c、释放旧资源
d、分配新的资源
e、拷贝新内容
注意**************与拷贝构造很类似,拷贝构造:使用对象A去创建出对象B(调用时对象B还末生成)。
赋值构造:对象A与对象B都已经构造完成,此时B = A;这是两者的主要区别。也就是使用不同的语句,那么会调用不同的构造函数。
下面给出一个赋值构造的例子:
#include
#include
using namespace std;
class Student
{
char* name;
char sex;
mutable short age;
public:
Student(const char* _name="",char sex='f',short age=1):sex(sex),age(age)
{
name = new char[strlen(_name)+1];
strcpy(name,_name);
}
Student(const Student& that)
{
name = new char[strlen(that.name)+1];
strcpy(name,that.name);
sex = that.sex;
age = that.age;
}
void show(void) const
{
cout << name << " " << age << endl;
}
void show(void)
{
cout << name << " " << age << endl;
}
void addAge(void) const
{
age++;
}
void operator = (Student& that)
{
if(this == &that) return;
// 释放旧资源
delete[] name;
Student(that);
/*
// 申请新资源
name = new char[strlen(that.name)+1];
// 拷贝新内容
strcpy(name,that.name);
sex = that.sex;
age = that.age;
*/
}
~Student(void)
{
delete[] name;
}
};
int main()
{
Student s1("lili",'m',33);
s1.show();
Student s2 = s1;
s2.show();
s2.addAge();
s2.show();
s2 = s1;
s2.show();
}
总之:在类中通常会有一个空的构造函数,这样,在创建对象时如果没有初始化,会自动调用此构造函数,但是要注意,在自己写的初始化构造函数中,就不能给默认值,否则会造成冲突,是他们之间不构成重载。