个人理解,如果有误请及时指出,我将非常感谢!
还是从一个题目开始吧~
- 关于C++/Java类中的static成员和对象成员的说法正确的是:
A:static成员变量在对象构造时候生成
B: static成员函数在对象成员函数中无法调用
C: 虚成员函数不可能是static成员函数
D: static成员函数不能访问static成员变量
参考答案:C
step 0. static变量的存放位置 - 静态存储区
我整理如下(已更新)
- 这里的静态/动态指的是:
- 静态:在程序运行过程中,1. 空间大小不变,这些空间在程序结束后由系统回收. 2.存在这些区域的变量生命周期贯穿整个程序(进程).
- 动态:程序运行过程中,1. 大小可变,有可能会动态地进行申请/释放,2. 这里的变量声明周期不是贯穿整个程序,而是可能由程序员控制的。
- 相对的,需要区分静/动态与读写/只读属性.
-
附:
- 附2:验证一下:
1 #include "common.h"
2 int g_uninitial;
3 int g_uni[1024];
4
5 int g_initial_d = 1000;
6
7 int main()
8 {
9 static int local_static_var = 100;
10 static int local_static_un_var;
11 int stack_var = 100;
12 const int ss = 100;
13
14 printf("constant var - %p\n", &ss);
15 printf("local var - %p\n", &stack_var);
16
17 printf("lo_sta_uninit(.bss) - %p (value %d)\n", &local_static_un_var, local_static_un_var);
18 printf("global_uninited(.bss) - %p (value %d)\n", &g_uninitial, g_uninitial);
19
20
21 printf("global_inited(.data) - %p\n", &g_initial_d);
22 printf("local_static(.data) - %p\n", &local_static_var);
23 printf("constant - %p\n", "abcd");
24 printf("MAIN's addr(.text) - %p\n", main);
25
26 return 0;
27 }
结果输出.
work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ ./test
constant var - 0x7ffc9760ee6c <--------所以const变量本身还是位于堆栈内部,const仅仅是个只读标记而已.
local var - 0x7ffc9760ee68
lo_sta_uninit(.bss) - 0x6020a4 (value 0)
global_uninited(.bss) - 0x601080 (value 0) <------至于谁高谁低就需要了解装入/分配机制了
global_inited(.data) - 0x601058
local_static(.data) - 0x60105c
constant - 0x400982 <-------------字符串常量,位于main程序所在位置上面一点
MAIN's addr(.text) - 0x40070d
- 总结一下要点:
- 字符串常量位于.text段.
- 全局初始化变量(包括static)位于.data段.
- 全局未初始化变量位于.bss段,且以
0
填充(linux).
- 关于 malloc和calloc的验证:
work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ ./test
0 0 0 0 0 0 0 0 16 66 80 11 -128 52 7 -16 32 68 -112 0 -126 84 11 113 48 64 -48 27 -125 116 15 -16 64 72 17 35 -124 -108 0 114 80 74 81 43 -128 -76 23 -14 96 76 -112 0 -122 -44 27 115 112 64 -47 59 -121 -12 31 -16 -128 80 18 67 -120 20 0 116 -112 82 82 75 -128 53 39 -12 -96 84 -112 0 -118 85 43 117 -80 64 -46 91 -117 117 47 -16 -64 88 19 99
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
step 1. C中的static
- c中的static关键字有个重要作用,是使该变量(全局static变量)仅本文件可见.
- c中局部static变量,每次访问时都是对同一个变量做操作,并不会因为函数退出而销毁.
#include "common.h"
void functest(){
static int x = 0;
x++;
cout<
输出
work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ ./test
1
2
step 2. 面向对象中的static. 如何使用.
- 类的static成员,类内只是声明,需要在类外定义,否则链接阶段出错:
未定义的标识符(ld2: undefined reference to...)
。(phase 1)- 类的static成员,类外定义的数据类型需要跟声明时是相同的.定义的语法是
:: [ = initial_value];
- 类的static成员,类外定义的数据类型需要跟声明时是相同的.定义的语法是
- 类的static成员不占该类大小的空间_因为static成员是位于.data段的。(phase 1)
- 附3:(验证ok)
1 #include "common.h"
2 int g_uninitial;
3 int g_uni[1024];
4
5 int g_initial_d = 1000;
6 class base{
7 public:
8 static int ss1;
9 static double ss2;
10 static void func();
11 };
12 int base::ss1;
13 double base::ss2 = 100;
14
15 int main()
16 {
17 static int local_static_var = 100;
18 static int local_static_un_var;
19 int stack_var = 100;
20 const int ss = 100;
21
22 printf("constant var - %p\n", &ss);
23 printf("local var - %p\n", &stack_var);
24
25 printf("global_uninited(.bss) - %p (value %d)\n", &g_uninitial, g_uninitial);
26 printf("lo_sta_uninit(.bss) - %p (value %d)\n", &local_static_un_var, local_static_un_var);
27 printf("base::ss1 - %p (value %d)\n", &base::ss1, base::ss1);
28
29 printf("global_inited(.data) - %p\n", &g_initial_d);
30 printf("local_static(.data) - %p\n", &local_static_var);
31 printf("base::ss2 - %p\n", &base::ss2);
32
33 printf("constant - %p\n", "abcd");
34 printf("MAIN's addr(.text) - %p\n", main);
35
36 printf("sizeof(base) - %ld\n", sizeof(base));
37 return 0;
38 }
输出
work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ ./test
constant var - 0x7fff562b87dc
local var - 0x7fff562b87d8
global_uninited(.bss) - 0x6010a0 (value 0)
lo_sta_uninit(.bss) - 0x6020c8 (value 0)
base::ss1 - 0x6020c0 (value 0)
global_inited(.data) - 0x601058
local_static(.data) - 0x601068
base::ss2 - 0x601060
constant - 0x400a17
MAIN's addr(.text) - 0x40070d
sizeof(base) - 1
-
类的非static成员函数,需要通过具体对象调用,不能以类似
类名::函数名
来调用。 (1)- 否则,错误信息将类似
error: cannot call member function ‘int base::out()’ without object
.
- 否则,错误信息将类似
类的非static成员函数可以访问static成员和static成员函数(ok)(2)
-
类的static成员函数,static成员函数不能访问非static成员。也就是说static成员函数只能访问static成员。(3)
- 否则,错误信息将类似
invalid use of member ‘base::kk’ in static member function
- 否则,错误信息将类似
类的static成员函数在声明时语法:
static
同时,在定义该成员函数时,不需要再写一遍(list_of_parameters...); static
了。-
类的static成员函数不能是virtual的,
因此就不具备多态。(后半句存在争议,主要是对“多态”这个词的定义)(4)- 类的static成员函数可以是具有不同参数列表的重载函数(overload),但不能是virtual。这里需要区分重载并不是多态.
- 重载是静态多态(编译期绑定,静态绑定),区别于我们常说的多态(需要在继承关系中发生的即virtual,动态绑定,也就是override,运行时决定)。
附4:
(扩展上面的代码)
#include "common.h"
int g_uninitial;
int g_uni[1024];
int g_initial_d = 1000;
class base{
public:
static int ss1;
static double ss2;
//virtual static void func2(double k); //error: member ‘func’ cannot be declared both virtual and static.........(4)
static void func(double k);
static void func(int k);
int out(){//成员函数也不占类的大小.........(2)
____("out----");
ss1++;
cout<
输出
constant var - 0x7ffdb0b98e4c
local var - 0x7ffdb0b98e48
global_uninited(.bss) - 0x6021c0 (value 0)
lo_sta_uninit(.bss) - 0x6031e8 (value 0)
base::ss1 - 0x6031e0 (value 0)
global_inited(.data) - 0x602080
local_static(.data) - 0x602090
base::ss2 - 0x602088
constant - 0x400d77
MAIN's addr(.text) - 0x4009b4
sizeof(base) - 4
----------
int func---
1
0
double func---
2
100.2
-----------
out----
1
int func---
1
1
out----
2
int func---
2
2
step 3. 如何应用到实际工程中
- c++面向对象可以用static来实现单例模式.很多情况都需要单例,比如某个工程需要一个日志模块。
你肯定不能这里一个日志那里一个日志,而是整个工程就一个日志,各个模块把日志信息都输入到同一个日志对象中。
Ref:
- http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf (反正我已经暂时看晕了)
归纳一下(黑字是我的补充):
上面的链接中给的一些示例:
- 单例模式 != 全静态函数(失去了多态性)
- 单例模式的饿汉模式(运行时立刻执行初始化,m_*是个本类类型,本类静态成员)
- 初始化顺序没有保证
- 单例模式的懒汉模式(改进版)(当引用对象时才进行初始化,m_*是个本类类型指针,getInstance()函数的局部静态变量)
- 线程不安全 可以通过加锁(双检锁)来解决
- 有内存泄漏的危险 可以增加一个destruct函数来解决
- 单例模式的代理模式(通过一个静态嵌套类成员来实现)
step 3. 按上面的建议实现一个单例模式 并在多线程环境中测试
由于工程经验尚不是很多,4我就暂时不考虑,先来个懒汉模式 + 双检锁(DCL)吧.
值得一提的是第二个Ref材料中说明了一个问题,就是static静态成员指针可能被编译器优化(主要是执行顺序的问题),导致一些情况下运行不正常,需要增加volatile关键字.(我先不管那么多)
关于执行顺序主要有2种办法:(目前所知)
1.内存栅栏
- levelDB中有2种实现
- e
- d
2.volatile关键字(需要把volatile搞得遍地都是..)
遗留的问题有:
- 嵌套代理类
- volatile关键字