类的六个成员函数
构造函数
特点
- 函数名与类名相同
- 无返回值
- 对象实例化时编译器自动调用对应的构造函数
- 构造函数可以重载
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
- 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意: - 无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
- 一般情况下,有内置类型成员,就需要自己写构造函数,不能用编译器自己生成的。
- 全部都是自定义类型成员,可以考虑让编译器自己生成
- 如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
内置类型 | 自定义类型 |
---|---|
语言提供,无需用户定义 | 用户根据需求定义 |
通常由编译器自动管理 | 需要用户手动管理 |
自定义类型,默认缺省值,编译器自动生成
#include<iostream>
using namespace std;
class date
{
public:
date(int year=2025,int month=03,int day=03)
{
_year = year;
_month = month;
_day = day;
cout << _year << endl;
}
void print()
{
cout << _year << " " << _month << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
date d1;
d1.print();
return 0;
}
无参调用,对象后面不能带括号
#include<iostream>
using namespace std;
class date
{
public:
date()
{
_year = 1;
_month = 2;
_day = 3;
cout << _year << endl;
}
void print()
{
cout << _year <<" "<<_month<< endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
date d1;
d1.print();
return 0;
}
半缺省调用
#include<iostream>
using namespace std;
class date
{
public:
date(int year=2025,int month=03,int day=03)
{
_year = year;
_month = month;
_day = day;
cout << _year << endl;
}
void print()
{
cout << _year <<" "<<_month<< endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
date d1(2029);
d1.print();
return 0;
}
构造函数的重载
#include<iostream>
using namespace std;
class date
{
public:
date(int year=2025,int month=03,int day=03)
{
_year = year;
_month = month;
_day = day;
cout << _year << endl;
}
date(double h=2.2)
{
cout << h << endl;
}
void print()
{
cout << _year <<" "<<_month<< endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
date d1(2029);
d1.print();
date d2(5.5);
return 0;
}
析构函数
与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
特点
- 析构函数名是在类名前加上字符 ~
- 无参数无返回值类型。
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
- 析构函数不能重载
- 对象生命周期结束时,C++编译系统系统自动调用析构函数
- 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类
- 需要释放资源的都是自定义类型,不需要写析构
#include<iostream>
using namespace std;
class stack
{
private:
int* _a = nullptr;
int _top = 0;
int _capacity;
public:
stack(int capacity=4)
{
cout << "stack()" << endl;
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == nullptr)
{
perror("malloc fail");
return;
}
_capacity = capacity;
_top = 0;
}
~stack()
{
cout << "~~" << endl;
free(_a);
_a = nullptr;
_capacity = _top = 0;
}
};
int main()
{
stack st1;
return 0;
}
构造函数与析构函数的调用顺序
- 构造函数是按照语句的顺序进行构造,析构函数是按照与构造函数相反的顺序进行析构
- 对象析构要在生存在作用域结束的时候析构
- static改变对象的生存作用域,要等到程序结束时才会析构释放对象
假定已有A,B,C,D四个类的定义
C c;
int main()
{
A a;
B b;
static D d;
return 0;
}
它们的析构函数调用顺序是B,A,D,C。全局对象先于局部对象进行构造。局部对象按照出现的顺序进行构造,无论是否为static。所以构造的顺序为 c a b d。析构的顺序按照构造的相反顺序析构,只需注意static改变对象的生存作用域之后,会放在局部对象之后进行析构。因此析构顺序为B A D C。
拷贝构造
深拷贝与浅拷贝
深拷贝:深拷贝是指在拷贝对象时,为新对象分配独立的内存资源,并复制原对象的数据到新内存中。这样,新旧对象的资源是独立的,不会相互影响。深拷贝通常需要在拷贝构造函数中手动实现。
浅拷贝:默认的拷贝构造函数执行的是浅拷贝,即逐位复制对象的内存内容。对于包含指针等动态资源的成员变量,浅拷贝会导致新旧对象的指针指向相同的内存地址,这可能导致资源管理问题,如重复释放内存等。
拷贝构造函数是C++中用于初始化类的新对象的一个特殊构造函数,它以同类型的另一个对象为参数,用于复制对象的成员变量。
特点
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须是类类型对象的引用(const 类&),使用传值方式编译器直接报错,因为会引发无穷递归调用
- 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
- 在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的
- 类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝
- 没有返回值
#include<iostream>
using namespace std;
class date
{
public:
date(int year = 2000, int month = 1, int day = 1)
{
cout << 1 << endl;
_year = year;
_month = month;
_day = day;
}
date(const date& d)
{
cout << 2 << endl;
_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;
}
class Date
{
public:
Date(int year, int minute, int day)
{
cout << "Date(int,int,int):" << this << endl;
}
Date(const Date& d)
{
cout << "Date(const Date& d):" << this << endl;
}
~Date()
{
cout << "~Date():" << this << endl;
}
private:
int _year;
int _month;
int _day;
};
Date Test(Date d)
{
Date temp(d);
return temp;
}
int main()
{
Date d1(2025, 1, 26);
Test(d1);
return 0;
}
运算符重载
它允许程序员为自定义类型(如类和结构体)定义运算符的行为。通过运算符重载,可以使得代码更加直观和易读。运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
运算符重载语法形式:
返回类型 operator操作符(参数列表){}
特点:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数目少一,因为成员函数的第一个参数为隐藏的this
- .* :: sizeof ?: . 这五个运算符不能重载
赋值运算符重载
语法形式
类名& operator=(const 类名& 右值);
- 赋值运算符只能重载成类的成员函数不能重载成全局函数
- 默认成员函数只能写在类里面,不能写到全局,因为会自动生成,但可以在类里面声明。
- 赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
- 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值
日期类的实现
- 成员函数后面加上const以后,普通对象和const对象都可以调用
- 只要成员函数内部不修改成员变量,就应该加上const
- 流插入不能写成成员函数
- Date对象默认占用第一个参数,即左操作数
#include<iostream>
#include<assert.h>
using namespace std;
class Date
{
friend ostream& operator<<(ostream& out, const Date& d);//友元函数的声明
friend istream& operator>>(istream& in, Date& d);
public:
Date(int year = 1, int month = 2, int day = 3);
void Print()
{
cout << _year << " " << _month << " " << _day << endl;
}
Date(const Date& d)
{
_year = d._year;//this->_year=d._year(隐藏的参数this指针)
_month = d._month;
_day = d._day;
}
bool operator<(const Date& x)const;
bool operator==(const Date& x)const;
bool operator<=(const Date& x)const;
bool operator>(const Date& x)const;
bool operator>=(const Date& x)const;
bool operator!=(const Date& x)const;
int GetMonthDay(int year, int month);
Date& operator+=(int day);
Date operator+(int day)const;
Date& operator-=(int day);
Date operator-(int day)const;
Date& operator++();//前置++
Date operator++(int);//后置++(增加这个int参数,不是为了接受具体值,仅仅是为了占位,与前置++构成重载
Date& operator--();
Date operator--(int);
int operator-(const Date& d) ;
private:
int _year;
int _month;
int _day;
};
// 流插入不能写成成员函数?
// 因为Date对象默认占用第一个参数,就是做了左操作数
// 写出来就一定是下面这样子,不符合使用习惯
//d1 << cout; // d1.operator<<(cout);
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);
#include"project3.h"
Date::Date(int year , int month , int day )
{
if (month > 0 && month < 13 && day>0 && day < GetMonthDay(year, month))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "非法日期" << endl;
assert(false);
}
}
int Date::GetMonthDay(int year, int month)
{
static int daysarr[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 daysarr[month];
}
bool Date::operator<(const Date& x)const
{
if (_year < x._year)
{
return true;
}
if (_year == x._year && _month < x._month)
{
return true;
}
if (_year == x._year && _month == x._month && _day < x._day)
{
return true;
}
return false;
}
bool Date::operator==(const Date& x)const
{
if (_year == x._year && _month == x._month && _day == x._day)
{
return true;
}
return false;
}
bool Date::operator<=(const Date& x)const
{
return (*this < x || *this == x);
}
bool Date::operator>(const Date& x)const
{
return !(*this <= x);
}
bool Date::operator>=(const Date& x)const
{
return !(*this < x);
}
bool Date::operator!=(const Date& x)const
{
return !(*this == x);
}
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)
{
_month = 1;
++_year;
}
}
return *this;
}
Date Date::operator+(int day)const
{
Date tmp (*this);
tmp._day += day;
while (tmp._day > GetMonthDay(_year, _month))
{
tmp._day -= GetMonthDay(_year, _month);
++tmp._month;
if (tmp._month == 13)
{
tmp._month = 1;
++tmp._year;
}
}
return tmp;
}
Date& Date::operator-=(int day)
{
if (day < 0)
{
_day += -day;
}
_day -= day;
while (_day < 0)
{
_month--;
while (_month == 0)
{
_month = 12;
_year--;
}
_day += GetMonthDay(_year,_month);
}
return *this;
}
Date Date::operator-(int day)const
{
Date tmp(*this);
tmp -= day;
return tmp;
}
Date& Date::operator++()//前置++
{
*this += 1;
return *this;
}
Date Date::operator++(int)//增加int参数占位,与前置++构成重载
{
Date tmp = *this;
*this += 1;
return tmp;
}
Date& Date::operator--()
{
*this -= 1;
return *this;
}
Date Date::operator--(int)
{
Date tmp = *this;
*this -= 1;
return tmp;
}
int Date::operator-(const Date& d)
{
Date min = *this;
Date max = d;
int flag = 1;
if (min > max)
{
min = d;
max = *this;
flag = -1;
}
int n = 0;
while (min != max)
{
min++;
n++;
}
return n * flag;
}
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << " " << d._month << " " << d._day << endl;
return out;
}
istream& operator>>(istream& in, Date& d)
{
int year, month, day;
in >> year >> month >> day;
if (month > 0 && month < 13 && day>0 && day < d.GetMonthDay(year, month))
{
d._year = year;
d._month = month;
d._day = day;
}
else
{
cout << "非法日期" << endl;
assert(false);
}
return in;
}
#include"project3.h"
int main()
{
Date d1(2022, 2, 22);
d1 += 100;//日期类加等
d1.Print();
d1 -= 200;//日期类减等
d1.Print();
++d1;//前置++
d1.Print();
d1++;//后置++
d1.Print();
Date d2(2025, 2, 26);
Date d3(d2 + 100);
//Date d3=d2+100;
Date d4 = d2;//用已经存在的一个对象初始化另一个对象————构造函数,不是赋值
d4.Print();
d2.Print();
cout << d1 - d2 << endl;//日期计算天数差
cout << d2 << d3 << d1;//流插入
//流提取
cin >> d1 >> d2;
cout << d2 << d1;
return 0;
}
链接: C++类和对象基本认识下