您的位置:首页 > 编程语言 > C语言/C++

《C++大学教程》学习笔记(十)

2018-02-15 01:13 429 查看

《C++大学教程》学习笔记(十)

1.使用标准库中string类的重载运算符

下面这个实例给出了几个string类的重载运算符,事实上,几乎能想到的所有运算符都已经被string重载了。通过这个实例,我们可以对运算符重载有个初步的认识,下面是实例代码:

//使用string类中的重载运算符
#include <iostream>
#include <string>
using namespace std;

int main(){

string s1("Happy ");
string s2("birthday");
string s3;

cout << "Original: " << "s1:" << s1 << " s2:"<< s2 << " s3:" << s3 << endl << endl;

if (s1 < s2)        //string类中重载的<
cout << "s1 < s2" << endl;
if (s1 != s3)       //string类中重载的!=
cout << "s1 != s3" << endl;

s1 += s2;           //string类中重载的+=
cout << "s1 after += s2:" << s1 << endl;

s1 += " to you";    //string类中重载的+=
cout << "s1 after += \" to you\":" << s1 << endl << endl;

s3 = s1.substr(0,14);   //截取从0开始,长度为14的字符串
cout << "s3: " << s3 << endl;

s3 = s1.substr(15);     //截取从15开始剩余部分
cout << "s3: " << s3 << endl;
}


测试结果如下图所示:



从运行结果可以发现,常用的运算符、比较符这些都已进行了重载,使用起来符合C++ 的习惯。

2.初入运算符重载

2.1一些要点

大部分的C++运算符都能被重载,但有一些不能,它们分别是:

.           .*             ::            ?:


这四种。

而当重载( )、[ ]、-> 和 任何赋值操作符( = )时,运算符重载函数必须被声明为类成员,即必须是成员函数

2.2重载二元运算符

二元运算符可以重载为:

带有一个参数的非static成员函数

带两个参数的非成员函数,一般为友元函数

下面我们以流输入与流输出运算符举例,给出一个重载 >> 与 << 的实例,具体的文件结构与代码如下所示:



PhoneNumber.h文件:

#ifndef PhoneNumber_h
#define PhoneNumber_h

#include <iostream>
#include <string>
using namespace std;

class PhoneNumber{
//下面两个都是被声明为非成员的友元函数,用来重载>>与<<
friend ostream &operator << (ostream &,const PhoneNumber &); //带const
friend istream &operator >> (istream &,PhoneNumber &); //不需要const

private:
string areaCode;
string exchange;
string line;
};

#endif /* PhoneNumber_h */


PhoneNumber.cpp文件:

#include "PhoneNumber.h"
#include <iomanip>
using namespace std;

ostream & operator << (ostream &output,const PhoneNumber &number){
output << "(" << number.areaCode << ") "
<< number.exchange << "-" << number.line;

return  output;                 //返回类型为ostream的引用
}

istream & operator >> (istream &input,PhoneNumber &number){
input.ignore();          //跳过(
input >> setw(3) >> number.areaCode;
input.ignore();          //跳过)
input >> setw(3) >> number.exchange;
input.ignore();          //跳过-
input >> setw(4) >> number.line;

return input;                  //返回类型为istream的引用
}


main.cpp文件:

#include "PhoneNumber.h"
#include <iostream>
using namespace std;

int main(){

PhoneNumber phone;

cout << "Enter phone number in this form:(123) 456-7890:\n";

cin >> phone;         //调用的是自定义的 >>

cout << "The phone number entered is: " << phone << endl;  //调用的是自定义的 <<

return 0;
}


运行结果如下图所示:



在上面这个实例中,当编译器遇到了

cin >> phone;


这句表达式时,它实际上产生的是这样的调用:

operator >> (cin , phone);


这显然是一个声明为非成员函数的友元函数(带两个参数),那么我们能不能把这个符号重载函数声明为一个成员函数(带一个参数)呢?

理论上是可行的,但如果那么做,会产生一种十分尴尬的现象,比如当我们想用自定义的 << 或 >> 时,就得采用如下这种格式:

phone << cout;
phone >> cin;


这种怪异的形式显然与C++的使用习惯不同。

因此,二元运算符的重载运算符 可以作为 成员函数 来实现的前提条件是 仅当左操作数是该函数所在类的对象

在上面这个实例中,无论是 >> 还是 << ,其左操作数都是 输入(istream) 或 输出(ostream) ,右操作数才是函数所在类的对象,所以不能采用成员函数的方式来重载。

2.3重载一元运算符

同理,一元运算符可以重载为:

不带参数的非static成员函数

带一个参数的非成员函数,一般为友元函数

下面我们一样以一个实例来介绍如何重载一元运算符,并着重探究了重载一元运算符中前置++与后置++的区别,下面是文件结构和具体代码:



Date.h文件:

#ifndef Date_h
#define Date_h

#include <iostream>
#include <array>
using namespace std;

class Date{

friend ostream & operator << (ostream &,const Date &);  //用友元函数重载一个 << 运算符

public:
Date(int m = 1,int d = 1,int y = 1900);
void setDate(int,int,int);
Date &operator++();         //重载前置++
Date operator++(int);      //重载后置++,注意返回类型没有引用
Date &operator+=(unsigned int);     //重载+=

static bool leapYear(int);      //判断某一年是否是闰年
bool endOfMonth(int) const;     //判断某一天是否是月底

private:
unsigned int month;     //1 - 12
unsigned int day;       //1 - 31 based on month
unsigned int year;

static const array<unsigned int, 13> days;
void helpIncrement();        //完成加一天的函数
};

#endif /* Date_h */


Date.cpp文件:

#include "Date.h"
#include <iostream>
#include <string>
using namespace std;

const array<unsigned int, 13> Date::days =
{0,31,28,31,30,31,30,31,31,30,31,30,31};        //注意这里的days一定要声明是Date作用域的

Date::Date(int m,int d,int y){
setDate(m, d, y);
}

void Date::setDate(int mm, int dd, int yy){

if (mm >= 1 && mm <= 12)
month = mm;
else
throw invalid_argument("Month must be 1-12");

if (yy >= 1900 && mm <= 2100)
year = yy;
else
throw invalid_argument("Year must be 1900-2100");

if ((dd >= 1 && dd <= days[month]) || (month == 2 && dd == 29 && leapYear(year)))
day = dd;
else
throw invalid_argument("Day is out of range");
}

//前置++的重载函数
Date & Date::operator++(){
helpIncrement();    //先加
return *this;       //返回加完后的对象
}

//后置++的重载函数,int不用管,只是用来代表这是后置++
Date Date::operator++(int){
Date temp = *this;      //先将当前对象拷贝到一个临时对象
helpIncrement();        //后加
return temp;            //返回临时对象
}

Date & Date::operator+=(unsigned int addDays){
for (int i = 0; i < addDays; i++)
helpIncrement();

return *this;
}

bool Date::leapYear(int testYear){
if ((testYear % 400 == 0) || (testYear % 4 == 0 && testYear % 100 != 0))
return true;
else
return false;
}

bool Date::endOfMonth(int testDay) const{

if (month == 2 && leapYear(year))
return testDay == 29;
else
return testDay == days[month];
}

void Date::helpIncrement(){
if (!endOfMonth(day))   //如果不是当前月的最后一天
day++;
else
if (month < 12){    //如果是当前月的最后一天且不是12月31号
month++;
day = 1;
}
else{               //12月31号
year++;
month = 1;
day = 1;
}
}

ostream & operator << (ostream &output,const Date &date){
output << date.month << "/" << date.day << "/" << date.year;
return output;
}


main.cpp文件:

#include <iostream>
#include "Date.h"
using namespace std;

int main(){

Date d1(12,27,2010);
Date d2;

cout << "d1 is: " << d1 << "\nd2 is: " << d2 ;
cout << "\n\nd1 += 7 is: " << (d1 += 7);

d2.setDate(2, 28, 2008);
cout << "\n\nd2 is: " << d2;
cout << "\n++d2 is: " << ++d2;

Date d3(7,13,2010);
cout << "\n\n测试前置++\nd3 is: " << d3;
cout << "\n++d3 is: " << ++d3;
cout << "\nd3 is: " << d3;

cout << "\n\n测试后置++\nd3 is: " << d3;
cout << "\nd3++ is: " << d3++;
cout << "\nd3 is: " << d3 << endl;

return 0;
}


运行结果如下图所示:



从上面这个实例我们可以看出,重载前置++与后置++的声明及实现方式都是完全不同的。

对于前置++来说,通过成员函数来实现重载的函数原型是:

Date & operator++();


而对于后置++,其通过成员函数来实现重载的函数原型则是:

Date operator++( int );      //注意函数返回的类型没有引用


这里的int没有实际意义,只是为了让编译器能够区分前置++和后置++,就算是在这个函数的具体实现中也只用写一个int即可(即不需要定义局部变量)。

值得注意的是,由于后置++的函数按值返回一个Date对象,因此在具体实现中:

//后置++的重载函数,int不用管,只是用来代表这是后置++
Date Date::operator++(int){
Date temp = *this;      //先将当前对象拷贝到一个临时对象
helpIncrement();        //后加
return temp;            //返回临时对象
}


通常先返回一个包含对象原始值的临时对象(temp),这会对性能造成很大的影响。

出于这个原因,我们更倾向使用前置自增与自减运算符。

3.零零散散

通过new运算符为一个数组对象动态分配空间:

int *ptr = new int[100]();


此语句分配了一个可以存储100个初始化为0的整数的数组,可以用下面的语句删除这个数组

delete [] ptr;


explicit的意义:为了防止无意中(编译器一般默认)将单参数构造函数用作转换构造函数,我们在声明每个单参数构造函数的前面加了关键词explicit,其目的是禁止不应该允许的由构造函数完成的隐式转换

>> 既可以作为按位右移运算符,也可以作为流提取(输入)运算符;

<< 既可以作为按位左移运算符,也可以作为流插入(输出)运算符。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: