C++中的构造函数总结

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 <iostream>
#include<unistd.h>

#include<stdlib.h>
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(sum<x)
		{
			a=1;
			month++;
			if(month>12)
			{
				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(sum<x)
		{
			a=1;
			month++;
			if(month>12)
			{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<iostream>
#include<string.h>
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"<<endl;
		name=new char[strlen(stu.name)+1];
	
		strcpy(name,stu.name);
		sex=stu.sex;
		age=stu.age;
	}
	~Student()
	{
		delete[] name;
		cout<<"我是析构函数"<<endl;
	
	}
	void show(void)
	{
		cout<<name<<" "<<sex<<" "<<age<<endl;
	}
	
};
int main()
{
	Student* stu=new Student("ha",'m',15);
	//stu->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 <iostream>
#include <string.h>

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();
}

总之:在类中通常会有一个空的构造函数,这样,在创建对象时如果没有初始化,会自动调用此构造函数,但是要注意,在自己写的初始化构造函数中,就不能给默认值,否则会造成冲突,是他们之间不构成重载。

猜你喜欢

转载自blog.csdn.net/Dachao0707/article/details/81514097