类和对象竟是这样的(中集)

2024-06-06 21:27:04 浏览数 (2)

一.类的六个默认成员函数

如果一个类中啥都没有,那咱们可以叫他空类。

可是这个空类真的名副其实的“空虚”吗?当然不是,其实编译器还为这个类安排了6个默认成员函数保镖。

诶!可能有人就要问了,啥是默认成员函数呀?

默认成员函数:用户没有显示定义,而编译器会自动生成的成员函数叫做默认成员函数。

二.构造函数

1.概念

对于下面的类:

代码语言:javascript复制
class Date

{

public:
 void Init(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.Init(2022, 7, 5);
 d1.Print();
 Date d2;
 d2.Init(2022, 7, 6);
 d2.Print();
 return 0;
}

初看感觉好像很合理,可是蛇蛇们有没有发现如果每创建一次Date类就得调用Init初始化一下,是不是感觉很麻烦。诶,这里就该我们的主角----构造函数大显身手了。

构造函数:构造函数是一个特殊的成员函数,它的函数名和类名相同,创建类类型对象时由编译器调用来初始化成员变量的函数,并且在成员周期内只调用一次。

2.特性

注意注意!构造函数虽然有构造两字,可是其真实作用是初始化变量,而不是开辟变量空间!

特征:

(1)函数名与类名相同

(2)无返回值

(3)类对象实例化时编译器自动调用对应的构造函数

(4)构造函数可以重载

(5)如果用户没有显示声明构造函数,则C嘎嘎编译器会自动生成一个无参的默认构造函数,当用户显示声明时就不生成了。

下面公主王子请看代码:

代码语言:javascript复制
class Date
{
public:
    //无参的构造函数
    Date()
    {}

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

private:
    int _year;
    int _month;
    int _day;
};
itn main()
{
    Date d1;//调用无参构造函数
    Date d2(2023,12,8);//调用带参构造函数
}

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

例如:

代码语言:javascript复制
class Date

 {
  public:
      // 1.无参构造函数

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

此时会直接报错!!!

对于构造函数的第五点特征:

代码语言:javascript复制
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类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函
数

 // 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再
生成

      // 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用

 Date d1;
 return 0;
 }

关于编译器自己生成的默认构造函数很多蛇蛇们可能会很疑惑。我们写了构造函数编译器又不生成默认构造函数,但是当我们没写构造函数时,编译器生成的默认构造函数又不初始化变量,可是通过调试知道,这个默认构造函数没有初始化变量,_year/_month/_day还是随机值。看起来是不是很鸡肋。其实不是,我们直接上代码:

代码语言:javascript复制
class Time

{

public:
     Time()
     {
         cout << "Time()" << endl;
         _hour = 0;
         _minute = 0;
         _second = 0;
     }

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

class Date

{

private:
     // 基本类型(内置类型)

     int _year;
     int _month;
     int _day;
     // 自定义类型

     Time _t;
};

int main()
{
     Date d;
     return 0;
}

其实C嘎嘎把变量分为了两类,一类为内置类型,一类为自定义类型,内置类型包括int/char/指针...

,而自定义类型人如其名是程序员自己定义的类型,例如stack/Date等等,由此程序可以知道编译器生成的默认构造函数会对自定义类型的变量调用其成员函数。

要注意的是:C 11又打了一个补丁,即:内置类型对象可以在类中声明的时候给缺省值。

代码语言:javascript复制
class Time

{

public:
     Time()
     {
         cout << "Time()" << endl;
         _hour = 0;
         _minute = 0;
         _second = 0;
     }

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

注意注意!!!

无参的构造函数和全缺省的构造函数都叫默认构造函数,两个只能出现一个。

三.析构函数

1.概念

通过前面构造函数的学习,我们知道了一个对象是怎么来的,那他又是怎么销毁的呢?

我们先来讲析构函数的概念:与构造函数功能相反,析构函数并不是完成对象本身的销毁,对象本身的销毁是由编译器完成的。而对象销毁时会自动调用析构函数,析构函数负责资源清理的工作。

2.特性

析构函数的特征如下:

(1)析构函数名是类名前加~

(2)无返回值无参数

(3)一个类只能有一个析构函数,不能重载,当用户没有显示定义时,编译器会自动生成一个默认析构函数

(4)当对象生命周期结束时,会自动调用析构函数

代码语言:javascript复制
typedef int DataType;

class Stack

{

public:
     Stack(size_t capacity = 3)
     {
         _array = (DataType*)malloc(sizeof(DataType) * capacity);
         if (NULL == _array)
     {
         perror("malloc申请空间失败!!!");
         return;
     }
         _capacity = capacity;
         _size = 0;
     }
     void Push(DataType data)
     {
         // CheckCapacity();

         _array[_size] = data;
         _size  ;
     }
     // 其他方法...

     ~Stack()
     {
         if (_array)
         {
             free(_array);
             _array = NULL;
             _capacity = 0;
             _size = 0;
         }
     }

private:
     DataType* _array;
     int _capacity;
     int _size;
};

void TestStack()
{
     Stack s;
     s.Push(1);
     s.Push(2);
}

与上面构造函数一样,对于自定义成员类型,编译器会去调用他的析构函数。

四.拷贝构造函数

1.概念

相信大家生活中都看见过双胞胎,那么在我们编程中是否会有两个变量也是差不多的呢?

诶!还真有,不过得用到拷贝构造函数。

拷贝构造函数:只有单个形参,该参数是对类类型对象的引用(一般前面加上const修饰),在通过类对象创建新的对象时自动调用。

2.特性

(1)拷贝构造函数是构造函数的重载

(2)拷贝构造函数有且只有一个参数且为类类型对象的引用 ,返回类型为类类型

(3)当用户没有显示定义拷贝构造函数时,编译器会生成一个默认的拷贝构造函数,按内存顺序字节顺序直接复制拷贝,这种拷贝称为浅拷贝,也叫值拷贝(与上面一样,对于内置类型直接根据字节拷贝,对于自定义类型调用它的拷贝构造函数)

(4)若类中没有涉及到资源的申请,则写不写拷贝构造函数都行,而如果涉及到了,则必须写拷贝构造函数。

有的小伙伴初学时可能会忘记加上引用,由此可能会引发无穷递归,话不多说,直接上代码:

代码语言:javascript复制
class Date

{

public:
     Date(int year = 1900, int month = 1, int day = 1)
     {
         _year = year;
         _month = month;
         _day = day;
     }
     // Date(const Date& date)   // 正确写法

     Date(const Date date)   // 错误写法:编译报错,会引发无穷递归

     {
         _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;
}

那为什么会引发无穷递归呢?

这是由于对于拷贝构造函数的参数来说相当于又创建了个新的Date类对象,而这个对象无可避免的要去调用他的拷贝构造函数,用d1来拷贝构造date参数,由此无穷递归下去...

五.赋值运算符重载

1.运算符重载

C 为了增强代码的可读性,增加了运算符重载函数,这个函数是具有特殊函数名的函数。

函数基本构造:返回值 operator 要重载的运算符(参数列表)

注意注意!!!

(1)不能通过函数重载来创建新的操作符,例如:operator@

(2)运算符重载函数必须有一个参数为类类型

(3)对于内置类型的运算符来说,不能通过运算符重载改变它的值,例如:

(4)实际看起来作为成员函数的运算符重载函数的参数要比设想时少一个,这其实是因为this指针的存在

(5)注意注意:.*  sizeof . :: ?:   这几个运算符不能重载

2.赋值运算符重载

重载格式:

(1)参数:const 类类型对象的引用

(2)函数名:operator=

(3)返回值:类类型的引用

代码语言:javascript复制
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;
       }
        
        return *this;
 }

private:

 int _year ;
 int _month ;
 int _day ;
};

Pay attention!!!

赋值运算符重载只能作为成员函数而不能作为全局函数

代码语言:javascript复制
class Date

{

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

// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数

Date& operator=(Date& left, const Date& right)
{
     if (&left != &right)
     {
         left._year = right._year;
         left._month = right._month;
         left._day = right._day;
     }
 return left;
}

// 编译失败:

// error C2801: “operator =”必须是非静态成员

为什么会报错呢?

其实啊是因为,当我们写成全局函数的时候,在我们的类中其实已经有了一个由编译器默认生成的复制运算符函数,就会与我们的全局函数起冲突。

最后一点,当我们没有显示定义赋值运算符函数时,编译器会自动生成一个函数,以值的方式逐字节拷贝,对于内置类型直接实施复制,对于自定义类型回去调用他的赋值运算符函数。并且当我们的类涉及到开辟空间时,我们必须要显示定义。

六.多种运算符重载的具体代码实现

代码语言:javascript复制
int Date::Getmonthday(int year, int month)
{
	assert(month >= 1 && month <= 12 && year >= 1);
	int day[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	if (month==2&&((_year % 4 == 0 && _year % 100 != 0) || (_year % 400 == 0)))
	{
			return 29;
	}
	return day[month];
}
Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
	if (year <= 0 || month <= 0 || month >= 13 || day > Getmonthday(year, month))
	{
		cout<<"日期非法"<<endl;
	}
}
Date::Date(const Date& d1)
{
	_year = d1._year;
	_month = d1._month;
	_day = d1._day;
}
Date::~Date()
{
	//...
}
void Date::Print()
{
	cout << _year << "/" << _month << "/" << _day << endl;
}
bool Date::operator==(const Date& y)
{
	return _year == y._year
		&& _month == y._month
		&& _day == y._day;
}
bool Date::operator!=(const Date& d1)
{
	return !(*this == d1);
}
bool Date::operator>(const Date& y)
{
	if (_year > y._year)
	{
		return true;
	}
	else if (_year==y._year&&_month > y._month)
	{
		return true;
	}
	else if (_year==y._year&&_month==y._month&&_day > y._day)
	{
		return true;
	}
	return false;
}
bool Date::operator>=(const Date& y)
{
	return (*this > y) || (*this == y);
}
bool Date::operator<(const Date& y)
{
	return !(*this >= y);
}
bool Date::operator<=(const Date& y)
{
	return !(*this > y);
}
Date& Date::operator =(int day)
{
	if (day < 0)
	{
		*this -= (-day);
		return *this;
	}
	_day  = day;
	while (_day > Date::Getmonthday(_year, _month))
	{
		_day -= Date::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)
{
	if (day < 0)
	{
		*this  = (-day);
		return *this;
	}
	_day -= day;
	while (_day <=0)
	{

		--_month;

		if (_month ==0)
		{
			_year--;
			_month = 12;
		}
		_day  = Date::Getmonthday(_year, _month);
	}

	return *this;
}
Date Date::operator-(int day)
{
	Date tmp(*this);
	tmp -= day;
	return tmp;
}
Date& Date::operator  ()
{
	return *this  = 1;
}
Date& Date::operator  (int)
{
	Date tmp(*this);
	tmp  = 1;
	return tmp;
}
Date& Date::operator--()
{
	return *this -= 1;
}
Date& Date::operator--(int)
{
	Date tmp(*this);
	tmp -= 1;
	return tmp;
}
int Date::operator-(Date& y)
{
	int count=0;
	//假设左大右小
	int flag = 1;
	Date tmp = *this;
	Date tmp2 = y;
	//假设错了,左小右大
	if (*this < y)
	{
		tmp = y;
		tmp2 = *this;
		flag = -1;
	}
	while (tmp != tmp2)
	{
		tmp2  =1;
		count =flag;
	}
	return count*flag;
}

总结

好了,到这里今天的知识就讲完了,大家有错误一点要在评论指出,我怕我一人搁这瞎bb,没人告诉我错误就寄了。

祝大家越来越好,不用关注我(疯狂暗示)

0 人点赞