C语言:随笔4

1、函数形参和实参的说明:

(1)在定义函数时指定的形参,在未出现函数调用时,他们并不占内存中的存储单元。只有发生函数调用时,函数中的形参才被分配内存单元。在调用结束后,形参所占的内存单元也被释放。

(2)实参可以是常量、变量或表达式,如:max(3,a+b);但要求他们有确定的值。在调用时将实参的值赋给(是赋值给)形参。

(3)在被定义的函数中,必须指定形参的类型;

(4)实参与形参的类型应相同或者赋值兼容。

(5)在C语言中,实参向形参的数据传递是“值传递”(相当于COPY)单向传递,只由实参传给形参,而不能由形参传回来给实参。在内存中,实参单元与形参单元是不同的。

PS:在调用函数时,给形参分配存储单元,并将实参对应的值传递给形参,调用结束后形参单元被释放,实参单元仍保留并维持原值,因此在执行一个被调用函数时,形参的值如果发生改变,并不会改变主调函数的实参的值。(如果要发生改变,我们后期会使用一个传址,此处是地址的址,就会改变他的内存,就是传过去的是地址或者引用)(现在是传值,只是copy,数值的值)

2、函数的返回值:

(1)函数的返回值是通过函数中的return语句获得的;

return语句将被调用函数中的一个确定值带回主调函数中去。

一个函数中可以有一个以上的return语句,执行到哪一个return语句,哪一个语句起作用。

return后边可以是一个值也可以是一个表达式,只要你这个表达式最后能够算出一个确切的值那就ok了。

在C语言中凡是不加类型说明的函数,自动按照整型处理。

3、函数声明的作用;

声明的作用是把函数名、函数参数的个数和函数类型等信息通知编译系统,以便在遇到函数调用时,编译系统能正确识别函数并检查调用是否合法。(例如函数名是否正确,实参与形参的类型和个数是否一致)

函数的“定义”和“声明”不是一回事,函数的定义是指函数功能的确立。包括函数名、函数值类型、形参及其类型、函数体等,他是一个完整的、独立的函数单位。(所以函数声明是不占内存的,他的定义是占内存的)

如果函数定义出现在主调函数之前,可以不必加以声明。

C语言不能嵌套定义函数,但是可以嵌套调用函数。

4、递归

在调用一个函数的过程中又出现直接或间接的调用该函数本身,称为函数的递归调用。

5、数组作为函数参数。

数组可以作为函数的参数使用,进行数据传送数组用作函数参数有两种形式:

(1)一种是把数组元素(下标变量)作为实参使用。

数组元素就是下标变量(a[1]就是第二个元素),他与普通变量并无区别,因此,他作为函数实参使用与普通变量是完全相同的,在发生函数调用时,把作为实参的数组元素的值传送给形参,实现单向的值传递。

(2)另一种是把数组名作为函数的形参和实参使用。

PS:用数组名作函数参数与用数组元素做实参有几点不同:

1):用数组元素做实参时,只要数组类型和函数的形参变量的类型一致,那么作为下标变量的数组元素的类型也和函数形参变量的类型是一致的,因此,并不要求函数的形参也是下标变量。即对数组元素的处理是按普通变量对待的。

2)用数组名做函数的参数时,则要求形参和相对应的实参都必须是类型相同的数组,都必须有明确的数组说明,当形参和实参二者不一致时,即会发生错误。

为什么如果是传递数组名的化,他的实参和形参都必须是数组?

数组名其实就是一堆数组变量的第一个元素的地址,领头羊的地址,相当于指针,指针变量里边存放的也是一个地址。

3)在普通变量或下标变量做函数参数时,形参变量和实参变量是由编译系统分配的两个不同的内存单元。在函数调用时发生的值传递是把实参变量的值赋予形参变量。

在用数组名做函数参数时,不是进行值的传送(而是进行(地址)传递),即不是把实参数组的每一个元素值都赋予形参数组的各个元素。

为什么呢?

因为:实际上形参数组并不存在,编译系统不为形参数组分配内存单元。(那为什么之前我们说实参变量把他变成一个元素的时候他就可以为它分配单元,而把它当作地址的化它就不分配内存呢?)

那么数据的传送是如何实现的呢?

在此之前介绍过,数组名就是数组的首地址。因此,在数组名作为函数参数(实参)时所进行的传送只是地址(数组的第一个元素的地址)的传送(把他第一个元素的地址传过去),也就是说把实参数组的首地址赋予形参数组名。(那这样的话,他就没有必要把整个数组都传过去,因为等一下拿到地址之后我就知道他在那里了,我直接拿来用就行了)

形参数组名取得该首地址之后,也就等于有了实在的数组(相同的数组)。实际上是形参数组和实参数组为同一数组(因为他们指向同一段内存空间),共同拥有一段内存空间。

#include<stdio.h>

void test(int b[10])
void main()
{
    int a[10]={2,4,6,8};//数组的地址的连续的
    test(a);//传一个地址过去,因为数组名就是数组的首地址
    putchar("\n")
}
void test(int b[10])//int一个 b数组接收到地址,可以说b数组也是指向了首地址
{
   int i=0;
   for(;i<10;i++)
   {
      printf("%d",b[i])//再打印
   }
}

见下图存放:

5、1形参数组不定义长度

就是把上边的b不定义长度,说明你只要定义他是一个数组类型跟编译器说他是一个数组类型那就ok了,你不用给他说这个数组有多大,因为编译器他根本不会再定义一个数组出来,他只是指向了原来的数组,原来的数组有多大,他就有多大。(或者你也可以写array[2]或者array[20]都行,他只是把他指向了a数组,并没有重新定义一个新的数组。)

#include<stdio.h>

void test(int array[])//不给他一个大小
void main()
{
    int a[10]={2,4,6,8};//数组的地址的连续的
    test(a);//传一个地址过去,因为数组名就是数组的首地址
    putchar("\n")
}
void test(int array[])//array不定义长度
   int i=0;
   for(;i<10;i++)
   {
      printf("%d",b[i])
   }
}

6、形参也是局部变量(局部变量存放在栈中)。(函数中的变量调用完之后就会自动销毁,但是全局变量的化必须等整个程序完全中止才会结束。(不同函数中可以使用同一个局部变量名,虽然他们是名字一样但是不同的内存空间的。)

变量的存储类别:

6.1变量的存储类别

大家知道从变量的作用域(即从空间)角度来分,可以分为全居变量(作用域不同全局变量的作用域是整个文件)和局部变量(局部变量的作用域是整个函数)。那么从变量值存在的时间(即生存期)角度来分又可以分为静态存储方式和动态存储方式。

关键字auto可以省略,auto不写则隐含定为“自动存储类别”,属于动态存储方式。

有时候函数中的局部变量的值在函数调用结束后不消失而保留原值,即其占用的存储单元不释放,在下次该函数调用时,该变量已有值就是上次函数调用结束时的值。这时就应该指定该局部变量为“静态局部变量”,用关键字static进行声明。

下边例子进行说明:

#include <stdio.h>
int f(int a)//a传给它的是2//相当于a=2赋值过去(两个a在不同的函数,位置不同,此处的a你定义为d也可以将main函数中的a传过来的)
{
   auto int b=0;//局部变量存放的是在栈这个位置,
   static int c=3;//而静态变量存放在数据区这个位置
   b=b+1;//b==1,1,1
   c=c+1;//c==4,5,6
   return(a+b+c);//返回3次7,8,9(第二次调用f函数的时候,b重新赋值为0(因为b不是static型),而c因为是static是静态的,上一次函数调用完之后没有销毁保留了第一次的4,所以第二次加1就是5)
}
void main()
{
    int a=2,i;
    for(i=0;i<3;i++)
    {
        printf("%d\n",f(a));
    } 
    
}

(1)静态局部变量属于静态存储类别,在静态存储区内分配存储单元,在程序整个运行期间都不释放。而我们的动态存储是在栈里边存储,栈里面有一个特点就是你每当调用一个函数时,OK我给你生成一个栈,调用完这个函数后,OK,这个栈由系统自动回收(这就是轻轻的来轻轻的走不带走一片内存,所以呢他不会占空间,而静态的呢,他就永远的呆在那里了直到你整个程序销毁,所以静态定义的多的话会比较占内存)。

(2)而动态变量(即动态局部变量)属于动态存储类别,占动态存储区空间而不占静态存储区空间,所在函数调用结束后即释放。

(3)对于静态局部变量是在编译时赋初值的,即只付初值一次,在程序运行时他已经有初值(比如上边的c,在进行单步调试时,将断点设置在for循环那里,第一次还未进入f函数时,c已经有值了是3了,在运行的时直接跳过c赋值了, 但是此时b还没有值呢。当进入f函数后运行到b时,b才被赋值为0),以后每次调用函数时不再重新赋初值而是保留上次函数调用结束时的值。

而对自动变量赋初值时,不是在编译时进行的,而是在函数调用时进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句。

(4)如在定义局部变量时不赋初值的话,则对静态变量来说,编译时自动赋初值0(对数值型变量)或空字符(对字符型变量)。而对自动变量来说,如果不赋初值则他的值是一个不确定的值。一般是一个负的很大很大的数。这是由于每次函数调用结束后存储单元已经释放下次调用时又重新另外分配存储单元,而所分配的单元中的值是不确定的。

(5)虽然静态局部变量在调用结束后仍然存在,但其他函数是不能引用它的。(因为他是静态的,他只归他所有)

一般情况下,变量(包括静态存储方式和动态存储方式)的值是存放在内存中的。 如果要执行都要经过这么个过程:

当程序中用到哪一个变量的值时,由控制器发出指令,将内存中该变量的值送到运算器中,经过运算器进行运算,如果需要存数,再从运算器将数据送到内存存放。

如果有一些变量使用频繁,(例如一个函数中执行10000次循环,每次循环中都要引用某局部变量)那么这样为存取变量的值要花费不少时间。为了提高执行效率,C语言允许将局部变量的值放在CPU中的寄存器,直接用时直接从寄存器取出参加运算,不必再到内存中去存取。由于寄存器的存取速度远高于对内存的存取速度,因此这样做可以提高执行效率。这种变量叫做寄存器变量,用关键字register做声明。

#incluede<stdio.h>
int fac(int n)
{
   register int i;f=1;//把f和i都定义为寄存器,让他存储在寄存器,而不存储在内存
   for(i=1;i<=n,i++)
    {
       f*=i;
    }
   return (f)
}
void main()
{
   int i;
   for(i=1;i<=5;i++)
      {
          printf("%d!=%d\n",i,fac(i))
      }
}
//但是不建议把所有的变量都变成寄存器,因为CPU的容量空间是有限的,所以他里边的寄存器也是有限的

(6)extern声明外部变量

外部变量即全局变量,他的作用域是从变量的定义开始,到本程序文件的末尾。在此作用域内全局变量可以为程序中各个函数所引用,编译时将外部变量分配在静态存储区。有时需要用extern来声明外部变量,以扩展外部变量的作用域。

#include<stdio.h>
int max(int x,int y)
{
    int z;
    z=x>y?x:y;
    return(z);
}
void main()//程序首先从main开始他extern了A,B(意思是告诉我们这个A和B的两个变量是全局变量),我们如果去掉他就看了,因为他是在下边定义的。如果是在上边定义他就看到了,因为编译器是从上往下读的。而读到此出还没有读到,所染A和B是全局变量因为他定义在各个函数之外。但是我们仍需要用extern声明一下,告诉他他是全局变量,只是你现在还没有看到他而已。这时候就能够顺利的调用他。(否则使用是在定义之前就不能编译)
{
    extern a,b;//可以尝试一下去掉extern关键字是什么后果
    print("%d\n",max(A,B))
}
int A=13,B=-6;

进阶一下,在多个文件的程序中声明外部变量

在此之前所讲的所有的程序都是在一个程序中搞定的,就是在一个后缀名为.c的文件中搞定的但事实上我们遇到的一些程序,一些现成的程序他们都是由非常多的一些文件组成的,一个程序里你随便打开一个程序它的内部源文件都是由非常多个文件组成的,每一个文件基本上都是实现了一个程序的部分功能,然后再把各个功能凑起来,所以我们现在需要学着把一个文件,把他拆成多个文件,都是来连接成这个程序的。

将一个程序拆成多个文件:

在一个文件中定义A。那么如果在另外一个文件中要使用就得extern A//表明A为一个已定义的外部变量。

用static声明外部变量(static之前提到过声明局部变量的化会使得这个局部变量不再是动态的存储,他不再是因为每一个函数的返回而撤销,它会一直的保存在内存里面,直到整个程序的结束,用static声明一个局部变量的话也就是声明一个内部变量或者局部变量的话就会使这个局部变量变成一个静态变量。),那么用static声明一个外部变量会怎么样?

有时候在程序设计中,希望某些外部变量只限于被本文件引用,而不能被其他文件引用,这时就可以在定义外部变量时加一个static声明(即增加了static不让别的文件引用)。

关于变量的声明和定义:

对变量而言,申明与定义的关系稍微复杂一些。在申明部分出现的变量有两种情况:一种是需要建立内存储空间的(如:int a;)另一种是不需要建立存储空间的(如:extern a;)(像extern a就不需要建立内存空间,它只是告诉编译器说已经有a这么一个定义的存在了。)那么前者叫做定义性声明或称为定义,后者叫做引用性声明(而单单只是声明不是定义,定义的话要建立存储空间的)。

一般为了叙述方便,把建立存储空间的声明称为定义,而把不需要建立存储空间的声明称为声明。显然这里指的声明是狭义的,即非定义性声明。

PS:小结一下:

(1)从作用域(就是作用范围)角度分,有局部变量和全局变量。他们采用的存储类别如下:

局部变量(就是只能看到本函数) 自动变量auto,即动态局部变量(离开函数,值就消失)
静态局部变量static(离开函数,值仍保留)
寄存器变量(离开函数,值就消失)(它只是存的位置不同存在哪里,寄存器)
(形式参数可以定义为自动变量或寄存器变量,但是不能定义为静态变量会报错的)
全局变量(就是在最外层都看得到的) 静态外部变量(只限本文件引用)
外部变量
(即非静态的外部变量,允许其他文件引用)

(2)从变量存在的时间(生存期)来区分,有动态存储和静态存储两种类型。静态存储是程序整个运行时间都存在(等到整个程序停下来之后整个程序结束之后他才会消失),而动态存储则是调用函数时临时分配单元(而动态存储他是整个函数结束之后才会消失)。(因为他们存储的范围不同,也就是他们在内存中呆的位置不同,而使他们不同的存储时间。像静态存储他是放在data段的,数据段的。而动态存储他是放在栈段的,所以他会临时消失)

动态存储 自动变量(本函数内有效)
寄存器变量(本函数内有效)
形式参数(本函数内有效)
静态存储 静态局部变量(函数内有效)
静态外部变量(本文件内有效)
外部变量(其他文件可引用)

(3)从变量值存放位置来区分,可分为:

内存中静态存储区 静态局部变量
静态外部变量(函数外部静态变量)
外部变量(可为其他文件引用)
内存中动态存储区域(也就是栈区) 自动变量和形式参数
CPU中的寄存器 寄存器变量

关于作用域和生存期,前者是从空间角度,后者是从时间角度。

(4)static对局部变量和全局变量的作用域不同。

对局部变量来说,他使变量由动态存储存储改变为静态存储。对于全局变量,它使变量局部化(只是局部于本文件),但仍为静态存储方式。从作用域角度看,凡有static声明,其作用域都是局限的,或者是局限与本函数(静态局部变量),或者局限于本文件内(静态外部变量)

------内部函数和外部函数----

函数本质上是全局的,因为一个函数要被另外的函数调用,但是也可以指定函数不能被其他文件调用。

我们根据函数能否被其他源文件调用,将函数区分为内部函数和外部函数。

如果一个函数只能被本文件中其他函数所调用,它称为内部函数。

在定义内部函数时,在函数名和函数类型的前面加static。即;

即static类型标识符   函数名(形参表)
如static int fun(int a,int b)

(1)在定义函数时,如果在函数首部的最左端加关键字extern,则表示此函数是外部函数,可供其他文件调用。如函数首部可以写为extern int fun(int a,int b)

这样,函数fun就可以为其他文件调用。C语言规定,如果在定义函数时省略extern,则隐含为外部函数。

猜你喜欢

转载自blog.csdn.net/m0_37957160/article/details/108182338