《C和指针》阅读笔记(3)

《C和指针》第三章的内容还是基础部分,主要介绍了各种基本数据类型,数据类型的特点,变量的声明以及说明符的三个属性。本文的重点是对 标识符、说明符及其属性的理解,是非常重要的基石。

标识符(identifier)

标识也就是对某个对象进行标记或者说给某个对象一个别名(代称)。例如,40年前中国南部沿海城市的一个小渔村,它的名字就叫深圳,那么,"深圳"就相当于一个标识符,代表了这个40年前的"小渔村"城市。同样的道理,在C语言中,也需要给很多"对象"一个标记,以至于不会混淆。我想这就是标识符为什么产生的缘由。

标识符就是给变量、函数、数据类型等取的名字。并且取名必须要按照指定的规则,否则就乱套了。标识符由字母、数字、下划线组成,不能以数字开头,而且字母大小写敏感。例如,

// 定义一个变量名
int number = 10;
// 声明一个函数
void test();
// 定义一个数据类型
typedef struct
{
    int a;
    int b;
} new_type;

C语言有一些保留关键字是不能被用作标识符的

auto do goto signed unsigned break double if sizeof void

case else int static volatile char enum long struct while

const extern register switch continue float return typedef

default for short union

说明符(specifier)

标识符解决了"对象"的表示问题,但是这个"对象"到底是什么,有什么样的属性,并不知道。这就需要说明符来进行说明。

说明符由一些关键词组成,用于描述被声明的标识符的数据类型、作用域、链接属性、存储类型。

为了更容易阐述,后面我会先将"对象"用变量代替,当然"对象"还包含函数。

变量声明的基本形式:说明符(一个或多个) 标识符列表

例如,

// main.c
#include <stdio.h>
#include <stdlib.h>

int g_num = 0;

int main(int argc, char** argv)
{
    printf("hello world\n");
    return 0;
}

g_num是一个变量的标识符,int 是对g_num的说明符,说明了它是一个整形变量;当然,还有隐式的说明信息,g_num的作用域是文件作用域,链接属性是外部链接(external),存储类型是静态存储区。

在我之前的一篇博文c基础-变量的存储中已经非常详细的介绍过变量的作用域链接属性存储类型。这里我再做个总结和更新吧。

作用域(scope)

作用域决定了符号(变量或函数)可访问的范围。例如,某个变量在函数内部声明,那么它的作用域就在函数内部,函数外部是不能够使用这个变量的。除了控制可访问范围外,作用域还有一个作用就是可以使不同作用域的变量名可以同名而不冲突。

我将变量的作用域划分成了三大类:文件作用域、代码块作用域、原型作用域,而代码块作用域又分为:代码块作用域、函数作用域。我觉得函数体也是在一对花括号中,也相当于代码块。

文件作用域

任何在代码块之外声明的标识符,在其声明之处开始到所在源文件结尾处都可以访问。

代码块作用域

代码块作用域

任何在代码块开始处声明的标识符,都可以被块内的语句访问。

函数作用域

任何在函数体内声明的标识符,都可以被函数体内的语句访问。

原型作用域

它的范围是在函数原型声明中,形参声明处到原型声明结束。这个作用域的意义其实不大。因为函数的声明中,形参名可有可无。

链接属性(linkage)

从上一篇博文《C和指针》阅读笔记(2)介绍的程序的构建中,我们知道构建的最后一步是将一个或多个目标文件以及库文件进行链接,生成可执行程序;但是不同作用域的标识符可以同名,那么,是把所有同名的标识符当作同一个"对象"呢?还是把所有同名的标识符都当做不同的"对象"呢?或者是部分同名标识符当作同一个"对象",另一部分同名标识符当作不同的"对象"呢?这个问题就由链接属性来解决,处理不同目标文件中的标识符。链接属性有三种:外部链接、内部链接、空链接

外部链接(external linkage)

外部链接的同名标识符可以被多个源文件使用且当作是一个"对象"。通常,文件作用域的变量、函数名默认的链接属性就是外部链接,而并没有用extern关键词显示说明。

内部链接(internal linkage)

内部链接的同名标识符在同一个源文件内的所有声明处都指同一个"对象"。通常使用static关键字来显示指明内部链接属性,使标识符只能在源文件内访问。(PS:此处便体现了static的一种作用,改变标识符(变量、函数)的链接属性)

空链接(none linkage)

空链接的同名标识符都被当作不同的"对象"。通常,代码块作用域的变量是空链接属性,换句话说,就是代码块私有。

存储类型(storage class)

变量的存储类型是指存储变量值的"空间"的类型,而这个类型决定了变量何时创建、"活"多久、何时销毁。这种"空间"的类型有三种:普通内存、运行时堆栈、寄存器。

普通内存

存储在普通内存中的变量通常称为静态存储变量。这种变量在任何代码块之外声明,在编译阶段就分配好了内存(程序运行之前)—何时创建,在整个程序运行期间一直存在—"活"多久,程序结束后释放—何时销毁。例如,文件作用域定义的变量(我们常说的全局变量)默认就是静态存储变量,对全局变量使用static说明符,就会修改该变量的作用域属性,使其变成内部链接而只在本源文件内可访问;函数作用域定义的变量(我们常说的局部变量)默认是自动存储变量,对局部变量使用static说明符,就会修改该变量的存储类型,使其变成静态存储变量(PS:此处便体现了static的另一种作用,改变变量的存储类型)。

运行时堆栈

存储在堆栈中的变量通常称为自动存储变量。这种变量在代码块内部声明,在程序执行到声明该变量的代码块时,该变量才被创建,在本次代码块执行期间,该变量一直存在,在本次代码块执行完后,该变量被释放。我之所以强调本次执行是因为该代码块可能会被多次执行,例如函数,那么每次执行该代码块,这个变量的地址可能会发生变化,即使恰巧该变量的地址没有发生变化,也不能保证这个地址的值没有被修改过。所以,自动存储变量用时重新创建,用完释放,没有记忆功能。

寄存器

存储在寄存器中的变量通常称为寄存器变量。这种变量使用register关键字来声明,表明这个变量存储在寄存器中而不是在内存中。相较于内存变量,寄存器变量的访问速度更快,但是寄存器资源有限,即使你声明了register,编译器也不一定将这个变量作为寄存器变量而存储于寄存器中。

特别说明

指针声明

我之前有个不好的习惯,就是喜欢将指针声明的型号写在靠近数据类型的一侧,例如,int* pN = NULL;,似乎看起来更清晰一些,其实不然。

int* a, b, c; 

上面这种声明方式很容易让人产生错觉,变量a、变量b、变量c都是指向int的指针。并非如此,变量b和c只是普通的int类型。更好的书写方式应该是int *a,b,c;,因为星号也是标识符列表的一部分,而int才是说明符。这种书写方式也与变量声明的定义吻合。

回想我一直以来之所以没有出错是因为我还有一个编码习惯,就是 每个变量的声明都是单独一行,而不会出现一行中声明多个变量。

C语言是一种自由形式的语言,"自由"也是一柄双刃剑,用的好,事半功倍,用的不好,抓耳挠腮。

static关键字

在我上学那会,刚学c语言的时候,static的作用总是记不住,现在回想起来也是有点儿好笑,没有理解当然记不住啊。static在不同的上下文环境下,所起的作用就不同,变换形式非常多,但是,万变不离其宗。

  • 改变标识符(变量、函数)的链接属性
  • 改变变量的存储类型

好了,这就是第三章阅读后的总结。

关注我

我的公众号二维码,欢迎关注

QQ讨论群:679603305

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sif_666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值