essential c++读书笔记2

本文是《essential c++》读书笔记的第二章,重点讲解了面向过程编程风格,涵盖函数撰写、调用、默认参数值、局部静态对象、函数指针等关键概念。介绍了传值与传址的差异,以及动态内存管理和默认参数值的规则。还探讨了函数模板和头文件的使用策略。
摘要由CSDN通过智能技术生成

第二章 面向过程的编程风格

2.1 如何撰写函数

函数的四个部分:返回值类型、函数名、参数列表、函数体
函数必须先被声明,才能被调用 int fibon_elem(int pos); 声明
函数定义则包括函数原型及函数体。

2.2 调用一个函数

Pass by Reference语意
reference扮演着外界与对象之间的一个间接号码牌的角色。只要在型别名称和reference名称之间插入&符号,便是声明了一个reference。
int ival = 1024;
int *pi = &ival;
int &rval = ival;
当我们这么写:
int jval = 4096;
rval = jval;
时,便是将jval赋值给rval所代表的对象。
面对reference的所有操作都像面对“reference所代表之对象”所进行的操作一般无二。所以我们可以这样编写swap函数:

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

当我们以by reference方式传递对象当做函数参数,对象本身并不会复制出另一份—复制的是对象的地址。函数中对该对象进行的任何操作,都相当于是对传入的对象进行间接的操作。
将参数声明为reference通常有两个原因:
1) 希望得以直接对所传入的对象进行修改。
2) 为了降低复制大型对象的负担。

传值与传址
传值:当我们将对象传递给函数,默认情况下,其值是被复制了一份,成为参数的局部性定义,这种方式称为传值;
传址:要想使函数的形式参数与传入的实际对象产生关联,加上引用,这是传址。主要是希望对传入的对象进行间接操作。

一般情况下,我们也可以传递指针来代替引用。但是指针得判断是否为空指针,而引用则无须此判断。
一般来说,除非我们希望在函数内更改参数值,否则在传递内建型别时,不要使用传址方式。传址机制主要是作为传递class objects用的。

生存空间(scope)及生存范围(extent)
除了static例外,函数内定义的对象,只存活于函数执行之际。如果将这些局部对象(local object)的地址返回,会导致执行器错误。如果以传值的方式返回倒正确,因为返回的是复制品。
内建型别的对象,如果定义在file scope(全局对象)之外,必定被初始化为0.但如果它们被定义于local scope(局部对象)之内,那么除非程序员指定其初始值,否则不会被初始化。 //全局自动初始化,局部手动初始值
动态内存管理
我们可以使用new和delete来进行动态的内存管理。
int *pia = new int[24];
delete [] pia;
删除数组时候之所以要[],是因为如果没有[],则动态释放的是第一个数组元素的空间。[]在内部会产生数组的长度,进行释放。

全局变量可以自动初始化;局部变量不能自动初始化,必须手动初始化。

2.3 提供默认参数值

假设我们需要对bubble_sort提供参数,用于记录swap的参数,我们编写函数如下:
void bubble_sort(vector<int> &vec, ofstream &ofil)
{
	for (int ix = 0; ix < vec.size(); ++ix) {
		for (int jx = ix + 1; jx < vec.size(); ++jx) {
			if (vec[ix] > vec[jx]) {
				ofil << "about to call swap! ix: " << ix
					 << " jx: " << jx << "\tswapping: "
					 << vec[ix] << " with " << vec[jx] << endl;
			}
			swap(vec[ix], vec[jx], ofil);
		}
	}
}
但在不需要看调试信息时,此程序无法关闭调试信息。所以我们需要提供默认参数(指针),并令其默认值为0(之所以不是引用,是因为引用必须引用某个对象)
void bubble_sort(vector<int> &vec, ofstream *ofil=0)
{
	for (int ix = 0; ix < vec.size(); ++ix) {
		for (int jx = ix + 1; jx < vec.size(); ++jx) {
			if (vec[ix] > vec[jx]) {
				if (ofil != 0) {
					(*ofil) << "about to call swap! ix: " << ix
						 << " jx: " << jx << "\tswapping: "
						 << vec[ix] << " with " << vec[jx] << endl;
				}
			}
			swap(vec[ix], vec[jx], ofil);
		}
	}
}
我们也可以将display函数修改为如下:
void display(const vector<int> &vec, ostream &os = cout)
{
	for (int ix = 0; ix < vec.size(); ++ix) {
		os << vec[ix] << ' ';
	}
	os << endl;
}

关于默认参数值的提供,有两个不甚直观的规则。第一个规则是,默认值的决议(resolve)操作由最右边开始进行。如果我们为某个参数提供了默认值,那么这个参数右侧的所有参数都必须也具有默认参数才行。下列属于非法:
void display(ostream &os = cout, const vector &vec);
第二个规则是,默认值只能指定一次,可以在函数声明处,也可以在函数定义处。但我们一般都在函数的声明处指定默认值。

2.4 使用局部静态对象

局部静态对象所处于的内存空间,即使在不同的函数调用过程中,依然存在。  调用完不销毁
    可以避免重复计算。就可以声明为static 局部静态对象

2.5 声明一个inline函数

我们可以将Fibonacci函数分开写成如下三个函数:
bool is_size_ok(int size)
{
	const int max_size = 1024;
	if (size <= 0 || size > max_size) {
		cerr << "Oops: requested size is not supported: " << size << endl;
		return false;
	}
	return true;
}
const vector<int> *fibon_seq(int size)
{
	const int max_size = 1024;
	static vector<int> elems;

	if (!is_size_ok(size)) {
		return 0;
	}
	for (int ix = elems.size(); ix < size; ++ix) {
		if (ix == 0 || ix == 1) {
			elems.push_back(1);
		} else {
			elems.push_back(elems[ix -1] + elems[ix - 2]);
		}
	}
	return &elems;
}
inline bool fibon_elem(int pos, int &elem)
{
	const vector<int> *pseq = fibon_seq(pos);
	if (!pseq) {
		elem = 0;
		return false;
	}
	elem = (*pseq)[pos - 1];
	return true;
}
原本只需一个函数就可以完成所有操作,如今却需要三个,会降低性能。我们可以将函数声明为inline即可。内联函数
将函数声明为inline,表示要求编译器在每个函数调用点上,将函数的内容展开。这样就可以将三个函数写入fibon_elem函数内,但依然维持三个独立的运算单元。

2.6 提供重载函数

重载函数的区分在于:函数名相同,形式参数的个数、类型不同。构造函数

2.7 定义并使用Template Function模板函数

主要是为了不特定指定某种类型,在函数调用时才决定类型。
function template将参数表中指定的所有(或部分)参数的型别信息抽离出来。
function template以关键词template开场,其后紧接着成对尖括号包围起来的一个或多个识别的名称。
template <typename elemType>
void display_message(const string &msg, const vector<elemType> &vec)
{
	cout << msg;
	for (int ix = 0; ix < vec.size(); ++ix) {
		elemType t = vec[ix];
		cout << t << ' ';
	}
}

则我们可以如下调用display_message:

vector<int> ivec;
string msg;
display_message(msg, ivec);

vector<string> svec;
display_message(msg, svec);
一般而言,如果函数具备多种实现方式,我们可将它重载,其每份实体提供的是相同的通用服务。如果我们希望让程序代码的主体不变,仅仅改变其中用到的数据型别,可以通过function template达成目的。

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

函数指针必须指明所指函数的返回类型及参数列表:const vector* (seq_ptr)(int); 参数列表内容为int,返回值类型为const vector,名称为seq_ptr
书上的函数指针不太好进行概括,我们通过一个简单的程序来说明一切:

#include <iostream>
#include <vector>
#include <string>
using namespace std;
 
void fun1(int iValue)
{
    cout << iValue << endl;
}
 
void fun2(float fValue)
{
    cout << fValue << endl;
}
 
void fun3(string sValue)
{
    cout << sValue << endl;
}
 
template<typename funType>
void fun(void (*subfun)(funType), funType val)
{
    (*subfun)(val);
}
 
int main(void)
{
    fun<int>(fun1, 12);
    fun<float>(fun2, 12.3);
    fun<string>(fun3, "hello world");
 
    return 0;
}

这里我们可以这样编写:
fun(fun1, 12);
但是不能这样编写: fun(fun2, 12.3);
因为编译器无法区分float和double.

2.9 设定头文件

任何函数都可以有多份声明,但是只会有一份定义。所以声明通常放在头文件中,inline函数是例外,inline定义放在头文件中。

关于尖括号和双引号的区别:
如果此文件被认定为标准的,或项目专属的头文件,我们便以尖括号将文件名括住。编译器搜寻此文档时,会先在某些默认的驱动器目录中找寻。如果文件名由成对的双引号括住,此文件便被认为是一个用户自行提供的头文件;搜寻此文件时,会由含入此文件之文件所在的驱动器目录开始找起。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值