C++plus6th 第7章函数(上)

第七章 函数(模块)

1. 关于函数返回值

  • 在C++中,函数分为有返回值和无返回值两种。函数可以返回除数组之外的任意类型,包括基本类型、指针、结构体和对象。所以虽然数组不能返回,但可以将数组作为结构或对象(类的实例)的组成部分来返回。

    • 如果需要将数组作为参数,一般的做法是传递数组名和数组大小。例如int funa(int arr[], int arr_size);。因为传递的是数组名(实际上在作为函数形参后将退化成数组首元素地址),这种方法会改变原数组的值的风险。当然在C++和ANSI C中,可以使用const限定符。

    • 实参(数组名)和形参(已退化成数组首元素地址)指向同一个地址,但对实参(数组名)使用sizeof将得到数组所占字节数,对形参使用sizeof将得到指针所占字节数。

  • 函数通过函数定义将特定类型的返回值复制到指定的寄存器或内存地址将其返回。随后,调用程序将查看该地址,并通过函数原型得知数据类型。

  • 所以在调用函数前,一定要先声明函数原型(prototype),一般来说,会以函数定义所在文件的名字,创建一个同名头文件,并在其中集中声明所有函数的原型。当然,如果某个静态函数是先定义后被调用的话也可以不需要声明。

注意:在C++中,括号为空与在括号中使用void关键字是等效的。但在ANSI C中,括号为空意味着不明确指出参数。在C++中,表示不明确指出参数列表使用省略号(3个连续点号)。通常,仅当与接受可变参数的C函数(如printf())交互时才需要这么做。

2.关于数组作为函数参数

C++通常按值传递参数,即将实参(argument)赋值给形参(parameter)后进行运算。这样不会因为函数调用而影响到实参的值。

image-20210317233456734
  • 数组处理函数的常用编写方式

假设编写一个处理double数组的函数。

如果该函数要修改数组,其原型如下:

void func_modify(double arr[], int size);

如果该函数不修改数组,其原型如下:

void func_no_change(const double arr[], int size);

当然,在原型中可以省略形参名,也可以将返回类型指定为其它类型。此处的要点是arr实际上是一个指向数组首元素的指针。注意:在函数内部不能通过sizeof(arr) 获取数组长度,而必须通过参数size传入。

  • 当然也可以通过向函数传递数组区间信息:数组首元素地址、数组尾元素后面一个元素地址(超尾)。例如有数组int arr[20];,则数组区间信息就是:arrarr + 20

指针加减法都是以指针所指向数据类型为单位进行的,例如:&arr[19] - &arr[0] = 19, &arr[0] + 1 = &arr[1]。

  • 请注意:const int *pint * const p的区别,前者表示p指向的地址里存储的数据不能改变,后者表示p代表的地址不能改变,强调的是地址不变。

  • 将常规变量的地址赋给const指针可行(但仅限于一级指针,二级及以上指针将不再成立),但将const变量的地址赋给常规指针却不行。

<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c++" cid="n40" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> int age = 18;
const float length = 180;
const int * p = &age; // 一级指针下合法。
float * pl = &length; // 不合法!

*p = 20; //错误,利用p表达式指向的地址单元存贮的元素不能变。

age = 20; //成功,因为age声明本身就是一个非常量变量,它可以改变,只是不能利用p进行改变而已。</pre>

image-20210319222350563
  • 当二维数组需要作为函数参数时,仅需要传递数组首地址和行数,列数在原型中指出即可。

假设存在如下二维数组int arr[3][2] = {{1,2},{3,4},{5,6}};,当其作为参数需要被传入函数sum()时,可以采用如下两种原型:

int sum(int (*arr)[2], int row);或者int sum(int arr[][2], int row);

arr[3]里存的3个元素是含有2个int元素的数组地址,因为*arrarr[]是可以相互转换的,所以存在以上两种写法,但显然后一种更容易理解。

因为arr指针类型决定了sum函数只能接受2列二维数组,但row变量指定了行数。

  • 字符串作为函数参数时,可以将其看作是字符数组来对待,但不需要传递数组个数,因为字符串的末尾存的是'\0'。在定义函数的形参时有:char str[]、char *str、"hello world!"三种形式。

3.关于结构体作为函数参数

结构体作为函数参数或返回值时是作为一个整体进行值传递的,且结构体变量名并不代表结构体地址,而必须前缀&符号。但如果结构体很大,则也可以采用结构体地址作为参数,另外C++中还提供了引用传递来解决这个问题。

4.关于string对象作为函数参数

虽然字符串和string类对象的用途几乎相同,但与数组相比,string对象与结构体更相似。它们都可以作为一个整体传递给函数,也可以相互间直接赋值。如果需要多个字符串,可以声明多个string对象,而无需建立二维数组。

下面的程序声明了一个string对象数组,并将该数组传递给一个函数以显示字符内容:

<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c++" cid="n57" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> //XXX.cpp -- handing an array of string objects

include <iostream>

include <string>

using namespace std;
const int SIZE = 5;
void display(const string sa[], int n);

int main()
{
string list[SIZE];
cout << "Enter your " << SIZE << "favorite astronomical sights:\n";
for (int i = 0; i < SIZE; i++)
{
cout << i+1 << ": ";
getline(cin, list[i])
}

cout << "Your list:\n";
display(list, SIZE);

return 0;
}

void display(const string sa[], int n)
{
for (int i = 0; i<n; i++)
cout << i+1 <<": " << sa[i] << endl;
}</pre>

5.关于array对象作为函数参数

因为类对象是基于结构的,因此类对象和结构体一样可按值将对象传递给函数,此时函数处理的是类对象的副本;也可传递对象的地址,这样可以处理原始对象。要使用array类,需要包含头文件array,且命名快进为std。

如果要使用array对象(名为arr_name)存储4个double型元素,可以按此定义:std::array<double, 4> arr_name;

  • 当该array对象按值传递作为函数参数时,声明如下:void func_name(std::array<double, 4> arr_name);。(形参名可省略)

  • 当该array对象按地址传递作为函数参数时,声明如下:void func_name(std::array<double, 4> * arr_p);。(形参名可省略)

6. 关于递归

通常将递归调用放在if语句中,其结构如下:

<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c++" cid="n68" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> void recurs(参数列表)
{
语句1
if (测试语句)
recurs(参数列表)
语句2
}</pre>

测试语句终会为false,调用解开。

只要测试语句为真,每个recurs()调用都将执行语句1,然后再调用下一层recurs(),而不会执行语句2,直到测试语句为假,当前调用解开循环链,返回后将控制权交予上一层调用,而上一层执行语句2后再网上一层释放控制权,依次类推。如果recurs()进行了5次递归调用,则语句1将按照函数调用顺序执行5次,然后语句2将以相反的顺序执行5次。

请看示例:

<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c++" cid="n72" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> #include <iostream>
void countdown(int n);

int main()
{
countdown(4);
return 0;
}

void countdown(int n)
{
using namespace std;
count << "Counting down ... " << n << endl;
if (n>0)
countdown(n-1);
count << n << ": Kaboom!\n";
}


下面是程序输出:
Counting down ... 4 //第1层
Counting down ... 3 //第2层
Counting down ... 2 //第3层
Counting down ... 1 //第4层
Counting down ... 0 //第5层
0: Kaboom! //第5层,开始返回
1: Kaboom! //第4层,开始返回
2: Kaboom!
3: Kaboom!
4: Kaboom! //第1层,开始返回</pre>

注意:每一层调用,函数都将创建自己的变量存储区,所以如果嵌套过深,对系统内存消耗很大。

  • 另外一种递归是包含多次调用自身的形式,例如:

<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c++" cid="n77" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> void subdivide(char ar[], int low, int high, int level)
{
if (level == 0) //递归终止条件
return;
int mid = (high + low) / 2;
ar[mid] = '|';
subdivide(ar, low, mid, level-1);
subdivide(ar, mid, high, level-1);
}</pre>

7. 关于函数指针

  • 函数名就是函数的地址;

  • 函数声明如下:函数返回值类型 (*pf)(参数类型列表),简单来说就是将函数原型中的函数名换成(*p)就是指向该类型函数的指针声明。例如,double (*pf)(int);声明了一个返回double类型,参数是一个int的函数指针,它可以指向形如double pam(int)的函数,当执行pf = pam;后就可以以(*pf)(n)来调用函数pam(n)了(n为int变量)。但其实在C++中,也可以把函数指针变量当成函数名来使用,即写成pf(n)也可以调用pam(n)函数.

  • 如何声明一个包含3个函数指针的数组呢?

    • 首先它是一个包含3个元素的数组:p[3]

    • 数组的元素是指针:*p[3]

    • 什么指针呢?指向 “返回值是double *的,参数有一个int的函数”的指针

    • 最后形式为:double * (*p[3])(int);

  • 注意区分如下声明:

    • p[3]: 包含3个指针元素的数组*。

    • (p)[3]: 一个指向 ”有3个元素的数组“ 的指针*。

    • 假如存在数组arr[8],arr、&arr[0]、&arr均指向同一个地址,但代表的意义不一样:

      • arr:数组名,代表数组首元素地址

      • &arr[0]:数组首元素地址

      • &arr:整个数组的地址,其+1后等于整个数组后面8个内存块的地址。

      • **&arr == *arr == arr[0]

  • C++11中有一个叫做auto的类型,它会根据其右值变量的类型自动定义左值类型,在用于比较难以准确声明的类型时非常有用。例如存在函数原型:double * func(const char[], int);当我需要定义一个指针变量用于指向该类函数时非常难写其类型,此时只需要用:anto pf = func_name;就可以申明pf的类型了。func_name为一个func类型的函数名。

<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c++" cid="n115" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> double * func(const char[], int n)
{
...
}

auto pf = func; //auto将自动推断出pf的类型应该为指向func()一类函数的指针;</pre>

  • 除了自动类型可以减少变量类型的书写外,typedef关键字也可以省略繁琐的类型声明。例如:

<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c++" cid="n120" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> typedef const double (p_fun)(const double *, int); //p_fun作为某型函数指针类型
p_fun p1 = fun_1; //利用p_fun来定义变量p1</pre>

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

推荐阅读更多精彩内容

  • 本章内容概览 基本知识 函数原型 按值传递函数参数 函数和数组 const指针参数 函数和字符串 递归 指向函数的...
    Dragon_boy阅读 234评论 0 0
  • 1.关于读取字符串 在读取输入时,如果使用cin函数,仅能每次读取一个单词,因为该函数默认遇到空白(空格、换行、制...
    Leon_Geo阅读 239评论 0 0
  • C++同样不能返回数组,但可以返回其他任何类型,虽然不能直接返回数组但是可以将数组作为结构或对象组成部分来返回。 ...
    鬼枭嗜阅读 284评论 0 0
  • 这是C++类重新复习学习笔记的第 四 篇,同专题的其他文章可以移步:https://www.jianshu.com...
    超级超级小天才阅读 489评论 0 1
  • 第五章(循环和关系表达式)和第六章(分支语句和逻辑运算符)直接跳过,所有语言都一样的,if/else/switch...
    zinclee123阅读 318评论 0 0