C语言基础篇4:变量、存储、库函数
1 局部变量和全局变量
在介绍局部变量和全局变量前,先了解一些关于作用域方面的内容。作用域的作用就是决定程序中的哪些语句是可用的,换句话说,就是程序中的可见性。作用域有局部作用域和全局作用域,那么局部变量就具有局部作用域,而全局变量就具有全局作用域。
1.1 局部变量
在一个函数的内部定义的变量就是局部变量,无法被别的函数使用。函数的形参也属于局部变量,作用范围仅限于函数内部的语句块。
【示例1.1】局部变量的作用域
#include <stdio.h>
int main(){
int i = 1;
if(i > 0){
int j = 2;
if(j > 0){
int m = 3;
printf("all number %d %d %d \n",i,j,m);
}
}
return 0;
}
在C语言中位于不同作用域的变量可以使用相同的标识符,也就是在这种情况下,变量名可以相同。如果在一个函数中,内层作用域中定义的变量和已经声明过的某个外层变量有相同的名字,那么内层作用域中的变量将覆盖外层作用域中的那个变量。
1.2 全局变量
程序的编译单位是源文件,上面说到,在函数中定义的变量称为局部变量。如果一个变量在所有的函数的外部声明,这个变量就是全局变量。全局变量是可以在程序中的任何位置进行访问的变量。
【注意】全局变量不属于某个函数,而是整个源文件。但如果外部文件要进行调用,需要使用extern进行引用修饰。
定义全局变量的作用是增加了函数间数据联系的渠道。由于同一个文件中的所有函数都能引用全局变量的值,因此如果在一个函数中改变了全局变量的值,就会影响到其他函数。
【示例1.2】使用全局变量
#include <stdio.h>
int global = 100; //定义全局变量
void Store1Price();
void Store2Price();
void Store3Price();
void ChangePrice();
int main(){
printf("调整之前,全局变量的值是: %d \n",global);
Store1Price();
Store2Price();
Store3Price();
ChangePrice();//修改全局变量的值
printf("调整之后,全局变量的值是: %d \n",global);
Store1Price();
Store2Price();
Store3Price();
}
void Store1Price(){
printf("全局变量的值是:%d \n",global);
}
void Store2Price(){
printf("全局变量的值是:%d \n",global);
}
void Store3Price(){
printf("全局变量的值是:%d \n",global);
}
void ChangePrice(){
printf("是否需要改变全局变量的值,希望改成:");
scanf("%d",&global);
}
2 变量的存储类别
变量的存储类别决定变量什么时候被分配到指定的内存空间中,以及在什么时候释放内存空间。因此,存储类别就是为变量分配使用内存空间的方式,也可以称为存储方式。变量的存储类别分为两种形式,动态存储和静态存储。并且可以通过存储类修改符来告诉编译器要处理什么样的类型变量。具体有自动(auto)、静态(static)、寄存器(register)和外部(extern)4种。
2.1 静态存储和动态存储
从变量的产生时间上可以分为静态存储和动态存储。
静态存储就是指在程序运行时分配的固定的存储方式,而动态存储则是在程序运行期间根据需要进行动态的分配存储空间。
要理解动态存储和静态存储方式,首先要了解内存中用户存储空间的基本情况。系统提供用户的存储空间可以分为3个部分:程序区、静态存储区和动态存储区。
其中,程序区用来存放用户要执行的程序段。数据分别放在静态存储区和动态存储区中。
静态存储的变量位于内存的静态存储区,全局变量都保存在静态存储区中,因此全局变量从程序执行时开发分配存储单元,直到程序运行结束,才释放其所占的存储单元。
在动态存储区中存储跟堆栈操作相关的数据有关,堆栈中的数据随着进栈出栈操作而变化,当变量被弹出堆栈以后,其生存周期也就结束了。在调用函数时,其局部变量也被保存到动态存储区中,当函数结束执行,返回到主调函数时,变量所占用的空间将被释放,此时局部变量也将消失。由此可见,如果一个函数被调用了两次,其中变量的存储空间可能为不同的地址。
个存储区所存放的数据内容如下:
1、静态存储区:存储全局变量,在程序执行过程中,全局变量占据固定的内存空间,直到程序执行完毕才释放内存。
2、动态存储区:
1)自动变量,在函数调用时分配存储空间,调用完成释放存储空间。
2)函数调用时现场保护和返回地址,在函数被调用时分配内存空间。
3)函数形参:只有在调用该函数时才能为形参分配内存空间,调用完以后会将所有的空间释放掉。
从上述分析可知,静态存储变量是一直都存在的,而动态存储变量则是根据函数执行过程决定是否存在还是消失。
2.2 auto变量
该存储类型是C语言中使用最广泛的一种类型。C语音规定,函数内凡未加存储类型说明的变量,默认都是自动变量,也就是说自动变量可以省去说明符auto。
自动变量有以下特点:
1、自动变量的作用域仅限于定义该变量的个体内。在函数中定义的自动变量,只在该函数中有效。在复合语句中定义的自动变量只在该复合语句中有效。
2、自动变量属于动态存储方式,只有在使用它时,即定义该变量的函数被调用时才给它分配存储单元,函数调用结束,释放存储单元。因此函数调用结束后,自动变量的值不能保留。同样在复合语句中定义的自动变量,在退出复合语句后也不能在使用。
2.3 static变量
在编写程序的过程中,有些函数的局部变量的值在函数调用结束后不希望值消失,也就是不释放该变量所占用的存储单元;同样,有时在程序设计中也希望某些外部变量只限于被本文件引用,而不能被其他文件引用。这时就需要使用关键词static对变量进行声明。
1、静态局部变量
在局部变量的说明前加上static说明符就构成了静态局部变量。
静态局部变量属于静态存储方式,具有以下特点:
1)、静态局部变量在函数内定义,但与自动变量不同,当调用时就已经存在,退出函数时消失。静态局部变量属于静态存储类别,在静态存储区内分配存储单元,在程序整个运行期间都不会释放,静态局部便令始终存储,也就是说它的生存期为整个源程序。
2)、静态局部变量的生存期虽然为整个源程序,但是其作用域仍与自动变量相同,只能在定义该变量的函数内使用该变量。退出函数后,尽管改变量还存在,但不能使用它,如果再次调用其定义的函数,又可以继续使用。
3)、对基本类型的静态局部变量,如果在声明时没有赋初始值,则系统自动赋值0。而对于自动变量不赋初值,其值是不确定的。
【示例2.3】使用静态变量
#include <stdio.h>
int add(int x){
static int n = 0;
n = n + x;
return n;
}
int main(){
int i,j,sum;
printf("请输入一个整数:\n");
scanf("%d",&i);
for (j = 1; j< i; j++) {
sum = add(j);
printf("%d:%d \n",j,sum);
}
return 0;
}
上面代码中的静态局部变量n是一种生存期为真个源程序的变量。每次调用add函数时,静态局部变量n都保存了前次被调用后留下的值。如果将static改成auto,那么运行结果是:
因此,从运行结果可以看出,自动变量占动态存储区空间,而不占静态存储区空间,导致每次调用后变量n的值都被释放,每次调用add方法时,n的值都是从0开始。
2 静态全局变量
在全局变量的变量类型说明之前加上static就构成了静态的全局变量。全局变量本身就是静态存储方式,静态全局变量自然也是静态存储方式。两者在存储方式上并不同。两者的区别在于作用域不同,非静态全局变量的作用域是整个源程序,当一个源程序有多个源文件组成时,非静态的全局变量在各个源文件都是有效的。
例如,在file1.c中定义了非静态全局变量i,则在其他的源文件(如file2.c)中也可以调用;而静态全局变量则限制了其作用域,即只在定义该变量的源文件中有效,在同一源程序的其他源文件中不能使用它。例如,在file1.c中定义了静态全局变量,则在其他的源文件(file2.c)中不可以调用。
【说明】static说明符在不同的地方所起的作用是不同的。将局部变量改变为静态变量后是改变了它的存储方式,即改变了其生存周期。将全局变量改变为静态变量后是改变了它的作用域,限制其使用范围。
2.4 register变量
通常变量的值是存放在内存中,当对一个变量频繁读写时,则需要反复访问内存,从而花费大量的时间。为了提高效率,C语言提供了另一种变量,即寄存器变量。这种变量允许将局部变量的值存放在CPU的寄存器中,使用时不需要访问内存,而直接从寄存器中读写。寄存器变量的说明符是register。
对寄存器变量还需要说明以下几点:
用户无法获取寄存器变量的地址,因为绝大多数计算机的硬件寄存器都不占用内存地址。而且,即使编译器忽略register而把变量存放在可设定的内存中,也是无法获取变量的地址的。
如果想要有效地利用寄存器register关键字,必须像汇编语言程序员那样了解处理器的内部结构,直到可用于存储变量的寄存器的数量、种类以及工作方式。但是,对于这些细节不同的计算机还可能是不同的。因此,对于一个要具备可移植的程序来说,regis作用不大。
2.5 extern变量
由于C语言允许将一个较大的程序分成若干独立模块文件分别编译,如果一个源文件中的函数想引用其他源文件中的变量,就可以用extern来声明外部变量,也就是说extern变量可以扩展外部变量的作用域。
1、在多文件的程序中声明外部变量
定义时默认static关键字的外部变量,即为非静态外部变量。其他源文件中的函数,引用非静态外部变量时,需要在引用函数所在的源文件进行如下声明:
extern 数据类型 外部变量表;
注意:在函数内的extern变量说明,表示引用本源文件中的外部变量,而函数外的extern变量说明,表示引用其他文件中的外部变量。
例如,有一个源程序由源文件file1.c和file2.c组成。在file1.c和file2.c两个文件中都要使用x y z 三个变量。在file1.c文件中把x y z 都定音为外部变量。在file2.c文件中用extern把三个变量声明为外部变量,表示这些变量已在其他文件中定义,编译系统不在为它们分配内容空间。对构造类型的外部变量(如数组等)可以在声明时作为初始化赋值,如果不赋初值,则系统自动定义它们的初始值为0。
2、在一个文件内声明外部变量
如果外部变量不在文件的开头定义,其有效的作用范围只限于定义到文件结束处。此时如果想在定义该变量的位置之前调用此变量,则应该在应用之前用关键字extern对该变量作“外部变量声明”。
3 内部函数和外部函数
函数时C语言最小单位,往往把一个函数或多个函数保存为一个文件,这个文件称为源文件。定义一个函数,这个函数就要被另外的函数所调用。但当一个源程序由多个源文件组成时,可以指定函数不能被其他文件调用,这样c语言又把函数分为两类:内部函数和外部函数。
3.1 内部函数
定义的函数只被所在的源文件中使用,称为内部函数。内部函数又称为静态函数。使用内部函数,可以是函数只局限于函数所在的源文件中,如果在不同的源文件中有同名的内部函数,这些同名的内部函数也不互相干扰。
在定义内部函数时,要在函数返回值和函数名前加上关键字static进行修饰。
static 返回值类型 函数名(参数列表)
使用内部函数的好处,不能同的开发者可以分别编写不同的函数,而不必担心函数是否会和其他的源文件的函数重名。
【示例 3.1】使用内部函数,通过一个函数对字符串进行赋值,在通过一个函数对字符串进行输出显示。
#include <stdio.h>
static char* GetString(char* pString){
return pString;
}
static void ShowString(char* pString){
printf("%s\n",pString);
}
int main(){
char* pString;
pString = GetString("hello!");
ShowString(pString);
return 0;
}
3.2 外部函数
与内部函数相反的是外部函数,外部函数就是可以被其他源文件调用的函数。定义外部函数使用关键字extern进行修饰。在使用一个外部函数时,要先用关键字extern声明所用的函数时外部函数。例如,函数头可以写成:extern int add(int i,int j);这样add函数就可以被其他文件调用了。
注意:C语言定义函数时,如果不指明函数是内部函数或外部函数,那么默认将函数指定为外部函数。也就是说,定义外部函数时,可以省略extern关键字。
4 数学函数
在程序中经常会使用一些数学的运算或者公式,这时就要用到数学函数。数学函数的返回值都是双精度数据类型的,用到它们的程序中都包含头文件math.h
4.1 绝对值函数
abs 函数的功能是求整数的绝对值,参数x是所要求的绝对值的整数。定义形式如下:
int abs(int x)
#include <stdio.h>
#include <math.h>
int absTest();
int main(){
int result = absTest(-10);
printf("绝对值是:%d",result);
return 0;
}
int absTest(int x){
int result = abs(x);
return result;
}
labs函数:求长整数的绝对值。定义形式如下:long labs(long x)
#include <stdio.h>
#include <math.h>
int absTest();
long labsTest();
int main(){
long result = labsTest(-1234567890L);
printf("绝对值是:%d",result);
return 0;
}
long labsTest(long x){
int result = labs(x);
return result;
}
fabs函数:返回浮点数的绝对值。定义形式如下:
double fabs(double n)
#include <stdio.h>
#include <math.h>
int absTest();
long labsTest();
double fabsTest();
int main(){
double result = fabsTest(-123.45);
printf("绝对值是:%lf",result);
return 0;
}
double fabsTest(double x){
double result = fabs(x);
return result;
}
5 字符和字符串函数(例子在后面)
使用字符串函数时要包含头文件string.h,而使用字符函数要包含头文件ctype.h
5.1 isalpha函数
用来检测字母,如果参数(ch)是字母表中的字母(大写或小写),则返回非0。要包含头文件ctype.h。定义形式:int isalpah(int ch)。
5.2 isdigit函数
用来检测数字,如果ch是数字则函数返回非0,否则返回0。要包含头文件ctype.h。定义形式:int isdigit(int ch)。
5.3 isalnum函数
用来检测字母或数字,如果参数时字母表中的一个字母或数字,则函数返回非0。否则返回0。要包含头文件ctype.h。定义形式:int isalnum(int ch)。
【示例2.1】使用字符函数判断输入字符。
#include <stdio.h>
#include <ctype.h>
void SwitchShow(char c);
int main(){
//定义字符变量,用来接收输入的字符
char cCharPut;
//定义字符变量,用来接收回车
char cCharTemp;
//提示消息
printf("第一次输入字符");
//输入字符
scanf("%c",&cCharPut);
//调用函数进行判断
SwitchShow(cCharPut);
//接受回车
cCharTemp = getchar();
//消息提示
printf("进行第二次输入字符:");
//输入字符
scanf("%c",&cCharPut);
//调用函数进行判断
SwitchShow(cCharPut);
//接受回车
cCharTemp = getchar();
//消息提示
printf("进行第三次输入字符:");
//输入字符
scanf("%c",&cCharPut);
//调用函数进行判断
SwitchShow(cCharPut);
//程序结束
return 0;
}
void SwitchShow(char cChar){
//判断是否是字母
if(isalpha(cChar)){
printf("您输入的是字符表里的字母:%c \n",cChar);
}
//判断是否是数字
if(isdigit(cChar)){
printf("您输入的是阿拉伯数字:%c \n",cChar);
}
//判断是否是字母或数字
if(isalnum(cChar)){
printf("你输入的是包含字母和数字:%c \n",cChar);
}
else{
printf("您输入的既不是字母也不是数字:%c \n",cChar);
}
}
5.4 strchr函数
返回由str所指向的字符串中首先出现ch的位置指针,如果未发现与ch匹配的字符,则返回空指针。需要以如头文件 string.h。定义如下:
char strchar(char *str,char ch)
【示例】使用strchr函数查找字符串s中指定字符u的首位置。
#include <stdio.h>
#include <string.h>
int main(){
//定义字符数组
char s[20];
//声明要查找的字符
char *p,c = 'u';
//复制字符串
strcpy(s,"good luck to you");
//查找字符
p = strchr(s,c);
//输出字符首位置
if(p){
printf("字符%c在数组的位置是:%d\n",c,p-s);
}else{
printf("字符u在数组中没找到\n");
}
return 0;
}