C++(学习笔记)使用类

文章目录

  • 前言
  • 一、运算符重载
    • 重载限制
  • 二、友元
    • 1.创建友元
    • 2.常用的友元:重载<<运算符
  • 三、一个矢量类
  • 四、类的自动转换和强制类型转换
    • 转换函数
  • 总结


前言

  本人在阅读C++ primer plus(第六版)的过程中做的笔记,写这篇文章既是为了分享,也是为了以后查阅。以下是本篇文章正式内容。


一、运算符重载

  要重载运算符,需要使用运算符函数:
  operatorop(argument-list);
  例如,operator+()重载+运算符,operator*()重载*运算符,operator重载[]运算符。例如将两个Time对象相加:

Time Time::Sum(const Time &t) const
{
	Time sum;
	sum.minutes = minutes + t.minutes;
	sum.hours = hours + t.hours + sum.minutes / 60;
	sum.minutes %= 60;
	return sum;
}

  使用重载+运算符的函数只需将函数名改为operator+:

Time Time::operator+(const Time &t) const
{
	Time sum;
	sum.minutes = minutes + t.minutes;
	sum.hours = hours + t.hours + sum.minutes / 60;
	sum.minutes %= 60;
	return sum;
}

  和Sum()一样,operator+()也是由对象调用的,它将第一个对象当作调用对象,第二个对象当作参数,可以像调用Sum()那样调用operator+():
  total = coding.operator+(fixing);   //函数表示法
  total = coding + fixing;      //运算符表示法
  在运算符表示法中,运算符左侧的对象是调用对象,右侧的对象是作为参数被传递的对象。

重载限制

  1. 重载后的运算符必须至少有一个操作数是用户定义的类型;
  2. 不能违反运算符原来的句法规则,例如不能将求模运算符%重载为使用一个操作数;不能修改运算符的优先级;
  3. 不能创建新运算符;
  4. 不能重载下面的运算符:
    (1)sizeof : sizeof运算符;
    (2). : 成员运算符;
    (3)* : 成员指针运算符;
    (4):: : 作用域解析运算符;
    (5)?: : 条件运算符;
    (6)typeid : 一个RTTI运算符;
    (7)const_cast : 强制类型转换运算符;
    (8)dynamics_cast : 强制类型转换运算符;
    (9)reinterpret_cast : 强制类型转换运算符;
    (10)static_cast : 强制类型转换运算符。
  5. 下表中的运算符都可以通过成员函数或非成员函数进行重载,但下面的运算符只能通过成员函数进行重载:
    (1)= : 赋值运算符;
    (2)() : 函数调用运算符;
    (3)[] : 下标运算符;
    (4)-> : 通过指针访问类成员的运算符。
+ - * / % ^
& | ~= ! = <
> += -= *= /= %=
^= &= |= << >> >>=
<<= == != <= >= &&
|| ++ -- , ->* ->
() [] new delete new[] delete[]

二、友元

  友元有三种:友元函数、友元类、友元成员函数。对于上面的Time类,如果让Time对象与一个double值相乘,则需要重载*运算符,而且*运算符左边的操作数是调用重载*函数的Time对象,右边的操作数是double值,也就是说对于Time对象t和double值m,只能这样使用:Time total = t * m,而不能Time total = m * t。成员函数和非成员函数都可以重载运算符,因此可以定义这样的非成员函数:
  Time operator*(const double &m, const Time &t);
  使Time total = m * t即Time total = operator*(m, t)可以使用。然而非成员函数不能直接访问类的私有数据,但是特殊的非成员函数——友元函数可以。

1.创建友元

  首先需要将友元函数的原型放在类声明中,并在原型前加上friend关键字:
  friend Time operator*(const double &m, const Time &t);
  有下面两点需要说明:
  ♦虽然函数在类声明中声明,但不是成员函数,不能使用成员运算符来调用;
  ♦关键字friend表明虽然它不是成员函数,但它与成员函数具有同样的访问权限。
  接下来是进行定义,友元函数不是成员函数,所以不使用Time::限定符,另外不使用关键字friend:

Time operator*(const double &m, const Time &t)
{
	Time result;
	long totalminutes = t.hours * m * 60 + t.minutes * m;
	result.hours = totalminutes / 60;
	result.minutes = totalminutes % 60;
	return result;
}

  有了上述声明和定义后,Time total = 2.75 * t可以使用,即Time total = operator*(2.75, t)。
  实际上,可以像下面这样修改定义:

Time operator*(const double &m, const Time &t)
{
	return t * m;									//调用成员函数
}

  修改定义后该函数不再访问类的私有数据成员,因此可以将其编写为非友元函数。

2.常用的友元:重载<<运算符

  <<运算符是需要两个操作数的运算符,如cout << x,cout是左操作数,x是右操作数。假设trip是Time类的一个对象,为了显示trip的值,可以使用cout << trip最好,所以我们需要重载<<运算符。但是左操作数必须是调用重载函数的对象,显然cout是ostream的对象,而不是Time类对象,而trip << cout太奇怪,所以我们需要通过友元函数来重载<<运算符,并且重载<<函数的第一个参数是cout的引用,第二个参数是trip的引用。而且函数返回类型必须是ostream &,否则下面的语句不能正常执行:
  cout << trip << “ Done!\n”;
  因为<<左操作数必须是ostream对象,重载<<运算符函数的返回类型为ostream &可以使cout << trip返回ostream对象用于输出字符串” Done!”。函数原型如下:
  friend ostream & operator<<(ostream &os, const Time &t);
  函数定义如下:

ostream & operator<<(ostream &os, const Time &t)
{
	os << t.hours << “ hours,<< t.minutes << “ minutes.\n”;
	return os;
}

  为什么可以返回ostream &?os在函数代码执行结束后不是会被释放吗?不是的,os不是在函数代码块中被创建的,而是以按引用传递的方式作为参数传递到函数中的,函数所返回的os其实就是作为函数参数被传递进来的os。
  有趣的是,重载<<运算符还可用于将输出写入到文件中:

#include 
#include 
……
ofstream fout;
fout.open(“saving.txt”);
Time trip(3, 25);
fout << trip;

  可行的原因是ofstream继承自ostream。
  友元函数和<<运算符重载示例:

mytime.h
#pragma once
#ifndef MYTIME_H_
#define MYTIME_H_

#include 

class Time
{
private:
	int hours;
	int minutes;
public:
	Time();
	Time(int h, int m = 0);
	void AddMin(int m);
	void AddHr(int h);
	void Reset(int h = 0, int m = 0);
	Time operator+(const Time &t) const;
	Time operator-(const Time &t) const;
	Time operator*(double n) const;
	friend Time operator*(double m, const Time &t)
	{
		return t * m;
	}
	friend std::ostream & operator<<(std::ostream &os, const Time &t);
};
#endif
mytime.cpp
#include "mytime.h"

Time::Time()
{
	hours = minutes = 0;
}

Time::Time(int h, int m)
{
	hours = h;
	minutes = m;
}

void Time::AddMin(int m)
{
	minutes += m;
	hours += minutes / 60;
	minutes %= 60;
}

void Time::AddHr(int h)
{
	hours += h;
}

void Time::Reset(int h, int m)
{
	hours = h;
	minutes = m;
}

Time Time::operator+(const Time &t) const
{
	Time sum;
	sum.minutes = minutes + t.minutes;
	sum.hours = hours + t.hours + sum.minutes / 60;
	sum.minutes %= 60;
	return sum;
}

Time Time::operator-(const Time &t) const
{
	Time diff;
	int tot1, tot2;
	tot1 = t.minutes + 60 * t.hours;
	tot2 = minutes + 60 * hours;
	diff.minutes = (tot2 - tot1) % 60;
	diff.hours = (tot2 - tot1) / 60;
	return diff;
}

Time Time::operator*(double mult) const
{
	Time result;
	long totalminutes = hours * mult * 60 + minutes * mult;
	result.hours = totalminutes / 60;
	result.minutes = totalminutes % 60;
	return result;
}

std::ostream & operator<<(std::ostream &os, const Time &t)
{
	os << t.hours << " hours, " << t.minutes << " minutes";
	return os;
}
usetime.cpp
#include "mytime.h"

int main()
{
	using std::cout;
	using std::endl;
	Time aida(3, 35);
	Time tosca(2, 48);
	Time temp;

	cout << "Aida and Tosca:\n";
	cout << aida << "; " << tosca << endl;
	temp = aida + tosca;
	cout << "Aida + Tosca: " << temp << endl;
	temp = aida * 1.17;
	cout << "Aida * 1.17: " << temp << endl;
	cout << "10.0 * Tosca: " << 10.0 * tosca << endl;

	return 0;
}

三、一个矢量类

  下面的程序清单模拟了随机漫步问题。其意思是,将一个人领到街灯住下。这个人开始走动,但每一步的方向都是随机的(与前一步不同)。这个问题的一种表述是,这个人走到离灯柱50英尺处需要多少步。从矢量的角度看,这相当于不断将方向随机的矢量相加,直到长度超过50英尺。

vector.h
#pragma once
#ifndef VECTOR_H_
#define VECTOR_H_

#include 
#include 

namespace VECTOR
{
	class Vector
	{
	public:
		enum Mode { RECT, POL };
	private:
		double x;
		double y;
		double mag;
		double ang;
		Mode mode;
	public:
		Vector();
		Vector(double n1, double n2, Mode form = RECT);
		~Vector() {};
		void setx();
		void sety();
		void setmag();
		void setang();
		void setmode(Mode md);
		double getx() const { return x; }
		double gety() const { return y; }
		double getmag() const { return mag; }
		double getang() const { return ang; }
		Mode getmode() const { return mode; }
		friend std::ostream & operator<<(std::ostream &os, const Vector &v);
		Vector operator+(const Vector &v) const;
		Vector operator*(const double &d) const;
		friend Vector operator*(const double &d, const Vector &v);
		Vector operator-(const Vector &v) const;
		Vector operator-() const;
		void reset(double n1, double n2);
	};
}

#endif
vector.cpp
#include "vector.h"

namespace VECTOR
{
	Vector::Vector()
	{
		x = y = mag = ang = 0.0;
		mode = RECT;
	}
	Vector::Vector(double n1, double n2, Mode form)
	{
		if (form == RECT)
		{
			mode = form;
			x = n1;
			y = n2;
			setmag();
			setang();
		}
		else if (form == POL)
		{
			mode = form;
			mag = n1;
			ang = n2;
			setx();
			sety();
		}
		else
		{
			std::cout << "出现了既不是直角坐标又不是极坐标的情况。\n";
		}
	}
	void Vector::setx()
	{
		x = mag * sin(ang);
	}
	void Vector::sety()
	{
		y = mag * cos(ang);
	}
	void Vector::setmag()
	{
		mag = sqrt(x * x + y * y);
	}
	void Vector::setang()
	{
		if (x == 0.0 && y == 0.0)
			ang = 0.0;
		else
			ang = atan2(y, x);								//atan2()是内置数学函数,由x, y值求得弧度
	}
	void Vector::setmode(Mode md)
	{
		mode = md;
	}
	Vector Vector::operator+(const Vector & v) const
	{
		Vector temp(x + v.x, y + v.y);
		temp.setmode(getmode());
		return temp;
		//return Vector(x + v.x, y + v.y);
	}
	Vector Vector::operator*(const double & d) const
	{
		Vector temp(mag * d, ang, POL);
		temp.setmode(getmode());
		return temp;
		//return Vector(mag * d, ang, POL);
	}
	Vector Vector::operator-(const Vector & v) const
	{
		Vector temp(x - v.x, y - v.y);
		temp.setmode(getmode());
		return temp;
		//return Vector(x - v.x, y - v.y);
	}
	Vector Vector::operator-() const
	{
		Vector temp(-x, -y);
		temp.setmode(getmode());
		return temp;
		//return Vector(-x, -y);
	}
	void Vector::reset(double n1, double n2)
	{
		if (mode == RECT)
		{
			x = n1;
			y = n2;
			setmag();
			setang();
		}
		else if (mode == POL)
		{
			mag = n1;
			ang = n2;
			setx();
			sety();
		}
		else
		{
			std::cout << "出现了既不是直角坐标又不是极坐标的情况。\n";
		}
	}
	std::ostream & operator<<(std::ostream &os, const Vector &v)
	{
		if (v.mode == Vector::RECT)
		{
			os << "(x, y) = (" << v.x << ", " << v.y << ")\n";
		}
		else if (v.mode == Vector::POL)
		{
			os << "(mag, ang) = (" << v.mag << ", " << v.ang << ")\n";
		}
		else
			std::cout << "出现了既不是直角坐标又不是极坐标的情况。\n";
		return os;
	}
	Vector operator*(const double & d, const Vector & v)
	{
		return v * d;
	}
}
main.cpp
#include "vector.h"
#include 												//包含rand()和srand()函数的原型
#include 												//包含time()函数原型
#include 

int main()
{
	//模拟随机漫步问题
	using std::cout;
	using std::cin;
	using std::endl;
	using std::ofstream;

	ofstream outFile;
	outFile.open("VECTOR.txt");

	using VECTOR::Vector;
	Vector step(0.0, 0.0, Vector::POL);							//单步
	Vector result(0.0, 0.0, Vector::POL);						//到达指定地点后的方向和距离
	double stepLength;											//步长
	double stepAngle;											//方向(单位为弧度)
	unsigned long stepCounts = 0;								//步数
	double targetLength;										//指定距离
	cout << "输入随机漫步的距离:";
	cin >> targetLength;
	srand(time(0));												//设置种子值

	outFile << "目标距离:" << targetLength << endl;
	outFile << stepCounts << ": " << result << endl;
	while (result.getmag() < targetLength)
	{
		stepLength = rand() % 3;
		stepAngle = rand() % 360;
		step.reset(stepLength, stepAngle);
		result = result + step;
		stepCounts++;
		outFile << stepCounts << ": " << result << endl;
	}
	cout << "模拟结束:" << result;
	cout << "一共走了" << stepCounts << "步。\n";
	outFile << "模拟结束:" << result;
	outFile << "一共走了" << stepCounts << "步,平均每步"
		<< result.getmag() / stepCounts << endl;

	outFile.close();
	return 0;
}

  谈谈程序中的随机数。库中有一个rand()函数,它返回一个从0到某个值的随机整数,使用求模操作符来实现。例如求从0到100(不包含100)的随机整数:rand() % 100。rand()函数用一个初始种子生成随机数,10次连续的使用通常将生成10个一样的随机数。然而,srand()函数允许覆盖默认的种子值,本程序使用time(0)的返回值作为种子,time(0)返回当前时间,通常为从某个日期开始的秒数,因此,srand(time(0))在每次程序运行时都将设置不同的种子值。rand()函数和srand()函数的原型在头文件cstdlib中,time()函数的原型在头文件ctime中。

四、类的自动转换和强制类型转换

  可以将构造函数用作自动类型转换函数,比如下面的构造函数将double类型的值转换为Stonewt类型:
  Stonewt(double lbs)
  {
    Stonewt myCat;
    myCat = 19.6;
  }
  程序将使用构造函数Stonewt(double)来创建一个临时的Stonewt对象,并将19.6作为初始值。随后采用逐成员赋值的方式将临时对象的内容复制到myCat中。这一过程被称为隐式转换,因为它是自动进行的。
  只有一个参数的构造函数可以用于自动转换,下面的构造函数有两个参数,不能用来转换类型:
  Stonewt(int stn, double lbs);
  如果给第二个参数提供默认值,它便可以用来转换int:
  Stonewt(int stn, double lbs = 0);
  关键字explicit可以关闭构造函数的自动转换特性:
  explicit Stonewt(double lbs);
  Stonewt myCat;
  myCat = 16.9;        //不允许,构造函数被声明为explicit
  myCat = Stonewt(16.9);   //允许,显示强制类型转换
  当构造函数只接受一个参数时,可以使用下面的格式来初始化类对象:
  Stonewt incognito = 275;
  它等价于另外两种格式:
  Stonewt incognito(275);
  Stonewt incognito = Stonewt(275);

转换函数

  数字可以转换为Stonewt类型,是否可以做相反的转换呢?也就是说,能否把Stonewt类型转换为double类型或者是int类型呢?就像下面的这样:
  Stonewt wolfe(285.7);
  double host = wolfe;    //?
  可以这样做,但不是使用构造函数,构造函数只用于从某种类型转换为类类型,要做相反的转换,需要使用特殊的C++运算符函数——转换函数。转换函数是用户定义的强制类型转换,可以像使用强制类型转换那样使用它们。
  Stonewt wolfe(285.7);
  double host = double(wolfe);    //第一种方式
  double host = (double)wolfe;    //第二种方式
  转换函数是这样创建的:
  operator typeName();      //typeName是要转换成的类型
  注意以下几点:
  ♦转换函数必须是类方法;
  ♦转换函数不能指定返回类型;
  ♦转换函数不能有参数。
  例如,要转换为double类型的转换函数是这样的:
  operator double();
  typeName指出了要转换为的类型,因此不需要返回类型;必须是类方法意味着它必须通过类对象来调用,从而告知函数要转换的值,因此不需要参数。
  如果需要将类类型转换为double和int类型,需要在类声明中加入以下这两个函数:

operator double() const;
{
	return pounds;						//pounds是double类型的数据成员
}
operator int() const;
{
	return int(pounds);
}

  在C++11中可以在转换函数前加上关键字explicit将其用于显示强制类型转换:
  explicit operator double() const;
  explicit operator int() const;


总结

  以上就是本文的内容——运算符重载、友元函数、一个模拟随机漫步问题的矢量类、类的自动转换和强制类型转换。

你可能感兴趣的:(C++基础知识,c++,开发语言,后端)