《Essential C++》读书笔记 之 面向过程编程风格

2014-06-18

2.2 调用(invoking)一个函数

2.2.1 Pass by Reference语义

在函数swap的参数中使用reference和pointer

pointer参数和reference参数的差异和用法

2.4 使用局部静态对象(Local Static Objects)

2.5 声明一个inline函数

2.7 定义并使用Template Functions (模板函数)

2.8 函数指针(Pointers to Functions)带来更大的弹性

函数指针和指针函数区别

2.9 设定头文件(Header Fiels)

2.2 调用(invoking)一个函数


返回

2.2.1 Pass by Reference语义

reference扮演着外界与对象之间的一个间接号码牌的角色。只要在型别名称和reference名称之间插入&符号,便声明了一个reference:

 #include <iostream>
using namespace std; int main()
{
int ival=; //对象,型别为int
int *pi=&ival; //pointer(指针),指向一个int对象
int &rval=ival; //reference(化身),代表一个int对象 //这里不是令rval改为代表jval对象,而是将jval赋值给rval所代表的对象(也就是ival)。
int jval=;
rval=jval; ival =;
pi;
//这里不是令pi指向rval对象,而是将ival(此为rval所代表之对象)的地址赋给pi
pi=&rval; return ;
}

重点是:面对reference的所有操作都像面对“reference所代表的对象”所进行的操作一样。

在函数swap的参数中使用reference和pointer

当我们以reference作为函数参数时,情况是一样的,如下代码所示:

 #include <iostream>
void swap(int &, int &); int main()
{
int v1=;
int v2=;
swap(v1,v2); v1;
v2; return ;
} void swap(int &val1, int &val2)
{
int temp=val1;
val1=val2;
val2=temp;
}

运行结果如下图所示:

将参数声明为reference的理由有两个:

  • 希望直接对所传入的对象进行修改;
  • 为了降低复制大型对象的负担。

如果我们愿意,也可以将参数以pointer形式传递。这和以reference传递的效用相同:传递的是对象地址,而不是整个对象的复制品。唯一的差别是他们的用法不同。如下代码所示:

 #include <iostream>

 void swap(int *,int *);

 int main()
{
int v1=;
int v2=;
swap(&v1,&v2); v1;
v2; return ;
} void swap(int *val1, int *val2)
{
int temp=*val1;
*val1=*val2;
*val2=temp;
}

但如果swap方法改成如下,变量v1,v2不会调换:

 void swap(int *val1, int *val2)
{
int *temp=val1;
val1=val2;
val2=temp;
}

因为上述方法只是更改了指针本身的地址,如下图:

pointer参数和reference参数的差异和用法

pointer可能(也可能不)指向一个实际对象。当我门提领pointer时,一定要先确定其值并非为0。至于reference则必定会代表某个对象。

一般来说,除非你希望在函数内更改参数值,否则建议传递内建型别时,不要使用传址的方式。传址机制主要是作为传递class objects之用。

2.4 使用局部静态对象(Local Static Objects)


返回

fibon_seq()函数是这样一个函数,每次调用时,会计算出Fibonacci数列(元数数目由用户指定),并以一个vector存储计算出来的元素值,然后返回。代码如下:

 vector<int> fibon_seq( int size )
{
if ( size <= || size > )
{
cerr << "Warning: fibon_seq(): "
<< size << " not supported -- resetting to 8\n";
size = ;
} vector<int> elems( size ); for ( int ix = ; ix < size; ++ix )
if ( ix == || ix == )
elems[ ix ] = ;
else elems[ ix ] = elems[ix-] + elems[ix-]; return elems;
}

以上代码有一个问题:每次调用时,都要重新计算。
我们希望,保存已经计算出来的元素。上面代码的局部变量肯定不行。如果将vector对象定义于file scope之中,又过于冒险,它会打乱不同函数之间的独立性,使它们难以理解。

本例的另一个解法便是使用局部静态对象:

 #include <iostream>
#include <string>
#include<vector>
using namespace std; //定义函数
const vector<int> *fibon_seq( int); int main()
{
fibon_seq( );
fibon_seq( ); //前4个element都已计算,不会重复计算
fibon_seq( ); //只计算还没有计算出来的第6个element return ;
} const vector<int> *fibon_seq( int size )
{
const int max_size = ;
static vector< int > elems; if ( size <= || size > max_size ){
cerr << "fibon_seq(): oops: invalid size: "
<< size << " -- can’t fulfill request.\n";
return ;
} // if size is equal to or greater than elems.size(),
// no calculations are necessary ...
for ( int ix = elems.size(); ix < size; ++ix ){
if ( ix == || ix == )
elems.push_back( );
else elems.push_back( elems[ix-]+elems[ix-] );
} return &elems;
}

2.5 声明一个inline函数


返回

回想一下,fibon_elem()返回一个Fibonacci数列元素,其位置由用户指定。在最初的版本中,每次调用,它都会重新计算每一个数列元素,直到用户指定的位置位置。它会检验用户所指定的位置是否合理。

为了使这个函数更容易理解,我们可以将各个小工作分解为独立函数,以求更简化:

 bool is_size_ok(int size)
{
const int max_size=;
if(size<=||size>max_size)
{
cerr<<"Oops: requested size is not supported: "
<<size
<<" --can't fulfill request.\n";
return false;
}
return true;
}
//计算Fibonacci数列中的size个元素
const vector<int>* fibon_seq(int size)
{
static vector<int> elems;
if(!is_size_ok(size))
return ;
for(int ix=elems.size();ix<size;++ix)
{
if(ix==||ix==)
elems.push_back();
else elems.push_back(elems[ix-]+elems[ix-]);
}
return &elems;
}
//返回Fibonaci数列中位置为pos的元素
bool fibon_elem(int pos,int &elem)
{
const vector<int> *pseq=fibon_seq(pos);
if(!pseq)
{
elem=;
return false;
}
elem=(*pseq)[pos-];
return true;
}

但是,先前的做法中,fibon_elem()只须调用一个函数便可完成所有运算,如今必须动用3个函数。这成了它的缺点。这项负担是否很重要呢?这和应用时的形势有关。如果其执行效能不符合理想,只能在将3个函数重新组合成一个。

然而C++还提供了另一个解决方法,就是将这些函数声明为inline。

将函数声明为inline,表示要求编译器在每个函数调用点上,将函数的内容展开。面对一个inline函数,编译器可将该函数的调用操作改为一份函数代码副本取而代之。只要在函数前面加上关键字inline即可:

 //ok:现在fibon_elem()成了inline函数
inline bool fibon_elem(int pos,int &elem)
{
/*函数定义与先前版本相同*/
}

注意:将函数指定为inline,只是对编译器提出一种要求而没有强制性,具体分析,请参考7.1.1节

inline函数的定义常常被置于头文件中。由于编译器必须在它被调用的时候加以展开,所有这个时候起定义必须是有效的,2.9节有更深入的讨论。

2.7 定义并使用Template Functions (模板函数)


返回

假设有3个diplay_messaeg()函数,分别用以处理元数型别为int、double、string的3中vectors:

 void display_message(const string&, const vector<int>&);
void display_message(const string&, const vector<double>&);
void display_message(const string&, const vector<string>&);

我们在假设他们的函数主体也很相似,唯一的差别仅在于第二个参数的型别。
这样的情况很多,C++提供一种机制,函数模板(function template),将参数表中指定的参数的型别信息抽离出来。

 #include <iostream>
#include <string>
#include<vector>
using namespace std; //在mian()之前,就不用声明函数了
template <typename elemType>
void display_message(const string &msg, const vector<elemType> &vec )
{
cout<<msg;
for ( int ix = ; ix < vec.size(); ++ix )
{
elemType t =vec[ix];
cout<<t
<<' ';
}
} int main()
{
string iMsg="show int vector: ";
int iArr[]={,,};
vector<int> iVec(iArr,iArr+);
display_message(iMsg,iVec);
cout<<'\n'; string cMsg="show char vector: ";
char cArr[]={'a','b','c'};
vector<char> cVec(cArr,cArr+);
display_message(cMsg,cVec); return ;
}

2.8 函数指针(Pointers to Functions)带来更大的弹性


返回

假设有一个函数"fibon_elem()"要返回fibon数列指定位置的元素:

 bool fibon_elem( int pos, int &elem )
{
const vector<int> *pseq=fibon_seq(pos); //(A) if(!pseq)
{
elem=;
return false;
}
elem=(*pseq)[pos-];
return true;
}

上述代码,调用了函数"fibon_seq()",但除了fibon,还有其它5种数列和相应的"数列_seq()"函数:

 const vector<int> *lucas_seq(int size);
const vector<int> *pell_seq(int size);
//...

难道我们要实现其他5种“数列_elem()”?
可以发现在函数"fibon_elem()"中,唯一和数列相关的部分,只有(A)。如果我们可以消除这个关联性,就可以不必提供多个相似函数了。

所谓函数指针,必须指明其所指向之函数的返回值类型及参数表,此外,函数指针必须将*置于某个位置,表示这份定义所表现的是一个指针。当然,最后还必须给于一个名称。

const vector<int>* *seq_ptr(int); //几乎是对的了

但这其实不是我们所要的。上述这行将seq_ptr定义为一个函数,参数表中仅有一个int类型,返回值类型是个指针,这个指针指向另一个指针,后者指向一个const vector,其元素类型为int。为了让seq_ptr被视为一个指针,我们必须以小括号改变运算顺序:

const vector<int>* (*seq_ptr)(int); //ok

现在,seq_ptr可以指向“具有所列之返回值类型及参数表”的任何一个函数。让我们将fibon_elem()重新写国,使它蜕变成更为通用的seq_elem():

 bool seq_elem( int pos, int &elem, const vector<int>* (*seq_ptr)(int))
{
//调用seq_ptr所指的函数
const vector<int> *pseq=seq_ptr(pos); //(A) if(!pseq)
{
elem=;
return false;
}
elem=(*pseq)[pos-];
return true;
}

现在的问题是如何取得函数的地址呢?只要给于函数名称就可以了:

     //将pell_seq()地址赋给seq_ptr
seq_ptr=pess_seq;

如何想把函数指针放入一个数组,可以这么定义:

     //seq_array是数组,内放函数指针
const vector<int* (*seq_array[])(int)=
{fibon_seq, lucas_seq, pell_seq, triang_seq, squqre_seq, pent_seq};

函数指针和指针函数区别

指针函数是指带指针的函数,即本质是一个函数。函数返回类型是某一类型的指针:

    //类型标识符    *函数名(参数表)
int *f(x,y);

函数指针是指向函数的指针变量,即本质是一个指针变量。

    int (*f) (int x); /* 声明一个函数指针 */
f=func; /* 将func函数的首地址赋给指针f */

2.9 设定头文件(Header Fiels)


返回

调用seq_elem()之前,必须先声明它,以便让程序知道它的存在。如果它被5个程序文件调用,就必须调用5次。C++提供一种简单的方法,把函数声明置于头文件中,并在每个程序文件代码文件中含入(include)这些函数声明。

头文件的扩展名,习惯上是.h。标准程序库例外,它们没有扩展名。我把我们的头文件命名为NumSeq.h,并将于数列处理相关的所有函数的声明都置于此文件中:

 //NumSeq.h
bool seq_elem(int pos, int &elem);
const vector<int> *fibon_seq(int size);
const vector<int> *lucas_seq(int size);
const vector<int> *pell_seq(int size);
//...

注意:函数的定义只能有一份,不过声明可以有很多份。我们不能把函数的定义纳入头文件,因为同一个程序的多个代码文件可能都会含入这个头文件。

“只定义一份”的规则有个例外:inlne函数的定义。 为了能扩展inline函数的内容,在每个调用点上,编译器都得取得其定义。这意味着我们必须将inline函数的定义置于头文件中。

另外,const object和inline函数一样,是“一次定义规则”的例外。下面代码显示了把const object seq_cnt的定义加入到头文件NumSeq.h:

const int seq_cnt=;

为什么const object是是“一次定义规则”的例外?

  因为const object的定义只要一出文件之外便不可见。这意味着我们可以在多个文件中加以定义,不会导致任何错误。

我们何时将const object加入头文件呢?

  当它需要跨文件使用时。

如果想把指针放入头文件NumSeq.h,需要加上关键字extern:

const int seq_cnt=;
//seq_array是指向const object的指针
extern const vector<int>* (*seq_array[seq_cnt])(int);

以下代码是含入头文件iostream和NumSeq.h:

#include <iostream>
#include "NumSeq.h"

为什么头文件iostream用尖括号,而NumerSeq.h用双引号?

  如果此文件被认定是标准的、或项目专属的头文件,我们便以尖括号括住:编译器搜索此文件时,会现在某些默认驱动器目录中寻找。

  如果文件名由成队双引号括住,此文件 便被认为是一个用户自行提供的头文件:编译器搜索此文件时,会由含入此文件所在的驱动器目录开始找起。

《Essential C++》读书笔记 之 面向过程编程风格的更多相关文章

  1. Essential C#读书笔记

    Essential C#读书笔记 这是一个多变的时代,一次又一次的浪潮将不同的人推上了巅峰.新的人想搭上这一波,同时老的人也不想死在沙滩上.这些年新的浪潮又一次推开,历史不停地重复上演,那便是移动互联 ...

  2. 读书笔记 |Google C++编程风格指南

    Google C++编程风格指南 ## 0. 背景 每一个C++程序员都知道,C++具有很多强大的语言特性,但这种强大不可避免的导致它的复杂,这种复杂会使得代码更易于出现bug.难于阅读和维护. 本指 ...

  3. 《Essential C++》读书笔记 之 目录导航

    <Essential C++>读书笔记 之 目录导航 2014-07-06 第一章:<Essential C++>读书笔记 之 C++编程基础 第二章:<Essentia ...

  4. 《Essential C++》读书笔记 之 泛型编程风格

    <Essential C++>读书笔记 之 泛型编程风格 2014-07-07 3.1 指针的算术运算(The Arithmetic of Pointer) 新需求1 新需求2 新需求3 ...

  5. 《Essential C++》读书笔记 之 基于对象编程风格

    <Essential C++>读书笔记 之 基于对象编程风格 2014-07-13 4.1 如何实现一个class 4.2 什么是Constructors(构造函数)和Destructor ...

  6. 《Essential C++》读书笔记 之 C++编程基础

    <Essential C++>读书笔记 之 C++编程基础 2014-07-03 1.1 如何撰写C++程序 头文件 命名空间 1.2 对象的定义与初始化 1.3 撰写表达式 运算符的优先 ...

  7. 《C#本质论》读书笔记(18)多线程处理

    .NET Framework 4.0 看(本质论第3版) .NET Framework 4.5 看(本质论第4版) .NET 4.0为多线程引入了两组新API:TPL(Task Parallel Li ...

  8. Android驱动开发5-8章读书笔记

    Android驱动开发读书笔记                                                              第五章 S5PV210是一款32位处理器,具有 ...

  9. 第一章 Andorid系统移植与驱动开发概述 - 读书笔记

    Android驱动月考1 第一章 Andorid系统移植与驱动开发概述 - 读书笔记 1.Android系统的架构: (1)Linux内核,Android是基于Linux内核的操作系统,并且开源,所以 ...

随机推荐

  1. Codeforces-541div2

    https://www.cnblogs.com/31415926535x/p/10427505.html codeforces-1131A~G 这场很多题都很简单,,应该是要能至少做出4道的,,但是我 ...

  2. cf348D. Turtles(LGV定理 dp)

    题意 题目链接 在\(n \times m\)有坏点的矩形中找出两条从起点到终点的不相交路径的方案数 Sol Lindström–Gessel–Viennot lemma的裸题? 这个定理是说点集\( ...

  3. Python图形编程探索系列-03-标签组件(Label)

    跳转到自己的博客 tkinter.Label介绍 什么是标签? 通俗的将就相当于word的功能,能够进行显示不可修改的文字.图片或者图文混排. 直观体会一下 图1 背景图构成:内容区(黑色),填充区( ...

  4. Python中的pass的作用

    1.pass语句什么也不做,一般作为占位符或者创建占位程序,pass语句不会执行任何操作2.保证格式完整 3.保证语义完整 以if语句为例,在c或c++/java中: ? if(true) ;//do ...

  5. BZOJ2759一个动态树好题 LCT

    题如其名啊 昨天晚上写了一发忘保存 只好今天又码一遍了 将题目中怕$p[i]$看做$i$的$father$ 可以发现每个联通块都是一个基环树 我们对每个基环删掉环上一条边 就可以得到一个森林了 可以用 ...

  6. print2flash文档在线预览应用(java,.net)

    一.背景 前段时间,LZ的boss突然给了出了这样一个需求:将原项目中的所有文章关联的附件TXT.PDF.office相关文件全部以flash的形式在网页上进行展示,便于预览.看似简单的需求,整个研发 ...

  7. Object类--toString方法

    toString()方法 1.在Object类中定义toString()方法的时候返回对象的哈希code码(对象地址字符串) 直接输出对象: 2.可以通过重写toString()方法表示出对象的属性之 ...

  8. 实现Qemu aarch32虚拟开发板ping www.baidu.com

    环境 Qemu: 2.8.0 开发板: vexpress-ca9 概述 如果要玩物联网,至少应该让开发板实现联网,让qemu支持联网在之前的博文中已经有介绍了,但是如果只能在自己的局域网内玩耍就太没意 ...

  9. String literal is not properly closed by a double-quote eclipse

    中文乱码, 解决方法之一就是更改工程的编码方式. 首先选择工程,右键打开property窗口,resource下的text  file incoding,看看是不是选择的utf-8,如果不是的话更改为 ...

  10. BTrace使用简介

    很多时候在online的应用出现问题时,很多时候我们需要知道更多的程序的运行细节,但又不可能在开发的时候就把程序中所有的运行细节都打印到日志上,通常这个时候能采取的就是修改代码,重新部署,然后再观察, ...