[C高手编程] static与extern: 作用域、可见性与存储类全面解析

news2024/9/23 5:30:31

在这里插入图片描述

💖💖⚡️⚡️专栏:C高手编程-面试宝典/技术手册/高手进阶⚡️⚡️💖💖
「C高手编程」专栏融合了作者十多年的C语言开发经验,汇集了从基础到进阶的关键知识点,是不可多得的知识宝典。如果你是即将毕业的学生,面临C语言的求职面试,本专栏将帮助你扎实地掌握核心概念,轻松应对笔试与面试;如果你已有两三年的工作经验,专栏中的内容将补充你在实践中可能忽略的新技术和技巧;而对于资深的C语言程序员,这里也将是一本实用的技术备查手册,提供全面的知识回顾与更新。无论处在哪个阶段,「C高手编程」都能助你一臂之力,成为C语言领域的行家里手。

概述

本章深入探讨C语言中的staticextern关键字。这两种关键字在控制变量的作用域、可见性和存储方式上发挥着重要作用。通过本章的学习,读者将能够理解这些关键字的工作原理,并能在实际编程中正确地运用它们。

1. static关键字

1.1 基本概念

static关键字用于声明具有静态存储持续期的变量或函数。这意味着一旦被声明,这些实体在整个程序执行期间都将存在。

1.2 变量作用域

1.2.1 局部静态变量
  • 定义:在函数内部声明时,局部静态变量的作用域仅限于该函数内部,但其生存周期跨越整个程序执行过程。

  • 初始化:局部静态变量在第一次被调用时初始化,之后每次函数调用时都会保留上次函数调用结束时的状态。

    详细说明

    • 局部静态变量是一种特殊的局部变量,它与普通局部变量的不同之处在于其生存周期不是局限于函数调用的生命周期内,而是贯穿整个程序执行过程。
    • 局部静态变量的内存是在数据段中分配的,而不是像普通局部变量那样在堆栈中分配。
    • 当函数首次被调用时,局部静态变量会被初始化,此后每次函数被调用时,局部静态变量都会保留上次函数调用结束时的值。
    • 局部静态变量的值在函数调用之间是持久的,这使得它们非常适合用来保存累计计数器或其他需要在多次函数调用之间保留状态的数据。

    示例代码:

    void increment() {
        static int counter = 0; // 局部静态变量
        counter++;
        printf("Counter is %d\n", counter);
    }
    
1.2.2 全局静态变量
  • 定义:在文件范围内声明时,全局静态变量仅在声明它的源文件内可见,对其他源文件不可见。

  • 示例代码:

    static int globalStatic = 10; // 全局静态变量
    void printGlobalStatic() {
        printf("Global static variable is %d\n", globalStatic);
    }
    

    详细说明

    • 全局静态变量是一种特殊类型的全局变量,它与普通全局变量的区别在于其可见性仅限于声明它的源文件。
    • 全局静态变量的内存同样是在数据段中分配的,这使得它们在整个程序执行期间都存在。
    • 全局静态变量在其他源文件中不可见,这有助于减少命名冲突的风险。
    • 由于全局静态变量在整个文件中可见,但不在其他文件中可见,因此它们可以作为一种有效的数据封装机制,有助于保持代码的模块化。

1.3 存储类

static变量在程序运行之前就已经分配了内存,即使函数调用结束,它们也不会被销毁。

  • 内存分配static变量的内存是在数据段中分配的,而动态分配的变量则是在堆栈中分配的。

  • 生命周期static变量的生命周期是从程序开始执行到程序结束。

    详细说明

    • 数据段是在程序开始执行之前就已经分配好的内存区域,用于存储全局变量和静态变量。
    • 与堆栈中的变量相比,static变量的生命周期更长,因为它们在整个程序执行期间都存在。
    • static变量的初始化通常发生在程序启动时,这意味着它们在程序执行之前就已经分配好内存并准备好使用。
    • static变量的生命周期长意味着它们可以保留状态,这对于需要在多次函数调用之间保持状态的场景非常有用。

1.4 示例代码

#include <stdio.h>

// 全局静态变量,只在本文件可见
static int globalStatic = 10;

void func() {
    static int count = 0; // 局部静态变量
    count++;
    printf("Count is %d\n", count);
}

int main() {
    func();
    func();
    return 0;
}

详细说明

  • globalStatic是一个全局静态变量,它在整个文件中可见,但在其他文件中不可见。
  • count是一个局部静态变量,它在函数func()中声明,但其生命周期跨越整个程序执行过程。
  • main()函数中调用func()两次,观察count的变化,可以看到它在两次函数调用之间保留了状态。

在这里插入图片描述

2. extern关键字

2.1 基本概念

extern关键字用于声明一个变量或函数的外部定义,即该实体是在另一个源文件中定义的。

2.2 变量可见性

2.2.1 外部变量
  • 定义:可以在多个源文件之间共享,只要在每个使用它的文件中使用extern声明即可。

  • 示例代码:

    // common.h
    #ifndef COMMON_H
    #define COMMON_H
    extern int sharedVar;
    #endif /* COMMON_H */
    
    // file1.c
    #include "common.h"
    int sharedVar = 100; // 定义共享变量
    
    void setSharedVar(int val) {
        sharedVar = val;
    }
    
    // file2.c
    #include "common.h"
    
    void printSharedVar() {
        printf("Shared var is %d\n", sharedVar);
    }
    
    int main() {
        setSharedVar(200); // 使用来自file1.c的函数
        printSharedVar();  // 打印共享变量
        return 0;
    }
    

    详细说明

    • sharedVar是一个外部变量,它在file1.c中定义,并在common.h中声明为extern,使得file2.c可以访问它。
    • 通过setSharedVar()函数设置sharedVar的值,并通过printSharedVar()函数打印其值。
    • 使用extern关键字声明变量或函数时,实际上是在告诉编译器:“这个实体是在其他地方定义的,请在链接阶段查找其实现。”
    • extern关键字使得变量可以在多个源文件之间共享,这对于需要在多个文件中访问相同数据的情况非常有用。
2.2.2 函数声明
  • 定义:使用extern声明函数,通常在头文件中进行。

  • 示例代码:

    // common.h
    #ifndef COMMON_H
    #define COMMON_H
    extern void printMessage(const char *msg);
    #endif /* COMMON_H */
    
    // file1.c
    #include "common.h"
    void printMessage(const char *msg) {
        printf("%s\n", msg);
    }
    
    // file2.c
    #include "common.h"
    int main() {
        printMessage("Hello, World!");
        return 0;
    }
    

    详细说明

    • printMessage()函数在file1.c中定义,并在common.h中声明为extern,使得file2.c可以调用它。
    • 使用extern关键字声明函数时,通常是为了确保函数在多个源文件之间的可见性,这对于需要在多个文件中调用同一函数的情况非常有用。

2.3 示例代码

假设我们有两个源文件file1.cfile2.c,以及一个头文件common.h

common.h
#ifndef COMMON_H
#define COMMON_H

extern int sharedVar;

#endif /* COMMON_H */
file1.c
#include "common.h"

int sharedVar = 100; // 定义共享变量

void setSharedVar(int val) {
    sharedVar = val;
}
file2.c
#include "common.h"

void printSharedVar() {
    printf("Shared var is %d\n", sharedVar);
}

int main() {
    setSharedVar(200); // 使用来自file1.c的函数
    printSharedVar();  // 打印共享变量
    return 0;
}

详细说明

  • sharedVarfile1.c中定义,并在common.h中声明为extern
  • setSharedVar()函数设置sharedVar的值,printSharedVar()函数打印其值。
  • 通过这种方式,sharedVar可以在file1.cfile2.c之间共享,file2.c可以使用file1.c中定义的函数来修改和查询sharedVar的值。

在这里插入图片描述

3. staticextern的综合使用

在实际编程中,staticextern常常结合使用,以达到特定的效果。

3.1.1 使用static extern声明可以在一个文件中引用另一个文件中的静态变量
  • 定义:通过这种方式可以在一个文件中引用另一个文件中的静态变量,但只能读取不能修改。

  • 示例代码:

    // file1.c
    #include <stdio.h>
    extern int globalStatic; // 声明全局静态变量
    
    void setGlobalStatic(int val) {
        globalStatic = val;
    }
    
    // file2.c
    #include <stdio.h>
    static int globalStatic = 0; // 定义全局静态变量
    
    void printGlobalStatic() {
        printf("Global static variable is %d\n", globalStatic);
    }
    
    int main() {
        setGlobalStatic(100); // 使用来自file1.c的函数
        printGlobalStatic();  // 打印全局静态变量
        return 0;
    }
    

    详细说明

    • globalStaticfile2.c中定义为静态变量,并在file1.c中声明为extern
    • 由于globalStatic是静态的,它只能在其定义的文件中被修改,但在其他文件中可以被读取。
    • 使用static extern的方式可以允许其他文件访问一个文件中的静态变量,但只能读取,不能修改,这有助于保护数据的完整性。
3.1.2 使用extern声明可以跨文件访问全局变量
  • 定义:通过这种方式可以在不同的源文件间共享全局变量。

  • 示例代码:

    // common.h
    #ifndef COMMON_H
    #define COMMON_H
    extern int globalVar;
    #endif /* COMMON_H */
    
    // file1.c
    #include "common.h"
    int globalVar = 100; // 定义全局变量
    
    void setGlobalVar(int val) {
        globalVar = val;
    }
    
    // file2.c
    #include "common.h"
    
    void printGlobalVar() {
        printf("Global var is %d\n", globalVar);
    }
    
    int main() {
        setGlobalVar(200); // 使用来自file1.c的函数
        printGlobalVar();  // 打印全局变量
        return 0;
    }
    

    详细说明

    • globalVarfile1.c中定义,并在common.h中声明为extern
    • 通过setGlobalVar()函数设置globalVar的值,并通过printGlobalVar()函数打印其值。
    • 使用extern声明全局变量可以实现跨文件的数据共享,这对于需要在多个文件中访问相同数据的情况非常有用。

3.2 示例代码

// file1.c
#include <stdio.h>
extern int globalStatic; // 声明全局静态变量

void setGlobalStatic(int val) {
    globalStatic = val;
}

// file2.c
#include <stdio.h>
static int globalStatic = 0; // 定义全局静态变量

void printGlobalStatic() {
    printf("Global static variable is %d\n", globalStatic);
}

int main() {
    setGlobalStatic(100); // 使用来自file1.c的函数
    printGlobalStatic();  // 打印全局静态变量
    return 0;
}

详细说明

  • globalStaticfile2.c中定义为静态变量,并在file1.c中声明为extern
  • setGlobalStatic()函数设置globalStatic的值,printGlobalStatic()函数打印其值。
  • 通过这种方式,file1.c可以读取file2.c中的globalStatic变量,但不能修改它。

在这里插入图片描述

4. 常见陷阱与注意事项

4.1 重复定义

  • 定义:如果在多个文件中定义了同一个extern变量,会导致链接错误。

  • 解决方案:确保变量只在一个文件中定义,其他文件中只声明。

    详细说明

    • 如果多个源文件中有相同的extern变量定义,编译器会在链接阶段报错,提示多重定义。
    • 为了避免这种情况,应确保每个extern变量只在一个源文件中定义。
    • 此外,可以使用条件编译指令(如#ifdef#ifndef)来确保在某个文件中只定义一次。

4.2 作用域问题

  • 定义:如果误用了staticextern,可能会导致作用域问题。

  • 解决方案:仔细检查变量的声明位置,确保使用正确的关键字。

    详细说明

    • 如果在不需要跨文件可见性的场景下使用了extern,可能会导致不必要的全局变量暴露。
    • 同样,如果在需要跨文件可见性的场景下使用了static,可能会导致变量无法被正确访问。
    • 为避免此类问题,建议在使用staticextern时,先明确思考变量的作用域需求,再选择合适的关键字。

4.3 最小化全局变量的使用

  • 定义:尽可能减少全局变量的使用,以降低代码间的耦合度。

  • 理由:过度使用全局变量会使程序难以理解和维护。

    详细说明

    • 全局变量可以在程序的任何地方被修改,这可能导致程序行为难以预测。
    • 使用局部变量或传递参数的方式可以更好地控制变量的可见性和修改权限。
    • 通过减少全局变量的使用,可以提高代码的模块化程度,使代码更易于理解和维护。

4.4 明确作用域

  • 定义:始终使用显式的作用域关键字,避免使用默认作用域。

  • 理由:显式指定作用域可以减少潜在的错误,并使代码更容易阅读。

    详细说明

    • 默认情况下,未声明作用域的变量具有文件范围的作用域,这可能导致意外的变量覆盖。
    • 使用staticextern关键字显式声明变量的作用域,可以使代码更加清晰和可维护。
    • 通过显式声明作用域,可以避免意外的全局变量覆盖,减少潜在的编程错误。

4.5 文档清晰

  • 定义:在声明变量时提供注释,说明其用途和预期的行为。

  • 理由:良好的文档有助于其他开发者更快地理解代码逻辑。

    详细说明

    • 为每个关键变量提供注释,说明其用途、预期的行为以及使用时的注意事项。
    • 清晰的文档可以帮助新加入项目的开发者快速上手,并减少误解。
    • 在使用staticextern关键字时,尤其要注意提供详细的注释,因为这些关键字涉及到变量的作用域和可见性,这对理解代码至关重要。

5. 性能考量与优化技巧

5.1 初始化成本

  • 定义:静态变量在程序启动时会自动初始化,这可能会带来一定的初始化开销。

  • 理由:如果静态变量需要复杂的初始化过程,这可能会影响程序启动速度。

    详细说明

    • 静态变量的初始化发生在程序的早期阶段,因此需要谨慎处理复杂的初始化逻辑。
    • 如果可能,尽量减少静态变量的初始化复杂度,以加快程序启动速度。
    • 对于需要复杂初始化的静态变量,可以考虑将其改为动态初始化,即在首次使用时进行初始化。

5.2 内存使用

  • 定义:静态变量在程序整个生命周期中都占用内存,这可能会影响内存管理。

  • 理由:如果程序中包含大量静态变量,可能会导致内存浪费。

    详细说明

    • 静态变量在整个程序执行过程中占用内存,即使不再需要时也不释放。
    • 对于不再使用的静态变量,应考虑是否可以通过其他方式管理,例如使用动态分配的变量。
    • 在资源受限的环境中,合理管理静态变量的内存使用尤为重要。

5.3 延迟初始化

  • 定义:如果可能的话,可以将静态变量的初始化推迟到首次使用时进行。

  • 理由:这样可以减少程序启动时的初始化开销。

    详细说明

    • 通过延迟初始化静态变量,可以在程序启动时减少不必要的初始化开销。
    • 这种技术特别适用于那些初始化过程较为复杂的静态变量。
    • 使用条件判断语句或函数来实现延迟初始化是一种常见的做法。

5.4 手动内存管理

  • 定义:对于大型静态数据结构,可以考虑使用手动内存管理技术来减少内存占用。

  • 理由:手动管理内存可以更有效地利用资源,特别是在资源受限的环境中。

    详细说明

    • 对于大型静态数据结构,可以考虑使用手动内存管理技术,如内存池,来减少内存碎片和提高内存利用率。
    • 在资源受限的环境中,这种技术尤为重要,因为它可以帮助节省宝贵的内存资源。
    • 手动管理内存还可以帮助开发者更好地控制内存的分配和回收过程。

6. 总结

通过本章的学习,我们深入了解了C语言中staticextern关键字的功能及其对变量作用域、可见性和存储类的影响。我们探讨了局部静态变量和全局静态变量的特点,以及如何通过extern关键字实现跨文件的变量共享。此外,我们还讨论了使用这些关键字时需要注意的一些常见陷阱,并提供了一些优化技巧,以帮助开发者编写更高效、更易于维护的代码。

  • 局部静态变量:在函数内部声明,但其生存周期跨越整个程序执行过程。
  • 全局静态变量:在整个源文件中可见,但在其他源文件中不可见。
  • 外部变量:可以在多个源文件之间共享,通过extern关键字声明。
  • 函数声明:使用extern声明函数,通常在头文件中进行。
  • 综合使用staticextern可以结合使用,以达到特定的效果,例如在不同文件间共享静态变量。
  • 常见陷阱:避免重复定义和作用域问题,最小化全局变量的使用,明确作用域,保持文档清晰。
  • 性能考量:注意初始化成本和内存使用,考虑使用延迟初始化和手动内存管理技术。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2117356.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

高职人工智能训练师边缘计算实训室解决方案

一、引言 随着物联网&#xff08;IoT&#xff09;、大数据、人工智能&#xff08;AI&#xff09;等技术的飞速发展&#xff0c;计算需求日益复杂和多样化。传统的云计算模式虽在一定程度上满足了这些需求&#xff0c;但在处理海量数据、保障实时性与安全性、提升计算效率等方面…

jenkins 部署应用到多个环境

在日常开发的过程中&#xff0c;我们经常会遇到将应用程序部署到多个环境的需求场景&#xff0c;如会先发布到测试环境&#xff0c;由测试人员进行测试&#xff0c;成功之后&#xff0c;会继续将当前应用部署到集成环境&#xff0c;进行集成测试&#xff0c;全部通过后&#xf…

单位权中误差 详细介绍

单位权中误差&#xff08;Unit Weight Error, UWE&#xff09;是用于描述测量数据不确定性的一个统计量&#xff0c;特别是在地理信息系统&#xff08;GIS&#xff09;、导航和定位系统中。它主要用于评估和比较不同测量系统或算法的精度。以下是对单位权中误差的详细介绍&…

C++第一节入门

一、历史 C是在C上继承拓展的&#xff01; java是一家公司&#xff08;甲骨文&#xff09;借鉴C生成的&#xff01; C#是微软借鉴java生成的&#xff01; 二、命名空间 当我们定义一个名叫rand的变量&#xff0c;但是由于stdlib头文件里面有个函数跟rand重名&#xff01;因此…

如何在 Linux 系统中禁用用户登录 ?

管理 Linux 系统上的帐户是系统管理员的一项重要任务。一个常见的任务是禁用帐户&#xff0c;由于各种原因可能需要禁用帐户&#xff0c;例如当员工离开公司或出于安全目的需要临时禁用访问时。 本指南将以简单易懂的步骤引导您完成在 Linux 系统上禁用帐户的过程。 Step 1: …

Ruoyi Cloud 本地启动

参考 http://doc.ruoyi.vip/ https://gitee.com/y_project/RuoYi-Cloud https://blog.csdn.net/cs_dnzk/article/details/135289966 https://doc.ruoyi.vip/ruoyi-cloud/cloud/seata.html#%E5%9F%BA%E6%9C%AC%E4%BB%8B%E7%BB%8D 拉取代码本地跑通 用 git 从 ruoyi 微服务版仓…

HCIA--实验十一:单区域OSPF路由实验

一、实验内容 1.需求/要求&#xff1a; 使用三个路由器互联&#xff0c;各自配置一个loopback接口&#xff0c;在三个路由器上配置ospf动态路由协议&#xff1b; 二、实验过程 1.拓扑图&#xff1a; 2.步骤&#xff1a; 1.router配置基本信息 各接口的ip地址、loopback接…

华为OD机试真题 - 特殊的加密算法 - 深度优先搜索DFS(Python/JS/C/C++ 2024 D卷 200分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…

C++:priority_queue(优先级队列)的模拟实现

目录 一、什么是优先级队列 二、优先级队列的定义 三、优先级队列的常用接口 四、模拟实现一个优先级队列 1、posh接口 2、empty接口、size接口和top接口 3、pop接口 4、构造函数 五、整体代码 一、什么是优先级队列 首先优先级队列不是队列&#xff0c;C 中的优先队…

IP包头的总长度字段和UDP包头的长度字段之间的关系

IP包头的总长度字段和UDP包头的长度字段之间的关系&#xff0c;并通过实例加以说明。 IP包头的总长度字段 **总长度&#xff08;Total Length&#xff09;**字段是一个16位的字段&#xff0c;表示整个IP数据包的总长度&#xff0c;包括IP包头和数据部分。单位是字节。由于该字…

AI一键生成 PPT

AI一键生成 PPT 操作步骤 作为一名打工人&#xff0c;是不是经常需要制作各种PPT来分享我的生活和想法。但是&#xff0c;你们知道&#xff0c;有时候灵感来了&#xff0c;时间却不够用了&#xff01;&#x1f629;直到我发现了Kimi AI——一个能够自动生成PPT的神奇助手&#…

PID控制算法(二)

&#xff08;BIlibili借鉴&#xff09;PID参数整定连接&#xff1a;Webpack App (rossning92.github.io) C的基本程序代码&#xff1a; 借鉴链接&#xff1a;PID超详细教程——PID原理串级PIDC代码在线仿真调参-CSDN博客 #include <iostream>using namespace std;stru…

【信创】统信UOS桌面UT-2024-0027漏洞修复

原文链接&#xff1a;【信创】统信UOS系统UT-2024-0027漏洞修复 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于统信UOS桌面操作系统UT-2024-0027漏洞修复的文章。漏洞修复是确保系统安全性的重要步骤&#xff0c;及时更新和修复系统中的安全漏洞&#xff0c;可…

大数据之Flink(三)

9.3、转换算子 9.3.1、基本转换算子 9.3.1.1、映射map 一一映射 package transform;import bean.WaterSensor; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; impor…

Go开源日志库Logrus的使用

一、Logrus简介 Logrus 是一个流行的 Go 语言日志库&#xff0c;以其功能强大、性能高效和高度灵活性而闻名。有关更多介绍可查看 Logrus。 主要特点 丰富的日志级别&#xff1a;Logrus 支持多种日志级别&#xff0c;包括 Debug、Info、Warn、Error、Fatal 和 Panic&#xf…

深入理解数据库的 4NF:多值依赖与消除数据异常

在数据库设计中&#xff0c; "范式" 是一个常常被提到的重要概念。许多初学者在学习数据库设计时&#xff0c;经常听到第一范式&#xff08;1NF&#xff09;、第二范式&#xff08;2NF&#xff09;、第三范式&#xff08;3NF&#xff09;以及 BCNF&#xff08;Boyce-…

RESTful 还是 JSON-RPC

前言 RESTful 比较简单地说就是&#xff0c;大家请求一样的url&#xff08;GET方法有一个例外&#xff0c;url中带了一个id&#xff09;&#xff0c;通过不同的请求方法&#xff0c;分别进行不同的操作&#xff08;CRUD&#xff09;。 JSON-RPC JSON-RPC是一个无状态且轻量级…

SpringBoot学习(7)(Bean对象注册)(自定义组合注解)

目录 一、引言 二、案例学习 &#xff08;一&#xff09;Bean &#xff08;二&#xff09;Import 三、补充 &#xff08;1&#xff09;关于Java中collection.toArray(new String[0])解释 &#xff08;2&#xff09;组合注解 一、引言 上次学习了解到&#xff0c;springb…

基于机器学习的阿尔兹海默症智能分析预测系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 阿尔兹海默症&#xff08;Alzheimers Disease, AD&#xff09;是一种常见的神经退行性疾病&#xff0c;主要影响老年人的认知功能。随着全球人口老龄化的加剧&#xff0c;阿尔兹海默症的患病率逐年…