引用该文章,注明其出处 http://blog.youkuaiyun.com/imwkj
二: C++中的 static
static 在C/C++中有两层含义:
(1) 表示静态存储,在一个称之为静态数据区的内存空间上存储对象,而不是象一般对象在调用函数产生的堆栈上存储,这使得静态对象比一般对象拥有更长的生命期(从该对象定义处一直到程序结束)。
(2) 控制(对象或者函数)名字的可见性,被static修饰的成员仅有文件作用域和类作用域,在其他单元内是不可见的,就是说通过extern 限定的变量是不可以引用 被声明为static变量的。
1:关于静态存储 |
1:在函数内部定义静态变量(内部数据类型) void fun() { static int i = 0; //static 变量 i++; int m = 0; m++; cout << "static i: " << i << endl; cout << "local m: " << m << endl; } int main() {
fun(); fun(); }
程序运行输出结果: static i: 1 local m: 1 static i: 2 local m: 1 这样的结果很好理解,静态变量在函数调用结束的时候不会被收回,而且仅在第一次调用函数的时候初始化。对于内部类型(int,char……)如果在声明的时候没有定义初值,系统会自动赋0值
2:在函数内部定义静态对象 对于静态对象,如果在声明的时候没有赋初值,那系统只能调用其缺省的构造函数,如果类不能调用缺省构造(私有构造),则必须自行初始化,看下面程序 class obj { public: obj() { cout << "obj::obj()" << endl; } obj(int i) { cout << "obj::obj(int i = 0)" << endl; } ~obj() { cout << "obj::~obj() " << endl; } }; void fun() { static obj x; //obj类必须有缺省构造函数 static obj x2(1); //可以初始化该对象 } int main() { fun(); fun(); }
输出结果: obj::obj() obj::obj(int i = 0) obj::~obj() obj::~obj()
在函数一共调用了两次fun,但是x和x2仅进行了一次初始化。
3:类中的静态数据成员 类的静态数据成员代表了该类的所有 对象实例只使用一个共用的静态变量,无论这个类产生了多少对象,看下面的程序和输出的内容: class obj { public: obj(char* str) {
cout << str << ": obj(); size: " <<size << endl; } void set(int i =0) { if(i != 0) { size = i; } } void print() const { cout <<" size: " << size << endl; } private: static int size; };
int obj::size = 100; //必须用这样的方式初始化
void fun() {
obj x1("x1"); x1.print(); x1.set(200); x1.print();
obj x2("x2"); x2.set(300); x2.print(); x1.print();
} int main() { fun(); } 输出结果: x1: obj(); size: 100 size: 100 size: 200 x2: obj(); size: 200 size: 300 size: 300 看输出的内容很快就会明白,x1和x2使用的是同一个 size
3:类中静态常量和静态数组 明白了类中静态变量的意思那静态常量已经静态常量数组就很快能明白,只是他们的初始化方式有些不同之处,看下面的代码: class Me { static int m_length; static const int size = 100; //仅内部类型可以在类内部定义静态常量 static const int number; static int m_tips[]; static const int array[]; }; //初始化静态成员 //必须为所有类静态对象提供外部定义,包括静态数组和静态常量数组 //由于是默认内部编译联接,可以在头文件中提供这些定义 int Me::m_length = 99; const int Me::number = 200; int Me::m_tips[] = {1,2,3,4,5}; const int array[] = {1,2,3,4,5};
对于嵌套类可以有静态成员,定义方式仅需要额外的加上类限定符就可以了,函数内部的局部类不可以有静态成员。
4:类中静态成员函数 类的静态成员函数和静态数据成员类似,它服务于类的全体对象。静态成员函数由于没有接收当前对象的地址(this),它就无法访问一般的非静态成员,也就是说,static成员函数只能使用静态数据成员和静态成员函数,关于静态成员函数和类成员的关系看下面的代码:
class XX { public: XX(int m = 0) : i(m) { add(); j = i; j++; //普通成员函数可以调用静态函数和成员 } int fun() { j++; return i = j; } static int add() { return j++; // i++; 不可以这样作,理由是静态成员函数仅能访问静态数据 } static int get() { // fun(); 不可以这样作,理由是静态成员函数仅能访问静态数据 return add(); } private: int i; static int j; };
int XX::j = 0;
int main() { XX x; XX* px = &x; //静态成员函数调用方式,意义相同 x.add(); px->add(); XX::add(); } |
2:关于static 和 extern 控制名字访问 |
一般情况下在定义文件中(一般是cpp文件)定义的所有的名字是外部连接的,对于连接器该名字是可以见的,在另外的定义文件中用extern关键字可以访问这样的名字,全局变量和普通变量还有函数都可以有外部连接。 但是用static修饰的对象或者函数的名字,仅有单文件作用域,在其他文件中不可以被链接,可以说static修饰的名字仅在该名字出现的文件内可用。见下面的代码 // 文件 main.cpp #include <iostream> using namespace std; int main() { extern int xsize; //此处用extern指定 xsize在别的地方定义 cout << "xsize: " << xsize << endl; }
//文件 test.cpp int xsize = 5; //或者可以这样做 extern int xsize = 5;
而如果上面在test.cpp文件中声明xsize为static ,在main.cpp中就不能使用该xsize了,程序如下;
// 文件 main.cpp #include <iostream> using namespace std; int main() { extern int xsize; // cout << "xsize: " << xsize << endl; //错误,xsize未定义 }
//文件 test.cpp static int xsize = 5; //文件内部作用域,别处不可见
仔细对比一下不难发现他们的差别,static声明的名字不可以用extern访问,这样可以有一个好处就是确保连接时不会发生名字冲突,如果想在文件作用域中定义一个自己用的全局变量,那最好使用static声明。
|
3:关于extern 声明对象为外部连接的初始化问题 |
用extern声明的对象在该对象被使用之前必须已经被初始化,如果不是这样程序就会发生错误。见下面的代码 //a.cpp #include <fstream> std::ofstream out("outff");
//b.cpp #include <fstream> extern std::ofstream out; class Ofile { public: Ofile() { out << "file" << std::endl; } } ofile; 在b.cpp中定义的对象初始化取决于a.cpp中的out是否已经被初始化,如果初始化顺序颠倒,系统就会出现错误。咱们再来看大师们作品中提到的例子《The Annotated c++ Reference Manual》 //a.cpp extern int y; int x = y + 1;
//b.cpp extern int x; int y = x + 1; 在不同的文件中,x,y的值取决于文件被初始化的顺序,由于上面定义的是内部类型,编译器可以在上述情况下确保一个变量初始化为零。 如果 a.cpp先被初始化则: y先被初始化为0,x = 1; b.cpp 后 y = 2
如果b.cpp先被初始化 x先被初始化为0, y = 1; a.cpp后 x =2;
看着这样的运行结果很是让人郁闷,真不知道为什么要有这样的特性。其实编程语言特性愈多就越能体现它的灵活,但是这样的语言写出的代码可读性却降低了。可能程序在一个编译器下编译出的程序运行正常,但是另外一个编译器编译出的东西就会让你大为郁闷。对于上面的问题我们可以有两种方法解决。这两个技术都在《thinking in c++ 》中有详细的阐述。我在copy一下就可能会侵犯版权了J 而且也说得并不是很详细,大家还是去买本书来瞧瞧来的实在些。
|
4:关于extern “C” |
在C++中写程序有时会用到用C写的库程序,这时候就需要用到 extern “C”,这就是所谓的“替代连接”,出现这样的情况是由于C编译的函数的隐式调用方式与C++调用函数的方式是不相同的,利用下面的语句声明表示在C++中想进行C方式的连接类型
//单个函数替代连接 extern "C" float fun(int a, int b); //表示连接已经定义过并用C编译过的函数
// 一组函数替代连接 extern "C" { float fun1(int a, int b); float fun2(int x, int y); //.... other functions }
//将另外头文件中函数进行替代连接 extern "C" { #include "header.h" //...other .h files } 基本替换方式就上面的几种,在很多情况下,C++编译器设计者或者库的设计者已经预先进行了上述的处理。 |