第十一章 使用类
本章内容包括:
- 运算符重载
- 友员函数
- 重载<<运算符用于输出
- 状态成员
- 使用rand()生成随机值
- 类的自动转换和强制类型转换
- 类转换函数
运算符重载:
计算时间:一个运算符重载示例
Time.h
class Time {
private:
int hours;
int minutes;
public:
Time();
Time(int h, int m=0);
Time operator+(const Time & time) const ;
Time operator*(int m) const ;
void operator++();
friend Time operator+(int m,const Time & time){
Time tmp;
tmp.minutes = time.minutes + m;
tmp.hours = time.hours;
return tmp;
};
void show();
};
Time.cpp
#include <iostream>
#include "Time.h"
Time Time::operator+(const Time &time) const {
Time sum;
sum.hours = this->hours + time.hours;
sum.minutes = this->minutes + time.minutes;
return sum;
}
Time::Time(int h, int m) {
hours = h;
minutes = m;
}
Time::Time() {
hours = 0;
minutes = 0;
}
void Time::show() {
std::cout << hours << ":" << minutes << '\n';
}
void Time::operator++() {
minutes++;
}
Time Time::operator*(int m) const {
Time time;
long totalminutes = hours * m * 60 + minutes * m;
time.hours = totalminutes / 60;
time.minutes = totalminutes % 60;
return time;
}
重载限制:
-
重载后的运算符必须至少有一个操作数是用户类型,这将防止用户为标准类型重载运算符。因此,不能重将减法运算符重载为计算两个double值的和,而不是它们的差。虽然这种限制将对创造性有影响,但可确保程序正常运行
-
使用运算符时不能违反运算符原来的语句规则。例如,不能将求模运算符(%)重载为使用一个操操作数
int x; % x;
同样,不能修改运算符的优先级。因此,如果将加号运算符重载成两个类相加,则新的运算符与原有的加号有相同的优先级。
-
不能创建新运算符。例如,不能定义operator**()函数来表示求幂。
-
不能重载下面的运算符:
- sizeof: sizeof运算符。
- . :成员运算符
- .* :成员指针运算符
- :: :域名解析运算符
- ?: 条件运算符
- typeid : 一个RTTI运算符
- const_cast :强制类型转换运算符
- dynamic_cast :强制类型转换运算符
- reinterpret_cast :强制类型转换运算符
- static_cast :强制类型转换运算符
-
下面的运算符只能通过成员函数进行重载
- = :赋值
- () :函数调用
- [] :下标运算符
- -> :通过指针访问类成员的运算符
可重载的运算符
+ | - | * | / | % | ^ |
---|---|---|---|---|---|
& | | | ~= | ! | = | < |
> | += | -= | *= | /= | %= |
^= | &= | |= | << | >> | >>= |
<<= | == | != | <= | >= | && |
|| | ++ | – | , | ->* | -> |
() | [] | new | delete | delete[] | new[] |
友元:
C++控制对类私有对象的访问。通常,公有类方法提供唯一的访问途径,但是有时候这种限制太严格,以至于不适合特定的编程问题。C++提供了另外一种访问权限:友元。友元有三种:
- 友元函数
- 友元类
- 友元成员函数
通过让函数成为类的友元,可以赋予该函数与类成员相同的访问权限。在前面的例子中,加法运算符与乘法运算符重载不同,因为加法是两个Time类相加,而乘法是Time 与int相加,这导致
time3 = time3 * 4; 可以
time3 = 4 * time3; 不可以
从概念上讲,上面两个相同,但第一个表达式不对应于成员函数,因为 4 不是Time类型的对象。因此编译器不能使用成员函数调用替换该表达式。
这里就可以使用友元来解决可以定义:
friend Time operator*(int m, const Time & time);
及实现:
Time operator*(int m, const Time &time) {
Time restime;
long totalminutes = time.hours * m * 60 + time.minutes * m;
restime.hours = totalminutes / 60;
restime.minutes = totalminutes % 60;
return restime;
}
常用的友元:重载<<运算符
一个很有趣的类的特性是,可以对<<运算符进行重载,使之能与cout一起来显式对象的内容:
friend std::ostream & operator<<(std::ostream &os,const Time &t);
// 及实现:
std::ostream & operator<<(std::ostream &os, const Time &t) {
os << t.hours << ":" << t.minutes;
return os;
}
重载运算符:作为成员函数还是非成员函数:
对于很多运算符来说,可以选择使用成员或非成员函数来实现运算符重载。一般来说,非成员函数应是友元函数,这样它才能直接访问类的私有数据。例如,Time类中的+重载,
Time operator+(const &time) const;
也可以:
frend Time operator(const Time &t1,const Time &t2);
使用类实现随机漫步效果:
vector.h
#ifndef D1_VECTOR_H
#define D1_VECTOR_H
#include <iostream>
namespace VECTOR{
class Vector {
public:
enum Mode{RECT,POL}; // 直角坐标模式与极坐标模式
private:
double x;
double y;
double mag;
double ang;
Mode mode;
void set_mag();
void set_ang();
void set_x();
void set_y();
public:
Vector();
Vector(double n1, double n2, Mode from = RECT);
void reset(double n1, double n2, Mode from = RECT);
~Vector();
double xval() const { return x;};
double yval() const { return y;};
double magval() const { return mag;};
double angval() const { return ang;};
void polar_mode();
void rect_mode();
Vector operator+(const Vector &b) const;
Vector operator-(const Vector &b) const;
Vector operator-() const;
Vector operator*(double n) const;
friend Vector operator*(double n, const Vector &a);
friend std::ostream & operator<< (std::ostream &os, const Vector &v);
};
}
#endif //D1_VECTOR_H
vector.cpp
#include "vector.h"
#include <cmath>
namespace VECTOR{
const double Rad_to_deg = 45.0/std::atan(1.0);
void Vector::set_mag() {
mag = std::sqrt(x*x + y*y);
}
void Vector::set_ang() {
if (x == 0 && y == 0){
ang = 0.0;
}
ang = atan2(y,x);
}
void Vector::set_x() {
x = mag * cos(ang);
}
void Vector::set_y() {
y = mag * sin(ang);
}
Vector::Vector() {
x = y = mag = ang = 0.0;
mode = RECT;
}
Vector::Vector(double n1, double n2, VECTOR::Vector::Mode from) {
mode = from;
if (from == RECT){
x = n1;
y = n2;
set_mag();
set_ang();
} else if (from == POL){
mag = n1;
ang = n2 /Rad_to_deg;
set_x();
set_y();
} else {
std::cout << "Incorrect 3rd argment to Vector() --";
std::cout << "vector set to 0\n";
x = y = mag = ang = 0.0;
mode = RECT;
}
}
void Vector::reset(double n1, double n2, VECTOR::Vector::Mode from) {
mode = from;
if (from == RECT){
x = n1;
y = n2;
set_mag();
set_ang();
} else if (from == POL){
mag = n1;
ang = n2 /Rad_to_deg;
set_x();
set_y();
} else {
std::cout << "Incorrect 3rd argment to Vector() --";
std::cout << "vector set to 0\n";
x = y = mag = ang = 0.0;
mode = RECT;
}
}
Vector::~Vector() {}
void Vector::polar_mode() { mode = POL;};
void Vector::rect_mode() { mode = RECT;};
Vector Vector::operator+(const VECTOR::Vector &b) const {
return Vector(x + b.x, y+b.y);
}
Vector Vector::operator-(const VECTOR::Vector &b) const {
return Vector(x-b.x,y-b.y);
}
Vector Vector::operator-() const {
return Vector(-x,-y);
}
Vector Vector::operator*(double n) const {
return Vector( n * x, n*y);
}
Vector operator*(double n, const VECTOR::Vector &a) {
return a * n;
}
std::ostream& operator<<(std::ostream &os, const VECTOR::Vector &v) {
if ( v.mode == Vector::RECT){
os << "(x,y) = (" << v.x << ", " << v.y << ")";
} else if (v.mode == Vector::POL){
os << "(m,a) = (" << v.mag << ", " << v.ang * Rad_to_deg << ")";
} else {
os << "Vector object mode is invalid";
}
return os;
}
}
randwalk.cpp
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "vector.h"
int main(){
using namespace std;
using VECTOR::Vector;
srand(time(0));
double direction;
Vector step;
Vector result(0.0,0.0);
unsigned long steps = 0;
double target;
double dstep;
cout << "Enter target distance (q to quit): ";
while (cin >> target) {
cout << "Enter step length: ";
if (!(cin >> dstep)){
break;
}
while (result.magval() < target){
direction = rand() % 360;
step.reset(dstep,direction,Vector::POL);
result = result + step;
steps++;
}
cout << "After " << steps << " steps,the subject has the following location:\n";
cout << result << endl;
result.polar_mode();
cout << " or\n" << result << endl;
cout << "Average outward distance per step = " << result.magval()/steps << endl;
steps = 0;
result.reset(0.0,0.0);
cout << "Enter target distance (q to quit): ";
}
cout << "Bye!\n";
cin.clear();
while (cin.get() != '\n') continue;
return 0;
}
类的自动转换和强制类型转换:
C++是如何处理内置类型转换的?将一个标准型变量的值赋值给另一种标准类型的变量时,如果这两种类型兼容,则C++自动将这个值转换为接收变量的类型,如:
long count = 9;
double time = 12;
int side = 12.3
上面赋值语句是可行的,因为在C++看来,各种数值类型都表示相同的东西,一个数字,同时,C++包含用于转换的内置规则
C++不自动转换不兼容的类型,比如:
int * p = 10; // 一个是指针,一个是数字
虽然计算机内部可能使用整数表示地址,但从概念上说,整数和指针不同。例如,不能计算指针的平方。然而,在无法自动转换时,可进行强制类型转换
int * p = (int *) 10;
可以将类定义成与基本类型或另一个类相关,使得从一种类型转换为另一种类型是有意义的。在这种情况下,程序员可以指示C++如何自动进行转换,或通过强制类型转换来完成。例如
stonewt.h
#ifndef D1_STONEWT_H
#define D1_STONEWT_H
class stonewt {
private:
enum {Lbs_per_stn = 14};
int stone;
double pds_left;
double pounds;
public:
stonewt(double lbs);
stonewt(int stn, double lbs);
stonewt();
~stonewt();
void show_lbs() const;
void show_stn() const ;
};
#endif //D1_STONEWT_H
stonewt.cpp
#include "stonewt.h"
#include <iostream>
stonewt::stonewt(double lbs) {
stone = int(lbs) / Lbs_per_stn;
pds_left = int(lbs) % Lbs_per_stn + lbs - int(lbs);
pounds = lbs
}
stonewt::stonewt(int stn, double lbs) {
stone = stn;
pds_left = lbs;
pounds = stn * Lbs_per_stn + lbs;
}
stonewt::stonewt() {
stone = pounds = pds_left = 0;
}
stonewt::~stonewt() {
}
void stonewt::show_lbs() const {
std::cout << pounds << " poundds\n";
}
void stonewt::show_stn() const {
std::cout << stone << " stone, " << pds_left << " poundds\n";
}
在C++中,接受一个参数的构造函数为将类型转换与该参数相同的值转化为类提供了蓝图。因此,下面的构造函数用于将double类转换为stonewt类型
stonewt sw = 120.6;
程序将使用构造函数stonewt::stonewt(double lbs)来创建一个临时的stonewt对象,并将120.6作为初始值。随后,采用逐成员赋值方式将该临时对象的内容复制到sw中这一过程称为隐式转换,因为它是自动的,不需要显式强制转换。
只有接受一个参数的构造函数才能作为转换函数。下面的构造函数有两个参数,因此不能用来转换
stonewt::stonewt(int stn, double lbs)
但是如果给第二个参数提供默认值,它就可用于int类型转换
stonewt::stonewt(int stn, double lbs = 0.0)
将构造函数用作自动类型转换函数似乎是一个不错的特性。然而,当程序员拥有更丰富的C++j经验时,将发现这种自动特性并非总是合乎需求的,因为这会导致意外的类型转换。因此,C++新增了关键字explicit,用于关闭这种隐式转换,但显式转换仍可用
explicit stonewt(double lbs);
// stonewt sw = 12; 隐式转换不可使用
// stonewt sw = stonewt(12);
stonewt sw = (stonewt) 12; // 强制类型转换可使用
编译器在什么时候使用stonewt(double)函数呢?如果声明了explicit,则stonewt将只用于显式强制类型转换,否则也可 用于下面 的隐式转换:
- 将stonewt对象初始化为double值时。
- 将double值赋给stonewt对象时。
- 将double值传递给接受stonewt参数的函数时。
- 返回值声明为stonewt的函数试图返回double值时。
- 在以上任意一种情况下,使用可转化为double类型的内置类型。
函数原型化提供的参数匹配过程,允许使用stonewt(double) 构造函数类转换其他数值类型。也就是说,下面两条语句都首先将int类型转换为double类型,然后使用stonewt(double)构造函数。
stonewt jum = 7300;
jum = 8000;
然而,仅当转换不存在二义性时,才会进行这种二步转换,也就是说,如果这个类型还定义了构造函数stonewt(long),这编译器拒绝这些语句。
stone.cpp
#include <iostream>
#include "stonewt.h"
using std::cout;
void display(const stonewt & st,int n);
int main(int argnum, char *args[]) {
display(1.3,4);
return 0;
}
void display(const stonewt & st,int n){
st.show_stn();
}
转换函数:
operator int() const; // stonewt.h
stonewt::operator int() const { // stone.cpp
return lround(pounds);
}
要转换为typeName类型,需要使用这种形式的转换函数:
operator typeName();
注意:
- 转换函数必须是类方法;
- 转换函数不能指定返回类型;
- 转换函数不能有参数;
自动应用类型转换:
如果类中定义了多个转换函数:
operator int() const;
operator double() const;
使用
stonewt sw = 22.3;
cout << sw ;
将不能进行转换,因为cout时,sw没有指明要转换为哪种类型。在缺少信息的情况下,编译器将指出,程序中使用了二义性转换。
赋值情况与此类似:
long gone = sw;
在c++中,int与double都可赋值给long,存在二义性。
在C++98中explicit 不能用于转换函数,但是C++11 中消除了这种限制,因此,在C++11中,可将转换运算符盛声明为显式的