CC++中static的⽤法全局变量与局部变量
1.什么是static?
  static 是C/C++中很常⽤的修饰符,它被⽤来控制变量的存储⽅式和可见性。
1.1static的引⼊
  我们知道在函数内部定义的变量,当程序执⾏到它的定义处时,编译器为它在栈上分配空间,函数在栈上分配的空间在此函数执⾏结束时会释放掉,这样就产⽣了⼀个问题: 如果想将函数中此变量的值保存⾄下⼀次调⽤时,如何实现?最容易想到的⽅法是定义为全局的变量,但定义⼀个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不只受此函数控制)。static关键字则可以很好的解决这个问题。
另外,在C++中,需要⼀个数据对象为整个类⽽⾮某个对象服务,同时⼜⼒求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见时,可将其定义为静态数据。
1.2静态数据的存储
  全局(静态)存储区:分为DATA段和BSS段。DATA段(全局初始化区)存放初始化的全局变量和静态
变量;BSS段(全局未初始化区)存放未初始化的全局变量和静态变量。程序运⾏结束时⾃动释放。其中BBS段在程序执⾏之前会被系统⾃动清0,所以未初始化的全局变量和静态变量在程序执⾏之前已经为0。存储在静态数据区的变量会在程序刚开始运⾏时就完成初始化,也是唯⼀的⼀次初始化。
在C++中static的内部实现机制:静态数据成员要在程序⼀开始运⾏时就必须存在。因为函数在程序运⾏中被调⽤,所以静态数据成员不能在任何函数内分配空间和初始化。
这样,它的空间分配有三个可能的地⽅,⼀是作为类的外部接⼝的头⽂件,那⾥有类声明;⼆是类定义的内部实现,那⾥有类的成员函数定义;三是应⽤程序的main()函数前的全局数据声明和定义处。
静态数据成员要实际地分配空间,故不能在类的声明中定义(只能声明数据成员)。类声明只声明⼀个类的“尺⼨和规格”,并不进⾏实际的内存分配,所以在类声明中写成定义是错误的。它也不能在头⽂件中类声明的外部定义,因为那会造成在多个使⽤该类的源⽂件中,对其重复定义。
static被引⼊以告知编译器,将变量存储在程序的静态存储区⽽⾮栈上空间,静态数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。
优势:可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储⼀
处,供所有对象共⽤。静态数据成员的值对每个对象都是⼀样,但它的值是可以更新的。只要对静态数据成员的值更新⼀次,保证所有对象存取更新后的相同的值,这样可以提⾼时间效率。
2.在C/C++中static的作⽤
2.1总的来说:
(1)⽣命周期:在修饰变量的时候,static修饰的静态局部变量只执⾏初始化⼀次,⽽且延长了局部变量的⽣命周期,直到程序运⾏结束以后才释放,但不改变作⽤域。⽐如修饰函数中存放在栈空间的数组。如果不想让这个数组在函数调⽤结束释放可以使⽤static修饰。
(2)可见性:static修饰全局变量或函数时,这个全局变量只能在本⽂件中访问,不能在其它⽂件中访问,即便是extern外部声明也不可以。这个函数也只能在本⽂件中调⽤,不能被其他⽂件调⽤。
(3)存储⽅式:Static修饰的变量存放在全局数据区的静态变量区,包括全局静态变量和局部静态变量,都在全局数据区分配内存。初始化的时候⾃动初始化为0。
(4)考虑到数据安全性(当程序想要使⽤全局变量的时候应该先考虑使⽤static)。
2.2静态变量与普通变量
静态全局变量有以下特点:
(1)静态变量都在全局数据区分配内存,包括后⾯将要提到的静态局部变量;
(2)未经初始化的静态全局变量会被程序⾃动初始化为0(在函数体内声明的⾃动变量的值是随机的,除⾮它被显式初始化,⽽在函数体外被声明的⾃动变量也会被初始化为0);
(3)静态全局变量在声明它的整个⽂件都是可见的,⽽在⽂件之外是不可见的。
优点:静态全局变量不能被其它⽂件所⽤;其它⽂件中可以定义相同名字的变量,不会发⽣冲突。
(4)全局变量和全局静态变量的区别
1)全局变量是不显式⽤static修饰的全局变量,全局变量默认是有外部链接性的,是整个⼯程,在⼀个⽂件内定义的全局变量,在另⼀个⽂件中,通过extern 全局变量名的声明,就可以使⽤全局变量。
2)全局是显式⽤static修饰的全局变量,作⽤域是声明此变量所在的⽂件,其他的⽂件即使⽤extern声明也不能使⽤。
2.3静态局部变量有以下特点:
实例化类和实例化对象
(1)该变量在全局数据区分配内存;
(2)静态局部变量在程序执⾏到该对象的声明处时被⾸次初始化,即以后的不再进⾏初始化;
(3)静态局部变量⼀般在声明处初始化,如果没有显式初始化,会被程序⾃动初始化为0;
(4)它始终驻留在全局数据区,直到程序运⾏结束。但其为局部作⽤域,当定义它的函数或语句块结束时,其作⽤域随之结束。
  ⼀般程序把新产⽣的动态数据存放在堆区,函数内部的存放在栈区。⾃动变量⼀般会随着函数的退出⽽释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出⽽释放空间。
看下⾯的例⼦:
1 //example:
2 #include <stdio.h>
3 #include <stdlib.h>
4 int k1 = 1;
5 int k2;
6 static int k3 = 2;
7 static int k4;
8 int main()
9 {
10    static int m1 = 2, m2;
11    int i = 1;
12    char*p;
13    char str[10] = "hello";
14    char*q = "hello";
15    p = (char *)malloc(100);
16    free(p);
17    printf("栈区-变量地址    i:%p\n", &i);
18    printf("栈区-变量地址  p:%p\n", &p);
19    printf("栈区-变量地址 str:%p\n", str);
20    printf("栈区-变量地址  q:%p\n", &q);
21    printf("堆区地址-动态申请:%p\n", p);
22    printf("全局外部有初值 k1:%p\n", &k1);
23    printf("  外部⽆初值 k2:%p\n", &k2);
24    printf("静态外部有初值 k3:%p\n", &k3);
25    printf("  外静⽆初值 k4:%p\n", &k4);
26    printf("  内静态有初值 m1:%p\n", &m1);
27    printf("  内静态⽆初值 m2:%p\n", &m2);
28    printf("    ⽂字常量地址:%p, %s\n", q, q);
29    printf("      程序区地址:%p\n", &main);
30    return 0;
31 }
3.1特别的,在C++中:
static关键字最基本的⽤法是:
1、被static修饰的变量属于类变量,可以通过类名.变量名直接引⽤,⽽不需要new出⼀个类来
2、被static修饰的⽅法属于类⽅法,可以通过类名.⽅法名直接引⽤,⽽不需要new出⼀个类来
被static修饰的变量、被static修饰的⽅法统⼀属于类的静态资源,是类实例之间共享的,换⾔之,⼀处变、处处变。
  在C++中,静态成员是属于整个类的⽽不是某个对象,静态成员变量只存储⼀份供所有对象共⽤。所以在所有对象中都可以共享它。使⽤静态成员变量实现多个对象之间的数据共享不会破坏隐藏的原则,保证了安全性还可以节省内存。
静态成员的定义或声明要加个关键static。静态成员可以通过双冒号来使⽤即<;类名>::<;静态成员名>。
3.2静态类相关
1 example1:通过类名调⽤静态成员函数和⾮静态成员函数
2  class Point
3    {
4    public:
5        void init()
6        {
7        }
8        static void output()
9        {
10        }
11    };
12    void main()
13    {
14        Point::init();
15        Point::output();
16    }
报错: 'Point::init' : illegal call of non-static member function
结论1:不能通过类名来调⽤类的⾮静态成员函数。
1 //example2:通过类的对象调⽤静态成员函数和⾮静态成员函数
2  class Point
3    {
4    public:
5        void init()
6        {
7        }
8        static void output()
9        {
10        }
11    };
12 void main()
13 {
14    Point pt;
15    pt.init();
16    pt.output();
17 }
编译通过。
结论2:类的对象可以使⽤静态成员函数和⾮静态成员函数。
1 //example3:在类的静态成员函数中使⽤类的⾮静态成员
2 #include <stdio.h>
3 class Point
4 {
5 public:
6    void init()
7    {
8    }
9    static void output()
10    {
11        printf("%d\n", m_x);
12    }
13 private:
14    int m_x;
15 };
16 void main()
17 {
18    Point pt;
19    pt.output();
20 }
编译出错:error C2597: illegal reference to data member 'Point::m_x' in a static member function
  因为静态成员函数属于整个类,在类实例化对象之前就已经分配空间了,⽽类的⾮静态成员必须在类实例化对象后才有内存空间,所以这个调⽤就出错了,就好⽐没有声明⼀个变量却提前使⽤它⼀样。
结论3:静态成员函数中不能引⽤⾮静态成员。
1 //example4:在类的⾮静态成员函数中使⽤类的静态成员
2 class Point
3 {
4 public:
5    void init()
6    {
7        output();
8    }
9    static void output()
10    {
11    }
12 };
13 void main()
14 {
15    Point pt;
15    Pt.init();
16    pt.output();
17 }
编译通过。
结论4:类的⾮静态成员函数可以调⽤⽤静态成员函数,但反之不能。
1    //example5:使⽤类的静态成员变量
2  #include <stdio.h>
3    class Point
4    {
5    public:
6        Point()
7        {
8            m_nPointCount++;
9        }
10        ~Point()
11        {
12            m_nPointCount--;
13        }
14        static void output()
15        {
16            printf("%d\n", m_nPointCount);
17        }
18    private:
19        static int m_nPointCount;
20    };
21    void main()
22    {
23        Point pt;
24        pt.output();
25    }
按Ctrl+F7编译⽆错误,按F7⽣成EXE程序时报链接错误
error LNK2001: unresolved external symbol "private: static int Point::m_nPointCount" (?m_nPointCount@Point@@0HA)
这是因为类的静态成员变量在使⽤前必须先初始化。
在main()函数前加上int Point::m_nPointCount = 0;
再编译链接⽆错误,运⾏程序将输出1。
结论5:类的静态成员变量必须先初始化再使⽤。
  思考总结:静态资源属于类,但是是独⽴于类存在的。从J类的加载机制的⾓度讲,静态资源是类初始化的时候加载的,⽽⾮静态资源是类实例化对象的时候加载的。类的初始化早于类实例化对象,⽐如Class.forName(“xxx”)⽅法,就是初始化了⼀个类,但是并没有实例化对象,只是加载这个类的静态资源罢了。所以对于静态资源来说,它是不可能知道⼀个类中有哪些⾮静态资源的;但是对于⾮静态资源来说就不⼀样了,由于它是实例化对象出来之后产⽣的,因此属于类的这些东西它都能认识。所以上⾯的⼏个问题答案就很明确了:
1)静态⽅法能不能引⽤⾮静态资源?不能,实例化对象的时候才会产⽣的东西,对于初始化后就存在的静态资源来说,根本不认识它。
2)静态⽅法⾥⾯能不能引⽤静态资源?可以,因为都是类初始化的时候加载的,⼤家相互都认识。
3)⾮静态⽅法⾥⾯能不能引⽤静态资源?可以,⾮静态⽅法就是实例⽅法,那是实例化对象之后才产⽣的,那么属于类的内容它都认识。
  (static修饰类:这个⽤得相对⽐前⾯的⽤法少多了,static⼀般情况下来说是不可以修饰类的,如果static要修饰⼀个类,说明这个类是⼀个静态内部类(注意static只能修饰⼀个内部类),也就是匿名内部类。像线程池 ThreadPoolExecutor中的四种拒绝机制CallerRunsPolicy、AbortPolicy、DiscardPolicy、 DiscardOldestPolicy 就是静态内部类。静态内部类相关内容会在写内部类的时候专门
讲到。)
3.3总结:
(1)静态成员函数中不能调⽤⾮静态成员。
(2)⾮静态成员函数中可以调⽤静态成员。因为静态成员属于类本⾝,在类的对象产⽣之前就已经存在了,所以在⾮静态成员函数中是可以调⽤静态成员的。
(3)静态成员变量使⽤前必须先初始化(如int MyClass::m_nNumber = 0;),否则会在linker时出错。
参考:
⼀般总结:在类中,static可以⽤来修饰静态数据成员和静态成员⽅法
静态数据成员
(1)静态数据成员可以实现多个对象之间的数据共享,它是类的所有对象的共享成员,它在内存中只占⼀份空间,如果改变它的值,则各对象中这个数据成员的值都被改变。
(2)静态数据成员是在程序开始运⾏时被分配空间,到程序结束之后才释放,只要类中指定了静态数
据成员,即使不定义对象,也会为静态数据成员分配空间。
(3)静态数据成员可以被初始化,但是只能在类体外进⾏初始化,若未对静态数据成员赋初值,则编译器会⾃动为其初始化为0
(4)静态数据成员既可以通过对象名引⽤,也可以通过类名引⽤。
静态成员函数
(1)静态成员函数和静态数据成员⼀样,他们都属于类的静态成员,⽽不是对象成员。
(2)⾮静态成员函数有this指针,⽽静态成员函数没有this指针。
(3)静态成员函数主要⽤来⽅位静态数据成员⽽不能访问⾮静态成员。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。