第十二周实验
第一题必做,第二题选做。
一:
定义一个Point(2维点)类,数据成员为坐标X、Y,重载一元操作符“++”和 “--”(包括前缀和后缀),使之具有坐标X、Y都加一、减一的功能;重载“>>”和“<<”运算符实现Point对象的输出和输入。输出形式为:(x,y)。可参考教材例子程序。
模板的实验(链表属于数据结构,还没学过,所以省略。)
二:
定义单向链表类模板,成员包括:链表的头指针;在链表头插入一个结点、从链表尾删除一个结点、显示链表各元素。
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
Number1:(错误版的,当时一脸茫然,一脸懵逼。)
#include<iostream>
using namespace std;
class Point
{
protected:
int X,Y;
public:
Point(int a,int b):X(a),Y(b){}
SetX(int a){X=a;}
SetY(int b){Y=b;}
GetX(){return X;}
GetY(){return Y;}
Point operator++(const Point &obj1) //重载后缀++,先取值,后自加
{ Point obj2;
obj2.X = obj1.X;//创建一个临时对象,原来保存对象原来的值
obj2.Y = obj1.Y;
if(obj1.X<65635) obj1.X++;//此处++为系统默认含义
else obj1.X = 0;//超过65535则置0 /////65635至今也搞不懂啥意思,领悟不到他的深意,待之后领悟之后再更新
if(obj1.Y<65635) obj1.Y++;
else obj1.Y = 0;
return obj2;
//返回对象原值,也就是修改前的值
}
Point operator++(const Point &obj3)//重载前缀++,先自加,后取值
{
if(obj3.X<65635) obj3.X++;//此处++为系统默认含义
else obj3.X = 0;//超过65535则置0
if(obj3.Y<65635) obj3.Y++;
return obj3;
//返回修改后的对象值
}
Point point::operator --(int) // 后缀, 先赋值再减!
{
point a
a.m_x=m_x;
a.m_y=m_y;
m_x--;
m_y--;
return a;
}
Point point::operator --(); // 前缀
{
point a;
a.m_x=m_x--;
a.m_y=m_y--;
return a;
}
};
istream &operator>>(istream &in, Point &P1)// 重载输入运算符>>
{
double X, Y;
cout << "输入横坐标:";
in >> X;
// 输入横坐标
cout << "输入纵坐标:";
in >> Y;
// 输入纵坐标
P1.SetX(X);
// 设置横坐标
P1.SetY(Y);
// 设置纵坐标
return in;
// 返回输入流对象
}
ostream &operator<<(ostream &out, const Point &P2)// 重载运算符<<
{
cout <<"(" <<P2.GetX() << "," << P2.GetY() <<")"<< "i";//虚部为正
return out;
// 返回输出流对象
}
///////////////////////////////////////////////////////反省一下///////////////////////////////////////////////////////////////////
1:对运算符的重载还没入门,不了解其语法结构以及相应的规则(++,--运算符(包括前缀以及后缀),输入输出运算符)
2:对友元关系及其相应的知识没有掌握
///////////////////////////////////////////////////////总结教训///////////////////////////////////////////////////////////////////
1.运算符的重载
(1)c++的以下运算符只能重载成成员运算符:(重载为成员函数时,函数的参数个数比原来参与运算的运算数少一)
= () [ ] ->
(2)c++有的运算符只能重载为类的非成员运算符:
<<(输出运算符) >>(输入运算符)
(3)类的非成员运算符重载函数可以声明为类的友元函数或者是一般的函数
非成员运算符重载函数可以声明为类的友元函数
friend <函数返回值类型> operator <运算符> (形参表)////////重载为友元函数
{
<函数体>
}
非成员运算符重载函数是一般的函数
<函数返回值类型> operator <运算符> (形参表)
{
<函数体>
}
(4)输入输出运算符的重载
类类型有时也可以定义自己的输入/输出运算符,以满足特殊的需求,即输入/输出运算符的重载,但都只能重载为非成员函数。且一般应声明它是类的
类的友元函数以便它访问类的私有成员
输出运算符<<,第一操作数必须是标准的输出类ostream对象的引用,如ostream &cout。它的第二操作数必须是本类的对象,为了保证
输出运算符的连通性,重载函数的返回值类型应该为 标准的输出类ostream的引用。
ostream & operator<<(ostream &cout , 类类型 obj ); ///////////////////////////////重载输出运算符<<
输入运算符>>,第一操作数必须是标准的输入类istream对象的引用,如istream &cin。它的第二操作数必须是本类的对象,为了保证
输入运算符的连通性,重载函数的返回值类型应该为 标准的输入类istream的引用。
2.1为什么要使用友元函数
在实现类之间数据共享时,减少系统开销,提高效率。如果类A中的函数要访问类B中的成员(例如:智能指针类的实现),那么类A中该函数要是类B的友元函数。具体来说:为了使其他类的成员函数直接访问该类的私有变量。即:允许外面的类或函数去访问类的私有变量和保护变量,从而使两个类共享同一函数。
实际上具体大概有下面两种情况需要使用友元函数:(1)运算符重载的某些场合需要使用友元。(2)两个类要共享数据的时候。
代码例题如下:
class INTEGER
{
friend void Print(const INTEGER& obj);//声明友元函数
};
void Print(const INTEGER& obj)
{
//函数体
}
void main()
{
INTEGER obj;
Print(obj);//直接调用
}
2.2类Y的所有成员函数都为类X友元函数—友元类
2.2.1目的:使用单个声明使Y类的所有函数成为类X的友元,它提供一种类之间合作的一种方式,使类Y的对象可以具有类X和类Y的功能。
2.4.2.2语法: friend + 类名(不是对象哦)
例题代码如下:class girl;
class boy
{
public:
void disp(girl &);
};
void boy::disp(girl &x) //函数disp()为类boy的成员函数,也是类girl的友元函数
{
cout<<"girl's name is:"<<x.name<<",age:"<<x.age<<endl;//借助友元,在boy的成员函数disp中,借助girl的对象,直接访问girl的私有变量
}
class girl
{
private:
char *name;
int age;
friend boy; //声明类boy是类girl的友元
};
2.3类Y的一个成员函数为类X的友元函数
2.3.1目的:使类Y的一个成员函数成为类X的友元,具体而言:在类Y的这个成员函数中,借助参数X,可以直接以X的私有变量
2.3.2语法:
声明位置:声明在公有中 (本身为函数)
声明:friend + 成员函数的声明
调用:先定义Y的对象y---使用y调用自己的成员函数---自己的成员函数中使用了友元机制
2.3.3代码:
实现代码和2.2.3中的实现及其相似只是设置友元的时候变为friend void boy::disp(girl &);自己解决喽……
小结:其实一些操作符的重载实现也是要在类外实现的,那么通常这样的话,声明为类的友元是必须滴。
4.友元函数和类的成员函数的区别
4.1 成员函数有this指针,而友元函数没有this指针。
4.2 友元函数是不能被继承的,就像父亲的朋友未必是儿子的朋友。
////////////////////////////////////////////////正确版的代码////////////////////////////
//////////////////////////////Point.h////////////////////
#include<iostream>using namespace std;
class Point
{
protected:
int X,Y;
public:
friend istream &operator>>(istream &in, Point &P1)
{
double X, Y;
cout << "输入横坐标:";
in >> X;// 输入横坐标
cout << "输入纵坐标:";
in >> Y;// 输入纵坐标
P1.Setx(X);// 设置横坐标
P1.Sety(Y);// 设置纵坐标
return in;// 返回输入流对象
}
friend ostream &operator<<(ostream &out, Point &P2) //////千万注意输入输出运算符只能重载成非成员函数,这里设计成友元函数,然后又考虑到vc6.0的bug问题,才把函数的定义也放在了类里面。
{
out<<"(" <<P2.Getx() << "," << P2.Gety() <<")";//虚部为正
return out;// 返回输出流对象
}
Point(int a,int b)
{
Setx(a);
Sety(b);
}
Point(){}
void Setx(int a)
{
X=a;
}
void Sety(int b)
{
Y=b;
}
int Getx()
{
return X;
}
int Gety() ///注意写函数时一定要写返回值的类型,当初忘了写,当然不写的话,系统默认的返回值为int型的,最好写上,规范代码。
{
return Y;
}
Point operator++( int i) //重载后缀++,先取值,后自加
{ Point obj1;
obj1.X = X;//创建一个临时对象,原来保存对象原来的值
obj1.Y = Y;
X++; //此处++为系统默认含义
Y++;
return obj1;//返回对象原值,也就是修改前的值
}
Point operator++( ) //重载前缀++,先自加,后取值
{ Point obj3; //下面拓展一下使用this指针的方法:
obj3.X=X; //this->X++;
obj3.Y=Y; /////this->Y++;
obj3.X++; ///return *this;
obj3.Y++; //此处++为系统默认含义
X++;
Y++;
return obj3;//返回修改后的对象值
}
Point operator --(int) // 后缀, 先赋值再减!
{
Point a;
a.X=X;
a.Y=Y;
X--;
Y--;
return a;
}
Point operator --() // 前缀
{ //下面拓展一下使用this指针的方法:
Point a; //// this->X--;
a.Y=Y; //// return *this;
X--;
Y--;
return a;
}
};
/////注意友元函数应该在类中声明,在类外定义,因为友元函数并不属于类,本题在类内定义是由于vc6.0编译器有bug,这样的话可以避开bug。
//istream &operator>>(istream &in, Point &P1)// 重载输入运算符>>
//ostream &operator<<(ostream &out, Point &P2)// 重载运算符<<
/////////////////////////////////////main.cpp//////////////////////////////////
void main()
{
Point obj1;
cin>>obj1;
cout<<obj1<<endl;
cout<<--obj1<<endl;
cout<<++obj1<<endl;
Point obj2(3,6);
cout<<obj2<<endl;
cout<<obj2++<<endl;
cout<<obj2--<<endl;
}
三:定义一个Rectangle类,要求重载运算符“>” 或 “<” 使之实现比较两个矩形对象面积的大小。
using namespace std;
class Rectangle
{
protected:
float W,H;
public:
Rectangle(float a ,float b)
{
SetW(a);
SetH(b);
}
void SetW(float a)
{W=a;}
void SetH(float b)
{H=b;}
float GetArea()
{
return W*H*1.0/2; ////////注意*是左结合性,如果写成 return 1/2*W*H;系统首先进行1/2运算,得到0.5,不知道它是什么类型的,自动转化为了in型,即0
}
float GetW()
{
return W;
}
float GetH()
{
return H;
}
bool operator >( Rectangle &obj1 ) //重载>//////////////////////////>运算符的重载
{
if (obj1.GetArea() < this->GetArea())
return true;
}
bool operator<( Rectangle &obj2) //重载>/////////////////////////<运算符的重载
{
if (this->GetArea() < obj2.GetArea())
return true;
}
};
在标准C语言的文档里,对操作符的结合性并没有做出非常清楚的解释。一个满分的回答是:它是仲裁者,在几个操作符具有相同的优先级时决定先执行哪一个。
每个操作符拥有某一级别的优先级,同时也拥有左结合性或右结合性。优先级决定一个不含括号的表达式中操作数之间的“紧密”程度。例如,在表达式a*b+c中,乘法运算的优先级高于加法运算符的优先级,所以先执行乘法a*b,而不是加法b+c。
但是,许多操作符的优先级都是相同的。这时,操作符的结合性就开始发挥作用了。在表达式中如果有几个优先级相同的操作符,结合性就起仲裁的作用,由它决定哪个操作符先执行。像下面这个表达式:
int a,b=1,c=2;
a=b=c;
我们发现,这个表达式只有赋值符,这样优秀级就无法帮助我们决定哪个操作先执行,是先执行b=c呢?还是先执行a=b。如果按前者,a=结果为2,如果按后者,a的结果为1。
所有的赋值符(包括复合赋值)都具有右结合性,就是在表达式中最右边的操作最先执行,然后从右到左依次执行。这样,c先赋值给b,然后b在赋值给a,最终a的值是2。类似地,具有左结合性的操作符(如位操作符“&”和“|”)则是从左至右依次执行。
结合性只用于表达式中出现两个以上相同优先级的操作符的情况,用于消除歧义。事实上你会注意到所有优先级相同的操作符,它们的结合性也相同。这是必须如此的,否则结合性依然无法消除歧义,如果在计算表达式的值时需要考虑结合性,那么最好把这个表达式一分为二或者使用括号。
例:
a=b+c+d
=是右结合的,所以先计算(b+c+d),然后再赋值给a
+是左结合的,所以先计算(b+c),然后再计算(b+c)+d
C语言中具有右结合性的运算符包括所有单目运算符以及赋值运算符(=)和条件运算符。其它都是左结合性。
在C语言中有少数运算符在C语言标准中是有规定表达式求值的顺序的:
1:&& 和 || 规定从左到右求值,并且在能确定整个表达式的值的时候就会停止,也就是常说的短路。
2:条件表达式的求值顺序是这样规定的:
test ? exp1 : exp2;
条件测试部分test非零,表达式exp1被求值,否则表达式exp2被求值,并且保证exp1和exp2两者之中只有一个被求值。
3:逗号运算符的求值顺序是从左到右顺序求值,并且整个表达式的值等于最后一个表达式的值,注意逗号','还可以作为函数参数的分隔符,变量定义的分隔符等,这时候表达式的求值顺序是没有规定的!
判断表达式计算顺序时,先按优先级高的先计算,优先级低的后计算,当优先级相同时再按结合性,或从左至右顺序计算,或从右至左顺序计算。