侯捷《C++面向对象开发》——动手实现自己的复数类
前言
最近在看侯捷的一套课程《C++面向对象开发》,刚看完第一节introduction之后就被疯狂圈粉。感觉侯捷所提及所重视的部分也正是我一知半解的知识盲区,我之前也写过一些C++面向对象的程序,不过正如侯捷所说,我还仅仅停留于Object-based层面,写程序时总是在想如何封装好一个类,而不是Object-oriented强调类与类之间关系的设计。
这门课程分为两部分,第一部分讲Object-based,第二部分讲Object-oriented;第一部分又分为两部分:带指针的类的封装和不带指针类的封装。
本文将以模板库中的complx复数类的部分内容为核心,在分析源代码的同时,讲解一些良好的代码风格和编程习惯,比如inline内联函数的使用、friend友元函数的使用、函数参数及返回值何时pass by value何时pass by reference等等。
部分代码
complex.h
#ifndef __COMPLEX__
#define __COMPLEX__ class complex
{
public:
complex(double r = , double i = )
: 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&);
}; #endif
complex.cpp
#include "complex.h"
#include <iostream> using namespace std; 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));
} ostream& operator << (ostream& os, const complex& x)
{
return os << ' (' << real (x) << "," << imag (x) << ')';
}
源码解析
一、complex.h
1.1 initialization list
//程序1.1
complex(double r = 0, double i = 0)
: re (r), im (i)
{ }
构造函数参数缺省,比较常规。
值得注意的是,变量的初始化尽量放在初始化列表中(initialization list)。当然,完全可以在构造函数的函数体中赋值进行初始化。不过,侯捷指出,一个对象在产生过程中分为初始化和成功产生两部分,initialization list相当于在初始化过程中对变量赋值,而在函数体中赋值则是放弃了initialization list初始化这一过程,会降低效率。对于“性能榨汁机”的C++语言来讲,重视每个细节效率的重要性是毫无疑问的。
1.2参数及返回值传递方式
//程序1.2 2
complex& operator += (const complex&);
传递参数时,如果能用引用传递那么一定不要用值传递,因为值传递的过程中变量需要copy一份样本传入函数中,当参数很多或参数类型复杂时,会导致效率变慢。
其次,如果函数不会改变参数的值,一定要加const限定,在初学时养成良好的变成习惯尤为重要。
关于函数的返回值,同样是最好按引用传递,当然,有些情况无法按引用传递,这点将在2.3讲解。
其实,参数列表中还隐藏一个this,这点将在2.2讲解。
1.3友元函数
//程序1.3
friend complex& __doapl (complex*, const complex&);
我们可以看到,在complex.h文件的末尾定义了一个友元函数,友元函数打破了类的封装,它不是类的成员函数,却可以使用点操作符来获取类的private变量。当然,非友元函数也可以通过get函数来获取,不过速度会慢一些。
二、complex.cpp
2.1 友元函数及内联函数
//程序2.1
inline complex& __doapl(complex* ths, const complex& r)
{
ths->re += r.re;
ths->im += r.im;
return *ths;
}
我们首先来分析一下这个友元函数,这里有两点值得探讨:
第一这个函数将r的实部和虚部加到ths上,r在函数体中值没用发生改变,所以使用const限定。
第二这个函数被设计成inline内联函数,我们都知道,内联函数是把代码块直接复制到函数需要调用的地方,通过省略函数调用这一过程来提高效率,那么我们为什么不将所有函数都设计成内联函数呢?其实我们的inline声明只是对编译器的一个建议,对于过于复杂的函数来讲,及时我们声明了inline,编译器也会调用执行。所以,对于一些“小巧”的函数,我们尽量设计为内联函数。
2.2 隐藏的“this”
//程序2.2
inline complex& complex::operator += (const complex& r)
{
return __doapl (this, r);
}
操作符重载作为C++的特点之一,有令别的语言羡慕之处,当然也有些难以理解。
实际上,这个函数的参数还有一个隐藏的this,这个this就是函数调用者。
2.3 不能为reference的返回值
//程序2.3
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));
}
注意,这里函数的返回值不能返回reference,这其实是使用临时对象(typename ()),在函数体内定义变量,然后把这个变量的引用传递出去,函数结束后变量本体死亡,传出去的引用既没有意义了。
2.4 非成员函数的操作符重载
//程序2.4
ostream& operator << (ostream& os, const complex& x)
{
return os << ' (' << real (x) << "," << imag (x) << ')';
}
下面讲一下为什么有的操作符重载函数定义成非成员函数?
我们知道,操作符重载只作用在左边的操作数上,试想一下,如果把“<<”定义为成员函数,那每次调用岂不是要这样c1 << cout
完整代码
以上就是我在学习过程中特别注意的地方,下面给出complex类完整代码,只不过多了几种操作运算,大体思路完全一致。
complex.h
#ifndef __MYCOMPLEX__
#define __MYCOMPLEX__ class complex;
complex&
__doapl (complex* ths, const complex& r);
complex&
__doami (complex* ths, const complex& r);
complex&
__doaml (complex* ths, const complex& r); class complex
{
public:
complex (double r = , double i = ): 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&);
}; 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) == ;
} inline bool
operator == (double x, const complex& y)
{
return x == real (y) && imag (y) == ;
} 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) != ;
} inline bool
operator != (double x, const complex& y)
{
return x != real (y) || imag (y) != ;
} #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);
} ostream&
operator << (ostream& os, const complex& x)
{
return os << '(' << real (x) << ',' << imag (x) << ')';
} #endif //__MYCOMPLEX__
complex_test.cpp
#include <iostream>
#include "complex.h" using namespace std; int main()
{
complex c1(, );
complex c2(, ); cout << c1 << endl;
cout << c2 << endl; cout << c1+c2 << endl;
cout << c1-c2 << endl;
cout << c1*c2 << endl;
cout << c1 / << endl; cout << conj(c1) << endl;
cout << norm(c1) << endl;
cout << polar(,) << endl; cout << (c1 += c2) << endl; cout << (c1 == c2) << endl;
cout << (c1 != c2) << endl;
cout << +c2 << endl;
cout << -c2 << endl; cout << (c2 - ) << endl;
cout << ( + c2) << endl; return ;
}
总结
作为初学者,一定要养成良好的编程习惯,正如侯捷所说:“一出手就是大家风范”。
侯捷《C++面向对象开发》——动手实现自己的复数类的更多相关文章
- 侯捷 c++面向对象程序设计
基础知识 基于对象:Object Based 面对的是单一class的设计. 面向对象:Object Oriented 面对的是多重classes的设计,涉及到类和类之间的关系. 课程中设计到两种不同 ...
- 面向对象开发C++快速入门视频教程 C++基础加实战视频教程
课程目录: ├<C++面向对象高级开发(上)> │ ├1.C++编程简介.mp4 │ ├2.头文件与类的声明.mp4 │ ├3.构造函数.mp4 │ ├4.参数传递与返回值.mp4 │ ├ ...
- 评侯捷的<深入浅出MFC>和李久进的<MFC深入浅出>
侯捷的<深入浅出mfc>相信大家都已经很熟悉了,论坛上也有很多介绍,这里我就不多说了. 而李久进的<mfc深入浅出>,听说的人可能就少得多.原因听说是这本书当时没有怎么宣传,而 ...
- 新手如何理解JS面向对象开发?
今天有时间讲讲我对面向对象的理解跟看法,尽量用通俗的语言来表达,多多指教! 如今前端开发已经越来越火了,对于前端开发的要求也是越来越高了,在面试中,经常有面试官会问:你对JS面向对象熟悉吗? 其实,也 ...
- 侯捷STL课程及源码剖析学习2: allocator
以STL 的运用角度而言,空间配置器是最不需要介绍的东西,它总是隐藏在一切组件(更具体地说是指容器,container)的背后,默默工作默默付出. 一.分配器测试 测试代码 #include < ...
- C++标准库(体系结构与内核分析)(侯捷第二讲)
一.OOP和GP的区别(video7) OOP:面向对象编程(Object-Oriented programming) GP:泛化编程(Generic programming) 对于OOP来说,我们要 ...
- Java面向对象编程 第一章 面向对象开发方法概述
一.软件开发经历的生命周期: ①软件分析 ②软件设计 ③软件编码 ④ 软件测试 ⑤ 软件部署 ⑥软件维护 二.为了提高软件开发效率,降低软件开发成本,一个优良的软件系统应该具备以下特点: ① 可重用性 ...
- Java面向对象 第一章 面向对象开发方法概述
一.软件开发经历的生命周期: ①软件分析 ②软件设计 ③软件编码 ④ 软件测试 ⑤ 软件部署 ⑥软件维护 二.为了提高软件开发效率,降低软件开发成本,一个优良的软件系统应该具备以下特点: ① 可重用性 ...
- ASP.NET的简单与面向对象开发
ASP.NET开发,一开始是为了超赶时间完成任务,只能把功能实现即可.如下面一个功能,在网页中有一个铵钮,用户点一点切换网页的图片,再点一点又切换回来.我们要怎样做?在铵钮事件中去变更图片的路径即可. ...
随机推荐
- 根据时间显示不同的问候语的JavaScript代码
对于最近有许多的初学开发者问我关于根据时间显示不同的问候语的JavaScript代码问题,所以今天将自己整理的一些代码在这里分享出来,供初学者参考,如果在运行过程中有问题,可以给我在下方留言. < ...
- jquery validation yyyy-MM-dd格式日期在ie中无法验证通过
自己开发的公众号,可以领取淘宝内部优惠券 问题 首先在ie6.7.8下面打开这个页面http://jqueryvalidation.org/date-method/,输入的日期格式为:yyyy-MM- ...
- 1.C#中的注释符
1.软件行业的道德规范 (1).程序员在日常写代码的过程中,一定要养成注释的好习惯,方便后面对理解和使用. (2).在给标识符命名的时候一定要规范,有理有据的,名字不能瞎写. 2.注释 注释符的作用: ...
- 洛谷P3128 [USACO15DEC]最大流Max Flow(树上差分)
题意 题目链接 Sol 树上差分模板题 发现自己傻傻的分不清边差分和点差分 边差分就是对边进行操作,我们在\(u, v\)除加上\(val\),同时在\(lca\)处减去\(2 * val\) 点差分 ...
- 微信小程序实战篇:商品属性联动选择(案例)
本期的微信小程序实战篇来做一个电商网站经常用到的-商品属性联动选择的效果,素材参考了一点点奶茶. 效果演示: 商品属性联动.gif 代码示例 1.commodity.xml <!-- < ...
- php cur错误:SSL错误 unable to get local issuer certificatebool
采集https链接时出现的问题 办法:跳过SSL证书检查 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLO ...
- 栅格那点儿事(四B)---多波段栅格数据的显示
多波段栅格数据的显示 我上面说了这么多,可能有的人会觉得平时也根本用不上.自然,说起影像数据,大家接触到对多的就是最最常见的航片或卫片.对于这种栅格数据呢,大多数的场景下,都只需实现一个效果,就是最接 ...
- python3绘图示例6-2(基于matplotlib,绘图流程介绍及设置等)
#!/usr/bin/env python# -*- coding:utf-8 -*- import os import numpy as npimport matplotlib as mpltfro ...
- python3绘图示例3(基于matplotlib:折线图等)
#!/usr/bin/env python# -*- coding:utf-8 -*-from pylab import *from numpy import *import numpy # 数据点图 ...
- 在android开发中如何使用JavaMail程序
javaMail,是提供给开发者处理电子邮件相关的编程接口.它是Sun发布的用来处理email的API.它可以方便地执行一些常用的邮件传输.我们可以基于JavaMail开发出类似于Microsoft ...