C++回顾

一、函数与参数

1. 传值参数

int Add(int a, int b)
{
	return a + b ;
}

在该程序中,a,b作为函数Add的形参(formal parameter),每个形参都是整型的,可以采用以下形式调用:
z = Add(2, x);
那么,2,x便分别是a和b对应的实参(actual parameter)

形参a、b实际上是传值参数(value parameter)。在运行时,函数Add执行前,把实参复制给形参。复制过程是由形参类型的**拷贝构造函数(copy constructor)**来完成的。如果实参和形参的类型不同,必须进行类型转换,把实参换为形参的类型(前提是这样的类型转换是允许的)。

当调用Add(2, x)时,a被赋值为2,b被赋值为x。如果x不是int类型,那么在把他的值赋值给b之前,首先要对他进行类型转换。例如,如果x是doule类型,其值为 3.8 ,那么b将被赋值为 3。

当函数运行结束时,形参类型的**析构函数(dersructor)**负责释放形式参数。当一个函数运行结束之时,形参的值不会复制到对应的实参中。因此,函数调用不会修改与形参对应的实参的值。看下面例子:

void swap (int a, int b)
{
	int temp = a;
	a = b;
	b = tem;
}

这是最常见的交换两数的办法,在该函数内部,确实进行了交换,但是要想通过该函数完成对实参的交换,却是我们的想当然了。

2. 模板参数

模板的作用,大家最先想到的都是代码复用,是的,就是代码复用,例如我们现在还需要一个浮点数的加法,考虑到加法的类型可能很多,我们不妨写一个模板函数:

template <class T>
T Add(T a, T b)
{
	return a + b;
}

从这段通用的代码,编译器通过实际传给T的类型,进行类型推演,生成对应类型的加法程序,这样可以减轻我们的代码量。

3. 引用参数

我们考虑一下函数传参的过程,其实是发生了一次拷贝构造的,内置类型还好,但要是自定义类型,也许会增加程序运行时间。

大家应该还记得数组在传参的时候,会降维,比如一个整型一维数组,传参会降级为一个整型指针,这个指针指向数组起始位置。

但是,即使是降维为一级指针,32位系统还是会占4个字节,那在C++中,提出了引用的概念,理论上,引用一个变量,该引用就是这个变量的别名,是不占空间的,但是在实现上,VS2013底层还是通过指针的方式实现的,前面有篇博客提到过,有兴趣的可以去看看哟:C++左值引用,那么我们改变一下该程序的写法:

template <class T>
T Add(T& a, T& b)
{
	return a + b;
}

这里的a,b是引用参数(reference parameter), 在函数调用时,这个程序没有复制实参的值,在函数返回时,也没有调用析构函数,只是构造了一个无名对象返回了。

4. 常引用参数

在上面的程序中,函数内部我们不会修改a,b的值,理论上也是不能修改的,所以C++提供了另一种参数传递模式----常量引用(const reference)这种模式指明的形式参数不能被函数修改,换句话说,如果你不修改某个形参的值,你可以传常引用,如果你不希望函数修改形参的值,你也可以传常引用。

template  <class T>
T Add(const T& a, constT& b)
{
	return a + b;
}

5. 返回值

一个函数可以返回一个值、一个引用或一个常量引用。

对于函数Add来说,返回的对象被复制到调用环境中,这是有必要的,因为函数计算出来的表达式结果被存储在一个局部的临时变量中,但函数返回时,这个临时变量所占用的空间将被释放,其值当然也不再有效。为了不丢失这个值,在释放临时变量、局部变量以及传值参数的空间之前,要把这个值从临时变量复制到调用该函数的环境中去。

给函数返回类型增加一个后缀&,我们便指定了一个引用返回(reference return)

但是需要注意的是,不能返回栈空间的引用,就是不能返回函数内部的局部变量。原因很简单,想想函数调用过程就知道了。

6. 重载函数

一个函数的签名(signature) 是由这个函数的形参类型以及形参个数确定的。

C++可以定义两个或多个同名函数,但是任何两个同名的函数不能有同样的签名。定义多个同名函数的机制称为函数重载(function overloading)

将函数调用语句中的签名与函数定义中的签名进行匹配,C++编译器就可以确定是哪一个重载函数被调用了。

二、异常

7. 抛出异常

异常表示程序出现错误的信息。例如,除0,访问越界等。对这些错误,虽然C++检查不出来,但是硬件会检查出来,并抛出一个异常。

int Div(int a, int b)
{
	if(b == 0)
	{
		throw "The dividend must be greater than 0";
	}
	
	return a / b;
}

这个程序抛出的是一个char * 类型的异常。

程序可能抛出的异常有很多类型,例如,除0、非法参数值、非法输入值、数组下标越界。C++具有一个异常类的层次结构,类exception是根。标准C++函数通过抛出异常来表明异常的出现,而这种异常是从基类exception派生的类型。

例如,C++的动态内存分配操作符new,在得不到内存空间分配时,就抛出类型为bad_alloc的异常,bad_alloc是继承自类exception的。

8.处理异常

一段代码抛出的异常由包含这段代码的try块来处理。紧跟try块之后的是catch块。每一个我catch块都有一个参数,参数的类型决定了这个catch块要捕捉的异常的类型。例如,
catch(char * e) { }
捕捉的异常类型是char *,而块
catch(bad_alloc e) { }
捕捉的异常类型是bad_alloc。块
catch(exception e) { }
捕捉的异常类型是基类型以及所有从exception派生的类型(例如bad_allocbad_typeid)。块
catch(...) { }
捕捉所有异常,不管是什么类型。

try块之后可以有多个catch块。如果一个try块之内的代码结束没有发生异常,那么catch块就被忽略。如果一个异常被抛出,那么try块正常运行终止,程序进入第一个能够捕捉到这种异常类型的catch块,在这个catch块被执行结束之后,其他的catch块就被忽略了。如果没有一个catch块能够与抛出的异常类型匹配,那就接着往出抛…如果该异常没有被任何catch捕捉,那么程序非正常终止。

#include <iostream>

int Div(int a, int b)
{
	if(b == 0)
	{
		throw "The dividend must be greater than 0";
	}
	
	return a / b;
}

int main(void)
{
	try
	{
		std::cout << Div(10, 0) << std::endl;
	}
	catch (const char* e)
	{
		std::cout << "An exception has been thrown" << std::endl;
		std::cout << "e = [" << e << "]" << std::endl;
	}

	return 0;
}

CentOS7编译执行结果:
执行结果

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值