《C++ Primer Plus》:函数

本章内容概览

  • 基本知识
  • 函数原型
  • 按值传递函数参数
  • 函数和数组
  • 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;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352