static 关键字
static关键字:按编程语言划分
- 纯C:C语言中的static关键字用法
- C++:保留C语言的用法,新增了类和结构体的static用法.
两大基本概念说明
- 生命周期:变量实际在内存中存在的时间
- 作用域:变量可以访问的范围.
举例说明
char* get_name()
{char name[20];scanf("%s",name);return name;
]
name这个字符数组的生命周期是
get_name
函数调用开始到结束调用.
return name;
这一语句返回了一个栈上的地址.函数栈帧销毁.这个变量的内存回收了.这会产生一个段错误
.
name的作用域:正常只能get_name
函数内部可以访问,除非传递指针或者引用的操作.—name作为一个局部变量.
C语言
1. 更改内存存储位置(static 修饰局部变量)
static int i = 0;
注意这里的i为局部变量。
局部变量在栈上创建,static修饰局部变量会更改其在内存的存储位置。这时候的i
会在静态区,生命周期:创建到程序运行结束,且这个代码语句只会执行一次,因为其只能在静态区存储一次,后面默认存在。
static修饰的局部变量类似全局变量,但不意味着它们完全相同,
- 对于变量,我们讨论三个部分,即作用域,生命周期,链接属性。
static修饰的局部变量,只是更改了其生命周期,但其作用域还是限定在函数块内,或者函数内部的某一块作用域(循环内或者某个if语句内)。直接用i
变量访问只能限定在某个该死的块内。
Q1:既然其生命周期已经更改,意味着它只要创建了,就必然一直存在。能否通过指针的方式实现访问呢?
当然可以,我们可以通过作用域更大的指针来存储改局部变量在静态区的地址。
#include<stdio.h>
static int* p;//定义一个全局作用域的static int的指针
void test()
{static int i = 0;p = &i;
}int main()
{test();printf("%d", *p);return 0;
}
- 上面的static 修饰了指针变量,但指针变量也是变量,也分局部变量和全局变量。static 无论修饰全局指针变量还是局部指针变量,谨记,不要让它指向一个栈上的局部变量的内存。因为函数栈帧空间一旦销毁,局部变量销毁,栈帧空间重复利用会覆盖原来地址的数据,static指针会成为悬垂指针,访问未知内存,这是很危险的。因此,必须让其存储一个静态区段或者堆区的地址,指向静态区或者堆区。
#include<stdio.h>
static int* p;
void test()
{int i = 100;p = &i;
}
void test2()
{printf("test2():\n");
}
int main()
{test();test2();//利用test先前的栈帧空间,来达到覆盖数据的效果printf("%d", *p);return 0;
}
这里的p指向了一个垃圾值(无意义的值)。
如果static修饰局部指针变量,那么这个指针变量也存储在静态区。
2.更改链接属性(static修饰全局变量,函数)
1.什么是链接属性?
回顾:源文件到可执行代码。
预处理,编译,汇编,链接。
预处理:头文件内容拷贝/宏替换/去掉注释/条件编译
编译:检查语法错误,生成汇编代码区(指令集)
汇编:将汇编代码生成二进制机器码。
链接:把各个文件的二进制机器码链接在一起生成可执行程序。
先看第一个例子
在static.c
文件中声明一个静态全局变量.
//static.c
static int s_var = 666;
//Main.c
#include<stdio.h>
int main(void)
{//能打印成功吗?printf("%d",s_var);return 0;
}
失败了,在Main.c中添加 int s_var =999; 这一语句.
会出现命名冲突吗?
答案肯定不会.成功打印999.
这说明了什么?
static修饰的全局变量,只能作用在它自己的单个文件内.外部文件不会链接到它,对于其它文件好像隐藏了.----这是C中用static实现封装性的一个好方式
再看
//static.c
//去掉static关键字
int s_var = 666;
//Main.c
#include<stdio.h>
extern int s_var;//外部声明,提醒链接器在其它文件找这个变量.
int main(void)
{printf("%d",s_var);return 0;
}
静态函数部分
//main.c
#include<stdio.h>
#include"add.h"
int main()
{printf("%d", add(2,3));return 0;
}
//add.c
#include"add.h"
static int add(int x ,int y)
{return x + y;
}
static int add(int x, int y);//add.h
链接属性是分为外部链接,内部链接属性和无链接属性。
我们上面的例子说明来链接属性。
在链接之前每个文件都单独预处理,编译,汇编。在这过程中,main.c
不知道add函数的存在,因为add函数具体实现放在add.c
中了。它只能搁置这个函数的调用指令。
等到链接时,它会根据生成的符号表(函数只有实现了才会生成地址)找到add函数并调用。
static修饰的函数具有内部链接属性
啥意思?在链接过程中,add函数被static修饰了,那么我们称add具有内部链接属性。只能在add.c文件链接该函数,外部文件调用了add
函数,链接阶段也找不到add函数,这里会报一个链接错误。
未被static的函数具有外部链接属性链接时,只要有函数声明,它能在外部文件找函数的定义,找不到就报错。
这里把上面例子的static关键字去掉即可。
总结: static修饰变量
首先,局部变量,块作用域的变量(也是局部变量)无链接属性。
因为它们的作用域限制了它们注定不会在外部文件使用。
(不要想着通过static和指针的方式,延长生命周期让外部文件能使用这个局部变量,没意义!)。
一般的全局变量如int size = 0
,具有外部链接属性。
//main.c
#include<stdio.h>
int size = 10;
int main()
{printf("%d", size);return 0;
}
//test.c
//注意使用外部文件的全局变量要声明!!!
#include<stdio.h>
extern int size;//声明使用外部文件的全局变量size。
void test()
{printf("%d", size);
}
将main.c
的int size = 10
改成static int size = 10;
。
全局变量size就有内部链接属性了,无论外部怎么声明都找不到main.c
的size了。
总结:static修饰函数和局部变量,那么它们只能在定义它们的文件内部访问。有助于隐藏实现细节,防止不同文件之间的命名冲突。
C++
C++引入了面向对象的特性.
C++中结构体和类都能写入函数.
下面讨论类与结构体的static关键字,作为C的拓展,前面C部分的static用法完全实用C++.
在类中的static这么理解.
静态的变量或者方法—类中的函数,我们称为方法.
等价于类的字段和方法.
class Object {
public:static int x;static int y;static void print() {std::cout << "(" << x << "," << y << ")" << std::endl;}
};
int Object::x = 0;
int Object::y = 0;
int main()
{Object obj;obj.x = 10;obj.y = 5;Object obj2;obj2.x = 20;obj2.y = 10;obj.print();std::cin.get();
}
static修饰类的x,y是所以实例公有的字段.
任何实例都可以访问该静态字段,也可直接修改该字段.
`从内存角度上说,obj,obj2的x,y实际指向同一块内存.只要一个实例修改了这块内存的值,那么可以说这个类的部分修改了.
//最好的方式:通过类名和::访问修改字段和调用类的方法
int main()
{Object::x = 10;Object::y = 20;Object::print();std::cin.get();
}
若你要放入一个信息,这个信息与类有关,同时你又希望所有对象共享这一块数据就用static修饰.
静态方法
简单来说,若你了解C++的this指针,那么无须我多说了.
对于静态方法,它不会隐式传递this指针,静态方法不能访问类的实例字段.
比如下面就是一种错误写法
.
class Object {
public:int x;int y;static void print() {std::cout << "(" << x << "," << y << ")" << std::endl;}
};
class Object {
public:int x;int y;static void print() {std::cout << "(" << x << "," << y << ")" << std::endl;}
};
int main()
{Object obj;obj.x = 10;obj.y = 20;obj.print();return 0;
}
print方法是静态方法,它不会拿到类里任何实例的字段.所以这里会报一个错误
既然静态方法没有隐式传递指针,那么我们就显示传递对象不就好了吗?
static void print(Object& obj) {std::cout << "(" << obj.x << "," << obj.y << ")" << std::endl;}
完,static的个人总结差不多就这些了.