《Essential C++》学习DAY2|函数操作与一些注意事项

因为小姚在学习数据结构和算法的时候发现,她的C++基础十分薄弱,于是这一系列笔记为自学《Essential C++》划重点系列,希望能对C++的初学者有所帮助!

一、针对一个不合理的值,函数应该怎么办?

1.极端做法,终止整个程序

# include<cstdlib>
if(post <= 0)
    exit(-1);
//传入一个值给exit,此值将成为程序结束时的状态值

2.丢出异常

3.改变返回值类别,使之得以表示函数是否能够计算出用户想要的值

//重新修正函数原型
bool fibon_elem(int pos, int &elem);
//第二个参数是reference to int

//fibonacci数列
//而对于fibon_elem
bool fibon_elem(int pos, int &elem)
{
    //检查位置值是否合理
    if(pos==0||pos>1024)
        {elem = 0;return false;}

    //位置值是1和2的时候,elem的值是1
    elem=1;
    int n_1 = 1,n_2 =1;
    
    for(int ix=3; ix<=pos;++ix)
    {
        elem = n_2+n_1;
        n_2=n_1; n_1=elem;
    }

    return true;
}

二、值传递与地址传递

void swap(int a, int b)//值传递,我们传入swap的对象,和在swap内操作的对象(副本),其实是没有任何关联的两组对象,所以swap之后,原来的值不发生改变。

void swap(int &a, int &b)//地址传递,以地址的形式声明a和b,那么swap()内对这两个参数的改变,会反映到传入swap()的实际对象身上。

三、Pass by Reference语意

reference扮演着外界与对象之间的一个间接号码牌的角色,只要在型别名称和reference名称之间插入&符号,便是声明了一个reference。 

int ival = 1024;//对象,型别为int
int *pi = &ival;//指针,指向一个int对象
int &rval = ival;//reference,代表一个int对象

C++不允许我们改变reference所代表的对象,他们必须从一而终;面对reference的所有操作,都像面对“reference所代表的对象”进行的操作。所以当我们以reference作为函数参数时,亦复如此。

当我们以by reference的方式传递对象作为函数参数时,对象本身并不会复制出另一份,复制的是对象的地址,函数中对该对象进行的任何操作,都相当于是对传入的对象进行间接操作。

将参数声明为reference的理由之一是,希望得以直接对所传入的对象进行修改;同时也为了降低复制大型对象的负担。

//我将打算显示的vector以传值的方式传入display()中,
//就是说每当我想进行显示操作时,vector内的所有元素都会被复制
//但是更快的传输手段是直接传入地址
void display(const vector<int> &vec)
{
    for(int ix=0; ix <vec.size(); ++ix)
    {
        cout<<vec[ix]<<' ';
        cout<<endl;
    }
}

加了const,可以让阅读程序的人了解,我们以传地址的方式来传递vector,为的是避免复制操作,而不是为了要在函数之中对他进行修改。

reference和pointer的区别

相同点:他们传递的都是对象地址,而不是整个对象的复制品

不同点:1.用法不同:相同的一段程序,如果是pointer,只需把&改成*。2.pointer可能是个空指针,但我们使用pointer时,一定要保证其指向对象不为空(需要一个检验环节);而reference不需要检验,一定不为空。

一般来说,除非你想要在函数内更改参数值,否则我建议不要使用传地址的方式,传地址的机制主要是作为传递class objects之用。

四、生存空间与生存范围

除了static之外,函数内定义的对象,只存活于函数执行之际,如果将这些所谓局部对象的地址返回,会导致执行错误。就是说在函数体内以地址形式返回某个值,都不正确;但是如果将某个值以传值方式返回就不会产生任何问题,因为返回的乃是对象的复制品,他在函数之外依然存在。

五、动态内存管理

系统自动管理:local scope,file extent

程序员自行管理:dynamic extent(动态范围),其内存是由程序的自由空间配置而来,有时也称heap memory(堆内存),其配置是通过new表达式来形成的,而其释放是由delete表达式实现。

new Type;//此处的Type可为任意内建型别,也可以是程序知道的class型别
new Type(initial_value);//另一种写法

int *pi;
pi = new int;//就是先由heap配置出一个型别为int的对象,再将其地址赋值给pi
//在默认情况下,由heap配置而来的对象,皆未经过初始化
//new表达式的另一种形式允许我们指定初值
pi = new int(1024);
//从heap中配置数组
int *pia = nem int [24];
//上述代码会从heap中配置一数目组,拥有24个整数,pia会被初始化为数组第一个元素的地址
//因为他们是在执行期通过new表达式配置来的
//因此可以持续存活,直到delete将其释放为止。
delete pi;//释放pi所指的对象
delete []pia;//删除数组中的所有对象,且无需检验pi是否为零

六、提供默认参数值

一般的程序撰写法则是,以“参数传递”作为函数间的沟通方式,比直接将对象定义在全局作用域要好的多。解决方案:C++允许我们为所有参数或部分参数设定默认值,如:

ofstream *ofil=0(是pointer而不是reference,是因为reference一定得代表某个对象,无法被设为0)

还有一种方法是将默认值置于函数声明处,既:
 

//头文件声明
void display(const vector<int>&, ostream&=cout)
//此为.h文件

//.cpp文件应该这样
# include"NumericSeq.h"

void display(const vector<int>&vec, ostream &os)
{
    for(int ix=0;ix<vec.size(); ++ix)
    {
        os<<vec[ix]<<' ';//这里的os==cout
    }
    os<<endl;
}

为了节省函数间的通信问题而将对象定义在file scope中,永远是一种冒险,所以我们选择使用“局部静态对象(local static objects)”。就是在所定义对象前加一个static,局部对象所处的静态空间,即使在不同的函数调用过程中,依然持续存在。

七、内联函数、重载函数与模板函数

1.Inline函数

将函数声明为inline,表示要求编辑器在每个函数调用点上,将函数的内容展开。面对一个inline函数,编译器可将该函数的调用操作改以一份函数码副本而取而代之,提高效率。

适合被声明为Inline的函数:体积小,常被调用,计算不复杂(inline函数的定义常常在头文件里)

2.重载函数 

 名称相同的函数但是传入参数不同(参数型别不同/参数数目不同)可以进行重载,编译器会根据调用者的实际参数拿来和每个重载参数作比较,找到最恰当的。

3.模板函数

void display_message(const string &msg, const vector<int>&);
void display_message(const string &msg, const vector<double>&);
void display_message(const string &msg, const vector<string>&);

我们发现,这三个函数的主体部分十分相像,唯一的差别在于函数第二参数的型别,所以我们需要模板函数(template functions)来让我们得以将单一函数的内容与希望显示的各种vector型别捆绑起来。 

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<<' ';
    }
}
//elemType在ddisplay_message()中是一个临时放置型别的代称,
//elemType是个任意名称,我们使用他只是因为我们希望延缓指定欲显示的vector的元素型别

如何使用function template?

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

八、函数指针带来更大弹性

函数指针,必须指明其所指向之函数的返回值类型及参数列表。

//原来可以通过vector返回另5种数列
const vector<int> *fibon_seq(int size);
const vector<int> *lucas_seq(int size);
const vector<int> *pell_seq(int size);
const vector<int> *square_seq(int size);
const vector<int> *pent_seq(int size);
//但是没有必要定义一整组函数,我们可以使用函数指针来简化
const vector<int>* (*seq_ptr)(int);
//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);
//会间接调用seq_ptr所寻址出来的函数,你可以将任何一个地址传入seq_ptr,比如seq_ptr = pell_seq
    if(!pesq)
    { elem=0; return false;}
    elem = (*pseq) [pos-1];
    return true;
}
//你甚至可以写一个迭代循环,在每次迭代过程种将seq_ptr设为各个不同的数列函数(而非一一写出数列函数的名称)
//我们可以再次利用数组的索引技巧定义一数目组,内放函数指针
const vector<int>* (*seq_array[])(int)= {
    fibon_seq,lucas_seq,pell_seq,
    square_seq, pent_seq
};//这五个分别在数组中占据0,1,2,3,4这些个位置
//为了以明确指定的方式取用指针,我们可以使用对应的枚举成员作为数组索引值
enum ns_type{
    ns_fibon,ns_lucas,ns_pell,
    ns_square,ns_pent
};
//那么我们就可以
seq_ptr = seq_array[ns_pell];
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值