Essential C++学习(二)面向过程编程

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> &lt);

2.8 函数指针带来更大的弹性

  • 函数指针(pointer to function)
  1. 必须指明其所指函数的返回类型及参数列表
  2. 必须将 * 放在某个位置
  3. 必须给予一个名称
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"

使用双引号“”而不是<>的原因:""包住的头文件被认为是用户提供的,搜索时编译器先从包含此文件的文件所在磁盘目录找起;<>包住的头文件被认为是标准的或者项目专属的,编译器会先在某些默认的磁盘目录找起。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值