一、
问题:现在我们尝试去重载operator<<,然后发现我们没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以我们要将operator<<重载成全局函数。但是这样的话,又会导致类外没办法访问成员,那么这里就需要友元来解决。operator>>同理。
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
ostream& operator<<(ostream& _cout)
{
_cout << _year << "-" << _month << "-" << _day;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d(2017, 12, 24);
d << cout; //2017-12-24
return 0;
}
二、
友元函数:
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
#include<iostream>
using namespace std;
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
public:
Date()
{
}
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
istream& operator>>(istream& _cin, Date& d) //不能是const Date& d 否则会报错
{
_cin >> d._year;
_cin >> d._month;
_cin >> d._day;
return _cin;
}
int main()
{
Date d;
cin >> d; //输入2022 9 24
cout << d << endl; //输出2022-9-24
return 0;
}
结果如下:
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用和原理相同
友元类:
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元关系是单向的,不具有交换性。
比如Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time
类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
友元关系不能传递:如果B是A的友元,C是B的友元,则不能说明C时A的友元。
#include<iostream>
using namespace std;
class Date; // 前置声明
class Time
{
friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
Time() //必须有
{
}
Time(int hour, int minute, int second)
: _hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour=0;
int _minute=0;
int _second=0;
};
class Date
{
public:
Date() //可以没有
{
}
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
void SetTimeOfDate(int hour, int minute, int second)
{
// 直接访问时间类私有的成员变量
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
void print(Date d)
{
cout << d._year << " " << d._month << " " << d._day << " " << d._t._hour << ":" << d._t._minute << ":" << d._t._second << endl;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
int main()
{
Date d(2022, 10, 18);
d.SetTimeOfDate(13, 40, 00);
d.print(d);
return 0;
}
三、内部类
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
特性:
1. 内部类可以定义在外部类的public、protected、private都是可以的。
2. 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
3. sizeof(外部类)=外部类,和内部类没有任何关系。
#include<iostream>
using namespace std;
class A
{
private:
static int k;
int h;
public:
class B
{
public:
void foo(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
};
};
int A::k = 1;
int main()
{
A::B b;
b.foo(A()); //输出1 换行 0
return 0;
}
四、
#include<stdio.h>
// 以C语言的方式重新实现上述功能
// 在C语言中,所看即所得
struct Date
{
int year;
int month;
int day;
};
void Init(Date* pthis, int year, int month, int day)
{
pthis->year = year;
pthis->month = month;
pthis->day = day;
}
void Print(Date* pthis)
{
printf("%d-%d-%d\n", pthis->year, pthis->month, pthis->day);
}
int main()
{
Date d1;
Init(&d1, 2022, 4, 7);
Print(&d1); //2022-4-7
Date d2;
Init(&d2, 2022, 4, 8);
Print(&d2); //2022-4-8
return 0;
}
五、如果成员函数是通过指针调用,this有可能会为nullptr
class Date
{
public:
void Init(int year, int month, int day)
{
// cout << &this << endl;
int a = 10;
int& ra = a;
Date* const & rthis = this; // rthis就是this的别名
cout << &rthis << endl;
_year = year;
_month = month;
_day = day;
}
void TestThis()
{
cout << this << endl;
cout << "Date::TestThis()" << endl;
}
void Print()
{
cout << this << endl;
cout << this->_year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Init(2022, 4, 7);
d1.Print();//2022-4-7
Date* pd = &d1;
pd->Print();//2022-4-7
// 如果成员函数是通过指针调用,this有可能会为nullptr
// 当指针指向nullptr的时候,this就是nullptr 以下代码会引发异常
pd = nullptr;
pd->TestThis(); // Date::TestThis(pd);
pd->Print();
return 0;
}
注释掉部分代码后:
class Date
{
public:
void Init(int year, int month, int day)
{
// cout << &this << endl;
int a = 10;
int& ra = a;
Date* const & rthis = this; // rthis就是this的别名
cout << &rthis << endl;
_year = year;
_month = month;
_day = day;
}
void TestThis()
{
cout << this << endl;
cout << "Date::TestThis()" << endl;
}
void Print()
{
cout << this << endl;
cout << this->_year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Init(2022, 4, 7);
d1.Print();
Date* pd = &d1;
pd->Print();
// 如果成员函数是通过指针调用,this有可能会为nullptr
// 当指针指向nullptr的时候,this就是nullptr
//pd = nullptr;
//pd->TestThis(); // Date::TestThis(pd);
//pd->Print();
return 0;
}
六、无参构造和全缺省构造函数。统称为默认构造函数,且不能共存。
class Time
{
public:
Time()
{
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
// 无参构造和全缺省构造函数,统称为默认的构造函数
// 并且不能共存的
Date()
{}
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
int main()
{
Date d1(2022, 4, 7);
Date d; // 报错:编译器发现调用无参构造函数可以,调用全缺省的构造函数也可以
return 0;
}
七、如果用户没有显式提供任何构造函数,则编译器会生成一个无参的默认构造函数
class Date
{
public:
/*
如果用户没有显式提供任何构造函数,则编译器会生成一个无参的默认构造函数
Date()
{}
当Date d; 发现d对象创建好之后,d对象的成员变量全部都是随机值
也就是说编译器生成的无参构造函数没有意义
是不是说:编译器生成的无参构造函数一定是没有意义的呢? 答案:no
*/
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
d.Print();//-858993460--858993460--858993460
return 0;
}
八、
class Time
{
public:
Time()
{
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
/*
如果用户没有显式提供任何构造函数,则编译器会生成一个无参的默认构造函数
Date()
{}
当Date d; 发现d对象创建好之后,d对象的成员变量全部都是随机值
也就是说编译器生成的无参构造函数没有意义
是不是说:编译器生成的无参构造函数一定是没有意义的呢? 答案:no
*/
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
// C++11中:为了解决默认构造函数不对内置类型初始化问题
int _year=1900;
int _month = 1;
int _day = 1;
Time _t; // 日期类型对象中包含了一个时间对象
};
int main()
{
Date d; // 创建那个类的对象,编译器就会调用该类的构造函数
return 0;
}
九、拷贝构造函数
编译器会生成默认的构造函数,但一旦类中涉及到资源的管理,拷贝构造函数一定要显示实现。
// 是否就说明:直接使用编译器生成的默认拷贝构造函数就可以了呢?
// 答案:对于日期类这种没有涉及到资源管理的类是可以的
// 一旦类中涉及到资源的管理,拷贝构造函数一定要实现
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 4, 12);
Date d2(d1);
d2.Print();
_CrtDumpMemoryLeaks();
return 0;
}
以下程序会触发异常:
#include <stdlib.h>
#include <assert.h>
typedef int DataType;
// Stack没有显式实现拷贝构造函数,则编译器会生成一份默认的拷贝构造函数
class Stack
{
public:
Stack()
{
_array = (DataType*)malloc(sizeof(DataType)* 10);
if (NULL == _array)
{
assert(0);
return;
}
_capacity = 10;
_size = 0;
}
~Stack()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
void Push(const DataType& data)
{
// CheckCapcity();
_array[_size] = data;
_size++;
}
void Pop()
{
if (Empty())
return;
--_size;
}
DataType Top()
{
return _array[_size - 1];
}
size_t Size()
{
return _size;
}
bool Empty()
{
return 0 == _size;
}
private:
DataType* _array;
size_t _capacity;
size_t _size;
};
void TestStack()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2(s1); //涉及到资源管理,需要显示实现拷贝构造函数
cout << s2.Size() << endl;
cout << s2.Top() << endl;
}
int main()
{
TestStack();
return 0;
}
对于自定义类型作为函数的参数或者返回值时,能传递引用尽量传递引用,否则会调用拷贝构造函数,让程序效率低下。
// 拷贝构造函数调用场景
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;
cout << "Date(const Date&)" << endl;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
// 2. 以类类型方式传参--以值的方式
void TestDate1(Date d)
{}
// 3. 以类类型方式作为函数的返回值---以值的方式返回的
Date TestDate2()
{
Date d(2022, 4, 13);
return d;
}
Date TestDate3()
{
// 创建一个匿名对象:即没有名字的对象
// 在返回时,编译器不会在通过拷贝构造函数创建一个临时对象返回
// 而是将匿名对象直接返回了
// 相当于以匿名对象直接返回时,编译器会做优化
return Date(2022, 4, 13);
}
// 告诉一个结论:对于自定义类型作为函数的参数或者返回值时,能传递引用尽量传递引用
Date& TestDate4(Date& d)
{
return d;
}
int main()
{
// 1. 用一个对象直接构造一个新对象
Date d1(2022, 4, 12);
Date d2(d1);//输出Date(const Date&)
d2.Print();//输出2022-4-12
TestDate1(d1); //自定义类型作为函数参数,这里调用了拷贝构造函数 输出Date(const Date&)
d2 = TestDate2(); //自定义类型作为函数返回值,也调用了拷贝构造函数,生成一份临时对象,然后返回。 输出Date(const Date&)
d2 = TestDate3(); //相当于以匿名对象直接返回,不会调用拷贝构造函数,效率更高。
d2 = TestDate4(d1);//以引用类型作为参数,不会调用拷贝构造函数。
return 0;
}
十、构造函数
// 构造
class Date
{
public:
/*
// 如果编译器会给Date类生成的构造函数,对于当前的Date类,生成的构造函数一定是下面:
Date()
{}
上述构造函数没有任何意义,掉完之后对象中日期还是随机值
那生成和不生成没有任何影响,反而生成之后程序运行的效率就降低了---干脆:对于这种没有意义的构造函数就不用生成
*/
void PrintDate()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
当A类中包含有B类的对象时,如果A类没有显式定义任何构造函数,而B类显式提供了默认的构造函数。则:编译器一定会给A类生成默认的构造函数,目的就是要将A类中包含的B类的对象调用B类的构造函数初始化完整 。A-Date B-Time
class Time
{
public:
Time(int hour = 0, int minute = 0, int second = 0)
{
_hour = hour;
_minute = minute;
_second = second;
}
private:
int _hour;
int _minute;
int _second;
};
/*
当A类中包含有B类的对象时,如果A类没有显式定义任何构造函数,而B类显式提供了默认的构造函数
则:编译器一定会给A类生成默认的构造函数,目的就是要将A类中包含的B类的对象调用B类的构造函数初始化完整
Date--->A
Time--->B
*/
class Date
{
public:
void PrintDate()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
Time _t; // 类类型对象,并且Time具有默认的构造函数
};
void TestDate()
{
Date d;
d.PrintDate();
}
int main()
{
TestDate();
return 0;
}
十一、赋值运算符重载
// 赋值运算符重载
// 对于Date类编译器不会生成与赋值有关的函数
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;
}
void PrintDate()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
void TestDate()
{
Date d1(2022, 4, 16);
d1.PrintDate();//2022-4-16
Date d2;
d2.PrintDate();//1900-1-1
Date d3 = d1; // 等价于:Date d3(d1) 调用拷贝构造函数
d3.PrintDate(); //2022-4-16
d2 = d1; // 赋值
d2.PrintDate(); //2022-4-16
}
int main()
{
TestDate();
_CrtDumpMemoryLeaks();
return 0;
}
// 运算符重载:
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;
}
bool IsEqual(const Date& d)
{
return _year == d._year &&
_month == d._month &&
_day == d._day;
}
void PrintDate()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//private:
int _year;
int _month;
int _day;
};
/*
1. 什么是运算符重载
2. 为什么要重载
3. 如何重载
4. 和函数重载区别?
*/
// 运算符重载目的:告诉编译器,自定义的对象遇到该运算符时应该如何操作
// 返回值类型 operator操作符(参数列表)
bool operator==(const Date& left, const Date& right)
{
return left._year == right._year &&
left._month == right._month &&
left._day == right._day;
}
void TestDate()
{
// 内置类型定义的变量,可以直接进行相关运算符的操作
int a = 10;
int b = 20;
if (a < b)
{
cout << "a < b" << endl;
}
else if (a == b)
{
cout << "a == b" << endl;
}
else
{
cout << "a > b" << endl;
}
Date d1(2022, 4, 16);
Date d2(2022, 4, 17);
// 对于自定义类型的对象,不能直接使用对应的运算符,比如:要判断两个对象是否相等
// 原因:自定义类型的对象中包含有多个成员,如何进行比较编译器不知道
// 编译报错
// 可读性更高,但是编译器不能直接支持
//if (d1 == d2)
//{
// ;
//}
if (d1.IsEqual(d2))
{
cout << "d1 == d2" << endl;
}
else
{
cout << "d1 != d2" << endl;
}
operator==(d1, d2);
if (d1 == d2) // 此处调用:operator==(d1,d2);
{
cout << "d1 == d2" << endl;
}
else
{
cout << "d1 != d2" << endl;
}
}
int main()
{
TestDate();
return 0;
}
十二、复数类
// 复数类
class Complex
{
public:
Complex(double real = 0.0, double image = 0.0)
{
_real = real;
_image = image;
}
/*
// error C2804: 二进制“operator *”的参数太多
// 成员函数有一个隐藏的this指针
Complex operator*(const Complex& left, const Complex& right)
{
return left;
}
*/
// 复数类在相乘时,人家有自己特定的规则,不是实部乘实部,虚部乘虚部
Complex operator*(const Complex& c)
{
return Complex();
}
double _real;
double _image;
};
Complex operator+(const Complex& left, const Complex& right)
{
//Complex temp(left._real+right._real, left._image + right._image);
//return temp;
return Complex(left._real + right._real, left._image + right._image);
}
// 3.每个运算符要复合其含义
Complex operator-(const Complex& left, const Complex& right)
{
//Complex temp(left._real+right._real, left._image + right._image);
//return temp;
return Complex(left._real + right._real, left._image + right._image);
}
int main()
{
Complex c1(1.0, 2.0);
Complex c2(3.0, 4.0);
Complex c;
c = c1 + c2;
//c = c2 - c1;
return 0;
}
十三、前置++、后置++重载
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;
}
// 运算符重载时一定要复合其含义
// d1 = d2 = d3;
// d1.operator=(d2.operator=(d3));
//
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
void PrintDate()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
// 比如:重载++
// ++是单目运算符--只需要一个操作数
// ret = ++d;结果也是日期
// 前置++
Date& operator++()
{
_day += 1;
return *this;
}
// 后置++
// ret = d++;
// 注意:为了让前置++和后置++同时存在,语法说:在重载后置++的时候,可以多增加一个int参数
// 目的让前置++和后置++形成重载
// 后置++需要返回+1之前的结果
Date operator++(int)
{
Date temp(*this);
_day += 1;
return temp;
}
Date& operator--()
{
_day -= 1;
return *this;
}
Date operator--(int)
{
Date temp(*this);
_day -= 1;
return temp;
}
private:
int _year;
int _month;
int _day;
};
// 注意:赋值运算符重载必须要重载成类的成员函数,否则编译失败
// 因为:赋值运算符重载是默认成员函数,如果用户没有显式实现时,则编译器会自动生成一份
// 如果自己再在类外实现一份 相当于赋值运算符重载存在两份
//void operator=(const Date& left, const Date& right)
//{}
void TestDate()
{
Date d1(2022, 4, 16);
Date d2(2022, 4, 17);
Date ret;
ret = ++d1;
ret.PrintDate(); //2022-4-17
ret = d1++;
ret.PrintDate(); //2022-4-17
}
int main()
{
TestDate();
Date d1(2022, 4, 16);
Date d2(2022, 4, 17);
d1 = d2;
d1.PrintDate();//2022-4-17
int a = 1;
int b = 2;
int c = 3;
a = b;
a = b = c; // =可以支持连续赋值
cout <<"a b c的值:" <<a << " " << b << " " << c << endl;
Date d3(2022,4,18);
d1 = d2;
d1.PrintDate(); //2022-4-17
d1.operator=(d2);
d1.PrintDate(); //2022-4-17
// d1 = d2 = d3; // 本质是:两次operator=函数的调用
// 先用d3给d2赋值 再用d2给d3赋值
// d1.operator=(d2.operator=(d3));
d1 = d1;
Date& d4 = d1;
//....
//...
d4 = d1;
return 0;
}
十四、对&进行重载(取地址同时将地址打印出来)
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date* operator&()
{
cout << this << endl;
_day += 1;
return this;
}
const Date* operator&()const //第一个const限制的是返回值 第二个是指成员变量不能被修改 用const修饰成员函数,称其为const成员函数,在该函数内部不能修改任何成员变量。
{
cout << this << endl;
return this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 4, 16);
// 需求:在对Date类对象取地址,顺便将该对象的地址打印出来
Date* pd = &d1;//在取地址瞬间也对变量做了修改,不安全
const Date d2(d1); //如果是const型对象,就不会对变量做出修改。编译器不会让const对象调用普通成员函数。
const Date* pd2 = &d2;//因为d2是const型,所以pd2也必须是const类型 让const类型对象取地址瞬间也打印地址
return 0;
}
十五、const修饰成员函数
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 该函数只需将对象中的成员变量打印出来就可以了
// 在运行的整个阶段不需要对成员变量进行修改
// const修饰该成员函数即可---const成员函数
void PrintDate()const
{
cout << _year << "-" << _month << "-" << _day << endl;
// _day += 1;
}
void SetYear(int year)
{
cout << typeid(this).name() << endl;
_year = year;
}
int GetYear()const
{
cout << typeid(this).name() << endl;
//_year += 1;
return _year;
}
// this的类型:Date* const
Date* operator&()
{
return this;
}
// this的类型:const Date* const
const Date* operator&()const
{
return this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d(2022, 4, 16);
d.PrintDate(); //2022-4-16
d.SetYear(2023);//class Date * __ptr64
cout << d.GetYear() << endl; //class Date const * __ptr64 2023
return 0;
}
十六、const成员函数和普通成员函数
- const类型对象,只能调用const成员函数,不能调用普通成员函数
- 普通对象,普通成员函数和const成员函数都可以调用
- 在普通成员函数中,既可以调用普通成员函数也可以调用const成员函数
- const成员函数本质就是在修饰this,表明this指向对象中成员是不能被修改的,所以const成员函数中不能调用普通成员函数
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 在该成员函数中对当前对象:可读可写
void func1()
{
_day += 1;
}
// 在该成员函数中对当前对象:只读
void func2()const
{
// _day += 1; // 编译报错
cout << _day << endl;
}
// 结论:普通成员函数中--既可以调用普通成员函数,也可以调用const成员函数
void func3()
{
_day += 1;
this->func1();
this->func2();
}
// 能否调用普通成员函数
void func4()const
{
// _day += 1; // 编译报错
/*const 修饰成员函数 本质就是在修饰this,表明this指向对象中成员不能修改的
如果编译器允许在const成员函数中调用func1的普通成员函数,那么func1执行完成之后
很可能会将this指向对象中成员修改掉---代码不安全,结论:
const成员函数中不能调用普通成员函数
*/
// this->func1();
this->func2(); // 本来就是const成员函数,即func2中一定不会对this指向的对象修改
}
// 需求:除了一个成员变量可以允许被修改,其余的都不能被修改
void func5()const
{
// _year = 1900;
// _month = 1;
_day = 1;
}
private:
int _year;
int _month;
mutable int _day; // mutable修饰成员变量意思:该成员变量可以在const中被修改
};
int main()
{
return 0;
}
十七、const
// 构造函数体,{}的内部,并不是对成员变量进行初化,而是对成员变量进行赋值
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
// 赋值可以赋值多次,但是初始化只能初始化一次
_day = day;
_day = day;
_day = day;
_day = day;
// _a = 10;
}
private:
int _year;
int _month;
int _day;
const int _a=10; //必须给个初值
};
int main()
{
// const修饰的是一个常量,而且在编译阶段会进行替换
// const修饰的常量在定义时必须要初始化
const int a =1;
return 0;
}
十八、初始化列表
// 那些成员必须在初始化列表的位置进行初始化?
class Time
{
public:
Time(int hour, int minute, int second)
: _hour(hour)
, _minute(minute)
, _second(second)
{
cout << "Time(int,int,int)" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
// 每个成员变量在初始化列表中只能出现一次
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
, _a(10)
, _rb(_day)
, _t(16,15,30)
{
cout << "Date(int,int,int)" << endl;
}
private:
int _year;
int _month;
int _day;
const int _a; // const类型的成员变量
int& _rb; // 引用类型的成员变量
Time _t; // 类类型对象,并且该类的构造函数不是默认的构造函数时候
};
十九、初始化列表 Date Time
Time必须有默认构造函数(全缺省参数也算),Date的初始化列表才不用对Time类对象进行初始化。
class Time
{
public:
Time(int hour = 0, int minute = 0, int second = 0)
: _hour(hour)
, _minute(minute)
, _second(second)
{
cout << "Time(int,int,int)" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
// 每个成员变量在初始化列表中只能出现一次
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{
cout << "Date(int,int,int)" << endl;
}
private:
int _year;
int _month;
int _day;
Time _t; // 类类型对象,并且该类的构造函数不是默认的构造函数时候
};
int main()
{
const int a = 10;
int b = 10;
int& rb = b;
Date d(2022, 4, 16);
return 0;
}
二十、初始化列表
初始化列表中:真正的初始化次序与成员变量在类中声明的次序一致,与成员变量在初始化列表中
出现的先后次序无关
class Date
{
public:
// Date d(2022, 4, 10);
// 初始化列表中:真正的初始化次序与成员变量在类中声明的次序一致,与成员变量在初始化列表中
// 出现的先后次序无关
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _day(day)
, _month(_day)
{
cout << "Date(int,int,int)" << endl;
}
Date(const Date& d)
: _year(d._year)
, _month(d._month)
, _day(d._day)
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d(2022, 4, 10);
return 0;
}
二十一、explicit
在构造函数前加上explicit会抑制数字向对象的隐式转换。代码就不会通过编译。
// 单参构造函数具有类型转换的作用
class Date
{
public:
// 单参构造函数
explicit Date(int year, int month = 1)
: _year(year)
, _month(month)
, _day(1)
{
cout << "Date(int,int,int):"<<this << endl;
}
Date& operator=(const Date& d)
{
cout << this << "=" << &d << endl;
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
~Date()
{
cout << this << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
int a = 10;
int b = 20;
a = b; // 逻辑才是正确的,相同类型的变量相互赋值
double d = 12.34;
a = d; // 类型不同,但是int和double之间本来就可以进行隐式类型转换
cout << a << endl;
Date dd(2023);
/*
1. 编译器先使用2022调用单参构造函数创建一个临时对象
2. 使用临时对象给dd进行赋值
3. 赋值完之后,将临时对象销毁掉
*/
dd = 2022; // d是日期类型的对象,2022是一个整形数字,天哪整形数字竟然还可以给日期对象赋值?
return 0;
}
去掉explicit之后:
// 单参构造函数具有类型转换的作用
class Date
{
public:
// 单参构造函数
Date(int year, int month = 1)
: _year(year)
, _month(month)
, _day(1)
{
cout << "Date(int,int,int):"<<this << endl;
}
Date& operator=(const Date& d)
{
cout << this << "=" << &d << endl;
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
~Date()
{
cout << this << endl;
}
void printf()
{
cout << _year << " " << _month << " " << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
int a = 10;
int b = 20;
a = b; // 逻辑才是正确的,相同类型的变量相互赋值
double d = 12.34;
a = d; // 类型不同,但是int和double之间本来就可以进行隐式类型转换
cout << a << endl;
Date dd(2023);
/*
1. 编译器先使用2022调用单参构造函数创建一个临时对象
2. 使用临时对象给dd进行赋值
3. 赋值完之后,将临时对象销毁掉
*/
dd = 2022; // d是日期类型的对象,2022是一个整形数字,天哪整形数字竟然还可以给日期对象赋值?
dd.printf();
return 0;
}
二十二、static
静态成员变量在类中只是声明,需要在类外重新进行定义,在类外定义时不加static,但要加上类名。eg:int Student::a=0;
class Sum
{
public:
Sum()
{
++count;
sum += count;
}
static int GetSum()
{
return sum;
}
static void Reset()
{
count = 0;
sum = 0;
}
private:
static int count;
static int sum;
};
int Sum::count = 0;
int Sum::sum = 0;
class Solution {
public:
int Sum_Solution(int n) {
Sum::Reset();
// 在堆上创建了n个对象
Sum* pt = new Sum[n];
delete[] pt;
return Sum::GetSum();
}
};
int main()
{
Solution s;
cout << s.Sum_Solution(5) << endl; // Sum::count=5 Sum::sum=15
cout << s.Sum_Solution(5) << endl; // 15 注意函数里调用了Reset,即原先的1+2+3+4+5已经被清零。如果不调用Reset,该函数输出55(15+6+7+8+9+10)
return 0;
}
二十三、<<运算符重载
声明其为友元函数,在类外可以访问该类的私有成员变量。
class Date
{
private:
friend ostream& operator<<(ostream& _cout, const Date& d);
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{
// 注意:构造日期是否合法
if (!(_year > 0 &&
(_month >= 1 && _month <= 12) &&
(_day >= 1 && _day <= GetDaysOfMonth(_year, _month))))
{
_year = 1900;
_month = 1;
_day = 1;
}
}
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;
}
~Date()
{}
// 两个日期对象可以相减---求两个日期之间相差了多少天
// a - b
// Date d1(2022,4,19);
// Date d2(2022,5,1);
// d1 - d2 = 5 d2 - d1 = 5
int operator-(const Date& d)const
{
Date minDate(*this);
Date maxDate(d);
if (minDate > maxDate)
{
minDate = d;
maxDate = *this;
}
int count = 0;
while (minDate != maxDate)
{
++minDate;
++count;
}
return count;
}
bool operator==(const Date& d)const
{
return _year == d._year &&
_month == d._month &&
_day == d._day;
}
bool operator!=(const Date& d)const
{
return !(*this == d);
}
bool operator>(const Date& d)const
{
if ((_year > d._year) ||
(_year == d._year && _month > d._month) ||
(_year == d._year && _month == d._month && _day > d._day))
{
return true;
}
return false;
}
bool operator<(const Date& d)const
{
return !(*this == d || *this > d);
}
// 100天之后是哪一天? d1+100
// Date d1(2022,4,19);
// d1+5===>2022,4,24
// d1+100===>?
// a+b a+=b
Date operator+(int days)
{
if (days < 0)
{
return *this - (0 - days);
}
Date temp(*this);
temp._day += days;
int daysOfMonth = 0;
// 检测temp中的天数是否合法
while (temp._day >(daysOfMonth = GetDaysOfMonth(temp._year, temp._month)))
{
temp._day -= daysOfMonth;
temp._month++;
if (13 == temp._month)
{
temp._year += 1;
temp._month = 1;
}
}
return temp;
}
// 100天以前是哪一天? d1-100
Date operator-(int days)
{
if (days < 0)
{
return *this + (0 - days);
}
Date temp(*this);
temp._day -= days;
while (temp._day <= 0)
{
temp._month -= 1;
if (0 == temp._month)
{
temp._year -= 1;
temp._month = 12;
}
temp._day += GetDaysOfMonth(temp._year, temp._month);
}
return temp;
}
// 电子日历日期会自动加
// 前置++
Date& operator++()
{
*this = *this + 1;
return *this;
}
Date operator++(int)
{
Date temp(*this);
*this = *this + 1;
return temp;
}
// 倒计时
Date& operator--()
{
*this = *this - 1;
return *this;
}
Date operator--(int)
{
Date temp(*this);
*this = *this - 1;
return temp;
}
bool IsLeapYear()const
{
return IsLeapYear(_year);
}
void PrintDate()const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
#if 0
// 可以对流插入运算符进行重载
// <<有两个操作数:1. 输出流对象 2. 要打印的内容
void operator<<(/*Date* cont this,*/ostream& _cout)
{
_cout << _year << "-" << _month << "-" << _day;
}
// d << cout; cout << d
#endif
private:
int GetDaysOfMonth(int year, int month)const
{
int days[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (2 == month && IsLeapYear(year))
{
days[2] += 1;
}
return days[month];
}
bool IsLeapYear(int year)const
{
if ((0 == year % 4 && 0 != year % 100) ||
(0 == year % 400))
{
return true;
}
return false;
}
int GetYear()const
{
return _year;
}
int GetMonth()const
{
return _month;
}
int GetDay()const
{
return _day;
}
//public:
private:
int _year;
int _month;
int _day;
};
// 在<<重载的时候,最好不要加格式控制,比如换行
ostream& operator<<(ostream& _cout, const Date& d)
{
//_cout << d.GetYear() << "-" << d.GetMonth() << "-" << d.GetDay();
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
int main()
{
Date d(2022, 4, 19);
Date d1(2022, 4, 19);
d.PrintDate();
// cout << d; // 编译失败
//d << cout;
//d.operator<<(cout);
// cout << d << endl;
cout << 123 << " " << 12.34 << endl;
cout << d;
operator<<(cout, d);
cout << d << d1;
operator<<(operator<<(cout, d), d1);
// cout << d << endl;
cout << "hello world!!!";
cout << "hello bit" << endl;
return 0;
}
二十四、friend
友元函数位置不影响,即不受访问权限服的限制
另外,感觉是标准写法
class A
{
friend void TestFriend();
public:
int GetA()const
{
return _a;
}
private:
int _a;
};
class B
{
public:
int GetB()const
{
return _b;
}
private:
int _b;
friend void TestFriend();
};
void TestFriend()
{
A aa;
aa._a = 10;
B bb;
bb._b = 20;
cout << aa.GetA() << endl; //10
cout << bb.GetB() << endl; //20
}
int main()
{
TestFriend();
}
二十五、Date,Time类
class Time
{
friend class Date;
public:
Time(int hour, int minute, int second)
: _hour(hour)
, _minute(minute)
, _second(second)
{}
private:
void f()const
{
//Date d(2022,4,23);
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
, _t(0,0,0)
{}
void PrintDate()const
{
cout << _year << "-" << _month << "-" << _day << endl;
cout << _t._hour << ":" << _t._minute << ":" << _t._second << endl;
_t.f();
}
private:
int _year;
int _month;
int _day;
Time _t;
};
int main()
{
Date d(2022, 10, 25);
d.PrintDate();
}