全局变量、静态全局变量、静态局部变量和局部变量的区别

static和const都是C++中比较麻烦的东西,只要是太容易混淆他们的作用,之前对static做过点总结,但是不全面,还有很多小的错误,现在整理一下:

变量可以分为:全局变量静态全局变量静态局部变量局部变量


『维基百科』给出如下定义

“静态变量”这一术语有两个容易混淆的定义:

  1. 语言无关的通用定义:与程序有着相同生命周期英语Object lifetime的变量;
  2. C族语言特有的定义:以static存储类声明的变量。

而在以Pascal为代表的许多程序语言中,所有局部变量都由系统自动分配存储空间,而所有全局变量的存储空间则以静态分配的方式获取(对应“静态变量”),因此由于实际上“局部变量”和“全局变量”这两个术语已足以涵盖所有的情况,在这些程序语言中通常不使用“静态变量”这一术语,而直接以“全局变量”代之。一般来说,在这些程序语言中,静态变量就是全局变量,而即使在有明确区分全局和静态变量的程序语言中,在编译后的代码里二者也以相同的方式获取存储空间。而今术语“静态变量”的概念则主要基于C族语言的“static”的定义。


按存储区域分,全局变量、静态全局变量和静态局部变量都存放在内存的静态存储区域,局部变量存放在内存的栈区。

按作用域分,全局变量在整个工程文件内都有效静态全局变量只在定义它的文件内有效静态局部变量只在定义它的函数内有效只是程序仅分配一次内存,函数返回后,该变量不会消失;局部变量在定义它的函数内有效,但是函数返回后失效。

通常,static的使用在三个方面:

(1)静态局部变量 (C语言)

(2)外部静态变量/函数 (C语言)

(3)静态成员变量/函数 (C++语言_类)

一.静态局部变量(一般定义在函数内部):

该情况下又可以分为三种功能:

a:隐藏

代码0:下面是a.C的内容:

char a = 'A'; // global variable
            void msg()
            {
            printf("Hello\n");
            }

下面是main.C的内容:

int main(void)
            {
            extern char a; // extern variable must be declared before use
            printf("%c ", a);
            (void)msg();
            return 0;
            }

程序的运行结果是:

A Hello

你可能会问:为什么在a.c中定义的全局变量a和函数msg能在main.c中使用?前面说过,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。此例中,a是全局变量,msg是函数,并且都没有加static前缀,因此对于另外的源文件main.c是可见的。

如果加了static,就会对其它源文件隐藏。例如在a和msg的定义前加上static,main.c就看不到它们了。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。Static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏,而对于变量,static还有下面两个作用。

b:保持变量的持久

代码1:

#include <stdio.h>

void func() {
       static int x = 0; // 在对func的三次调用中,x只进行一次初始化
        printf("%d\n", x); // 输出x的值
        x = x + 1;
}

int main(int argc, char * const argv[]) {
        func(); // 输出0
        func(); // 输出1
        func(); // 输出2
        return 0;
}

代码2:

//C中static的封装性。
//换句话说,封装在函数内的static变量只在本函数内部可见。
#include <iostream>
using namespace std;
int fun(void)
{
    //static int count = 10; //事实上此赋值语句从来没有执行过
    /*此时输出结果为:1 10;2 9;3 8;4 7......*/
    int count = 10; //事实上此赋值语句从来没有执行过
    /*此时输出结果为:1 10;2 10;3 10;4 10......*/
    return count--;
}

int count = 1; //全局变量

void main()
{
    cout<<"Output the data"<<endl;;
    for(; count <= 10; ++count)
        cout<<count<<' '<<fun()<<endl;
}

此例中,static int count = 10将count分配在静态存储区,可见区域范围是fun()函数内部,而且其值在整个程序运行时均存在,并没有因为fun函数作用域的消失而消失,而是整个函数均保留。

全局变量也是存储在静态存储区,static和全局变量的区别在哪里?他们的可见区域不同,如上面的例子,若不用static那么改用全局变量也是可以,但是用全局变量,其他函数也可见,也可用,这会破坏函数的封装性。

c:具有默认值0

在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都置0,然后把不是0的几个元素赋值。如果定义成静态的,就省去了一开始置0的操作。再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末尾加‘\0’太麻烦。如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是‘\0’。不妨做个小实验验证一下。

代码3:

#include <stdio.h>
            int a;
            int main(void)
            {
            int i;
            static char str[10];
            printf("integer: %d; string: (begin)%s(end)", a, str);
            return 0;
            }

程序的运行结果如下integer: 0; string: (begin)(end)

最后对static的三条作用做一句话总结

首先static的最主要功能是隐藏;

其次因为static变量存放在静态存储区,具有和全局变量相同的功能,所以它具备持久性和默认值0;

二.外部静态变量和函数

在C中 static有了第二种含义:用来表示不能被其它文件访问的全局变量和函数。但为了限制全局变量/函数的作用域, 函数或变量前加static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函 数)注意此时, 对于外部(全局)变量, 不论是否有static限制, 它的存储区域都是在静态存储区,生存期都是全局的. 此时的static只是起作用域限制作用, 限定作用域在本模块(文件)内部.使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。

//C中static的隐藏性。
//file1.cpp

static int varA;
int varB;
extern void funA()
{
    ……
}

static void funB()
{
    ……
}

//file2.cpp

extern int varB;         // 使用file1.cpp中定义的全局变量
extern int varA;         // 错误! varA是static类型, 无法在其他文件中使用
extern vod funA();       // 使用file1.cpp中定义的函数
extern void funB();      // 错误! 无法使用file1.cpp文件中static函数

三. 静态成员变量和函数(C++)

对此,我们可以从两个方面考虑

a:静态成员变量

在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员

使用静态数据成员可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。

静态数据成员的使用方法和注意事项如下:

1、静态数据成员在定义或说明时前面加关键字static。



2、静态成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式如下:



<数据类型><类名>::<静态数据成员名>=<值>



这表明:



(1) 初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆。



(2) 初始化时不加该成员的访问权限控制符private,public等。



(3) 初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员



3、静态数据成员是静态存储的,它是静态生存期,必须对它进行初始化。



4、引用静态数据成员时,采用如下格式:



<类名>::<静态成员名>



如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员。

我们通过下面的例子来解释C++中静态数据成员变量的问题:

#include <iostream>
using namespace std;

class Myclass
{
public:
    Myclass(int a, int b, int c);
    void GetSum();
private:
    int A, B, C;
    static int Sum; //define a private static member
};

int Myclass::Sum = 0;  //set value for the static member in the outclass

Myclass::Myclass(int a, int b, int c)
{
    A = a;
    B = b;
    C = c;
    Sum += A+B+C;
}

void Myclass::GetSum()
{
    cout<<"Sum="<<Sum<<endl;
 }

void main()
{
    Myclass M(3, 7, 10),N(14, 9, 11);
    M.GetSum(); //输出结果为54
    N.GetSum(); //输出结果为54
}

从输出结果可以看到Sum的值对M对象和对N对象都是相等的。这是因为在初始化M对象时,将M对象的三个int型数据成员的值求和后赋给了Sum,

于是Sum保存了该值。在初始化N对象时,对将N对象的三个int型数据成员的值求和后又加到Sum已有的值上,于是Sum将保存另后的值。所以,不论是通过对象M还是通过对象N来引用的值都是一样的,即为54。

b:静态成员函数

静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名



在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员。

如果静态成员函数中要引用非静态成员时,可通过对象来引用。下面通过例子来说明这一点。

//调用静态成员函数
#include  <iostream>
using namespace std;

class M
{
public:
    M(int a) { A=a; B+=a;}
    static void f1(M m);
private:
    int A;
    static int B;
};

void M::f1(M m)
{
    cout<<"A="<<m.A<<endl;
    cout<<"B="<<B<<endl;
 }

int M::B=0;
void main()
{
    M P(5);
    M Q(10);
    M::f1(P); //调用时不用对象名
    M::f1(Q);
}

输出结果为:

5

15

10

15

很容易理解:当执行M P(5)时,调用构造函数,将5赋值给A,并将5赋值给静态变量B,然后执行M Q(10),由于B为静态成员变量,

所以B的第一次执行的结果保持不变,最后输出结果。

参考链接:http://www.cnblogs.com/Kane_zzt/archive/2009/05/18/1459697.html

http://www.yesky.com/20010828/194000.shtml

原文链接: https://www.cnblogs.com/CBDoctor/archive/2012/06/25/2561426.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/53482

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月9日 上午4:49
下一篇 2023年2月9日 上午4:50

相关推荐