《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. cache 基本原理

        Cache 主要由 Cache Tag,Cache 存储体,Cache 控制模块组成.Cache Tag 主要用来记录 Cache 存储体中数据的位置和判断 Cache 内数据是否命中: Ca ...

  2. shell 环境变量

    Ubuntu系统设置的环境变量 .profile .bashrc 在 .profile中 有一段代码: if [ -d "$HOME/bin" ] ; then PATH=&quo ...

  3. Emit学习笔记

    1,给字段设置值,并返回 static void Main(string[] args) { //给字段设置值,并返回 AssemblyName assemblyName = new Assembly ...

  4. reactNative环境搭建+打包+部分报错总结

    个人搭建记录+个人收集: 多些真诚,少些坑. 排版书写过程可能不够详细,还望见谅. 详细见:http://files.cnblogs.com/files/chunlei36/reactNative%E ...

  5. vue中的dom基本渲染

    一.输出动态标签请只对可信内容使用HTML插值,绝不要对用户提供的内容使用插值,容易导致xss攻击. <div id="J_app"> <div v-html=& ...

  6. 关于文档模式、DCOTYPE声明及严格模式

    1.文档模式 文档模式的概念是由IE5.5引入,通过使用文档类型(DOCTYPE)切换实现的.不同的文档模式主要影响CSS内容的呈现,尤其是浏览器对盒模型的解析,但在某些情况下也会影响到JavaScr ...

  7. centos7环境下对防火墙的操作

    我是运行了systemctl stop firewalld.service && systemctl disabl e firewalld.service命令于是显示 [root@in ...

  8. 使用 IntraWeb (28) - 基本控件之 TIWTemplateProcessorHTML、TIWLayoutMgrHTML、TIWLayoutMgrForm

    TIWTemplateProcessorHTML //使用外部的 html 文件做模板 TIWLayoutMgrHTML //直接输入 Html 文本做模板 TIWLayoutMgrForm //这应 ...

  9. AngularJS中控制器继承

    本篇关注AngularJS中的控制器继承,了解属性和方法是如何被继承的. 嵌套控制器中属性是如何被继承的? ==属性值是字符串 myApp.controller("ParentCtrl&qu ...

  10. Java8 利用Lambda处理List集合循环给另外一个List赋值过滤处理

    1.利用stream().forEach()循环处理List; List<String> list = Lists.newArrayList();//新建一个List 用的google提供 ...