美文网首页c++精进
《C++ Primer Plus》:函数

《C++ Primer Plus》:函数

作者: Dragon_boy | 来源:发表于2020-07-28 16:05 被阅读0次

本章内容概览

  • 基本知识
  • 函数原型
  • 按值传递函数参数
  • 函数和数组
  • const指针参数
  • 函数和字符串
  • 递归
  • 指向函数的指针

函数基本知识

要使用C++函数必须完成以下工作:

  • 提供函数定义
  • 提供函数原型
  • 调用函数

void函数:

void functionName(parameterList)
{
     statements
}

有返回值函数:

typeName functionName(parameterList)
{
    statements
    return value;
}

返回值可以是整数、浮点数、指针、结构和对象等。

函数原型

函数原型在C++中是必须的,可以不提供变量名,有类型就够了:

double cube(double);

原型确保以下几点:

  • 编译器正确处理函数返回值
  • 编译器检查使用的参数数目是否正确
  • 编译器检查使用的参数类型是否正确。不正确则转化为正确的类型(如果可能)

注意,仅当有意义时,原型化才会导致类型转化(参数),例如:原型不会将整数转化为指针或结构。

函数参数和按值传递

接受传递值的变量称为形参,传递给函数的值称为实参。在函数调用时,计算机为函数中声明的变量分配内存,函数结束时自动释放,这被称为局部变量。

一个函数原型:

void fifi(float a, float b);

之后调用:

float c= 1.0f;
float d = 1.0f;
fifi(c, d)

c,d按形参位置传递给函数a,b。

函数和数组

将数组作为形参的函数原型:

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

arr是一个指针。

之前说过,C++将数组名解释为其第一个元素的地址,即函数传递的参数是一个地址,也就是形参类型为指针,所以上述原型等价于:

int sum_arr(int* arr, int n);

在C++中的函数头和函数原型中,int*等价于int[],但在函数体中并不等价。

如果我们调用上述函数:

int a[10] = {};
sum_arr(a, 10);

我们将数组首地址传递给函数,即将该地址赋予arr指针。这与常规变量不同,常规变量传给函数的是其拷贝,而这里是传地址,也就是该函数可以修改真的数组。

不过由于可以真的修改原数组,所以这种做法可能有风险,比如我们不想修改数组里的元素,只想使用的话,可以使用const 限定符来定义函数原型:

void show_array(const double ar[], int n);

如果在函数体中试图修改传入的数组,编译器就会报错。而函数原型将const double[]解释为const double* ar

const和指针

比如,我们声明一个指向常量的指针pt:

int age = 39;
const int* pt = &age;

该声明指出,pt指向一个const int(39),所以不能使用pt来修改该值,也就是*pt的值为const,不能修改:

*pt += 1;  //invalid
cin >> *pt;  //invalid

这样就很微妙,我们可以直接修改age的值,但不能通过pt来修改age的值:

*pt = 20;  //invalid
age = 20;  //valid

我们向下面这样声明指针变量:

const float g_earth = 9.80;
const float* pe = &g_earth;  //valid

const float g_moon = 1.63;
float* pm = &g_moon;  //invalid

第一种可行,但第二种不行。第一种情况,既不能直接修改g_earth的值,也不能通过pe修改。第二种情况下,如果将g_moon地址赋给pm,则可以使用pm修改g_moon的值,这样的话g_moon的const限定符就没意义了,所以C++禁止这种做法。

如果将指针指向指针,这会更复杂。如果是一级间接关系,将非const指针赋给const指针是可以的:

int age = 39;
int* pd = &age;
const int* pt = pd;  

但二级间接关系不行,如果可以进入二级间接关系,就可以可以这么做:

const int** pp2;
int *p1;
const int n =13;
pp2 = &p1;  //假设可以
**pp2 = &n;  //valid
*p1 = 10;  //valid

上述代码将p1地址赋给const pp2,因此可以用p1来修改const数据,这不安全,所以不能这么使用。

将指针参数声明为指向常量数据的指针的理由(即使用const):

  • 可以避免由于无意间修改数据而导致的编程错误。
  • 使用const使得函数能够处理const和非const实参,否则只能接受非const实参。

函数和二维数组

将二位数组作为函数形参的形式:

int sum(int (*ar2)[4], int size);

括号是必须的,表明参数是指针而不是数组,即一个指向由4个int组成数组的指针,下面的是不行的:

int *ar2[4]

上述声明是一个由4个指向int的指针组成的数组。
等价原型:

int sum(int ar2[][4], int size);

形参的指针类型声明了列数,size中指定了行数。

函数和C风格字符串

表示字符串的三种方式:

  • char数组
  • 用引号括起的字符串常量
  • 被设置为字符串的地址的char指针

这三种类型都是char*,所以在函数中作为形参时可以直接这么声明:

void int c_in_str(char* str);

同时可以使用const限定符:

void int c_in_str(const char* str);

函数无法返回字符串,但可以返回字符串的地址:

char* buildstr(const char*str);

函数和结构体

结构体可以直接作为变量类型在函数中使用:

struct travel_time
{
    int hours;
    int mins;
};

travel_time sum(travel_time t1, travel_time t2);

这时候传入函数的实参是结构体的副本,返回值也是。

不过还可以传递结构体的地址作为实参,声明函数时形参是结构体指针,同时应该使用const限定符:

void show_polar(const polar* pda);

使用函数时:

show_polar(&polar1);

函数和string对象

string对象可以像结构体那样作为变量直接传入,如果要使用多个字符串,可以声明一个string数组:

void display(const string sa);
void display(const string sa[], int n);

递归

函数自己调用自己即递归,例子:

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

递归一定要有出口条件,否则会无限递归。

函数指针

和数据项一样,函数也有地址。

比如编写要估算某种代码执行的时间的函数estimate(),要完成以下工作:

  • 获取函数的地址
  • 声明一个函数指针
  • 使用函数指针来调用函数

获取函数地址使用函数名即可。声明函数指针需要与原型对应,比如:

double pam(int);

针对上述原型,声明指针为:

double (*pf)(int);

这与原型方式一致,只是将函数名换为(*pf),也就是pam和(*pf)都是函数,那么pf就是函数指针。声明函数指针后,就可以将函数地址赋予它:

pf = pam;

上面的estimate()函数可以这么声明:

void estimate(int lines, double (*pf)(int));

第二个参数是一个函数指针,调用该函数:

estimate(50, pam);

我们还可以使用函数指针调用函数:

double x = pam(4);
double y = (*pf)(5);

C++还允许这么使用:

double y = pf(5);

深入讨论函数指针

下面一些函数原型,它们等价:

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

如果我们要定义一个函数指针指向上述三个函数之一:

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

还可以声明的时候初始化:

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

使用auto关键字会超级简单:

auto p2 = f2;

现在(*p1)(av,3)和p2(av,3)都调用指向的函数,即f1(),f2()。

如果要使用三个函数,定义一个函数指针数组很方便:

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

运算符[]优先级高于**pa[3]表明pa是一个包含3个指针的数组。这时候就不能使用auto进行简单地类型推断了,不过定义好函数指针数组后就可以使用auto:

auto pb = pa;

要调用函数的话,可以这样:

const double* px = pa[0](av, 3);
const double* py = (*pb[1])(av, 3);

第二种更直观。

然后如何创建指向整个数组的指针,我们可以使用auto:

auto pc = &pa;

如果要自己声明的话,可以这么做:

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

*pd[3]表明是一个3个指针的数组,(*pd)[3]表明是一个指向三个元素数组的指针。

要调用函数的话,pd是一个指针,那么*pd就是数组,(*pd)[i]是数组中的元素,即函数指针,比如(*pd)[i](av, 3),更直观的方式是(*(*pd)[i])(av, 3)

除了使用auto进行简化,C++还提供了typedef,针对类型可以使用typedef进行类型别名:

typedef double real;

对于函数指针还可以:

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

p_fun就成为了函数指针类型别名,然后可以:

p_fun pa[3] = {f1,f2,f3};
p_fun (*pd)[3] = &pa;

相关文章

网友评论

    本文标题:《C++ Primer Plus》:函数

    本文链接:https://www.haomeiwen.com/subject/sujxrktx.html