提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
提示:这里可以添加本文要记录的大概内容:
接着学习c++的类和对象
提示:以下是本篇文章正文内容,下面案例可供参考
一、类的默认成员函数
如果对于一个类,我们不予任何函数和变量,那么它里面真的就什么都没有吗。
其实并不是,对于空类,编译器会自动生成下面几个默认成员变量
其中,前四个是比较重要的,需要重点讲解的
二、构造函数
1.概念
对于这样一个类,在创建一个对象之后,还需要用init函数为其初识化,那么有没有一种方法在对象创建的同时为其初始化呢?这就是构造函数的作用。
构造函数是一种特殊的成员函数,函数名和类名相同,在编译时自动调用,使每一个对象都有一个合适的初始值。
#include<iostream>
using namespace std;
class Date
{
public:
//void Init(int year, int month, int day)
//{
// _year = year;
// _month = month;
// _day = day;
//}
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 d1(2003, 3, 13);
d1.Print();
return 0;
}
2.特性
构造函数的作用就是初识化对象,对于使用也有一定规范
1、函数名要和类名相同
2、无返回值
3、对象实例化(上章有提到)时,编译器自动调用
4、支持函数重载(注意二义性)
5、在使用无参的构造函数时,不能在对象后面加上括号,否则可能变成函数声明
Date d(void);
6、如果用户没有给显式的构造函数定义,那么编译器会自动生成一个无参的构造函数
有很多人肯定想问,编译器给的构造函数能用吗,未知的东西没有什么使用价值。
7、c++中的成员变量类型分为内置类型和自定义类型,对于内置类型,我们可以在定义时手动给上缺省值。而对于自定义类型,编译器会自动调用它的默认成员函数。
#include<iostream>
using namespace std;
class Time
{
public:
Time()
{
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
//void Init(int year, int month, int day)
//{
// _year = year;
// _month = month;
// _day = day;
//}
//Date(int year, int month, int day)
//{
// _year = year;
// _month = month;
// _day = day;
//}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year = 0;
int _month = 0;
int _day = 8;
Time _t;
};
int main()
{
Date d1;
d1.Print();
return 0;
}
8、无参构造函数,全缺省构造函数,没写时编译器自动生成的构造函数都叫默认构造函数。在使用无参来创建一个变量时(Date d),默认构造函数必须存在且只能存在一个。
三、析构函数
1、概念
一个对象通过构造函数可以很方便的初始化,那么在对象使用完成后,这个对象是如何销毁的呢,其实并不是通过析构函数来进行销毁的。
对象在销毁时会自动调用析构函数来对其内的资源进行集中销毁。
2、特性
1、析构函数函数名是在类名前加上~
2、无参数和返回值
3、如果用户没有写,编译器会自动生成
4、在对象的生命周期结束时,编译器自动调用
5、在自定义类型存在时,会自动调用自定义类型的析构函数。
6、如果类中没有申请空间,只有一些内置类型变量,析构函数可以不写,使用编译器自带的析构函数就行
四、拷贝构造函数
1、概念
顾名思义,拷贝构造函数是构造函数的另一种函数重载,在创建对象时,创造一个和所给对象完全相同的对象,由编译器自己调用。
2、特性
参数只有一个,是所给的对象的引用传参,如果使用传值调用会导致无穷递归而报错。
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year = 0;
int _month = 0;
int _day = 8;
};
int main()
{
Date d(2022, 10, 3);
Date d2(d);
d.Print();
d2.Print();
return 0;
}
若未显式定义,编译器会使用默认拷贝构造函数。但只是浅拷贝,需要和后面学的深拷贝相结合,印证。
注:类中如果没有涉及到内存申请,是否写拷贝构造函数都行,一旦遇到内存申请,就必须要自己写,否则就是浅拷贝。
五、赋值运算符重载
1、运算符重载
在c++中,为了增强代码的可读性,加入了运算符重载这个概念
函数原型:返回值类型 operator 操作符(参数)
需要注意
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
{
_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;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year = 0;
int _month = 0;
int _day = 8;
};
int main()
{
Date d(2022, 10, 3);
Date d2(d);
cout << (d == d2) << endl;
d.Print();
d2.Print();
return 0;
}
这里的d==d2会在编译时自动转化为d.opeartor == (d2)所以上面在写函数时指望里面传一个参数,剩下的一个由this指针代替。
2、赋值运算符
//赋值运算符
void operator =(const Date& d)
{
_year = d._year;
_day = d._day;
_month = d._month;
}
像这样的写法看似没什么问题,但还是不够完美,在给内置类型赋值时,我们常常会使用连续赋值的方法给多个数进行赋值,我们所写的方法就没有这个功能。
这里稍微做出一点改善
Date& operator =(const Date& d)
{
_year = d._year;
_day = d._day;
_month = d._month;
return *this;
}
这里使用引用传参,虽然this指针会在出了函数作用域后销毁,但*this是d,不会销毁,引用传参的效率更高。
六、日期类的实现
具体代码如下
Date.h
#pragma once
#include<iostream>
using namespace std;
class Date
{
public:
int GetDay(int year, int month)//得到所给年,月所对于的天数
{
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 % 400 == 0))
{
return 29;
}//为闰年时
else
return arr[month];
}
//构造函数
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
if (!((year >= 1)
&& (_month >= 1 && _month <= 12) &&
(_day >= 1 && _day <= GetDay(_year, _month))))
{
cout<<"日期输入错误"<<endl;
exit(-1);
}
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
Date& 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);
bool operator != (const Date& d);
int operator - (const Date& d);//日期与日期相减
Date& operator += (int day);
Date operator + (int day);
Date& operator -= (int day);
Date operator - (int day);
Date& operator --();//前置减减
Date operator --(int);//后置减减
Date& operator ++();//前置加加
Date operator ++(int);//后置加加
private:
int _day = 0;
int _month = 0;
int _year = 0;
};
Date.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
Date& Date::operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
bool Date::operator == (const Date& d)
{
return (_year == d._year
&& _month == d._month
&&_day == d._day);
}
bool Date::operator>(const Date& d)
{
if (_year > d._year
|| (_year == d._year && _month > d._month)
|| (_year == d._year && _month == d._month && _day > d._day))
return true;
else
return false;
}
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);
}
Date& Date::operator += (int day)
{
if (day < 0)
return *this -= abs(day);
_day += day;
while (_day > GetDay(_year, _month))
{
_day -= GetDay(_year, _month);
_month++;
if (_month > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
Date Date::operator + (int day)
{
Date ret(*this);
ret += day;
return ret;
}
Date& Date::operator -= (int day)
{
if (day < 0)
return *this += abs(day);
_day -= day;
while (_day < 0)
{
_month--;
if (_month == 0)
{
_year--;
_month = 12;
}
_day += GetDay(_year, _month);
}
return *this;
}
Date Date::operator - (int day)
{
Date ret(*this);
ret -= day;
return ret;
}
Date& Date::operator --()
{
*this -= 1;
return *this;
}
Date Date::operator --(int)
{
Date ret(*this);
*this -= 1;
return ret;
}
Date& Date::operator ++()
{
*this += 1;
return *this;
}
Date Date::operator ++(int)
{
Date ret(*this);
*this += 1;
return ret;
}
int Date::operator - (const Date& d)
{
Date max = *this;
Date min = d;
int flag = 1;
if (*this < d)
{
flag = -1;
max = d;
min = *this;
}
int count = 0;
while (min != *this)
{
min++;
count++;
}
return count;
}
Test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
void Test1()
{
Date d1(2022, 10, 17);
Date d2 = d1;
Date d3(2022, 10, 17);
d1.Print();
d3.Print();
cout << (d3<=d1) << endl;
}
void Test2()
{
Date d1(2022, 10, 17);
Date d3(2022, 10, 27);
d1.Print();
d3.Print();
cout << (d3 - d1) << endl;
(++d3).Print();
}
int main()
{
//Test1();
Test2();
return 0;
}
七、输入流和输出流
我们都知道,c++在输出和输入时,使用cin和cout,但这只能对内置类型进行识别并输出,首先来研究一下它时如何识别的,实际上,cin和cout只是库里面的两个函数,进行重载后可以对内置类型进行识别
如果我们想要用cin和cout来对我们上面创建的类(日期)进行输入和打印,自然也缺少不了函数重载
从iostream可以清晰的看出,流提取和流插入,分别是属于ostream和istream。
从图片中可以看出,若想打印,必须使用d<<cout(d.operator(cout))这样的打印方式,很显然是不方便的。那么如何克服这种this指针强制占据第一位的问题呢。
其实很简单,我们只需要将这个重载函数从类中拿出来,自己给它设置两个参数。但另一个问题又来了:我们将函数拿出来后,会面临私有成员无法访问的尴尬问题。这边有两个解决方案,一个是冒险将成员变量公开化,另一种就是使用友元的用法,这边我们下一期详谈。
我们成功使用cout后,在内置类型使用时,往往会有cout<<a<<b<<c<<endl这种情况,这就要我们在函数重载的时候把返回值给设为ostream&(cout在流时是从左边开始一个一个流取的)。
总结
以上就是类和对象里比较重要的一部分内容。通过一个日期类和对象的代码,展开了一系列问题,每一个问题都值得深思,后面也会有很多的其他内容,欢迎继续关注,如有问题,还请指出,万分感谢