C++入门--lesson3

发布于:2025-05-20 ⋅ 阅读:(14) ⋅ 点赞:(0)

目录:

1.类的6个默认成员函数

2.构造函数

3.析构函数

4.拷贝构造函数

5.赋值运算符重载

1.类的6个默认成员函数

一个空类中难道真的是什么都没有吗?当然不是,其实编译器会自动生成6个默认成员函数。

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

6个成员函数有:1.构造函数(是初始化一个对象)    2.析构函数(完成对对象的空间的释放)    3.拷贝构造函数(同类对象初始化创建对象,初始化创建至关重要,这是拷贝构造函数的核心定义所在)    4.复制重载是把一个对象的值赋值给另一个对象(这两个对象都是先前已经存在的)。


2.构造函数

2.1构造函数的概念

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

2.2构造函数的特性

构造函数是特殊的成员函数,虽然名字叫构造,但是构造函数的主要任务并不是开辟空间,而是初始化对象

其主要特征如下

1.函数名与类名相同 

2.没有返回值

3.支持函数重载

4.对象实例化时自动调用对应的构造函数

5.类中如果没有我们自己手写的构造函数,那么C++编译器就会自动生成一个无参的默认构造函数,一旦用户显示定义,编译器将不再生成

我们先来学习意下如何手写构造函数

这是第一种无参数的构造函数

using namespace std;
class Date
{
public:
	Date()//现在我们学习的是如何写构造函数,函数名和类名一样
	{
		_year = 2005;
		_mounth = 11;
		_date = 13;
	}
	/*Date(int year = 2005, int mounth = 11, int date = 13)
	{
		_year = year;
		_mounth = mounth;
		_date = date;
	}*/
	void print()
	{
		cout << _year << "-" << _mounth << "-" << _date << endl;
	}
private:
	int _year;
	int _date;
	int _mounth;
};
int main()
{
	Date d1;
	d1.print();
	return 0;
}

这是第二种全缺省的构造函数

using namespace std;
class Date
{
public:
	//Date()//现在我们学习的是如何写构造函数,函数名和类名一样
	//{
	//	_year = 2005;
	//	_mounth = 11;
	//	_date = 13;
	//}
	Date(int year = 2005, int mounth = 11, int date = 13)
	{
		_year = year;
		_mounth = mounth;
		_date = date;
	}
	void print()
	{
		cout << _year << "-" << _mounth << "-" << _date << endl;
	}
private:
	int _year;
	int _date;
	int _mounth;
};
int main()
{
	Date d1;
	d1.print();
	return 0;
}

注意:如果调用无参构造函数或者是全缺省构造函数,对象后面不需要加括号,否则就变成了函数声明,返回值是一个类。

Data d1();//这样的调用方法是不对的

6.关于编译器自动生成的默认构造函数,很多人会有疑问,编译器自动生成的构造函数也不会对内置类型的变量进行初始化,那他这个生成的有什么用呢?(首先补充一点:C++中将类型分为了两类,内置类型和自定义类型。内置类型就是C++里面规定的类型,而自定义类型就是自己定义的,如class,struct)。其实,c++自动生成的默认构造函数会初始化自定义类型(实质上是调用它的默认构造函数)

using namespace std;
class Time
{
public:
	Time()//这个是我们写的构造函数
	{
		cout << "是的,我调用了这个函数" << endl;
		_hour = 0;
		_minute = 0;
		second = 0;
	}
private:
	int _hour;
	int _minute;
	int second;
};

class Date
{
public:
	//Date()//现在我们学习的是如何写构造函数,函数名和类名一样
	//{
	//	_year = 2005;
	//	_mounth = 11;
	//	_date = 13;
	//}
	/*Date(int year = 2005, int mounth = 11, int date = 13)
	{
		_year = year;
		_mounth = mounth;
		_date = date;
	}*/
	void print()
	{
		cout << _year << "-" << _mounth << "-" << _date << endl;
	}
private:
	int _year;
	int _date;
	int _mounth;
	Time today;
};
int main()
{
	Date d1;
    d1.print();
	return 0;
}

 #这里的代码可以自己下去运行一下,我在这里就不一一运行了

注意:C++为了弥补系统自己生成的默认构造函数不给内置类型初始化的缺陷,打了一个补丁,即:内置类型成员变量在类中声明时可以给默认值 

using namespace std;
class Time
{
public:
	Time()//这个是我们写的构造函数
	{
		cout << "是的,我调用了这个函数" << endl;
		_hour = 0;
		_minute = 0;
		second = 0;
	}
private:
	int _hour;
	int _minute;
	int second;
};

class Date
{
public:
	//Date()//现在我们学习的是如何写构造函数,函数名和类名一样
	//{
	//	_year = 2005;
	//	_mounth = 11;
	//	_date = 13;
	//}
	/*Date(int year = 2005, int mounth = 11, int date = 13)
	{
		_year = year;
		_mounth = mounth;
		_date = date;
	}*/
	void print()
	{
		cout << _year << "-" << _mounth << "-" << _date << endl;
	}
private:
	int _year = 2005;
	int _date = 13;
	int _mounth = 11;
	Time today;
};
int main()
{
	Date d1;
	d1.print();
	return 0;
}

再次注意一下:这里不是初始化,这里只是声明,给的是自定义类型默认的缺省值,就是缺省函数的形参是一样的,给编译器自己生成构造函数时使用。

7.注意:默认构造函数分三种,无参数的构造函数,全缺省的构造函数,以及我们没有写编译器自动生成的构造函数。(但是默认构造函数只能存在一个!!!)


3.析构函数

3.1析构函数的概念

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

 3.2析构函数的特性

1.析构函数的函数名是在类名前面加上~

2.没有参数没有返回值类型

3.一个类只能有一个析构函数。如果用户没有显示定义,系统会自动生成默认的析构函数。注意:析构函数没有参数也没有返回值,所以不能重载!!!

4.在对象的生命周期结束时,c++的编译器会自动调用析构函数

class Date
{
public:
	//Date()//现在我们学习的是如何写构造函数,函数名和类名一样
	//{
	//	_year = 2005;
	//	_mounth = 11;
	//	_date = 13;
	//}
	Date(int year = 2005, int mounth = 11, int date = 13)
	{
		_year = year;
		_mounth = mounth;
		_date = date;
	}
	~Date()
	{
		cout << "析构函数" << endl;
	}
	void print()
	{
		cout << _year << "-" << _mounth << "-" << _date << endl;
	}
private:
	int _year;
	int _date;
	int _mounth;
	/*Time today;*/
};
int main()
{
	Date d1;
	d1.print();
	return 0;
}

5.关于编译器自动生成的析构函数,会完成哪些事情呢?答案是:编译器生成的默认狗仔函数,对自定义类型成员调用它的析构函数(因为内置类型成员不需要释放)。


using namespace std;
class Time
{
public:
	Time()//这个是我们写的构造函数
	{
		cout << "是的,我调用了这个函数" << endl;
		_hour = 0;
		_minute = 0;
		second = 0;
	}
	~Time()
	{
		cout << "析构函数" << endl;
	}
private:
	int _hour;
	int _minute;
	int second;
};

class Date
{
public:
	Date(int year = 2005, int mounth = 11, int date = 13)
	{
		_year = year;
		_mounth = mounth;
		_date = date;
	}
	/*~Date()
	{
		cout << "析构函数" << endl;
	}*/
	void print()
	{
		cout << _year << "-" << _mounth << "-" << _date << endl;
	}
private:
	int _year;
	int _date;
	int _mounth;
	Time today;
};
int main()
{
	Date d1;
	d1.print();
	return 0;
}

#在程序运行下我们可以看到,为什么没有创建TIme类型的对象,但是编译器还是调用了TIme的析构函数呢?其实是因为我们实例化的d1里面包含了四个成员变量,三个内置类型和一个自定义类型成员变量。在创建变量时编译器会自动调用它的构造函数,在销毁的时候,同样会调用它的析构函数。对于内置类型的成员,销毁时不需要资源清理。

注意:1.如果类中没有向内存申请资源,析构函数可以不写,因为本身也没啥需要释放的东西     2.在一个类销毁的时候,他会先保证自己的自定义类型的成员变量先销毁,所以会先调用自定义成员变量的析构函数


4.拷贝构造函数

4.1拷贝构造函数的概念

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

4.2拷贝构造函数的特性

1.是构造函数的一个重载形式

2.拷贝构造函数的参数只有一个(是对类类型对象的引用,但使用传值的方式的话编译器会直接报错,因为会引发无穷递归调用)

问题:为什么函数的参数用传值接收的话就会报错呢?是因为C++规定了  1.在调用函数的时候先传参数    2.如果实参是内置类型的话直接拷贝,如果是自定义类型的话调用拷贝构造函数来拷贝

现在我们来写一个拷贝构造函数

Date d1;
Date d2(d1);	
Date(const Date d1)
	{
		_year = d1._year;
		_mounth = d1._mounth;
		_date = d1._date;
	}

如果我们像这样接收的话,那就是值传递,而且还是自定义类型的值传递,在运行第二行代码的时候呢,我们本意是想调用这个拷贝构造函数,但是在调用过程中,我们要去传参,传参就又涉及了拷贝构造,现在我们就回到了最初的状态,调用拷贝构造,传参,传承那就是调用拷贝构造......这样就构成了死循环。所以我们只能使用引用传递

Date(const Date& d1)
{
	_year = d1._year;
	_mounth = d1._mounth;
	_date = d1._date;
}

3.如果我们没有显示定义拷贝构造函数的话,编译器就会默认生成一个拷贝构造函数。但是默认的拷贝构造函数按内存存储按字节序完成拷贝(这种拷贝方式叫做浅拷贝),这个拷贝构造函数对内置类型的成员变量进行浅拷贝,对自定义类型则是调用它的拷贝构造函数。

补充一点:浅拷贝和深拷贝的区别。浅拷贝就像是引用,也就是别名,在对新对象操作的时候原对象也会受影响。深拷贝就是弄出来个独立的个体,新旧对象互不干扰。

class Time
{
public:
	Time()//这个是我们写的构造函数
	{
		cout << "是的,我调用了这个函数" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
	~Time()
	{
		cout << "析构函数" << endl;
	}
	Time(const Time& d1)
	{
		_hour = d1._hour;
		_minute = d1._minute;
		_second = d1._second;
		cout << "拷贝构造函数" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
public:
	Date(int year = 2005, int mounth = 11, int date = 13)
	{
		_year = year;
		_mounth = mounth;
		_date = date;
	}
	/*
	Date(const Date& d1)
	{
		_year = d1._year;
		_mounth = d1._mounth;
		_date = d1._date;
	}*/

	~Date()
	{
		cout << "析构函数" << endl;
	}
	void print()
	{
		cout << _year << "-" << _mounth << "-" << _date << endl;
	}
private:
	int _year;
	int _date;
	int _mounth;
	Time today;
};
int main()
{
	Date d1;
	Date d2(d1);
	d1.print();
	d2.print();
	return 0;
}

4.编译器自动生成的拷贝构造函数看似已经可以应对很多场景了,那么我们还需要显示实现吗?先看下面的这一串代码

#pragma once
typedef int DataType;
#include<iostream>
#include<cstring>
using namespace std;
class Stack
{
public:
	Stack(int capacity = 4)
	{
		_capacity = capacity;
		_a = (DataType*)malloc(sizeof(DataType) * _capacity);
		if (_a == nullptr)
		{
			perror("malloc");
			return;
		}
		_size = 0;
	}
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_size = 0;
		_capacity = 0;
	}
	/*Stack(const Stack& st1)
	{
		_capacity = st1._capacity;
		_size = st1._size;
		int sz = sizeof(DataType)* st1._capacity;
		_a = (DataType*)malloc(sz);
		memcpy(_a,st1._a,sizeof(DataType)*st1._size);
	}*/
	void Push(DataType x)
	{
		if (_size == _capacity)
		{
			_a = (DataType*)realloc(_a,sizeof(DataType)*_capacity*2);
			if (_a == nullptr)
			{
				perror("realloc");
				return;
			}
			_capacity *= 2;
		}
		_a[_size] = x;
		_size++;
	}
//private:
	int _size;
	DataType* _a;
	int _capacity;
};

你自己去运行一下就能发现,编译器报错了。为什么会报错呢?这就涉及到了之前的浅拷贝和深拷贝问题,我认为浅拷贝就是傻瓜式的复制,所以st1中的_a和st2中的_a指向了同一块空间,在两个自定义变量的生命周期结束的时候,编译器会自动调用析构函数,这个时候_a指向的空间被连续的释放了两次,所以报错了。

5.拷贝构造函数的使用场景

1.使用已存在的对象创建新的对象

2.只有一个参数,并且参数类型是类类型对象的引用


5.赋值运算符重载

5.1运算符重载

C++为了增强代码的可读性,引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有返回值类型,以及参数列表。

函数的名字为:operator后面接需要重载的运算符符号

注意:

# 不能创建新的操作符,如operator@

# 赋值运算符重载函数的参数必须有一个类类型参数(不能全是内置类型,否则运算符重载就没有意义了)

# 对于内置类型的操作符,不能改变其含义,比如+,你就不能改变含义

# 作为类成员函数重载时(当写在全局的时候,就不会出现这样的情况),其形参看起来会比操作数数目少1,因为成员函数的第一个参数是this

# .*    ::    sizeof    ?:    .以上5个运算符不能重载(经常在笔试中出现)

# 运算符有几个操作数就有几个参数

//全局的运算符重载函数
using namespace std;
bool operator==(const Date& d1,const Date& d2)
{
	if ((d1._year == d2._year) && (d1._mounth == d2._mounth) && (d1._date == d2._date))\
		return true;
	else
		return false
}

class Date
{
public:
	Date(int year = 2005, int mounth = 11, int date = 13)
	{
		_year = year;
		_mounth = mounth;
		_date = date;
	}
	/*
	Date(const Date& d1)
	{
		_year = d1._year;
		_mounth = d1._mounth;
		_date = d1._date;
	}*/

	~Date()
	{
		cout << "析构函数" << endl;
	}
	void print()
	{
		cout << _year << "-" << _mounth << "-" << _date << endl;
	}
private:
	int _year;
	int _date;
	int _mounth;
	//Time today;
};
int main()
{
	Date d1;
	Date d2(d1);
	d1.print();
	d2.print();
	return 0;
}

我们会发现,全局的运算符重载函数无法访问私有的成员变量。解决方法有两个:1.使用友元,但是这个不推荐使用    2.把运算符重载写在类的里面

using namespace std;
class Date
{
public:
	Date(int year = 2005, int mounth = 11, int date = 13)
	{
		_year = year;
		_mounth = mounth;
		_date = date;
	}

	bool operator==( const Date& d1)
	{
		if ((d1._year == _year) && (d1._mounth == _mounth) && (d1._date == _date))
			return true;
		else
			return false;
	}
	/*
	Date(const Date& d1)
	{
		_year = d1._year;
		_mounth = d1._mounth;
		_date = d1._date;
	}*/

	~Date()
	{
		cout << "析构函数" << endl;
	}
	void print()
	{
		cout << _year << "-" << _mounth << "-" << _date << endl;
	}
private:
	int _year;
	int _date;
	int _mounth;
	//Time today;
};
int main()
{
	Date d1;
	Date d2(d1);
	d1.print();
	d2.print();
	bool a = d1 == d2;
	printf("%d \n", a);
	return 0;
}

5.2赋值运算符重载

1.赋值运算符重载的格式

# 参数类型是const T&,引用传递可以提高效率(因为不会额外开辟空间)

# 返回值类型:T&,因为要涉到连等问题

# 检查是否能自己给自己赋值(检查的时候建议使用空间地址来进行检查,否则的话又涉及函数重载的问题)

# 返回*this

using namespace std;
class Date
{
public:
	Date(int year = 2005, int mounth = 11, int date = 13)
	{
		_year = year;
		_mounth = mounth;
		_date = date;
	}

	bool operator==( const Date& d1)
	{
		if ((d1._year == _year) && (d1._mounth == _mounth) && (d1._date == _date))
			return true;
		else
			return false;
	}

	~Date()
	{
		cout << "析构函数" << endl;
	}

	void print()
	{
		cout << _year << "-" << _mounth << "-" << _date << endl;
	}

	Date& operator=(const Date& d1)
	{
		if (this != &d1)
		{
			_year = d1._year;
			_mounth = d1._mounth;
			_date = d1._date;
		}
		return *this;
	}
private:
	int _year;
	int _date;
	int _mounth;
	//Time today;
};
int main()
{
	Date d1(2025,5,19);
	Date d2;
	d1.print();
	d2.print();
	d2 = d1;
	d2.print();
	return 0;
}

2.赋值运算符不能重载成全局函数,因为如果类里面没有赋值运算符(注意,是赋值运算符=)那么会自动生成一个赋值运算符,如果你在全局写了,那么编译器就不知道要调用哪一个了(不只是赋值运算符, 构造函数,拷贝构造函数,析构函数都不能写在全局)

3.编译器自动生成的赋值运算重载,会进行浅拷贝  1.如果是内置类型就逐个字节进行拷贝  2.如果是自定义的类型,就会调用对应的赋值运算符

既然我们已经学过拷贝构造,那就不多说了,直接上结论,如果没有涉及资源管理,用不用赋值运算符都可以;一旦涉及资源管理,就必须自己去实现


网站公告

今日签到

点亮在社区的每一天
去签到