C++中的static、const、mutable用法学习

来源:互联网 发布:微信充值琴岛通的软件 编辑:程序博客网 时间:2024/05/17 01:36

1.Static的用法

转载地址:http://blog.csdn.net/hackbuteer1/article/details/7487694

-------------------------------------------------------------------------------------------------------------

C++的static有两种用法:面向过程程序设计中的static和面向对象程序设计中的static。前者应用于普通变量和函数,不涉及类;后者主要说明static在类中的作用。
一、面向过程设计中的static
1、静态全局变量
在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。我们先举一个静态全局变量的例子,如下:

#include<iostream>using namespace std;static int n;  //定义静态全局变量void fn(){n++;cout<<n<<endl;}int main(void){n = 20;cout<<n<<endl;fn();return 0;}

静态全局变量有以下特点:

  • 该变量在全局数据区分配内存;
  • 未经初始化的静态全局变量会被程序自动初始化为0(自动变量的值是随机的,除非它被显式初始化);
  • 静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的; 

静态变量都在全局数据区分配内存,包括后面将要提到的静态局部变量。对于一个完整的程序,在内存中的分布情况如下图:
代码区全局数据区堆区栈区

  一般程序的由new产生的动态数据存放在堆区,函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。细心的读者可能会发现,Example 1中的代码中将

static int n;  //定义静态全局变量

改为

int n;  //定义全局变量

程序照样正常运行。
的确,定义全局变量就可以实现变量在文件中的共享,但定义静态全局变量还有以下好处:
       静态全局变量不能被其它文件所用;
       其它文件中可以定义相同名字的变量,不会发生冲突;
您可以将上述示例代码改为如下:

//File1#include<iostream>using namespace std;void fn();static int n;  //定义静态全局变量int main(void){n = 20;cout<<n<<endl;fn();return 0;}//File2#include<iostream>using namespace std;extern int n;void fn(){n++;cout<<n<<endl;}

编译并运行这个程序,您就会发现上述代码可以分别通过编译,但运行时出现错误。试着将

static int n;  //定义静态全局变量

改为

int n;  //定义全局变量

再次编译运行程序,细心体会全局变量和静态全局变量的区别。

2、静态局部变量
在局部变量前,加上关键字static,该变量就被定义成为一个静态局部变量。
我们先举一个静态局部变量的例子,如下:

#include<iostream>using namespace std;void fn();int main(void){fn();fn();fn();return 0;}void fn(){static int n = 10;cout<<n<<endl;n++;}

通常,在函数体内定义了一个变量,每当程序运行到该语句时都会给该局部变量分配栈内存。但随着程序退出函数体,系统就会收回栈内存,局部变量也相应失效。
  但有时候我们需要在两次调用之间对变量的值进行保存。通常的想法是定义一个全局变量来实现。但这样一来,变量已经不再属于函数本身了,不再仅受函数的控制,给程序的维护带来不便。
  静态局部变量正好可以解决这个问题。静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下一次调用,直到下次赋新值。
静态局部变量有以下特点:
    (1)该变量在全局数据区分配内存;
    (2)静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
    (3)静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
    (4)它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束;

3、静态函数
  在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。静态函数的例子:

#include<iostream>using namespace std;static void fn();   //声明静态函数int main(void){fn();return 0;}void fn()     //定义静态函数{int n = 10;cout<<n<<endl;}

定义静态函数的好处:
       静态函数不能被其它文件所用;
       其它文件中可以定义相同名字的函数,不会发生冲突;

二、面向对象的static关键字(类中的static关键字)
1、静态数据成员
在类内数据成员的声明前加上关键字static,该数据成员就是类内的静态数据成员。先举一个静态数据成员的例子。

#include<iostream>using namespace std;class Myclass{private:int a , b , c;static int sum;  //声明静态数据成员public:Myclass(int a , int b , int c);void GetSum();};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;}int main(void){Myclass M(1 , 2 , 3);M.GetSum();Myclass N(4 , 5 , 6);N.GetSum();M.GetSum();return 0;}

可以看出,静态数据成员有以下特点:

  • 对于非静态数据成员,每个类对象都有自己的拷贝。而静态数据成员被当作是类的成员。无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问。也就是说,静态数据成员是该类的所有对象所共有的。对该类的多个对象来说,静态数据成员只分配一次内存,供所有对象共用。所以,静态数据成员的值对每个对象都是一样的,它的值可以更新;
  • 静态数据成员存储在全局数据区。静态数据成员定义时要分配空间,所以不能在类声明中定义。在Example 5中,语句int Myclass::Sum=0;是定义静态数据成员;
  • 静态数据成员和普通数据成员一样遵从public,protected,private访问规则;
  • 因为静态数据成员在全局数据区分配内存,属于本类的所有对象共享,所以,它不属于特定的类对象,在没有产生类对象时其作用域就可见,即在没有产生类的实例时,我们就可以操作它;
  • 静态数据成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式为:
    <数据类型><类名>::<静态数据成员名>=<值>
  • 类的静态数据成员有两种访问形式:
    <类对象名>.<静态数据成员名> 或 <类类型名>::<静态数据成员名>
    如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员 ;
  • 静态数据成员主要用在各个对象都有相同的某项属性的时候。比如对于一个存款类,每个实例的利息都是相同的。所以,应该把利息设为存款类的静态数据成员。这有两个好处,第一,不管定义多少个存款类对象,利息数据成员都共享分配在全局数据区的内存,所以节省存储空间。第二,一旦利息需要改变时,只要改变一次,则所有存款类对象的利息全改变过来了;
  • 同全局变量相比,使用静态数据成员有两个优势:
  1. 静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性;
  2. 可以实现信息隐藏。静态数据成员可以是private成员,而全局变量不能;

2、静态成员函数

  与静态数据成员一样,我们也可以创建一个静态成员函数,它为类的全部服务而不是为某一个类的具体对象服务。静态成员函数与静态数据成员一样,都是类的内部实现,属于类定义的一部分。普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。通常情况下,this是缺省的。如函数fn()实际上是this->fn()。但是与普通函数相比,静态成员函数由于不是与任何的对象相联系,因此它不具有this指针。从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。下面举个静态成员函数的例子。

#include<iostream>using namespace std;class Myclass{private:int a , b , c;static int sum;  //声明静态数据成员public:Myclass(int a , int b , int c);static void GetSum();  //声明静态成员函数};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;}int main(void){Myclass M(1 , 2 , 3);M.GetSum();Myclass N(4 , 5 , 6);N.GetSum();Myclass::GetSum();return 0;}

关于静态成员函数,可以总结为以下几点:

  • 出现在类体外的函数定义不能指定关键字static;
  • 静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
  • 非静态成员函数可以任意地访问静态成员函数和静态数据成员;
  • 静态成员函数不能访问非静态成员函数和非静态数据成员;
  • 由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长;
  • 调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数,也可以直接使用如下格式:
    <类名>::<静态成员函数名>(<参数表>)
    调用类的静态成员函数。

关于Static用在C中的结构体中:

C语言不允许这样,C++可以,这个只是语言标准支不支持的概念,没必要深究

C中struct只是类型声明,没有内存空间的分配,而static变量是需要分配内存的。

-----------------------------------------------------------------

2.Const的用法

转载地址:http://www.360doc.com/content/09/0930/13/332670_6637297.shtml

-----------------------------------------------------------------

一、关于一般常量

声明或定义的格式如下:
const <类型说明符> <变量名> = <常量或常量表达式>; [1]
<类型说明符> const <变量名> = <常量或常量表达式>; [2]
[1]和[2]的定义是完全等价的。
例如:
整形int(或其他内置类型:float,double,char)
const int bufSize = 512;
或者
int const bufSize = 512;
因为常量在定义后就不能被修改,所以定义时必须初始化。
bufSize = 128;   // error:attempt to write to const object
const string cntStr = "hello!"; // ok:initialized
const i, j = 0; // error: i is uninitialized const
非const变量默认为extern。
const 对象默认为文件的局部变量。要使const变量能够在其他的文件中访问,必须显式地指定它为extern。
例如:
const int bufSize = 512;        // 作用域只限于定义此变量的文件
extern const int bufSize = 512; // extern用于扩大作用域,作用域为整个源程序(只有extern 位于函数外部时,才可以含有初始化式)

二、关于数组及结构体

声明或定义的格式如下:
const <类型说明符> <数组名>[<大小>]…… [1]
<类型说明符> const <数组名>[<大小>]…… [2]
[1]和[2]的定义是完全等价的。
例如:
整形int(或其他内置类型:float,double,char)
const int cntIntArr[] = {1,2,3,4,5};
或者
int const cntIntArr[] = {1,2,3,4,5};
struct SI
{
    int i1;
    int i2;
};
const SI s[] = {{1,2},{3,4}};
// 上面的两个const都是变量集合,编译器会为其分配内存,所以不能在编译期间使用其中的值(例如:int temp[cntIntArr[2] ],这样的话编译器会报告不能找到常量表达式)

三、关于引用

声明或定义的格式如下:
const <类型说明符> &<变量名> = …… [1]
<类型说明符> const &<变量名> = …… [2]
[1]和[2]的定义是完全等价的。
例如:
const int i = 128;
const int &r = i;(或者 int const &r = i;)
const 引用就是指向const 对象的引用。
普通引用不能绑定到const 对象,但const 引用可以绑定到非const 对象。
const int ii = 456;
int &rii = ii; // error
int jj = 123;
const int &rjj = jj; // ok
非const 引用只能绑定到与该引用同类型的对象。
const 引用则可以绑定到不同但相关的类型的对象或绑定到右值。
例如:
1.const int &r = 100;     // 绑定到字面值常量
2.int i = 50;
const int &r2 = r + i; // 引用r绑定到右值
3.double dVal = 3.1415;
const int &ri = dVal;   // 整型引用绑定到double 类型
编译器会把以上代码转换成如下形式的编码:
int temp = dVal;      // create temporary int from double
const int &ri = temp; // bind ri to that temporary

四、关于指针

1.指向const 对象的指针(指针所指向的内容为常量)
声明或定义的格式如下(定义时可以不初始化):
const <类型说明符> *<变量名> …… [1]
<类型说明符> const *<变量名> …… [2]
[1]和[2]的定义是完全等价的。
例如:
const int i = 100;
const int *cptr = &i;
或者
int const *cptr = &i; [cptr 是指向int 类型的const 对象的指针]
允许把非const 对象的地址赋给指向const 对象的指针,例如:
double dVal = 3.14;          // dVal is a double; its value can be change
const double *cdptr = &dVal; // ok;but can't change dVal through cdptr
不能使用指向const 对象的指针修改基础对象。然而如果该指针指向的是一个没const 对象(如cdptr),可用其他方法修改其所指向的对象。
如何将一个const 对象合法地赋给一个普通指针???
例如:
const double dVal = 3.14;
double *ptr = &dVal; // error
double *ptr = const_cast<double*>(&dVal);
// ok: const_cast是C++中标准的强制转换,C语言使用:double *ptr = (double*)&dVal;
2.const 指针(指针本身为常量)
声明或定义的格式如下(定义时必须初始化):
<类型说明符> *const <变量名> = ……
例如:
int errNumb = 0;
int iVal = 10;
int *const curErr = &errNumb; [curErr 是指向int 型对象的const 指针]
指针的指向不能被修改。
curErr = &iVal; // error: curErr is const
指针所指向的基础对象可以修改。
*curErr = 1; // ok:reset value of the object(errNumb) which curErr is bind
3.指向const 对象的const 指针(指针本身和指向的内容均为常量
声明或定义的格式如下(定义时必须初始化):
const <类型说明符> *const <变量名> = ……
例如:
const double pi = 3.14159;
const double dVal = 3.14;
const double *const pi_ptr = &pi; [pi_ptr 是指向double 类型的const 对象的const 指针]
指针的指向不能被修改。
pi_ptr = &dVal; // error: pi_ptr is const
指针所指向的基础对象也不能被修改。
*pi_ptr = dVal; // error: pi is const

五、关于一般函数

1.修饰函数的参数
class A;
void func1(const int i); // i不能被修改
void func3 (const A &rA);        // rA所引用的对象不能被修改
void func2 (const char *pstr); // pstr所指向的内容不能被修改
2.修饰函数的返回值
返回值:const int func1(); // 此处返回int 类型的const值,意思指返回的原函数里的变量的初值不能被修改,但是函数按值返回的这个变量被制成副本,能不能被修改就没有了意义,它可以被赋给任何的const或非const类型变量,完全不需要加上这个const关键字。
[*注意*]但这只对于内部类型而言(因为内部类型返回的肯定是一个值,而不会返回一个变量,不会作为左值使用,否则编译器会报错),对于用户自定义类型,返回值是常量是非常重要的(后面在类里面会谈到)。
返回引用:const int &func2(); // 注意千万不要返回局部对象的引用,否则会报运行时错误:因为一旦函数结束,局部对象被释放,函数返回值指向了一个对程序来说不再有效的内存空间。
返回指针:const int *func3(); // 注意千万不要返回指向局部对象的指针,因为一旦函数结束,局部对象被释放,返回的指针变成了指向一个不再存在的对象的悬垂指针。

六、关于类

class A
{
public:
    void func();
    void func() const;
    const A operator+(const A &) const;
private:
    int num1;
    mutable int num2;
    const size_t size;
};
1.修饰成员变量
const size_t size; // 对于const的成员变量,[1]必须在构造函数里面进行初始化;[2]只能通过初始化成员列表来初始化;[3]试图在构造函数体内对const成员变量进行初始化会引起编译错误。
例如:
A::A(size_t sz):size(sz) // ok:使用初始化成员列表来初始化
{
}
A::A(size_t sz)
2.修饰类成员函数
void func() const; // const成员函数中不允许对数据成员进行修改,如果修改,编译器将报错。如果某成员函数不需要对数据成员进行修改,最好将其声明为const 成员函数,这将大大提高程序的健壮性。
const 为函数重载提供了一个参考
class A
{
public:
    void func();        // [1]:一个函数
    void func() const; // [2]:上一个函数[1]的重载
    ……
};
A a(10);
a.func(); // 调用函数[1]
const A b(100);
b.func(); // 调用函数[2]
如何在const成员函数中对成员变量进行修改???
下面提供几种方式(只提倡使用第一种,其他方式不建议使用)
(1)标准方式:mutable
class A
{
public:
    A::A(int i):m_data(i){}
    void SetValue(int i){ m_data = i; }
private:
    mutable int m_data; // 这里处理
};
(2)强制转换:static_cast
class A
{
public:
    A::A(int i):m_data(i){}
    void SetValue(int i)
    { static_cast<int>(m_data) = i; } // 这里处理
private:
    int m_data;
};
(3)强制转换:const_cast
class A
{
public:
    A::A(int i):m_data(i){}
    void SetValue(int i)
    { const_cast<A*>(this)->m_data = i; } // 这里处理
private:
    int m_data;
};
(4)使用指针:int *
class A
{
public:
    A::A(int i):m_data(i){}
    void SetValue(int i)
    { *m_data = i; } // 这里处理
private:
    int *m_data;
};
(5)未定义的处理方式
class A
{
public:
    A::A(int i):m_data(i){}
    void SetValue(int i)
    { int *p = (int*)&m_data; *p = i } // 这里处理
private:
    int m_data;
};
注意:这里虽然说可以修改,但结果是未定义的,避免使用!
3.修饰类对象
const A a; // 类对象a 只能调用const 成员函数,否则编译器报错。
4.修饰类成员函数的返回值
const A operator+(const A &) const; // 前一个const 用来修饰重载函数operator+的返回值,可防止返回值作为左值进行赋值操作。
例如:
A a;
A b;
A c;
a + b = c; // errro: 如果在没有const 修饰返回值的情况下,编译器不会报错。

七、使用const的一些建议  

1.要大胆的使用const,这将给你带来无尽的益处,但前提是你必须搞清楚原委;
2.要避免最一般的赋值操作错误,如将const变量赋值;
3.在参数中使用const应该使用引用或指针,而不是一般的对象实例,原因同上;
4.const在成员函数中的三种用法(参数、返回值、函数)要很好的使用;
5.不要轻易的将函数的返回值类型定为const;
6.除了重载操作符外一般不要将返回值类型定为对某个对象的const引用;

八、cons有什么主要的作用?

1.可以定义const常量,具有不可变性。
例如:
const int Max=100;
int Array[Max];
2.便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。
例如:
void f(const int i) { .........}
编译器就会知道i是一个常量,不允许修改;
3.可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。
同宏定义一样,可以做到不变则已,一变都变!如(1)中,如果想修改Max的内容,只需要:const int Max=you want;即可!
4.可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。
还是上面的例子,如果在函数体内修改了i,编译器就会报错;
例如: 
void f(const int i) { i=10;//error! }
5.为函数重载提供了一个参考。
class A
{
     ......
    void f(int i)       {......} file://一个函数
    void f(int i) const {......} file://上一个函数的重载
    ......
};
6.可以节省空间,避免不必要的内存分配。
例如:
#define PI 3.14159         file://常量宏
const doulbe Pi=3.14159; file://此时并未将Pi放入ROM中
......
double i=Pi;               file://此时为Pi分配内存,以后不再分配!
double I=PI;               file://编译期间进行宏替换,分配内存
double j=Pi;               file://没有内存分配
double J=PI;               file://再进行宏替换,又一次分配内存!
const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。
7.提高了效率。
编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
{
    size = sz; // error:试图在构造函数体内对const成员变量进行初始化
}

 

mutable关键字

  关键字mutable是C++中一个不常用的关键字,他只能用于类的非静态和非常量数据成员我们知道一个对象的状态由该对象的非静态数据成员决定,所以随着数据成员的改变,对像的状态也会随之发生变化!

  如果一个类的成员函数被声明为const类型,表示该函数不会改变对象的状态,也就是该函数不会修改类的非静态数据成员.但是有些时候需要在该类函数中对类的数据成员进行赋值.这个时候就需要用到mutable关键字了

  例如:

  class Demo  {  public:  Demo(){}  ~Demo(){}  public:  bool getFlag() const  {  m_nAccess++;  return m_bFlag;  }  private:  int m_nAccess;  bool m_bFlag;  };   int main()  {  return 0;  }

      编译上面的代码会出现 error C2166: l-value specifies const object的错误说明在const类型的函数中改变了类的非静态数据成员.

  这个时候需要使用mutable来修饰一下要在const成员函数中改变的非静态数据成员m_nAccess,代码如下:

    class Demo  {  public:  Demo(){}  ~Demo(){}  public:  bool getFlag() const  {  m_nAccess++;  return m_bFlag;  }  private:  mutable int m_nAccess;  bool m_bFlag;  };   int main()  {  return 0;  }  

  这样再重新编译的时候就不会出现错误了!

原创粉丝点击