概述

基本上每种语言都要讨论这个话题,C语言也不例外,因为只有你完全了解每个变量或函数存储方式、作用范围和销毁时间才可能正确的使用这门语言。今天将着重介绍C语言中变量作用范围、存储方式、生命周期、作用域和可访问性。

  • 变量作用范围
  • 存储方式
  • 可访问性

变量作用范围

在C语言中变量从作用范围包括全局变量和局部变量。全局变量在定义之后所有的函数中均可以使用,只要前面的代码修改了,那么后面的代码中再使用就是修改后的值;局部变量的作用范围一般在一个函数内部(通常在一对大括号{}内),外面的程序无法访问它,它却可以访问外面的变量。

//
// main.c
// ScopeAndLifeCycle
//
// Created by Kenshin Cui on 14-7-12.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#include <stdio.h>

int a=1;
void changeValue(){
 a=2;
 printf("a=%d\n",a);
}
int main(int argc, const char * argv[]) {
 int b=1;
 changeValue(); //结果:a=2
 printf("a=%d,b=%d\n",a,b); //结果:a=2,b=1 ,因为changeValue修改了这个全局变量
 return 0;
}

变量存储方式

C语言的强大之处在于它能直接操作内存(指针),但是要完全熟悉它的操作方式我们必须要弄清它的存储方式。存储变量的位置分为:普通内存(静态存储区)、运行时堆栈(动态存储区)、硬件寄存器(动态存储区),当然这几种存储的效率是从低到高的。而根据存储位置的不同在C语言中又可以将变量依次分为:静态变量、自动变量、寄存器变量。

静态变量

首先说一下存储在普通内存中的静态变量,全局变量和使用static声明的局部变量都是静态变量,在系统运行过程中只初始化一次(在下面的例子中虽然变量b是局部变量,在外部无法访问,但是他的生命周期一直延续到程序结束,而变量c则在第一次执行完就释放,第二次执行时重新创建)。

//
// 2.1.c
// ScopeAndLifeCycle
//
// Created by Kenshin Cui on 14-7-12.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#include <stdio.h>

int a=1; //全局变量存储在静态内存中,只初始化一次

void showMessage(){
 static int b=1; //静态变量存储在静态内存中,第二次调用不会再进行初始化
 int c=1;
   b;
 a =2;
 printf("a=%d,b=%d,c=%d\n",a,b,c);
}

int main(int argc, const char * argv[]) {
 showMessage(); //结果:a=3,b=2,c=1
 showMessage(); //结果:a=5,b=3,c=1
 return 0;
}

自动变量

被关键字auto修饰的局部变量是自动变量,但是auto关键字可以省略,因此可以得出结论:所有没有被static修饰的局部变量都是自动变量。

//
// 1.3.c
// ScopeAndLifeCycle
//
// Created by Kenshin Cui on 14-7-12.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#include <stdio.h>
#include <stdlib.h>

int main(int argc, const char * argv[]) {
 int a=1;
 auto int b=2;
 printf("a=%d,b=%d\n",a,b); //结果:a=1,b=2 ,a和b都是自动变量,auto可以省略
 
 //需要注意的是,上面的自动变量是存储在栈中,其实还可以存储到堆中
 char c[]="hello,world!";
 long len=strlen(c)*sizeof(char) 1;//之所以加1是因为字符串后面默认有一个\0空操作符不计算在长度内
 char *p=NULL;//可以直接写成:char *p;
 p=(char *)malloc(len);//分配指定的字节存放c中字符串,注意由于malloc默认返回“void *”需要转化
 memset(p,0,len);//清空指向内存中的存储内容,因为分配的内存是随机的,如果不清空可能会因为垃圾数据产生不必要的麻烦
 strcpy(p,c);
 printf("p=%s\n",p);//结果:p=hello,world!
 free(p);//释放分配的空间
 p=NULL;//注意让p指向空,否则p将会是一个存储一个无用地址的野指针
 
 
 return 0;
}

当然存储自动变量的栈和堆其实是两个完全不同的空间(虽然都在运行时有效的空间内):栈一般是程序自动分配,其存储结果类似于数据结构中的栈,先进后出,程序结束时由编译器自动释放;而堆则是开发人员手动编码分配,如果不进行手动释放就只有等到程序运行完操作系统回收,其存储结构类似于链表。在上面的代码中p变量同样是一个自动变量,同样可以使用auto修饰,只是它所指向的内容放在堆上(p本身存放在栈上)。

这里说明几点:malloc分配的空间在逻辑上连续,物理上未必连续;p必须手动释放,否则直到程序运行结束它占用的内存将一直被占用;释放p的过程只是把p指向的空间释放掉,p中存放的地址并未释放,需要手动设置为NULL,否则这将是一个无用的野指针;

寄存器变量

默认情况下无论是自动变量还是静态变量它们都在内存中,不同之处就是自动变量放在一块运行时分配的特殊内存中。但是寄存器变量却是在硬件寄存器中,从物理上来说它和内存处在两个完全不同的硬件中。大家都是知道寄存器存储空间很小,但是它的效率很高,那么合理使用寄存器变量就相当重要了。什么是寄存器变量呢?使用register修饰的int或char类型的非静态局部变量是寄存器变量。没错,需要三个条件支撑:register修饰、必须是int或char类型、必须是非静态局部变量。

除了存储位置不同外,寄存器变量完全符合自动变量的条件,因此它的生命周期其实是和自动变量完全一样的,当函数运行结束后它就会被自动释放。由于寄存器空间珍贵,因此我们需要合理使用寄存器变量,只有访问度很高的变量我们才考虑使用寄存器变量,如果过多的定义寄存器变量,当寄存器空间不够用时会自动转化为自动变量。

//
// 1.3.c
// ScopeAndLifeCycle
//
// Created by Kenshin Cui on 14-7-12.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#include <stdio.h>

int main(int argc, const char * argv[]) {
 register int a=1;
 printf("a=%d\n",a);
 return 0;
}

 上面我们说到变量的存储类型,其实在C语言中还有两种存储类型:常量存储区和代码存储区,分别用于存储字符串常量、使用const修饰的全局变量以及二进制函数代码。

可访问性

在C语言中没有其他高级语言public、private等修饰符,来限定变量和函数的有效范围,但是却有两个类似的关键字能达到类似的效果:extern和static。

extern

extern作用于变量

我们知道在C语言中变量的定义顺序是有严格要求的,要使用变量则必须在使用之前定义,extern用于声明一个已经存在的变量,这样一来即使在后面定义一个变量只要前面声明了,也同样可以使用。具体的细节通过下面的代码相信大家都可以看明白:

//
// 2.1.c
// ScopeAndLifeCycle
//
// Created by Kenshin Cui on 14-7-12.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#include <stdio.h>

//如果在main函数下面定义了一个变量a,如果在main上面不进行声明是无法在main中使用a的;
//同样如果只进行了extern声明不进行定义一样会报错,因为extern并不负责定义变量a而仅仅是声明一个已经定义过的变量;
//当然如果说在main上面定义int a;去掉main下面的定义同样是可以的,相当于在上面定义,但如果两个地方都定义a的话(main上面的extern去掉),则程序认为上面的定义是声明,只是省略了extern关键字;

//第一种情况,在下面定义,不进行声明,报错
int main(int argc, const char * argv[]) {
 
 printf("a=%d\n",a);
 return 0;
}

int a;

//第二种情况,在上面定义,正确
int a;
int main(int argc, const char * argv[]) {
 
 printf("a=%d\n",a);
 return 0;
}


//第三种情况,在下面定义在上面声明,正确
extern int a;
int main(int argc, const char * argv[]) {
 
 printf("a=%d\n",a);
 return 0;
}

int a;


//第四种情况,只在上面声明(编译时没有问题,因为上面的声明骗过了编译器,但运行时报错,因为extern只能声明一个已经定义的变量),错误
extern int a;
int main(int argc, const char * argv[]) {
 
 printf("a=%d\n",a);
 return 0;
}


//第五种情况,上下同时定义(这种方式是正确的,因为上面的定义会被认为是省略了extern的声明),正确
int a;
int main(int argc, const char * argv[]) {
 
 printf("a=%d\n",a);
 return 0;
}
int a;
//其实下面的情况也是不会出错的
int a;
int a;
int main(int argc, const char * argv[]) {
 
 printf("a=%d\n",a);
 return 0;
}
int a;
int a;

//第六种情况,将全局变量声明为局部变量,但是它的实质还是全局变量,正确
int a;
int main(int argc, const char * argv[]) {
 extern int a;
 printf("a=%d\n",a);
 return 0;
}
int a;

//第七种情况,在函数内部重新定义一个变量a,虽然不会报错,但是两个a不是同一个
int a;
int main(int argc, const char * argv[]) {
 int a;
 printf("a=%d\n",a);//注意这里输出的a其实是内部定义的a,和函数外定义的a没有关系
 return 0;
}
int a;

如果两个文件同时定义一个全局变量,那实质上他们指的是同一个变量。从下面的例子可以看出,在main.c中修改了变量a之后message.c中的变量a值也修改了。

需要注意,在上面的代码中无论在message.h中将a定义前加上extern,还是在main.h中的a定以前加上extern结果都是一样的,extern同样适用。和在单文件中一样,不能两个定义都添加extern,否则就没有定义了。如果把message.c中a的定义(或声明)去掉呢,那么它能否访问main.c中的全局变量a呢,答案是否定的(这和在一个文件中定义了一个函数在另一个文件不声明就直接用是类似的)。

extern作用于函数

extern作用于函数就不再是简单的声明函数了,而是将这个函数作为外部函数(当然还有内部函数,下面会说到),在其他文件中也可以访问。但是大家应该已经注意到,在上面的代码中message.c中的showMessage前面并没有添加extern关键字,在main.c中不是照样访问吗?那是因为这个关键字是可以省略的,默认情况下所有的函数都是外部函数。

和作用于变量不同,上面main.c和message.c中的extern都可以省略,在这里extern的作用就是定义或声明一个外部函数。从上面可以看到在不同的文件中可以定义同一个变量,它们被视为同一个变量,但是需要指出的是外部函数在一个程序中是不能重名的,否则会报错。

static

static作用于变量

其实在前面的例子中我们已经看到static关键字在变量中的使用了,在例子中使用static定了一个局部变量,而且我们强调static局部变量在函数中只被初始化一次。那么如果static作用于全局变量是什么效果呢?如果static作用于全局变量它的作用就是定义一个只能在当前文件访问的全局变量,相等于私有全局变量。

从上面的输出结果可以看出message.c中的变量a和main.c中的变量a并不是同一个变量,事实上message.c中的变量a只能在message.c中使用,虽然main.c中的变量a是全局变量但是就近原则,message.c会使用自己内部的变量a。当然,上面例子中main.c中的变量a定义成静态全局变量结果也是一样的,只是这样如果还有其他源文件就不能使用a变量了。但是main.c中的a不能声明成extern,因为main.c不能访问message.c中的变量a,这样在main.c中就没变量a的定义了。

static作用于函数

static作用于函数和作用于变量其实是类似的,如果static作用于函数则这个函数就是内部函数,其他文件中的代码不可以访问。下面的代码在运行时会报错,因为mesage.c中的showMessage()函数是私有的,在main.c中尽管进行了声明,可以在编译阶段通过,但是在链接阶段会报错。

总结

最后做一下简单总结一下:

  1. extern作用于变量时用于声明一个已经定义的变量,但是并不能定义变量;使用extern你可以在其他文件中使用全局变量(当然此时extern可以省略);
  2. extern作用于函数时与它作用于全局变量有点类似,声明这个函数是外部函数,其他文件可以访问,但不同的是当它作用于函数时不仅可以声明函数还可以定义函数(用在函数定义前面),不管是定义还是声明都可以省略,C语言默认认为函数定义或声明都是外部函数;
  3. static作用于变量时,该变量只会定义一次,以后在使用时不会重新定义,当static作用于全局变量时说明该变量只能在当前文件可以访问,其他文件中不能访问;
  4. static作用于函数时与作用于全局变量类似,表示声明或定义该函数是内部函数(又叫静态函数),在该函数所在文件外的其他文件中无法访问此函数;

原文链接:http://www.cnblogs.com/kenshincui/p/3854243.html

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持Devmax。

iOS开发系列--详细讲解C语言之存储方式和作用域的更多相关文章

  1. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  2. iOS实现拖拽View跟随手指浮动效果

    这篇文章主要为大家详细介绍了iOS实现拖拽View跟随手指浮动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  4. ios – Testflight无法安装应用程序

    我有几个测试人员注册了testflight并连接了他们的设备……他们有不同的ios型号……但是所有这些都有同样的问题.当他们从“safari”或“testflight”应用程序本身单击应用程序的安装按钮时……达到约90%并出现错误消息…

  5. ibm-mobilefirst – 在iOS 7.1上获取“无法安装应用程序,因为证书无效”错误

    当我的客户端将他们的设备更新到iOS7.1,然后尝试从AppCenter更新我们的应用程序时,我收到了上述错误.经过一番搜索,我找到了一个类似问题的帖子here.但是后来因为我在客户端使用AppCenter更新应用程序的环境中,我无法使用USB插件并为他们安装应用程序.在发布支持之前,是否有通过AppCenter进行下载的解决方法?

  6. ios – 视图的简单拖放?

    我正在学习iOS,但我找不到如何向UIView添加拖放行为.我试过了:它说“UIView没有可见的接口声明选择器addTarget”此外,我尝试添加平移手势识别器,但不确定这是否是我需要的它被称为,但不知道如何获得事件的坐标.在iOS中注册移动事件回调/拖放操作的标准简单方法是什么?

  7. ios – 什么控制iTunes中iPhone应用程序支持的语言列表?

    什么控制iPhone应用程序的iTunes页面中支持的语言?

  8. ios – 获得APNs响应BadDeviceToken或Unregistered的可能原因是什么?

    我知道设备令牌在某些时候是有效的.用户如何使其设备令牌变坏?从关于“未注册”的文档:Thedevicetokenisinactiveforthespecifiedtopic.这是否意味着应用程序已被删除?.您应该看到四种分发方法:如果您选择AppStore或Enterprise,您将在后面的对话框中看到Xcode将APNS权利更改为生产:如果选择AdHoc或Development,则aps-environment下的文本将是开发,然后应与后端的配置匹配.

  9. ios – 当我关闭应用程序时,我从调试器获得消息:由于信号15而终止

    我怎么能解决这个问题,我不知道这个链接MypreviousproblemaboutCoredata对我的问题有影响吗?当我cmd应用程序的Q时,将出现此消息.Messagefromdebugger:Terminatedduetosignal15如果谁知道我以前的问题的解决方案,请告诉我.解决方法>来自调试器的消息:每当用户通过CMD-Q(退出)或STOP手动终止应用程序(无论是在iOS模拟器中还是

  10. ios – NSUbiquityIdentityDidChangeNotification和SIGKILL

    当应用程序被发送到后台时,我们会删除观察者吗?我遇到的问题是,当UbiquityToken发生变化时,应用程序终止,因为用户已经更改了iCloud设置.你们如何设法订阅这个通知,如果你不这样做,你会做什么来跟踪当前登录的iCloud用户?

随机推荐

  1. iOS实现拖拽View跟随手指浮动效果

    这篇文章主要为大家详细介绍了iOS实现拖拽View跟随手指浮动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  2. iOS – genstrings:无法连接到输出目录en.lproj

    使用我桌面上的项目文件夹,我启动终端输入:cd然后将我的项目文件夹拖到终端,它给了我路径.然后我将这行代码粘贴到终端中找.-name*.m|xargsgenstrings-oen.lproj我在终端中收到此错误消息:genstrings:无法连接到输出目录en.lproj它多次打印这行,然后说我的项目是一个目录的路径?没有.strings文件.对我做错了什么的想法?

  3. iOS 7 UIButtonBarItem图像没有色调

    如何确保按钮图标采用全局色调?解决方法只是想将其转换为根注释,以便为“回答”复选标记提供更好的上下文,并提供更好的格式.我能想出这个!

  4. ios – 在自定义相机层的AVFoundation中自动对焦和自动曝光

    为AVFoundation定制图层相机创建精确的自动对焦和曝光的最佳方法是什么?

  5. ios – Xcode找不到Alamofire,错误:没有这样的模块’Alamofire’

    我正在尝试按照github(https://github.com/Alamofire/Alamofire#cocoapods)指令将Alamofire包含在我的Swift项目中.我创建了一个新项目,导航到项目目录并运行此命令sudogeminstallcocoapods.然后我面临以下错误:搜索后我设法通过运行此命令安装cocoapodssudogeminstall-n/usr/local/bin

  6. ios – 在没有iPhone6s或更新的情况下测试ARKit

    我在决定下载Xcode9之前.我想玩新的框架–ARKit.我知道要用ARKit运行app我需要一个带有A9芯片或更新版本的设备.不幸的是我有一个较旧的.我的问题是已经下载了新Xcode的人.在我的情况下有可能运行ARKit应用程序吗?那个或其他任何模拟器?任何想法或我将不得不购买新设备?解决方法任何iOS11设备都可以使用ARKit,但是具有高质量AR体验的全球跟踪功能需要使用A9或更高版本处理器的设备.使用iOS11测试版更新您的设备是必要的.

  7. 将iOS应用移植到Android

    我们制作了一个具有2000个目标c类的退出大型iOS应用程序.我想知道有一个最佳实践指南将其移植到Android?此外,由于我们的应用程序大量使用UINavigation和UIView控制器,我想知道在Android上有类似的模型和实现.谢谢到目前为止,guenter解决方法老实说,我认为你正在计划的只是制作难以维护的糟糕代码.我意识到这听起来像很多工作,但从长远来看它会更容易,我只是将应用程序的概念“移植”到android并从头开始编写.

  8. ios – 在Swift中覆盖Objective C类方法

    我是Swift的初学者,我正在尝试在Swift项目中使用JSONModel.我想从JSONModel覆盖方法keyMapper,但我没有找到如何覆盖模型类中的Objective-C类方法.该方法的签名是:我怎样才能做到这一点?解决方法您可以像覆盖实例方法一样执行此操作,但使用class关键字除外:

  9. ios – 在WKWebView中获取链接URL

    我想在WKWebView中获取tapped链接的url.链接采用自定义格式,可触发应用中的某些操作.例如HTTP://我的网站/帮助#深层链接对讲.我这样使用KVO:这在第一次点击链接时效果很好.但是,如果我连续两次点击相同的链接,它将不报告链接点击.是否有解决方法来解决这个问题,以便我可以检测每个点击并获取链接?任何关于这个的指针都会很棒!解决方法像这样更改addobserver在observeValue函数中,您可以获得两个值

  10. ios – 在Swift的UIView中找到UILabel

    我正在尝试在我的UIViewControllers的超级视图中找到我的UILabels.这是我的代码:这是在Objective-C中推荐的方式,但是在Swift中我只得到UIViews和CALayer.我肯定在提供给这个方法的视图中有UILabel.我错过了什么?我的UIViewController中的调用:解决方法使用函数式编程概念可以更轻松地实现这一目标.

返回
顶部