Essential c++ 第二章
这一章节的主要内容在于,教你学会如何正确的写一个函数。
文章目录
1.如何处理用户的错误输入
由于用户输入可能会出现超出函数能够处理范围的情况,因此需要我们在写函数时候注意这一点
1.1结束函数运行
在cstdlib库中有一个exit函数可以使用,如果用户出现错误输入的时候可以直接结束函数,不过这种方法太过于激烈。
1.2 抛出异常
可以通过抛出异常的方式进行处理,这一部分会在第七章中进行介绍
1.3 返回值设置为bool类型
当用户输入错误的数据类型时,返回false,并且输出错误原因,以告诫用户。只有数据输入正确,才会返回true进行正确的运算。
2.如何查询每种数据类型的范围
在头文件#include中,有两个函数,用于分析一种数据类型的最大值和最小值
#include<limits>
int max_int = numeric_limits<int>::max();
int min_double = numeric_limits<double>::min();
3.参数列表应该传入数值还是传入地址
3.1 传址和传值的规则
地址传递主要有两种应用情况,一种是需要在函数内部修改这个传递的变量;另外一种是这是一种复杂的数据类型,进行数值重新复制会消耗很多时间,传址更有效率。如果不是以上这两种情况,不建议使用传递地址的方式传入变量,比较不安全
3.2引用和指针
指针能够直接的指向变量的地址,指针可以赋予空值,因此在使用前应该检查当前指针是否为空指针。
引用也能够指向变量的地址,本质是一种常量指针,指向一个变量的地址之后,就不允许改变了,也称为与这个变量绑定了。因为引用是一种绑定的机制,因此引用不会出现空值的情况,使用前不需要检测是否为空
3.3引用使用技巧
引用主要有四种使用技巧
(1)传递希望在函数中能够修改的变量,如交换函数
swap(int &a,int &b)
{
int c;
c = a;
a = b;
b = c;
}
(2)传递复杂的类,希望减少复制的时间
(3)使用const 的引用作为参数进行传递,不希望这个复制的变量在函数内被人修改,如:
void (const myClass& test)
{
}
(4) 实现多个参数作为返回值。在函数参数列表中加入一个引用类型的变量,把数据存入这个引用类型变量中,就能够实现多个参数作为返回值。
4.内存管理
在c++中,变量主要有三种类型的储存位置
4.1栈区
栈区存储变量由编译器自动生成,是存放临时变量的地方,这些变量位于local scope
4.2堆区
堆区的变量是由程序员定义生成,通过new关键字进行定义,必须使用delete关键词才能从堆区删除,否则变量一直无法被释放,形成内存泄露的问题
4.3全局变量区
全局变量去主要用于存放位于file scope的变量,还有static的变量,因为这些变量的生存域是整个文件,因此这些变量在main开始运行之前就被定义好了。
这里顺便提及一下,static具有两个含义,一个是静态变量作用区域为整个文件,另外一个含义是,静态变量具有隐藏属性,不能被其他文件所发现,不能使用extern关键字进行扩展。
5.默认值
有的时候,有些参数是可有可无的,就可以把这些参数做成默认值,只有需要用户指定的时候,才进行输入。
值得注意的是,默认值在声明和定义中只能出现一次,因为头文件透明度比较高,因此我们一般将默认值放在声明里面,也就是头文件里面。
6.提高函数运行效率
6.1在函数中使用局部静态变量
有时候我们在函数中会出现某个变量需要重复计算的情况,比如返回斐波那契数列,每次调用函数都需要用循环从头生成一遍,使用局部静态变量之后,可以节省这个重新生成的时间。
将斐波那契数列存放在static的vector中,只有输入的index大于数列长度的时候,才重新生成没有的那一段,非常节约计算时间。
6.2使用inline函数
inline函数运行机制是直接把函数复制到引用位置,而不是在内存中生成代码块反复调用,反复调用会比较慢,这是一种空间换时间的做法。
6.2.1 inline函数使用规则
- inline写在函数最前面
- inline函数必须写在头文件中,否则main函数找不到他的定义
6.2.2 inline使用的场景
- 函数体非常短
- 这个函数被反复调用
- 这个函数中没有出现递归的算法
7. 减少函数名称,使函数数量更少的方法
7.1 重载
重载函数是指变量名一样,参数列表不同的函数。这种函数主要用于处理思路一样,但是具有不一样的处理对象的函数的合并。
比如比较两个int的最大值、比较两个double的最大值,就可以使用重载的方法,两个函数定义为同名函数。
int max(int a,int b);
double max(double a,double b);
注意重载必须是参数列表不同才行,返回值不同无法构成重载。
7.2 模板
模板适用于函数处理思路一样,处理流程一样,但是处理变量类型不同的函数的合并。比如7.1的例子,除了使用重载以外,还可以使用模板的方法。
template <typename T>
T max(T a,T b);
7.3函数指针
函数指针适用于,函数内部需要调用外部函数,但是这些外部函数比较相似,如果要为每个调用的外部函数专门另写一个函数,会需要创建很多的函数。如果使用函数指针的方法,就可以使用一个函数,其他外部引用函数采取指针的策略,减少了函数个数
7.3.1 函数指针的写法
//原来的函数
int max(int a,int b)
{
return (a>b?a:b);
}
//定义函数指针
int (*f)(int a,int b);
//函数指针指向新的地址
f = max;
//以函数指针的方式调用
f(1,2);
7.3.2 使用索引管理函数指针
//01 假设有这么几个函数要被其他函数调用,返回长度为index的数列
const vector<int>* Fibno(int index);
const vector<int>*Pell(int index);
const vector<int>*Pentagonal(int index);
//02 定义函数指针
const vector<int>* (*ptr)(int );
//注意ptr和*一定要使用括号,这是程序优先级的问题
//03 建立数组进行管理
const vector<int>* (*ptr_arraty[])(int)=
{
Fibno,Pell,Pentagonal
}
//04 为了区分每个索引是什么,要给索引赋予意义
enum ns_ptr
{
ns_Fibno,
ns_Pell,
ns_Pentagonal,
}
//05 使用
const vector<int> (*f)(int)=ptr_array[ns_Pell];
const vector<int> * test
=f(10);
7.3.3 一个比较不恰当的例子
#include<iostream>
using namespace std;
//01 函数指针怎么用
//01-1 假设我有一些专门打印序列名字的函数
void fibonPrint()
{
cout << "this is fibon" << endl;
}
void lucusPrint()
{
cout << "this is lucus" << endl;
}
void pellPrint()
{
cout << "this is pell" << endl;
}
void trianglePrint()
{
cout << "this is triangle" << endl;
}
void squarePrint()
{
cout << "this is square" << endl;
}
void pentPrint()
{
cout << "this is pent" << endl;
}
//01-2 假如我有一个专门处理数列的函数(假设返回数列第几个数字),处理完了以后打印数列名字
//如果直接写,要写多个函数,处理fibno函数的打印fibno名字,就要写多个函数
void func_fibno()
{
//随便什么处理
fibonPrint();
}
void func_pell()
{
pellPrint();
}
//使用函数指针,可以用一个函数代表全部的函数
void func2(void (*ptr)(void))
{
ptr();
}
//02 函数指针使用的进一步优化
//用数组来储存函数指针,这样就可以用序列来访问了
void (*ptr_array[])(void) =
{
fibonPrint,lucusPrint,pellPrint,trianglePrint,squarePrint,pentPrint
};
//用枚举给每个序列赋予意义
enum ns_type
{
ns_bibno,
ns_lucus,
ns_pell,
ns_triangle,
ns_square,
ns_pent
};
int main()
{
//解决问题:有同样参数列表、同样返回值的函数,在其他函数中需要调用这些函数,就可用函数指针的方法来
//减少函数的定义数量。因为相当于你要为每个调用的函数写一个专门的函数。
//01 函数指针的使用
//直接写各个函数的处理+打印函数
func_fibno();
func_pell();
//用函数指针就可以一个函数代表了,与上面效果一样,但是更简单
func2(fibonPrint);
func2(pellPrint);
//02 用序列的方法优化函数指针,不然要记住很多函数的名字
//用序列对函数指针进行管理,更加的方便
func2(ptr_array[0]);
func2(ptr_array[ns_pell]);
return 0;
}
8.头文件
8.1关于函数
函数声明放置在头文件中,定义放置在程序中。但是inline函数特殊,要放在头文件中
8.2关于全局变量
全局变量要想被其他文件调用,应该在头文件中加入extern,当程序中出现这句话的时候,他的意思是,在程序中有存在这样一个全局变量,但是我不知道他在哪,你去其他文件中找找看。其他文件中存放的就是这个全局变量的定义。全局变量的定义不能放在头文件中,头文件只能放置声明,所以要加extern。但是const变量特殊,要把定义放在头文件中,因为const变量出了文件之后就失效了,所以不能放在其他源文件中。