2.1 如何编写函数
函数包含函数名,返回类型,参数列表和函数体,必须先声明才能被调用。
练习2.1 用户询问Fibonacci数列的任一位置的元素值,输出并打印数列。
#include <iostream>
#include<string>
#include<vector>
using namespace std;
bool fibon_elem(int, int&);
bool fibon_elem(int pos, int&elem)
{
//检查位置是否合理
if(pos<=0||pos>1024)
{
elem = 0;
return false;
}
//第一个和第二个数都是1 elem=1
elem = 1;
int n1 = 1;
int n2 = 1;
for (int ix = 2; ix < pos; ++ix)
{
elem = n2 + n1;
n2 = n1; n1 = elem;
}
return true;
}
bool print_sequence(int pos)
{
if (pos <= 0 || pos>1024)
{
cerr << "invalid position: " << pos << "--cannot handle request!\n";
return false;
}
cout << "The Fibonacci Sequence for " << pos << " positions: \n\t"<<endl;
switch (pos)
{
default:
case 2:
cout << "1 ";
case 1:
cout << "1 ";
break;
}
int elem;
int n2 = 1;
int n1 = 1;
for (int ix = 2; ix < pos; ++ix)
{
elem = n2 + n1;
n2 = n1; n1 = elem;
//一行打印10个元素
cout << elem << (!(ix % 10) ? "\n\t" : " ");
}
return true;
}
int main()
{
char usr_ans;
bool usr_req = true;
while (usr_req == true)
{
int pos;
cout << " Please enter a position: ";
cin >> pos;
int elem;
if (fibon_elem(pos, elem))
{
cout << "element # " << pos << " is " << elem << endl;
print_sequence(pos);
cout << "Do u wanna to ask another question? Y/N" << endl;
cin >> usr_ans;
if (usr_ans == 'N' || usr_ans == 'n')
usr_req = false;
}
else cout << "WTF" << endl;
}
system("pause");
return 0;
}
2.2 调用函数
当我们调用一个函数时,会在内存中建立一块特殊区域,称为程序堆栈”program stack“。堆栈提供了每个函数参数的储存空间,也提供了函数所定义的每个对象的内存空间,这些对象被称为local object(局部对象)。一旦函数完成,这块内存会被释放掉,也就是pop出来。
要将函数的参数和传入的实际对象产生关联,即”传址“,需要将参数声明为一个reference。不就是形参和实参?
函数参数的传递应该采用传址方式而非传值方式。
void swap(int val1, int val2);
void swap(int &val1, int &val2);
- 语义
声明一个reference:
int ival=1024;
int *pi=&ival;
int &rval=ival;
//将jval赋值给ival
int jval=4096;
rval=jval;
//将ival的地址赋值给pi
pi=&rval;
面对reference的所有操作都和面对”reference所代表的对象“所进行的操作一样。传址主要用于全局对象class object而非局部对象local object。reference和pointer的效果是相同的,不一样的是使用方式,而且pointer有可能为空指针,使用前要先检查pointer的值非0。
reference:
const vector<int> &vec
vec.size()
vec[ix]
pointer:
const vector<int> *vec
vec->size()
(*vec)[ix]
- 动态内存管理
程序的的heap memory(堆内存)分配使用new,释放通过delete。
int *pi = new int(1024);//从heap分配对象可初始化也可不指定初值
int *pia =new int[24];//从heap分配数组无法为元素设定初值
delete pi;
delete [] pia;//要加一个空的下标运算符
从heap分配来的对象除非被delete掉否则永远不会被释放,这叫做memory leak(内存泄露)。
2.3 提供默认参数值
可为函数的部分或全部参数设定默认值,两个规则:
1. 为某个参数提供了默认值,那么这一参数右侧的所有参数都必须也具有默认参数值;
2. 默认值只能声明一次,可以将默认值放在函数声明处放在头文件.h里。
2.4 使用局部静态对象
局部静态对象(local static object)所处内存空间,即使在不同的函数调用过程中,依然持续存在。
2.5 声明inline函数
关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。
使用情况:使用多个函数,执行效果不理想。将函数声明为inline,表示要求编译器在每个函数调用点上,将函数的内容展开。面对一个inline函数,编译器可将该函数的调用操作改为以一份函数代码副本代替。这将使我们获得性能改善。为什么呢?
慎用内联
内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
适合被声明为inline的函数:体积小,常被调用,所从事的计算不复杂。
inline函数的定义常被放在头文件中。
2.6 提供重载函数
function overloading:参数列表不相同的两个或者多个函数可以拥有相同的函数名称。可以将一组实现代码不同但是工作内容相似的函数加以重载,调用时根据调用者提供的实际参数来区分函数。
void display_message(char ch);
void display_message(const string&);
void display_message(const string&, int);
2.7 定义并使用模板函数
function template(函数模板):将单一函数的内容与希望显示的各种vector类型绑定(bind)起来,将参数列表中指定的全部(或部分)参数的类型信息抽离了出来。
以关键字template开场,紧接以<>包围起来的一个或多个标识符。用户每次利用模板产生函数都必须提供确实的类型信息。
template<tpename elemType>
void display(const string string &msg, const vector<elemType> &vec){}
elemType在display_message()函数中是一个暂时放置类型的占位符,可以被绑定为内置类型或用户自定义类型。
vector<int> ivec;
string msg;
//...
display_message(msg,ivec);
编译器会将elemType绑定为int类型,然后产生一份display_message()函数实例,于是第二参数的类型即变成vector<int>。
function template同时也可以function overload。下面一个的第二参数类型为vector,另一个的第二参数类型为list。
template<typename elemType>
void display_message(const string &msg, const vector<elemType> &vec);
template<typename elemType>
void display_message(const string &msg, const list<elemType> <);
2.8 函数指针带来更大的弹性
- 函数指针(pointer to function)
- 必须指明其所指函数的返回类型及参数列表
- 必须将 * 放在某个位置
- 必须给予一个名称
const vector<int>* (*seq_ptr) (int);
seq_ptr被定义为一个指针,指向返回类型为const vector<int>*和参数列表为int的任何一个函数。
- 调用方式
和一般函数相同。
const vector<int>* pseq = seq_ptr(pos);
会间接调用seq_ptr所指向的函数。如果函数指针的初值为0,表示并未指向任何函数。
const vector<int>* (*seq_ptr) (int) = 0;
提供函数名称即可取得函数地址,将其作为函数指针的初值。定义一个数组,内放函数指针:
//seq_array是个数组,内放函数指针
const vector<int>* (*seq_array[])(int)={
fibon_seq, lucas_seq, pell_seq, triang_seq, square_seq, pent_seq};
- 枚举类型
关键字enum。默认情形下,第一个枚举项的值为0,接下来每个枚举项都多1。可以作为数组索引值。
enum ns_type{
ns_fibon, ns_lucas, ns_pell, ns_triang, ns_square, ns_pent};
2.9 设定头文件
函数的定义只能有一份,不过可以有许多份声明,函数的声明都放在头文件里,inline函数除外。
在file scope(全局范围)内定义的对象,如果可能被多个文件访问,就应被声明于头文件中。
在seq_array的定义前加关键字extern使它成为一个声明。
const int seq_cnt = 6;
extern const vector<int>* (*seq_array[seq_cnt])(int);
为什么在seq_cnt前不加extern?
const object的定义只要一出文件之外便不可见,可以在多个程序代码文件中加以定义,不会导致任何错误。因为我们希望编译器在数组声明中使用这一const object,并且在其他需要用到常量表达式的场合中(可能跨文件)也能使用。
任何需要用到seq_array的文件都要包含对应头文件。
#include "NumSeq.h"
使用双引号“”而不是<>的原因:""包住的头文件被认为是用户提供的,搜索时编译器先从包含此文件的文件所在磁盘目录找起;<>包住的头文件被认为是标准的或者项目专属的,编译器会先在某些默认的磁盘目录找起。