总体认知初始化
2009-02-24王恒滨
文章编号:1672-5913(2009)02-0075-05
摘 要:介绍C语言的教科书都会在某种类型的变量或数组引出后,紧接着就给出初始化方法。这种分散介绍,既欠完整又拉长篇幅,还不利于说清楚究竟可以用什么,以怎样的次序做初始化。况且初始化毕竟不是非静态局部的必须。本文总体地考察了初始化问题,只要把这些内容放在其他知识之后,就能达到全面、透彻的目的。
关键词:C语言;初始化;数组
中图分类号:G642
文献标识码:B
1 概述
初始化是C系列高级语言的一种特殊用法。在定义变量或数组时,在名字后跟随赋值运算符及欲赋的值——称作初始值,系统就会在分配内存的同时存入相应的初始值。正是这种存储时机的超前性,引出了许多与一般赋值截然不同的语言现象。例如
可以把定义int a, b, c; 改作对a、c有初始化的定义:
int a = 0, b, c = 100;
此时系统在给a、c分配内存的同时还会分别在其中存储初始值0和100。
由于程序中定义的变量或数组往往不止一个,它们在开始有效的时间上,一定存在着先后次序,也即起始有效时刻不尽相同。起始有效时刻取决于它们出现在定义中的次序。从总体上来看,全局的起始有效时刻提前于局部的。也是由于初始化是在定义中给出的,不理解起始有效时刻的先后,往往会错误地把还未有效的名字用在提供初始值的表达式中。例如
int *p = a, a[10];
是错误的,问题出在对p初始化时a还未有效。
从定义的次序上还能发现,提供初始值的表达式中错用了一个未曾初始化的变量的不确定值。例如
int a[10], b = a[1];
也是错误的,问题出在a[1]未曾初始化,它的值不确定。
2 初始化意义下的常值
有时提供的初始值必须是常值意义的表达式,所谓的常值,包括如下两类:
1) 整型常值
整数(即整型常量)、字符常量(也是整型常量)或由它们构成的表达式。例如
-3512 + 5 * 6'd''d' + 3
2) 指针型常值
(1) 字面指针常量和字符串常量(代表指针常量)。例如
(int *)1000"I love China"
(2) 具有指针值的表达式。其中可以出现整型常值,但出现的变量名或数组名乃至成员名,都必须是全局的或静态的,并且还要保证,在计算该表达式时,仅仅取用这些名字对应的内存地址形成的指针值。例如,假设有以下的声明和定义:
struct tag
{
short a;
char b[3];
};
short v, *m, n[7], *p[5], r[5][7];
main()
{
static struct tag vs, ns[4];
/* …… ……*/
}
并且还假设它们对应内存的情况为:
v、m、n、p、r、vs、ns
分别对应:5000、5002、5006、5020、5040、10000、10030
那么,从中可以写出一系列指针型常值,见下表,而且这些常值是什么也体现在表里,其中用阴影淡化的极不常用,列于表中仅为了表格完整,也可以作为指针运算的练习。
3 变量的初始化
3.1 全局和静态局部变量的初始化
凡是可以写出直接由变量名接收常值的赋值语句,都可以写出相应的初始化。例如
int va = 1,
int *m, n[7], *p[5], r[5][7];
main()
{
static int vb = 123;
static int *ma = &v, *mb = n,
*mc = r[3],
*md = (int*)"I love here";
static int **oa = &m, **ob = p;
static int (*qa)[7] = &n,
(*qb)[7] = r, (*qc)[7] = &r[4];
……
}
其中,对全局变量va和静态局部变量vb、ma、mb、mc、md、oa、ob、qa、qb、qc做了初始化。
3.2 非静态局部变量的初始化
凡是可以写出直接由变量名接收赋值的赋值语句,都可以写出相应的初始化。
例如
int v = 1, r[5][7];
main()
{
int n[7];
int va = v + 1, vb = va + 2,
vc = va * vb;
int *ma = n + va,
*mb = &n[vc] - vb;
int (*qa)[7] = r + va,
(*qb)[7] = &r[vc] - vb;
……
}
其中,对非静态局部变量va、vb、vc、ma、mb、qa、qb做了初始化。这方面更多的例子将随后面的例子一道给出。
结构体变量直接由变量名接收赋值的赋值语句,只有一种形式,即提供值的表达式得是一个同类型的结构体变量。然而,结构体变量的初始化还存在另外的形式,也随后面的例子一道给出。
4 数组和结构体变量的初始化
这类初始化,是在赋值运算符后面跟上由一对花括号形成的整体,其中用逗号间隔的每一项依次是为相应元素或相应成员提供的值。如果元素或成员仍为数组或结构体变量,那么为其提供的值还得是由一对花括号括起来的有逗号间隔的一些项,以此类推。最终,从整体外观看,为初始化提供的可能是有嵌套的花括号层次结构。
例如,有以下定义:
int r[2][5];
struct tag ns[2];
其中的结构体类型如从前那样做了声明。现在来看怎样为r和ns提供初始值。
1) 对r来说,它有2个元素r[0]、r[1],应在花括号内准备2项,即 {#, #},而r[0] 又有5个元素r[0][0]、r[0][1]、r[0][2]、r[0][3]、r[0][4],应当是包含5项的花括号,即:
{ #, # } => {{#}, #}
=> {{#, #, #, #, #}, # }
关于r[1] 同理,即有:
{{#, #, #, #, #}, #}
=> {{#, #, #, #, #}, {#, #, #, #, #}}
于是,便得到了最终的花括号层次结构。
2) 对ns来说,它有2个元素ns[0]、ns[1],应在花括号内准备2项,即{#, #},而ns[0] 又有2个成员ns[0].u、ns[0].v,应当是包含2项的花括号,即:
{#, # } => {{#}, # } => {{#, #}, # }
而ns[0].v又有3个元素ns[0].v[0]、ns[0].v[1]、ns[0].v[2],应当是包含3项的花括号,即:
{{#, # }, # } => {{#, {#}}, #}
=> {{#, {#, #, #}}, # }
关于ns[1] 同理,即有:
{{#, {#, #, #}}, # }
=> {{#, {#, #, #}}, {#, #}}
=> {{#, {#, #, #}}, {#, #}}
int r[3][3]={{0, 1, 2},{3, 4, 5},{6, 7}};
此处再一次体会到,二维数组的列数是不可省略的重要信息!
6.2 用字符串常量做初始化
允许使用一个字符串常量,作为提供初始值所需的诸多字符。
1) 初始化一维字符数组。例如
char nc[3] = {"123"}; 或
char nc[3] = "123";
都相当于
char nc[3] = {'1', '2', '3'};
准确地说,此处字符串常量的一个字符刚好代表一般初始化方法的一项。
2) 初始化二维字符数组。但一个字符串常量仅能作为一行的初始值。例如
char rc[2][3] = {{"12"}, {"123"}};
或 char rc[2][3] = {"12", "123"};
都相当于
char rc[2][3] = {{'1','2'}, {'1','2', '3'}};
仅能作为一行的初始值意指,尽管以下的初始化方法是允许的,
char rc[2][3] = {'1','2','1','2','3'};
相当于
char rc[2][3] = {{'1','2','1'}, {'2','3'}};
但不能写成:
char rc[2][3] = {"12123"};
或 char rc[2][3] = "12123";
3) 一维字符数组的方括号内可空。字符串常量结尾的空字符也算数。例如
char nc[] = {"123"};
或 char nc[] = "123";
可以决定nc有4个元素,相当于
char nc[] = {'1', '2', '3', ' '};
或相当于“满值初始化”
char nc[4] = {'1', '2', '3', ' '};
以及相当于“缺值初始化”
char nc[4] = {'1', '2', '3'};
因为第4号元素nc[4] 默认地得到了0值,也就是 ' '。
这种初始化的好处比较明显,可确保不会漏掉作为字符串结束标识的空字符,使得需要这种标识的工作能够顺利进行。比如,用printf()输出其中的字符串。
注意:二维字符数组或作为成员的字符数组,都不存在此类问题,因相关的方括号内一定有确切的值。
4) 几点区别。以下四条语句,提供的初始值两两相同,但意义截然不同。
char *m = "I love China";
char n[] = "I love China";
char *p[] = {"I love", " China"};
char r[][6] = {"I love", " China"};
对m而言,需要给字符串常量分配内存,然后才把它代表的指针存储到为m分配的内存中。对n而言,不需要给字符串常量分配内存,但需要按照字符串常量的字符个数,为n分配内存,然后再把各个字符存入其中。对p而言,两个字符串常量对应于类似m的p[0]、p[1],可仿照m理解。对r而言,两个字符串常量对应于类似n的r[0]、r[1],可仿照n理解。不同的是,它俩的元素个数已被6确定下来,所以为r[0]和r[1]分配的内存都只能是6个字节,并且两者相连看作是给r分配的内存。这同时还约束了字符串常量的长度(不包括' ')限制在6以内。
参考文献
[1] 王恒滨. 指针教学内容改革(一)[J]. 计算机教育,2009,(3).
[2] 王恒滨. 指针教学内容改革(二)[J]. 计算机教育,2009,(4).
[3] 傅育熙等译. 程序设计语言:设计与实现(第四版)[M]. 北京:电子工业出版社,2001.
[4] Brian W.Kernighan,Dennis M.Ritchie. C程序设计语言(第二版)[M]. 北京:清华大学出版社,2002.