C++学习笔记(8)——初识函数

函数是C++语言的编程模块。一个程序是由若干个函数组成的,比如程序的入口函数——main函数。但实际上函数的功能和他涉及的语法并不总是这么简单。本篇笔记将总结函数的基本知识。并将在后面的笔记中一步步深入总结函数的相关功能。

1.使用函数的基本工作

要使用函数需要完成三项基本工作,即:提供函数的定义,提供函数原型,调用函数。当然这三者从语法上讲并不要求同时存在。比如,可以只定义函数,而不调用。而且如果在调用之前定义函数,甚至可以不用再提供函数原型,编译器会把该函数定义理解为函数原型。

函数定义的一般形式是:

typeName FunctionaName(paralist)

{

         Statement;

         return value;

}

函数分为有返回值和没有返回值两类。无返回值用void修饰,即上述typename为void,由返回值则用具体的返回值类型修饰,该类型则可以是除数组外的任意类型。如果是无返回值的函数,则可以省略return value的返回语句,或者只写return;而没有返回值。

其中的paralist是函数参数。函数参数通常按值传递参数(按值传递通常指表达式的左值),具体的函数参数内容将在后文中详细介绍。当然,函数也可以不带参数,仅为一个空括号或者也用void表示。

比如每个程序都不可缺少的main函数。通常的定义形式是

int main()

{

         //dosth

         return 0;

}

main函数是一个可执行程序必不可少的,如果你编写过MFC的程序,会发现没有main函数而是一个tmain函数,这种情况的main函数是被隐藏的,而这个tmain函数是被隐藏的卖弄调用的函数。main函数通常是一个以int类型为返回值的,没有函数参数的函数。return 0返回的0是告诉操作系统的,一般认为0是执行成功,而非0值尤其是负值表示执行失败。

函数原型为何存在呢?函数原型实际提供了函数到编译器的接口,也就是说它将函数返回值的类型以及参数的类型和数量告诉编译器,因此编译器知道应检索多少个字节以及如何解释调用的函数。如果想避免使用函数原型,则唯一的方法是在函数被调用之前定义它,但C++通常不这么做,c++的函数原型通常被写在头文件中,并通过预编译指令首先编译它。

函数原型的最简单写法是将函数头加上分号,形成函数声明语句。寒素原型不要求必须提供变量名,因为编译器做这项工作时不需要知道名字。通常看到的原型中是写有变量名的,主要是因为看上去更好理解。这里的变量名仅仅是占位符而已。

函数原型可以帮助编译器,或者说帮助程序员检查是否正确处理返回值,使用的函数参数是否正确,参数的类型是否正确等等。这种检查被称为静态类型检查。

2.函数参数

函数参数可以用来按值传递变量,成为连接函数的接口。用以接收传递值的变量被称为形参,实际传递给函数的值被称为实参。所以定义函数时的函数参数是形参,所谓形参,即形式上的参数,它的地位等同于函数内部建立的临时变量(局部变量)。只有在函数调用时采为这些变量分配内存,在函数使用结束后会自动释放内存。这意味着形参只是作用与函数体内的实参的一个临时副本而已。

举个栗子:

int add(int a, int b);

int main()

{

         int d1 = 3;

         int d2 = 4;

         int sum = add(3,4);

         return 0;

}

int add(int a, int b)

{

         return a+b;

}

以上第一句为函数原型。这使得在main中使用add函数时编译器已经知道它是什么了。add函数是一个返回值为int类型的函数。函数参数为两个int型数据,a和b即为形参,在调用时的d1和d2是实参。调用函数时,将实参的值3和4传递给形参a和b,在函数内部,a的值对应为3,b的值对应为4。返回值a+b的表达式左值7.所有main中的sum即为返回值7.当函数执行到main的return 0;时,a和b的值被释放(无法查看值)。

3.函数和复合类型

函数参数可以任何类型。基本类型的处理很简单,下面总结下传递复合类型,如数组,指针以及结构体的基本原理。

首先要谨记一个原理,那就是C++处理数组的根本方式是通过指针。因此,在函数参数为数组时,实际上传递的是一个指针。如下面的函数:

int sum_arr(int arr[], int n);

这里arr并不是一个数组,而是指针,它等同于数组名。所以上述的表达方式不多见,但确实是合法的。

那么函数参数为指针是如何处理的?函数参数为指针实际上也是一种按值传递的体现,只不过传递的是一个地址值。相当于将实参的地址传递给函数形参,函数的内部则可以通过指针指向该地址来获取值,甚至通过地址偏移或数组下标的方式获取不同地址下的值(不同的元素)。

我们要时刻清楚以下三个公式则可以举一反三:

arr[i] == *(ar+i);

&arr[i] == ar+I;

arr[r][c] == *(*(ar+r) + c);//二维数组

既然,传递给函数的是地址,如果实参是数组,则意味着函数并不知道数组的大小,这就需要在形参中加入一个指示数组大小,甚至不等于大小而是函数要处理的长度的变量。如上述函数中的n。如果要处理的是字符串,那么则不必将字符串的长度传递给函数,因为字符串有自己的结束标志(’\0’),可以通过一些方式来获取长度,比如strlen。

对于函数参数是结构体或类,可以当做基本类型那样来处理,也可以用指针处理,那么在实参的地方需要用&来传递地址。但实际上对于较大的结构体,创建一个副本非常消耗资源,用指针或者引用的方式更好。

4.引用

引用因为是C++新增的符合类型,这里单独总结一下。引用是已定义变量的别名,通常用在函数参数的类型中。通过引用变量类型,函数将使用原始数据而不是其副本。这意味着可以节省大量资源,尤其是对于较大的结构体或类。

引用的类型用&符号,这里的&不再是取地址,而是类型标识。就像int *pr一样,int &表示指向int的引用。不同于指针,必须在声明引用的时候将其初始化,比如int b; int &a; a=b;这种写法编译器将报错。但实际上几乎不存在这种用法,因为引用通常仅仅用于形参。

当引用用于函数形参,函数参数将不再按值传递,而是按引用传递。这使得函数内可以使用实参本身。这么做的直接目的是避免复制副本,以节约资源。另一种使用引用的目的是修改实参,但要注意,如果实参和形参的类型不一致,但可以自动转化,如int 转为double,则函数会自动创建临时变量,则达不到修改实参的目的。

5.如何让函数输出变量

函数的输出一种是用返回值。用返回值返回基本类型,不用赘述。如果要返回指针(函数不能返回数组,但可以通过返回指针的形式间接返回数组),尤其是常见的返回字符串地址。这种情况下要在函数内为要返回的字符串动态分配内存,这些内存将被分配在自由存储空间上,在用户delete前会一直保持。但也导致用户一定要记得手动delete这些被分配的内存,不然就造成内存泄露了。

栗子:

char *GetChar(int n)

{

         char *pch = new[n+1];

         pch = ‘\0’;

         while(n-- > 0)

                   pch[n] = ‘h’;

         return pch;

}

int main()

{

         char *pr = GetChar(4);

         delete[] pr;

         return 0;

}

另一种输出方式就是通过函数参数输出,这要求参数为指针或者引用。如果参数为函数指针,传递的值是地址,则可以在函数内对该地址内的数据进行修改输出。另外,引用直接用的是函数的实参变量,没有创建副本,则可以在函数内直接修改该值,这似乎更简单粗暴。

void add1(int a, int b int*sum)

{

         *sum = a+b;

}

void add2(int a, int b, int &sum)

{

         sum = a+b;

}

int main()

{

         int a=1;

         int b=2;

         int c=0;

         add1(a,b,&c);

         add2(a,b,c);//the same as add1

}

6.函数参数到底应该用什么类型

在总结函数参数在什么情况下用什么类型之前,首先总结一下一个关键字const。

const约束使得编译器知道它修饰的值是不变的。用const修饰一个基本类型变量,如const int a编译器就会提示你const类型必须赋初值。其作用与#define a 1类似。

当const 用以修饰指针,如const int *pa;则pa指向的值为一个常量,表示该地址内的值不可修改。因此C++禁止将const的地址赋给非const的指针。再如int * const pa;则pa的值是常量,也即地址是不可修改的,但其存放的具体值是可以修改的。当然,如果const int * const pa;则都不可修改。

如果用以修饰函数形参,但来的效果将非常明显。这可以避免无意的操作而修改你本不想修改的值,同时const使得函数能够处理const和非const的类型,负责将只能接收非const的数据类型,而且const使得函数能够正确生成并使用临时变量。所以应尽可能的使用const。

那么现在来总结下如何定义函数的参数类型:

a)对应使用传递值而不作修改的情况:

如果数据量较小,如内置基本数据类型或小型结构,则按值传递;

如果数据对象是数组,则使用指针,因为这是唯一的选择。并将指针声明为指向const的指针。

如果数据量比较大的结构,则使用const指针或const引用,以提高程序效率。这样可以节省复制结构的时间和空间。

如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用。这是C++增加这项特性的原因。因此传递类对象参数的标准方式是按引用传递。

b)对于需要修改调用函数中数据的函数(通常讲的输出)

如果对象是内置数据类型,则使用指针。

如果对象是数组,则只能只用指针。

如果对象是结构。则使用引用或指针。

如果对象是类对象,则使用引用。

且上述变量都不能加const修饰。

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bjtuwayne

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值