C++学习4:详解不带指针的类(侯捷Complex类为例)

学习C++,希望可以像侯捷老师期望的那样,一出手就是大家风范!!
侯捷老师所说的大气的代码是这样写出来的,这里我结合视频编解码代码常用的风格加以调整,讲解如下:

文章目录

  • 一、头文件
    • 1.1 防卫式声明:
    • 1.2 前置声明
    • 1.3 类的声明与定义
      • 1.3.1 内联函数inline
      • 1.3.2 访问级别
      • 1.3.3 构造函数
      • 1.3.4 重载
      • 1.3.5 常量函数: 圆括号后面,花括号前面的const
      • 1.3.6 参数传递,(会用到const)
      • 1.3.7 返回值传递,(会用到const)
      • 1.3.8 友元
      • 1.3.9 模板
  • 二、源文件
    • 2.1 前置声明
    • 2.2 函数体内注意事项
      • 2.2.1 返回方式
      • 2.2.2 操作符重载——成员函数及this
      • 2.2.3 操作符重载——非成员函数(无this)
      • 2.2.4 全局(global)函数
      • 2.2.5 返回类型想远些
  • 三、main函数

一、头文件

头文件最好包括四部分:防卫式声明、前置声明、类的声明、部分类的定义及声明。

1.1 防卫式声明:

在大型工程中应当加以使用,如下例:

#ifndef  __Complex__
#define __Complex__

//其余部分......

#endif // ! __Complex__

1.2 前置声明

定义类的时候需要引入一些库或类,应放于类的声明之前,如下例:

#ifndef  __Complex__
#define __Complex__

/****** 前置声明 ******/
#include
class ostream;
class complex;

complex& __doapl(complex* ths, const complex& r);
/**** 前置声明完毕 ****/

//其他部分

#endif // ! __Complex__

1.3 类的声明与定义

我们先看例子再进行下面的剖析:

/****** 防卫式声明 *****/
#ifndef  __Complex__
#define __Complex__


/****** 前置声明 ******/
#include
#include 
using namespace std;
class complex;
complex& __doapl(complex* ths, const complex& r);
/**** 前置声明完毕 ****/


/****** 类的声明 ******/
class complex
{
public:
	complex(double r = 0, double i = 0)
		:re(r), im(i)
	{}
	complex& operator += (const complex&);//有分号,声明
	double real() const { return re; }//有大括号,定义
	double imag() const { return im; }

private:
	double re, im;
	friend complex& __doapl(complex* , const complex& );
};
/**** 类的声明完毕 ****/


/***** 函数声明 *****/
inline complex& __doapl(complex* ths, const complex& r);
inline double imag(const complex& x);
inline double real(const complex& x);
inline complex operator + (const complex& x, const complex& y);
inline complex operator + (const complex& x, double y);
inline complex operator + (double x, const complex& y);
inline complex operator + (const complex& x);
inline complex operator - (const complex& x);
inline bool operator == (const complex& x, const complex& y);
inline bool operator == (const complex& x, double y);
inline bool operator == (double x, const complex& y);
inline bool operator != (const complex& x, const complex& y);
inline bool operator != (const complex& x, double y);
inline bool operator != (double x, const complex& y);
inline complex conj(const complex& x);
ostream& operator << (ostream& os, const complex& x);
/*** 函数声明完毕 ***/


#endif // ! __Complex__
/*** 防卫式声明完毕 ***/

1.3.1 内联函数inline

是一种请求,是inline的会很快、很好,由编译器最后决定是否inline。函数在class本体里定义定义完成,就是有inline请求的。在外面也可以请求inline,见下方 “二 源文件” 部分

	complex& operator += (const complex&);//有分号,属于类的声明,看在外界的定义是否请求内联函数
	double real() const { return re; }//有花括号,属于类的定义,是内联函数
	double imag() const { return im; }

1.3.2 访问级别

1、public:公开的,外界看得到的,打算被外界调用的函数放在public里。
2、private:私有的,不为外界所见的,所有的数据和一些打算内部处理的函数都应当放在private里,想传出去都需要通过对应的函数,这也就是封装性。

publicprivate

1.3.3 构造函数

定义:如果想创建一个对象,会自动调用的一个函数,即构造函数。所以构造函数就是用于创建对象的。
1.构造函数的名称一定与类的名称要相同,且没有返回类型。
2.括号内应该有参数,参数可以有默认值,若创建对象没指明时,则使用默认参数:

	complex(double r = 0, double i = 0)

3.要有初始列,用于做初始化操作:

	complex(double r = 0, double i = 0)
		:re(r), im(i)
	{}

4.要有函数体,可以做赋值操作(更多的情况:经常为空,即空的花括号)

	complex(double r = 0, double i = 0)
	{ re = r; im = i;}

注意:初始化和赋值是不同操作。原因:初始化后多了一步赋值操作,效率变差,因此最好使用初始列。
5.构造函数一般不放进private里,除非不允许被外界创建,如singleton(单例)设计模式,这里需要用static,类名直接调用。
PS:有构造函数就应该有析构函数,不过不带指针的类通常不用写析构函数。

1.3.4 重载

同名的函数,但应当调用函数的不同,常常发生在构造函数中。主要构造函数有默认值的情况,常常会发生冲突,不能重载。
可以重载的情况:

	double real() const { return re; }
	void real(double r) {re = r;}

不能重载的情况:

	complex(double r = 0, double i = 0):re(r), im(i){}
	complex():re(0), im(0){}

1.3.5 常量函数: 圆括号后面,花括号前面的const

成员函数可以有如下操作:

	double real() const { return re; }
	double imag() const { return im; }

1、不会改变数据的情况:real()和imag()函数只是把实部re和虚部im读出来,而不会改变实部虚部的值。这时候马上加上const。
2、会改变数据的情况:如果是写进去re和im的值

注意:
1、当成员函数的const和non-const版本同时存在,const object只会(只能)调用const版本,保证数据不改变;non-const object只会(只能)调用non-const版本。
2、若non-const object调用了const成员函数(没有非const成员函数的版本),则也可以确保数据不发生改变。
3、常量对象不应该调用非常量成员函数.
4、圆括号后面,花括号前面的const算是函数签名的一部分。
5、COW:(copy on write)不可牵一发而动全身。常量函数可以不考虑COW,而非常量函数必须要考虑COW。

1.3.6 参数传递,(会用到const)

C++学习4:详解不带指针的类(侯捷Complex类为例)_第1张图片
1、值传递 pass by value:有几个字节就传几个字节,所以尽量不用值传递。(如果希望不改内部值,可用值传递,但情况很少见,且一般也能用pass by reference to const代替)
2、引用传递 pass by reference :传一个固定的字节数(貌似是4?)传递速度很快,建议都传引用。
3、pass by reference to const:用于传过去只是为了速度,而不希望“一改则改从根改”。
注:ostream不用const

1.3.7 返回值传递,(会用到const)

C++学习4:详解不带指针的类(侯捷Complex类为例)_第2张图片
1、返回值 return by value
2、返回引用 return by reference:尽量返回引用,但并不是都可以返回引用
(究竟何时返回何种呢?见本文2.2.1部分)

1.3.8 友元

friend:打破了封装。对private的数据可以访问。可以直接拿,而不必通过某个函数来拿

friend complex__doapl(complex* , const complex& );

注:相同class里的各个对象互为友元!!

1.3.9 模板

模板的作用有很多,但大同小异,这里先学习一下对类型的模板:

template <typename T>
class complex
{
public:
	complex(T r = 0, T i = 0)
		:re(r), im(i)
	{}
	complex& operator += (const complex&);
	T real() const { return re; }
	T imag() const { return im; }

private:
	T re, im;
	friend complex__doapl(complex* , const complex& );
};

这里T可以代表任何数字相关的类型,使用起来也很方便,用尖括号包住一种数据类型,即可将T换成该数据类型。
使用也很方便,如下:

complex<int> c(3,5);

二、源文件

不同于头文件,这里通常放class外的一些定义。如下例:

#include"Complex.h"

inline complex&
__doapl(complex* ths, const complex& r)
{
	ths->re += r.re;
	ths->im += r.im;
	return *ths;
}

inline complex&
complex::operator += (const complex& r)
{
	return __doapl(this, r);
}

inline double
imag(const complex& x)
{
	return x.imag();
}

inline double
real(const complex& x)
{
	return x.real();
}

inline complex
operator + (const complex& x, const complex& y)
{
	return complex(real(x) + real(y), imag(x) + imag(y));
}

inline complex
operator + (const complex& x, double y)
{
	return complex(real(x) + y, imag(x));
}

inline complex
operator + (double x, const complex& y)
{
	return complex(x + real(y), imag(y));
}


inline complex
operator + (const complex& x)
{
	return x;
}

inline complex
operator - (const complex& x)
{
	return complex(-real(x), -imag(x));
}

inline bool
operator == (const complex& x, const complex& y)
{
	return real(x) == real(y) && imag(x) == imag(y);
}

inline bool
operator == (const complex& x, double y)
{
	return real(x) == y && imag(x) == 0;
}

inline bool
operator == (double x, const complex& y)
{
	return x == real(y) && imag(y) == 0;
}

inline bool
operator != (const complex& x, const complex& y)
{
	return real(x) != real(y) || imag(x) != imag(y);
}

inline bool
operator != (const complex& x, double y)
{
	return real(x) != y || imag(x) != 0;
}

inline bool
operator != (double x, const complex& y)
{
	return x != real(y) || imag(y) != 0;
}


inline complex
conj(const complex& x)
{
	return complex(real(x), -imag(x));
}

ostream&
operator << (ostream& os, const complex& x)
{
	return os << '(' << real(x) << ',' << imag(x) << ')';
}

2.1 前置声明

定义类的时候需要引入一些库或类,应放于类的声明之前,在class body外的也是一样,通常引用类名所在的头文件名及相关其他。

2.2 函数体内注意事项

2.2.1 返回方式

可以返回引用:第一参数将会被改动,第二参数不会被改动。如c1 += c2,将c1+c2的值存在c1中,c1的这块内存本来就是存在的。这时可以返回引用
只能返回值:两个参数相加,如c1+c2,算得的结果既不存在c1,也不存在c2中,要创建一个内存存它,函数结束后这块内存就自动释放了,所以必须返回值。

inline complex&
_doapl(complex* ths, const complex& r) {//doapl表示:do assignment plus。即赋值加法
	ths->re += r.re;
	ths->im += r.im;
	return *ths;
}

一个疑惑,返回出来的东西如下:

complex& 
			*ths

这里:*ths是个对象,是个值,接收的是个引用。所以我们可以得知:传递者无需知道接收端是以何种(引用)方式接收。所以是可以的。

2.2.2 操作符重载——成员函数及this

成员函数:放在类里的函数,定义的时候要加上类名和::。如complex::
定义某种操作。如复数的加法不能像实数的加法一样,故加号“+=”不能使用,而定义一个plus函数不够直观。因此在这里想继续使用“+=”,就要对其进行重新定义,这个过程就是操作符的重载。
如下代码阐述了+=和this的用法,请结合注释来理解:

inline complex&
complex::operator += (  /*这里隐藏了一个 “ this,”(不能写出来)*/  const complex& r) {
	return _doapl(this, r);
}

//如做一次c2 += c1,这里this就是c2,r就是c1
//返回的是个函数,则继续进行下一个操作

下面这个虽然不是成员函数,但放在这里为了连接上文便于理解。

inline complex&
__doapl(complex* ths, const complex& r)
{
	ths->re += r.re;
	ths->im += r.im;
	return *ths;
}
//把这个符号作用在左边,即返回的是c2,返回的*ths就是c2的值。

任何一个成员函数都有一个隐藏的指针this,指向调用者。这里的c2调用的 += 操作,故返回的是c2。

2.2.3 操作符重载——非成员函数(无this)

为了应对多种情况,常要定义多种形式的操作符重载。
1.下面是加号的操作符重载

inline complex
operator + (const complex& x, const complex& y)
{
	return complex(real(x) + real(y), imag(x) + imag(y));
}

inline complex
operator + (const complex& x, double y)
{
	return complex(real(x) + y, imag(x));
}

inline complex
operator + (double x, const complex& y)
{
	return complex(x + real(y), imag(y));
}

这些返回值绝对不可以是引用!
因为他们返回的必定是一个临时变量,是一个临时创建出来的,离开函数之后就没有了。所以要返回一个值。
临时对象:typename():如int(2)。complex(real(x) + real(y), imag(x) + imag(y))返回的就是一个临时的complex类型值。生存期:到了下一行就没有了。这种情况一般程序员很少用,但标准库中很多。
2.下面这部分表示重载正号+和负号-,是通过参数数量来决定调用的版本。

inline complex//标准库是这样写的,但侯老师任务,这里返回引用是更好的
operator + (const complex& x)
{
	return x;
}

inline complex
operator - (const complex& x)
{
	return complex(-real(x), -imag(x));//临时对象
}

3.下面是等号及不等号的操作符重载:

inline bool
operator == (const complex& x, const complex& y)
{
	return real(x) == real(y) && imag(x) == imag(y);
}

inline bool
operator == (const complex& x, double y)
{
	return real(x) == y && imag(x) == 0;
}

inline bool
operator == (double x, const complex& y)
{
	return x == real(y) && imag(y) == 0;
}
inline bool
operator != (const complex& x, const complex& y)
{
	return real(x) != real(y) || imag(x) != imag(y);
}

inline bool
operator != (const complex& x, double y)
{
	return real(x) != y || imag(x) != 0;
}

inline bool
operator != (double x, const complex& y)
{
	return x != real(y) || imag(y) != 0;
}

4.输出符号(<<)的重载:
cout可以代表是“屏幕”。只能输出内置类型的东西,所以我们要输出新类型,一定重载一次<<,且必须是非成员函数类型的重载:

#include 
ostream&
operator << (ostream& os, const complex& x)
{
	return os << '(' << real(x) << ',' << imag(x) << ')';
}

2.2.4 全局(global)函数

任何操作都可以有两种想法:一种是将其设计为成员函数,另一种是将其设计为全局函数
前面没有类名的函数:

inline double
imag(const complex& x)
{
	return x.imag();
}

inline double
real(const complex& x)
{
	return x.real();
}

inline complex
conj(const complex& x)
{
	return complex(real(x), -imag(x));
}

2.2.5 返回类型想远些

ostream&
operator << (ostream& os, const complex& x)
{
	return os << '(' << real(x) << ',' << imag(x) << ')';
}

inline complex&
complex::operator += (  /*这里隐藏了一个 “ this,”(不能写出来)*/  const complex& r) {
	return _doapl(this, r);
}

这里的返回值都可以用void,但为了防止以下这种“连发”的情况。

cout << c1 << c2;
c1 += c2 += c3;

三、main函数

在写main函数之前先回顾一下如些才更有大家风范:
1.构造函数用没用初始列;
2.函数要不要加const;
3.参数的传递尽量考虑引用传递;
4.return的应该用值还是引用;
5.数据要放在private里,函数主要应放在public里。

以下是一种本类中一种main函数的例子:

#include "Complex.h"

int main()
{
	complex c1(2, 1);
	complex c2(4, 0);

	cout << c1 << endl;
	cout << c2 << endl;
	cout << c1 + c2 << endl;
	cout << conj(c1) << endl;
	cout << (c1 += c2) << endl;
	cout << (c1 == c2) << endl;
	cout << (c1 != c2) << endl;
	cout << +c2 << endl;
	cout << -c2 << endl;
	cout << (5 + c2) << endl;

	return 0;
}

你可能感兴趣的:(侯捷老师C++)