前言:《c++类和数据结构》这本书是寒假前在学校图书馆借的,然后寒假基本没怎么看,前两天回学校翻了翻发现挺厉害的…于是有了一个大胆的想法:跟着书做笔记…虽然大部分都是跟着书敲…虽然不知道能坚持几天…不管了敲了再说…
PS:本文纯粹本人无聊跟着书乱敲,毫无逻辑,没啥营养!!慎读!!
运算符的概念:运算符通常指的是一些符号,作用于一个或者多个变量或常量,产生一个结果。如:+、<=和&&等。有时候结果是经计算的得出的值,有时候结果是false或者true,运算符总能获得某些类型的结果,否则就没有理由使用运算符了。
但我们现在需要改变一下思考方式,把运算符看作是一次函数调用,而不是简单的符号,调用函数的返回值取代了函数本身,也就是运算符的运算结果,事实上,所有我们所用的运算符都是调用程序员所编写的函数才能实现功能的。
例如。如果函数xdd返回一个整型数3,那么表达式
x = xdd() + 2;
将把值5存放到x中。在这是因为xdd()被其返回值3取代,然后3加上2得到5,最后将5存储在x中。同理,在表达式
if (x > y)
中, >是重载运算符,表达式 x > y 是函数调用。因此整个表达式 x > y 将被函数的返回值取代。在这种情况下,该函数的返回值可能是一个布尔类型,因此整个表达式 x > y 将被函数执行完后所返回的true或者false取代。
在以上的例子中,如果 x , y都是对象(如整型),且重载运算符被定义了,那么c++就知道如何去做了;因此,重载运算符只能为对象编写,而且必须为作用于对象的运算符编写重载运算符函数。
例如,我们定义一个结构
struct CarType {
string maker;
int year;
float price;
};
假定我们将myCar声明为该结构的一个对象,并且为该对象的所有数据成员赋值。然后我们编写下述代码:
if (myCar > 2000)
cout << "My car is more than 2000!" << endl;
这时,c++不知道如何处理这段代码,因为c++不知道应该拿myCar中的哪一个变量和2000比较,所以我们必须通过编写一个c++能够执行的函数,告诉c++应该如何处理这一情况。
因为运算符的广泛性,所以有许多种方式进行编写。我们将讨论限定于二元运算符(binary operator)——而且只是用于计算或者比较的运算符,一个二元运算符只作用于2个操作数。
在c++中,二元运算符通常左边有一个操作数,右边有一个操作数。前面讲过,为了编写重载运算符函数,两个操作数必须有一个是对象。如果左边的操作数是结构的对象,那么整个函数定义通常位于结构定义内部,然后运算符右边的操作数作为参数传递给函数。下面是一个描述这样一个函数的简单程序:
//A program that uses an overloaded operator in a struct
#include
#include
using namespace std;
struct CarType {
string maker;
int year;
float price;
bool operator > (int num) {if (price > num) return true;
return false;}
};
int main()
{
CarType myCar;
myCar.maker = "Mercedes";
myCar.year = 2019;
myCar.price = 199999.99;
if (myCar > 200000)
cout << "myCar is more than 200000!" << endl;
return 0;
}
注意第10行,这一行代码是重载运算符>的函数定义,该函数是一个返回类型为布尔类型的函数,也正是我们希望的。这里函数名称必须使用关键字operator,后面跟着被重载运算符的符号。
现在,如果把2000也改成CarType的对象 yourCar,会发生什么呢?
if (myCar > yourCar)
cout << "My car is more than 2000!" << endl;
编译器会报错,因为yourCar不是一个整型变量,不能传递给参数num。我们需要编写另一个运算符函数
struct CarType {
string maker;
int year;
float price;
bool operator > (int num) {if (price > num) return true;
return false;}
bool operator > (CarType car) {if (price > car.price) return true;
return false;}
};
那么如果按下述方式编写代码会发生什么情况呢
if (200000 < myCar)
cout << "My car is more than 2000!" << endl;
这里由于左操作数不是对象,而右操作数是对象,我们将函数定义放在结构定义的下方
struct CarType {
string maker;
int year;
float price;
bool operator > (int num) {if (price > num) return true;
return false;}
bool operator > (CarType car) {if (price > car.price) return true;
return false;}
};
bool operator < (int num, CarType car) {if (num < car.price) return true;
return false;}
我们希望在Checkbook类中放入一个Check结构:
struct Check {
float checkAmount;
int checkNum;
string date;
string receiver;
};
// Checkbook.h - A class for a checkbook
class Checkbook
{
public:
void setBalance(float amount);
bool writeCheck(float amount); // return false if amount is greater than balance
//otherwise return true
void deposit(float amount);
float getBalance();
float getLastCheck();
float getLastDeposit();
private:
float balance;
float lastCheck;
float lastDeposit;
};
我们准备向 writeCheck 函数传递一个Check对象,而不再是一个简单的浮点数。我们准备从 getLastCheck 函数返回一个Check对象,从而主程序员可以获得所需的全部信息。函数的私有部分中不再定义 lastCheck 浮点型变量,而定义一个名为 lastCheck 的 Check对象。
则我们可以做一下改进:
struct Check {
float checkAmount;
int checkNum;
string date;
string receiver;
};
// Checkbook.h - A class for a checkbook
class Checkbook
{
public:
void setBalance(float amount);
bool writeCheck(Check amount); // return false if amount is greater than balance
//otherwise return true
void deposit(float amount);
float getBalance();
Check getLastCheck();
float getLastDeposit();
private:
float balance;
Check lastCheck;
float lastDeposit;
但在现实生活中,需求往往是不一样的,理想的情况是,客户可以在客户文件或者主程序文件中生成自己的结构,即根据需要创建各种不同类型的Checkbook。例如,客户可能希望创建一个Checkbook对象将支票金额储存为浮点型,创建另一个Checkbook对象将支票金额储存为Check对象。客户有可能希望在同一个程序中生成几十个不同类型的Checkbook。
所有这些改进可行吗?这些改进并不是没有可能。类模板(class template)就是针对客户这些改进的答案。
类模板是生成类的蓝图,就像类是生成对象的蓝图一样。由同一个类模板可以生成布置一个类。
类模板看上去有点像类,而且有时候我们确实也按类讨论他们,但是他们实际上并不是类。实际上,当创建类模板时,除非客户生成类,否则是没有类存在的。
类似于我们修改Checkbook类提供Check对象的情形,我们所需做的所有工作就是将适当位置上的单词float修改为Check。类模板也采用这一原则,只是类模板允许在这些适当的位置上使用任意的类型:字符串、字符、整型、浮点型、结构以及其他可以用作数据类型的类型。下面给出一个Checkbook类的类模板:
//checkbook.h - a class template for a Checkbook, where eht check is any data type
template <class DataType>
class Checkbook
{
public:
void setBalance(float amount);
bool writeCheck(DataType amount); // return false if amount is greater than balance; otherwise returns true
void deposit(float amount);
float getBalance();
DataType getLastCheck();
float getLastDeposit();
private:
float balance;
DataType lastCheck;
float lastDeposit;
};
#include "checkbook.cpp"
这个类模板看上去和类相似。其中一个非常出色的优点就是我们不用再在它的上面包含结构定义了。结构定义可以在主程序中进行。要定义一个类模板,我们必须要在程序中添加一行代码,从而让编译器知道这是一个类模板。这行代码位于第3行。这一行代码由关键字template大头;然后在尖括号中是关键字class和类模板的名称,这个名称将用作支票的数据类型。有时候,人们将它看作是数据类型的一个变量。
在最后一行,我们包含了类实现文件,在只使用类时还不用包含它,但是在使用类模板时就需要包含它。
类模板的类实现文件如下所示:(接上面的代码)
//checkbook.cpp -- the function definitions of the class template for the Checkbook
template <class DataType>
void Checkbook<DataType>::setBalance(float amount)
{
balance = amount;
}
template <class DataType>
bool Checkbook<DataType>::writeCheck(DataType amount)
{
if (amount > balance)
return false;
balance -= amount;
lastCheck = amount;
return true;
template <class DataType>
void Checkbook<DataType>::deposit(float amount)
{
balance += amount;
lastDeposit = amount;
}
template <class DataType>
...
}
注意该文件的顶部没有包含checkbook.h文件。在每个函数上面,我们必须为类定义包含同一个模板行,即第3行中编写的代码。另一个不同就是函数头中每个类名称的末尾都添加了一个。
定义类模板与定义类的区别:定义类模板的类说明文件时,类实现文件(上例的checkbook.cpp)要跟在类说明文件(checkbook.h)的底部。而当我们编写的只是一个类时,我们不用在类说明文件的底部包含类实现文件,但是我们要在实现文件的顶部包含说明文件(#include “checkbook.h”)。
这一区别的原因在于,对于类模板来说,编译器不能直接编译实现文件;类模板必须先生成类。所以实现文件包含在头文件的底部,实质上生成了一个更长的头文件。
const 限定符
template <class DataType>
class Checkbook
{
public:
void setBalance(float amount);
bool writeCheck(const DataType & amount); // return false if amount is greater than balance; otherwise returns true
void deposit(float amount);
float getBalance() const;
DataType getLastCheck() const;
float getLastDeposit() const;
private:
float balance;
DataType lastCheck;
float lastDeposit;
};
#include "checkbook.cpp"
构造函数
类的另一个重要成分就是所谓的构造函数(constructor)。每个类都要至少拥有一个构造函数,如果程序员没有为类编写构造函数,那么编译器就会自动地为类提供一个不做任何操作的构造函数。
构造函数时类的一个函数,但是它并不是一个普通的函数。首先,构造函数没有返回类型,甚至都不是void,如果程序员为构造函数指定了一个返回类型,那么编译器将提示有错。其次,构造函数的名称必须是类的名称,程序员没有选择的余地。第三个使构造函数不一般的原因是它的调用方式,仅当声明类的对象时才会调用构造函数。因此,如果按下述语句声明一个对象:
Checkbook cbook;
那么该语句将自动为cbook对象调用构造函数(位于Checkbook类中),因此,你现在知道这一行代码要做两件事:
1、它声明一个对象
2、它调用cbook对象的构造函数
当为类声明对象时总是会调用构造函数,如果没有在类中编写构造函数,编译器就会自动为类提供一个不做任何操作的构造函数。Checkbook类中的不做任何操作的函数原型如下所示:
Checkbook();
// 在类实现文件中,不做任何操作的构造函数的定义如下所示:
Checkbook::Checkbook()
{
}
在构造函数中,可以像在其他函数中一样编写几乎任意的代码,你可以自由发挥,如果构造函数很长,那么看起来不起眼的一个对象声明就有可能执行数百行的代码。但在大多数情况下,构造函数相当短小,构造函数通常用来初始化私有类的数据成员。
在Checkbook类中添加构造函数:
template <class DataType>
class Checkbook
{
public:
Checkbook();
Checkbook(float initbalance)
void setBalance(float amount);
bool writeCheck(const DataType & amount); // return false if amount is greater than balance; otherwise returns true
void deposit(float amount);
float getBalance() const;
DataType getLastCheck() const;
float getLastDeposit() const;
private:
float balance;
DataType lastCheck;
float lastDeposit;
};
#include "checkbook.cpp"
这样我们可以在声明对象时就将balance的值初始化(不用再使用setBalance()函数了)。
Checkbook<float> cbook(1000);
// 声明一个cbook的对象,并初始化balance的值为1000
//等于不使用构造函数的下列两行代码
Checkbook<float> cbook;
setBalance(1000);
那么,既然我们可以使用构造函数来设置初始balance,那么是否可以将 setBalance 函数从类定义中删除呢?而且为什么还要定义一个不做任何事情的构造函数呢?是否也可以将这个构造函数删除掉?
答案是肯定的,但我们在这里只是在玩一个学习游戏,在我们平时修改类时,应该避免删除函数。在修改类的过程中,你的目标是对类进行改进,对任然使用旧类的客户程序提供向后兼容,保证使用旧类的客户程序仍然可以使用新类正确编译和运行。