目录

什么是函数

库函数和自定义函数

返回值

函数的定义

C++语言有参函数的定义

函数不能嵌套定义

函数的返回值

实参与形参

函数的调用

函数的嵌套调用

函数的申明和原型

局部变量和全局变量​​​​​​​

局部变量和全局变量的综合示例

变量的作用域

在函数内部定义的变量(局部变量)

在所有函数外部定义的变量(全局变量)

关于变量的命名

块级变量

在 for循环里面定义变量

单独的代码块

再谈作用域


什么是函数

从表面上看,函数在使用时必须带上括号,有必要的话还要传递参数,函数的执行结果也可以赋值给其它变量。例如,strlen() 是一个用来计算字符串数组长度的函数,它的用法如下:

#include <cstdio>
#include <cstring>int main(){char str[30]="hello";int result=strlen(str);printf("%d",result);return 0;
}

str传递给函数strlen()的参数,strlen()的处理结果赋值给了result。

我们不妨设想一下,如果没有strlen()函数,如何计算字符数组的长度?看如下代码:

#include <cstdio>
#include <cstring>int main(){char str[30]="hello";int result=0;bool end=false;while(!end){if(str[result]!='\0'){result++;}else{end=true;}}printf("%d",result);return 0;
}

计算字符串长度是常用的功能,一个程序可能会用到很多次,如果每次都写这样一段重复的代码,不但费时费力、容易出错,而且交给别人时也很麻烦,所以C语言提供了一个功能,允许我们将常用的代码以固定的格式封装(包装)成一个独立的模块,只要知道这个模块的名字就可以重复使用它,这个模块就叫做函数(Function)

函数的本质是一段可以重复使用的代码,这段代码被提前编写好了,放到了指定的文件中,使用时直接调取即可。

下面我们就来演示一下如何封装 strlen() 这个函数。

#include <cstdio>int strlen_alias(char str[]){int result=0;bool end=false;while(!end){if(str[result]!='\0'){result++;}else{end=true;}}return result;
}
int main(){char b[30]="hello";int a=strlen_alias(b);printf("%d",a);return 0;
}

为了避免与原有的 strlen产生命名冲突,我将新函数命名为 strlen_alias。

这是我们自己编写的函数,放在了当前源文件中(函数封装和函数使用在同一个源文件中),所以不需要引入头文件;而C++语言自带的 strlen() 放在了其它的源文件中(函数封装和函数使用不在同一个源文件中),并在 cstring 文件中告诉我们如何使用,所以我们必须引入 cstring文件。

我们自己编写的 strlen_alias() 和原有的 strlen() 在功能和格式上都是一样的,只是存放的位置不同,所以一个需要引入头文件,一个不需要引入。

库函数和自定义函数

C++语言在发布时已经为我们封装好了很多函数,它们被分门别类地放到了不同的头文件中(暂时先这样认为),使用函数时引入对应的头文件即可。这些函数都是专家编写的,执行效率极高,并且考虑到了各种边界情况,各位读者请放心使用。

C++语言自带的函数称为库函数(Library Function)。库(Library)是编程中的一个基本概念,可以简单地认为它是一系列函数的集合,在磁盘上往往是一个文件夹。C++语言自带的库称为标准库(Standard Library),其他公司或个人开发的库称为第三方库(Third-Party Library)。

除了库函数,我们还可以编写自己的函数,拓展程序的功能。自己编写的函数称为自定义函数。自定义函数和库函数在编写和使用方式上完全相同,只是由不同的机构来编写。

参数

函数的一个明显特征就是使用时带括号( ),有必要的话,括号中还要包含数据或变量,称为参数(Parameter)。参数是函数需要处理的数据,例如:

  • strlen(str1)用来计算字符串的长度,str1就是参数。
  • puts("hello world")用来输出字符串,"hello world"就是参数。

返回值

既然函数可以处理数据,那就有必要将处理结果告诉我们,所以很多函数都有返回值(Return Value)。所谓返回值,就是函数的执行结果。例如:

char str1[] = "C Language";
int len = strlen(str1);

strlen() 的处理结果是字符串 str1 的长度,是一个整数,我们通过 len 变量来接收。
函数返回值有固定的数据类型(int、char、float等),用来接收返回值的变量类型要一致。

函数的定义

函数是一段可以重复使用的代码,用来独立地完成某个功能,它可以接收用户传递的数据,也可以不接收。接收用户数据的函数在定义时要指明参数,不接收用户数据的不需要指明,根据这一点可以将函数分为有参函数和无参函数。

将代码段封装成函数的过程叫做函数定义

C语言无参函数的定义

如果函数不接收用户传递的数据,那么定义时可以不带参数。如下所示:

dataType  functionName(){//body
}
  • dataType 是返回值类型,它可以是C语言中的任意数据类型,例如 int、float、char 等。
  • functionName 是函数名,命名规则和变量的命名规则相同。函数名后面的括号( )不能少。
  • body 是函数体,它是函数需要执行的代码,是函数的主体部分。即使只有一个语句,函数体也要由{ }包围。
  • 如果有返回值,在函数体中使用 return 语句返回。return 出来的数据的类型要和 dataType 一样。

例如,定义一个函数,计算从 1 加到 100 的结果:

int sum(){int i, sum=0;for(i=1; i<=100; i++){sum+=i;}return sum;
}

累加结果保存在变量sum中,最后通过return语句返回。sum 是 int 型,返回值也是 int 类型,它们一一对应。return是C++语言中的一个关键字,只能用在函数中,用来返回处理结果。

将上面的代码补充完整:

#include <cstdio>
int sum(){int i, sum=0;for(i=1; i<=100; i++){sum+=i;}return sum;
}
int main(){int a = sum();printf("The sum is %d\n", a);return 0;
}

运行结果:
The sum is 5050

函数不能嵌套定义,main 也是一个函数定义,所以要将 sum 放在 main 外面。函数必须先定义后使用,所以 sum 要放在 main 前面。

注意:main 是函数定义,不是函数调用。当可执行文件加载到内存后,系统从 main 函数开始执行,也就是说,系统会调用我们定义的 main 函数。

无返回值函数

有的函数不需要返回值,那么可以用 void 表示,例如:

void hello(){printf ("Hello,world \n");//没有返回值就不需要 return 语句
}

void是C语言中的一个关键字,表示“空类型”或“无类型”,绝大部分情况下也就意味着没有 return 语句

C++语言有参函数的定义

如果函数需要接收用户传递的数据,那么定义时就要带上参数。如下所示:

dataType  functionName( dataType1 param1, dataType2 param2 ... ){//body
}

dataType1 param1, dataType2 param2 ...是参数列表。函数可以只有一个参数,也可以有多个,多个参数之间由“,”(逗号)分隔。参数本质上也是变量,定义时要指明类型和名称。与无参函数的定义相比,有参函数的定义仅仅是多了一个参数列表。

数据通过参数传递到函数内部进行处理,处理完成以后再通过返回值告知函数外部。

更改上面的例子,计算从 m 加到 n 的结果:

int sum(int m, int n){int i, sum=0;for(i=m; i<=n; i++){sum+=i;}return sum;
}

参数列表中给出的参数可以在函数体中使用,使用方式和普通变量一样。

调用 sum() 函数时,需要给它传递两份数据,一份传递给 m,一份传递给 n。你可以直接传递整数,例如:

int result = sum(1, 100);  //1传递给m,100传递给n

也可以传递变量:

int begin = 4;
int end = 86;
int result = sum(begin, end);  //begin传递给m,end传递给n

也可以整数和变量一起传递:

int num = 33;
int result = sum(num, 80);  //num传递给m,80传递给n

函数定义时给出的参数称为形式参数,简称形参;函数调用时给出的参数(也就是传递的数据)称为实际参数,简称实参。函数调用时,将实参的值传递给形参,相当于一次赋值操作。

原则上讲,实参的类型和数目要与形参保持一致。如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型,例如将 int 类型的实参传递给 float 类型的形参就会发生自动类型转换。

将上面的代码补充完整:

#include <cstdio>int sum(int m, int n){int i, sum=0;for(i=m; i<=n; i++){sum+=i;}return sum;
}
int main(){int begin = 5, end = 86;int result = sum(begin, end);printf("The sum from %d to %d is %d\n", begin, end, result);return 0;
}

运行结果:
The sum from 5 to 86 is 3731

定义 sum() 时,参数 m、n 的值都是未知的;调用 sum() 时,将 begin、end 的值分别传递给 m、n,这和给变量赋值的过程是一样的,它等价于:

m = begin;
n = end;

函数不能嵌套定义

强调一点,C语言不允许函数嵌套定义;也就是说,不能在一个函数中定义另外一个函数,必须在所有函数之外定义另外一个函数。main() 也是一个函数定义,也不能在 main() 函数内部定义新函数。

下面的例子是错误的

#include <cstdio>void func1(){printf("http://c.biancheng.net");void func2(){printf("C语言小白变怪兽");}
}
int main(){func1();return 0;
}

func1()、func2()、main() 三个函数是平行的,谁也不能位于谁的内部,要想达到「调用 func1() 时也调用 func2()」的目的,必须将 func2() 定义在 func1() 外面,并在 func1() 内部调用 func2()。

函数的返回值

函数的返回值是指函数被调用之后,执行函数体中的代码所得到的结果,这个结果通过 return 语句返回。

return 语句的一般形式为

return 表达式;

例如:

return max;
return a+b;
return (100+200);

返回值的说明:

1) 没有返回值的函数为空类型,用void表示。例如:

void func(){printf("hello\n");
}

一旦函数的返回值类型被定义为 void,就不能再接收它的值了。例如,下面的语句是错误的:

int a = func();

为了使程序有良好的可读性并减少出错, 凡不要求返回值的函数都应定义为 void 类型。

2) return 语句可以有多个,可以出现在函数体的任意位置,但是每次调用函数只能有一个 return 语句被执行,所以只有一个返回值

int max(int a, int b){if(a > b){return a;}else{return b;}
}

如果a>b成立,就执行return a,return b不会执行;如果不成立,就执行return b,return a不会执行。

3) 函数一旦遇到 return 语句就立即返回,后面的所有语句都不会被执行到了。从这个角度看,return 语句还有强制结束函数执行的作用。例如:

//返回两个整数中较大的一个
int max(int a, int b){return (a>b) ? a : b;printf("Function is performed\n");
}

printf就是多余的,永远没有执行的机会。

下面我们定义了一个判断素数的函数,这个例子更加实用:

#include <cstdio>int prime(int n){int is_prime = 1, i;//n一旦小于0就不符合条件,就没必要执行后面的代码了,所以提前结束函数if(n < 0){ return -1; }for(i=2; i<n; i++){if(n % i == 0){is_prime = 0;break;}}return is_prime;
}
int main(){int num, is_prime;scanf("%d", &num);is_prime = prime(num);if(is_prime < 0){printf("%d is a illegal number.\n", num);}else if(is_prime > 0){printf("%d is a prime number.\n", num);}else{printf("%d is not a prime number.\n", num);}return 0;
}

prime() 是一个用来求素数的函数。素数是自然数,它的值大于等于零,一旦传递给 prime() 的值小于零就没有意义了,就无法判断是否是素数了,所以一旦检测到参数 n 的值小于 0,就使用 return 语句提前结束函数。

return 语句是提前结束函数的唯一办法。return 后面可以跟一份数据,表示将这份数据返回到函数外面;return 后面也可以不跟任何数据,表示什么也不返回,仅仅用来结束函数。

更改上面的代码,使得 return 后面不跟任何数据:

#include <cstdio>void prime(int n){int is_prime = 1, i;if(n < 0){printf("%d is a illegal number.\n", n);return;  //return后面不带任何数据}for(i=2; i<n; i++){if(n % i == 0){is_prime = 0;break;}}if(is_prime > 0){printf("%d is a prime number.\n", n);}else{printf("%d is not a prime number.\n", n);}
}
int main(){int num;scanf("%d", &num);prime(num);return 0;
}

prime() 的返回值是 void,return 后面不能带任何数据,直接写分号即可。

实参与形参

如果把函数比喻成一台机器,那么参数就是原材料,返回值就是最终产品;从一定程度上讲,函数的作用就是根据不同的参数产生不同的返回值。

C语言函数的参数会出现在两个地方,分别是函数定义处和函数调用处,这两个地方的参数是有区别的。

形参(形式参数)

在函数定义中出现的参数可以看做是一个占位符,它没有数据,只能等到函数被调用时接收传递进来的数据,所以称为形式参数,简称形参

实参(实际参数)

函数被调用时给出的参数包含了实实在在的数据,会被函数内部的代码使用,所以称为实际参数,简称实参

形参和实参的功能是传递数据,发生函数调用时,实参的值会传递给形参。

形参和实参的区别和联系

1) 形参变量只有在函数被调用时才会分配内存,调用结束后,立刻释放内存,所以形参变量只有在函数内部有效,不能在函数外部使用。

2) 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的数据,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参,所以应该提前用赋值、输入等办法使实参获得确定值。

3) 实参和形参在数量上、类型上、顺序上必须严格一致,否则会发生“类型不匹配”的错误。当然,如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型。

4) 函数调用中发生的数据传递是单向的,只能把实参的值传递给形参,而不能把形参的值反向地传递给实参;换句话说,一旦完成数据的传递,实参和形参就再也没有瓜葛了,所以,在函数调用过程中,形参的值发生改变并不会影响实参。

请看下面的例子

#include <cstdio>//计算从m加到n的值
int sum(int m, int n) {int i;for (i = m+1; i <= n; ++i) {m += i;}return m;
}
int main() {int a, b, total;printf("Input two numbers: ");scanf("%d %d", &a, &b);total = sum(a, b);printf("a=%d, b=%d\n", a, b);printf("total=%d\n", total);return 0;
}

运行结果:
Input two numbers: 1 100↙
a=1, b=100
total=5050

在这段代码中,函数定义处的 m、n 是形参,函数调用处的 a、b 是实参。通过 scanf() 可以读取用户输入的数据,并赋值给 a、b,在调用 sum() 函数时,这份数据会传递给形参 m、n。

从运行情况看,输入 a 值为 1,即实参 a 的值为 1,把这个值传递给函数 sum() 后,形参 m 的初始值也为 1,在函数执行过程中,形参 m 的值变为 5050。函数运行结束后,输出实参 a 的值仍为 1,可见实参的值不会随形参的变化而变化。

以上调用 sum() 时是将变量作为函数实参,除此以外,你也可以将常量、表达式、函数返回值作为实参,如下所示:

total = sum(10, 98);  //将常量作为实参
total = sum(a+10, b-3);  //将表达式作为实参
total = sum( pow(2,2), abs(-100) );  //将函数返回值作为实参

5) 形参和实参虽然可以同名,但它们之间是相互独立的,互不影响,因为实参在函数外部有效,而形参在函数内部有效。

#include <cstdio>//计算从m加到n的值
int sum(int m, int n) {int i;for (i = m + 1; i <= n; ++i) {m += i;}return m;
}
int main() {int m, n, total;printf("Input two numbers: ");scanf("%d %d", &m, &n);total = sum(m, n);printf("m=%d, n=%d\n", m, n);printf("total=%d\n", total);return 0;
}

运行结果:
Input two numbers: 1 100
m=1, n=100
total=5050

调用 sum() 函数后,函数内部的形参 m 的值已经发生了变化,而函数外部的实参 m 的值依然保持不变,可见它们是相互独立的两个变量,除了传递参数的一瞬间,其它时候是没有瓜葛的。

函数的调用

所谓函数调用(Function Call),就是使用已经定义好的函数。函数调用的一般形式为:

functionName(param1, param2, param3 ...);

functionName 是函数名称,param1, param2, param3 ...是实参列表。实参可以是常数、变量、表达式等,多个实参用“,”(逗号)分隔。

在C++语言中,函数调用的方式有多种,例如:

//函数作为表达式中的一项出现在表达式中
z = max(x, y);
m = n + max(x, y);
//函数作为一个单独的语句
printf("%d", a);
scanf("%d", &b);
//函数作为调用另一个函数时的实参
printf( "%d", max(x, y) );
total( max(x, y), min(m, n) );

函数的嵌套调用

函数不能嵌套定义,但可以嵌套调用,也就是在一个函数的定义或调用过程中允许出现对另外一个函数的调用。

【示例】计算sum = 1! + 2! + 3! + ... + (n-1)! + n!

#include <cstdio>//求阶乘
long factorial(int n){int i;long result=1;for(i=1; i<=n; i++){result *= i;}return result;
}
// 求累加的和
long sum(long n){int i;long result = 0;for(i=1; i<=n; i++){//在定义过程中出现嵌套调用result += factorial(i);}return result;
}
int main(){printf("1!+2!+...+9!+10! = %ld\n", sum(10));  //嵌套调用return 0;
}

运行结果:
1!+2!+...+9!+10! = 4037913

sum() 的定义中出现了对 factorial() 的调用,printf() 的调用过程中出现了对 sum() 的调用,而 printf() 又被 main() 调用,它们整体调用关系为:

main() --> printf() --> sum() --> factorial()

如果一个函数 A() 在定义或调用过程中出现了对另外一个函数 B() 的调用,那么我们就称 A() 为主调函数或主函数,称 B() 为被调函数。

当主调函数遇到被调函数时,主调函数会暂停,CPU 转而执行被调函数的代码;被调函数执行完毕后再返回主调函数,主调函数根据刚才的状态继续往下执行。

一个C++语言程序的执行过程可以认为是多个函数之间的相互调用过程,它们形成了一个或简单或复杂的调用链条。这个链条的起点是 main(),终点也是 main()。当 main() 调用完了所有的函数,它会返回一个值(例如return 0;)来结束自己的生命,从而结束整个程序。

函数是一个可以重复使用的代码块,CPU 会一条一条地挨着执行其中的代码,当遇到函数调用时,CPU 首先要记录下当前代码块中下一条代码的地址(假设地址为 0X1000),然后跳转到另外一个代码块,执行完毕后再回来继续执行 0X1000 处的代码。整个过程相当于 CPU 开了一个小差,暂时放下手中的工作去做点别的事情,做完了再继续刚才的工作。

从上面的分析可以推断出,在所有函数之外进行加减乘除运算、使用 if...else 语句、调用一个函数等都是没有意义的,这些代码位于整个函数调用链条之外,永远都不会被执行到。C语言也禁止出现这种情况,会报语法错误,请看下面的代码:

#include <cstdio>int a = 10, b = 20, c;
//错误:不能出现加减乘除运算
c = a + b;
//错误:不能出现对其他函数的调用
printf("hello");
int main(){return 0;
}

函数的申明和原型

C++语言代码由上到下依次执行,原则上函数定义要出现在函数调用之前,否则就会报错。但在实际开发中,经常会在函数定义之前使用它们,这个时候就需要提前声明。C语言代码由上到下依次执行,原则上函数定义要出现在函数调用之前,否则就会报错。但在实际开发中,经常会在函数定义之前使用它们,这个时候就需要提前声明。

所谓声明(Declaration),就是告诉编译器我要使用这个函数,你现在没有找到它的定义不要紧,请不要报错,稍后我会把定义补上。

函数声明的格式非常简单,相当于去掉函数定义中的函数体,并在最后加上“;”(分号),如下所示:

dataType  functionName( dataType1 param1, dataType2 param2 ... );

函数声明给出了函数名、返回值类型、参数列表(重点是参数类型)等与该函数有关的信息,称为函数原型(Function Prototype)。函数原型的作用是告诉编译器与该函数有关的信息,让编译器知道函数的存在,以及存在的形式,即使函数暂时没有定义,编译器也知道如何使用它。

有了函数声明,函数定义就可以出现在任何地方了。

【实例1】定义一个函数 sum(),计算从 m 加到 n 的和,并将 sum() 的定义放到 main() 后面。

#include <cstdio>//函数声明
int sum(int m, int n);  //也可以写作int sum(int, int);int main(){int begin = 5, end = 86;int result = sum(begin, end);printf("The sum from %d to %d is %d\n", begin, end, result);return 0;
}//函数定义
int sum(int m, int n){int i, sum=0;for(i=m; i<=n; i++){sum+=i;}return sum;
}

我们在 main() 函数中调用了 sum() 函数,编译器在它前面虽然没有发现函数定义,但是发现了函数声明,这样编译器就知道函数怎么使用了,至于函数体到底是什么,暂时可以不用操心,后续再把函数体补上就行。

局部变量和全局变量

在形参和实参的区别中提到,形参变量要等到函数被调用时才分配内存,调用结束后立即释放内存。这说明形参变量的作用域非常有限,只能在函数内部使用,离开该函数就无效了。所谓作用域(Scope),就是变量的有效范围。
       不仅对于形参变量,C++语言中所有的变量都有自己的作用域。决定变量作用域的是变量的定义位置。

局部变量

定义在函数内部的变量称为局部变量(Local Variable),它的作用域仅限于函数内部, 离开该函数后就是无效的,再使用就会报错。例如:

int f1(int a){int b,c;  //a,b,c仅在函数f1()内有效return a+b+c;
}
int main(){int m,n;  //m,n仅在函数main()内有效return 0;
}

几点说明:
1) 在 main 函数中定义的变量也是局部变量,只能在 main 函数中使用;同时,main 函数中也不能使用其它函数中定义的变量。main 函数也是一个函数,与其它函数地位平等。

2) 形参变量、在函数体内定义的变量都是局部变量。实参给形参传值的过程也就是给局部变量赋值的过程。

3) 可以在不同的函数中使用相同的变量名,它们表示不同的数据,分配不同的内存,互不干扰,也不会发生混淆。

4) 在语句块中也可定义变量,它的作用域只限于当前语句块。

全局变量

在所有函数外部定义的变量称为全局变量(Global Variable),它的作用域默认是整个程序,也就是所有的源文件:

int a, b;  //全局变量
void func1(){//TODO:
}
float x,y;  //全局变量
int func2(){//TODO:
}
int main(){//TODO:return 0;
}

a、b、x、y 都是在函数外部定义的全局变量。C++语言代码是从前往后依次执行的,由于 x、y 定义在函数 func1() 之后,所以在 func1() 内无效;而 a、b 定义在源程序的开头,所以在 func1()、func2() 和 main() 内都有效。

局部变量和全局变量的综合示例

【示例1】输出变量的值:

#include <cstdio>int n = 10;  //全局变量
void func1(){int n = 20;  //局部变量printf("func1 n: %d\n", n);
}
void func2(int n){printf("func2 n: %d\n", n);
}
void func3(){printf("func3 n: %d\n", n);
}
int main(){int n = 30;  //局部变量func1();func2(n);func3();//代码块由{}包围{int n = 40;  //局部变量printf("block n: %d\n", n);}printf("main n: %d\n", n);return 0;
}

运行结果:
func1 n: 20
func2 n: 30
func3 n: 10
block n: 40
main n: 30

代码中虽然定义了多个同名变量 n,但它们的作用域不同,在内存中的位置(地址)也不同,所以是相互独立的变量,互不影响,不会产生重复定义(Redefinition)错误。

1) 对于 func1(),输出结果为 20,显然使用的是函数内部的 n,而不是外部的 n;func2() 也是相同的情况。

当全局变量和局部变量同名时,在局部范围内全局变量被“屏蔽”,不再起作用。或者说,变量的使用遵循就近原则,如果在当前作用域中存在同名变量,就不会向更大的作用域中去寻找变量。

2) func3() 输出 10,使用的是全局变量,因为在 func3() 函数中不存在局部变量 n,所以编译器只能到函数外部,也就是全局作用域中去寻找变量 n。

3) 由{ }包围的代码块也拥有独立的作用域,printf() 使用它自己内部的变量 n,输出 40。

4) C语言规定,只能从小的作用域向大的作用域中去寻找变量,而不能反过来,使用更小的作用域中的变量。对于 main() 函数,即使代码块中的 n 离输出语句更近,但它仍然会使用 main() 函数开头定义的 n,所以输出结果是 30。

【示例2】根据长方体的长宽高求它的体积以及三个面的面积。

#include <cstdio>int s1, s2, s3;  //面积
int vs(int a, int b, int c){int v;  //体积v = a * b * c;s1 = a * b;s2 = b * c;s3 = a * c;return v;
}
int main(){int v, length, width, height;printf("Input length, width and height: ");scanf("%d %d %d", &length, &width, &height);v = vs(length, width, height);printf("v=%d, s1=%d, s2=%d, s3=%d\n", v, s1, s2, s3);return 0;
}

运行结果:
Input length, width and height: 10 20 30↙
v=6000, s1=200, s2=600, s3=300

根据题意,我们希望借助一个函数得到三个值:体积 v 以及三个面的面积 s1、s2、s3。遗憾的是,C语言中的函数只能有一个返回值,我们只能将其中的一份数据,也就是体积 v 放到返回值中,而将面积 s1、s2、s3 设置为全局变量。全局变量的作用域是整个程序,在函数 vs() 中修改 s1、s2、s3 的值,能够影响到包括 main() 在内的其它函数。

变量的作用域

所谓作用域(Scope),就是变量的有效范围,就是变量可以在哪个范围以内使用。有些变量可以在所有代码文件中使用,有些变量只能在当前的文件中使用,有些变量只能在函数内部使用,有些变量只能在 for循环内部使用。

变量的作用域由变量的定义位置决定,在不同位置定义的变量,它的作用域是不一样的。本节我们只讲解两种变量,一种是只能在函数内部使用的变量,另一种是可以在所有代码文件中使用的变量。

在函数内部定义的变量(局部变量)

在函数内部定义的变量,它的作用域也仅限于函数内部,出了函数就不能使用了,我们将这样的变量称为局部变量(Local Variable)。函数的形参也是局部变量,也只能在函数内部使用。请看下面的例子:

#include <cstdio>int sum(int m, int n){int i, sum=0;//m、n、i、sum 都是局部变量,只能在 sum() 内部使用for(i=m; i<=n; i++){sum+=i;}return sum;
}
int main(){int begin = 5, end = 86;int result = sum(begin, end);//begin、end、result 也都是局部变量,只能在 main() 内部使用printf("The sum from %d to %d is %d\n", begin, end, result);return 0;
}

m、n、i、sum 是局部变量,只能在 sum() 内部使用;begin、end、result 也是局部变量,只能在 main() 内部使用。

对局部变量的两点说明:

1、main() 也是一个函数,在 main() 内部定义的变量也是局部变量,只能在 main() 函数内部使用。

2、形参也是局部变量,将实参传递给形参的过程,就是用实参给局部变量赋值的过程,它和a=b; sum=m+n;这样的赋值没有什么区别。

在所有函数外部定义的变量(全局变量)

允许在所有函数的外部定义变量,这样的变量称为全局变量(Global Variable)。

全局变量的默认作用域是整个程序,也就是所有的代码文件。如果给全局变量加上 static 关键字,它的作用域就变成了当前文件,在其它文件中就无效了。我们目前编写的代码都是在一个源文件中,所以暂时不用考虑 static 关键字。

【实例】定义一个函数,根据长方体的长宽高求它的体积以及三个面的面积。

#include <cstdio>//定义三个全局变量,分别表示三个面的面积
int s1 = 0, s2 = 0, s3 = 0;
int vs(int length, int width, int height){int v;  //体积v = length * width * height;s1 = length * width;s2 = width * height;s3 = length * height;return v;
}
int main(){int v = 0;v = vs(15, 20, 30);printf("v=%d, s1=%d, s2=%d, s3=%d\n", v, s1, s2, s3);v = vs(5, 17, 8);printf("v=%d, s1=%d, s2=%d, s3=%d\n", v, s1, s2, s3);return 0;
}

运行结果:
v=9000, s1=300, s2=600, s3=450
v=680, s1=85, s2=136, s3=40

根据题意,我们希望借助一个函数得到四份数据:体积 v 以及三个面的面积 s1、s2、s3。遗憾的是,C++语言中的函数只能有一个返回值,我们只能将其中的一份数据(也就是体积 v)放到返回值中,其它三份数据(也就是面积 s1、s2、s3)只能保存到全局变量中。

C++语言代码从前往后依次执行,变量在使用之前必须定义或者声明,全局变量 s1、s2、s3 定义在程序开头,所以在 vs() 和 main() 中都有效。

在 vs() 中将求得的面积放到 s1、s2、s3 中,在 main() 中能够顺利取得它们的值,这说明:在一个函数内部修改全局变量的值会影响其它函数,全局变量的值在函数内部被修改后并不会自动恢复,它会一直保留该值,直到下次被修改。

全局变量也是变量,变量只能保存一份数据,一旦数据被修改了,原来的数据就被冲刷掉了,再也无法恢复了,所以不管是全局变量还是局部变量,一旦它的值被修改,这种影响都会一直持续下去,直到再次被修改。

关于变量的命名

每一段可运行的C++语言代码都包含了多个作用域,即使最简单的C++语言代码也是如此。

#include <cstdio>int main(){return 0;
}

这就是最简单的、可运行的C++语言代码,它包含了两个作用域,一个是 main() 函数内部的局部作用域,一个是 main() 函数外部的全局作用域。

C++语言规定,在同一个作用域中不能出现两个名字相同的变量,否则会产生命名冲突;但是在不同的作用域中,允许出现名字相同的变量,它们的作用范围不同,彼此之间不会产生冲突。这句话有两层含义:

1、不同函数内部可以出现同名的变量,不同函数是不同的局部作用域;

2、函数内部和外部可以出现同名的变量,函数内部是局部作用域,函数外部是全局作用域。

a、不同函数内部的同名变量是两个完全独立的变量,它们之间没有任何关联,也不会相互影响。请看下面的代码:

#include <cstdio>void func_a(){int n = 100;printf("func_a: n = %d\n", n);n = 86;printf("func_a: n = %d\n", n);
}
void func_b(){int n = 29;printf("func_b: n = %d\n", n);func_a(); //调用func_a()printf("func_b: n = %d\n", n);
}
int main(){func_b();return 0;
}

运行结果:
func_b: n = 29
func_a: n = 100
func_a: n = 86
func_b: n = 29

func_a() 和 func_b() 内部都定义了一个变量 n,在 func_b() 中,n 的初始值是 29,调用 func_a() 后,n 值还是 29,这说明 func_b() 内部的 n 并没有影响 func_a() 内部的 n。这两个 n 是完全不同的变量,彼此之间根本“不认识”,只是起了个相同的名字而已,这就好像明星撞衫,北京和云南都有叫李红的,赶巧了而已。

b、 函数内部的局部变量和函数外部的全局变量同名时,在当前函数这个局部作用域中,全局变量会被“屏蔽”,不再起作用。也就是说,在函数内部使用的是局部变量,而不是全局变量。

变量的使用遵循就近原则,如果在当前的局部作用域中找到了同名变量,就不会再去更大的全局作用域中查找。另外,只能从小的作用域向大的作用域中去寻找变量,而不能反过来,使用更小的作用域中的变量。

下面我们通过一个具体的例子来说明:

#include <cstdio>int n = 10;  //全局变量
void func1(){int n = 20;  //局部变量printf("func1 n: %d\n", n);
}
void func2(int n){printf("func2 n: %d\n", n);
}
void func3(){printf("func3 n: %d\n", n);
}
int main(){int n = 30;  //局部变量func1();func2(n);func3();printf("main n: %d\n", n);return 0;
}

运行结果:
func1 n: 20
func2 n: 30
func3 n: 10
main n: 30

代码中虽然定义了多个同名变量 n,但它们的作用域不同,所有不会产生命名冲突。

下面是对输出结果的分析:

1、对于 func1(),输出结果为 20,显然使用的是 func1() 内部的 n,而不是外部的 n。

2、调用 func2() 时,会把 main() 中的实参 n 传递给 func2() 中的形参 n,此时形参 n 的值变为 30。形参 n 也是局部变量,所以就使用它了。

3、func3() 输出 10,使用的是全局变量,因为在 func3() 中不存在局部变量 n,所以编译器只能到函数外部,也就是全局作用域中去寻找变量 n。

4、main() 中 printf() 语句输出 30,说明使用的是 main() 中的 n,而不是外部的 n。

块级变量

所谓代码块,就是由{ }包围起来的代码。代码块在C++语言中随处可见,例如函数体、选择结构、循环结构等。不包含代码块的C++语言程序根本不能运行,即使最简单的C++语言程序(上节已经进行了展示)也要包含代码块。

C++语言允许在代码块内部定义变量,这样的变量具有块级作用域;换句话说,在代码块内部定义的变量只能在代码块内部使用,出了代码块就无效了。

我们已经讲解了函数,在函数内部定义的变量叫做局部变量,这节我们接着讲解选择结构和循环结构。

实例1】定义一个函数 gcd(),求两个整数的最大公约数。

#include <cstdio>//函数声明
int gcd(int a, int b);  //也可以写作 int gcd(int, int);
int main(){printf("The greatest common divisor is %d\n", gcd(100, 60));return 0;
}
//函数定义
int gcd(int a, int b){//若a<b,那么交换两变量的值if(a < b){int temp1 = a;  //块级变量a = b;b = temp1;}//求最大公约数while(b!=0){int temp2 = b;  //块级变量b = a % b;a = temp2;}return a;
}

运行结果:
The greatest common divisor is 20

同学们暂时不用理解 gcd() 函数的思路,只需要关注 temp1 和 temp2 这两个变量,它们都是在代码块内部定义的块级变量,temp1 的作用域是 if 内部,temp2 的作用域是 while 内部。

在 for循环里面定义变量

允许在 for 循环条件里面定义新变量,这样的变量也是块级变量,它的作用域仅限于 for 循环内部。例如,计算从 m 累加到 n 的和:

#include <cstdio>int sum(int m, int n);
int main(){printf("The sum from 1 to 100 is %d\n", sum(1, 100));return 0;
}
int sum(int m, int n){int sum = 0;for(int i=m; i<=n; i++){  //i是块级变量sum += i;}return sum;
}

变量 i 定义在循环条件里面,所以是一个块级变量,它的作用域就是当前 for 循环,出了 for 循环就无效了。

如果一个变量只在 for 循环内部使用,就可以将它定义在循环条件里面,这样做可以避免在函数开头定义过多的变量,使得代码结构更加清晰,所以我鼓励大家这样做,当然,前提是你的编译器支持。

【实例2】定义一个函数 strchar(),查看给定的字符是否位于某个字符串中。

#include <cstdio>
#include <cstring>int strchar(char str[], char c);
int main(){char url[] = "hello world";char letter = 'l';if(strchar(url, letter) >= 0){printf("The letter is in the string.\n");}else{printf("The letter is not in the string.\n");}return 0;
}
int strchar(char str[], char c){for(int i=0,len=strlen(str); i<len; i++){  //i和len都是块级变量if(str[i] == c){return i;}}return -1;
}

循环条件里面可以定义一个或者多个变量,这段代码我们就定义了两个变量,分别是 i 和 len,它们都是块级变量,作用域都是当前 for 循环。

单独的代码块

C++语言还允许出现单独的代码块,它也是一个作用域。请看下面的代码:

#include <cstdio>
int main(){int n = 22;  //编号①//由{ }包围的代码块{int n = 40;  //编号②printf("block n: %d\n", n);}printf("main n: %d\n", n);return 0;
}

运行结果:
block n: 40
main n: 22

这里有两个 n,它们位于不同的作用域,不会产生命名冲突。{ } 的作用域比 main() 更小,{ } 内部的 printf() 使用的是编号为②的 n,main() 内部的 printf() 使用的是编号为①的 n。

再谈作用域

每个C语言程序都包含了多个作用域,不同的作用域中可以出现同名的变量,C语言会按照从小到大的顺序、一层一层地去父级作用域中查找变量,如果在最顶层的全局作用域中还未找到这个变量,那么就会报错。

下面我们通过具体的代码来演示

#include <cstdio>int m = 13;
int n = 10;
void func1(){int n = 20;{int n = 822;printf("block1 n: %d\n", n);}printf("func1 n: %d\n", n);
}
void func2(int n){for(int i=0; i<10; i++){if(i % 5 == 0){printf("if m: %d\n", m);}else{int n = i % 4;if(n<2 && n>0){printf("else m: %d\n", m);}}}printf("func2 n: %d\n", n);
}
void func3(){printf("func3 n: %d\n", n);
}
int main(){int n = 30;func1();func2(n);func3();printf("main n: %d\n", n);return 0;
}

下图展示了这段代码的作用域:

蓝色表示作用域的名称,红色表示作用域中的变量,global 表示全局作用域。在灰色背景的作用域中,我们使用到了 m 变量,而该变量位于全局作用域中,所以得穿越好几层作用域才能找到 m。

函数的定义与变量作用域相关推荐

  1. python 全局变量使用报错没有定义_Python变量作用域代码解析

    本篇文章小编给大家分享一下Python变量作用域代码解析,文章代码介绍的很详细,小编觉得挺不错的,现在分享给大家供大家参考,有需要的小伙伴们可以来看看. 特点 python的作用域是静态的,在源代码中 ...

  2. 函数不可以直接调用其他函数内部定义的变量_python的函数

    上节课的补充 字典的键(key) 和集合的值都是唯一的,如果插入相同的会进行替换 函数 它可以解决两个问题 1.重复性 2.封装 定义函数语法格式 def + 函数名 + () +: 封装的代码(调用 ...

  3. main c语言中变量的定义,C语言中在main函数中定义的变量是全局变量么_后端开发...

    PHP 和 JavaSript 区别_后端开发 PHP是一种创建动态交互性站点的强有力的服务器端脚本语言,主要用于Web开发领域,而JavaSript是一种具有函数优先的轻量级,解释型或即时编译型的高 ...

  4. 【C语言进阶】函数三要素,变量作用域、生存周期、存储类型

    目录 写在前面 一.函数的用法 1.声明 2.调用 3.定义 形参与实参类型不一致 形参与实参类型一致 函数值类型与返回值类型不一致 函数值类型与返回值类型一致 二.变量的作用域与生存周期 三.变量的 ...

  5. node.js 函数外定义的变量 函数内赋值后为什么不能带出_(44)python少儿编程之函数(五)--- 作用域...

    函数嵌套 在一个函数内定义了另一个函数 如图,定义了一个outer函数,然后在outer函数里面又定义了一个inner()函数,此时我们只能在outer函数调用inner()函数,如果在outer函数 ...

  6. 函数不可以直接调用其他函数内部定义的变量_基础知识回顾函数(一)

    一.函数的作用 函数就是将一段具有独立功能的代码块整合到一个整体并命名,在需要的位置调用这个名称即可完成对应的需求.函数在开发过程中,可以更高效的实现代码重用. # 备注:因为在Python里面,函数 ...

  7. bash shell函数的定义及变量的简单说明

    From: http://blog.sina.com.cn/s/blog_4ce3a68a0100i77a.html 函数: "函数是一种脚本内脚本",程序员很难想像没有函数的日子 ...

  8. 函数(定义、参数、return、变量、作用域、预解析)

    一.函数定义 1.方式一       function 函数名(参数){  函数体  }------函数声明的方法 function fn(a){console.log(a);}: 2.方式二    ...

  9. Go 学习笔记(6)— 变量定义、变量声明、变量作用域

    1. 变量定义 Go 语言变量名由字母.数字.下划线组成,其中首个字符不能为数字.声明变量的一般形式是使用 var 关键字: var varName dataType [= value] Go 语言和 ...

  10. 【函数的定义、调用(嵌套调用、递归调用)、声明、函数的分类(有无返回值、有无参数)、变量(自动变量与静态变量、局部变量与全局变量、只读变量)】(学习笔记7--函数)

    第一篇博文,打卡新星计划第三季3.4~4.4,希望能有质的飞跃,顶峰相见 一.自定义函数 1.函数的定义 函数在使用前也需要定义,定义的格式如下: 数据类型 函数名([数据类型 参数1],[数据类型 ...

最新文章

  1. python安装numpy-python安装numpy和pandas的方法步骤
  2. 一篇文章让你轻松搞定SpringBoot和SpringCloud之间的版本选择!!!
  3. Python成长之路【第七篇】:Python基础之装饰器
  4. nssl1156-今天你AK了吗?【康托展开,高精度,二分答案,树状数组】
  5. Maven搭建Nexus私服
  6. 纯JS前端分页方法(JS分页)
  7. mybatis多对一,一对一,多对多resultMap映射,pojo映射,传参集合,封装的对象传参
  8. 【图解算法】最小生成树
  9. 奇怪的电梯(DP动态规划和BFS)
  10. utc时间 单位换算_国际时间换算
  11. 对话仟峰资本Steven:DeFi大赢家是怎样炼成的 |链捕手
  12. 上市只是开端,库客音乐用版权打出组合拳
  13. 如何把docx变为ass_电影字幕转Word教程
  14. 正则表达式之多种格式的电话号码匹配
  15. 文章标题一个应届计算机毕业生的2012求职之路
  16. 28岁华为员工工资表曝光牛逼的人注定会牛逼你们难道自甘堕落?
  17. 【转】一起做RGB-D SLAM (1)
  18. 服装家纺生产制造执行系统——MES
  19. ECC证书操作汇总(ECC certificate operations summary)
  20. 解读焦虑:为什么不断赚钱却很心慌?

热门文章

  1. Redis学习笔记~关于空间换时间的查询案例
  2. Oracle 备份与恢复学习笔记(8)
  3. Python在线 基础教程
  4. 中国互联网今日正式满 20 岁
  5. DB2 表的常用命令
  6. 数据结构以及相关排序
  7. RecycleView的普通适配器(另加RecycleView的格局格式)
  8. mysql配置my.cnf文件,以及参数优化提升性能
  9. Ice_cream’s world III(prime)
  10. Ekho TTS 5.1发布