变量(value)是标识符(identifier)的一个子集。因此在讲变量之前,先要说一下标识符(identifier)。

1、标识符

标识符,本质上来讲,就是一个名字。通过这个名字,我们可以引用到(refers to) C 语言里的很多东西:

  • 函数(function)。
  • 枚举、联合、结构体的标签(tags of enumeration, union and structure)。
  • 联合、结构体的成员(members of union and structure)。
  • 枚举常量(enumeration constant)。
  • 用 typedef 创建的名字(typedef names)。
  • 对象(object)。

标志符与生俱来就有两个属性。我们思考一下,当我们写下一个标识符的时候,会面临两个问题的:

  1. 这个标志符的可见性是?
  2. 如果我写了一个标识符:randomvalue,在别的模块里,别人也写了一样的标识符,那怎么办?

这两个问题就延伸出两种极其重要的属性了。

A name also has a scope, which is the region of the program in which it is known, and a linkage, which detemines whether the same name in another scope refers to the same object or function.

即:一个名称(标识符)还具有作用域,即其所在的程序区域,在该区域内可以识别该名称,并且还具有链接性,决定了在另一个作用域中是否使用相同的名称引用相同的对象或函数。

2、变量

根据上一节我们知道,标识符具有两个属性。

作用域: 指在程序中定义的变量、函数或其他命名实体的可见性和有效范围。它决定了在程序的哪些部分可以访问和使用这些实体。作用域规定了变量或函数的名称在程序中的可访问范围,并且可以帮助避免命名冲突。

链接性:指在程序中不同作用域中同名实体之间的关联性和连接性。它确定了同名实体在不同作用域中是否引用相同的对象或函数。

变量是标识符的子集,因此也是天然继承了这两个属性的。除此之外,变量还有另外两个属性:存储类别(storage class)类型(type)

下面,我要对变量的这四种属性进行细说。

2.1、存储类别

在编程中,存储类别(storage class)是用于指定变量或函数的存储位置、生命周期和可见性的修饰符。

在C语言中有以下存储类别:

auto:默认的存储类别,用于指定局部变量的存储,表示变量在代码块中的自动创建和销毁。

register:用于请求将变量存储在寄存器中,以便快速访问。然而,这只是一个建议,编译器可以忽略它。

static:用于指定静态变量的存储,这些变量在整个程序执行期间都存在,不会随着作用域的进入和退出而创建或销毁。

extern:用于声明外部变量或函数,表示这些实体是在其他文件中定义的。

typedef:用于创建自定义的类型别名,不直接与存储相关,但在存储类别的讨论中常常提及。

2.2、类型

在C语言中,类型(type)是用来描述数据的性质、大小和操作方式的概念。每个变量、表达式和函数都有一个特定的类型,它决定了该实体能够存储的数据范围、内存占用以及可对其执行的操作。

C语言提供了多种基本类型和派生类型,包括但不限于以下几种:

  1. 基本类型(Basic Types):C语言提供了一些基本的数据类型,如整数类型(如intchar)、浮点数类型(如floatdouble)和布尔类型(_Bool)。这些类型用于存储基本的数据值。

  2. 数组类型(Array Types):数组是一种由相同类型的元素组成的集合,可以通过下标访问和操作其中的元素。

  3. 结构体类型(Struct Types):结构体是一种用户自定义的数据类型,它可以包含多个不同类型的成员变量。

  4. 枚举类型(Enum Types):枚举类型是一种用户自定义的类型,用于定义一组相关的命名常量。

  5. 指针类型(Pointer Types):指针是一种特殊的数据类型,它存储了其他类型数据对象的内存地址。

  6. 函数类型(Function Types):函数也具有类型,它描述了函数的返回值类型和参数类型。

类型在C语言中具有重要的作用,它们决定了数据的表示和操作方式。在编程过程中,正确的类型使用可以确保数据的正确性和一致性,同时提供了对数据进行有效操作和处理的工具。

例如,考虑以下代码片段:

1
2
3
4
5
6
7
int x = 5;
float y = 3.14;
char c = 'A';

int add(int a, int b) {
  return a + b;
}

在上述代码中,变量 x 的类型为 int,可以存储整数值。变量 y 的类型为 float,可以存储浮点数值。变量 c 的类型为 char,可以存储单个字符。函数 add 的类型为返回值为 int,参数为两个 int 类型的函数。

理解C语言中的类型有助于正确声明和使用变量、函数和数据结构,并确保在操作数据时符合语言规范和预期行为。

2.3、链接性

在C语言中,链接性(linkage)指的是标识符(变量、函数、常量等)在不同编译单元(源文件)之间的关联性和连接性。它确定了同名标识符在不同编译单元中是否引用相同的对象或函数。

C语言中的链接性有以下几种类型:

  1. 外部链接性(External Linkage):具有外部链接性的标识符可以在不同的源文件之间共享和访问。它们在不同的编译单元中表示同一个实体。可以通过使用 extern 关键字来声明具有外部链接性的变量或函数。

例子:

在源文件 A.c 中定义了一个具有外部链接性的全局变量:

1
2
// A.c
int globalVar = 10;

在源文件 B.c 中使用了外部链接性的全局变量:

1
2
3
4
5
6
// B.c
extern int globalVar;

void myFunction() {
  // 在此可以使用 globalVar
}

在上述示例中,全局变量 globalVar 在 A.c 中定义,具有外部链接性,然后在 B.c 中使用 extern 关键字声明,并在函数 myFunction 中访问。通过外部链接性,B.c 可以访问并共享 A.c 中定义的 globalVar

  1. 内部链接性(Internal Linkage):具有内部链接性的标识符只能在定义它们的单个源文件中访问。它们在文件内部是可见的,但无法跨源文件共享。

例子:

在源文件 A.c 中定义了一个具有内部链接性的静态变量:

1
2
// A.c
static int staticVar = 20;

在源文件 B.c 中无法访问具有内部链接性的静态变量:

1
2
3
4
5
6
// B.c
extern int staticVar; // 错误:无法访问具有内部链接性的静态变量

void myFunction() {
  // 无法访问 staticVar
}

在上述示例中,静态变量 staticVar 在 A.c 中定义,具有内部链接性,只能在 A.c 内部访问。在 B.c 中使用 extern 关键字尝试访问 staticVar 会导致编译错误。

需要注意的是,具有外部链接性和内部链接性的标识符可以是全局变量、静态变量或函数。它们的链接性是由标识符的声明或定义方式决定的。

理解链接性的概念有助于编写模块化的代码,并控制标识符的可见性和共享性。它可以用于限制变量和函数的作用范围,避免命名冲突,并支持多文件的程序开发。

链接性的概念在多文件的程序开发中尤为重要。通过定义具有外部链接性的变量或函数,可以在多个文件中共享它们,从而促进代码的模块化和重用。而具有内部链接性的实体则限制了它们的可见性,有助于将实现细节隐藏在文件内部,提高代码的封装性。

2.4、作用域

在C语言中,作用域(scope)是指在程序中某个特定区域或上下文中可见和有效的标识符(变量、函数、类型等)的范围。

C语言中存在以下几种作用域:

  1. 块作用域(Block Scope):块作用域指的是由花括号({})包围的代码块内部的范围。在块作用域中定义的标识符仅在该块内可见,在块外部不可访问。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void myFunction() {
  int x = 10; // 块作用域中定义的变量
  // x 在此可见

  {
    int y = 20; // 嵌套的块作用域中定义的变量
    // x 和 y 在此可见
  }

  // x 在此仍然可见,但 y 不可见
}
  1. 函数作用域(Function Scope):函数作用域指的是函数内部定义的标识符的范围。在函数作用域中定义的标识符对于函数内的所有代码块都是可见的,但在函数外部不可访问。
1
2
3
4
5
6
7
8
int x = 10; // 全局变量

void myFunction() {
  int y = 20; // 函数作用域中定义的变量
  // x 和 y 在此可见
}

// x 在此仍然可见,但 y 不可见
  1. 文件作用域(File Scope):文件作用域指的是在函数外部定义的标识符的范围。在文件作用域中定义的标识符对整个文件内的代码块都是可见的,可以被多个函数访问。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 文件作用域中定义的全局变量
int x = 10;

void myFunction1() {
  // x 在此可见
}

void myFunction2() {
  // x 在此可见
}

作用域的概念对于正确的变量命名和避免命名冲突非常重要。通过将标识符限定在特定的作用域内,可以提高代码的可读性、维护性和可靠性。在C语言中,作用域由大括号和函数定义等控制结构来界定,并且作用域规则在编译时由编译器进行解析和处理。

总结

refs

  1. 《The C programming language, second edition》