类和对象(中)

目录

一、类的6个默认成员函数

 二、 构造函数

        1.什么是构造函数

2.关于系统自动生成的构造函数

三、析构函数

        1.什么是析构函数

四、拷贝构造

        1.拷贝构造函数的定义

2.拷贝构造的特征

小知识

        1.深拷贝举例

2. 拷贝构造另一等价写法

3.传引用传参是不需要拷贝构造的

4.传值返回创建临时变量时,也会调用拷贝构造

五、赋值运算符重载

1.运算符重载

2.赋值运算符重载

知识补充

1.函数声明的缺省值

2.前置++和后置++的区分

六、日期类函数

1.Date.h

2.Date.cpp 

3.test.cpp

七、const成员

八、取地址及const取地址操作符重载

小知识


一、类的6个默认成员函数

        如果一个类中什么成员都没有,简称为空类。
        空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员
函数。
        默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

 二、 构造函数

        1.什么是构造函数

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

        其有四大特征

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

class Date
{
 public:
   // 1.无参构造函数
   Date()
  {
    _year = 1;
    _month = 1;
    _day = 1;
   }
   // 2.带参构造函数
   Date(int year, int month, int day)
  {
     _year = year;
     _month = month;
     _day = day;
  }
 private:
   int _year;
   int _month;
   int _day;
};
int main()
{

Date d1;
Date d2(2024,7,13);
return 0;
}

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

        同时我们还可以运用之前在c++入门中缺省值的知识来进行一些优化。我们构造一个全缺省的构造函数

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

        因此我们可以构造

Date d1;
Date d2(2024);
Date d3(2024,7);
Date d4(2024,7,13);

        注意,全缺省的函数与无参的函数,在语法上构成函数重载,但是在实际调用时会产生错误,因为编译器不知道调用哪一个

2.关于系统自动生成的构造函数

        1.我们不写才会生成,我们写了任意一个构造函数就不会生成了

        2.不会处理内置类型的成员,例如int double...,是随机值,所有指针,包括自定义类型的指针也都是内置类型,一般会初始化为nullptr。(但是c++11支持声明的时候给缺省值)

    

    在声明给缺省值是给自动生成的构造函数使用的,但是当我们自己写了构造函数但是没有初始化那个内置类型时,也会使用缺省值

        3.会处理自定义类型的成员,会去调用这个成员的默认构造函数

        我们用栈来实现队列,不需要自己写队列的构造函数,自动生成的构造函数会帮我们调栈的构造函数 

        总结:一般情况下都需要我们去写构造函数,除非成员全都是自定义函数 。

        注意点:无参的构造函数,全缺省的构造函数,编译器默认生成的构造函数都被称为默认构造函数(不传参就可以直接用)。他们三个只能存在一个,多个并存会出现二义性。

三、析构函数

        1.什么是析构函数

        是与构造函数相配套的函数,对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

        析构函数也有四大特征

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

       5. 和构造函数类似,析构函数不会处理内置类型成员

        但是对于自定义类型,会调用这个成员的析构函数

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

( 后定义的函数生命周期会先结束,所以会先调用析构函数)

class Time
{
public:
    ~Time()
    {
        cout << "~Time()" << endl;
    }
private:
    int _hour;
    int _minute;
    int _second;
};
class Date
{
     private:
    // 基本类型(内置类型)
    int _year = 1970;
    int _month = 1;
    int _day = 1;
    // 自定义类型
    Time _t;
};
int main()
{
    Date d;
    return 0;
}

        小总结:c++构造函数和析构函数最重要的特点是自动调用。

四、拷贝构造

        我们先由一个现象引出拷贝构造

        在c语言中(Stack已经使用typedef)

        我们_array是通过malloc开辟空间的,这时候我们发现,传参的时候s仅仅只是将s1中_array变量中储存的地址拿了过去,只是将main栈帧里面存的东西原模原样复制到func2栈帧中而已,他们的_array中存的都是指向堆区的同一块空间的指针。这是一种浅拷贝

        但是如果我们在c++中这样使用,当我们调用结束,系统编译器自动调用析构函数时,同一块空间就会出现被释放两次的情况,就会出现错误。

        为了避免这种状况,c++中传值传参要调用拷贝构造。

        1.拷贝构造函数的定义

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

2.拷贝构造的特征

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

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

        如果是传值,如下图

        我们首先要传值传参,把d1传到d,就要调用我们自己的拷贝构造函数,把d1拷贝到d

        然后我们这个拷贝构造函数又要传值传参,把d1传给d,就又要调用我们自己的拷贝构造函数

        因此会进行循环

        3. 若未显式定义,编译器会生成默认的拷贝构造函数,它对于内置类型进行浅拷贝,对于自定义类型调用它的拷贝构造。 因此当我们没有写任何一个拷贝构造函数时,传值传参时候的现象于c语言中一模一样。这也是c++兼容c语言的体现

小知识

        1.深拷贝举例

2. 拷贝构造另一等价写法

        Date d3 = d1

        与Date d3(d1)是等价的

3.传引用传参是不需要拷贝构造的

4.传值返回创建临时变量时,也会调用拷贝构造

(临时变量sizeof较小的话会用寄存器返回,如果较大,会在main函数中提前开辟空间)

(并且临时变量具有常性)

五、赋值运算符重载

1.运算符重载

        运算符重载之后会使我们的代码可读性更强

         函数名字为:关键字operator后面接需要重载的运算符符号。例如:operator >  ,operator +

        函数原型:返回值类型 operator操作符(参数列表) 例如

        bool operator==(const Date& d1, const Date& d2)

        注意点

        1.不能通过连接其他符号来创建新的操作符:比如operator@
        2.重载操作符必须有一个自定义类型参数,用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
        3.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐
藏的this

例如  bool operator==(const Date& d)        d1.opetator ==(d2)

当然也可隐式 d1 == d2  二者等价


        4.   .*   ::   sizeof   ?: (三目操作符)     . 注意以上5个运算符不能重载。这个经常在笔试选择题中出

2.赋值运算符重载

        仍然用日期类进行举例

void operator= (const Date& d)//这里const防止被误修改 不传引用也可以,但是传引用不用再开空间,效 
                               //率更高
{
 _year = d._year;
 _month = d._month;
 _day = d._day;
}

        使用时

d1.operator=(d2);
d1 = d2;

        与拷贝构造有点像,但是与拷贝构造不同的是,我们要现有两个对象,然后把一个对象的值赋到另一个对象中

        拷贝对象是依靠一个已经存在的对象初始化另一个对象

        不过我们还可以继续优化

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

return *this;
}

        这样就可以支持连续赋值了

d1 = d2 = d3;

        而且一般出了作用域还在,我们就返回引用。

        为了防止有人写

d1 = d1;

        这样的代码我们可以添加一个if判断一下 

Date& operator= (const Date& d)
{
if(this != &d)
{
     _year = d._year;
     _month = d._month;
     _day = d._day;
}
return *this;
}

        再小小地补充一个知识

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

return *this;
}

        传值传参和传值返回的时候都要调用拷贝构造,一个是把实参传给形参,另一个是创建一个临时变量 。

        最后,对于赋值重载,我们不写它也会自动生成,它对于内置类型会进行浅拷贝,对于自定义类型会调用它的赋值重载函数

知识补充

1.函数声明的缺省值

        函数如果声明和定义分开,缺省值只能在声明的时候给。声明和定义分开可以让我们更容易地去看这个类的结构。

        声明的时候给了缺省值,编译的时候就可以编译通过了,然后在链接的时候通过修饰过后的函数名去找这个函数

        在.cpp中定义函数的时候,函数名前面要加类名:

2.前置++和后置++的区分

         添加int参数占位形成函数重载

++d1 -> d1.operator++();
Date& operator++()
{
    *this += 1;
    retuen *this;
}

d1++ -> d1.operator++(int);//后置++加了一个int参数,进行占位,根前置++构成函数重载
Date operator++(0)         //本质后置++调用,编译器进行特殊处理
{
    Date tmp(*this);
    *this += 1;
    return tmp;
}

六、日期类函数

1.Date.h

#pragma once
class date
{
public:
	
	int GetMonthDay(int year, int month);// 获取某年某月的天数
	
	date(int year = 2024, int month = 1, int day = 1);// 全缺省的构造函数

	date(const date& d);// 拷贝构造函数d2(d1)
	
	date& operator=(const date& d);// 赋值运算符重载// d2 = d3 -> d2.operator=(&d2, d3)
	
	~date();// 析构函数
	
	date& operator+=(int day);// 日期+=天数

	date operator+(int day);	// 日期+天数
	
	date operator-(int day);// 日期-天数
	
	date& operator-=(int day);// 日期-=天数
	
	date& operator++();// 前置++

	date operator++(int);	// 后置++
	
	date operator--(int);// 后置--
	
	date& operator--();// 前置--
	
	bool operator>(const date& d);// >运算符重载
	
	bool operator==(const date& d);// ==运算符重载
	
	bool operator >= (const date& d);// >=运算符重载
	
	bool operator < (const date& d);// <运算符重载

	bool operator <= (const date& d);	// <=运算符重载
	
	bool operator != (const date& d);// !=运算符重载

	int operator-(const date& d);// 日期-日期 返回天数

	void print()const;//打印
private:
	int _year;
	int _month;
	int _day;
};

2.Date.cpp 

#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
#include <iostream>
using namespace std;


int date::GetMonthDay(int year, int month)// 获取某年某月的天数
{
	const static int arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 40 == 0))
	{
		return 29;
	}
	return arr[month];
}
date::date(int year , int month, int day)// 全缺省的构造函数
{
	_year = year;
	_month = month;
	_day = day;
}
date::date(const date& d)// 拷贝构造函数d2(d1)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
date& date::operator=(const date& d)// 赋值运算符重载// d2 = d3 -> d2.operator=(&d2, d3)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
	return *this;
}
date::~date()// 析构函数
{
	_year = 0;
	_month = 0;
	_day = 0;
}
date& date::operator+=(int day)// 日期+=天数
{
	if (day < 0)
	{
		*this -= day;
	}
	_day = _day + day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day = _day - GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;
}
date date::operator+(int day)// 日期+天数
{
	date tmp = *this;
	tmp += day;
	return tmp;
}
date date::operator-(int day)// 日期-天数
{
	date tmp = *this;
	tmp -= day;
	return tmp;
}
date& date::operator-=(int day)// 日期-=天数
{
	if (day < 0)
	{
		return *this += ( - day);
	}

		_day -= day;
		while (_day <= 0)
		{
			_month--;
			if (_month < 1)
			{
				_year--;
				_month = 12;
			}
			_day = _day + GetMonthDay(_year, _month);
		}
	return *this;
}


date& date::operator++()// 前置++
{
	_day = _day + 1;
	if (_day > GetMonthDay(_year, _month))
	{
		_day = _day - GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;
}

date date::operator++(int)// 后置++
{
	date tmp = *this;
	*this = ++(*this);
	return tmp;
}
date&date :: operator--()// 前置--
{
	_day = _day - 1;
	if (_day <= 0)
	{
		_month--;
		if (_month < 1)
		{
			_year--;
			_month = 12;
		}
		_day = _day + GetMonthDay(_year, _month);
	}
	return *this;
}
date date::operator--(int)// 后置--
{
	date tmp = *this;
	*this = --(*this);
	return tmp;
}
bool date::operator>(const date& d)// >运算符重载
{
	if (_year > d._year)
	{
		return true;
	}
	else if (_year == d._year && _month > d._month)
	{
		return true;
	}
	else if (_year == d._year && _month == d._month && _day > d._day)
	{
		return true;
	}
	return false;
}

bool date::operator==(const date& d)// ==运算符重载
{
	if (_year == d._year && _month == d._month && _day == d._day)return true;
	return false;
}

bool date::operator >= (const date& d)// >=运算符重载
{
	if (*this > d || *this == d)return true;
	return false;
}

bool date::operator < (const date& d)// <运算符重载
{
	if (*this >= d)return false;
	return true;
}

bool date::operator <= (const date& d)	// <=运算符重载
{
	if (*this > d)return false;
	return true;
}

bool date::operator != (const date& d)// !=运算符重载
{
	if (*this == d)return false;
	return true;
}

int date::operator-(const date& d)// 日期-日期 返回天数
{
	date datemax = *this;
	date datemin = d;
	int flag = 1;
	if (*this < d)
	{
		datemax = d;
		datemin = *this;
		flag = -1;
	}
	int count = 0;
	while (datemin != datemax)
	{
		datemin++;
		count++;
	}
	return count * flag;
}

void date::print()const//打印
{
	cout << _year << "年" << _month << "月" << _day << "日" << endl;
}

3.test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
#include <iostream>

using namespace std;
void test1()
{
	date d1;
	d1.print();

	date d2(d1);
	d2.print();

	date d3;
	d3 = d1;
	d3.print();
}
void test2()
{
	date d1;
	d1 += 100;
	d1.print();

	date d2 = d1 + 50;
	d2.print();

	date d3;
	d3 -= 100;
	d3.print();

	date d4 = d3 - 50;
	d4.print();
}
void test3()
{
	date d1;
	date d2 =++d1;
	d2.print();

	date d3;
	date d4 = d3++;
	d4.print();

	date d5;
	date d6 = --d5;
	d6.print();

	date d7;
	date d8 = d7--;
	d8.print();
}
void test4()
{
	date d1(2024, 7, 15);
	date d2(2024, 9, 1);
	bool a, b, c, d, e, f;
	a = d1 > d2;
	b = d1 == d2;
	c = d1 >= d2;
	d = d1 < d2;
	e = d1 <= d2;
	f = d1 != d2;
	int ret= d1 - d2;
	printf("%d%d%d%d%d%d\n", a, b, c, d, e, f);
	printf("%d\n", ret);
}
int main()
{
	//test1();
	//test2();
	//test3();
	test4();

	return 0;
}

七、const成员

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

void Date::Print()const
{
    cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
//相当于void Date:Print(const Date* this)
//但是形参和实参中不能显式写出this指针
const Date d1(2024,7,15);
d1.Print();
//权限平移
Date d2(2024,7,15);
d2.Print();
//权限缩小

八、取地址及const取地址操作符重载

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

class Date
{
public :
    Date* operator&()
    {
        return this ;
    }
    const Date* operator&()const
    {
    return this ;
    }
    private :
    int _year ; // 年
    int _month ; // 月
    int _day ; // 日
};

        这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容。

小知识

1临时对象具有常性,不能修改

        所以在传值返回前面加个const没有什么意义

        例如 const  date  operator + (int)

2.函数 const  与 函数 可以同时存在,例如

         date operator = (int)const      date operator = (int),

        编译器会认为里面隐含的参数分别是const date* this 和date *this,构成函数重载。编译器会调用最匹配的。同时,如果没有非const 版本的,那就只会去调用const版本的,因为权限可以缩小

3.const 对象不能调用非const成员函数

        我们先有如下一个类

struct SeqList
{
public:
	int size()
{
    return _size;
}

private:
	// C++11
	int*   _a    = (A*)malloc(sizeof(A)*10);
	size_t _size = 0;
	size_t _capacity = 0;
};

        当我们再去·全局定义一个函数        

void func(const SeqList s1)
{
    for(int i = 0; i < s1.size();i++)
    {
      ;
    }
}
这里s1.size()调不了 因为int size()的参数是 SeqList* this而不是const SeqList* this
     int size()
     {
        return _size;
     }

所以我们要把函数改成
     int size()const
     {
        return _size;
     }

4.

const 修饰的对象,我们上面的SeqList类
我们不能修改
_a  
size_t 
size_t 
的值。
但是我们可以修改
_a里面储存的指针指向的值
可以_a[0]++

5.

        根据4中的问题,如果我们想要让const对象里面成员变量里面储存的值不能被修改,我们可以使用2中的函数重载。

即

const int& operator[](size_t i)const // 读
{
    assert(i < _size0);
    return _a[i];
}

int& operator[](size_t i)   //写
{
    assert(i < _size0);
    return _a[i];
}
这样就可以把读写分开

6.

        const对象不能调用非const函数

        同理const函数也不能调用其它非const函数

7.

        总结一下,只读函数可以加上const,内部不涉及修改的函数都是只读函数

        一组相互附用的函数如果一个函数加了const,必须全部加const

        声明和定义分离两边都加const

  • 27
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
深拷贝是一种在对象赋值时,将源对象的所有成员变量都复制到目标对象的操作。深拷贝赋值重载函数是为了实现深拷贝而定义的特殊函数。 在C++,如果一个类包含指针类型的成员变量,简单的赋值操作只会复制指针的地址,而不会复制指针指向的实际数据。这就可能导致多个对象共享同一块内存,当其一个对象释放内存时,其他对象也会受到影响。 为了解决这个问题,我们需要自定义深拷贝赋值重载函数。该函数首先会为目标对象分配新的内存空间,然后将源对象的数据复制到新的内存空间,从而实现真正的拷贝。 以下是一个示例的深拷贝赋值重载函数的实现: ```cpp class MyClass { private: int* data; public: // 构造函数 MyClass(int value) { data = new int(value); } // 拷贝构造函数 MyClass(const MyClass& other) { data = new int(*other.data); } // 赋值重载函数 MyClass& operator=(const MyClass& other) { if (this != &other) { delete data; // 释放原有内存 data = new int(*other.data); // 深拷贝数据 } return *this; } // 析构函数 ~MyClass() { delete data; } }; ``` 在上述示例,赋值重载函数首先检查源对象和目标对象是否是同一个对象,如果是同一个对象则不进行拷贝操作。然后,它释放目标对象原有的内存空间,并为目标对象分配新的内存空间,将源对象的数据复制到新的内存空间
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值