类与函数的规范化写法(二)—HJ-record02

目录

Header(头文件)的布局

类声明部分

constructor(ctor,构造函数)被放在private区

const member functions(常量成员函数)

参数传递:pass by value vs. pass by reference(to const)

返回值传递:return by value vs. return by reference(to const)

friend(友元)

相同class的各个objects互为friends(友元)

类声明部分代码

类定义部分

class body外的各种定义(definitions)

operator overloading(操作符重载-1,成员函数包含this指针)

用引用的好处:传递者无需知道接收者是以引用形式接收

operator overloading(操作符重载-2,非成员函数,无this指针)

temp object(临时对象):“typename();”

取反操作:negate

特殊符号的重载:”<<“操作符的重载

类定义部分代码

前置声明部分代码

如何区分程序员是不是有良好的编程习惯?


本篇接着上篇博客继续进行续记。

Header(头文件)的布局

类声明部分

constructor(ctor,构造函数)被放在private区

构造函数是通过对应的类创建对象的时候,自动调用的,如果把其放在了private区域,那么默认其不能被外界调用的,这就会出现矛盾,所以不能通过这种生硬的方式直接将构造函数放在private区域。

那是不是这就意味着不要把构造函数放在private区域了?也不是,这有独特的应用场景,比如,当我不希望为外界创建这个对象的时候,在Singleton(单体)设计模式中,意思是说,通过我所设计的这个A类给外界所设计的对象,外界只能创建一份,即强制要求我这个类所创建的对象只能为一份

那么这种场景下,就是在private区中创建了一个构造函数,那稍微再引申一下,那如何在不能调用构造函数的情况下去创建独有的对象呢?外界可以通过"A::getInstance().setup();"的方式,去调用A类中被规定为"static"的getInstance()函数去创建唯一的对象。所以,通过这个场景,希望你明白,在C++中,确实有一种需求是要把构造函数放在private区域中的。

const member functions(常量成员函数)

这里有个被很多人忽略的规范:要在函数的后头(小括号"()"的后面,大括号"{}"的前面),加关键字"const"

这是什么意思呢?以上面的real函数和image函数为例,这两个函数要完成取得复数实部跟虚部的任务,所以,这两个函数并不会改变这两个数据的,只是把这俩数据给取出来,那么这就又引出了一个区分类中的函数的标准:按是否会改变类中的数据来进行区分,一种是会改变类中的数据的,另一种是不会改变类中的数据的不会改变数据内容的函数,一定要加上const关键字!如果一个函数不会改变数据,那这个函数一定要加上const!

这个的应用场景是,比如你今天在设计接口,那么通过会改变数据,和不会改变数据先给区分出来,那么当进行实际函数定义的时候(可能是一个礼拜之后的事情了),就可以清晰的知道,那些函数是可以改变值的,那些是不可以的!

那如果该加const的时候,如果不加const会有什么后果呢?还是从编译器的角度去考虑,如果是如下方式调用的:

这样是没问题的,因为是正常的调用。

再看另一种调用的方式,在外界直接声明const,说明我调用的这个函数的值是不可改变的:

但是,当运行到内部的时候,编译器发现,函数内部的函数没有加const,说明内部函数是运行更改值的,那么,外界说不可更改,内部说允许更改,这不就矛盾了吗?遇到这种情况,编译器就会报错了!

参数传递:pass by value vs. pass by reference(to const)

如图上的标示,一个就是通过值传递,一个是引用传递。

值传递,就是整包传递,把所有的值都传给进去,如果值非常大,那把所有的字节(很多字节)给都传进去就效率非常低,在C中,可以传指针(相当于四个字节),而在C++中,可以传引用。传引用就相当于传指针,但引用的形式是很漂亮的!传引用速度很快!

最好所有的参数传递都传引用!,当然如过变量只有一个字节,或者两个字节,少于传递引用所用的四个字节,传值也是可以的,但这是小众了就。

那么再接着往下想,传引用的确是比传值的速度更快,但是,如果我传的值不想被更改的话,我是在函数中再创建一个变量来存放这个值,虽然慢,但我函数中如果更改这个值,是不会影响到函数外的这个值的,但是,传引用(指针)的话,我如果在函数中更改了值,那么函数外的值也会联动的被进行相应的更改,那其不是传引用在安全性上不让传值吗?

这样考虑是对的,为了解决这个问题,我们需要在引用前加"const"关键字,来让传进去的引用值,为只读属性,只能读,不能改。这样速度和安全性就兼得了!

比如,下面是一个重载的例子,单看函数名,就可以知道,os变量要在函数中被修改,而x变量在函数中仅作调用使用,甚至编程老鸟可以通过这种判断,一眼就可以看出一组函数接口各自变量所充当的角色!

返回值传递:return by value vs. return by reference(to const)

同样,在返回值传递的时候,也是应该用引用的,理由跟上面一样。

需要强调的是返回值也好,值也好,都是尽量鼓励在适合的场景用引用的方式传递,但并非所有的场景都需要,要根据实际情况来判断。 

friend(友元)

类的友元函数可以取类中的数据来用:

那么,在函数外,__diap1函数就可以去拿类中的私有成员变量,re和im:

友元的存在,其实是打破了函数的封装属性。 

相同class的各个objects互为friends(友元)

当位于一个类中的多个函数,可以互相进行调用彼此的数据,这是为什么呢?假设在这个复数类中有个函数func,这个函数可以获得传进来的一个复数的实部和虚部,通过这种直接拿的方式来使用:

外界调用:

 这样的话,不是破坏了封装性吗?而且,这种方式也没有友元的关键字,这是为什么呢?答案就是:相同class的各个objects互为friends(友元)

类声明部分代码

class complex
{
public:
  complex (double r = 0, double i = 0): re (r), im (i) { }
  complex& operator += (const complex&);
  complex& operator -= (const complex&);
  complex& operator *= (const complex&);
  complex& operator /= (const complex&);
  double real () const { return re; }
  double imag () const { return im; }
private:
  double re, im;

  friend complex& __doapl (complex *, const complex&);
  friend complex& __doami (complex *, const complex&);
  friend complex& __doaml (complex *, const complex&);
};

类定义部分

class body外的各种定义(definitions)

区分什么时候返回引用,什么时候返回值。举个例子:

看上面的这个函数,中间是进行一个“+=”的操作,那么它的逻辑是将第二个变量的值加到第一个变量的值上,最后和的结果,也是存在第一个变量的内存上,那么这个过程中,没有划分新的内存去存储加和的结果;

那么,如果是执行的“+”操作呢?那么就是第一个变量的值加上第二个变量的值,它们的结果,要存放在一个新开辟的内存空间上面,这个新开辟的内存空间的生命周期,是跟所在的函数的生命周期一致的,当函数消亡的时候,其对应的内存空间也就被析构掉了。

反过头来,再说我们最开始讨论的什么时候应该返回引用,什么时候应该返回值,对于第一种情况,就应该返回引用,因为第一个变量的内存空间并不是在当前这个函数中创建的,而是在进入函数前就创建好的,所以,即便这个当前函数析构了,也不会受到影响,所以,应该返回一个引用。

而对于第二种情况,如果返回引用的话,相当于返回一个在函数中新创建的一个地址,这个地址的内容是所存储的加和的值,那么当函数结束,所存储的值也会被析构,如果返回引用的话,只会得到一个被析构后的内存空间值,所以这种情况不能返回引用,要返回值。

operator overloading(操作符重载-1,成员函数包含this指针)

所有的成员函数都带着一个隐藏的参数,一个是this指针(谁调用这个函数,this就指向谁),不可以在参数里面写出来,但可以直接在函数中写出来用。

用引用的好处:传递者无需知道接收者是以引用形式接收

下面举个例子:

这个例子中出现了非常奇特的现象,返回的是一个指针指向的内容(传递者),或者可以理解为是一个值,而函数名中定义的返回形式是一个引用(接收者),这两个是不对营的,这样可以吗?答案是可以的,这便是个用引用来充当接收者的一个好处:传递者无需知道接收者是以引用形式接收。如果用指针来作为接收者,那传递的时候,必须跟接收者对应,才可以。那么这样对使用的人就非常方便了,就不用考虑传递者和接收者之间的关系,之间用就可以了。

那么,为什么"+="的这个重载操作函数的返回值不可以设定为"void"呢?这是,因为,如果使用者进行链式赋值的时候,就如上面的:“c3+=c2+=c1;”,先是把c1的值加到c2中,再把c2的结果加到了c3上,最后,要返回的结果是c3所存的变量,如果设置返回类型为"void",那不就没法返回值了嘛!因为把c1的结果加到c2上去之后,c2返回的是一个void,那么不就加到c3的时候是一个空值吗?细细体会一下可以发现,再编写函数的时候,要把所有的情况都给考虑到是非常重要且必要的。

operator overloading(操作符重载-2,非成员函数,无this指针)

如果执行下面的语句:

那编译器就开始找对应的函数来进行处理,至于编译器到底是先找成员函数还是先找非成员函数,我们是不知道的, 但是先找谁都无所谓,因为对于编译器而言,只能有一个是合适的,如果两个函数都合适,那编译器就会因为无法区分清楚而报错。那么对应的其实是有三个版本的,那对应的究竟那个是合适的呢?

 那么上面是三种重载函数,为了应对使用者不同场景的需要。需要注意的是,这种是非成员函数,是定义在类之外的,通过设定为内联函数的方式来进行的,所以,这种方式定义到函数,不会涉及到this指针的使用。

那么我们接着看,为什么在这种场景下,不返回一个引用类型,而都是返回的值呢(蓝色标亮的部分),答案也很简单,因为在者三个函数中执行的是“+”操作,而不是“+=”操作,这就意味着,在这三个函数中,加和的结果是要新开辟一个内存来进行存储的,在函数快结束时,把这个加和的值给返回出去,然后这个新开辟出来的内存空间就可以随着函数的消亡而消亡了,所以只能返回值,而不能返回引用。

temp object(临时对象):“typename();”

那么接着上面的思路再细究,不是说要“新开辟一个内存来进行存储”的吗?为啥没见到程序里面,有新建什么变量出来呢?这就牵扯到临时对象的概念了(对很多人而言,这个语法是陌生的,但这个在标准库里面是非常容易见到的),仔细对照上面的三个函数的return返回语句可以发现,它们都是“typename();”的形式,这其实就是一个创建临时变量的语法,就类似于"int(i)"这样一种语法,创建一个临时对象(临时创建出来的对象,也不想给他名称)出来。那么这个临时对象具体放的数据,就是括号里面数据计算之后的结果了。

应该强调的是临时对象的生命周期只有一行,用于return的时候,临时对象就为了返回一个值而存在,这种场景和合适。

其实临时对象在语法编程中很常见,就比如上面的“Complex();”这条语句其实也是创建一个临时对象,Complex是一个类名,也是一个typename,括号内容为空,说明会导入默认值进来。只不过,其生命周期很短,当进行到下一行的时候,临时对象就被析构了。同理,“Complex(4,5);”也是如此,而“Complex c1(2,1);”因为有名称c1,不会再第二行就被析构了。

取反操作:negate

“+"不仅可以表示加号,还可以表示正号,在被使用的时候,通过靠参数的个数来表现到底是加号还是正号。这个地方其实在第一个函数的时候,返回引用更好!(标准库也是有错误的)

特殊符号的重载:”<<“操作符的重载

设计的一个共轭复数:"conj()"吗,用"<<"输出出来,但如果连用的话,发现并不是我想要的那种效果,所以需要重载"<<"。

重载的过程,发现ostream前不能加const,因为ostream是标准库,当每次输出的时候,都在改变cout(os)的状态,下面再看返回的类型,因为输出到屏幕就是结果,没在乎还会传回什么结果,所以,按照常理返回值”void“也是可以的,但如果使用者会连续输出,进行链式编程,那么就不形了,可见不能写成“void”。

类定义部分代码

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 complex&
__doami (complex* ths, const complex& r)
{
  ths->re -= r.re;
  ths->im -= r.im;
  return *ths;
}
 
inline complex&
complex::operator -= (const complex& r)
{
  return __doami (this, r);
}
 
inline complex&
__doaml (complex* ths, const complex& r)
{
  double f = ths->re * r.re - ths->im * r.im;
  ths->im = ths->re * r.im + ths->im * r.re;
  ths->re = f;
  return *ths;
}

inline complex&
complex::operator *= (const complex& r)
{
  return __doaml (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, 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, const complex& y)
{
  return complex (real (x) * real (y) - imag (x) * imag (y),
			   real (x) * imag (y) + imag (x) * real (y));
}

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

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

complex
operator / (const complex& x, double y)
{
  return complex (real (x) / y, imag (x) / 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;
}

#include <cmath>

inline complex
polar (double r, double t)
{
  return complex (r * cos (t), r * sin (t));
}

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

inline double
norm (const complex& x)
{
  return real (x) * real (x) + imag (x) * imag (x);
}

 

前置声明部分代码

class complex; 
complex&
  __doapl (complex* ths, const complex& r);
complex&
  __doami (complex* ths, const complex& r);
complex&
  __doaml (complex* ths, const complex& r);

 

如何区分程序员是不是有良好的编程习惯?

  • 数据要放在private里面
  • 参数尽可能的用引用来传,看状况加const
  • 返回值尽可能用引用来传
  • 在类的本体中,应该加const的,就要加,如果不加,使用者使用的时候,编译器会报错
  • 构造函数的特殊写法(使用冒号的那种)
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值