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类型,则返回为错指针。
原文地址:https://www.cnblogs.com/timssd/p/4104812.html