(九十二)函数
函数的分类:
①有返回值的;
②无返回值的。
无返回值函数:
被称为 void 函数,其通用格式如下:
//可以不传递变量
void 函数名(传递的变量)
{
}
函数内部的代码;
return; //可有可无
例如:
void abc(int a)
{
using namespace std;
//如果函数外面没有全局的 std,那么可以加上,或者用其替代版
for (int i = 0;i < a;i++)
cout << i << endl;
}
效果是输出 i,一直到 i 比传递的变量 a 小为止。
int a 表示在调用函数 abc 时,应传递一个参数给这个函数。比如 abc(5);就是让函
数在执行的时候 a=5。
有返回值的函数:
通用格式类似 void 函数,只不过 return 是必须的,并且需要返回一个函数值,
格式如:
类型名 函数名(传递的变量) //可以不传递变量
{
函数内部的代码;
return 值;
//必须有
}
return 返回的值是根据类型名决定的。例如返回 5.1,假如类型名为 int,那么返
回值为 5(因为 int 类型强制转换返回值)。
返回值:
C++对返回值的类型限制为:不能是数组,但可以是其他类型。比如 int,比如表
达式,比如指针,比如结构和对象(暂时还不能理解)。
另外,按照说明,虽然函数不能返回数组,但是可以将数组作为结构或者对象组
成部分来返回)。
函数在遇见第一个返回语句(return)后结束,无视后面的语句。
返回语句可以是表达式,例如 return a*a*a;
函数原型:
为什么需要函数原型:
①原型描述了函数到编译器的接口。——将函数返回值的类型,和参数的类型、
数量告诉编译器。
②原型告诉编译器,函数需要什么。
例如代码:
int abc(double); //函数原型
……
double b = abc(3.3);
(1)比如在调用函数的时候,函数原型就告诉编译器,需要输入一个 double 类
型的值(假如输入类型错误,比如输入了一个 char 类型的,编译器就会提示错
误)。
//通过函数原型调用函数,将返回值用于赋值
(2)然后 abc(3.3)在调用函数并计算之后,返回一个值,并将该值放在一个指定
的位置上(可能是 CPU 寄存器,或内存之中,具体我不知道……),然后调用
函数(比如说 main())从这个位置取回返回值;
由于函数原型指出了 abc()的类型为 int,因此编译器知道应检索多少个字节,以
及如何解释他们。如果没有这些信息,编译器只能进行猜测,当然,编译器不可
能去猜他们。——还是不太理解这段话第二个逗号之后的意思。
③之所以需要原型,而不是在使用的时候在文件中进行检索函数的定义。是因为
这样做效率低。因为在检索的时候,编译器停止对例如 main()的编译,甚至函数
定义可能不在文件之中。——文中的解释看起来有点晕。
④但是函数原型是如何和函数定义相关联的?——即其机制是怎么样的?
函数原型的语法:
类型名 函数名(传递的变量);
①函数原型是一条语句,所以以分号结束“;”(英文的分号);
②如果有返回值的话,需要有对应的类型名;如果没有的话,可以为 void;
③函数名就是函数名,和函数定义的函数名要相同;
④如果有传递的变量,则写上变量名;如果没有,可以为空。可以加变量名,也
可以不加(并没有什么区别,变量名只是起到一个占位符的作用,也可以和函数
定义的变量名不一样)比如(int a)和(int)似乎没有区别。
⑤可以直接复制函数定义的函数头,作为函数原型。
两种函数原型的区别(ANSI C 和 C++):
ANSI C 借鉴了 C++的原型,但为了与基本 C 兼容,ANSI C 的原型是可选的,C++
是必备。
double abc() ;
以上这个函数原型,在 C++里,绿色部分实质上和(void)是等价的;
在 ANSI C 中,意味着不指出参数——在之后定义参数列表(不懂!是指在函数
定义时么?)。
而 C++如果是不指定参数列表的话,应该用省略号,如
double abc(...) ;
————但是 C++中的不指定参数列表在实践中失败了。
一般仅当与接受可变参数的 C 函数(比如 printf())交互时才这么做。
原型的功能:
原型对程序员来说,可以极大的降低程序出错的几率。因为,原型可以确保以下
几点:
①编译器能正确处理函数返回值(通过函数的类型名);
②编译器检查使用的参数数目是否正确(比如需要 2 个参数,在调用的时候,是
否传递了 2 个参数);
③编译器检查使用的参数类型是否正确(比如 int 和 char)。如果不正确,则转
换为正确的类型(如果可能的话,比如把 int 转为 double,把 double 转为 int 之
类可以互转的);
④可以提示程序员,原型中传递的参数的变量名(作为占位符使用)在函数定义
中意味着什么(例如某个 int 类型变量表示传递的是血量,则在原型中使用 int hp)
⑤避免发生以下错误:
(1)传递的变量的类型和要求的变量类型不同——此时将通过强制类型转换解
决这个问题(但前提两个都是算数类型);
不过潜在的错误是,将大的范围的数转为小的范围的数,可能会出错。比如某个
超出 int 类型范围的数转为 int。
函数原型自动将被传递的参数(比如 abc(5.5)中的 5.5)强制转换为期望的类型(比
如 int 类型的 5)。
(2)但若传递变量的类型和要求变量的类型差异较大时(比如 int 和指针),那
么不会启动强制转换——因为没有意义。
函数定义:
形参和实参:
例如一个函数头为:double abc (double x);在调用这个函数的时候,函数创建了
一个 double 变量 x,就像在函数里面进行声明一样。
只不过,这个变量 x 的值,是在调用的时候根据传递参数的值来确定的。
例如在调用函数时:double e=abc (5); 那么在调用函数 abc 的时候,x 的值就为 5。
假如说有代码:
int a = 5;
double b = abc(a);
……
double abc(int x)
{
int b = x++;
return b;
}
在调用函数时,函数 abc 使用的 x 是之前函数中变量 a 的副本,对 x 的操作(x++)
将不影响之前函数中的变量 a。
对于这种情况,用于接收传递值的变量(在这里是 x)被称为形参(在 C++又被
称为参量 parameter);
传递给函数的值被称为实参(在这里是变量 a)(在 C++又被称为参数 argument)。
在函数中声明的变量,例如上面的 int b,以及形参(参量)(如上面的 int x),
是该函数(double abc)所私有的。
在函数被调用的时候,计算机为这些变量分配内存,在函数结束时,计算机将释
放这些函数的内存。因此,他们是局部变量(自动变量)。
形参和其他局部变量最大的区别是:
①形参从调用该函数的那里获得自己的值;
②而其他变量在函数内部获得自己的值。
多个参数:
函数可以有多个参数,在调用的时候,参数和参数之间用逗号分开即可。
例如:double abc(int a,double b,string c)
在调用的时候:double m=abc(1, 2.2, "abc")
三个不同类型的变量 1、2.2、abc 被同时传递给函数 abc 的三个不同类型的变量
a、b、c。
在声明的时候需要这么做:double abc(int, double, string);
和普通声明非常相似。
注意:
①多个参数的类型可以相同,也可以不同。
②多个参数在传递变量时,要依次给一个确定的、且符合要求的值。
(九十三)函数与数组
当且仅当在函数头或者函数原型中:
例如:int *a 和 int a[] 这两个的含义是相同的,指的是 a 是一个 int 指针。
但是在函数调用和函数内部,这两个的含义并不相同。
指针算数:
在之前的指针算数中说过,指针实际上就是一个地址,是指针指向的那个地址;
指针可以直接和一个 int 值相加,结果是指针指向的地址偏移。
在使用函数时,传递参数可以是一个地址,通过传递地址、以及成员数量,然后
变相传递一个数组。
例如代码:
//关键词:随机受到多次伤害,并通报每次伤害的值。
#include
#include
//函数 clock()使用
#include
//函数 Sleep()使用
using namespace std;
const int number = 5; //常量 number,方便修改次数
int dam(int*str, int cishu);
//总伤害=每次的伤害之和
int main()
{
int abc[number];
cout << "你受到了" << number << "次伤害,系统将随机计算这" << number << "次的伤害值:
" << endl;
for (int i = 0;i < number;i++) //为数组循环赋值,利用循环数
{
double delay = rand() % 1000; //随机一个 rand 值,范围为 0~999。注意,这是伪随机
数,所以每次随机的结果似乎都是一样的。
cout << "等待" << delay / 1000 << "秒,计算下次伤害" << endl;
Sleep(delay); //等待 rand 值的毫秒
abc[i] = clock() % 100;
//利用时钟除以 100 求余,计算 0~99 的随机值,并为数组成
cout << "你受到的第一次伤害为:" << abc[i] << endl;
//通报数组成员的值
员赋值
}
int total = dam(abc, number); //定义 total,传递的函数为数组 abc(的地址),以及数组
成员的个数 number
cout << "你总共受到了" << total << "点伤害。" << endl;
cout << "sizeof 总伤害的数组: " << sizeof(abc) << endl << endl;
//计算前 3 次伤害
total = dam(abc, 3);
//计算前 3 次伤害
cout << "你前三次总共受到了" << total << "点伤害。" << endl;
//偏移指针
total = dam(abc + 2, 3);
//计算第 3~5 次伤害
cout << "你第三次~第五次总共受到了" << total << "点伤害。" << endl;
system("pause");
return 0;
}
int dam(int*str, int m)
{
int total = 0;
for (int i = 0;i < m;i++)
total = total + str[i];
//等于之前的和加现在的这个
cout << "dam 函数中的指针的地址:"<
dam 函数中的指针的地址:003FFBEC
sizeof dam 函数中的指针 = 4
你总共受到了 213 点伤害。
sizeof 总伤害的数组: 20
dam 函数中的指针的地址:003FFBEC
sizeof dam 函数中的指针 = 4
你前三次总共受到了 106 点伤害。
dam 函数中的指针的地址:003FFBF4
sizeof dam 函数中的指针 = 4
你第三次~第五次总共受到了 123 点伤害。
请按任意键继续. . .
总结:
①想给函数传递一个数组,那么就先传递数组的地址,再传递数组成员的个数。
——相当于把一个数组分成两部分。
②数组名,实际上就是数组的地址,也是一个指针;
③对指针加减实际上是使指针指向地址的偏移(若干个指针类型的宽度);
④由于传递给函数的参数是地址,且是 int 或者其他类型的指针变量,所以,sizeof
这个形参,值为类型的宽度,而不是数组的宽度。
而实参的 sizeof 是数组的宽度,一般要比形参大。
⑤给数组名加上“&”则显示的是指针地址;
直接输出指针,显示的也是地址;
如果不偏移指针的话,显示的是偏移前的指针地址;
偏移指针的话,显示的是偏移后的指针地址。
⑥不能在函数头中使用例如 int a[5]来传递数组,只能使用 int*a 来传递地址;
将传递的参数设为只读:
在不用指针的情况下,传递给函数的是一个副本变量(形参),形参的改变不影
响实参。
在使用指针的情况下,传递的参数是指针,因此可以通过操作指针来对值进行修
改,这在某些时候是有用的。
然而,在某些情况下,我们希望不要修改传递给函数的值,这个时候,我们需要
使用的是关键字 const,例如 void abc(const int a);这样
如代码:
#include
using namespace std;
void abc(const int*,int);
void def(int*, int);
int main()
{
const int m = 2;
int a[m] = { 5,10 };
abc(a, m);
cout << a[0] << endl;
cout << a[1] << endl;
def(a, m);
cout << a[0] << endl;
cout << a[1] << endl;
system("pause");
return 0;
}
void abc(const int*x, int y)
//假如这里不使用 const,那么这个函数里面的两句代码就可以
执行
{
}
//x[0] = 1;
//x[1] = 15;
void def(int*x, int y)//这里没有使用 const,于是可以修改值
{
}
x[0] = 1;
x[1] = 15;
输出:
5
10
1
15
请按任意键继续. . .
总结:
①使用 const 的情况下,函数 abc 内的两行代码是无法执行的,数组也无法被修
改。
②不使用 const 的时候,函数 def 内的两行代码可以执行,于是可以通过修改指