首页 > 其他分享 >【C语言基础】全局变量与局部变量的深入解析

【C语言基础】全局变量与局部变量的深入解析

时间:2024-10-13 08:48:42浏览次数:3  
标签:定义 作用域 代码 局部变量 C语言 全局变量 函数

目录

一、全局变量

1.1. 定义与声明

1.2. 特性

1.2.1. 生命周期

1.2.2. 作用域

1.2.3. 跨文件访问

1.2.4. 限制访问范围

1.3. 示例

1.4. 注意事项

1.4.1. 过度使用全局变量导致代码难以理解和维护

1.4.2. 限制全局变量的使用范围

1.4.3. 清晰的命名和文档

1.4.4. 考虑替代方案

1.4.5. 使用封装和抽象

1.4.6. 代码审查和测试

1.4.7. 遵循最佳实践

二、局部变量

2.1. 声明和定义

2.2. 特性

2.2.1. 生命周期

2.2.2. 作用域

2.2.3. 内存分配

2.3. 示例

 2.4. 注意事项

2.4.1. 作用域限制

2.4.2. 生命周期

2.4.3. 命名冲突

2.4.4. 避免重复定义

2.4.5. 初始化

2.4.6. 避免过度使用

2.4.7. 递归函数中的局部变量

2.4.8. 线程安全

三、总结


在C语言编程中,变量扮演着至关重要的角色。全局变量与局部变量作为两种基本的变量类型,各自具有独特的特点和使用场景。本篇将深入探讨这两种变量的特性和应用。

一、全局变量

在C语言中,全局变量是在函数外部定义的变量,其作用域贯穿整个程序。这意味着全局变量可以在程序的任何位置(包括所有的函数内部)被访问和修改(尽管在某些情况下,你能需要在函数内部使用extern关键字来声明全局变量的存在,尤其是当在多个文件之间共享全局变量时)。

1.1. 定义与声明

  • 定义:全局变量通常在所有函数之外定义,位于文件的顶层作用域中。
int globalVar = 10;  // 定义一个全局变量并初始化为10
  •  声明(在需要时):如果全局变量定义在一个文件中,而希望在另一个文件中访问它,需要在那个文件中使用extern关键字来声明它。
// 在另一个文件中  
extern int globalVar;  // 声明全局变量,但不分配存储空间

1.2. 特性

1.2.1. 生命周期

  • 全局变量在程序整个运行期间都有效:这意味着从程序开始执行(即main函数被调用)到程序结束(即main函数返回或程序异常终止),全局变量都一直存在并保持其值(除非被显式修改)。全局变量在程序的静态存储区分配内存,因此它们的生命周期与程序的运行时间相同。

1.2.2. 作用域

  • 全局变量的作用域是整个程序:在程序的任何地方(包括所有的函数内部)都可以访问全局变量,前提是它们没有被static关键字修饰。全局变量的作用域从它们被定义的位置开始,一直延伸到程序的末尾。

  • static修饰的全局变量:如果全局变量被static关键字修饰,它的作用域就被限制在了定义它的文件内部。这样的全局变量被称为“文件作用域”变量或“静态全局变量”。它们不能在其他文件中通过extern关键字访问。

1.2.3. 跨文件访问

  • 默认情况下,全局变量可以被多个文件访问:这是全局变量名字“全局”的由来。但是,要在其他文件中使用某个全局变量,必须在该文件中使用extern关键字声明该变量。这个声明告诉编译器该变量在程序的其他地方已经定义,并且在这里是可见的。

  • 使用extern关键字声明全局变量extern声明不会为变量分配内存,它只是告诉编译器该变量在程序的其他地方已经存在。因此,extern声明通常出现在需要使用全局变量的文件的顶部,即全局作用域中。

1.2.4. 限制访问范围

  • 使用static关键字修饰全局变量:通过用static关键字修饰全局变量,可以将其作用域限制在定义它的文件内部。这样,即使其他文件包含了定义该全局变量的头文件,它们也无法访问这个被static修饰的全局变量。这有助于减少全局变量的副作用,提高代码的模块化和可维护性。

1.2.5. 存储区域

  • 全局变量存储在静态数据区(也称为全局/静态存储区)。这个区域用于存储全局变量、静态变量和常量。

1.2.6. 存储方式

  • 全局变量在编译时分配内存,并在程序的整个运行期间都存在。

全局变量在C语言中是非常有用的工具,但应该谨慎使用。过度依赖全局变量可能导致代码难以理解和维护。在可能的情况下,应该考虑使用局部变量、函数参数、返回值或结构体等替代方案来封装数据。

1.3. 示例

以下是一个C语言代码示例,它展示了全局变量的使用,包括其生命周期、作用域、跨文件访问以及如何通过static关键字限制其访问范围。

我们将创建两个文件:main.c 和 helper.c,以及一个头文件 header.h 来共享全局变量的声明。

  • header.h

#ifndef HEADER_H  
#define HEADER_H  
  
// 声明全局变量  
extern int globalVar;  
extern int staticGlobalVar; // 注意:这里实际上不应该使用extern与static一起声明全局变量,  
                            // 但为了展示static的效果,我们稍后在main.c中定义它时会使用static。  
// 正确的做法是在需要限制访问范围的文件内部直接定义static全局变量,  
// 而不是在头文件中声明它。这里的声明仅用于说明目的。  
  
#endif // HEADER_H
  • main.c

#include <stdio.h>  
#include "header.h"  
  
// 定义全局变量  
int globalVar = 10;  
  
// 定义文件作用域变量(静态全局变量)  
static int staticGlobalVar = 20;  
  
void printGlobalVars() {  
    printf("Global variable: %d\n", globalVar);  
    printf("Static global variable (file scope): %d\n", staticGlobalVar);  
}  
  
int main() {  
    // 访问和修改全局变量  
    printGlobalVars();  
    globalVar = 30;  
    printGlobalVars();  
  
    // 尝试访问staticGlobalVar会失败,如果我们在helper.c中这样做的话,  
    // 因为它是文件作用域的,仅限于main.c中访问。  
  
    return 0;  
}
  • helper.c

#include <stdio.h>  
#include "header.h"  
  
void printGlobalVarFromHelper() {  
    // 访问全局变量  
    printf("Accessing global variable from helper.c: %d\n", globalVar);  
  
    // 尝试访问staticGlobalVar会导致编译错误,  
    // 因为它是main.c中的文件作用域变量。  
    // printf("Static global variable from helper.c: %d\n", staticGlobalVar); // 错误!  
}
  •  编译与链接

要编译和链接这些文件,可以使用以下命令(假设使用的是gcc编译器):

gcc -o myprogram main.c helper.c
  • 运行程序
./myprogram
  • 输出
Global variable: 10  
Static global variable (file scope): 20  
Global variable: 30  
Static global variable (file scope): 20  
Accessing global variable from helper.c: 30

 注意

  • header.h 中的 extern 与 static:在头文件中,通常不会将 static 与 extern 一起使用来声明全局变量。static 用于限制变量的作用域为文件内部,而 extern 用于在其他文件中声明已经存在的全局变量。为了展示 static 的效果,我们在 main.c 中定义了 staticGlobalVar 并使用了 static 关键字。在头文件中,我们仅仅是为了说明而保留了 extern int staticGlobalVar; 的声明,但实际上这是不正确的做法。正确的做法是在需要限制访问范围的文件内部直接定义 static 全局变量,而不是在头文件中声明它。

  • 跨文件访问globalVar 可以在 main.c 和 helper.c 中被访问和修改,因为它是一个全局变量。

  • 文件作用域变量staticGlobalVar 仅在 main.c 中可见和可访问,因为它被声明为 static。尝试在 helper.c 中访问它会导致编译错误。

1.4. 注意事项

1.4.1. 过度使用全局变量导致代码难以理解和维护

  • 全局变量的值可以在程序的任何地方被改变,这会导致代码的逻辑变得复杂且难以追踪。
  • 当多个函数或模块依赖于同一个全局变量时,理解每个函数或模块的行为就变得非常困难,因为需要知道全局变量的当前状态以及它是如何被其他部分改变的。

1.4.2. 限制全局变量的使用范围

  • 在大型项目中,最好将全局变量的使用限制在必要的范围内。例如,只在特定的模块或库中使用全局变量,而不是在整个项目中广泛使用。
  • 使用static关键字将全局变量的作用域限制在定义它的文件内部,这有助于减少全局变量的副作用并提高代码的模块化程度。

1.4.3. 清晰的命名和文档

  • 为全局变量选择清晰、描述性的名称,以便其他开发者能够容易地理解它们的作用和用途。
  • 在项目的文档中详细记录全局变量的用途、修改方式和可能的副作用,以帮助其他开发者理解和使用这些变量。

1.4.4. 考虑替代方案

  • 如果可能的话,考虑使用局部变量、函数参数、返回值或结构体等替代方案来封装数据。
  • 局部变量和函数参数仅在函数内部可见,这有助于减少代码之间的依赖性和复杂性。
  • 结构体可以封装多个相关的数据项,并通过函数接口进行访问和修改,这有助于提高代码的可读性和可维护性。

1.4.5. 使用封装和抽象

  • 将相关的函数和数据封装在模块或类中,以减少全局变量的使用并提高代码的可重用性和可维护性。
  • 使用抽象数据类型(如C语言中的结构体和指针)来隐藏数据的实现细节,只暴露必要的接口给外部使用。

1.4.6. 代码审查和测试

  • 在项目中进行代码审查时,特别注意全局变量的使用。确保全局变量的使用是合理的,并且没有引入不必要的复杂性或错误。
  • 对包含全局变量的代码进行充分的测试,以确保它们的行为符合预期,并且在不同的情况下都能正确工作。

1.4.7. 遵循最佳实践

  • 遵循C语言编程的最佳实践,如使用const修饰符来保护不变的数据,使用枚举类型来定义有限集合的值等。
  • 学习并应用设计模式来优化代码结构,减少全局变量的使用,并提高代码的可读性和可维护性。

二、局部变量

在C语言中,局部变量是在函数内部定义的变量,其作用域仅限于该函数内部。局部变量的声明和定义通常是在函数体的开始部分进行的,即在任何执行语句之前。

2.1. 声明和定义

  • 声明:声明变量是告诉编译器变量的类型、名称和(对于函数原型中的变量)是否需要参数。但是,它并不为变量分配内存空间。在C语言中,局部变量的声明通常是在函数体的开始部分,使用类型说明符(如intfloatchar等)后跟变量名来完成。

  • 定义:定义变量是声明变量并为它分配内存空间的过程。在C语言中,局部变量的定义通常与声明同时进行,即在函数内部使用类型说明符声明变量时,编译器会自动为其分配内存。

  • 代码示例:下面是一个包含局部变量声明和定义的C语言代码示例。

#include <stdio.h>  
  
// 函数声明  
void calculateSum(int a, int b);  
  
int main() {  
    // 全局变量(虽然在这个例子中并没有真正使用到全局变量)  
    // 但为了对比,可以想象一下如果这里定义了一个全局变量会如何  
  
    // 调用函数,并传递局部变量作为参数  
    calculateSum(5, 10);  
  
    return 0;  
}  
  
// 函数定义  
void calculateSum(int a, int b) {  
    // 局部变量声明和定义  
    int sum;  
  
    // 计算两个数的和  
    sum = a + b;  
  
    // 打印结果  
    printf("The sum of %d and %d is %d\n", a, b, sum);  
  
    // 注意:当函数返回时,局部变量sum会自动销毁,其内存会被释放  
}

 在这个例子中:

  • main函数是程序的入口点。
  • calculateSum函数是一个自定义函数,它接受两个整数参数ab,并计算它们的和。
  • calculateSum函数内部,sum是一个局部变量,用于存储计算的结果。
  • 局部变量sumcalculateSum函数被调用时创建,并在函数返回时自动销毁。
  • 运行结果:

需要注意的是,在C语言中,局部变量的声明和定义通常是在同一个语句中完成的,这与全局变量(在文件外部声明和定义)不同。

2.2. 特性

2.2.1. 生命周期

局部变量的生命周期指的是它存在的时间段。局部变量在以下两个时刻之间有效:

  • 定义时刻:局部变量在代码块中定义时开始存在。
  • 销毁时刻:一旦离开该代码块(例如函数执行完毕、跳出循环或条件语句),局部变量就会失去作用并被销毁。

这意味着局部变量只在它被定义的那个代码块内有效。一旦代码块执行完毕,局部变量的内存空间就会被释放,无法再被访问。

2.2.2. 作用域

作用域指的是变量可以被访问的代码区域。局部变量的作用域仅限于定义它的代码块。具体来说:

  • 函数内部:在函数内部定义的局部变量只能在该函数内部被访问。
  • 循环和条件语句:在循环或条件语句中定义的局部变量只能在该循环或条件语句的代码块内被访问。

在代码块外部尝试访问局部变量会导致编译错误,因为该变量在这些区域是不可见的。

2.2.3. 内存分配

局部变量的内存分配是动态的,具体表现如下:

  • 每次调用函数或进入代码块:每次当函数被调用或进入一个新的代码块时,该代码块中定义的局部变量都会重新分配内存空间。
  • 函数执行完毕或离开代码块:当函数执行完毕或离开代码块时,局部变量所占用的内存空间会被释放。这意味着每次函数调用或代码块执行时,局部变量都是独立的,互不影响。

这种内存分配方式确保了局部变量的内存使用是局部的、临时的,并且避免了不必要的内存占用和潜在的内存泄漏问题。

2.2.4. 存储区域

  • 局部变量存储在栈区。栈区用于存储函数内部的局部变量、函数参数以及返回地址等。当函数被调用时,会在栈上为该函数分配一块内存区域,用于存储该函数的局部变量等。当函数执行完毕后,这块内存区域会被释放。

2.2.5. 存储方式

  • 局部变量在函数被调用时分配内存,并在函数结束时释放内存。由于它们存储在栈上,因此访问速度较快,但需要注意栈的大小限制,以避免栈溢出。

2.3. 示例

下面是一个简单的C语言示例,展示了局部变量的生命周期、作用域和内存分配:

#include <stdio.h>  
  
// 函数声明  
void printLocalVariable();  
  
int main() {  
    // 这是一个全局变量,它的作用域是整个程序  
    int globalVar = 100;  
  
    // 调用函数  
    printLocalVariable();  
  
    // 尝试访问局部变量(会导致编译错误,因为局部变量在函数外部不可见)  
    // printf("%d\n", localVar); // 错误:localVar未定义  
  
    // 可以访问全局变量  
    printf("Global variable: %d\n", globalVar);  
  
    return 0;  
}  
  
// 函数定义  
void printLocalVariable() {  
    // 这是一个局部变量,它的作用域仅限于printLocalVariable函数内部  
    int localVar = 20;  
  
    // 打印局部变量的值  
    printf("Local variable: %d\n", localVar);  
  
    // 局部变量在函数返回后将不再存在  
    // 尝试在函数外部访问localVar将导致编译错误  
  
    // 演示局部变量的生命周期和作用域  
    {  
        // 这是一个嵌套的作用域块  
        int nestedVar = 30;  
  
        // 打印嵌套变量的值  
        printf("Nested variable: %d\n", nestedVar);  
  
        // 离开嵌套作用域块后,nestedVar将不再存在  
    }  
    // 尝试访问nestedVar将导致编译错误  
    // printf("%d\n", nestedVar); // 错误:nestedVar未定义  
  
    // 可以访问函数内部的局部变量(只要还在函数内部)  
    // 但注意,一旦函数返回,localVar的内存将被释放,变量将不再存在  
}
  • 全局变量:在main函数中定义了一个全局变量globalVar,它的作用域是整个程序。在main函数和任何被main调用的函数中都可以访问它。

  • 局部变量:在printLocalVariable函数中定义了一个局部变量localVar,它的作用域仅限于该函数内部。尝试在printLocalVariable函数外部访问localVar将导致编译错误。

  • 嵌套作用域:在printLocalVariable函数内部,定义了一个嵌套的作用域块,并在其中定义了一个局部变量nestedVarnestedVar的作用域仅限于这个嵌套块内部。一旦离开这个块,nestedVar将不再存在。

  • 生命周期:局部变量localVarnestedVar在它们各自的作用域块内有效。一旦离开这些作用域块(如函数返回或跳出嵌套块),这些变量的内存空间就会被释放,并且它们将不再存在。

  • 编译错误:在注释中标记了尝试访问不在作用域内的变量

  • 会导致编译错误的地方。

  • 运行结果:

 2.4. 注意事项

使用局部变量时需要注意以下几点:

2.4.1. 作用域限制

  • 局部变量只能在定义它们的函数或代码块内部访问。一旦离开这个作用域,变量就不再可用。
  • 尝试在作用域外部访问局部变量会导致编译错误或运行时错误。

2.4.2. 生命周期

  • 局部变量的生命周期从它们被定义开始,到包含它们的函数或代码块执行完毕结束。
  • 一旦函数或代码块执行完毕,局部变量所占用的内存会被释放。

2.4.3. 命名冲突

  • 在不同的函数或代码块中,可以定义相同名称的局部变量,因为它们的作用域是独立的。
  • 但在同一个作用域内,不能定义两个相同名称的局部变量,这会导致编译错误。

2.4.4. 避免重复定义

  • 在同一个作用域内,避免重复定义相同名称的变量,即使它们的类型不同,也可能导致代码难以理解和维护。

2.4.5. 初始化

  • 局部变量在使用前应该被初始化,以避免使用未定义的值。未初始化的局部变量可能导致不可预测的行为或程序崩溃。
  • 某些编译器可能会自动初始化局部变量为默认值(如0或null),但这并不是所有编译器都保证的行为。

2.4.6. 避免过度使用

  • 局部变量应该只在需要时定义,避免在一个函数或代码块中定义过多的局部变量,这会使代码难以阅读和维护。
  • 合理使用局部变量可以提高代码的可读性和性能。

2.4.7. 递归函数中的局部变量

  • 在递归函数中,每次递归调用都会创建新的局部变量实例,这些实例是独立的。
  • 递归调用中的局部变量不会影响到其他递归调用中的同名变量。

2.4.8. 线程安全

  • 在多线程环境中,局部变量是线程安全的,因为每个线程都有自己的栈空间,局部变量不会共享。
  • 但是,如果局部变量引用了共享资源(如对象或数组),则需要小心处理同步和并发问题。

三、存储区域和方式

3.1. 全局变量

存储区域
全局变量存储在静态数据区(也称为全局/静态存储区)。这个区域用于存储全局变量、静态变量和常量。

作用域
全局变量的作用域是整个程序,即它们可以在程序的任何地方被访问,只要它们在声明之后被引用。但是,通过适当的声明(例如使用static关键字),可以限制全局变量的作用域,使其仅在定义它的文件内部可见。

生命周期
全局变量的生命周期从程序开始执行时开始,直到程序结束。即使函数被多次调用,全局变量也只会初始化一次,并且保持其值直到程序结束。

存储方式
全局变量在编译时分配内存,并在程序的整个运行期间都存在。

局部变量

存储区域
局部变量存储在栈区。栈区用于存储函数内部的局部变量、函数参数以及返回地址等。当函数被调用时,会在栈上为该函数分配一块内存区域,用于存储该函数的局部变量等。当函数执行完毕后,这块内存区域会被释放。

作用域
局部变量的作用域仅限于定义它们的函数。一旦函数执行完毕,局部变量就会被销毁,并且不能被函数外部的代码访问。

生命周期
局部变量的生命周期从它们被定义时开始(即在函数被调用时),直到函数执行完毕。每次函数被调用时,局部变量都会被重新创建,并在函数结束时销毁。

存储方式
局部变量在函数被调用时分配内存,并在函数结束时释放内存。由于它们存储在栈上,因此访问速度较快,但需要注意栈的大小限制,以避免栈溢出。

四、总结

在C语言编程中,全局变量与局部变量扮演着举足轻重的角色。全局变量,顾名思义,其定义位于函数外部,作用域覆盖整个程序。这意味着,在程序的任意位置,包括所有函数中,都可以对全局变量进行访问和修改。全局变量常用于需要在多个函数间共享的数据存储场景。

相比之下,局部变量则定义在函数内部或特定的代码块内。它们的作用域被严格限制在定义它们的函数或代码块中,一旦离开这个作用域,局部变量就会失效。这种特性使得局部变量成为数据封装和避免命名冲突的有效手段。

全局变量与局部变量各有其独特的用途和优势。在编程实践中,我们需要根据具体需求,合理选择使用全局变量或局部变量,以确保代码的可读性、可维护性和运行效率。

标签:定义,作用域,代码,局部变量,C语言,全局变量,函数
From: https://blog.csdn.net/weixin_37800531/article/details/142868305

相关文章

  • C语言中指针在数组的使用--摘自Micro_Frank
    #include<stdio.h>#include<stdint.h>intmain(void){ //指针的算术运算 int32_tnumbers[]={10,20,30,40,50,60,70,80,90,100}; int32_t*ptr=numbers;//无取地址符,也无数组下标,表示&numbers[0] //数组在内存中是连续的 //0->00000000090 //所以......
  • C语言之printf的解析
    一、前言我们学习程序开发的第一个编程基本就是输出。下面我们学习一下的输出函数printf并学习。二、项目实践1.引入库文件#include<stdio.h>2.标准输出标准格式:printf(格式控制字符串,输出列表);#include<stdio.h>intmain(){ printf("helloworld\n"); return0;}执行程......
  • c语言进阶版第19课—文件操作
    文章目录1.文件1.1文件的作用1.2文件是什么1.3文件名1.4二进制文件和文本文件2.文件的打开和关闭2.1流和标准流2.2文件指针2.3文件的打开和关闭2.4文件的顺序读写2.4.1fputc函数2.4.2fgetc函数2.4.3fputs函数2.4.4fgets函数2.4.5fprintf函数2.4.6fscanf......
  • C语言 队列例程
    队列的数据成员为结构体或数组#include<stdio.h>#include<stdlib.h>#include<assert.h>typedefstructnodeArr{ intdat[2]; structnodeArr*next;}NodeArr;NodeArr*initQueueAr();intis_emeptyAr(NodeArr*Q);voidenQueueAr(NodeArr*Q,int*arr);......
  • c语言链表-学生管理系统
    include<stdio.h>include<stdlib.h>include<string.h>//定义结构体structSTU{charnum[8];//学号charname[5];//姓名intscore;//成绩};//定义链表structtemp{structSTU*s;structtemp*next;};voidadd(structtemp**head);//......
  • 关于C++当中全局变量的释放问题
    一、由来主要是在修改公司的一个MFC项目的时候遇到的问题,我在MFC页面的析构函数当中对一个全局图像变量进行了释放,具体如下:ai_engine_OCR::~ai_engine_OCR(){//及时释放内存if(g_pImg_open!=NULL){deleteg_pImg_open;g_pImg_open=NULL......
  • C语言的常见概念(三)
    上一期讲到了关于转义字符的知识与使用,今天继续为大家介绍C语言中的语句与语句使用,以及关于注释的相关知识语句和语句的使用语句和语句分类C语言的代码是由⼀条⼀条的语句构成的,C语言中的语句可为以下五类:•空语句•表达式语句•函数调用语句•复合语句•控制......
  • 【C语言】语义陷阱(5):揭秘空指针与空字符串的微妙差异
    目录一、空指针(NullPointer)1.1.定义与表示1.2.用途1.3.安全性 1.4.注意事项1.5.空指针与野指针的区别1.5.1.特性对比1.5.2.安全性与风险1.5.3.编程实践二、指向空字符串的指针2.1.定义2.2.字符数组与空字符串2.3.指针的初始化2.4.空字符串的用途2......
  • 【趣学C语言和数据结构100例】
    【趣学C语言和数据结构100例】问题描述一个球从100m高度自由落下,每次落地后反弹回原高度的一半,再落下,求它在第10次时共经过多少米,第10次反弹多高。猴子吃桃问题。猴子第1天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了一个。第2天早上又将剩下的桃子吃掉一......
  • 动态内存管理(c语言)
    这里写目录标题1.为什么有动态内存分配2.malloc函数和free函数3.calloc和realloc1.为什么有动态内存分配在讲动态内存的优势之前,先聊聊其他内存开辟方法的不足之处。上图内存开辟方法的特点为:1.空间开辟的大小是固定的。2.数组在声明的时候需要指定长度,数组空间......