对于静态语言,类型是编译期确定的。变量在运行期,有创建、初始化、使用、销毁四个生命周期状态。

C语言和C++

变量创建和销毁

运行期局部变量在栈上创建,全局变量和静态变量在静态区创建,new 产生的变量在堆上创建。栈变量的作用域等于函数作用域,全局变量和静态变量的作用域是进程执行期,new产生的堆变量可能逃逸,需要用户自行管理生命周期和内存释放。堆变量内存未释放会导致内粗泄露,内存重复释放会导致进程崩溃。

为了避免堆变量的遗忘释放或重复释放,C++标准库提供智能指针帮助管理对象的释放。原则上C++一切堆变量都应该使用智能指针。

  1. std::unique_ptr,独占所有权,不可复制,只能移动。
  2. std::shared_ptr,共享所有权,使用引用计数(reference count)管理资源的释放。
  3. std::weak_ptr,弱引用,不增加引用计数;依赖于 std::shared_ptr,用于解决循环引用问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <memory>

int main() {
std::unique_ptr<int> ptr1 = std::make_unique<int>(10);
std::cout << *ptr1 << std::endl;

// std::unique_ptr<int> ptr2 = ptr1; // 错误:不能复制
std::unique_ptr<int> ptr2 = std::move(ptr1); // 转移所有权
std::cout << *ptr2 << std::endl;

std::shared_ptr<int> sp = std::make_shared<int>(30);
std::weak_ptr<int> wp = sp; // 创建weak_ptr

std::cout << "Use count: " << sp.use_count() << std::endl;

if (auto sp2 = wp.lock()) { // 检weak_ptr检查资源是否有效
std::cout << *sp2 << std::endl;
}

sp.reset(); // 释放资源
if (wp.lock() == nullptr) {
std::cout << "Resource no longer exists" << std::endl;
}
}

// 输出
10
Use count: 1
30
Resource no longer exists

变量初始化

  1. 局部变量,未初始化的为不确定值(即垃圾值)
  2. 静态变量,自动初始化为 0 或 NULL(指针类型),静态变量初始化只执行一次
  3. 全局变量,自动初始化为 0 或 NULL

C++类变量规则

  1. 普通成员变量, 必须在构造函数中显式初始化,未初始化的普通成员变量值是未定义的(包含随机值)
  2. 静态成员变量是类级别的,需要在类外初始化,自动初始化为 0 或 NULL(指针类型),静态变量初始化只执行一次
  3. const变量必须在构造函数的初始化列表中初始化,初始化后不可修改
  4. C++11 允许为 非静态成员变量 提供类内初始值。静态成员变量仍需在类外初始化。
  5. 成员变量按 声明的顺序 初始化,与初始化列表的顺序无关。

变量作用域

全局变量跨.cpp文件,链接多文件共享;静态变量只能被当前.cpp文件访问;局部变量作用域为函数;类成员变量需要在类内或对象中访问,随着对象的销毁而销毁。

变量修饰

static, 静态变量

const,表示常量,不可改变量。修饰函数表示函数不可能修改类成员变量

volatile,告诉编译器不要对该变量进行优化。常用于 多线程编程 或 硬件编程,避免编译器对变量的访问进行优化。

extern,声明变量或函数在其他文件中定义。它不分配存储空间,只是告诉编译器该变量在其他地方定义过。链接时全局变量在多个文件共享。

mutable 用来修饰类中的成员变量,即使类的对象是 const 类型,成员变量仍然可以被修改。

JAVA

JAVA所有变量和函数都是写到类中,类名和文件名相同。main函数是在主类中定义的static函数。JAVA没有全局变量,因为所有变量都是在类中。

JAVA基本类型变量存储在栈中,引用类型变量存储在堆中。引用类型变量使用new创建,jvm的垃圾回收模块负责对象回收。

JAVA的变量都会初始化,不会有随机值。

类静态变量和静态模块随着类加载而初始化和执行,如果某类无须被加载,则不会执行该类中的静态变量和静态块。

  1. 类的加载通常是在访问类的静态成员(如静态字段、静态方法)或创建类的实例时触发的。
  2. 类加载器会在需要时加载类,不会提前加载。

变量访问控制

Java中,如果需要使用其他包中的类,必须利用import显式地导入它们。import 语句只能导入类,而不能导入方法、字段

Java 的访问控制包括四种权限:

  1. public:类或成员可以被任何类访问。
  2. protected:类或成员可以被同一个包中的类或子类访问。
  3. default(无修饰符):类或成员只能被同一个包中的类访问。
  4. private:类或成员只能在当前类内部访问。

包名通常采用 小写字母,以避免与类名的命名冲突。

包名通常使用 反向域名,例如 com.example.myapp。这种做法避免了不同公司或组织的包名冲突。

变量修饰

除了类,java的变量和函数也有访问修饰符

1
2
3
4
5
6
7
8
// public:变量可以被任何其他类访问,无论它们在同一包中还是不同的包中。
public int x;
// protected:变量可以被同一包中的类或继承自该类的子类访问
protected int y;
// default(无修饰符):变量的默认访问权限是包级访问(包私有)。
int z;
// private:变量只能在当前类内部访问
private int a;

static,声明为 static 为静态变量,属于类。

final,声明为 final 的变量是常量,初始化之后不能再修改。final修饰函数表示该函数不能被重写

volatile 变量会告诉 Java 虚拟机(JVM)每次访问该变量时都要从内存中读取,而不是从缓存中读取。它通常用于多线程编程中,确保变量的可见性。

synchronized 主要用于方法,确保同一时刻只有一个线程能执行该方法。

native 关键字用于声明本地方法,这些方法通过 JNI(Java Native Interface)调用非 Java 语言(如 C、C++)编写的代码。native通常用于方法声明。

Go

栈变量指局部变量、以及值语义的数据结构(int, float, bool, struct)。当变量的生命周期仅限于当前函数时,Go 会尽量将其分配到栈上。

堆内存,通常分配给引用类型(slice, map, channel, pointer)或者使用new创建变量且变量的地址被传递到函数外部。

Go 变量的访问控制。全局定义的小写字母只能包内访问,大写字母可以跨包访问。访问其他包的变量需要先import。

堆分配的变量由GC的垃圾回收模块负责回收。

Go变量相关的关键字,

  1. var:声明变量
  2. :=:简短声明变量
  3. const:声明常量

Go不提供继承,不提供静态变量

Python

Python的变量有自己的特别之处,比如,Python局部变量区域可以访问全局变量,但无法修改全局变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
x = 10
def my_function():
print(x)

my_function()

# 执行结果
10

x = 10
def my_function():
# 修改全局变量需要在局部变量区加 global x
print(x)
x=20

my_function()

# 执行结果
UnboundLocalError: local variable 'x' referenced before assignment

if __name__ == "__main__"函数里可以正常修改全局变量(其余函数不行

1
2
3
4
5
6
7
8
9
10
x = 10
def my_function():
print(x)

if __name__ == "__main__":
x=20
my_function()

# 输出
20

python无法对变量进行修饰,但可以通过@注解对函数进行修饰。例如@staticmethod,@classmethod

常见@注解

1
2
3
@classmethod 装饰器定义类方法,允许方法访问类本身,而不是实例。
@staticmethod 装饰器定义静态方法,静态方法不能访问实例和类本身
@property 用于将类的方法变成一个只读属性