默认成员函数
接下来继续看剩下的两个默认成员函数。
const成员函数
将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后 ⾯。const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进⾏修改。
先来看例子:

所以C++设计了使用 const 修饰成员函数来达到目的。
所以,只需在成员函数参数列表后添加 const ,就能解决问题。

所以,对于一个类,如果我们希望所有的成员函数都默认为const,即不修改对象状态,我们可以使用const关键字来修饰成员函数。
取地址运算符重载
取地址运算符重载分为普通取地址运算符重载和 const 取地址运算符重载,⼀般这两个函数编译器⾃动 ⽣成的就可以够我们⽤了,不需要去显⽰实现。取地址的意思就是返回当前对象的地址,对于成员函数来讲,this指针就是它的地址。
cpp
class Date
{
public:
//普通取地址运算符重载
Date* operator&()
{
return this;
}
//const取地址运算符重载,返回一个const Date* 的指针。
const Date* operator&() const
{
return this;
}
private:
int _year;
int _month;
int _day;
};
友元
友元就是提供了⼀种突破类访问限定符封装的⽅式,友元分为:友元函数和友元类,在函数声明或者类 声明的前⾯加 friend,并且把友元声明放到⼀个类的⾥⾯。外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数。友元函数可以在类定义的任何地⽅声明,不受类访问限定符限制。
cpp
#include<iostream>
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//友元函数声明
friend void func(const Date& d);
private:
int _year;
int _month;
int _day;
};
void func(const Date& d)
{
std::cout << d._year << "年" << d._month << "月" << d._day << "日" << std::endl;
}
int main()
{
Date d1;
func(d1);
return 0;
}
并且⼀个函数可以是多个类的友元函数。
cpp
#include<iostream>
//前置声明,否则Date类中的友元函数不认识A类
class A;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//友元函数声明
friend void func(const Date& d, const A& a);
private:
int _year;
int _month;
int _day;
};
class A
{
friend void func(const Date& d, const A& a);
private:
int _a1 = 1;
int _a2 = 2;
};
void func(const Date& d, const A& a)
{
std::cout << d._year << "年" << d._month << "月" << d._day << "日" << std::endl;
std::cout << a._a1 << std::endl;
}
int main()
{
Date d1;
A a;
func(d1, a);
return 0;
}
友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。
cpp
#include<iostream>
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
friend class A;
private:
int _year;
int _month;
int _day;
};
class A
{
public:
void func(const Date& d)
{
std::cout << d._year << "-" << d._month << "-" << d._day << std::endl;
std::cout << _a1 << ' ' << _a2 << std::endl;
}
private:
int _a1 = 1;
int _a2 = 2;
};
int main()
{
Date d;
A a;
a.func(d);
return 0;
}
友元类的关系是单向的,不具有交换性。比如A类是B类的友元,但是B类不是A类的友元。而且友元类关系是不能传递,如果A是B的友元,B是C的友元,但是A不是C的友元。
赋值重载流插入和流提取
上一篇的利用运算符重载在类中实现了各种操作(详见:点这里),接下来就在类内实现流插入和流提取的重载(也就是在类内实现自定义类型的输入输出)。



流插入
通过查阅资料可以看到,cin 是 istream 类型的对象,cout 是 ostream 类型的对象,如果要重载,则类型简单可以写为:

但这样写是有问题的,是不对的。

其报错信息显示出,出现这样的错误本质上是因为参数不匹配造成的。

如果改为这样,就不会报错,也能正常运行。

所以,重载 >> 和 << 时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参位置,第⼀个形参位置是左侧运算对象,调⽤时就变成了 << cout 对象,不符合使⽤习惯和可读性。重载为全局函数把 ostream/istream 放到第⼀个形参位置就可以了,第⼆个形参位置当类类型对象。

但又出现了新的问题,就是无法访问类内的成员变量。所以我们可以使用C++的 友元 来访问类内部的成员变量。

但其还不支持连续赋值,和=等运算符一样,它一定是要支持连续赋值的,所以还要进行改造。
但与=运算符不同,流插入流提取运算符是从左到右赋值的。所以得:
cpp
#include<iostream>
using namespace std;
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)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
// 析构函数
//日期类无要释放的资源,编译器自动生成的析构即可满足要求。
void Print()
{
std::cout << _year << "-" << _month << "-" << _day << std::endl;
}
//友元函数声明
friend ostream& operator<<(ostream& out, const Date& d);
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
void test()
{
Date d1(2024, 6, 6);
Date d2(2006, 1, 2);
cout << d1 << d2;
}
int main()
{
test();
return 0;
}
接下来就是流提取得重载函数。
流提取
流提取是 istream 类型的对象,所以:
cpp
#include<iostream>
using namespace std;
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)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
// 析构函数
//日期类无要释放的资源,编译器自动生成的析构即可满足要求。
void Print()
{
std::cout << _year << "-" << _month << "-" << _day << std::endl;
}
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
istream& operator>>(istream& in, Date& d)
{
cout << "请输入年月日:";
in >> d._year >> d._month >> d._day;
return in;
}
void test()
{
Date d1(2024, 6, 6);
Date d2(2006, 1, 2);
cin >> d1 >> d2;
cout << d1 << d2;
}
int main()
{
test();
return 0;
}
内部类
如果⼀个类定义在另⼀个类的内部,这个内部类就叫做内部类。内部类是⼀个独立的类,跟定义在 全局相比,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。并且内部类默认是外部类的友元类。
简单来说就是,内部类可以访问到外部类成员,但外部类不能访问到内部类得成员。
cpp
#include<iostream>
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
class A
{
public:
void func(const Date& d)
{
std::cout << d._year << "-" << d._month << "-" << d._day << std::endl;
std::cout << _a1 << ' ' << _a2 << std::endl;
}
private:
int _a1 = 1;
int _a2 = 2;
};
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date::A a;
a.func(d1);
return 0;
}
匿名函数
直接⽤ 类型 (实参) 定义出来的对象叫做匿名对象,相⽐之前我们定义的 类型 对象名(实参) 定义出来的叫有名对象。匿名对象⽣命周期只在当前⼀⾏,⼀般用于临时定义⼀个对象。
cpp
#include<iostream>
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
std::cout << _year << "-" << _month << "-" << _day << std::endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//有名函数
Date d1;
d1.Print();
//匿名函数
Date().Print();
return 0;
}