c++的学习之路:6、类和对象(2)

简介: c++的学习之路:6、类和对象(2)

一、 构造函数

如果一个类什么成员都没有,那么他是一个空类吗?在c++的创建时,就规定了在类没有成员时,也会有六个默认的成员,简称6个默认成员函数,如下图所示

先介绍一下构造函数,这里就利用代码的演示来解释,这里是利用日期类来介绍的,如下代码就是正常的初始化,利用Init函数进行的初始化。

对于这个日期类来说每次都利用Init去初始化会显得很麻烦,这时c++的创造着就提出了一个函数叫做构造函数,这个函数主要就是在创建这个类的时候就进行初始化。

class Date
{
public:
  void Init(int year, int month, int day)
  {
    _year = year;
    _month = month;
    _day = day;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d1;
  d1.Init(2024, 3, 28);
  Date d2;
  d2.Init(2023, 3, 28);
  return 0;
}

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任

务并不是开空间创建对象,而是初始化对象。

如下代码所示就是创建的构造函数,构造函数是可以重载的,这里定义了两个,一个是没给参数,创建的可以看出是个随机值,一个是传参创建的是我想要的结果,可以得出构造函数可以重载是正确的,在创建时自动调用了,那么在创建类的时候没有参数可以像第二个构造函数一样吗?可以在后面加上括号吗?如图二看出创建d3的时候警告了,所以是不可以的。

 

class Date
{
public:
  Date()
  {}
  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, 3, 28);
  return 0;
}

如果不写构造函数那么会自动创建构造函数吗?从下面的图一看出只是发出警告,但是编译可以通过,只是再把构建函数加上我发现反而编译不通过了如图二,只是因为在类里面没有构造函数时,编译器自动生成了一个,但是在我加上后反而会因为找不到编译器默认生成的构造函数而报错。

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

那么构造函数可以向函数一样进行缺省吗 ?如图所示是可以进行缺省的,那么可以部分缺省吗?答案也是可以的

class Date
{
public:
  Date(int year = 2024, int month = 3, int day = 28)
  {
    _year = year;
    _month = month;
    _day = day;
  }
private:
  int _year;
  int _month;
  int _day;
};
 
int main()
{
  Date d1;
  return 0;
}

二、析构函数

析构函数和构造函数是一对的,与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。析构函数名是在类名前加上字符 ~。无参数无返回值类型。一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载,对象生命周期结束时、C++编译系统系统自动调用析构函数。这里就用栈来举例了,在创建栈的时候时是需要进行申请空间的,所以在程序结束时会自动调用吗?

如下代码和图可以看出在程序结束时是自动调用了析构函数,可以从图中看到,c++中是可以创造默认的析构函数时,如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如日期类;有资源申请时,一定要写,否则会造成资源泄漏,比如栈类。

typedef int DataType;
class Stack
{
public:
  Stack(size_t capacity = 4)
  {
    _array = (DataType*)malloc(sizeof(DataType) * capacity);
    if (NULL == _array)
    {
      perror("malloc fail");
      return;
    }
    _capacity = capacity;
    _size = 0;
  }
  void Push(DataType data)
  {
    _array[_size] = data;
    _size++;
  }
  ~Stack()
  {
    cout << "~Stack()" << endl;
    if (_array)
    {
      free(_array);
      _array = NULL;
      _capacity = 0;
      _size = 0;
    }
  }
private:
  DataType* _array;
  int _capacity;
  int _size;
};
 
void Test1()
{
  Stack s1;
  s1.Push(1);
  s1.Push(2);
  s1.Push(3);
  s1.Push(4);
  Stack s2;
  s2.Push(1);
  s2.Push(2);
  s2.Push(3);
  s2.Push(4);
}
 
int main()
{
  Test1();
  return 0;
}

三、拷贝构造函数

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存

在的类类型对象创建新对象时由编译器自动调用。拷贝构造函数也是特殊的成员函数,其特征如下:

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

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

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

如下面代码被屏蔽的那一行可以看出在拷贝构造的过程中可以看出不用引用的符号就是会报错,因为他会无限的递归下去,在传值的底层中就是先拷贝一份,然后在赋值过去,这样传值就会无限的拷贝下去。如下方图二就可以看出在引用后就会正常拷贝进行构造了。

紧接着就是默认生成的拷贝构造,这个拷贝构造的就是浅拷贝,这个拷贝就是和构造函数一样,只能进行内置类型的拷贝,也就是int char 这种类型的,像栈这种的需要自己创建类型的就需要自己写的这种拷贝构造了,也就是深拷贝。

 

class Date
{
public:
  Date(int year = 204, int month = 3, int day = 28)
  {
    _year = year;
    _month = month;
    _day = day;
  }
    //Date(const Date d) 
  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;
}

四、 赋值运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其

返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

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

但是要注意以下几点:

1、不能通过连接其他符号来创建新的操作符:比如operator@

2、重载操作符必须有一个类类型参数

3、用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义

4、作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this

5、.*   ::  sizeof  ?:   . 注意以上5个运算符不能重载

如下方代码所示就是写的一个判断是否等于,注意下这里不需要改变数值,所以运用const修饰加上引用可以增加效率,这里就不一一演示各种符号了,在文章末会附上各种符号的写法。

class Date
{
public:
  Date(int year = 204, int month = 3, int day = 28)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  Date(const Date& d) 
  {
    _year = d._year;
    _month = d._month;
    _day = d._day;
  }
  bool operator==(const Date& d)
  {
    return _year == d._year && _month == d._month && _day == d._day;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d1;
  Date d2(d1);
  if (d1 == d2)
  {
    cout << "d1==d2" << endl;
  }
  return 0;
}

那么前置++和后置++如何实现呢,前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载 C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递,注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this+1,而temp是临时对象,因此只能以值的方式返回,不能返回引用,如下代码和下图所示在d1++是后++所以先给d1,然后d2就是28,然后d1变成29,然后前置++就是变成30,d3就是30。

class Date
{
public:
  Date(int year = 204, int month = 3, int day = 28)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  Date(const Date& d) 
  {
    _year = d._year;
    _month = d._month;
    _day = d._day;
  }
  bool operator==(const Date& d)
  {
    return _year == d._year && _month == d._month && _day == d._day;
  }
  Date& operator++()
  {
    _day += 1;
    return *this;
  }
  Date operator++(int)
  {
    Date temp(*this);
    _day += 1;
    return temp;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d1;
  Date d2;
  Date d3;
  d2 = d1++;
  d3 = ++d1;
  return 0;
}

五、const成员函数

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数

隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改,如下方代码和图片显示,就可以看出在调用const后就会调用对应的函数,这样就不会改变类的数据。

class Date
{
public:
  Date(int year, int month, int day)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  void Print()
  {
    cout << "Print()" << '-';
    cout << "year:" << _year << '-';
    cout << "month:" << _month << '-';
    cout << "day:" << _day << endl << endl;
  }
  void Print() const
  {
    cout << "Print()const" << '-';
    cout << "year:" << _year << '-';
    cout << "month:" << _month << '-';
    cout << "day:" << _day << endl << endl;
  }
private:
  int _year; 
  int _month; 
  int _day; 
};
int main()
{
  Date d1(2024, 3, 28);
  d1.Print();
  const Date d2(2024, 3, 28);
  d2.Print();
  return 0;
}

六、日期类

日期类的代码放在这里了,测试结果如图

test.cpp

#include "Date.h"
 
void Test1()
{
  Date d1(2023,3,28);
  Date d2(2000,1,1);
  Date d3;
  d1.Print();
  d2.Print();
  d3.Print();
  d3 = d3 + 100;
  d3.Print();
  d3 = d3 - 50;
  d3.Print();
  d3 += 100;
  d3.Print();
  d3 -= 50;
  d3.Print();
  d3 = d2++;
  d3.Print();
  d3 = ++d2;
  d3.Print();
  d3 = d2--;
  d3.Print();
  d3 = --d2;
  d3.Print();
  if (d1 > d2)
  {
    cout << "d1>d2" << endl;
  }
  else
  {
    cout << "d1<d2" << endl;
  }
  if (d3 < d1)
  {
    cout << "d3<d1" << endl;
  }
  else
  {
    cout << "d3>d1" << endl;
  }
  if (d3 == d2)
  {
    cout << "d3==d2" << endl;
  }
  else
  {
    cout << "d3!=d2" << endl;
  }
  if (d3 != d1)
  {
    cout << "d3!=d1" << endl;
  }
  else
  {
    cout << "d3==d1" << endl;
  }
}
 
int main()
{
  Test1();
  return 0;
}


Date.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include "Date.h"
 
// 全缺省的构造函数
Date::Date(int year, int month, int day)
{
  _year = year;
  _month = month;
  _day = day;
}
// 拷贝构造函数
Date::Date(const Date& d)
{
  _year = d._year;
  _month = d._month;
  _day = d._day;
}
// 析构函数
Date::~Date()
{
  _year = 0;
  _month = 0;
  _day = 0;
}
// 赋值运算符重载
Date& Date::operator=(const Date& d)
{
  if (this!=&d)
  {
    _year = d._year;
    _month = d._month;
    _day = d._day;
  }
  return *this;
}
// 日期+=天数
Date& Date::operator+=(int day)
{
  if (day < 0)
  {
    return *this -= (-day);
  }
  _day += day;
  while (_day > GetMonthDay(_year, _month))
  {
    _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 == 0)
    {
      --_year;
      _month = 12;
    }
 
    _day += GetMonthDay(_year, _month);
  }
  return *this;
}
// 前置++
Date& Date::operator++()
{
  *this += 1;
  return *this;
}
// 后置++
Date Date::operator++(int)
{
  Date tmp(*this);
  *this += 1;
  return tmp;
}
// 后置--
Date Date::operator--(int)
{
  Date tmp(*this);
  *this -= 1;
  return tmp;
}
// 前置--
Date& Date::operator--()
{
  *this -= 1;
  return *this;
}
// <运算符重载
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;
  }
  else
  {
    return false;
  }
}
// ==运算符重载
bool Date::operator==(const Date& d)
{
  return _year == d._year
    && _month == d._month
    && _day == d._day;
}
// <=运算符重载
bool Date::operator <= (const Date& d)
{
  return *this < d || *this == d;
}
// !=运算符重载
bool Date::operator != (const Date& d)
{
  return !(*this == d);
}
// >运算符重载
bool Date::operator>(const Date& d)
{
  return !(*this <= d);
}
// >=运算符重载
bool Date::operator >= (const Date& d)
{
  return !(*this < d);
}
// 日期-日期 返回天数
int Date::operator-(const Date& d)
{
  Date max = *this;
  Date min = d;
  int flag = 1;
  if (*this < d)
  {
    max = d;
    min = *this;
    flag = -1;
  }
  int n = 0;
  while (min != max)
  {
    ++min;
    ++n;
  }
  return n * flag;
}
// 获取某年某月的天数
int Date::GetMonthDay(int year, int month)
{
  static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30,
  31 };
  int day = days[month];
  if (month == 2
    && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
  {
    day += 1;
  }
  return day;
}
//打印
void Date::Print()
{
  cout << "Print:" << _year << '/' << _month << '/' << _day << endl << endl;
}

Date.h

#pragma once
#include <iostream>
using namespace std;
 
class Date
{
public:
  // 获取某年某月的天数
  int GetMonthDay(int year, int month);
  // 全缺省的构造函数
  Date(int year = 1, int month = 1, int day = 1);
  // 拷贝构造函数
  Date(const Date& d);
  // 赋值运算符重载
  Date& operator=(const Date& d);
  // 析构函数
  ~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();
private:
  int _year;
  int _month;
  int _day;
};



目录
相关文章
|
2天前
|
C语言 C++ 容器
C++ string类
C++ string类
8 0
|
2天前
|
C++ Linux
|
2天前
|
编译器 C++
【C++】继续学习 string类 吧
首先不得不说的是由于历史原因,string的接口多达130多个,简直冗杂… 所以学习过程中,我们只需要选取常用的,好用的来进行使用即可(有种垃圾堆里翻美食的感觉)
8 1
|
2天前
|
算法 安全 程序员
【C++】STL学习之旅——初识STL,认识string类
现在我正式开始学习STL,这让我期待好久了,一想到不用手撕链表,手搓堆栈,心里非常爽
16 0
|
2天前
|
设计模式 安全 算法
【C++入门到精通】特殊类的设计 | 单例模式 [ C++入门 ]
【C++入门到精通】特殊类的设计 | 单例模式 [ C++入门 ]
18 0
|
2天前
|
C语言 C++
【C++】string类(常用接口)
【C++】string类(常用接口)
21 1
|
2天前
|
存储 安全 测试技术
【C++】string学习 — 手搓string类项目
C++ 的 string 类是 C++ 标准库中提供的一个用于处理字符串的类。它在 C++ 的历史中扮演了重要的角色,为字符串处理提供了更加方便、高效的方法。
18 0
【C++】string学习 — 手搓string类项目
|
2天前
|
Java C++ Python
【C++从练气到飞升】06---重识类和对象(二)
【C++从练气到飞升】06---重识类和对象(二)
|
2天前
|
编译器 C++
【C++从练气到飞升】06---重识类和对象(一)
【C++从练气到飞升】06---重识类和对象(一)