目录
日期类的前置--和后置--
前置--
日期类相减:
cin和cout
编辑
流插入:
友元声明:
流提取:
const成员
取地址重载:
const修饰的取地址重载:
初始化列表
为什么要使用初始化列表:
引用也必须在初始化列表初始化
初始化顺序
Date& Date::operator--()
{
*this -= 1;
return *this;
}
后置--:
Date Date::operator--(int)
{
Date ret = *this;
*this -= 1;
return ret;
}
#include"Date.h"
void TestDate1()
{
//Date d1(2022, 10, 19);
///*
//(d1 + (-10)).Print();*/
//(++d1).Print();
//(d1++).Print();
Date d1(2022, 10, 19);
(--d1).Print();
(d1--).Print();
}
int main()
{
TestDate1();
return 0;
}
对于自定义对象,最好用前置:
1:前置是返回--之后的值
2:前置的传引用返回。
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)
{
++n;
++min;
}
return n*flag;
}
#include"Date.h"
void TestDate1()
{
//Date d1(2022, 10, 19);
///*
//(d1 + (-10)).Print();*/
//(++d1).Print();
//(d1++).Print();
Date d1(2022, 10, 19);
/*(--d1).Print();
(d1--).Print();*/
Date d2(2023, 11, 23);
cout << d1 - d2 << endl;
}
int main()
{
TestDate1();
return 0;
}
cin是istream类型的对象 ,cout是ostream类型的对象。
istream是一个类,ostream也是一个类。
为什么cin和cout可以自动识别参数的类型:
cout是流插入,表示把d1的数据插入到cout对象里去。
cin是流提取,表示把输入的数据提取到d1里去。
因为库中有这些内置类型的函数重载。
cout是ostream类型的对象,相当于cout符号重载<<。
假如我们要在类里面定义流插入函数:
这种写法是错误的:首先在类内定义的函数,this指针会默认抢占第一个参数的位置,并且对于<<,有两个操作数,也就是有两个函数参数,一个是Date类型的对象,一个是ostream类型的对象。
我们需要这样写:
void operator<<(ostream&out)
{
out << _year << " " << _month << " " << _day;
}
void TestDate1()
{
Date d1, d2;
/*cin >> d1;
cout << d2;*/
d1 << cout;
}
int main()
{
TestDate1();
return 0;
}
但是我们发现只能这样写:
这样就没有可读性了,原因在哪里?
对于双操作数的符号,第一个参数参数就是左操作数,而对于成员函数,第一个位置被this指针抢占,所以我们在类内无法实现cout。
我们只好写成全局函数:
void operator<<(ostream&out, const Date&d)
{
out << d._year << " " << d._month << " " << d._day;
}
但是在全局的函数无法访问类内私有的成员变量。
我们可以先把成员变量设置为共有的。
报错的原因是:我们在头文件中定义了全局函数,头文件在两个源文件变为目标文件时进行展开
两个目标文件分别有两个同名函数,两个符号表也有两个同名函数,所以重定义问题。
不仅仅是我们运算符重载出现的问题,我们在头文件中定义全局函数就容易产生问题:
void Print(const Date&d)
{
cout << d._year << "/" <
注意:在头文件中尽量不要定义全局变量和全局函数,否则会导致重定义的问题。
我们如何解决这个问题:
静态函数:
static修饰全局变量和全局函数时会改变其链接属性,使其只在当前文件中可见,其他文件不可见,也无法使用。
声明和定义分离:
我们在cpp文件中进行定义,在.h文件中进行声明。
头文件Date.h会在两个cpp文件中展开,但是Date.h的文件中只有声明没有定义,所以无法进入符号表。
void TestDate1()
{
Date d1, d2;
/*cin >> d1;
cout << d2;*/
cout << d1;
}
int main()
{
TestDate1();
return 0;
}
需要优化的问题:
无法像cout调用内置类型一样连续调用。
从左往右进行调用,cout< 我们还可以用内联函数解决: 注意:内联函数直接定义在头文件中即可,不能声明和定义分离: 内联的原理:内联会在调用时直接展开,不会进入符号表。 但是以上的方法都是在我们的成员变量为公有的前提下,假如成员变量为私有呢? 第一种方法:创建函数取成员变量。 我们在类中进行声明,表示我们这个运算符重载函数是一个友元函数,友元函数可以直接访问成员变量。 对于流提取函数,我们也要进行友元声明。 我们发现,用const修饰的对象无法调用Print函数: 原因如下: 在我们的Print函数中,有一个隐藏的this指针,该指针的类型是Date*,const用来修饰this,防止对this本身进行修改。 而const修饰的是d2,我们传参的时候传递的也是d2,d2用const修饰是只读的,而对于this,我们却可以访问this指向的数据。 更简单的理解: 把d2的地址传递给this,发生了权限的扩大。 我们如何避免这种问题呢? 我们可以后置const 后置const表示把this的类型由Date*this修改为const Date*this的形式。 并且对于普通的对象也可以调用print函数,因为权限的缩小是不受限制的。 我们再举一个例子: 同样属于权限的放大,我们后置const。 凡是函数内部没有改变成员变量的,这些成员函数的声明和定义都要加上const修饰。 这里的取地址相当于调用取地址重载函数: 对于const类型的对象,我们调用该函数。 这两个函数,我们即使不写,编译器也会自动生成: 我们注释掉取地址重载函数进行运行: 我们如何使用取地址重载呢? 假如我们规定,在日期类中不能使用取地址,我们可以这样写: 我们可以在构造函数中使用初始化列表,构造函数可以什么都不用写(针对的是日期类) 写一个栈类的初始化列表: 也可以这样写: 所以初始化列表和函数体内初始化可以混着一起使用。 _n是cons类型的对象,我们知道const类型的对象只能在定义的时候初始化,我们的构造函数是无法对_n进行修改的。 const修饰的对象定义的时候必须初始化。 在这里是_n的声明,创建对象时才会定义。 这里是定义,是整体定义,我们无法对类内的一些const修饰的对象定义。 对象的每一个成员变量是在初始化列表时定义的。 我们可以用初始化列表来定义const修饰的成员变量: 每一个成员变量都会走初始化列表,就算没有显示,也会走。 这里的_m也会走初始化列表: 如果我们没有在初始化列表中显示的写,对于内置类型,默认是随机值,对于自定义类型调用其默认构造。 对于这个随机值,c++打了一个补丁: 我们可以在成员变量的声明位置给缺省值。 这个缺省值是在初始化列表中使用的,假如我们的初始化列表没有显示写成员变量,就用缺省值。 总结: _a走初始化列表时,_a是自定义类型,对于自定义类型调用默认构造,因为A类没有默认构造,所以报错。 有两种方法解决: 1:注释掉A的构造函数,使用系统给的默认构造: 这时候的_aa是随机值。 方法2: 我们可以在初始化列表给_a一个值,相当于调用_a的构造函数。 再举一个例子: 这样写不会报错:对于自定义类型Stack,调用其默认构造,对于内置类型我们有缺省值,就给缺省值0. 当我们把栈的函数写成是非默认构造就会报错。 初始化顺序是按照成员变量的声明顺序的: 先对_a2进行定义,_a1还不存在,所以报错原因是编译不通过。ostream& operator<<(ostream&out, const Date&d)
{
out << d._year << " " << d._month << " " << d._day;
return out;
}
void TestDate1()
{
Date d1, d2;
/*cin >> d1;
cout << d2;*/
cout << d1 << d2 << endl;
}
int main()
{
TestDate1();
return 0;
}
inline ostream& operator<<(ostream&out, const Date&d)
{
out << d._year << " " << d._month << " " << d._day;
return out;
}
int GetYear() const
{
return _year;
}
int GetMonth() const
{
return _year;
}
int GetDay() const
{
return _year;
}
inline ostream& operator<<(ostream&out, const Date&d)
{
out <
友元声明:
friend ostream& operator<<(ostream&out, const Date&d);
流提取:
inline istream& operator>>(istream& in, Date&d)
{
in >> d._year >> d._month >> d._day;
return in;
}
const成员
void TestDate2()
{
const Date d1(2022, 9, 10);
d1.Print();
}
int main()
{
TestDate2();
return 0;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
void TestDate2()
{
const Date d1(2022, 9, 10);
d1.Print();
Date d2(2022, 9, 11);
d2.Print();
}
int main()
{
TestDate2();
return 0;
}
取地址重载:
Date*operator&()
{
return this;
}
void TestDate2()
{
const Date d1(2022, 9, 10);
d1.Print();
Date d2(2022, 9, 11);
d2.Print();
cout << &d2 << endl;
}
int main()
{
TestDate2();
return 0;
}
const修饰的取地址重载:
const Date*operator&() const
{
return this;
}
void TestDate2()
{
const Date d1(2022, 9, 10);
d1.Print();
Date d2(2022, 9, 11);
d2.Print();
cout << &d2 << endl;
cout << &d1 << endl;
}
int main()
{
TestDate2();
return 0;
}
void TestDate2()
{
const Date d1(2022, 9, 10);
d1.Print();
Date d2(2022, 9, 11);
d2.Print();
cout << &d2 << endl;
cout << &d1 << endl;
}
int main()
{
TestDate2();
return 0;
}
Date*operator&()
{
return nullptr;
}
const Date*operator&() const
{
return nullptr;
}
初始化列表
Stack(int capacity=4)
:_a(((int*)malloc(sizeof(int)*_capacity)))
, _capacity(capacity)
, _top(0)
{
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
memset(_a, 0, sizeof(int)*capacity);
}
Stack(int capacity=4)
: _capacity(capacity)
, _top(0)
{
_a = ((int*)malloc(sizeof(int)*_capacity));
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
memset(_a, 0, sizeof(int)*capacity);
}
为什么要使用初始化列表:
class B
{
public:
B()
{
_n = 10;
}
private:
const int _n;
};
int main()
{
B b;
return 0;
}
B()
:_n(10)
{
;
}
class B
{
public:
B()
:_n(10)
{
;
}
private:
const int _n;
int _m;
};
class A
{
public:
A(int a)
{
;
}
private:
int _aa;
};
class B
{
public:
B()
:_n(10)
, _m(2)
{
;
}
private:
const int _n;
int _m=1;
A _a;
};
int main()
{
B b;
return 0;
}
class A
{
public:
A(int a)
{
;
}
private:
int _aa;
};
class B
{
public:
B()
:_n(10)
, _m(2)
, _a(11)
{
;
}
private:
const int _n;
int _m=1;
A _a;
};
int main()
{
B b;
return 0;
}
class MyQueue
{
public:
MyQueue()
{
}
void push(int x)
{
_pushST.Push(x);
}
private:
Stack _pushST;
Stack _popST;
size_t _size = 0;
};
int main()
{
MyQueue m1;
return 0;
}
引用也必须在初始化列表初始化
class B
{
public:
B()
:_n(10)
, _m(2)
, b(_m)
{
;
}
private:
const int _n;
int _m=1;
int&b;
};
//class MyQueue
//{
//public:
// MyQueue()
// {
// }
// void push(int x)
// {
// _pushST.Push(x);
// }
//private:
// Stack _pushST;
// Stack _popST;
// size_t _size = 0;
//};
int main()
{
//MyQueue m1;
B b;
return 0;
}
初始化顺序
class A
{
public:
A(int a)
:_a1(a)
, _a2(_a1)
{
}
void Print()
{
cout << a1 << "" << a2 << endl;
}
private:
int _a2;
int _a1;
};
int main()
{
A aa(1);
aa.Print();
}