详细分析c/c++语言中的static

一 static在c语言中的应用

详细分析一下static关键字在c语言中编写程序时有的三大类用法:

        一,static全局变量

           我们知道,一个进程在内存中的布局如图1所示:

      其中.text段保存进程所执行的程序二进制文件,.data段保存进程所有的已初始化的全局变量,.bss段保存进程未初始化的全局变量(其他段中还有很多乱七八糟的段,暂且不表)。在进程的整个生命周期中,.data段和.bss段内的数据时跟整个进程同生共死的,也就是在进程结束之后这些数据才会寿终就寝。

     当一个进程的全局变量被声明为static之后,它的中文名叫静态全局变量。静态全局变量和其他的全局变量的存储地点并没有区别,都是在.data段(已初始化)或者.bss段(未初始化)内,但是它只在定义它的源文件内有效,其他源文件无法访问它。所以,普通全局变量穿上static外衣后,它就变成了新娘,已心有所属,只能被定义它的源文件(新郎)中的变量或函数访问。

以下是一些示例程序

file1.h如下:

#include <stdio.h>  
  
void printStr();

我们在file1.c中定义一个静态全局变量hello, 供file1.c中的函数printStr访问.

#include "file1.h"  
  
static char* hello = "hello cobing!";  
  
void printStr()  
{  
    printf("%s\n", hello);  
} 


file2.c是我们的主程序所在文件,file2.c中如果引用hello会编译出错

    #include "file1.h"  
      
    int main()  
    {  
        printStr();  
        printf("%s\n", hello);  
        return 0;  
    }  


报错如下:

[liujx@server235 static]$ gcc -Wall file2.c file1.c -o file2
file2.c: In function ‘main’:
file2.c:6: 错误:‘hello’ 未声明 (在此函数内第一次使用)
file2.c:6: 错误:(即使在一个函数内多次出现,每个未声明的标识符在其
file2.c:6: 错误:所在的函数内只报告一次。)


如果我们将file2.c改为下面的形式:

#include "file1.h"  
  
int main()  
{  
    printStr();  
    return 0;  
} 


则会顺利编译连接。

运行程序后的结果如下:
[liujx@server235 static]$ gcc -Wall file2.c file1.c -o file2
[liujx@server235 static]$ ./file2
hello cobing!

上面的例子中,file1.c中的hello就是一个静态全局变量,它可以被同一文件中的printStr调用,但是不能被不同源文件中的file2.c调用。

 

      二,static局部变量

      普通的局部变量在栈空间上分配,这个局部变量所在的函数被多次调用时,每次调用这个局部变量在栈上的位置都不一定相同。局部变量也可以在堆上动态分配,但是记得使用完这个堆空间后要释放之。

       static局部变量中文名叫静态局部变量。它与普通的局部变量比起来有如下几个区别:

           1)位置:静态局部变量被编译器放在全局存储区.data(注意:不在.bss段内,原因见3)),所以它虽然是局部的,但是在程序的整个生命周期中存在。

           2)访问权限:静态局部变量只能被其作用域内的变量或函数访问。也就是说虽然它会在程序的整个生命周期中存在,由于它是static的,它不能被其他的函数和源文件访问。

           3):静态局部变量如果没有被用户初始化,则会被编译器自动赋值为0,以后每次调用静态局部变量的时候都用上次调用后的值。这个比较好理解,每次函数调用静态局部变量的时候都修改它然后离开,下次读的时候从全局存储区读出的静态局部变量就是上次修改后的值。
以下是一些示例程序:

     file1.h的内容和上例中的相同,file1.c的内容如下:

#include "file1.h"  
  
void printStr()  
{  
    int normal = 0;  
    static int stat = 0;    //this is a static local var  
    printf("normal = %d ---- stat = %d\n",normal, stat);  
    normal++;  
    stat++;  
} 


为了便于比较,我定义了两个变量:普通局部变量normal和静态局部变量stat,它们都被赋予初值0;

file2.c中调用file1.h:

#include "file1.h"  
  
int main()  
{  
 printStr();  
 printStr();  
 printStr();  
 printStr();  
 printf("call stat in main: %d\n",stat);  
 return 0;  
}


这个调用会报错,因为file2.c中引用了file1.c中的静态局部变量stat,如下:

[liujx@server235 static]$ gcc -Wall file2.c file1.c -o file2
file2.c: In function ‘main’:
file2.c:9: 错误:‘stat’ 未声明 (在此函数内第一次使用)
file2.c:9: 错误:(即使在一个函数内多次出现,每个未声明的标识符在其
file2.c:9: 错误:所在的函数内只报告一次。)

编译器说stat未声明,这是因为它看不到file1.c中的stat,下面注掉这一行:

 

    #include "file1.h"  
      
    int main()  
    {  
        printStr();  
        printStr();  
        printStr();  
        printStr();  
    //  printf("call stat in main: %d\n",stat);  
        return 0;  
    }  


[liujx@server235 static]$ gcc -Wall file2.c file1.c -o file2
[liujx@server235 static]$ ./file2
normal = 0 ---- stat = 0
normal = 0 ---- stat = 1
normal = 0 ---- stat = 2
normal = 0 ---- stat = 3

运行如上所示。可以看出,函数每次被调用,普通局部变量都是重新分配,而静态局部变量保持上次调用的值不变。

需要注意的是由于static局部变量的这种特性,使得含静态局部变量的函数变得不可重入,即每次调用可能会产生不同的结果。这在多线程编程时可能会成为一种隐患。需要多加注意。


       三,static函数
              相信大家还记得C++面向对象编程中的private函数,私有函数只有该类的成员变量或成员函数可以访问。在C语言中,也有“private函数”,它就是接下来要说的static函数,完成面向对象编程中private函数的功能。

            当你的程序中有很多个源文件的时候,你肯定会让某个源文件只提供一些外界需要的接口,其他的函数可能是为了实现这些接口而编写,这些其他的函数你可能并不希望被外界(非本源文件)所看到,这时候就可以用static修饰这些“其他的函数”。

           所以static函数的作用域是本源文件,把它想象为面向对象中的private函数就可以了。

下面是一些示例:

file1.h如下:

    #include <stdio.h>  
      
    static int called();  
    void printStr();  


file1.c如下:

    #include "file1.h"  
      
    static int called()  
    {  
        return 6;  
    }  
    void printStr()  
    {  
        int returnVal;  
        returnVal = called();  
        printf("returnVal=%d\n",returnVal);  
    }  


file2.c中调用file1.h中声明的两个函数,此处我们故意调用called():

#include "file1.h"  
  
int main()  
{  
    int val;  
    val = called();  
    printStr();  
    return 0;  
} 


编译时会报错:

[liujx@server235 static]$ gcc -Wall file2.c file1.c -o file2
file1.h:3: 警告:‘called’ 使用过但从未定义
/tmp/ccyLuBZU.o: In function `main':
file2.c:(.text+0x12): undefined reference to `called'
collect2: ld 返回 1

因为引用了file1.h中的static函数,所以file2.c中提示找不到这个函数:undefined reference to 'called'

下面修改file2.c:

#include "file1.h"  
  
int main()  
{  
    printStr();  
    return 0;  
} 


编译运行:

[liujx@server235 static]$ gcc -Wall file2.c file1.c -o file2
[liujx@server235 static]$ ./file2
returnVal=6

       static函数可以很好地解决不同原文件中函数同名的问题,因为一个源文件对于其他源文件中的static函数是不可见的。

二 static在c语言中的应用


当然以上的几种,也可以用在c++中。还有额外的两种用法:

1.静态数据成员:用于修饰 class 的数据成员,即所谓“静态成员”。这种数据成员的生存期大于 class 的对象(实体 instance)。静态数据成员是每个 class 有一份,普通数据成员是每个 instance 有一份,因此静态数据成员也叫做类变量,而普通数据成员也叫做实例变量。


  1. #include<iostream>  
  2. using namespace std;  
  3. class Rectangle  
  4. {  
  5. private:  
  6.     int m_w,m_h;  
  7.     static int s_sum;  
  8. public:  
  9.     Rectangle(int w,int h)  
  10.     {  
  11.         this->m_w = w;  
  12.         this->m_h = h;  
  13.         s_sum += (this->m_w * this->m_h);  
  14.     }  
  15.     void GetSum()  
  16.     {  
  17.         cout<<"sum = "<<s_sum<<endl;  
  18.     }  
  19.   
  20.   
  21. };  
  22.   
  23.   
  24. int Rectangle::s_sum = 0;  //初始化  
  25.   
  26.   
  27.   
  28.   
  29. int main()  
  30. {  
  31.     cout<<"sizeof(Rectangle)="<<sizeof(Rectangle)<<endl;  
  32.     Rectangle *rect1 = new Rectangle(3,4);  
  33.     rect1->GetSum();  
  34.     cout<<"sizeof(rect1)="<<sizeof(*rect1)<<endl;  
  35.     Rectangle rect2(2,3);  
  36.     rect2.GetSum();  
  37.     cout<<"sizeof(rect2)="<<sizeof(rect2)<<endl;  
  38.       
  39.     system("pause");  
  40.     return 0;  



结果如下:


由图可知:sizeof(Rectangle)=8bytes=sizeof(m_w)+sizeof(m_h)。也就是说 static 并不占用Rectangle的内存空间。
那么static在哪里分配内存的呢?是的,全局数据区(静态区)。
再看看GetSum(),第一次12=3*4,第二次18=12+2*3。由此可得,static只会被初始化一次,于实例无关。

结论:

对于非静态数据成员,每个类对象(实例)都有自己的拷贝。而静态数据成员被当作是类的成员,由该类型的所有对象共享访问,对该类的多个对象来说,静态数据成员只分配一次内存。
静态数据成员存储在全局数据区。静态数据成员定义时要分配空间,所以不能在类声明中定义。

也就是说,你每new一个Rectangle,并不会为static int s_sum的构建一份内存拷贝,它是不管你new了多少Rectangle的实例,因为它只与类Rectangle挂钩,而跟你每一个Rectangle的对象没关系。


2、静态成员函数:用于修饰 class 的成员函数。
我们对上面的例子稍加改动:


  1. #include<iostream>  
  2.   
  3.   
  4. using namespace std;  
  5.   
  6.   
  7. class Rectangle  
  8. {  
  9. private:  
  10.     int m_w,m_h;  
  11.     static int s_sum;  
  12.       
  13. public:  
  14.     Rectangle(int w,int h)  
  15.     {  
  16.         this->m_w = w;  
  17.         this->m_h = h;  
  18.         s_sum += (this->m_w * this->m_h);  
  19.     }  
  20.   
  21.   
  22.     static void GetSum()  //这里加上static  
  23.     {  
  24.         cout<<"sum = "<<s_sum<<endl;  
  25.     }  
  26.   
  27.   
  28. };  
  29.   
  30.   
  31. int Rectangle::s_sum = 0;  //初始化  
  32.   
  33.   
  34.   
  35.   
  36. int main()  
  37. {  
  38.     cout<<"sizeof(Rectangle)="<<sizeof(Rectangle)<<endl;  
  39.     Rectangle *rect1 = new Rectangle(3,4);  
  40.     rect1->GetSum();  
  41.     cout<<"sizeof(rect1)="<<sizeof(*rect1)<<endl;  
  42.     Rectangle rect2(2,3);  
  43.     rect2.GetSum();  //可以用对象名.函数名访问  
  44.     cout<<"sizeof(rect2)="<<sizeof(rect2)<<endl;  
  45.     Rectangle::GetSum();  //也可以可以用类名::函数名访问  
  46.   
  47.   
  48.     system("pause");  
  49.     return 0;  
  50. }  


上面注释可见:对GetSum()加上static,使它变成一个静态成员函数,可以用类名::函数名进行访问。
那么静态成员函数有特点呢?
1.静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
2.非静态成员函数可以任意地访问静态成员函数和静态数据成员;
3.静态成员函数不能访问非静态成员函数和非静态数据成员;
4.调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数,也可以用类名::函数名调用(因为他本来就是属于类的,用类名调用很正常)


前三点其实是一点:静态成员函数不能访问非静态(包括成员函数和数据成员),但是非静态可以访问静态,有点晕吗?没关系,我给你个解释,
因为静态是属于类的,它是不知道你创建了10个还是100个对象,所以它对你对象的函数或者数据是一无所知的,所以它没办法调用,而反过来,你创建的对象是对类一清二楚的(不然你怎么从它那里实例化呢),所以你是可以调用类函数和类成员的,就像不管GetSum是不是static,都可以调用static的s_sum一样。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值