运算符的重载(2)
5.左移(输出)运算符重载
如我们所知,IO标准库分别使用>>
和<<
执行输入输出操作。对于这两个运算符来说,IO库定义了其用读写内置类型的版本,但是类则需自定义适合其对象的新版本支持IO操作。
通过情况下左移运算符的第一形参是一个非常量ostream的引用。而ostream是非常量的原因是向流写入内容会改变其状态。而该形参是引用,是因为我们无法直接复制一个ostream对象。
第二个形参一般来说是一个常量的引用,该常量是我们想要打印的类类型。第二个形参是引用的原因是我们希望避免复制实参,而形参为常量是因为打印对象不会改变对象的内容。
举例如下:
#include <iostream>
#include<string>
using namespace std;
class Person
{
friend ostream &operator<<(ostream &cout, const Person &p);
public:
Person(string name, int age);
private:
string m_name;
int m_age;
};
Person::Person(string name, int age)
{
this->m_name = name;
this->m_age = age;
}
ostream &operator<<(ostream &cout, const Person &p)
{
cout << p.m_name << "的年龄为:" << p.m_age << endl;
return cout;
}
void test()
{
Person p("Aoki", 20);
cout << p;
cout << "Hello world" << endl;
}
int main()
{
test();
system("pause");
return EXIT_SUCCESS;
}
输出结果如下:
6.右移(输入)运算符重载
通常情况下,输入运算符的第一个形参是运算符简要读取的流的引用,第二个形参是将要读入的非常量对象的引用。该运算符通常会返回某个给定流的引用。第二个形参之所以必须是个非常量是因为输入运算符本身的目的就是将数据读入到这个对象中。
#include <iostream>
#include<string>
using namespace std;
class Person
{
friend istream &operator>>(istream &cin, Person &p);
public:
Person (){}
Person(string name, int age);
void Show();
private:
string m_name;
int m_age;
};
Person::Person(string name, int age)
{
this->m_name = name;
this->m_age = age;
}
istream & operator>>(istream & cin, Person & p)
{
string name;
int age;
cin >> name >> age;
if (cin)
{
p.m_name = name;
p.m_age = age;
}
else
{
p = Person();//如果输入失败,对象被赋予默认状态
}
return cin;
}
void Person::Show()
{
cout << this->m_name << "的年龄是:" << this->m_age << endl;
}
void test()
{
Person p1;
cin >> p1;
p1.Show();
}
int main()
{
test();
system("pause");
return EXIT_SUCCESS;
}
输出结果如下:
可以注意到,在输入运算符重载中,有输入失败的处理。那么输入时可能会发生什么错误呢?
在程序中我们没有逐个检查每个读取操作,而是等读取了所有数据之后赶在使用这些数据前面进行了一次性的检查。
如果在发生错误之前对象已经有一部分改变,则适时地将对象置为合法状态显得异常重要。
通过将对象置为合法状态,我们能保护使用者免受到输入错误的影响。此时的对象处于可用状态,即它的成员都是被正确定义的。而且该对象也不会产生误导性结果,因为它的数据本质上是一致的。
7.指针运算符重载
```c++
#include
class Person { public: Person(string name, int age); void Display();
private: string m_name; int m_age; };
class SmartPointer { public: SmartPointer(Person person); Person operator->(); Person& operator*(); ~SmartPointer(); public: Person *pPerson; };
运算符的重载(1)
#写在前面
运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
运算符重载(operator overloading)只是一种“语法上的方便”,也就它只是另一种函数调用的方式。
在C++中,可以定义一个处理类的新运算符。这种定义很像一个普通的函数定义,只是函数的名字由关键字operator
及其紧跟的运
算符组成。差别仅此而已。它像任何其他函数一样也是一个函数,当编译器遇到适当的模式时,就会调用这个函数。
重载运算符的参数数量与该运算符作用的对象数量一样多。一元运算符有一个参数,二元运算符有两个参数。对于二元运算符来说,左侧运算符对象传递给第一个参数,而右侧运算符对象传递给第二个参数。除了重载的函数调用运算符operator()
之外,其他重载运算符不能含有默认实参。
如果一个重载运算符函数时成员函数,则它的第一个(左侧)运算符对象绑定到隐式地this指针上,因此,成员运算符函数的(显式)参数数量比运算符的运算对象总数少一个。
#1.关系运算符重载
#include <iostream>
#include<string>
using namespace std;
class Person
{
public:
Person(string name, int age);
bool operator==(Person &p);//==运算符重载
bool operator!=(Person &p);//!=运算符重载
private:
string m_Name;
int m_Age;
};
Person::Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
}
bool Person::operator==(Person & p)
{
if (this->m_Name == p.m_Name&&this->m_Age == p.m_Age)
{
return true;
}
return false;
}
bool Person::operator!=(Person & p)
{
if (this->m_Name == p.m_Name&&this->m_Age == p.m_Age)
{
return false;
}
return true;
}
void test()
{
Person p1("Aoki", 22);
Person p2("Aoki", 22);
Person p3("Aoki", 20);
if (p1 == p2)
{
cout << "p1和p2相等" << endl;
}
else
{
cout << "p1和p2不相等" << endl;
}
if (p1 != p3)
{
cout << "p1和p3不相等" << endl;
}
else
{
cout << "p1和p3相等" << endl;
}
}
int main()
{
test();
}
输出结果如下:
#2.自增自减运算符重载
友元
写在前面
类的主要特点之一就是数据隐藏,即类的私有成员无法在类的外部(作用域之外)访问。但是,有时候需要在类的外部访问类的私有数据成员,怎么办呢?
解决办法是使用友元函数,友元函数时一种特权函数c,C++允许这个特权函数访问私有成员。这一点我们可以用生活中的例子来看:
比如你的家有客厅,有卧室,客厅是public的,所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去,但是,你可以允许你的好朋友进去。
程序员可以把一个全局函数、某个类中的成员函数、甚至整个类声明为友元。
1.全局函数做友元函数
友元语法
friend
关键字只出现在声明处#include <iostream>
#include<string>
using namespace std;
class Home
{
public:
friend void test(Home *home);
Home()
{
this->bedroom = "卧室";
this->sittingroom = "客厅";
}
string sittingroom;//客厅
private:
string bedroom;//卧室
};
void test(Home *home)
{
cout << "你的好友正在访问" << home->sittingroom << endl;
cout << "你的好友正在访问" << home->bedroom << endl;
}
int main()
{
Home *home=new Home;
test(home);
}
输出结果如下:
从输出结果可以看出,当全局函数作为声明为友元函数之后,全局函数也可以访问类的私有成员。
2.整个类做友元类
#include <iostream>
#include<string>
using namespace std;
class Home
{
public:
friend class Good_Friend;
Home()
{
this->bedroom = "卧室";
this->sittingroom = "客厅";
}
string sittingroom;//客厅
private:
string bedroom;//卧室
};
class Good_Friend
{
public:
Good_Friend(string name)
{
this->name = name;
}
void visit()
{
cout << "好友"<<this->name<<"正在拜访" << this->home.sittingroom << endl;
cout << "好友"<<this->name<<"正在拜访" << this->home.bedroom << endl;
}
private:
Home home;
string name;
};
void test()
{
Good_Friend frend("Aoki");
frend.visit();
}
int main()
{
test();
}
输出结果如下:
将好友类声明为友元类之后,好友类的对象可以访问Home类的私有成员。
在友元类中我们应当注意:
C++是纯面向对象的吗?
如果一个类被声明为friend ,意味着它不是这个类的成员函数,却可以是修改这个类的私有成员,而且必须列在类的定义中,因此它是一个特权函数。C++不是完全的面向对象语言,而是一个混合产品。增加friend关键字只是用来解决一些实际问题,这也说明这种语言是不纯的。毕竟C++设计的目的是为了实用性,而不是追求理想的抽象。
-———《Think in C++》
</br>
3.成员函数做友元函数
#include <iostream>
#include<string>
using namespace std;
class Home;
class Good_Friend
{
public:
Good_Friend(string name);
void visit();
void visit2();
private:
Home *home;
string name;
};
class Home
{
friend void Good_Friend::visit();
public:
Home();
public:
string sittingroom;
private:
string bedroom;
};
Good_Friend::Good_Friend(string name)
{
home = new Home;
this->name = name;
}
void Good_Friend::visit()
{
cout << "你的好友" << this->name << "正在访问" << this->home->sittingroom << endl;
cout << "你的好友" << this->name << "正在访问" << this->home->bedroom << endl;
}
void Good_Friend::visit2()
{
cout << "你的好友"<<this->name<<"正在访问" << this->home->sittingroom << endl;
//cout << "你的好友" << this->name << "正在访问" << this->home->bedroom << endl;
}
Home::Home()
{
this->sittingroom = "客厅";
this->bedroom = "卧室";
}
void test()
{
Good_Friend frien("Aoki");
frien.visit();
frien.visit2();
}
int main()
{
test();
return 0;
}
&emsp当我们试图使用一个没有声明为Home类友元的成员函数去访问Home的私有成员时,编译器报错如下:
正确是输出结果如下:
虽然我现在将visit()
函数定义为Home类的友元,但是事情并没有这么简单地结束,因为当我第一次定义友元函数时,编译器莫名其妙地报错。那么,编译器报错的原因是什么呢?
当我尝试着把成员函数的声明与定义分开的时候,编译器显示没有错误。这也就告诉我们,在声明成员函数的时候,将声明与定义分开放,编译器会更好地处理。
C++对象模型初探
1.成员变量和函数的存储
在C语言中,分开来声明,也就说,语言本身并没有支持“数据”和“函数”之间的关联性,我们把这种程序方法称为“程序性的”,由一组“分布在各个以功能为导向的函数中的算法驱动,它们处理的是共同的外部数据。
C++实现了“封装”,那么数据(成员属性)和操作(成员函数)是什么样的呢?
“数据”和“处理数据的操作(函数)”是分开存储的。
#include <iostream>
using namespace std;
class Myclass
{
public:
int m_A;
};
class Myclass1
{
public:
int m_A;
static int m_B;
};
class Myclass2
{
public:
void test01()
{
cout << "001" << endl;
}
public:
static int m_A;
};
class Myclass3
{
public:
static void test02()
{
cout << "002" << endl;
}
public:
int m_A;
static int m_B;
};
class Myclass4
{
public:
void test03()
{
cout << "003" << endl;
}
static void test04()
{
cout << "004" << endl;
}
public:
int m_A;
static int m_B;
};
int main()
{
Myclass myclass;
Myclass1 myclass1;
Myclass2 myclass2;
Myclass3 myclass3;
Myclass4 myclass4;
cout << "size of myclass:" << sizeof(myclass) << endl;
cout << "size of myclass1:" << sizeof(myclass1) << endl;
cout << "size of myclass2:" << sizeof(myclass2) << endl;
cout << "size of myclass3:" << sizeof(myclass3) << endl;
cout << "size of myclass4:" << sizeof(myclass4) << endl;
return 0;
}
输出结果如下:
从输出结果我们可以看出,C++中成员变量和成员属性是分开存储的。 而且,只有非静态成员才属于对象身上。
2.this指针
通过上面的例子我们知道,C++的数据和操作时分开存储的,并且每个非内联成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码。
那么问题是,这块代码是如何区分是哪个对象调用自己的呢?
C++通过提供特殊的对象指针,this指针,来解决上述问题。this指针指向被调用的成员函数所属的对象。
C++规定,this指针是隐含在对象成员函数内的一种指针。当一个对象被创建后,它的每一个成员函数都含有一个系统自动生成的隐含指针this,用以保存这个对象的地址。也就是说,虽然我们没有写上this指针,编译器在编译的时候也会加上的。因此,this指针也被称为“指向本对象的指针”,this指针不并不是对象的一部分,并不会影响sizeof(对象)对的结果。
this指针是C++实现封装的一种机制,它将对象和该对象调用的非成员函数连接在一起,在外部看来,每个对象都拥有自己的成员函数。一般情况下,并不写this,而是让系统进行默认设置。
this指针永远指向当前对象。 成员函数通过this指针即可知道操作的是哪个对象的数据。this指针是一种隐含指针,它隐含于每个类的非静态成员函数中,this指针无须定义,直接使用即可。
静态成员函数内部没有this指针,静态成员函数不能操作非静态成员变量。
3.this指针的使用
#include <iostream>
#include<string>
using namespace std;
class Person
{
public:
Person(string name, int age)
{//当形参与成员变量名相同时,可以使用this指针来区分
this->age = age;
this->name = name;
}
Person Person_Plus(Person &person)
{
string newname = this->name + person.name;
int newage = this->age + person.age;
Person newperson(newname, newage);
return newperson;
}
void show()
{
cout << "Name:" << name << ",Age:" << age << endl;
}
public:
int age;
string name;
};
void test()
{
Person person("Aoki", 20);
person.show();
Person p1("Aoki", 20);
Person p2("青木", 15);
Person p3 = p1.Person_Plus(p2);
p3.show();
}
int main()
{
test();
}
输出结果如下:
4.const修饰成员函数(常函数)
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
this->m_A = 0;
this->m_B = 0;
}
void showinfor () const
{
//this->m_B = 10;
this->m_A=100;
cout << "m_A=" <<m_A<< endl;
cout << "m_B=" << m_B<<endl;
}
mutable int m_A;//如果需要使用常函数修改变量,加mutable关键字
int m_B;
};
void test()
{
Person p1;
p1.showinfor();
}
int main()
{
test();
}
当我们试图使用常函数对成员变量进行修改时,编译器会报错。报错结果如下图:
由此可知,常函数不能修改this指针指向的值。
那么,当我们需要使用常函数对成员变量进行修改时呢?那么,我们可以在变量前面加mutable关键字。输出结果如下:
5.const修饰对象(常对象)
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
this->m_A = 0;
this->m_B = 0;
}
void showinfor () const
{
//this->m_B = 10;
this->m_A = 100;
cout << "m_A=" <<m_A<< endl;
cout << "m_B=" << m_B<<endl;
}
void show()
{
cout << "m_A=" << this->m_A << endl;
cout << "m_B=" << this->m_B << endl;
}
mutable int m_A;//如果需要使用常函数修改变量,加mutable关键字
int m_B;
};
void test()
{
const Person p1;
Person p2;
p2.show();
p2.showinfor();
p1.showinfor();
//p1.m_B = 100;
p1.m_A = 20;
//p1.show();
cout << "m_A=" << p1.m_A << endl;
cout << "m_B=" << p1.m_B << endl;
}
int main()
{
test();
}
当我试图使用常对象来对成员变量进行修改时,编译器报错如下:
常对象不能修改没有关键字mutable修饰的成员变量,也就是说,如果一个变量被关键字mutable修改时,那么常对象可以对其进行修改。
当我试图使用常对象来访问普通成员函数时,编译器报错如下:
也就说,常对象不能调用普通成员函数,它只能调用常函数。
最终输出结果如下所示:
5.常函数与常对象的总结