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];
,则数组区间信息就是:arr
和arr + 20
。
指针加减法都是以指针所指向数据类型为单位进行的,例如:&arr[19] - &arr[0] = 19, &arr[0] + 1 = &arr[1]。
-
请注意:
const int *p
和int * 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元素的数组地址,因为*arr
和arr[]
是可以相互转换的,所以存在以上两种写法,但显然后一种更容易理解。
因为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>