C++类和对象(中部曲)

目录

默认成员函数

 构造函数

特性

补丁:

默认构造函数

析构函数

特性

拷贝构造函数 

特征

拷贝构造函数典型调用场景:


默认成员函数
 

我们知道一个空类中什么成员都没有,但是事实上它并不是真的什么都没有

因为编译器会自动生成6个默认成员函数

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数
 

C++类和对象(中部曲)_第1张图片

 构造函数

构造函数是一个特殊成员函数名字与类名相同,创建类类型对象时由编译器自动调用,以保证
每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次
 

特性

构造函数的主要任务是初始化对象

1. 函数名与类名相同
2. 无返回值
3. 对象实例化时编译器自动调用对应的构造函数
4. 构造函数可以重载

重载实例:

class Date
{
public:
	//无参构造函数
	Date()
	{

	}
	//带参构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};


int main()
{
	Date d1;
	Date d2(2023, 7, 23);
	d1.Print();
	d2.Print();
	return 0;
}

 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明

错误实例:

	Date d3();//错误

5 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦
用户显式定义编译器将不再生成
 

我们不写构造函数,编译器会生成一个无参的默认构造函数,下面代码可以编译通过

class Date
{
public:
	
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	d1.Print();
	return 0;
}

 若是我们写了构造函数,则编译器不会自动生成无参的默认构造函数,则下面代码编译报错:

因为d1创建后需要调用无参的构造函数,但是没有

class Date
{
public:
	//带参构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;//需要无参的构造函数
	d1.Print();
	return 0;
}

6 调用编译器自动生成的默认构造函数,可能会对它的作用提出疑惑,怎么调用它之后对象还是随机值呢?

因为默认构造函数对于内置类型的成员不做处理(除非声明时给缺省值)

                                对自定义类型的成员才做处理,会去调用它的默认构造函数 

内置类型就是语言提供的数据类型,如:int/char等

自定义类型就是我们使用class/struct/union等自己定义的类型

实例:

class A
{
public:
	A()
	{
		cout << "A()" << endl;//仅是为了展示调用这个构造函数
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;

};

class Date
{
public:

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;
	A _a;
};

int main()
{
	Date d1;
	d1.Print();//d1内置类型的变量还是随机值
	return 0;
}

总结:一般情况都需要我们自己写构造函数,决定初始化方式

           成员变量全是自定义类型,可以考虑不写构造函数

C++11 中针对内置类型成员不初始化的缺陷,又打了补丁

补丁:

内置类型成员变量在类中声明时可以给默认值(缺省值)

我们不写构造函数时,对于编译器自动生成的默认构造函数进行的改造:内置类型和自定义类型都完成初始化

class A
{
public:
	A()
	{
		cout << "A()" << endl;//仅是为了展示调用这个构造函数
		_hour = 0;
		_minute = 0;
		_second = 0;
	}

private:
	int _hour;
	int _minute;
	int _second;

};

class Date
{
public:

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

private:
	int _year = 2023;//给缺省值
	int _month = 7;
	int _day = 22;
	A _a;
};

int main()
{
	Date d1;
	d1.Print();
	return 0;
}

C++类和对象(中部曲)_第2张图片

默认构造函数

无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都是默认构造函数
默认构造函数只能有一个,因为多个默认构造函数存在,会出现调用歧义

简而言之,不传参就可以调用的构造就是默认构造
错误实例:

class Date
{
public:
	//无参构造函数
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	//全缺省构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

private:
	int _year = 2023;//给缺省值
	int _month = 7;
	int _day = 22;
};

int main()
{
	Date d1;//调用无参构造函数可以,调用全缺省构造函数也可以,到底调用谁?
	d1.Print();
	return 0;
}

 C++类和对象(中部曲)_第3张图片

析构函数
 

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由
编译器完成的。而对象在销毁时自动调用析构函数完成对象中资源的清理工作


特性


1. 析构函数名是在类名前加上字符 ~
2. 无参数无返回值类型
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数,注意:析构
函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数

class Stack
{
public:
	Stack(int n = 4)
	{
		cout << "Stack(int n = 4)" << endl;//仅是为了展示调用此构造函数
		if (n == 0)
		{
			_a = nullptr;
			_top = _capacity = 0;
		}
		else
		{
			_a = (int*)malloc(sizeof(int) * n);
			if (_a == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			_top = 0;
			_capacity = n;
		}
	}

	~Stack()
	{
		cout << "~Stack()" << endl;//仅是为了展示调用此析构函数
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
	Stack st;
	return 0;
}

 5 系统自动生成的默认析构函数,对内置类型的成员不做处理,对自定义类型的成员会去调用它的析构函数

class Date
{
public:
	
	Date(int year = 1 , int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	~Date()
	{
		cout << "~Date()" << endl;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

private:
	int _year;
	int _month ;
	int _day ;
};

class Stack
{
public:
	Stack(int n = 4)
	{
		cout << "Stack(int n = 4)" << endl;//仅是为了展示调用此构造函数
		if (n == 0)
		{
			_a = nullptr;
			_top = _capacity = 0;
		}
		else
		{
			_a = (int*)malloc(sizeof(int) * n);
			if (_a == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			_top = 0;
			_capacity = n;
		}
	}

	~Stack()
	{
		cout << "~Stack()" << endl;//仅是为了展示调用此析构函数
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

private:
	int* _a;
	int _top;
	int _capacity;

	Date _b;//自定义类型成员
};

int main()
{
	Stack st;
	return 0;
}

C++类和对象(中部曲)_第4张图片

 6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如
Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类,需要在堆上申请空间

7 后定义的对象,先析构

   先定义的对象,先构造

class Stack
{
public:
	Stack(int n = 4)
	{
		cout << "Stack(int n = 4)" <<"->"<"<

 

C++类和对象(中部曲)_第5张图片 

拷贝构造函数
 

一个问题:在创建对象时,我们可不可以创建一个与已存在对象一模一样的新对象呢?

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存
在的类类型对象创建新对象
时由编译器自动调用


特征
 

1. 拷贝构造函数是构造函数的一个重载形式
2. 拷贝构造函数的参数只有一个必须是同类类型对象的引用,使用传值方式编译器直接报错
因为会引发无穷递归调用,因此需要传引用传参
 

正确实例:

class Date
{
public:
	
	Date(int year = 1 , int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	Date(const Date & d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

private:
	int _year;
	int _month ;
	int _day ;
};

int main()
{
	Date d1(2023,7,23);
	Date d2(d1);
	d1.Print();
	d2.Print();
	return 0;
}

 d2创建后的值和d1一模一样,是因为我们写了拷贝构造函数

若是没有拷贝构造函数,则d2创建后只会是由构造函数初始化后的值

比如:

int main()
{
	Date d1(2023,7,23);
	Date d2;
	d1.Print();
	d2.Print();
	return 0;
}

C++类和对象(中部曲)_第6张图片 

d2要作为d1的拷贝,编译器会去自动调用拷贝构造函数,在调用拷贝构造函数之前需要先传参,又是类类型对象之间的传值传参(拷贝),则又要去调用拷贝构造函数,又要先传参……如此往复形成无穷递归

C++类和对象(中部曲)_第7张图片 

 

C++类和对象(中部曲)_第8张图片 

 3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝

编译生成的默认拷贝构造函数:

对内置类型,值拷贝

对自定义的类型,调用他的拷贝构造函数

class A
{
public:
	A(int hour = 1, int minute = 1, int second = 1)
	{
		_hour = hour;
		_minute = minute;
		_second = second;
	}

	A(const A& d)
	{
		cout << "A(const A& d)" << endl;
		_hour = d._hour;
		_minute = d._minute;
		_second = d._second;
	}

private:
	int _hour;
	int _minute;
	int _second;
};


class Date
{
public:
	
	Date(int year = 1 , int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

private:
	int _year;
	int _month ;
	int _day ;
	A _b;
};

int main()
{
	Date d1(2023, 7, 23);
	Date d2(d1);
	d2.Print();
	return 0;
}

C++类和对象(中部曲)_第9张图片

 Date类我们没有写显示的拷贝构造函数,编译器生成的默认构造函数会对内置类型的成员进行值拷贝,对自定义类型的成员去调用它的拷贝构造函数

Date d2(d1);
Date d2 = d1;
//二者写法等价,都是把d1拷贝给d2

拷贝构造函数典型调用场景:

1 使用已存在对象创建新对象
2 函数参数类型为类类型对象
3函数返回值类型为类类型对象

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用
尽量使用引用
 

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请
时,则拷贝构造函数是一定要写的,否则就是浅拷贝
 

下面我们来看一个类:我们自己不写拷贝构造函数,由编译器生成默认构造函数

class Stack
{
public:
	Stack(int n = 4)
	{
		if (n == 0)
		{
			_a = nullptr;
			_top = _capacity = 0;
		}
		else
		{
			_a = (int*)malloc(sizeof(int) * n);
			if (_a == nullptr)
			{
				perror("malloc fail");
				exit(-1);
			}
			_top = 0;
			_capacity = n;
		}
	}

	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

	void Push(int x)
	{
		if (_top == _capacity)
		{
			int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
			int* tmp = (int*)realloc(_a, sizeof(int) * newcapacity);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}

			_a = tmp;
			_capacity = newcapacity;
		}

		_a[_top++] = x;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);

	Stack s2(s1);
	return 0;
}

C++类和对象(中部曲)_第10张图片

 程序直接崩溃了,原因如下:

我们没有显示定义一个拷贝构造函数,将栈s1拷贝给栈s2时,编译器生成的默认拷贝构造函数会对内置类型进行值拷贝,那么s2中的_a也指向了s1中的_a指向的堆上的数组空间

当s2的生命周期结束后,编译器会先调用s2的析构函数,对堆上申请的数组空间进行释放

当s1的生命周期结束后,编译器又会调用s1的析构函数,对堆上申请的数组空间进行释放

一块内存空间多次释放,肯定会导致程序崩溃

C++类和对象(中部曲)_第11张图片

 解决方法:我们自己写拷贝构造函数,深拷贝

让s2中的_a指向另一块空间(一块数据与s1指向的一模一样的空间)

正确实例:

class Stack
{
public:
	Stack(int n = 4)
	{
		if (n == 0)
		{
			_a = nullptr;
			_top = _capacity = 0;
		}
		else
		{
			_a = (int*)malloc(sizeof(int) * n);
			if (_a == nullptr)
			{
				perror("malloc fail");
				exit(-1);
			}
			_top = 0;
			_capacity = n;
		}
	}

	Stack(const Stack& s)
	{
		_a = (int*)malloc(sizeof(int) * s._capacity);
		if (_a == NULL)
		{
			perror("malloc fail");
			exit(-1);
		}

		memcpy(_a, s._a, sizeof(int) * s._top);
		_top = s._top;
		_capacity = s._capacity;
	}

	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

	

	void Push(int x)
	{
		if (_top == _capacity)
		{
			int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
			int* tmp = (int*)realloc(_a, sizeof(int) * newcapacity);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}

			_a = tmp;
			_capacity = newcapacity;
		}

		_a[_top++] = x;
	}

	void Print()
	{
		int i = 0;
		for (i = 0; i < _top; i++)
		{
			cout << _a[i] << " ";
		}
		cout << endl;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Print();

	Stack s2(s1);
	s2.Print();
	return 0;
}

C++类和对象(中部曲)_第12张图片

 C++类和对象(中部曲)_第13张图片

你可能感兴趣的:(C++,c++)