extern的使用详解(多文件编程)——C语言
extern——关键字
extern是C语言中的一个关键字,一般用在变量名前或函数名前,作用是用来说明“此变量/函数是在别处定义的,要在此处引用”,extern这个关键字大部分读者应该是在变量的存储类型这一类的内容中
遇到的,下面先分析C语言不同的存储类型
在C语言中变量和函数有数据类型和存储类型两个属性,因此变量定义的一般形式为:存储类型 数据类型 变量名表;
C语言提供了一下几种不同的存储类型:
(1) 自动变量(auto)
(2) 静态变量(static)
(3) 外部变量(extern)
(4) 寄存器变量(register)
(上面的auto、static、extern、register都是C语言的关键字),这里只分析extern关键字的使用
外部变量(全局变量)extern----全局静态存储区
标准定义格式:extern 类型名 变量名;
如果在所有函数之外定义的变量没有指定其存储类别,那么它就是一个外部变量,它的作用域是从它的定义点到本文件的末尾(在单个源文件中的确是这样,如果有多个源文件,全局变量的作用范围不是从变量定义处到该文件结尾,而是在其他文件中也有效),但是如果要在定义点之前或者其他文件中使用它,那么就需要使用关键字extern对其进行声明(注意不是定义,编译器并不为其分配内存)
Tips:
定义:表示创建变量或分配存储单元
声明:说明变量的性质,但并不分配存储单元
extern int i; //是声明,不是定义,没有分配内存
int i; //是定义
如果在声明的时候给变量赋值,那么就和去掉extern直接定义变量赋值是等价的
extern int a = ;//尽量不要写这种定义方式
int a = ;//上述两条语句等价
(注意上面的不同语句对声明和定义的区分,对源文件中的局部变量来说是成立的(也就是.c文件),而对于源文件中的全局变量(外部变量)int a和在头文件中的int a就不能用上面的语句来解释声明和定义的区别)
补充:定义和声明的一个小坑,对于int a;来说,在源文件中,如果是全局变量的话就是声明,如果是局部变量的话就是定义
全局变量:


文章前面提到过一句话:如果在所有函数之外定义的变量没有指定其存储类别,那么它就是一个外部变量,意思就是这里的int a;等价于extern int a;相当于声明,声明是可以多次的
局部变量:


文章中只提到了extern(外部变量),这里补充下auto(自动变量),由于auto(自动变量)极为常用,所以C语言把它设计成缺省的存储类型,即auto 可以省略不写,在main函数内部的变量int a也是局部变量,
相当于auto int a;相当于定义,定义只能一次

谨记:声明可以多次,定义只能一次
外部变量保存在静态存储区内,在程序运行期间分配固定的存储单元,其生存期是整个程序的运行期,没有显式初始化的外部变量由编译程序自动初始化为0(extern说明符来扩展全局变量的作用域,
可以将全局变量的作用域扩展到其他文件,但不能限制全局变量的作用域)
extern作用于函数名和变量名时的区别:
读者应该会发现,函数声明时并没有使用 extern 关键字,这是因为,函数的定义有函数体,函数的声明没有函数体,编译器很容易区分定义和声明,所以对于函数声明来说,有没有extern 都是一样的
但是作用于变量名时extern关键字就不是可有可无的了,全局变量在外部使用声明时,extern关键词是必须的,如果变量无extern修饰且没有显式的初始化,就成为了变量的定义,因此此时必须加extern,
(全局变量在不指定初值时会自动初始化为0)
多文件编程
C语言代码是由上到下依次执行的,不管是变量还是函数,原则上都要先定义再使用,否则就会报错。但在实际开发中,经常会在函数或变量定义之前就使用它们,这个时候就需要提前声明(extern)
头文件中包含的都是函数声明,而不是函数定义
最好不要在头文件中定义变量,例如全局变量

这里的int a是个全局变量的定义,所以如果这个头文件被多次引用的话,a会被重复定义,这显然是不允许的,下面举一个简单多文件编程的例子




从上面的四张图可以看出,一共用了3个文件,两个c文件(源文件)一个h文件(头文件),我在my.c中写了一个函数名为Max的函数,在头文件中声明了这个Max函数,在有main的这个test1.c这个
源文件中用#include "my.h"包含了这个头文件,然后在函数中就可以直接使用Max函数了(注意头文件中只是声明,没有定义,而且不建议在头文件中定义函数,会引起很多不必要的麻烦)
在《高质量C/C++编程指南》一书中,对此也有说明:
【建议1-2-1】头文件中只存放“声明”,而不存放“定义”。
在C++语法中,类的成员函数可以在声明的同时被定义,并且自动成为内联函数。这虽然会带来书写上的方便,但却造成了风格不一致,弊大于利。建议将成员函数的定义分开,不论该函数体有多么小。
【建议1-2-2】不提倡使用全局变量,尽量不要在头文件中出现现象 extern int value 这类声明。
作者:奄奄不息 
原文:https://blog.csdn.net/qq_41209741/article/details/84108962 
问题一:如果在my.c这个文件中定义了一个全局变量,想要在main函数中使用,该怎么做呢?
方法一:在含有main函数的源文件test1.c中加extern声明



运行结果:

方法二:在头文件中my.h中对my.c中的全局变量进行声明,再在test1.c中include头文件my.h



运行结果:

防止C语言头文件被重复包含
头文件包含命令 #include 的效果与直接复制粘贴头文件内容的效果是一样的,预处理器实际上也是这样做的,它会读取头文件的内容,然后输出到 #include 命令所在的位置,头文件包含是一个
递归(循环)的过程,如果被包含的头文件中还包含了其他的头文件,预处理器会继续将它们也包含进来;这个过程会一直持续下去,直到不再包含任何头文件,这与递归的过程颇为相似,
递归包含会导致一个问题,就是重复引入同一个头文件,重复引入同一头文件有什么问题呢,当你在头文件中定义变量或者函数时(注意是定义不是声明,多次声明是没有问题的)多次引入头文件就会报“变量被多次定义”的错误
下面来还原这种错误:





上面一共使用了3个头文件k1.h、k2.h、my.h在k1.h和k2.h中都包含了my.h,而且在my.h中有一个全局变量int k = 10,在test1.c中包含了上述三个头文件
编译结果

解决方法:
方法一:使用条件编译#ifndef(如果读者对条件编译不了解,可以先看一下这一篇随笔预处理命令使用详解----#if、#endif、#undef、#ifdef、#else、#elif)

解释一下上面的条件编译语句,如果_MY_H这个宏名没有被定义,那么定义_MY_H这个宏名,并且定义全局变量int k = 10,这样做就避免了头文件的重复包含引起的,变量或函数的重复定义问题
我们可以在stdio.h这个“标准输入\输出头文件”中看到类似的用法


格式就像下面这个样子:
#ifndef _INC_STDIO
#define _INC_STDIO
/* 头文件内容 */
#endif
这种宏保护方案使得程序员可以“任性”地引入当前模块需要的所有头文件,不用操心这些头文件中是否包含了其他的头文件
但也不是没有缺点
#ifndef的方式依赖于宏名不能冲突,这不光可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件不会被不小心同时包含
缺点就是如果不同头文件的宏名不小心“撞车”,可能就会导致头文件明明存在,编译器却硬说找不到声明的状况
方法二:使用#pragma once
#pramgma once是微软编译器独有的,也是后来才有的,所以知道的人并不是很多,用的人也不是很多,因为他不支持跨平台。如果你想写跨平台的代码,最好使用条件编译,如果想使用#pragma once,
只需在头文件开头加上#pragma once即可

#pragma带来的好处是:你不必再费劲想个宏名了,当然也就不会出现宏名碰撞引发的奇怪问题,坏处也有,#pragma once是由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件,如果某个头文件有多份拷贝,这个方法就不能保证他们不被重复包含。当然,相比宏名碰撞引发的“找不到声明”的问题,重复包含更容易被发现并修正
总结:1.#ifndef 由语言支持所以移植性好
2.#pragma 可以避免名字冲突
extern的使用详解(多文件编程)——C语言的更多相关文章
- ZT --- extern "C"用法详解 2010-08-21 19:14:12
		
extern "C"用法详解 2010-08-21 19:14:12 分类: C/C++ 1.前言: 时常在cpp的代码之中看到这样的代码: #ifdef __cplusplus ...
 - [转]文件IO详解(二)---文件描述符(fd)和inode号的关系
		
原文:https://www.cnblogs.com/frank-yxs/p/5925563.html 文件IO详解(二)---文件描述符(fd)和inode号的关系 ---------------- ...
 - MFC中文件对话框类CFileDialog详解及文件过滤器说明
		
当前位置 : 首页 » 文章分类 : 开发 » MFC中文件对话框类CFileDialog详解及文件过滤器说明 上一篇 利用OpenCV从摄像头获得图像的坐标原点是在左下角 下一篇 Word中为 ...
 - 一阶RC高通滤波器详解(仿真+matlab+C语言实现)
		
文章目录 预备知识 关于电容 HPF的推导 simulink 仿真 simulink 运行结果 matlab 实现 matlab 运行结果 C语言实现 如果本文帮到了你,帮忙点个赞: 如果本文帮到了你 ...
 - 一阶RC低通滤波器详解(仿真+matlab+C语言实现)
		
文章目录 1 预备知识 2 simulink 仿真 3 simulink 运行结果 4 matlab实现 5 matlab运行结果 6 C语言实现 7 C语言运行结果 如果本文帮到了你,帮忙点个赞: ...
 - Socket详解-Linux Socket编程(不限Linux)
		
“一切皆Socket!” 话虽些许夸张,但是事实也是,现在的网络编程几乎都是用的socket. ——有感于实际编程和开源项目研究. 我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览 ...
 - extern extern “C”用法详解
		
1.extern 修饰一个变量,告诉编译器这个变量在其他地方定义,编译器不会给出变量未定义的警告. extern tells the compiler that the variable is def ...
 - ***PHP $_FILES函数详解 + PHP文件上传 move_uploaded_file() 参数的正确写法
		
PHP $_FILES函数详解 在PHP中上传一个文件建一个表单要比ASP中灵活得多.具体的看代码. 如: 复制代码代码如下: <form enctype="multipart/fo ...
 - hadoop2.7作业提交详解之文件分片
		
在前面一篇文章中(hadoop2.7之作业提交详解(上))中涉及到文件的分片. JobSubmitter.submitJobInternal方法中调用了int maps = writeSplits(j ...
 
随机推荐
- tmp32dll\sha1-586.asm(1432) : error A2070:invalid instruction operands 编译openssl出错
			
vs命令行工具编译openssl最新版本的时候报perl版本太低. 后来换了openssl 1.0.2的版本旧版本到是可以正常编译了,但是1.0.2应该是版本还是优点新. 编译的时候报了下面的错误: ...
 - Java POI操作Excel注意点
			
excel的行索引和列索引都是从0开始,而行号和列号都是从1开始 POI·操作excel基本上都是使用索引 XSSFRow对象的 row.getLastCellNum() 方法返回的是当前行最后有效列 ...
 - js方法用来获取路径传参上所带的参数
			
//js方法用来获取路径传参上所带的参数 function GetQueryString(param) { var reg = new RegExp("(^|&)" + p ...
 - ES6中的let命令
			
ES6新增了let命令,用于声明变量.其用法类似var,区别是使用let命令声明的变量只在当前代码块有效. for循环的计数器就很适合使用let命令. var arr= [1,2,3,4,5]; fo ...
 - windows 远程连接“发生身份验证错误 要求的函数不受支持”
			
Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\P ...
 - 十字线阵---CBF,传统波束形成
			
%传统波束形成,CBF (Ps:这个程序是别人的,不是我写的,但是具体是在哪里找到的已经忘了) clear all; close all; clc; %---------初始化常量---------- ...
 - python积累二:中文乱码解决方法
			
根据网上提供的解决方法:添加#coding=utf-8或# -*- coding: utf-8 -*- #coding=utf-8 print "还不行?" 执行结果:还是乱码!: ...
 - day0-功能自动化的前提
			
自动化不是万能的,不是什么产品都适合自动化测试,那些产品适合自动化测试? 只要满足下面三个条件就可以进行自动化测试 一:软件需求变动不频繁 二:项目周期较长 自动化测试框架的设计.脚本的开发与调试需要 ...
 - python07 函数式编程
			
1.作用域 1.1 pass关键字表示,暂时不写该方法 1.2表示返回值为方法 输出结果333 1.3函数作用域:和函数调用没关系,和声明的位置有关系, 结果为444 2.匿名函数 lanmbda ...
 - 关于session共享
			
最近在银行部署项目,一台Nginx做负载均衡,两台Tomcat,两台Oracle互备,一台ftp文件服务器.Tomcat涉及到session共享问题,所以就在这里做一下总结. 首先关于session ...