目录
4、右值
前言
这篇文章将会详细介绍关于C 语言中一些细致的问题,这也是笔者学习C语言一直以来的困惑与思考,希望和大家一起交流。
一、关于变量、对象、左值、右值?
1、变量
什么是变量呢?C语⾔中把经常变化的值称为变量(variable),不变的值称为常量(constant),笔者认为这样的解释过于片面。
从内存的角度看,变量:就是用来存放数值和字符的“盒子”。变量中放入数值后,只要该盒子还在,值就一直被保存,而且还可以自由地取出或者替换数值。
来看如下一段代码:
int a = 10;
int b = 20;
a = b;
这段代码的内存布局如下图:
前两条语句是初始化:即在生成变量的同时放入数值
第三条语句为赋值:在已生成的变量中放入数值
2、对象
什么是对象呢? 这也是之前困扰笔者的问题,C语言的对象不同于面向对象程序中的对象,面向对象中的对象是指类对象,即包括数据和允许对数据进行的操作。
从硬件方面看,被存储的每个值都占用一定的内存空间,C语言把这样的一块内存称为对象(object),对象可以存储一个值或多个值。一个对象可能未存储实际的值,但它在存储适当值时一定具有相应的大小,这取决于对象的数据类型。
通俗理解:数据类型实际上相当于隐藏各种属性的一个设计蓝图(可以想象成做章鱼小丸子的模具),包含某个类型的对象,就是根据这个设计蓝图创建出的实体(通过模具制成的章鱼小丸子)。
那变量和对象等价吗?通常来说变量和对象等价,但对象的范围要广于变量,例如数组也是对象,它是由相同数据类型的变量组成;而数组的每一个元素又是一个对象。
所以笔者认为:如果对专业术语不想过于拘泥,变量和对象应该不加区分。
3、左值
在VS编译器上运行代码时,经常看到左值和不可修改的左值
左值:是C语言的术语,用于标识或指定特定数据对象的名称或者表达式。对象是实际的数据存储区,而左值是用于标识或定位存储位置的标签。使用变量名是标识对象的一种方法,因此变量名就是左值
例如如下代码中:
int a = 3;//a就是左值,它指定了一个大小为4个字节的对象,对象中的值是3
那还有其他的左值吗?例如如下代码:
int a = 3;
int ranks[10];
int *pt = &a;
//此时*pt 和 a标识的对象一样,因此*pt也是左值
//表达式*(ranks + 2 * a) 也是左值,它指定了数组第7个元素这个对象
那什么是可修改的左值?可修改的左值:用于标识值可修改的对象,因此上述代码中的左值都是可修改的左值。
那什么是不可修改的左值呢?例如如下代码:
const int a = 10;//由于const的修饰,此时a指定的对象里的值不能被修改
//因此a为不可修改的左值
由const修饰的变量不能被修改,因此变量名为不可修改的左值。
4、右值
右值指的是能赋值给可修改的左值的量,右值可以是常量、变量或者其他求值表达式(如函数 调用)
二、关于数组和指针
下面的代码体现了C语言的灵活性:
int arr[10];
arr + 2 == &arr[2]; //相同的地址
*(arr + 2) == arr[2];//相同的值
以上关系表面了数组和指针的关系十分密切,可以使用指针标识数组的元素。从本质上看,同一个对象有两种表示法,因此指针表示法和数组表示法在编译器看来就是等效的方法,编译器在处理时会统一转变为地址+偏移量的方式访问数组的每一个元素。
三、关于作用域、存储期、链接属性
要创建大规模程序,需要理解作用域、存储期和链接属性 :
1、作用域:
作用域表示的是变量名的可见范围:
块作用域
块是用一对花括号括起来的代码区域,例如整个函数体就是一个块,函数中的任意复合语句也是一个块 ,定义在块中的变量具有块作用域,块作用域变量的变量名可见范围是从定义处到包含该定义的块的末尾。注意:函数的形参也具有块作用域,属于函数体这个块。例如:
int blocky(int a)
{
int b = 0;
return b;
}
//变量a、b都具有块作用域
函数作用域
仅用于goto语句的标签。这意味着一个标签首次出现在函数内层,它的作用域将延伸至整个函数 (很少用到,不用细究)
函数原型作用域
用于函数原型中的形参名(变量名),如下所示:
int add(int a, int b);//函数原型声明
//a、b具有函数原型作用域
函数原型作用域的范围是从形参定义处到原型声明结束。因此,编译器在处理函数原型中的形参时只关心它的类型,而形参名通常无关紧要。而且,即使有形参名也不必和函数定义中的形参名一样。
文件作用域
变量定义在函数的外面,具有文件作用域。具有文件作用域的变量,从定义处到该定义所在文件的末尾均可见。例如:
#include<stdio.h>
int units = 0;//该变量具有文件作用域
int main()
{
.....
}
由于文件作用域变量可用于多个函数,因此文件作用域变量也称为全局变量。
翻译单元
C语言中编译器把源文件(.c)和它所包含的所有头文件(.h)看成一个翻译单元,因此描述具有文件作用域变量时,它的实际可见范围是整个翻译单元。如果程序由多个源代码文件组成,那么该程序也将由多个翻译单元组成,每个翻译单元均由一个源代码文件和它所包含的头文件组成。
链接
C变量的链接属性有三种:
外部链接:外部链接变量可在多文件程序中使用,外部链接的文件作用域变量的作用域是所有翻译单元
内部链接:内部链接变量只能在一个翻译单元使用。static修饰的变量具有内部链接属性
无链接:具有块作用域、函数作用域、函数原型作用域的变量都是无链接的。
例如:
int a = 5; //文件作用域,外部链接
static int b = 3; //文件作用域,内部链接
int main()
{
......
}
该文件和同一程序的其他文件都可以使用变量a,但变量b属于该文件私有,该文件的任意函数都可使用它。
3、存储期
存储期描述的是变量(对象)在内存中保留了多长时间,存储期大致可以分为3种:
静态存储期
如果变量具有静态存储期,它在程序执行的期间一直存在,文件作用域变量具有静态存储期。静态存储期变量保存在内存的静态区。
自动存储期
块作用域的变量具有自动存储期。当程序进入定义这些变量的块时,为这些变量分配内存;当退出块时,释放刚才为变量分配的内存。这种做法相当于把自动变量占用的内存视为一个可重复使用的工作区。例如,一个函数调用结束后,其变量占用的内存可用于存储下一个被调用函数的变量,内存可以重复利用。自动存储期的变量保存在内存的栈区。
动态分配存储期
通过malloc、calloc、realloc函数动态分配的对象具有动态分配存储期,这些对象被保存在内存的堆区。程序员往往需要手动释放动态分配的对象空间。
总结
最后通过表格的形式总结以上内容,如下表:
存储类别 | 存储期 | 作用域 | 链接 | 声明方式 |
自动 | 自动 | 块 | 无 | 块内 |
寄存器 | 自动 | 快 | 无 | 快内,使用register关键字 |
静态外部链接 | 静态 | 文件 | 外部 | 所有函数外 |
静态内部链接 | 静态 | 文件 | 内部 | 所有函数外,使用static关键字 |
静态无链接 | 静态 | 块 | 无 | 快内,使用static关键字 |