1.c++中运算符可以被重载
在c++中,几乎所有的运算符都可以被重载。
2.不可以被重载的运算符
有6个运算符是不能被重载的,它们分别是。
::
? :
.
.*
sizeof
#
##
3. 重载的格式
返回值 operator被重载的符号(参数)
{
重载的内容;
}
(1)如果重载是字母字符,例如new delete,那么operator与字幕字符之间至少留
一个空格,其它的可以留也可以不留。
(2)运算符被从重载后,原有的功能依然存在。
(3)运算符重载不是发明新的运算符,也不能修改运算符的优先级和操作数的个数。
4. 重载举例
(1)对<进行重载举例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>
using namespace std;
class Student
{
string name;
int age;
public:
Student(const string name=" ", int age=0):name(name), age(age) {}
bool operator<(const Student &stu) {
return (this->age < stu.get_age());
}
int get_age() const { return age; }
};
int main(void)
{
Student stu1("zhangsan", 24);
Student stu2("wangwu", 20);
if(stu1 < stu2) {
cout << "zhangsan' age < wangwu'age" << endl;
} else {
cout << "zhangsan' age > wangwu'age" << endl;
}
return 0;
}
运行结果:
zhangsan' age > wangwu'age
例子分析:
在本例中,对<进行了重载,所谓重载就是写一个重载函数,对符号的含义进行重新定义
,比如这个例子中按照年龄进行比较对象的大小,如果没有对<进行重载的话,由于<
默认的含义是只能对基本类型的数据进行比较,那么比较的代码就应该这样写,
if(stu1.get_age() < stu2.get_age()) {
cout << "zhangsan' age < wangwu'age" << endl;
} else {
cout << "zhangsan' age > wangwu'age" << endl;
}
显然可以很明显的看出这样写很繁琐,如果对象的成员要是在复杂点的话,这样写很不简洁,
如果要是能够直接写成stu1 < stu2 不就很直观吗,但问题是<只能对基本类型的数据
进行比较,不能直接对对象进行比较,因此我们就需要重载<符号,当我们按照stu1<stu2
进行比较时,它们能够自动的是使用年龄进行比较。
当<被重载后,执行stu1<stu2时,其实际的含义就是调用stu1的<重载函数,并将stu2
作为参数传递过去,也就是说stu1<stu2该成下面的调用形式完全是可以的。
stu1.operator<(stu2);
需要注意一点的是,由于重载函数中的参数是const的,当该参数调用get_age()函数
时,get_age()函数必须是const的。
(2)重载时需要注意的一些问题
(1)为了提高传参的效率,尽量传递引用或者指针
(2)使用引用或者指针时,如果不用修改内容的话,尽量使用const修饰
(3)尽量将运算符的重载写成内联函数形式,因为内联函数的效率会搞很多,所以很多时候
都是将重载函数直接写在类里面,编译器会自动对齐进行甄别,需不需要编译成为内
联函数。
(4)除非该类是某个友元类的私有类,可以将其设置为private,否者一般情况下,我们都将
该类的重载函数需要声明为public。
(3)例子2
接着上面的例子,定义一个学生对象数组,找出年龄最大的学生。类的定义保持不变。
主函数内容改为如下内容。
int main(void)
{
Student *stu[10];
for(int i=0; i<sizeof(stu)/sizeof(Student *); i++) {
char I = i+'0';
stu[i] = new Student(name+I, i+20);
}
int max = 0;
for(int i=0; i<sizeof(stu)/sizeof(Student *); i++) {
if(*stu[max] < *stu[i]) {
max = i;
}
}
cout << stu[max]->get_name() << " ";
cout << stu[max]->get_age() << endl;
return 0;
}
5. 全局重载函数
前面的例子中,<的重载函数是Student的成员函数,但是实际上我们完全可以将重载
函数定义为全局重载函数,只是相对于成员函数来说,
(1)对于二元操作符来说,全局的重载函数需要传递两个参数
(2)如果希望编译为内联函数,必须显式的声明为inline,否者编译器只会将其编
译为普通函数。从前面知道,成员函数是不需要显式写出的,当然显示写出
也不会错。
如果将前面的<的重载函数重载为全局函数的格式如下,
bool operator<(const Student &stu1, const Student &stu2) {
return (stu1.get_age() < stu2.get_age());
}
调用的时候,stu1<stu2与operator<(*stu[max], *stu[i])是完全等价的。
那么什么时候会用到全局的符号重载呢?这个在后面将会讲到。
6.重载运算符全部功能
(1)什么是运算符的全功能
以上面重载<运算符为例来说,其完整的功能应该有如下:
(1)可以对基本类型数值进行比较:12.5 < 32,这个是<原本就有的功能,无需重载
(2)左操作数和右操作数都是对象:stu1 < stu2,这个需要重载是实现
(3)左操作数为对象,右操作数为数值:stu < 34.0,这个需要重载是实现
(4)左操作数为数值,右操作数为对象:43 < stu,这个也需要重载实现
(2)重载时选择成员函数实现呢?还是选择外部函数实现呢?
对于左操作数为对象时,选择成员函数或者外函数实现都可以,之所以可以选择成员函
数,是因为左操作数为对象时,可以直接调用成员函数。
以stu < 34.0为例来说,等价于stu.operator<(34.0);
反过来,如果左操作数是数值右操作数是对象时,必须使用外部函数重载,
43 < stu,如果将其实现为成员函数重载时,就等价于
43.operator<(stu),这样的写法完全错误的。
总结起来就是,如果左操作数是对象,重载函数可以是成员函数,也可以是外部函数,
但是如果左操作数是数值,如果需要重载的话,必须使用外部函数进行重载。
(3)<全功重载例子
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>
using namespace std;
class Student
{
string name;
int age;
public:
Student(const string name=" ", int age=0):name(name), age(age) { }
int get_age() const { return age; }
bool operator<(const Student &stu) {
return (this->age < stu.get_age());
}
bool operator<(double age) {
return (this->age < age);
}
string get_name() const { return name; }
};
bool operator<(double age, const Student &stu) {
return (age < stu.get_age());
}
int main(void)
{
Student stu1("aaa", 20);
Student stu2("bbb", 15);
if(stu1 < stu2) {
printf("stu1 < stu2\n");
} else {
printf("stu1 > stu2\n");
}
if(stu1 < 34) {
printf("stu1 < 34\n");
} else {
printf("stu1 > 34\n");
}
if(25 < stu2) {
printf("25 < stu2\n");
} else {
printf("25 > stu2\n");
}
return 0;
}
例子分析:
在本例子中实现了<号的全部比较功能。
7. 重载小总结
(1)二元操作数的重载
(1)如果左操作数是对象,使用成员函数或者全局函数进行重载都可以
(1)如果成员函数是对二元操作符进行的重载,只需要一个传参,这个传参
就是二元操作符的右操作数
(2)如果左操作数不是对象而需要被重载的话,只能使用全局函数重载
(1)成员函数对二元操作符进行的重载时,必须传递两个参数,即运算符的左
操作数和右操作数。
(3)二元操作数的重载函数的返回值
(1)如果是关系运算或者逻辑运算的话,返回值为bool型
(2)如果是算数运算,比如*和+时候,返回值视具体情况而定
(2)一元操作符的重载
(1)一元操作符只有一个操作数
(2)如果操作的数值是基本类型没有必要重载,因为一元操作符本来就支持
基本类型数据的操作。
(3)如果操作数是对象时,使用成员函数和全局函数重载都可以。
(1)使用成员函数重载时,除了++和--外,都不需要传递参数
(2)如果是全局函数重载时,将一元操作符的操作数传递过去
(3)返回值的类型视情况而定
8. 赋值运算符=的重载
说到赋值运算符,实际上除了=之外,还有+=/*=/-=等等,这些都可以被重载,这里
就以=为例讲解赋值运算符在重载的过程中遇到的一些问题。
=实际上有两个方面的功能,一个是初始化,一个是赋值,比如:
Student stu1;
Student stu2 = stu1;//等价于Student stu2(stu1);
这就是初始化。
Student stu1;
Student stu2;
stu2 = stu1;
这就是赋值。
这里讲的对=的重载,指的是对=的赋值功能的重载。
(1)默认=重载函数
如果我们自己不给类重载一个赋值运算符的话,编译器会自动的提供一个默认的
operator=()。
编译器提供的默认的=重载运算符对于一些普通的对象赋值来说是没有问题的,
比如还是以Student类为例。
例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>
using namespace std;
class Student
{
public:
string name;
int age;
Student(const string name=" ", int age=0):name(name), age(age) {}
};
int main(void)
{
Student stu1("aaa", 20);
Student stu2;
stu2 = stu1;
cout << "stu1的信息"<<stu1.name <<" "<<stu1.age<<endl;
cout << "stu1的信息"<<stu2.name <<" "<<stu2.age<<endl;
cout<<"\n修改了stu2的信息后"<<endl;
stu2.name="www";
stu2.age = 25;
cout << "stu1的信息"<<stu1.name <<" "<<stu1.age<<endl;
cout << "stu1的信息"<<stu2.name <<" "<<stu2.age<<endl;
return 0;
}
运行结果:
stu1的信息aaa 20
stu1的信息aaa 20
修改了stu2的信息后
stu1的信息aaa 20
stu1的信息www 25
例子分析:
例子中,将stu1的内容通过=赋值给stu2,在没有修改stu2之前,stu1和stu2
的内容是一致的,当stu2的内容修改后,它们的信息不同了,说明stu2空间
与stu1是完全分离的,说明这个默认的=的重载函数起作用了。
如果将这个默认的=的重载函数显式的写出的话,应该是这样的。
Student &operator=(const Student &stu) {
this->name = stu.name;
this->age = stu.age;
return *this;
}
当然打印的结果是一致的。
(2)编译器提供的默认的=重载函数存在的问题
(1)=重载函数存在的问题
实际上默认的=重载函数是存在很大问题的,那就是如果类中包含动态分配
内存的成员的话,对于这样的成员=复制的只是指向动态内存的地址,这会
导致两个对象的动态内存成员空间是共享的,这其实就是浅复制和深复制的
问题。
(2)副本构造函数与=重载函数区别
上面提到=重载函数的问题时,估计都会联想到副本构造函数,因为副本构造
函数在面对动态分配内存的成员时,会出现深拷贝和浅拷贝的问题,这一点
和=重载函数的浅复制和深赋值的问题的根本原理的是一样的。
但是副本构造函数与=重载函数总是有区别的吧,那么它们的区别是什么。
如果我们不弄清它们的区别,我们非常容易将他们进行混淆。
(1)副本构造函数会被调用的情况
(1)使用一个对象直接去初始化另一个对象时,例如:
Student stu1("aaa", 20);
Student stu2(stu1);
或者
Student stu2 = stu1;
以上两种情况是等价的。
(2)传参时,间接实现一个对象直接去初始化另一个对象时
void show(const Student stu) {
......
}
Student stu("aaa", 20);
show(stu)
注意:如果函数直接传递的是引用的话,是不会掉用副本
构造函数的,当然=重载函数更不会调用了。
(2)=重载函数被调用的情况
Student stu1("aaa", 20);
Student stu2;
stu2 = stu1;
因为stu2 = stu1;执行的是赋值操作,因此会调用=重载函数。
(2)例子
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>
using namespace std;
class Student
{
public:
string name;
int age;
Student(const string name=" ", int age=0):name(name), age(age) {}
Student &operator=(const Student &stu) {
printf("=重载函数\n");
this->name = stu.name;
this->age = stu.age;
return *this;
}
Student(Student &stu) {
printf("副本构造函数\n");
this->name = stu.name;
this->age = stu.age;
}
};
void show(const Student stu) {
}
int main(void)
{
Student stu1("aaa", 20);
Student stu2=stu1;//调用副本构造函数
cout<<endl;
show(stu2);//调用副本构造函数
cout<<endl;
stu2 = stu1;//调用=重载函数
return 0;
}
(3)一种特殊情举例
任然上面的例子为例,但是将=重载函数的参数从引用改为对象值传递:
Student &operator=(const Student stu) {
内容不变
}
main函数的内容改为:
int main(void)
{
Student stu1("aaa", 20);
Student stu2;
stu2 = stu1;
return 0;
}
运行结果:
副本构造函数
=重载函数
例子分析:
这个例子的打印结果很是让人迷惑,因为main函数的如下语句,
Student stu1("aaa", 20);
Student stu2;
stu2 = stu1;
似乎很明显,只是进行了赋值操作,但是为什么即调用了副本构造函数,也调用
了=重载函数呢?
其实原因处在=重载函数的形参写法上,
Student &operator=(const Student stu) {
......
}
注意,形参写的不是引用,所以当使用=进行复制操作而调用=重载函数时,首先
会调用副本构造函数使用实参初始化形参stu,因此这里即调用了副本构造函数
也调用了=的重载函数,这样的结果一点也不奇怪。
(3)显式的写=重载函数,实现深复制
前面说过,如果类中包含动态分配的成员时,默认的=重载函数已经不够用了,这个时候
我们需要自己重载=,还是以Student类为例,给它定义一个需要动态分配的Birthday成员。
例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>
using namespace std;
class Birthday
{
public:
string year;
string month;
string day;
Birthday(const string year="0", const string month="0", const string day="0")
:year(year), month(month), day(day) { }
};
class Student
{
public:
string name;
Birthday *brhtday;
Student(const string name=" ", const string year=" ", const string month=" ", const string day=" ")
:name(name), brhtday(new Birthday(year, month, day)) {}
};
int main(void)
{
Student stu1("zhangsan", "2003", "12", "1");
Student stu2;
stu2 = stu1;
cout<<"stu1的信息:"<<stu1.name<<" "<<stu1.brhtday->year<<" "<<
stu1.brhtday->month<<" "<<stu1.brhtday->day<<" "<<endl;
cout<<"stu2的信息:"<<stu2.name<<" "<<stu2.brhtday->year<<" "
<<stu2.brhtday->month<<" "<<stu2.brhtday->day<<" "<<endl;
cout<<"\n修改stu2的生日信息后"<<endl;
stu1.brhtday->year="2009";
stu1.brhtday->month="9";
stu1.brhtday->day="2";
cout<<"stu1的信息:"<<stu1.name<<" "<<stu1.brhtday->year<<" "<<
stu1.brhtday->month<<" "<<stu1.brhtday->day<<" "<<endl;
cout<<"stu2的信息:"<<stu2.name<<" "<<stu2.brhtday->year<<" "
<<stu2.brhtday->month<<" "<<stu2.brhtday->day<<" "<<endl;
return 0;
}
运行结果:
stu1的信息:zhangsan 2003 12 1
stu2的信息:zhangsan 2003 12 1
修改stu2的生日信息后
stu1的信息:zhangsan 2009 9 2
stu2的信息:zhangsan 2009 9 2
例子分析:
从打印的结果可以看出,stu2的生日修改后,stu1的生日也修改了,原因是它们各自
的brthday成员都指向了同一个动态分配空间,因为=进行复制时,只复制了地址,也
就是说只进行了浅复制。
如果要改进这个问题,我们只需要添加自定义=的重载函数,其实现如下:
Student &operator=(const Student &stu) {
if(this == &stu) return *this;
this->name = stu.name;
this->brhtday = new Birthday();
this->brhtday->year = stu.brhtday->year;
this->brhtday->month = stu.brhtday->month;
this->brhtday->day = stu.brhtday->day;
return *this;
}
重新运行的结果:
=重载函数
stu1的信息:zhangsan 2003 12 1
stu2的信息:
修改stu2的生日信息后
stu1的信息:zhangsan 2009 9 2
stu2的信息:
分析:
显然,这个结果已经表明,自定义的=重载函数已经实现了深复制。
之所以能够实现深拷贝的原因是因为,在=重载函数中,对this指向的对象中的brthday
成员开辟了新的空间,然后再将被赋值对象的birthday指向空间的内容复制过来,如此
便实现了深拷贝。
(4)自定义=重载函数两个疑惑
(1)疑惑1:
为什么要在=重载函数中加如下这句话。
if(this == &stu) return *this;
这句话主要是为防止如下这样的赋值语句,
stu1 = stu1;
这条赋值语句实际上没什么实际意义,但是它的确是能够通过编译,如果出现
这种情况时,显然只需要直接return *this;返回即可。
(2)疑惑2:
为什么返回*this。
很多时候会出现连续赋值的情况,比如:
stu3=stu2=stu1;
将这话改为它的等价形式:
stu3.operator=(stu2.operator=(stu1));
显然从上面连续赋值中看出,operator=()的返回值在这种情况下会成为其它
operator=()的参数,而operator=()参数要求是一个引用,因此需要返回*this。
9. 算数运算符重载
(1)+重载的例子
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>
using namespace std;
class Box
{
public:
int length;
int witdh;
int height;
Box(int length=1, int witdh=1, int height=1): length(length), witdh(witdh), height(height) { }
Box& operator=(const Box &box) {
if(this == &box) return *this;
this->length = box.length;
this->witdh = box.witdh;
this->height = box.height;
return *this;
}
Box operator+(const Box &box) const {
Box tmpbox((this->length+box.length), (this->witdh+box.witdh), (this->height+box.height));
return tmpbox;
}
/*这么写是错误的
Box & operator+(const Box &box) const {
Box tmpbox((this->length+box.length), (this->witdh+box.witdh), (this->height+box.height));
return tmpbox;
} */
};
void show(const Box &box) {
cout<<"length:"<<box.length<<" witdh:"<<box.witdh<<" height:"<<box.height<<endl;
cout<<"体积="<<box.length*box.witdh*box.height<<endl;
}
int main(void)
{
Box box1 = Box(12, 33, 45);
Box box2 = Box(43, 23, 14);
Box box3 = box1+box2;
show(box3);
return 0;
}
运行结果:
length:55 witdh:56 height:59
体积=181720
例子分析:
例子中,定义了两个盒子box1和box2,然后将两个盒子对象相加,因此需要重载+。
(2)+号重载需要注意的地方
上面重载+的例子中,可以发现,+的重载需要注意如下问题。
(1)为了提高效率,最好传递引用,并指定为const(因为右操作数并不会被修改)
(2)由于+的左操作数也不会被修改,因此我们将operator+()函数也定义为const
(3)对于其返回值为返回动态空间或者返回引用的方式,都不是很可取。
返回动态分配空间的地址,不利于管理内存。而box因为是自动局部对象
又不能返回其引用,如果将+重载函数中的box定义为staitc的话,是可以返回
其引用的。(能不能返回局部的变量,完全看变量生存期,只要分析这个就可以。)
(4)因为+运算符返回值涉及赋值操作,准确的说是初始化,用一个对象初始化另一个独象,所以
返回函数内的临时对象调用的是复制构造函数。并不是等号复制运算。
(3)+=的重载
如果重载了+=,并可以使用+=快速重载+。+=比较特殊,它含有算数运算符的功能,同时
又含有赋值运算符的功能,这一点需要注意,正是由于它含有赋值运算符的功能,因此
重载函数需要像重载"="时一样返回*this。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>
using namespace std;
class Box
{
public:
int length;
int witdh;
int height;
Box(int length=1, int witdh=1, int height=1): length(length), witdh(witdh), height(height) { }
Box &operator+=(const Box &box) {
this->length += box.length;
this->witdh += box.length;
this->height += box.height;
return *this;
}
Box operator+(const Box &box) {
return Box(*this)+=box;
}
};
void show(const Box &box) {
cout<<"length:"<<box.length<<" witdh:"<<box.witdh<<" height:"<<box.height<<endl;
cout<<"体积="<<box.length*box.witdh*box.height<<endl;
}
int main(void)
{
Box box1 = Box(12, 33, 45);
Box box2 = Box(43, 23, 14);
Box box3(box2);
Box box4=box1+=box2+=box3;
Box box5 = box4+box1;
show(box5);
return 0;
}
例子分析:
本例子中重载了+=,然后借助于+=的重载函数,快速实现了+的重载。
对于main函数中Box box4=box1+=box2+=box3;语句需要注意,第一个=不能
改为+=,因为第一个是初始化而不是赋值,换句话说Box box4=box1+=box2+=box3;
等价于如下形式:
Box box4(box1+=box2+=box3);
box4还没有初始化不能参加+=运算。
(4)-/-=,/=等算数符号的重载
这些符号的重载与+/+=完全类似,请自己完成。
10. 下标运算符的重载
(1)[]与左值和右值
上一章链表的例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>
using namespace std;
class Studata
{
public:
int num;
string name;
Studata(int num=0, const string name=" "):num(num), name(name) {}
int get_num() const{ return num; }
string get_name() const{ return name; }
void set_num(int num) { this->num = num; }
void set_name(string name) { this->name = name; }
};
class Stunode
{
Stunode *prev;
Stunode *next;
Studata *studata;
public:
Stunode(int num=0, const string name=" ", Stunode *prev=NULL, \
Stunode *next=NULL):studata(new Studata(num, name)) {}
Stunode(const Studata &studata) {
this->studata = new Studata();
this->studata->set_num(studata.get_num());
this->studata->set_name(studata.get_name());
prev = next = NULL;
}
~Stunode() {
delete studata;
}
/*获取器*/
Stunode *get_prev() const {return prev;}
Stunode *get_next() const {return next;}
Studata *get_studata() const {return studata;}
/*设置器*/
void set_prev(Stunode *stunode) {this->prev = stunode;}
void set_next(Stunode *stunode) {this->next = stunode;}
void set_studata(Studata *studata) {this->studata = studata;}
};
class Dlist
{
Stunode *hp;
Stunode *current;
Stunode *tmp;
public:
Dlist() {
hp = current = new Stunode();//空头节点
hp->set_prev(hp);
hp->set_prev(hp);
tmp = NULL;
}
void err_fun(string filename, int line, string funname, int err_no) {
cerr<<filename<<" "<<line<<" "<<funname<<" fail"<<":"<<strerror(err_no)<<endl;
exit(-1);
}
void init_Dlist(string filename);
void insert_tail();
void insert_head();
void display();
Studata &operator[](int i);
};
Studata &Dlist::operator[](int index)
{
if(index < 0) {
cout<<"非法下标" << endl;
return *(hp->get_studata());
}
int i=0;
for(current=hp->get_next(); ; current=current->get_next(), i++) {
if(current == hp) {
cout << "无此元素" << endl;
return *(hp->get_studata());
}
else if(i == index) return *(current->get_studata());
}
}
void Dlist::display()
{
for(current=hp->get_next(); hp!=current; current=current->get_next()) {
cout<<current->get_studata()->get_num()<<"\t"<<current->get_studata()->get_name()<<endl;
}
}
void Dlist::insert_tail()
{
hp->get_prev()->set_next(tmp);
tmp->set_prev(hp->get_prev());
hp->set_prev(tmp);
tmp->set_next(hp);
}
using namespace std;
int main(void)
{
Studata data;
Dlist dlist;
dlist.init_Dlist("./stu.txt");
data = dlist[2];
cout << data.get_num()<<"\t"<<data.get_name() <<endl;
Studata data1(12, "qqqq");
dlist[1] = data1;
data = dlist[10];
cout << data.get_num()<<"\t"<<data.get_name() <<endl;
return 0;
}
例子分析:
为了让[]既能够做左值又能够做右值,需要将[]重载函数的返回值改为引用。[]重载
函数具体实现如下:
Studata &Dlist::operator[](int index)
{
if(index < 0) {
cout<<"非法下标" << endl;
return current->studatanul;
}
int i=0;
for(current=hp->get_next(); ; current=current->get_next(), i++) {
if(current == hp) {
cout << "无此元素" << endl;
return current->studatanul;
}
else if(i == index) return *(current->get_studata());
}
}
该重载函数先是判断下标的合法性,然后再去搜索链表,找到下标指定的链表节点,
但是由于返回值是引用,引用的空间不能为空,但是当下标非法或者没有找到该下标
对应的链表节点时,这个时候是没有有效节点的数据空间的,所以这个时候就将头节点
无效数据空间返回。
由于返回值是引用,因此在主函数中,[]既能作为左值也能作为右值。
(2)const与[]重载函数
有些时候我们是不允许对Studata的对象做修改的,但是有的时候又允许做修改,那
什么时候会遇到这种情况呢?如果定义的是一个const的Dlist,那么就不允许[]重载
函数作为左值,而且从前面的学习我们知道,const的对象只能调用const的函数。
如果我们希望这两种情况都能存在,那怎么办呢?
我们可以重载一个const的版本的[]重载函数,还是以上例为例,const版本的[]重载
函数为:
const Studata &Dlist::operator[](int index) const
{
if(index < 0) cout<<"非法下标" << endl;
int i=0;
for(current=hp->get_next(); ; current=current->get_next(), i++) {
if(current == hp) {
cout << "无此元素" << endl;
return current->studatanul;
}
else if(i == index) return *(current->get_studata());
}
}
同时不要忘了在Dlist里面做如下声明,
const Studata &operator[](int i) const;
我们看,这个const版本的[]重载函数,返回值是const的引用,因此不能作为左值,
同将函数也声明为了const。
当我们使用的是const的Dlist类对象时,会自动调用const版本的[]重载函数,如果
将其作为左值的话,将无法通过编译。
11. 重载递增++递减--
(1)++/--的两种情况
++和--运算符的重载需要分为两种,一种是前++/--,一种是后++/--,针对
这两种不同的情况需要给出不同重载。
(1)前++/--含义
前++/--表示,先在给this对象做++/--运算,再将运算后的值返回,为了
提高效率往往返回的是引用。
(2)后++/--含义
后++/--表示,先返回一个this对象的副本,然后在对this对象做运算,为了
避免出现连续的+++++/----这种容易混淆的情况,我们要求都将++/--的重载
函数返回值进行const限制。
(2)前++/--和后++/--的重载格式
这里只给出++的范例,--与之同。
(1)前++
const Object &operator++() {
......
}
(2)后++
const Object operator++(int) {
......
}
注意:这里的int仅仅是为了区分这两个重载函数,没有具体含义,c++
对于后++重载的格式就是这么规定的,如果经int写为其它类型是无
无法编译通过的。
(3)例子
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>
using namespace std;
class Box
{
public:
int length;
int witdh;
int height;
Box(int length=1, int witdh=1, int height=1): length(length), witdh(witdh), height(height) { }
const Box &operator++() {
++this->length;
++this->witdh;
++this->height;
return *this;
}
const Box operator++(int) {
Box box(*this);
this->length++;
this->witdh++;
this->height++;
return box;
}
};
void show(const Box &box) {
cout<<"length:"<<box.length<<" witdh:"<<box.witdh<<" height:"<<box.height<<endl;
cout<<"体积="<<box.length*box.witdh*box.height<<endl;
}
int main(void)
{
Box box1 = Box(12, 33, 45);
show(box1++);
show(++box1);
return 0;
}
12. 重载类型转换
(1)类类型转换为其它类型
我们知道正常情况下要将类类型转换为另一种类型是不行的,比如下面的例子无法
通过编译,因为c++中不允许将类类型强制转换为其它类型。
但是我们可以通过重载类型转换来实现这样的要求,当然另一种类型可以是基本类
型,也可以是其它类类型。
例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>
using namespace std;
class Box
{
public:
int length;
int witdh;
int height;
Box(int length=1, int witdh=1, int height=1)
: length(length), witdh(witdh), height(height) { }
};
int main(void)
{
Box box = Box(1, 1, 1);
int vol = box;
//或者int vol = static_cast<int> (box);
cout<<"体积:"<< vol << endl;
return 0;
}
例子分析:
上面的例子是无法通过编译的,因为main函数中,试图将Box类型对象转换为int,
默认是不支持的。
(2)使用类型重载解决上例的问题
(1)类型重载的格式
operator Type() {
......
}
格式中,没有表明返回值的类型,但是它默认的返回类型就是Type类型
(2)在例子中的Box类型中添加int型类型重载函数
记得将函数放在public下面,具体函数如下:
operator int() const {
cout<< "类型重载函数" << endl;
return (this->length*this->witdh*this->height);
}
然后编译代码运行即可:
分析:
当执行int vol = box;时, operator int()函数就会被调用,就会将
计算得到的体积值返回给vol,当然
int vol = box;
也可以使用
int vol = static_cast<int> (box);
进行替换,这二者是等价的。
12. 智能指针(*与->重载)
智能指针并不是完全意义上的指针,它只是一个类对象,这个对象实现了对
指针的封装,用于向前和向后索引(比如索引链表)。
实现智能指针涉及到*和->的重载,迭代器就是智能指针,在实际开发中,我们
很少会去自己去写智能指针,因为对于提高开发效率和代码运行效率的帮助并不
是很大,为了使用效率,我们往往时直接使用c++提供的智能指针(迭代器)。
但是我们需要了解到底什么是智能指针。智能指针主要用于遍历。
例子:
stulist.cpp
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>
using namespace std;
class Studata
{
public:
int num;
string name;
Studata(int num=0, const string name=" "):num(num), name(name) {}
int get_num() const{ return num; }
string get_name() const{ return name; }
void set_num(int num) { this->num = num; }
void set_name(string name) { this->name = name; }
};
class Stunode
{
Studata *studata;
Stunode *prev;
Stunode *next;
public:
Stunode(int num=0, const string name=" ", Stunode *prev=NULL, \
Stunode *next=NULL):studata(new Studata(num, name)) {}
Stunode(const Studata &studata) {
this->studata = new Studata();
this->studata->set_num(studata.get_num());
this->studata->set_name(studata.get_name());
prev = next = NULL;
}
~Stunode() {
delete studata;
}
/*获取器*/
Stunode *get_prev() const {return prev;}
Stunode *get_next() const {return next;}
Studata *get_studata() const {return studata;}
/*设置器*/
void set_prev(Stunode *stunode) {this->prev = stunode;}
void set_next(Stunode *stunode) {this->next = stunode;}
void set_studata(Studata *studata) {this->studata = studata;}
};
class Dlist
{
Stunode *hp;
Stunode *current;
Stunode *tmp;
public:
Dlist() {
hp = current = new Stunode();//空头节点
bzero(hp, sizeof(Stunode));
hp->set_prev(hp);
hp->set_prev(hp);
tmp = NULL;
}
void err_fun(string filename, int line, string funname, int err_no) {
cerr<<filename<<" "<<line<<" "<<funname<<" fail"<<":"<<strerror(err_no)<<endl;
exit(-1);
}
Stunode *getfirsNode() {
if(hp->get_next() == hp) return NULL;
else return hp->get_next();
}
void init_Dlist(string filename);
void insert_tail();
};
void Dlist::insert_tail()
{
hp->get_prev()->set_next(tmp);
tmp->set_prev(hp->get_prev());
hp->set_prev(tmp);
tmp->set_next(hp);
}
void Dlist::init_Dlist(string filename)
{
FILE *fp = NULL;
fp = fopen(filename.c_str(), "r");
if(NULL == fp) err_fun(__FILE__, __LINE__, "fopen", errno);
while(1)
{
int num;
char name[40];
fscanf(fp, "%d %s\n", &num, name);
//printf("%d %s\n", num, name);
tmp = new Stunode(num, name);
insert_tail();
if(1 == feof(fp)) break;
}
}
/* 定义一个智能指针(迭代器) */
class Dlistiterator {
public:
Dlistiterator(Dlist &dlist) {
pStunode = dlist.getfirsNode();
}
Studata &operator*() {
return *(pStunode->get_studata());
}
Studata *operator->() {
return pStunode->get_studata();
}
Studata *operator++() {
pStunode = pStunode->get_next();
return pStunode->get_studata();
}
Studata *operator++(int) {
Studata *tmpnode = pStunode->get_studata();
pStunode = pStunode->get_next();
return tmpnode;
}
operator bool () {
return(pStunode->get_studata()!=0);
}
private:
Stunode *pStunode;
};
int main(void)
{
Studata data;
Dlist dlist;
dlist.init_Dlist("./stu.txt");
class Dlistiterator dliter = Dlistiterator(dlist);
while(dliter) {
cout << dliter->get_name() << " "<<dliter->get_num() << endl;
dliter++;
}
return 0;
}
stu.txt
1 aaa
4 fff
3 sss
5 www
2 vvv
13. 重载new和delete
没有必要,但是我们要知道这样是可以重载的,当你看到有地方重载它们时,不应该
感到惊讶。