C/C++:static关键字的作用

一、C语言中的static
静态全局变量:在全局变量之前加上关键字static,该全局变量就被定义成了一个静态全局变量

特别注意:

1.静态全局变量在内存中的存储位置:静态区(全局区)。 [注]:静态区(全局区)在整个程序运行期间都存在

2.静态全局变量的初始化:未经初始化的静态全局变量会被程序自动初始化为0。 [注]:在C语言中,未经初始化的全局变量的值是任意的;而在C++中,未经初始化的全局变量也会被程序自动初始化为0

3.全局变量和静态全局变量的存储方式是一样的,但不同的是全局变量在整个源代码中都是可以使用的,而静态全局变量只能在当前文件中使用。比如一个程序有3个文件a.cpp、b.cpp和c.cpp,如果在文件a.cpp中定义了一个全局变量x,那么变量x在文件a.cpp、b.cpp和c.cpp中都可以使用;但如果在文件a.cpp中定义了一个静态全局变量y,那么变量y只能在文件a.cpp中使用文件b.cpp和文件c.cpp都不可以使用该静态全局变量y

例子

源文件file1.cpp:

int value1=10;//全局变量value1
static int value2;//静态全局变量value2,默认初始化为0

源文件file2.cpp:

extern int value1; //成功:期望使用文件file1中定义的全局变量value1
extern int value2; //错误:期望使用文件file1中定义的静态全局变量value2,但value2是static类型,只能在file1中使用,无法被其他文件使用

4.使用静态全局变量的好处:

a.将一个全局变量定义为静态全局变量,相当于让该全局变量对该程序的其他源文件进行了“隐藏”,也就是说该静态全局变量只能在定义它的源文件中使用,而程序的其他源文件则无法访问该变量

b.程序中的其他源文件中可以使用与该静态全局变量同名的变量,而不会发生冲突

5.在C/C++程序中,如果一个全局变量仅仅在单个源文件中使用,则可以将该全局变量修改为静态全局变量,以降低模块间的耦合度

静态局部变量:在局部变量之前加上关键字static,该局部变量就被定义成了一个静态局部变量

特别注意:

1.静态局部变量在内存中的存储位置:静态区(全局区)。[注]:普通局部变量在内存中的存储位置为栈区

2.静态局部变量的初始化:未经初始化的静态局部变量会被程序自动初始化为0。[注]:在C/C++中,未经初始化的普通局部变量值是任意的

3.static并没有改变局部变量的作用域。换句话说就是静态局部变量的作用域仍是局部作用域,当定义它的函数或语句块结束的时候,其作用域也就随之结束。 [注]:与之相反,static改变了全局变量的作用域(在声明它的文件之外是不可见的)

4.static虽然没有改变局部变量的作用域,但它改变了局部变量在内存中的存储位置(而static修饰的全局变量在内存中的存储位置则没有被static改变),即从原来的栈区中存放改变为静态区(全局区)中存放。因此静态局部变量在离开其作用域时,并没有像一般局部变量那样被销毁,而是仍驻留在内存中的静态区(全局区)中,直到程序结束,只不过在其离开自己的作用域时,我们无法再对其进行访问。

5.在C/C++中,如果全局变量仅有单个函数访问,则可以将该全局变量改为该函数的静态局部变量,以降低模块间的耦合度

• 静态函数:在函数的返回值类型前加上关键字static,该函数就被定义成了一个静态函数

特别注意:

1.和静态全局变量相似,静态函数只能在声明它的源文件中可见,而不能被该程序的其他源文件所使用。

源文件file1.cpp:

extern void fun1(){......} //定义函数fun1
static void fun2(){......} //定义静态函数fun2
......
源文件file2.cpp:

extern void fun1();//成功:期望使用文件file1.cpp中的函数fun1
extern void fun2();//错误:期望使用文件file1.cpp中的静态函数fun2,但fun2是static类型函数,只能在file1中使用,无法被其他文件使用
2.使用静态函数的好处:

a.在该程序的其他源文件中可以定义和使用与该静态函数同名的函数,而不会发生冲突

b.将一个普通函数定义为函数,相当于让该函数对该程序的其他源文件进行了“隐藏”,也就是说该静态函数只能在定义它的源文件中使用,而程序的其他源文件则无法调用该函数。

c.静态函数会被自动分配在一个一直使用的存储区域,直到程序结束。这样避免了普通函数在调用时的进栈出栈操作,从而提高程序的运行速度

• 总结:static在C语言中的作用:

——隐藏功能:static修饰的全局变量和普通函数只能在定义它们的源文件中使用,相当于对程序中的其他源文件进行了隐藏

——默认初始化功能:static修饰的变量,不管是全局变量还是局部变量,在用户没有显式初始化它们的条件下,系统都会将他们默认初始化为0

——延长“寿命”功能:static修饰的局部变量存储在内存的静态区(全局区)而非栈区,而静态区中存储的内容只会在程序结束时被OS释放,因此当static修饰的局部变量离开自己的作用域时,其不会像一般局部变量那样被系统销毁,而是一直驻留在系统的内存中,直到程序结束。

二、C++中的static
• 静态数据成员:自定义类中被static修饰的成员变量

特别注意:

1.静态数据成员在内存中的存储位置:静态区(全局区)

2.使用:

语法:类名::静态数据成员名
3.静态数据成员属于类。换句话说就是类的静态数据成员为其所有对象所共享,不管该类有多少对象,静态数据成员只有一份存储于公共的内存中

 

• 静态成员函数:自定义类中被static修饰的成员函数

 特别注意

1.在C++的自定义类中,一个常规的成员函数声明往往意味着:

  • 该成员函数能够访问其所在类的私有部分

  • 该成员函数位于其所在类的作用域之中

  • 该成员函数必须由一个对象去激活从而被调用(通过this指针来实现)

如果将该类的一个成员函数声明为static,则可以只让它拥有前两种性质

2.使用:

语法1:类名::静态成员函数名
语法2:对象名.静态成员函数名

3.和静态数据成员一样,静态成员函数也属于类,类的静态成员函数为其所有对象所共享。

4.类中的普通成员函数既可以访问一般成员变量,也可以访问静态数据成员;而类中的静态成员函数只能访问类中的静态数据成员。

[解释]:编译器在编译程序中的自定义类时,会在类中的每个普通成员函数中插入this指针,而对static修饰的静态成员函数则不会插入this指针。由于静态成员函数中没有this指针,因此静态成员函数无法在其函数体内访问非静态数据成员;相反,由于静态数据成员属于类而非特定对象,故不需要this指针也可以在函数体内访问静态数据成员,因此不管是普通成员函数还是静态成员函数,都可以在其函数体内访问静态数据成员。

5.同理,普通成员函数中可以调用静态成员方法,而静态成员方法中不能调用普通成员函数

例子:

#include<iostream>
#include<string>
using namespace std;
class Student{
public:
static int count; //静态数据成员
Student(){
count++;
}
Student(string name,int age):Name(name),Age(age){
count++;
}
~Student(){
count--;
}
void show1();
static void show2();
private:
string Name;
int Age;

};
int Student::count=0; //对count进行初始化
/*
编译器对成员函数show1的改写:
void Student::show1(Student* const this){
cout<<"Name:"<<this->Name<<endl;
cout<<"Age:"<<this->Age<<endl;
cout<<"Count:"<<count<<endl;
show2();
*/
void Student::show1(){
cout<<"调用了方法show1"<<endl;
cout<<"Name:"<<Name<<endl;
cout<<"Age:"<<Age<<endl;
cout<<"Count:"<<count<<endl;
show2();//在普通成员函数中调用静态成员函数
}
void Student::show2(){ //编译器不会为静态成员函数添加this指针
cout<<"调用了方法show2"<<endl;
//cout<<"Name:"<<Name<<endl; //错误:静态成员函数不能访问非静态数据成员
//cout<<"Age:"<<Age<<endl; //错误:静态成员函数不能访问非静态数据成员
//show1(); //错误:静态成员函数中不能调用普通成员函数 (只能以this->show1()的形式调用)
cout<<"Count:"<<count<<endl;
}
int main(){
Student stu1("Tomwenxing",23);
Student stu2("Ellen",22);
Student stu3("Jack",24);
stu3.show1();
cout<<"----------分界线----------------"<<endl;
stu2.show2();//可以通过对象来调用静态成员函数
cout<<"----------分界线----------------"<<endl;
Student::show2() ;//也可以通过类来调用静态成员函数
return 0;
}

结果:

 

 还有一种解释:

两个部分:不考虑类、考虑类中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 };
复制代码

 非静态数据成员不能作为默认实参,因为它的值本身属于对象的一部分。

 

静态成员函数编译时出现 static成员"Cannot declare member function ...to have static linkage"错误

解决方案

在.cpp文件中去掉static关键字

static的用法有好几种,在类中成员函数的声明使用static关键字则是规定说该成员函数为该类所有实例所共享也就是所谓的"one-per-class",而在.cpp文件中使用static关键字的作用也是说该函数或变量仅仅只有该文件可以访问,这就与第一个用法冲突了。不过在这种情况时,定义static的类成员变量不需要再使用static关键字了,或者直接在.h文件中声明+定义

posted @ 2021-06-08 15:08  konglingbin  阅读(963)  评论(0编辑  收藏  举报