C++中static用法
本文为个人学习笔记,参考《C++ Primer(中文第五版)》和《王道程序员求职宝典》
本文分为两个部分:不考虑类、类中static的作用
一、不考虑类,static的作用
1、对其他文件隐藏
当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都有全局可见性。同时编译两个源文件:a.cpp和main.cpp,在a.cpp中有以下内容:
1 #include<cstdio> 2 3 char a='A'; //全局变量 4 void msg() printf("Hello\n");
则在a.cpp中定义的全局变量a和函数msg能在main.cpp中使用。这是因为未加static前缀的全局变量都具有全局可见性,其他源文件也能访问。若加了static,就会对其他源文件隐藏了。如在a和msg的定义前加上static,main.cpp就不能访问了。
这样可以在不同的文件中定义同名函数和同名变量,而不用担心命名冲突。
2、static的第二作用是默认初始化为0。
包括未初始化的全局静态变量和局部静态变量。另外未初始化的全局变量也具备之一属性,因为为初始化的全局变量与未初始化的静态变量存储在同一区域内(BSS,全局(静态)存储区,BSS的特点是在程序执行之前BSS会自动清0)。
3、static的第三个作用是保持局部变量内容的持久。
函数内的局部变量,当调用时就存在,退出函数时就销毁,但静态局部变量虽然在函数内定义,但静态局部变量始终存在,也就是说它的生存期为整个源程序,其特点是只进行一次初始化且具有“记忆性”。
值得注意的是:虽然局部静态变量的生存周期为整个源程序,但其作用域仍与局部变量相同,即只能在定义该变量的函数内使用该变量。退出该函数后,尽管该变量嗨继续存在,但不能使用它了。
1 #include<iostream> 2 #include<cstddef> 3 4 using namespace std; 5 6 size_t count_call() 7 { 8 static size_t ctr = 0; 9 return ++ctr; 10 } 11 12 int main() 13 { 14 for (size_t i = 0; i != 10; ++i) 15 { 16 cout << count_call() << " "; 17 } 18 /*未加下一行之前输出1到10,加了之后显示出错:未声明的标识符ctr*/ 19 //cout << ctr << endl; 20 21 return 0; 22 }
二、类中static的作用
有时候类需要它的一些成员与类本身直接相关,而不是与类的各个对象保持关联,我们可以通过在成员的声明之前加上关键字static使得其与类关联在一起。即,表示属于一个类而不是属于此类的任何特定对象的变量和函数。
类的静态成员存在任何对象之外,对象中不包含任何与静态成员函数有关的数据。和其他成员一样,静态成员可以是public的或private的。什么意思了?下面结合例子我们分静态数据成员和静态成员函数来说
1 class Account 2 { 3 public: 4 void calculate() {amount+=amount*interestRate;} 5 static double rate() {return interestRate;} 6 static void rate(double); 7 private: 8 std::string owner; 9 double amount; 10 static double interestRate; 11 static double initRate(); 12 }
1、静态数据成员
通常,非static数据成员存在与类类型的每个对象中,而static数据成员独立与该类的任意对象而存在,即每个static数据成员是与类关联的对象,并不是与该类的对象相关联,通俗讲,静态数据成员不属于类的任一对象。也就是说,当某个类的实例修改了该静态成员变量,其修改值为该类的其他所有实例所见。
(1)静态数据成员定义
因为静态数据成员不属于类的任一对象,所以它们并不是创建类对象时被定义,这意味着不是有类的构造函数初始化。一般来说,static数据成员要在类定义体的外部定义,和其他对象一样,一个静态数据成员只能定义一次。另外,静态数据成语定义在任何函数之外,因为一旦它被定义,就将一直存在于程序的整个生命周期。通俗点讲,对静态数据成员就是不能在类中定义,要类似全局变量一样,单独定义。(有一种例外,见下)
1 /*定义并初始化一个静态成员*/ 2 double Account::interestTrate=initRate();
(2)静态成员的类中初始化
通常情况下,类的static成员(数据和函数)不应该在类的内部初始化,然而我们可以为静态成员提供const整数类型的类内初始值,不过要求静态成员必须是字面值常量类型的constexpr。
Tip:要想确保对象只定义一次,最好的办法是吧静态数据成员的定义与其他非内联函数的定义放在同一个文件中。
2、静态成员函数
因为普通的成员函数总是具体的属于某个类的具体对象,所以普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身。但是静态成员函数由于不予任何的对象相关联,因此它不具有this指针。因而无法访问类对象的非静态成员函数,也无法访问静态成员函数,只能调用其余的静态成员函数与访问静态数据成员。另外,因为static成员不是任何对象的组成部分,所以static成员函数不能被声明为const。
(1)静态类成员函数既可以定义在类的内部可以在内的外部,当定义在类的外部是,不能重复使用static关键字,该关键字只能出现在类内部的声明语句中。
1 void Account::rate(double newRate) 2 { 3 interestRate=newRate; 4 }
另外,static成员函数也不能被声明为虚函数、volatile。
(2)静态成员函数可以总结为:
a)静态成员之间可以相互访问,包括静态成员函数访问静态数据和访问静态数据函数。静态成员函数不能访问非静态成员函数和非静态数据成员,非静态成员函数可以任意地访问静态成员函数和静态数据成员。
b)由于没有this指针额外开销,因此静态成员函数与类的非静态成员函数相比速度上会有少许增加。
三、静态成员和普通成员对比
静态成员和普通成员的一个区别是,我们可以使用静态成员作为默认实参:
1 class Screen 2 { 3 public: 4 /*bkground表示一个在类中稍后定义的静态成员*/ 5 Screen &clean(char=bkground); 6 private: 7 static const char bkground; 8 };
非静态数据成员不能作为默认实参,因为它的值本身属于对象的一部分。