第七章 函数

第七章  函数

7.1  函数的基础知识

要使用函数,必须完成如下工作:

Ø  提供函数定义

Ø  提供函数原型

Ø  调用函数

7.1.1  函数的定义

函数总体来说可以分为两类,一类是没有返回值的,另一类是具有返回值的,两类的函数定义的格式如下

void functionName(parameterList)
{
    statement(s)
    return;            //可以有也可以没有
}
typeName functionName(parameterList)
{
    statement(s)
    return value;    // value 的类型是typeName或者可以自动转换为typeName
}

C++对于返回类型有限制,不能是数组,但可以是整型、浮点型、指针、结构和对象。函数内在遇到第一个返回语句将结束。     有返回值的函数,必须使用返回语句,返回值可以是常量、变量或者表达式。其结果必须可以转化为typeName类型。

7.1.2  函数原型和函数调用

函数原型描述了函数到编译器的接口,把函数的返回类型、参数列表(数量,类型和顺序)告知编译器。

函数原型是一条语句,必须以分号结束,可以通过复制函数头并添加分号来快速构建函数原型。

函数原型不要求提供变量名,但是拥有变量名的函数原型可以提高程序的可读性。

函数原型的作用有3个:

Ø  编译器正确处理函数返回值;

Ø  编译器检查使用的参数数目是否正确;

Ø  编译器检查使用的参数类型是否正确,如果不正确,则转化为正确的类型(如果可能)。

7.1.3  ANSI C和C++ 的函数原型的区别

在ANSI C中函数原型是可选的,而在C++中函数原型是必须的。

在C++中,圆括号中参数列表为空与在圆括号中使用void是等效的,意味着没有参数。在ANSI C中圆括号为空意味着不指出参数,将在后面定义参数列表。C++不指定参数列表应该使用省略号(…),例如:

void say_bye(...);        //不指出参数,将在后面的定义中指出参数列表

C++中,通常仅当与可变参数C函数交互时才这样做。

7.2  函数参数与按值传递

按值传递参数时,将数值参数传递给函数,而后将其赋值给新变量。如:

double cube(double x);
double a = 1.0;
double volume = cube(a);

函数cube被调用时,将创建一个名为x的新变量,并初始化为a的值1.0。这样在函数内部对x的修改,将不会改变a的值。

形参:接收传递值的变量。

实参:传递给函数的值。

C++使用参数(argument)来表示实参,参量(parameter)来表示形参。

函数中声明的变量是函数私有的,函数被调用是,为这些变量分配内存,在函数结束时,释放这些内存。这样的变量成为局部变量。存储类型为自动存储。

7.3  函数和数组

看如下原型:

int sum_arr(int arr[], int n)    //arr是数组,n是数组元素数量

参数arr是一个数组,但实际上在函数内部,arr并不是数组,而是一个指针。

在C++中,当且仅当用于函数头或函数原型时,int arr[]和int * arr等价。但是int arr[]可以具有提醒的作用,表示传递的指针为数组第一个元素的地址。

当把数组第一个元素的地址和数组元素的数目传递给函数时,并没有将函数的内容传递给函数,而是将数组第一个元素的地址,数组元素的类型和数组元素的数目传递给了函数。当传递常规变量时,函数将使用该变量的拷贝,淡传递数组时,函数将直接使用该数组,这一位置对数组内容的改变会改变原来的数组。

实际上传递数组时,所传递的地址也可以看做为普通变量,函数将使用指针的拷贝,把拷贝的指针指向其他地址,将不会修改原来的指针值。

传递数组地址可以节省复制整个数组内容所需的时间和内存,但是另一方面会对原数据带来风险。ANSI C和C++使用const限定符来解决这个问题。如下:

void show_arr(const double arr[], int n);        //参数为指向const的指针
void show_arr(const double *arr, int n);        //与上一个完全等价

这两个函数将不能在函数内部修改数组的内容,任何赋值操作将会被编译器报告错误。

可以用2种方式将关键字const用于指针。

1.        使指针指向常量;

2.        将指针本身声明为常量。

如下代码:

int n = 10;
const int * p1 = &n;    //可以修改p1的值,但是不可以修改*p1的值
int * const p2 = &n;    //不可以修改p2的值,但是可以修改*p2的值

可以将const变量或者非const变量的地址赋值给指向const的指针,但是只可以将非const变量的地址赋值给常规指针。

当在函数内部不修改指针或者数组的内容时,应当使用指向const的指针。有2个理由:

Ø  可以避免无意的修改而导致的错误

Ø  使用const作为形参,可以接受const和非const实参,否则只能接受非const实参。

7.4  函数和二维数组

看如下原型:

int sum(int m[][4], int size);    //size指定行数,4为列数
int sum(int(*m)[4], int size);    //与上一个等价

上面的int (*m)[4]的圆括号必不可少。带括号表示m是一个数组,数组内元素的类型是指向含有4个元素数组的指针;不带括号表示m是一个指针的数组,作为形参就是int **类型。原因是[]运算符的优先级高于*,不加括号时,[]先作用于m,表示m是一个具有4个元素的数组,而元素的类型是int *;加括号时,*先作用于m,表示m是一个指针,所指类型为一个具有4个int型元素的数组。

7.5  函数和C-风格字符串

表示字符串的方式有3种:

Ø  Char数组

Ø  用引号括起的字符串常量

Ø  被设置为字符串地址的char指针

C-风格字符串内置有’\0’,表示字符串的结尾,因此作为参数可以不用将元素的数量传递给函数。

7.6  函数和结构

函数处理结构和处理基本类型的变量类似。

需要注意的是,当结构很大时,应该选用指向const的指针来代替直接传递结构,这样做可以提高效率。

7.7  函数与array对象

Array为C++11的模板类。将array对象作为参数传递给函数时,将拷贝array的内容。

7.8  递归

C++函数可以自己调用自己,与C语言不同的是C++不允许main函数调用自己。这种功能成为递归。一般的格式如下:

void recurs(argumentList)
{
    statements1
    if (test)
    {
        recurs(arguments)
    }
    statements2
}

这里如果test值为false时,调用链将结束。

7.9  函数指针

函数指针的好处是可以将函数名作为参数传递给函数,这样可以给函数传递策略。当修改策略时,不用修改函数本身,只需要传递不同的函数指针实参,这就是委托协议。

使用函数指针必须完成以下工作:

Ø  获取函数地址

Ø  声明一个函数指针

Ø  使用函数指针调用函数

1) 获取函数地址

获取函数地址就是把函数名作为参数传递给其他函数,如:

process(think);        //把think函数地址传递给process
thought(think());    //把think函数的返回值传递给thought

2) 声明函数指针

声明方法如下:

double f(int);            //f是函数
double(*pf1) (int);        //pf1是函数指针
pf1 = f;                //为函数指针赋值
double(*pf2) (int) = f;    //声明时初始化

通常,为了声明特定类型的函数指针,可以首先编写函数原型,再用(*pf)替换函数名,这样pf就是这种类型的函数的指针。

3) 使用函数指针调用函数

使用函数指针有两种方式调用函数。一种是使用(*pf),另一种是直接使用pf。如下:

double x = f(4);               //通过函数名调用函数
double y = (*pf1)(5);         //通过函数指针调用函数
double z = pf2(6);           //函数指针名直接调用函数

实际上函数名就是函数的起始地址,C++折衷考虑使用两种方式都可以调用函数。

7.10  深入探讨函数指针

先看下面几个等价的函数:

    const double * f1(const double arr[], int n);
    const double * f2(const double[], int);
    const double * f3(const double *, int);

声明函数指针并赋予初值:

const double * (*p1) (const double*, int) = f1;

可以使用C++11的自动类型推断:

auto p2 = f2;

函数指针数组声明如下:

const double * (*pa[3]) (const double*, int) = { f1, f2, f3 };

注意这里[3]的位置。这里不用为*pa加括号是因为[]运算符的优先级高于*运算符。[]首先作用于pa,表示pa是一个具有3个元素的数组,元素的类型是函数指针。这里也不能用auto自动类型推断。因为自动类型推断只能用于单值初始化,而不能用于初始化列表。但是可以声明同样类型的数组:

auto pb = pa;

Pa和pb都是指向函数指针的指针,可以这样调用:

double av[3] = { 1.0, 2.0, 3.0 };
const double * px = pa[0](av, 3);
const double * py = (*pb[0])(av, 3);
double x = *pa[1](av, 3);
double y = *(*pb[1])(av, 3);

可以使用自动类型推断简单声明指向整个数组的指针:

auto pc = &pa;

Pc的类型为

const double * (*(*pd)[3]) (const double*, int) = &pa;

使用typedef简化声明:

typedef double real;    //real是double的别名
typedef const double * (*p_fun) (const double*, int);    //p_fun是函数指针类型
p_fun p1 = f1;
p_fun pa[3] = { f1, f2, f3 };
p_fun(*pd)[3] = &pa;

这里再说一下使用auto的利弊:

利:使用auto可以简化声明;

弊:可能提供错误的初值,使声明的类型不正确。

时间: 2024-08-05 15:25:01

第七章 函数的相关文章

语法》第七章 函数

(本文为阮一峰js标准教程的学习笔记,旨在总结该教程中涉及的知识点大纲及个人所做的一些拓展,方便作为"目录"或者"大纲"复习和查漏补缺,详细内容请参见阮一峰教程原文) 第二部分 语法 ************第七章 函数************ 一.概述函数就是一段可以反复调用的代码块.函数还能接受输入的参数,不同的参数会返回不同的值.1.函数的三种声明方法1.1function命令[标准函数声明方法]function 函数名(传入参数){函数体} 1.2采用函数表

C++_第七章函数的基本知识_求阶乘的子函数_ 函数参数类型为数组_ 求数组内所有元素和、部分元素和的方法_实现了先从键盘输入到一个数组中,再用for循环取读出数组中的元素 for循环也可以用break来结束循环的

/* 第七章函数的基本知识 */ /*01)c++对于返回值有一定的限制:可以是常量.变量.指针.结构对象或表达式,但不可以是数组02)c++返回数组的方法:将数组作为结构会对象组成部分来返回03)函数遇到return则结束该函数04)如果一个函数的两房额参数类型相同,则必须分别制定每个参数的类型,而不能像声明常规变量那样,将声明组合在一起05)*/ //本代码注意double类型的写法以及double和int类型数据的转换 1 #include <iostream> 2 3 void che

C++primer第七章 函数

本章将介绍函数的定义和声明.然后具体分析三类特殊的函数:内联(inline)函数.类成员函数和重载函数. 7.1. 函数的定义 函数由函数名以及一组操作数类型唯一地表示.函数的操作数,也即形参,在一对圆括号中声明,形参与形参之间以逗号分隔.函数执行的运算在一个称为函数体的块语句中定义.每一个函数都有一个相关联的返回类型. 函数的调用 C++ 语言使用调用操作符(即一对圆括号)实现函数的调用.正如其他操作符一样,调用操作符需要操作数并产生一个结果.调用操作符的操作数是函数名和一组(有可能是空的)由

JavaScript高级程序设计(第三版)第七章 函数表达式

一种是函数声明:另一种是函数表达式. 函数声明提升: say Hi(); function say Hi(){ alert("Hi!"); }      //这个例子不会抛出错误,因为在代码执行之前会先读取函数声明. 匿名函数:var functionName=function(arg0,arg1,arg2){函数体}; sayHi();   //错误:函数还不存在 var sayHi=function(){ alert("Hi!"); }; //不要这样做    

C和指针 第七章 函数递归与迭代

C语言通过运行时堆栈支持递归函数的实现,递归函数时直接或者间接调用自身的函数,经常有人拿斐波那契实现当做递归的实现,然后这样做效率并不高. n < 1;  Fib(1) =1 n = 2;  Fib(2) = 1 n > 2; Fib(n) = Fib(n - 1) + Fib(n - 2); 由于每个递归调用都会触发另外两个递归调用,而这两个调用还将继续触发下去,这样会有大量的冗余计算.例如:计算Fib(10)过程,Fib(3)被计算了21次: #include <stdio.h>

读书笔记 - js高级程序设计 - 第七章 函数表达式

闭包 有权访问另一个函数作用域中的变量的函数 匿名函数 函数没有名字 少用闭包 由于闭包会携带包含它的函数的作用域,因此会比其它函数占用更多的内存.过度使用闭包可能会导致内存占用过多,我们建议读者只在绝对必要时再考虑使用闭包 模块模式   增强的模块模式   特权方法 有权访问私有变量的公有方法叫做特权方法 块级作用域   实现单例的特权方法  

第七章 函数表达式

闭包 在一个函数内部创建一个子函数,子函数可以访问父函数作用域中的变量. function f (propertyName) { //创建 f()函数时,会创建一个预先包含全局变量的作用域链,这个作用域链中包含函数f(),并把这个作用域链保存再内部的[[scope]]属性中 return function(obj1, obj2) { //匿名函数(子函数)把父函数(f())的活动对象添加到它自己的作用域链中. var value1 = obj1[propertyName]; //匿名函数被返回后

JavaScript高级程序设计:第七章 - 函数

六.函数表达式 //把函数当成值来使用的情况下,都可以使用匿名函数 递归 //递归函数是在一个函数通过名字调用自身的情况下构成的 //使用函数声明来定义递归函数可能会出现问题 //这是一个经典的递归阶乘函数 function factorial(num) { if (num<1){ return 1; }else{ return num * factorial(num-1); } } //使用函数声名来定义该递归函数时,函数名和函数体会产生耦合. //当其他指针指向该函数体的时候,由于执行ano

C和指针 (pointers on C)——第七章:函数(上)

第七章 函数 这一章对于有一定C的基础的人有一定优秀代码风格的人来说,并不是很虐.关于stdarg宏可能有些陌生,它负责可变参数列表的定义. 总结: 新式风格和旧式风格就不要提了.八百年前的事情. 函数常见的是把原型放在一个单独的文件里,当其他文件需要这个原型时,就用#include指令把这个文件包含进来,这个技巧可以使原型必需的拷贝份数降低到最低,有助于提高程序的可维护性. return语句用于指定从一个函数返回的值,如果没有返回值,为void. 函数的参数是通过传值的方式进行转换,实际传递的