📄 c++5.dat
字号:
值传递也可以称之为"赋值调用",这种方式是把实参的值复制到函数的形式参数中,函数中的形式参数的任何变化都不会影响到实参变量的值.
3.3 函数调用过程
C++函数调用是基于栈存储结构来实现的.当函数被调用时,为函数形参、返回值和其它变量等在栈中分配内存空间,当函数返回时,这些内存空间又被释放,以便于再利用.
第四节 inline(内联函数)
我们看下面的函数,函数体中只有一行语句:
double Average(double total, int number)
{
return total/number;
}
定义这么简单的函数有必要吗?实际上,它还是有一些优点的:第一,它使程序更可读;第二,它使这段代码可以重复使用.但是,它也有缺点:当它被频繁地调用的时候,由于调用函数的开销,会对应用程序的性能有损失.例如,Average在一个循环语句中重复调用几千次,会降低程序的执行效率.
那么,有办法避免函数调用的开销吗?对于上面的函数,我么可以把它定义为内联函数的形式:
inline double Average(double total, int number)
{
return total/number;
上面的内联函数同它的非内联函数相比,仅仅是多了一个关键字inline,它们在功能上并没有区别:前者也是有两个形参,一个double型,一个int型,返回值是double型,且两个形参相除后所得的商作为结果返回.但是,编译器对这两个函数的调用过程的处理是不同的.对于内联函数的调用,编译器是将其函数体放在调用的地方,没有非内联函数调用时的栈内存的创建和释放开销.但是,所执行的计算是完全相同的.
使用内联函数时应注意以下几个问题:
(1) 在一个文件中定义的内联函数不能在另一个文件中使用.它们通常放在头文件中共享.
(2) 内联函数应该简洁,只有几个语句,如果语句较多,不适合于定义为内联函数.
(3) 内联函数体中,不能有循环语句、if语句或switch语句,否则,函数定义时即使有inline关键字,编译器也会把该函数作为非内联函数处理.
(4) 内联函数要在函数被调用之前声明.例如下面的代码将内联函数放在函数调用之后声明,不能起到预期的效果.
第五节 变量的作用域与存储期
在C++中,变量有效的范围(称为变量的作用域)和被存储的时间(称为变量的存储期或生存期)都是不同的.如果按变量的作用域来分类的话,变量可以分为局部变量和全局变量;如果按变量的存储期来分类的话,变量可以分为外部变量、静态变量、自动变量、寄存器变量.下面对各种类型的变量分别进行介绍.
5.1 作用域的概念
在C++中变量、函数和类(我们在后面要介绍类)的作用域是不同的,在函数和类外定义的变量,具有全局的作用域,这些变量称之为全局变量.同样,在类外定义的函数,我们称之为全局函数.
把一些C++语句,我们由一对花括号括起来,称之为语句块或块,在块中定义的变量作用域在块内,称之为局部作用域,在局部作用域中定义的变量称之为局部变量.例如,在函数和在一个复合语句中定义的变量都是局部变量.函数的形参的作用域在函数内,也是局部变量.局部变量只在局部作用域内有效,我们称之为可见,离开了其所在的局部作用域便无效,或称之为不可见.
在同一个作用域内,变量不能同名,否则,程序编译时,编译器会给出变量重复定义的错误.不同的作用域内,变量同名不会出现语法问题,但可能会使某些变量不能访问.例如:
int xyz; // xyz全局变量
void Foo (int xyz) // xyz 是 Foo函数中的局部变量
{
if (xyz > 0) {
double xyz; // xyz是if语句块中的局部变量
//...
}
}
上面的程序段中有三个作用域,全局作用域及两个局部作用域,且if语句块作用域嵌套在Foo函数作用域内.在if语句块内,访问不到Foo函数中的xyz变量,这说明内部作用域会覆盖外部作用域.需要注意的是:运用全局运算符::,我们可以在局部作用域中访问到全局变量.例如,在上面的if语句块中,我们增加一条语句:
double t = ::xyz;
就可以把全局变量xyz的值赋给局部变量t.
5.2 局部变量和全局变量
我们已经知道了什么叫全局变量,什么叫局部变量.一个应用程序可能包含多个源文件,而一个源文件可能包含多个函数.一般说来,全局变量的作用范围是定义点起至文件结束为止,局部变量的作用范围是从定义点起至该局部变量所在块的尾部为止.变量的存储期也限制在其作用域内.例如:全局变量的存储期与应用程序的生存期相同,局部变量是在进入作用域时创建,而在退出作用域时被销毁.
一、局部变量
由于局部变量的存储期是是在其所在的局部作用域内,其内存也是由系统自动分配的,所以,它也被称为自动变量.可以用一个关键字auto显式指定一个变量是自动变量.例如:
void Foo (void)
{
auto int xyz; // 等价于int xyz;
//...
}
一般说来,我们很少使用auto关键字.因为C++中,局部变量默认为自动变量.
我们已经知道,变量是存放在内存中.如果程序对一些频繁使用的变量(如循环变量),要求更高的访问效率,可以把这些变量保存在寄存器中,保存在寄存器中的变量,我们也称之为寄存器变量.
寄存器变量也是局部变量,定义寄存器变量需要用register关键字,例如:
for (register int i = 0; i < n; ++i)
sum += i;
需要注意的是:有时我们用register关键字定义了寄存器变量,编译器也可能不把该变量放在寄存器中,而放在内存中.因为机器的寄存器个数是有限,当我们申请寄器存放变量时,可能所有的寄存器都在被使用中.
二、全局变量
我们知道:全局变量是在函数和类外定义的,所以也称之为外部变量.全局变量一旦定义,从定义点开始至文件结束的所有函数都可以使用该变量.
一般情况下,我们把全局变量的定义放在引用它的所有函数之前.但是,如果在全局变量定义点之前的函数要引用该全局变量或另一个源文件中的函数要引用该全局变量,需要在函数内对要引用的全局变量加extern说明.例如:
#include <stdio.h>
int max(int x, int y);
main()
{
extern int a, b; //全局变量说明,而非定义
printf("%d\n", max(a, b));
return 0;
}
int a = 13, b = -8;
int max(int x, int y)
{
int z;
z = x > y ? x : y;
return z;
}
注意:用extern说明全局变量的时候,不能给初值.例如:
extern int size = 10; // 不再是说明!
因为这会使得size变成变量的定义,而不是说明,编译器会为它分配内存.如果别的地方定义了全局变量size,该程序在编译时,编译器会给出变量重复定义的错误.
使用全局变量,在我们编程中,有时会来一些方便.但是,它也有许多副作用:在程序的整个执行过程中始终占用内存空间,使程序的可读性、通用性和可移植性降低等,建议不在必要时,不使用全局变量
5.3 静态变量
如果在变量的定义前加上static关键字,就定义了静态变量.例如:
static int shortestRoute; // 静态全局变量
void Error (char *message)
{
static int count = 0; // 静态局部变量
...
}
在上面的程序段中,我们定义了一个静态全局变量shortestRoute和一个静态局部变量count.静态变量与全局变量具有相同的存储期,它们均与应用程序的生存期相同.但是,静态的全局变量只能在定义该全局变量的文件中访问,静态的局部变量只能在定义该局部变量的局部作用域中访问.
静态变量这种特性是有用的,如果我们需要某些全局变量只在本文件中访问,就可以把它们定义为静态的,这也可以减少了不同文件中定义的同名的全局变量而发生冲突的可能性,从而提高了程序的可移植性.
static关键字不仅可以放在变量的定义前,也可以放在函数的定义前.在全局函数的定义前加上了static关键字,就称为静态全局函数.例如:
static int FindNextRoute (void) // 仅在本文件中访问
{
//...
}
与静态全局变量的特性相似,静态全局函数也只能在定义该全局函数的文件中访问.
静态局部变量的特性也是很有用的.例如,假定我们在一个函数中定义了一个局部变量,需要该局部变量在函数退出时并不释放,下一次进入该函数时,局部变量原来的值还存在,我们就可以把该局部变量定义为静态的.
第七节 函数的重载
C++中,当有一组函数完成相似功能时,函数名允许重复使用,编译器根据参数表中参数的个数或类型(不能根据形参变量名)来判断调用哪一个函数,这就是函数的重载.
重载函数只要其参数表中参数个数或类型不同,就视为不同的函数.例如:
#include <stdio.h>
void show(int val)
{
printf("Integer: %d\n", val);
}
void show(double val)
{
printf("Double: %lf\n", val);
}
void show(char *val)
{
printf("String: %s\n", val);
}
int main()
{
show(12);
show(3.1415);
show("Hello World\n!");
return (0);
上面的程序段定义了三个有相同名字的函数show,但形参不同,分别为int、double和char *类型(这是指向字符的指针类型,有关指针类型,在第七章作详细介绍).
定义重载的函数时, 我们应该注意以下几个问题:
(1) 避免函数名字相同,但功能完全不同的情形.例如上面的重载函数show的功能就是相关的,它们均是向屏幕打印信息.
(2) 函数的形参变量名不同不能作为函数重载的依据.
(3) C++中不允许几个函数名相同、形参个数和类型也相同,仅仅是返回值不同的情形,否则,程序编译时会出现函数重复定义的错误.这是因为我们编程时常常忽略返回值,例如下面的代码段:
printf("Hello World!\n");
就没有函数printf函数返回值的信息(顺便提及:printf函数返回值是一个整数,表示打印出的字符的个数.这个返回值,我们实际上从来不用.).如果两个重载的printf函数仅仅是返回值不同,编译器便不能区分它们.
(4) 函数重载有时可能会产生意想不到的结果.例如:
show(0);
在上面给定的三个函数中,0可以被解释为一个空指针NULL,即(char *)0,也可以解释为一个整数0.C++选择调用有整型参数的show函数,这也可能不是你所期望的结果.
(5) 调用重载的函数时,如果实参类型与形参类型不匹配,编译器会自动进行类型转换.如果转换后仍然不能匹配到重载的函数,则会产生一个编译错误.例如:
#include <stdio.h>
void add(int val)
{
printf("Int: %d\n", val)
}
void add(long val)
{
printf("Int: %ld\n", val)
}
void main()
{
add(3.2); //调用发生歧异
}
编译器会由于无法决定将3.2转换成int还是long而产生调用发生歧异错误.
所谓函数重载是指同一个函数名可以对应着多个函数的实现.例如,可以给函数名add()定义多个函数实现,该函数的功能是求和,即求两个操作数的和.其中,一个函数实现是求两个int型数之和,另一个实现是求两个浮点型数之和.每种实现对应着一个函数体,这些函数的名字相同,但是函数参数的个数或类型不同,这就是函数重载的概念.
当调用多个同名的重载函数时,要求能够唯一地确定应执行哪一个函数,这是通过函数参数的个数和类型来区分的.所以,重载的函数要求参数个数或者参数类型上不同,否则会出现编译错误.下面是一个函数重载的例子,"int add(int i,int j)"函数实现整数的加法,"double add(double i,double j)"函数实现双精度浮点数的加法,它们的名字相同,仅仅是参数的类型不同,min函数的功能是在若干个整数中,求最小的整数,"int min(int a,int b)"、"int min(int a,int b,int c)"和"int min(int a,int b,int c,int d)"参数类型相同,仅仅是参数个数不同.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -