• static用法


    static法则:
        A、若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;
        B、若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;
        C、设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题;
    
    
    
    static用来控制变量的存储方式和可见性
    函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现? 最容易想到的方法是定义一个全局的变量,但定义为一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅受此 函数控制)。
    需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见。
    static的内部机制:
    静态数据成员要在程序一开始运行时就必须存在。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。
    这样,它的空间分配有三个可能的地方,一是作为类的外部接口的头文件,那里有类声明;二是类定义的内部实现,那里有类的成员函数定义;三是应用程序的main()函数前的全局数据声明和定义处。
    静态数据成员要实际地分配空间,故不能在类的声明中定义(只能声明数据成员)。类声明只声明一个类的“尺寸和规格”,并不进行实际的内存分配,所以在类声 明中写成定义是错误的。它也不能在头文件中类声明的外部定义,因为那会造成在多个使用该类的源文件中,对其重复定义。
    static被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间,静态数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。
    static的优势:
    可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的 值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。
    引用静态数据成员时,采用如下格式:
    <类名>::<静态成员名>
    如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员。
    PS:
    (1)类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致
    了它仅能访问类的静态数据和静态成员函数。
    (2)不能将静态成员函数定义为虚函数。
    (3)由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊,变量地址是指向其数据类型的指针 ,函数地址类型是一个“nonmember函数指针”。
    (4)由于静态成员函数没有this指针,所以就差不多等同于nonmember函数,结果就产生了一个意想不到的好处:成为一个callback函数,使得我们得以将C++和C-based X Window系统结合,同时也成功的应用于线程函数身上。
    (5)static并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问时间,节省了子类的内存空间。
    (6)静态数据成员在<定义或说明>时前面加关键字static。
    (7)静态数据成员是静态存储的,所以必须对它进行初始化。
    (8)静态成员初始化与一般数据成员初始化不同:
    •    初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆; 
    •    初始化时不加该成员的访问权限控制符private,public等; 
    •    初始化时使用作用域运算符来标明它所属类; 
    所以我们得出静态数据成员初始化的格式:<数据类型><类名>::<静态数据成员名>=<值>
    (9)为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:我们说静态成员为父类和子类共享,但我们有 重复定义了静态成员,这会不会引起错误呢?不会,我们的编译器采用了一种绝妙的手法:name-mangling 用以生成唯一的标志。
    补充:new delete[],基本类型的对象没有析构函数(例如 int , char ),所以回收基本类型组成的数组空间 delete delete[] 都是应该可以如: int p = new int[10], delete p 和delete[]p 都可 。但是对于类对象数组(如string strArr = new string[10]),只能 delete[]。对 new 的单个对象,只能 delete 不能 delete[] 回收空间 。
    
    
    1. 全局静态变量
       在全局变量之前加上关键字static,全局变量就被定义成为一个全局静态变量。
       1)内存中的位置:静态存储区(静态存储区在整个程序运行期间都存在)
       2)初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化)
       3)作用域:全局静态变量在声明他的文件之外是不可见的。准确地讲从定义之处开始到文件结尾。
    看下面关于作用域的程序:
    //teststatic1.c 
    void display();
    extern int n; 
    int main()
    {
      n = 20;
      printf("%d
    ",n);
      display();
      return 0;
    }
     
    //teststatic2.c 
    static int n;   //定义全局静态变量,自动初始化为0,仅在本文件中可见
    void display()
    {
      n++;
      printf("%d
    ",n);
    }
     
    文件分别编译通过,但link的时候teststatic1.c中的变量n找不到定义,产生错误。
     
    定义全局静态变量的好处:
    <1>不会被其他文件所访问,修改
    <2>其他文件中可以使用相同名字的变量,不会发生冲突。
    2. 局部静态变量
      在局部变量之前加上关键字static,局部变量就被定义成为一个局部静态变量。
      1)内存中的位置:静态存储区
      2)初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化)
      3)作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。
      注:当static用来修饰局部变量的时候,它就改变了局部变量的存储位置,从原来的栈中存放改为静态存储区。但是局部静态变量在离开作用域之后,并没有被销毁,而是仍然驻留在内存当中,直到程序结束,只不过我们不能再对他进行访问。
          当static用来修饰全局变量的时候,它就改变了全局变量的作用域(在声明他的文件之外是不可见的),但是没有改变它的存放位置,还是在静态存储区中。
    3. 静态函数
      在函数的返回类型前加上关键字static,函数就被定义成为静态函数。
      函数的定义和声明默认情况下是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。
      例如:
    //teststatic1.c
    void display();
    static void staticdis(); 
    int main()
    {
      display();
      staticdis();
      renturn 0;
    }
     
    //teststatic2.c
    void display()
    {
      staticdis();
      printf("display() has been called 
    ");
    }
     
    static void staticdis()
    {
      printf("staticDis() has been called
    ");
    }
     
    文件分别编译通过,但是连接的时候找不到函数staticdis()的定义,产生错误。
     
    定义静态函数的好处:
    <1> 其他文件中可以定义相同名字的函数,不会发生冲突
    <2> 静态函数不能被其他文件所用。
     
    存储说明符auto,register,externstatic,对应两种存储期:自动存储期和静态存储期。
     
    auto和register对应自动存储期。具有自动存储期的变量在进入声明该变量的程序块时被建立,它在该程序块活动时存在,退出该程序块时撤销。
    关键字extern和static用来说明具有静态存储期的变量和函数。用static声明的局部变量具有静态存储持续期(static storage duration),或静态范围(static extent)。虽然他的值在函数调用之间保持有效,但是其名字的可视性仍限制在其局部域内。静态局部对象在程序执行到该对象的声明处时被首次初始化。
    由于static变量的以上特性,可实现一些特定功能。
    1. 统计次数功能
    声明函数的一个局部变量,并设为static类型,作为一个计数器,这样函数每次被调用的时候就可以进行计数。这是统计函数被调用次数的最好的办法,因为这个变量是和函数息息相关的,而函数可能在多个不同的地方被调用,所以从调用者的角度来统计比较困难。代码如下:
     
    void count();
    int main()
    {
     int i;
     for (i = 1; i <= 3; i++)
      count();
      return 0;
    }
    void count()
    {
     static num = 0;
     num++;
     printf(" I have been called %d",num,"times
    ");
    }
    输出结果为:
    I have been called 1 times.
    
    
    
    C语言程序可以看成由一系列外部对象构成,这些外部对象可能是变量或函数。而内部变量是指定义在函数内部的函数参数及变量。外部变量定义在函数之外,因此可以在许多函数中使用。由于C语言不允许在一个函数中定义其它函数,因此函数本身只能是“外部的”。
          由于C语言代码是以文件为单位来组织的,在一个源程序所有源文件中,一个外部变量或函数只能在某个文件中定义一次,而其它文件可以通过extern声明来访问它(定义外部变量或函数的源文件中也可以包含对该外部变量的extern声明)。
          而static则可以限定变量或函数为静态存储。如果用static限定外部变量与函数,则可以将该对象的作用域限定为被编译源文件的剩余部分。通过static限定外部对象,可以达到隐藏外部对象的目的。因而,static限定的变量或函数不会和同一程序中其它文件中同名的相冲突。如果用static限定内部变量,则该变量从程序一开始就拥有内存,不会随其所在函数的调用和退出而分配和消失。
       C语言中使用静态函数的好处:
    1.          静态函数会被自动分配在一个一直使用的存储区,直到退出应用程序实例,避免了调用函数时压栈出栈,速度快很多。 
    2.          关键字“static”,译成中文就是“静态的”,所以内部函数又称静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件。 使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,因为同名也没有关系。 
    c语言中static的语义
    1.static变量:
    1).局部
    a.静态局部变量在函数内定义,生存期为整个源程序,但作用域与自动变量相同,只能在定义该变量的函数内使用。退出该函数后, 尽管该变量还继续存在,但不能使用它。
    b.对基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予0值。而对自动变量不赋初值,则其值是不定的。
    2).全局
    全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。但是他们的作用域,非静态全局 变量的作用域是整个源程序(多个源文件可以共同使用); 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。
    2.static函数(也叫内部函数)
    只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用。区别于一般的非静态函数(外部函数) 
        static在c里面可以用来修饰变量,也可以用来修饰函数。
             先看用来修饰变量的时候。变量在c里面可分为存在全局数据区、栈和堆里。其实我们平时所说的堆栈是栈而不包含对,不要弄混。
            int a ;
            main()
            {
                 int b ; 
                 int c* = (int *)malloc(sizeof(int));
            }
            a是全局变量,b是栈变量,c是堆变量。
            static对全局变量的修饰,可以认为是限制了只能是本文件引用此变量。有的程序是由好多.c文件构成。彼此可以互相引用变量,但加入static修饰之后,只能被本文件中函数引用此变量。
            static对栈变量的修饰,可以认为栈变量的生命周期延长到程序执行结束时。一般来说,栈变量的生命周期由OS管理,在退栈的过程中,栈变量的生命也就结束了。但加入static修饰之后,变量已经不在存储在栈中,而是和全局变量一起存储。同时,离开定义它的函数后不能使用,但如再次调用定义它的函数时,它又可继续使用, 而且保存了前次被调用后留下的值。
           static对函数的修饰与对全局变量的修饰相似,只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用。 
          static 声明的变量在C语言中有两方面的特征:
      1)、变量会被放在程序的全局存储区中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。 
      2)、变量用static告知编译器,自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。
      问题:Static的理解
      关于static变量,请选择下面所有说法正确的内容:
      A、若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;
      B、若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;
      C、设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题;
      D、静态全局变量过大,可那会导致堆栈溢出。 
      答案与分析:
      对于A,B:根据本篇概述部分的说明b),我们知道,A,B都是正确的。
      对于C:根据本篇概述部分的说明a),我们知道,C是正确的(所谓的函数重入问题,下面会详细阐述)。
      对于D:静态变量放在程序的全局数据区,而不是在堆栈中分配,所以不可能导致堆栈溢出,D是错误的。
      因此,答案是A、B、C。
      问题:不可重入函数
      曾经设计过如下一个函数,在代码检视的时候被提醒有bug,因为这个函数是不可重入的,为什么?
    unsigned int sum_int( unsigned int base )
    {
     unsigned int index;
     static unsigned int sum = 0; // 注意,是static类型的。 
     for (index = 1; index <= base; index++)
     {
      sum += index;
     }
     return sum;
    } 
      答案与分析:
      所谓的函数是可重入的(也可以说是可预测的),即:只要输入数据相同就应产生相同的输出。
      这个函数之所以是不可预测的,就是因为函数中使用了static变量,因为static变量的特征,这样的函数被称为:带“内部存储器”功能的的函数。因此如果我们需要一个可重入的函数,那么,我们一定要避免函数中使用static变量,这种函数中的static变量,使用原则是,能不用尽量不用。
      将上面的函数修改为可重入的函数很简单,只要将声明sum变量中的static关键字去掉,变量sum即变为一个auto 类型的变量,函数即变为一个可重入的函数。
      当然,有些时候,在函数中是必须要使用static变量的,比如当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。
    
    
    
    
    嵌入式操作系统中static 和const的解释
    static 和 const的解释 
          static 是c++中很常用的修饰符,它被用来控制变量的存储方式和可见性,下面我将从 static 
    修饰符的产生原因、作用谈起,全面分析static 修饰符的实质。  
    static 的两大作用: 
    一、控制存储方式: 
      static被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间。 
      1、引出原因:函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,大
    家知道,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数
    中此变量的值保存至下一次调用时,如何实现?  
    最容易想到的方法是定义一个全局的变量,但定义为一个全局变量有许多缺点,最明显的缺点是破坏了
    此变量的访问范围(使得在此函数中定义的变量,不仅仅受此函数控制)。 
      2、 解决方案:因此c++ 中引入了static,用它来修饰变量,它能够指示编译器将此变量在程序的
    静态存储区分配空间保存,这样即实现了目的,又使得此变量的存取范围不变。 
    二、控制可见性与连接类型 : 
      static还有一个作用,它会把变量的可见范围限制在编译单元中,使它成为一个内部连接,这时,
    它的反义词为”extern”. 
      static作用分析总结:static总是使得变量或对象的存储形式变成静态存储,连接方式变成内部连
    接,对于局部变量(已经是内部连接了),它仅改变其存储方式;对于全局变量(已经是静态存储了)
    ,它仅改变其连接类型。 
    类中的static成员: 
    一、出现原因及作用: 
      1、需要在一个类的各个对象间交互,即需要一个数据对象为整个类而非某个对象服务。 
      2、同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见。 
      类的static成员满足了上述的要求,因为它具有如下特征:有独立的存储区,属于整个类。 
    二、注意: 
      1、对于静态的数据成员,连接器会保证它拥有一个单一的外部定义。静态数据成员按定义出现的先
    后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化
    的反顺序。 
      2、类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致了它仅能访问类
    的静态数据和静态成员函数。 
    const 是c++中常用的类型修饰符,但我在工作中发现,许多人使用它仅仅是想当然尔,这样,有时也会
    用对,但在某些微妙的场合,可就没那么幸运了,究其实质原由,大多因为没有搞清本源。故在本篇中
    我将对const进行辨析。溯其本源,究其实质,希望能对大家理解const有所帮助,根据思维的承接关系
    ,分为如下几个部分进行阐述。 
    c++中为什么会引入const 
      c++的提出者当初是基于什么样的目的引入(或者说保留)const关键字呢?,这是一个有趣又有益
    的话题,对理解const很有帮助。 
    1. 大家知道,c++有一个类型严格的编译系统,这使得c++程序的错误在编译阶段即可发现许多,从而
    使得出错率大为减少,因此,也成为了c++与c相比,有着突出优点的一个方面。 
    2. c中很常见的预处理指令 #define variablename variablevalue 可以很方便地进行值替代,这种值
    替代至少在三个方面优点突出: 
      一是避免了意义模糊的数字出现,使得程序语义流畅清晰,如下例: 
      #define user_num_max 107 这样就避免了直接使用107带来的困惑。 
      二是可以很方便地进行参数的调整与修改,如上例,当人数由107变为201时,进改动此处即可,  
      三是提高了程序的执行效率,由于使用了预编译器进行值替代,并不需要为这些常量分配存储空间
    ,所以执行的效率较高。 
      鉴于以上的优点,这种预定义指令的使用在程序中随处可见。 
    3. 说到这里,大家可能会迷惑上述的1点、2点与const有什么关系呢?,好,请接着向下 
    看来: 
      预处理语句虽然有以上的许多优点,但它有个比较致命的缺点,即,预处理语句仅仅只是简单值替
    代,缺乏类型的检测机制。这样预处理语句就不能享受c++严格类型检查的好处,从而可能成为引发一系
    列错误的隐患。 
    4.好了,第一阶段结论出来了: 
    结论: const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点。 
    现在它的形式变成了: 
    const datatype variablename = variablevalue ; 
    为什么const能很好地取代预定义语句?  
    const 到底有什么大神通,使它可以振臂一挥取代预定义语句呢? 
    1. 首先,以const 修饰的常量值,具有不可变性,这是它能取代预定义语句的基础。 
    2. 第二,很明显,它也同样可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改
    。 
    3. 第三,c++的编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它
    成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高,同时,这也是它取代预
    定义语句的重要基础。这里,我要提一下,为什么说这一点是也是它能取代预定义语句的基础,这是因
    为,编译器不会去读存储的内容,如果编译器为const分配了存储空间,它就不能够成为一个编译期间的
    常量了。 
    4. 最后,const定义也像一个普通的变量定义一样,它会由编译器对它进行类型的检测,消除了预定义
    语句的隐患。 
    const 使用情况分类详析 
    1.const 用于指针的两种情况分析: 
     int const *a;  file://a可变,*a不可变 
     int *const a;  file://a不可变,*a可变  
      分析:const 是一个左结合的类型修饰符,它与其左侧的类型修饰符和为一个类型修饰符,所以,
    int const 限定 *a,不限定a。int *const 限定a,不限定*a。 
    2.const 限定函数的传递值参数: 
     void fun(const int var); 
      分析:上述写法限定参数在函数体中不可被改变。由值传递的特点可知,var在函数体中的改变不会
    影响到函数外部。所以,此限定与函数的使用者无关,仅与函数的编写者有关。 
    结论:最好在函数的内部进行限定,对外部调用者屏蔽,以免引起困惑。如可改写如下: 
    void fun(int var){ 
    const int & varalias = var; 
    varalias .... 
    ..... 
    }  
    3.const 限定函数的值型返回值: 
    const int fun1();  
    const myclass fun2(); 
     分析:上述写法限定函数的返回值不可被更新,当函数返回内部的类型时(如fun1),已经是一个数值
    ,当然不可被赋值更新,所以,此时const无意义,最好去掉,以免困惑。当函数返回自定义的类型时(
    如fun2),这个类型仍然包含可以被赋值的变量成员,所以,此时有意义。 
    4. 传递与返回地址: 此种情况最为常见,由地址变量的特点可知,适当使用const,意义昭然。 
    5. const 限定类的成员函数: 
    class classname { 
     public: 
      int fun() const; 
     ..... 
    } 
      注意:采用此种const 后置的形式是一种规定,亦为了不引起混淆。在此函数的声明中和定义中均
    要使用const,因为const已经成为类型信息的一部分。 
    获得能力:可以操作常量对象。 
    失去能力:不能修改类的数据成员,不能在函数中调用其他不是const的函数。 
      在本篇中,const方面的知识我讲的不多,因为我不想把它变成一本c++的教科书。我只是想详细地
    阐述它的实质和用处. 我会尽量说的很详细,因为我希望在一种很轻松随意的气氛中说出自己的某些想
    法,毕竟,编程也是轻松,快乐人生的一部分。有时候,你会惊叹这其中的世界原来是如此的精美。
    flw 回复于:2003-08-20 12:36:53  
    已收入精华。
    
    quanliking 回复于:2003-08-20 19:37:11  
    谢谢!
    
    jobman 回复于:2003-08-21 00:13:43  
    有几点不同意见: 
    [code:1:3186d5e9be]预处理语句虽然有以上的许多优点,但它有个比较致命的缺点,即,预处理语句仅
    仅只是简单值替代,缺乏类型的检测机制。这样预处理语句就不能享受c++严格类型检查的好处,从而可
    能成为引发一系列错误的隐患。 [/code:1:3186d5e9be] 
    宏定义是在预处理时完成的,可是依然要通过编译器,不可能躲过类型 
    检查,例如: 
    [code:1:3186d5e9be]#define    MY_MACRO    "fsdfsdfdsdf" 
    int func1( int parameter ) 
    { 
          ..... 
    } 
    main( ) 
    { 
          func1( MY_MACRO ); 
    }[/code:1:3186d5e9be] 
    这段代码肯定无法编译通过,所以这段描述不成立。 
    宏定义的使用技巧也是博大精深的,绝非 const 能替代, 
    当然在具有值替换的场合,用 const 来代替宏定义是个不错的 
    选择,可也仅此而已,而且用宏定义并不会引入类型隐患。 
    const 的引入其主要目的并不在于代替宏定义,这多少有点牵强了。
    
    小飞爱使申华 回复于:2003-08-21 03:23:02  
    [quote:fb2fd29f5e="quanliking"]谢谢![/quote:fb2fd29f5e]      
    马甲穿错了吧,^_^
    
    clion 回复于:2003-08-21 20:46:49  
    这个帖子很好
    
    aero 回复于:2003-08-21 21:00:31  
    [quote:e4afadc072="jobman"] 
    这段代码肯定无法编译通过,所以这段描述不成立。 
    宏定义的使用技巧也是博大精深的,绝非 const 能替代, 
    当然在具有值替换的场合,用 const 来代替宏定义是个不错的 
    选择,可也仅此而已,而且用宏定义并不会..........[/quote:e4afadc072]      
    呵呵,两位说的都对。但是编译器对类型的检查是发生在int func1( int parameter ) 的,而不是在
    #define    MY_MACRO    "fsdfsdfdsdf"  
    。所以原文说的还是没错,jobman的意思也对。
    
    HappyWin 回复于:2003-08-24 16:46:11  
    精华,收藏先
    
    天上的小星星 回复于:2004-02-10 11:44:07  
    好贴
    
    whyglinux 回复于:2004-04-10 03:51:16  
    [quote:edc740afb5="yuxq"]... 
    5. const 限定类的成员函数: 
    class classname { 
     public: 
      int fun() const; 
     ..... 
    } 
      注意:采用此种const 后置的形式是一种规定,亦为了不引起混淆。在此函数的声明中和定义中均
    要使用const,因为const已经成为类型信息的一部分。 
    获得能力:可以操作常量对象。 
    失去能力:不能修改类的数据成员,不能在函数中调用其他不是const的函数。 
    [/quote:edc740afb5] 
    楼主的这篇文章值得仔细阅读。但是,我觉得上述“const 限定类的成员函数”这一部分写得比较简略
    ,特别是其中“注意”后面的文字,更是使人不知所云,所以想对这一部分做一些补充说明。 
    类的成员函数后面加 const,表明这个函数不会对这个类对象的数据成员(准确地说是非静态数据成员
    )作任何改变。在设计类的时候,一个原则就是对于不改变数据成员的成员函数都要在后面加 const,
    而对于改变数据成员的成员函数不能加 const。所以 const 关键字对成员函数的行为作了更加明确的限
    定:有 const 修饰的成员函数(指 const 放在函数参数表的后面,而不是在函数前面或者参数表内)
    ,只能读取数据成员,不能改变数据成员;没有 const 修饰的成员函数,对数据成员则是可读可写的。
    除此之外,在类的成员函数后面加 const 还有什么好处呢?楼主告诉我们的:“获得能力:可以操作常
    量对象”,其实应该是常量(即 const)对象可以调用 const 成员函数,而不能调用非const修饰的函
    数。正如非const类型的数据可以给const类型的变量赋值一样,反之则不成立。 
    请看下面一个完整的例子,然后我再作一些说明。 
    [code:1:edc740afb5] 
    #include <iostream>  
    #include <string>  
    using namespace std;  
      
    class Student {  
    public:  
      Student() {}  
      Student( const string& nm, int sc = 0 )  
        : name( nm ), score( sc ) {}  
      
      void set_student( const string& nm, int sc = 0 )  
      {  
        name = nm;  
        score = sc;  
      }  
      
      const string& get_name() const  
      {  
        return name;  
      }  
      
      int get_score() const  
      {  
        return score;  
      }  
      
    private:  
      string name;  
      int score;  
    };  
    // output student's name and score  
    void output_student( const Student& student )  
    {  
      cout << student.get_name() << "	";  
      cout << student.get_score() << endl;  
    }  
      
    int main()  
    {  
      Student stu( "Wang", 85 );  
      output_student( stu );  
    } 
    [/code:1:edc740afb5] 
    设计了一个类 Student,数据成员有 name 和 score,有两个构造函数,有一个设置成员数据函数 
    set_student(),各有一个取得 name 和 score 的函数 get_name() 和 get_score()。请注意 
    get_name() 和 get_score() 后面都加了 const,而 set_student() 后面没有(也不能有const)。 
    首先说一点题外话,为什么 get_name() 前面也加 const。如果没有前后两个 const 的话,get_name() 
    返回的是对私有数据成员 name 的引用,所以通过这个引用可以改变私有成员 name 的值,如 
    [code:1:edc740afb5]  Student stu( "Wang", 85 ); 
      stu.get_name() = "Li"; 
    [/code:1:edc740afb5] 
    即把 name 由原来的 "Wang" 变成了 "Li",而这不是我们希望的发生的。所以在 get_name() 前面加 
    const 避免这种情况的发生。 
            那么,get_name() 和 get_score() 这两个后面应该加 const的成员函数,如果没有 const 
    修饰的话可不可以呢?回答是可以!但是这样做的代价是:const对象将不能再调用这两个非const成员
    函数了。如 
    [code:1:edc740afb5]const string& get_name(); // 这两个函数都应该设成 const 型 
    int get_score(); 
    void output_student( const Student& student )  
    {  
      cout << student.get_name() << "	"; // 如果 get_name() 和 get_score() 是非const成员函数,
    这一句和下一句调用是错误的 
      cout << student.get_score() << endl;  
    } 
    [/code:1:edc740afb5] 
            由于参数student表示的是一个对const Student型对象的引用,所以 student 不能调用非
    const成员函数如 set_student()。如果 get_name() 和 get_score() 成员函数也变成非const型,那么
    上面的 student.get_name() 和 student.get_score() 的使用就是非法的,这样就会给我们处理问题造
    成困难。 
            因此,我们没有理由反对使用const,该加const时就应该加上const,这样使成员函数除了非
    const的对象之外,const对象也能够调用它。 
    
    
    
    
    
    一、面向过程设计中的static
    1、静态全局变量
    在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。我们先举一个静态全局变量的例子,如下:
    //Example 1
    #include <iostream.h>
    void fn();
    static int n; //定义静态全局变量
    void main()
    {
        n=20;
        cout<<n<<endl;
        fn();
    }
    void fn()
    {
        n++;
        cout<<n<<endl;
    }
    静态全局变量有以下特点:
    •    该变量在全局数据区分配内存;
    •    未经初始化的静态全局变量会被程序自动初始化为0(自动变量的值是随机的,除非它被显式初始化);
    •    静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的;  
    静态变量都在全局数据区分配内存,包括后面将要提到的静态局部变量。对于一个完整的程序,在内存中的分布情况如下图:
     
    代码区
    全局数据区
    堆区
    栈区
      一般程序的由new产生的动态数据存放在堆区,函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。细心的读者可能会发现,Example 1中的代码中将
        static int n; //定义静态全局变量
    改为
        int n; //定义全局变量
    程序照样正常运行。
    的确,定义全局变量就可以实现变量在文件中的共享,但定义静态全局变量还有以下好处:
    •    静态全局变量不能被其它文件所用;
    •    其它文件中可以定义相同名字的变量,不会发生冲突;
    您可以将上述示例代码改为如下:
    //Example 2
    //File1
    #include <iostream.h>
    void fn();
    static int n; //定义静态全局变量
    void main()
    {
        n=20;
        cout<<n<<endl;
        fn();
    }
    //File2
    #include <iostream.h>
    extern int n;
    void fn()
    {
        n++;
        cout<<n<<endl;
    }
    编译并运行Example 2,您就会发现上述代码可以分别通过编译,但运行时出现错误。 试着将
    static int n; //定义静态全局变量
    改为
    int n; //定义全局变量
    再次编译运行程序,细心体会全局变量和静态全局变量的区别。
    2、静态局部变量
    在局部变量前,加上关键字static,该变量就被定义成为一个静态局部变量。
    我们先举一个静态局部变量的例子,如下:
    //Example 3
    #include <iostream.h>
    void fn();
    void main()
    {
        fn();
        fn();
        fn();
    }
    void fn()
    {
        static n=10;
        cout<<n<<endl;
        n++;
    }
      通常,在函数体内定义了一个变量,每当程序运行到该语句时都会给该局部变量分配栈内存。但随着程序退出函数体,系统就会收回栈内存,局部变量也相应失效。
      但有时候我们需要在两次调用之间对变量的值进行保存。通常的想法是定义一个全局变量来实现。但这样一来,变量已经不再属于函数本身了,不再仅受函数的控制,给程序的维护带来不便。
      静态局部变量正好可以解决这个问题。静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下一次调用,直到下次赋新值。
    静态局部变量有以下特点:
    •    该变量在全局数据区分配内存;
    •    静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
    •    静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
    •    它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束;
    3、静态函数
      在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。
    静态函数的例子:
    //Example 4
    #include <iostream.h>
    static void fn();//声明静态函数
    void main()
    {
        fn();
    }
    void fn()//定义静态函数
    {
        int n=10;
        cout<<n<<endl;
    }
    定义静态函数的好处:
    •    静态函数不能被其它文件所用;
    •    其它文件中可以定义相同名字的函数,不会发生冲突;
    二、面向对象的static关键字(类中的static关键字)
    1、静态数据成员
    在类内数据成员的声明前加上关键字static,该数据成员就是类内的静态数据成员。先举一个静态数据成员的例子。
    //Example 5
    #include <iostream.h>
    class Myclass
    {
    public:
        Myclass(int a,int b,int c);
        void GetSum();
    private:
        int a,b,c;
        static int Sum;//声明静态数据成员
    };
    int Myclass::Sum=0;//定义并初始化静态数据成员
    Myclass::Myclass(int a,int b,int c)
    {
        this->a=a;
        this->b=b;
        this->c=c;
        Sum+=a+b+c;
    }
    void Myclass::GetSum()
    {
        cout<<"Sum="<<Sum<<endl;
    }
    void main()
    {
        Myclass M(1,2,3);
        M.GetSum();
        Myclass N(4,5,6);
        N.GetSum();
        M.GetSum();
    }
    可以看出,静态数据成员有以下特点:
    •    对于非静态数据成员,每个类对象都有自己的拷贝。而静态数据成员被当作是类的成员。无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问。也就是说,静态数据成员是该类的所有对象所共有的。对该类的多个对象来说,静态数据成员只分配一次内存,供所有对象共用。所以,静态数据成员的值对每个对象都是一样的,它的值可以更新;
    •    静态数据成员存储在全局数据区。静态数据成员定义时要分配空间,所以不能在类声明中定义。在Example 5中,语句int Myclass::Sum=0;是定义静态数据成员;
    •    静态数据成员和普通数据成员一样遵从public,protected,private访问规则;
    •    因为静态数据成员在全局数据区分配内存,属于本类的所有对象共享,所以,它不属于特定的类对象,在没有产生类对象时其作用域就可见,即在没有产生类的实例时,我们就可以操作它;
    •    静态数据成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式为:
    •    <数据类型><类名>::<静态数据成员名>=<值>
    •    类的静态数据成员有两种访问形式:
    •    <类对象名>.<静态数据成员名> 或 <类类型名>::<静态数据成员名>
    •    如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员 ;
    •    静态数据成员主要用在各个对象都有相同的某项属性的时候。比如对于一个存款类,每个实例的利息都是相同的。所以,应该把利息设为存款类的静态数据成员。这有两个好处,第一,不管定义多少个存款类对象,利息数据成员都共享分配在全局数据区的内存,所以节省存储空间。第二,一旦利息需要改变时,只要改变一次,则所有存款类对象的利息全改变过来了;
    •    同全局变量相比,使用静态数据成员有两个优势:
    1.    静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性;
    2.    可以实现信息隐藏。静态数据成员可以是private成员,而全局变量不能;
    2、静态成员函数
       与静态数据成员一样,我们也可以创建一个静态成员函数,它为类的全部服务而不是为某一个类的具体对象服务。静态成员函数与静态数据成员一样,都是类的内部实现,属于类定义的一部分。 普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。通常情况下,this是缺省的。如函数fn()实际上是this->fn()。但是与普通函数相比,静态成员函数由于不是与任何的对象相联系,因此它不具有this指针。从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。 下面举个静态成员函数的例子。
    //Example 6
    #include <iostream.h>
    class Myclass
    {
    public:
        Myclass(int a,int b,int c);
        static void GetSum();/声明静态成员函数
    private:
        int a,b,c;
        static int Sum;//声明静态数据成员
    };
    int Myclass::Sum=0;//定义并初始化静态数据成员
    Myclass::Myclass(int a,int b,int c)
    {
        this->a=a;
        this->b=b;
        this->c=c;
        Sum+=a+b+c; //非静态成员函数可以访问静态数据成员
    }
    void Myclass::GetSum() //静态成员函数的实现
    {
    //    cout<<a<<endl; //错误代码,a是非静态数据成员
        cout<<"Sum="<<Sum<<endl;
    }
    void main()
    {
        Myclass M(1,2,3);
        M.GetSum();
        Myclass N(4,5,6);
        N.GetSum();
        Myclass::GetSum();
    }
    关于静态成员函数,可以总结为以下几点:
    •    出现在类体外的函数定义不能指定关键字static;
    •    静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
    •    非静态成员函数可以任意地访问静态成员函数和静态数据成员;
    •    静态成员函数不能访问非静态成员函数和非静态数据成员;
    •    由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长;
    •    调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数,也可以直接使用如下格式:
    •    <类名>::<静态成员函数名>(<参数表>)
    •    调用类的静态成员函数。
    
    
    水滴石穿C语言之static辨析
    1、概述
      static 声明的变量在C语言中有两方面的特征:
      1)、变量会被放在程序的全局存储区中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。
    
    
    
      2)、变量用static告知编译器,自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。
      2、问题:Static的理解
      关于static变量,请选择下面所有说法正确的内容:
      A、若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;
      B、若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;
      C、设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题;
      D、静态全局变量过大,可那会导致堆栈溢出。 
      答案与分析:
      对于A,B:根据本篇概述部分的说明b),我们知道,A,B都是正确的。
      对于C:根据本篇概述部分的说明a),我们知道,C是正确的(所谓的函数重入问题,下面会详细阐述)。
      对于D:静态变量放在程序的全局数据区,而不是在堆栈中分配,所以不可能导致堆栈溢出,D是错误的。
      因此,答案是A、B、C。
      3、问题:不可重入函数
      曾经设计过如下一个函数,在代码检视的时候被提醒有bug,因为这个函数是不可重入的,为什么?
    
    unsigned int sum_int( unsigned int base )
    {
     unsigned int index;
     static unsigned int sum = 0; // 注意,是static类型的。 
     for (index = 1; index <= base; index++)
     {
      sum += index;
     }
     return sum;
    }
    
      答案与分析:
      所谓的函数是可重入的(也可以说是可预测的),即:只要输入数据相同就应产生相同的输出。
      这个函数之所以是不可预测的,就是因为函数中使用了static变量,因为static变量的特征,这样的函数被称为:带“内部存储器”功能的的函数。因此如果我们需要一个可重入的函数,那么,我们一定要避免函数中使用static变量,这种函数中的static变量,使用原则是,能不用尽量不用。
      将上面的函数修改为可重入的函数很简单,只要将声明sum变量中的static关键字去掉,变量sum即变为一个auto 类型的变量,函数即变为一个可重入的函数。
      当然,有些时候,在函数中是必须要使用static变量的,比如当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。
  • 相关阅读:
    POJ 2749
    POJ 3422
    POJ 3621
    SQLSERVER 2005 重新安装过程中的疑难解决
    可遇不可求的Question之MySqlClient访问字段返回System.Byte[]篇
    可遇不可求的Question之odbc驱动无法加载
    可遇不可求的BUG之采用MYSQL odbc 3.51访问数据库返回值缺失
    可遇不可求的Bug之Convert.Int32(<未定义的值>)等于0
    可遇不可求的Question之数据库操作超时篇
    可遇不可求的Question之数据库 'tempdb' 的日志已满。
  • 原文地址:https://www.cnblogs.com/timssd/p/4104812.html
Copyright © 2020-2023  润新知