C++ 类的六个默认成员函数

1.构造函数

1.1概念

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

class Data
{
public:
	void Setdata(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Printdata()
	{
		cout << _year << "." << _month << "." << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Data d1;
	Data d2;
	d1.Setdata(2019, 8, 1);
	d2.Setdata(2018, 7, 1);
	d1.Printdata();
	d2.Printdata();
	system("pause");
	return 0;
}

对于Data类,每次都可以通过调用Setdata函数给对象设置内容。但每次都要创建对象很麻烦。可以通过构造函数,在创建对象的同时将内容设置。

1.2特性

构造函数是特殊的成员函数,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。

构造函数特征如下:

  1. 函数名与类名相同。
  2. 无返回值。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。
class Data
{
public:
	 Data()//无参构造函数
	{}
	 Data(int year, int month, int day)//带参构造函数
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Data d1;//调用无参构造函数(通过无参构造函数定义对象,后面不需加括号,否则成为函数声明)
	Data d2(2019, 8, 1)//调用带参构造函数
	// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明    
	// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
	Date d3(); 
}

5.如果类中没有显式定义一个构造函数,编译器会自动生成一个无参的默认构造函数。

class Data
{
public:

private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Data d1;//没有定义构造函数,对象也能创建成功,系统自动调用默认构造函数
}

6.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。

#include<iostream>
using namespace std;
class Date {
	int m_year;
	size_t m_month;
	size_t m_day;
public:
	Date() //无参构造函数
	{
		m_year = 1900;
		m_month = 01;
		m_day = 15;
	}
	//只允许存在一个默认构造函数!
	Date(int y = 2019, size_t m = 05, size_t d = 01) //全缺省构造函数
	{
		m_year = y;
		m_month = m;
		m_day = d;
	}
};
int main() 
{
	Date a;
	Date b(2019, 01, 01);
	return 0;
}

在这里插入图片描述

在这里插入图片描述

1.3初始化列表

在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值

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

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称作为类对象成员的初始化,构造函数体中的语句只能将其称作为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。

1.3.1概念

以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个**"成员变量"后面跟一个放在括号中的初始值或表达式。**

 Date(int year, int month, int day)
 : _year(year), 
 _month(month),
 _day(day)

类中包含以下成员,必须放在初始化列表位置进行初始化:
(1)类类型成员 (该类没有默认构造函数)

因为Test带参数的构造函数,那么他是无法依靠编译器生成无参构造函数的。所以没有三个int型数据,就无法创建Test的对象。Test类对象是MyTest的成员,想要初始化这个对象test,那就只能用成员初始化列表。

//类 类型成员
class Test
{
 public:
    Test (int, int, int){
    cout <<"Test" << endl;
 };
 private:
    int x;
    int y;
    int z;
};
class Mytest 
{
 public:
    Mytest():test(1,2,3){       //初始化
    cout << "Mytest" << endl;
    };
private:
    Test test; 
};

(2)const成员变量&引用类型成员变量

因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的

//const变量和引用
class Test
{
    const int a;             //const成员声明
 public:
    Test():a(10){}           //初始化
};
class Test
{
     int &a;                        //引用声明
 public:
     Test(int a):a(a){}        //初始化
}

(3)尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。

class Time
{
public:    
	Time(int hour = 0):_hour(hour)   
	{
	cout << "Time()" << endl;    
	}
private:    
	int _hour;
};
class Date
{
public:    
	Date(int day)    
	{}
private:    
	int _day;    
	Time _t;
};
int main()
{   
 Date d(1);
}

(4)成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

class Array
{
public:    
	Array(int size):
	_size(size),
	_array((int*)malloc(sizeof(int)*_size))    
	{}
private:    
	int* _array;    
	int _size;
};

2.析构函数

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

typedef int DataType;
class SeqList
{
public:
	SeqList(int capacity = 10)
	{
		_pData = (DataType*)malloc(capacity * sizeof(DataType));
		assert(_pData);

		_size = 0;
		_capacity = capacity;
	}
		~SeqList()
	{
			if (_pData)
			{
				free(_pData); // 释放堆上的空间
				_pData = NULL; // 将指针置为空
				_capacity = 0;
				_size = 0;
			}
		}

private:
	int* _pData;
	size_t _size;
	size_t _capacity;
};

析构函数是特殊的成员函数

2.2特征

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

3.拷贝构造函数

拷贝构造函数: 特殊的构造函数(构造函数的重载) , 只有单个形参(不算隐藏的this指针),该形参是对本类类型对象的引用(一般常用const修饰),在建立一个新对象时, 使用一个已经存在的对象去初始化这个新对象。

Date d2(d1);
Date d3=d1;
class Date
{
public:
	Date(int year = 1900, 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;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2(d1);
	return 0;
}

3.2特征

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。

因为当形参是对象时, 我们知道当一个(有参)函数被调用会发生实参给形参传值的情况。拷贝构造函数也一样, 形参是对象 , 实参也是对象 ,一传参就又要调用拷贝构造函数 , 再次调用又会出现这种情况, 无穷无尽递归调用 .我们传引用就可以很好地解决这个问题, 既让拷贝构造函数得到了已存在对象的数据给新对象赋值 , 又不会出现自递归。

3.3浅拷贝(简单的赋值拷贝操作)

若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。

class Date
{
public:
	Date(int year = 1900, int month = 01, int day = 01)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2(d1);//d2调用默认拷贝构造函数,d2的值和d1一样。
	//d2=d1=1900 1 1 
	return 0;
}

3.5深拷贝(在堆区重新申请空间进行拷贝)

#include<iostream>
using namespace std;
class Person {
public:
	Person()
	{
		cout << "Person的默认构造函数调用" << endl;
	}
	Person(int age,int height)
	{
		m_height=new int(height);
		m_age = age;
		cout << "Person的有参构造函数调用" << endl;
	}
	int m_age;
	int* m_height;
	~Person()
	{
		if (m_height != NULL)
		{
			delete m_height;
			m_height = NULL;
		}
		cout << "Person的析构函数调用" << endl;
	}
};
void test01()
{
	Person p1(18,160);
	cout << "p1的年龄为:" << p1.m_age <<"p1的身高为" <<p1.m_height<<endl;
	Person p2(p1);
	cout << "p2的年龄为:" << p2.m_age <<"p2的身高为" << p2.m_height<<endl;
}
int main() {
	test01();
	return 0;
}

程序编译会出现错误,因为p2浅拷贝p1, 两个对象都指向了p1开辟的内存空间(p2没有开辟其自身的内存空间),但在析构函数执行后,p2释放掉了内存空间,当p1再次执行析构函数时,其内存空间已经被释放掉,再次释放则会出错!(浅拷贝带来的问题:堆区内存重复释放)这时就需要深拷贝来解决问题。

#include<iostream>
using namespace std;
class Person {
public:
	Person()
	{
		cout << "Person的默认构造函数调用" << endl;
	}
	Person(int age,int height)
	{
		m_height=new int(height);
		m_age = age;
		cout << "Person的有参构造函数调用" << endl;
	}
	Person(const Person& p)
	{
		cout << "Person的拷贝构造函数调用" << endl;
		m_age = p.m_age;
		//深拷贝操作
		m_height = new int(*p.m_height);//开辟新的内存空间来储存p2的信息
	}
	int m_age;
	int* m_height;
	~Person()
	{
		if (m_height != NULL)
		{
			delete m_height;
			m_height = NULL;
		}
		cout << "Person的析构函数调用" << endl;
	}
};
void test01()
{
	Person p1(18,160);
	cout << "p1的年龄为:" << p1.m_age <<"p1的身高为" <<*p1.m_height<<endl;
	Person p2(p1);
	cout << "p2的年龄为:" << p2.m_age <<"p2的身高为" <<*p2.m_height<<endl;
}
int main() {
	test01();
	return 0;
}

在这里插入图片描述

4.运算符重载

运算符重载:如果不做特殊处理,C++ 的 +、-、*、/ 等运算符只能用于对基本类型的常量或变量进行运算,不能用于对象之间的运算。利用运算符重载机制,赋予运算符新的机制。
运算符重载的实质时编写以运算符为名称的函数。

返回值类型 operator 运算符(参数列表)

注意:
1.不能通过连接其他符号来创建新的操作符:比如operator@
2.重载操作符必须有一个类类型或者枚举类型的操作数
3.用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义
4.作为类成员的重载函数时,其形参看起来比操作数数目少1
5.操作符有一个默认的形参this,限定为第一个形参
6." .* " 、" :: " 、" sizeof " 、" ?: " 、" . " 以上5个运算符不能重载。

包含被重载的运算符的表达式会被编译成对运算符函数的调用,运算符的操作数成为函数调用时的实参,运算的结果就是函数的返回值。运算符可以被多次重载。

#include<iostream>
using namespace std;
class complex
{
public:
	int real;
	int imag;
	complex(int r = 0, int i = 0) :real(r), imag(i){}
	complex operator - (const complex& c);//-重载为一个成员函数 参数个数等于运算符操作数-1

};
complex operator + (const complex& a, const complex& b)//+重载为一个全局函数 参数个数等于运算符操作数
{
	return complex(a.real + b.real, a.imag + b.imag);
}
complex complex::operator-(const complex& c)
{
	return complex(real - c.real, imag - c.imag);//生成一个临时的complex对象作为return语句的返回值
}
int main()
{
	complex a(4,4), b(1,1), c;
	c = a + b;//等价于 c=operator+(a,b);
	cout << c.real << "," << c.imag << endl;
	cout << (a - b).real << "," << (a - b).imag << endl;//等价于 a.operator-(b);
	system("pause");
	return 0;
}

在这里插入图片描述

5.赋值运算符重载

class Date
{
public :
 Date(int year = 1900, 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;
 }

 Date& operator=(const Date& d)
 {
 		if(this != &d)
	 {
		 _year = d._year;
 		_month = d._month;
 		_day = d._day;
	 }
 }
private:
 int _year ;
 int _month ;
 int _day ;
};

特征 :

  1. 参数类型(自定义类型或者自定义类型的引用)
  2. 返回值(*this, 返回本对象的引用)
  3. 检测是否自己给自己赋值
  4. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。(浅拷贝)

6.const类型

6.1const成员修饰类的成员函数

将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

#include<iostream>
using namespace std;
class Date
{
public:
	void display()const
	{
		cout << _year << endl;
		cout << _month << endl;
		cout << _day << endl;

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

编译器的实际处理:

#include<iostream>
using namespace std;
class Date
{
public:
    //const修饰类成员函数,**实际修饰该成员函数隐含的this指针
	void display(const Date* this)
	{
		cout <<this-> _year << endl;
		cout <<this-> _month << endl;
		cout <<this-> _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

非const对象可以调用const成员函数,非const成员函数内可以调用其它的const成员函数。 反之则不可以。

#include<iostream>
using namespace std;
class Date {
	int m_year;
	size_t m_month;
	size_t m_day;
public:
	Date(int y, size_t m, size_t d) {
		m_year = y;
		m_month = m;
		m_day = d;
	}
	void PrintDate();//非const成员函数
	void PrintDate()const;//const成员函数
};
void Date::PrintDate() {
	cout << "调用PrintDate()\n";
	cout << m_year << "年" << m_month << "月" << m_day << "日\n";
}
void Date::PrintDate() const{
	cout << "调用PrintDate()const\n";
	cout << m_year << "年" << m_month << "月" << m_day << "日\n";
}
int main() {
	Date a(2019, 10, 1);
	const Date b(2019, 10, 2);
	a.PrintDate();
	b.PrintDate();
	system("pause");
	return 0;
}

在这里插入图片描述

  1. const对象可以调用非const成员函数吗?
    不可以, 因为const 类型对象的指针为 const类类型* 强转不成非const类型的的 类类型*
  1. 非const对象可以调用const成员函数吗?
    可以, 因为非const 类型对象的指针为 类类型* 可以转换成 const 类类型*
  1. const成员函数内可以调用其它的非const成员函数吗?
    不可以, 同样,因为const 类型对象的指针为 const类类型* 强转不成非const类型的的 类类型*
  1. 非const成员函数内可以调用其它的const成员函数吗?
    可以, 因为非const 类型对象的指针为 类类型* 可以转换成 const 类类型*

6.2取地址及const取地址操作符重载

这两个默认成员函数一般不用重新定义 ,编译器默认会生成

#include<iostream>
using namespace std;
class Date {
	int m_year;
	size_t m_month;
	size_t m_day;
public:
	Date() {
 
	}
	Date* operator& () {
		cout << "Date* operator& ()被调用\n";
		return this;
	}
	const Date* operator& () const { 
		cout << "const Date* operator& () const 被调用\n";
		return this;
	}
};
 
int main() {
	Date a;
	&a;
	const Date b;
	&b;
	system("pause");
	return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值