【C语言入门】解锁核心关键字的终极奥秘与实战应用(三)

news2025/2/4 21:39:34

目录

一、auto

1.1. 作用

1.2. 特性

1.3. 代码示例

二、register

2.1. 作用

2.2. 特性

2.3. 代码示例

三、static

3.1.  修饰局部变量

3.2. 修饰全局变量

3.3. 修饰函数

四、extern

4.1. 作用

4.2. 特性

4.3. 代码示例

五、volatile

5.1. 作用

5.2. 代码示例

六、总结


接上一篇https://blog.csdn.net/weixin_37800531/article/details/145430862?sharetype=blogdetail&sharerId=145430862&sharerefer=PC&sharesource=weixin_37800531&spm=1011.2480.3001.8118继续分析。

、auto

在C语言中,auto 关键字用于修饰局部变量,尽管在实际编程中,我们往往省略这个关键字,因为局部变量默认就是自动存储类型(auto)的。auto 关键字的主要作用是显式地表明变量的存储类型,并帮助程序员更好地理解代码。

1.1. 作用

  • 修饰局部变量,表明该变量是自动存储类型的。
  • 虽然通常省略,但在某些情况下,显式使用 auto 可以提高代码的可读性。

1.2. 特性

  • 自动分配内存:当函数被调用时,auto 变量会在栈上自动分配内存。
  • 自动释放内存:当函数返回时,auto 变量所占用的内存会自动释放,变量失效。
  • 生命周期auto 变量的生命周期仅限于定义它的函数或代码块内。

1.3. 代码示例

虽然auto关键字通常被省略,但以下示例可以展示其用法:

#include <stdio.h>  
  
void myFunction() {  
    auto int myAutoVar = 10; // 显式使用 auto 关键字,但通常可以省略  
    printf("Value of myAutoVar: %d\n", myAutoVar);  
    // myAutoVar 在这里有效,但函数返回后将自动释放内存  
}  
  
int main() {  
    myFunction();  
    // printf("Value of myAutoVar: %d\n", myAutoVar); // 错误:myAutoVar 在这里无效  
    return 0;  
}

myAutoVar 是一个 auto 类型的局部变量,它在 myFunction 函数内部被定义并初始化。当 myFunction 被调用时,myAutoVar 会在栈上分配内存。当 myFunction 返回时,myAutoVar 所占用的内存会自动释放,变量失效。因此,在 main 函数中尝试访问 myAutoVar 会导致编译错误。

运行结果: 

需要注意的是,由于局部变量默认就是 auto 类型的,所以在实际编程中,我们通常会省略 auto 关键字。

二、register

在C语言中,register 关键字用于向编译器提出建议,希望编译器能够将特定的变量存储在CPU的寄存器中,以便提高对该变量的访问速度。寄存器是CPU内部的一种高速存储单元,其访问速度远快于内存。因此,如果某个变量被频繁地读取,且不会被修改,那么将其存储在寄存器中可以显著提高程序的性能。

2.1. 作用

  • register 关键字的主要作用是向编译器发出建议,希望编译器能够优化变量的存储位置,将其从内存移动到寄存器中。
  • 然而,需要注意的是,这只是一个建议,编译器并不一定会采纳。编译器会根据自身的优化策略、寄存器的可用性以及变量的使用情况来决定是否将变量存储在寄存器中。

2.2. 特性

  • 适用于频繁读取且不会被修改的局部变量:由于寄存器的数量有限,且读写寄存器需要消耗一定的CPU资源,因此register关键字最适合用于那些被频繁读取且不会被修改的局部变量。

  • 编译器可能会忽略此建议:如前所述,register只是一个建议,编译器并不一定会采纳。编译器会根据实际情况来决定是否将变量存储在寄存器中。

  • 不能用于全局变量和静态变量:由于全局变量和静态变量在程序的整个生命周期内都有效,且可能会被多个函数访问和修改,因此它们不适合存储在寄存器中。

2.3. 代码示例

下面是一个使用register关键字的简单示例:

#include <stdio.h>  
  
void count_loops(int n) {  
    register int i; // 建议编译器将i存储在寄存器中  
    for (i = 0; i < n; i++) {  
        // 循环体为空,仅用于演示  
    }  
    printf("Loop count: %d\n", i); // 此时i的值应为n  
}  
  
int main() {  
    count_loops(1000000); // 调用函数,传入一个较大的值以演示效果  
    return 0;  
}

使用了register关键字来建议编译器将循环变量i存储在寄存器中。然而,需要注意的是,编译器可能会忽略这个建议,并将i存储在内存中。此外,即使编译器采纳了这个建议,由于寄存器的数量有限,如果程序中使用了大量的register变量,那么编译器也可能无法将它们全部存储在寄存器中。 

运行结果:

在使用register关键字时,我们需要保持谨慎,并意识到它只是一个建议,而不是一个强制性的要求。同时,我们还需要通过实际的性能测试来验证编译器是否采纳了我们的建议,并评估其对程序性能的影响。 

三、static

在C语言中,static 关键字有多种用途,包括修饰局部变量、全局变量和函数。

3.1.  修饰局部变量

static修饰局部变量时,它会延长该变量的生命周期至整个程序运行期间,但变量的作用域仍然保持不变,即只能在定义它的函数或代码块内部访问。意味着,即使函数执行完毕,该变量的值也会保留下来,供下次函数调用时使用。

  • 代码示例:
#include <stdio.h>  
  
void functionWithStaticVar() {  
    static int count = 0; // 静态局部变量,只在第一次调用时初始化  
    count++;  
    printf("Count: %d\n", count);  
}  
  
int main() {  
    functionWithStaticVar(); // 输出:Count: 1  
    functionWithStaticVar(); // 输出:Count: 2  
    functionWithStaticVar(); // 输出:Count: 3  
    return 0;  
}

count是一个静态局部变量。每次调用functionWithStaticVar函数时,count的值都会递增,并且在下次函数调用时保留下来。

  • 实际运行结果: 

3.2. 修饰全局变量

static修饰全局变量时,它会限制该变量的作用域,使其只能在定义它的文件内部访问。有助于避免不同文件之间的命名冲突。

  • 代码示例
// file1.c  
#include <stdio.h>  
  
static int globalVar = 100; // 静态全局变量,只能在file1.c内部访问  
  
void printGlobalVar() {  
    printf("GlobalVar in file1.c: %d\n", globalVar);  
}  
  
// file2.c  
#include <stdio.h>  
  
// 尝试访问file1.c中的globalVar会导致编译错误  
// extern int globalVar; // 注释掉这行以避免编译错误  
  
// void printGlobalVarFromFile2() {  
//     printf("GlobalVar in file2.c: %d\n", globalVar); // 这行会导致链接错误  
// }  
  
int main() {  
    // printGlobalVarFromFile2(); // 注释掉这行以避免编译错误  
    printGlobalVar(); // 调用file1.c中的函数来打印globalVar  
    return 0;  
}

globalVar是一个静态全局变量,它只能在file1.c内部访问。如果尝试在file2.c中访问它,会导致编译或链接错误。

3.3. 修饰函数

static修饰函数时,它会使该函数只能在定义它的文件内部使用,防止外部链接。有助于隐藏函数的实现细节,减少命名冲突的可能性。

  • 代码示例
// file1.c  
#include <stdio.h>  
  
static void internalFunction() {  
    printf("This is an internal function in file1.c\n");  
}  
  
void externalFunction() {  
    internalFunction(); // 调用内部函数  
    printf("This is an external function in file1.c\n");  
}  
  
// file2.c  
#include <stdio.h>  
  
// 尝试调用file1.c中的internalFunction会导致链接错误  
// void callInternalFunction() {  
//     internalFunction(); // 这行会导致链接错误  
// }  
  
int main() {  
    externalFunction(); // 调用file1.c中的外部函数  
    // callInternalFunction(); // 注释掉这行以避免链接错误  
    return 0;  
}

internalFunction是一个静态函数,它只能在file1.c内部使用。如果尝试在file2.c中调用它,会导致链接错误。而externalFunction是一个外部函数,它可以在其他文件中被调用。

四、extern

在C语言编程中,extern 关键字扮演着至关重要的角色,它允许我们声明在其他文件中定义的变量或函数,从而实现跨文件的资源共享。这是模块化编程的基础,使得我们可以将程序拆分为多个文件,每个文件负责特定的功能或数据结构,然后在需要时通过 extern 声明来访问这些外部定义的资源。

4.1. 作用

extern 关键字的主要作用是声明一个变量或函数是在其他文件中定义的,这样在当前文件中就可以访问到这个变量或函数。它是实现跨文件链接和访问的关键机制。

4.2. 特性

  • 跨文件访问extern 允许我们访问在其他文件中定义的变量或函数。
  • 声明顺序:在使用 extern 声明的变量或函数之前,编译器需要知道它们的存在。因此,extern 声明通常放在文件的开头部分,或者在变量或函数被实际使用之前。
  • 模块化编程extern 是模块化编程的基础,使得我们可以将程序拆分为多个独立的文件,每个文件都可以定义自己的变量和函数,并通过 extern 声明来访问其他文件中的资源。

4.3. 代码示例

下面是一个简单的示例,展示如何使用 extern 关键字来实现跨文件访问变量和函数。

  • file1.c
#include <stdio.h>  
  
// 定义一个全局变量  
int globalVar = 42;  
  
// 定义一个函数  
void printGlobalVar() {  
    printf("GlobalVar in file1.c: %d\n", globalVar);  
}
  • file1.h
// 在头文件中使用 extern 来声明 file1.c 中定义的变量和函数  
extern int globalVar;  
extern void printGlobalVar();
  •  file2.c
#include <stdio.h>  
#include "file1.h" // 包含 file1.c 的头文件以访问其声明的变量和函数  
  
int main() {  
    // 访问 file1.c 中定义的全局变量  
    printf("Accessing globalVar from file2.c: %d\n", globalVar);  
      
    // 调用 file1.c 中定义的函数  
    printGlobalVar();  
      
    return 0;  
}

 

file1.c 定义了一个全局变量 globalVar 和一个函数 printGlobalVar()。然后,在 file1.h 中使用 extern 关键字来声明这些变量和函数,以便在其他文件中访问它们。最后,在 file2.c 中,我们包含了 file1.h 头文件,从而能够访问 file1.c 中定义的变量和函数。

通过这种方式,我们可以将程序拆分为多个文件,每个文件负责特定的功能或数据结构,然后通过 extern 声明和头文件来实现跨文件的资源共享和访问。这是模块化编程的核心思想之一。

五、volatile

volatile 关键字在C/C++等编程语言中用于告诉编译器,某个变量的值可能会在程序的控制流之外被改变。这通常发生在硬件访问、多线程编程或中断服务程序中。使用 volatile 可以防止编译器对该变量进行优化,从而确保每次访问该变量时都能读取其最新的值。

5.1. 作用

  • 防止优化:编译器在优化代码时,可能会将变量的值缓存在寄存器中,以减少对内存的访问。如果变量被声明为 volatile,编译器就不会进行这种优化,而是每次访问该变量时都直接从内存中读取其值。

  • 硬件访问:在嵌入式系统编程中,volatile 常用于访问硬件寄存器的值。这些寄存器的值可能会由硬件本身或其他外部设备改变,因此需要使用 volatile 来确保每次都能读取到最新的值。

  • 多线程编程:在多线程环境中,一个线程可能会修改另一个线程中的变量。虽然C/C++标准并没有将 volatile 定义为线程之间的同步机制,但在某些平台上,使用 volatile 可以防止编译器对共享变量的优化,从而增加线程间通信的可靠性(尽管这不是跨平台或标准的方法,通常应使用同步原语如互斥锁)。

  • 中断服务程序:在中断服务程序中,全局变量的值可能会由中断处理程序改变。使用 volatile 可以确保主程序在访问这些变量时能够读取到最新的值。

5.2. 代码示例

示例1:硬件寄存器访问

#include <stdint.h>  
  
// 假设有一个硬件寄存器的地址是0x40000000  
#define HARDWARE_REGISTER *((volatile uint32_t *)0x40000000)  
  
int main() {  
    // 读取硬件寄存器的值  
    uint32_t value = HARDWARE_REGISTER;  
    // 对寄存器进行写操作  
    HARDWARE_REGISTER = 0xDEADBEEF;  
    // 再次读取寄存器的值  
    value = HARDWARE_REGISTER;  
    return 0;  
}

HARDWARE_REGISTER 是一个宏,它定义了一个指向硬件寄存器地址的 volatile 指针。确保了每次访问 HARDWARE_REGISTER 时都会直接从硬件寄存器中读取或写入值。

示例2:多线程编程中的共享变量

#include <stdio.h>  
#include <pthread.h>  
#include <unistd.h>  
  
// 注意:这不是跨平台或标准的方法来实现线程间同步  
volatile int shared_variable = 0;  
  
void *thread_function(void *arg) {  
    // 模拟一些工作  
    sleep(1);  
    // 修改共享变量的值  
    shared_variable = 1;  
    return NULL;  
}  
  
int main() {  
    pthread_t thread;  
    pthread_create(&thread, NULL, thread_function, NULL);  
  
    // 等待线程完成工作  
    while (shared_variable == 0) {  
        // 这里可能会进行忙等待,但这不是推荐的做法  
        // 在实际应用中,应该使用条件变量、信号量等同步原语  
    }  
  
    printf("Shared variable has been changed by the thread.\n");  
    pthread_join(thread, NULL);  
    return 0;  
}

shared_variable 是一个 volatile 变量,它在多线程环境中被共享。虽然 volatile 在这里可以防止编译器对 shared_variable 的优化,但它并不能保证线程之间的同步。在实际应用中,应该使用互斥锁、条件变量等同步原语来确保线程之间的正确同步。

volatile 并不提供原子性保证,即它不能保证对 volatile 变量的读写操作是原子的。在多线程环境中,即使使用了 volatile,也可能需要额外的同步机制来确保对共享变量的正确访问。

六、总结

C语言共有32个关键字。这些关键字根据作用可以分为以下四类:

  • 数据类型关键字(12个):用于声明变量的数据类型。包括char、double、float、int、long、short、signed、unsigned、struct、union、enum、void等。
  • 控制语句关键字(12个):用于控制程序的流程。包括for、do、while、break、continue、if、else、goto、switch、case、default、return等。
  • 存储类型关键字(4个或5个):用于声明变量的存储类型。常见的包括auto、extern、static、register等,有时也将typedef视为存储类型关键字的一种,尽管其主要功能是为数据类型取别名。
  • 其他关键字(3个或4个):包括const(声明只读变量)、sizeof(计算数据类型长度)、volatile(说明变量在程序执行中可被隐含地改变),有时不包括typedef,因其主要功能是数据类型定义。

这些关键字在C语言编程中具有重要的作用,是构成C语言程序的基本元素之一。掌握这些关键字的使用方法和注意事项,对于学习C语言和进行C语言编程至关重要。

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

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

相关文章

音标-- 02-- 重音 音节 变音

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 国际音标1.重音2.音节3.变音 国际音标 1.重音 2.音节 3.变音

[STM32 标准库]EXTI应用场景 功能框图 寄存器

一、EXTI 外部中断在嵌入式系统中有广泛的应用场景&#xff0c;如按钮开关控制&#xff0c;传感器触发&#xff0c;通信接口中断等。其原理都差不多&#xff0c;STM32会对外部中断引脚的边沿进行检测&#xff0c;若检测到相应的边沿会触发中断&#xff0c;在中断中做出相应的处…

C语言练习【互斥锁、信号量线程同步、条件变量实现生产者消费者模型】

练习1 请使用互斥锁 和 信号量分别实现5个线程之间的同步 互斥锁实现同步 #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>…

w190工作流程管理系统设计与实现

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…

linux下ollama更换模型路径

Linux下更换Ollama模型下载路径指南   在使用Ollama进行AI模型管理时&#xff0c;有时需要根据实际需求更改模型文件的存储路径。本文将详细介绍如何在Linux系统中更改Ollama模型的下载路径。 一、关闭Ollama服务   在更改模型路径之前&#xff0c;需要先停止Ollama服务。…

编程题-电话号码的字母组合(中等)

题目&#xff1a; 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 解法一&#xff08;哈希表动态添加&#xff09;&#x…

浅谈《图解HTTP》

感悟 滑至尾页的那一刻&#xff0c;内心突兀的涌来一阵畅快的感觉。如果说从前对互联网只是懵懵懂懂&#xff0c;但此刻却觉得她是如此清晰而可爱的呈现在哪里。 介绍中说&#xff0c;《图解HTTP》适合作为第一本网络协议书。确实&#xff0c;它就像一座桥梁&#xff0c;连接…

架构知识整理与思考(其四)

书接上回 建议&#xff0c;没有看过上一章的可以看一下&#xff0c;上一章“架构知识整理与思考&#xff08;其二&#xff09;” 感觉这都成链表了。 三生万物 软件架构 终于&#xff0c;我们进入了具体的软件架构讨论中。 软件架构是什么&#xff1f;相关定义如下&#xf…

【C++】B2124 判断字符串是否为回文

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;题目描述输入格式&#xff1a;输出格式&#xff1a;样例&#xff1a; &#x1f4af;方法一&#xff1a;我的第一种做法思路代码实现解析 &#x1f4af;方法二&#xff1a;我…

基于Spring Security 6的OAuth2 系列之八 - 授权服务器--Spring Authrization Server的基本原理

之所以想写这一系列&#xff0c;是因为之前工作过程中使用Spring Security OAuth2搭建了网关和授权服务器&#xff0c;但当时基于spring-boot 2.3.x&#xff0c;其默认的Spring Security是5.3.x。之后新项目升级到了spring-boot 3.3.0&#xff0c;结果一看Spring Security也升级…

算法题(48):反转链表

审题&#xff1a; 需要我们将链表反转并返回头结点地址 思路&#xff1a; 一般在面试中&#xff0c;涉及链表的题会主要考察链表的指向改变&#xff0c;所以一般不会允许我们改变节点val值。 这里是单向链表&#xff0c;如果要把指向反过来则需要同时知道前中后三个节点&#x…

梯度、梯度下降、最小二乘法

在求解机器学习算法的模型参数&#xff0c;即无约束优化问题时&#xff0c;梯度下降是最常采用的方法之一&#xff0c;另一种常用的方法是最小二乘法。 1. 梯度和梯度下降 在微积分里面&#xff0c;对多元函数的参数求∂偏导数&#xff0c;把求得的各个参数的偏导数以向量的形式…

独立开发者小程序开发变现思路

随着移动互联网的发展&#xff0c;小程序已成为许多独立开发者展示才能和实现收入的重要平台。作为一种轻量级的应用形态&#xff0c;小程序具有开发成本低、用户体验好、传播效率高等优势&#xff0c;为独立开发者提供了多种变现方式。然而&#xff0c;要想实现真正的盈利&…

软件测试 - 概念篇

目录 1. 需求 1.1 用户需求 1.2 软件需求 2. 开发模型 2.1 软件的生命周期 2.2 常见开发模型 2.2.1 瀑布模型 2.2.2 螺旋模型 1. 需求 对于软件开发而言, 需求分为以下两种: 用户需求软件需求 1.1 用户需求 用户需求, 就是用户提出的需求, 没有经过合理的评估, 通常…

使用SpringBoot发送邮件|解决了部署时连接超时的bug|网易163|2025

使用SpringBoot发送邮件 文章目录 使用SpringBoot发送邮件1. 获取网易邮箱服务的授权码2. 初始化项目maven部分web部分 3. 发送邮件填写配置EmailSendService [已解决]部署时连接超时附&#xff1a;Docker脚本Dockerfile创建镜像启动容器 1. 获取网易邮箱服务的授权码 温馨提示…

基于springboot+vue的航空散货调度系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

[MRCTF2020]Ez_bypass1(md5绕过)

[MRCTF2020]Ez_bypass1(md5绕过) ​​ 这道题就是要绕过md5强类型比较&#xff0c;但是本身又不相等&#xff1a; md5无法处理数组&#xff0c;如果传入的是数组进行md5加密&#xff0c;会直接放回NULL&#xff0c;两个NuLL相比较会等于true&#xff1b; 所以?id[]1&gg…

MATLAB实现多种群遗传算法

多种群遗传算法&#xff08;MPGA, Multi-Population Genetic Algorithm&#xff09;是一种改进的遗传算法&#xff0c;它通过将种群分成多个子种群并在不同的子种群之间进行交叉和交换&#xff0c;旨在提高全局搜索能力并避免早期收敛。下面是多种群遗传算法的主要步骤和流程&a…

强化学习笔记(5)——PPO

PPO视频课程来源 首先理解采样期望的转换 变量x在p(x)分布下&#xff0c;函数f(x)的期望 等于f(x)乘以对应出现概率p(x)的累加 经过转换后变成 x在q(x)分布下&#xff0c;f(x)*p(x)/q(x) 的期望。 起因是&#xff1a;求最大化回报的期望&#xff0c;所以对ceta求梯度 具体举例…

【MATLAB例程】TOA和AOA混合的高精度定位程序,适用于三维、N锚点的情况

代码实现了一个基于到达角&#xff08;AOA&#xff09;和到达时间&#xff08;TOA&#xff09;混合定位的例程。该算法能够根据不同基站接收到的信号信息&#xff0c;自适应地计算目标的位置&#xff0c;适用于多个基站的场景 文章目录 主要功能代码结构运行结果程序代码 主要功…