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,extern,static,对应两种存储期:自动存储期和静态存储期。 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类型,则返回为错指针。