C语言的那些事儿.docx
- 文档编号:8018799
- 上传时间:2023-05-12
- 格式:DOCX
- 页数:51
- 大小:222.57KB
C语言的那些事儿.docx
《C语言的那些事儿.docx》由会员分享,可在线阅读,更多相关《C语言的那些事儿.docx(51页珍藏版)》请在冰点文库上搜索。
C语言的那些事儿
C语言的那些事儿’
多维数组那回事儿
2011年2月10日由edsionte留言»
前面几篇“那回事儿”的文章更强调一维组和指针之间的关系,本文关注的是多维数组,即“数组的数组”。
多维数组
我们可以将多维数组抽象的看作是具有某种类型的一维数组。
当“某种类型”为基本的数据类型时,多维数组就退化成普通的一维数组;当“某种类型”仍然为数组时,那么就形成了多维数组。
也就是说任何一个多维数组都可以分解成几个一维数组。
下面通过示例程序来深入了解多维数组ma[2][3]的构成。
viewsource
print?
01
#include
02
03
intmain()
04
{
05
intma[2][3];
06
int(*r)[2][3];
07
int(*p)[3];
08
int*t;
09
10
/*代码段1*/
11
p=ma;
12
printf("sizeof(ma[0])=%d\n",sizeof(ma[0]));
13
printf("ma=%p\tp=%p\n",ma,p);
14
printf("p+1=%p\n",p+1);
15
/*代码段2*/
16
r=&ma;
17
printf("sizeof(ma)=%d\n",sizeof(ma));
18
printf("&ma=%p\tr=%p\n",&ma,r);
19
printf("&ma+1=%p\tr+1=%p\n",&ma+1,r+1);
20
/*代码段3*/
21
t=ma[0];
22
printf("sizeof(ma[0][0])=%d\n",sizeof(ma[0][0]));
23
printf("ma[0]=%p\tt=%p\n",ma[0],t);
24
printf("ma[0]+1=%p\tt+1=%p\n",ma[0]+1,t+1);
25
return0;
26
}
由多维数组ma最左维的长度2可知,ma数组包含两个元素ma[0]和ma[1]。
数组名ma在表达式中是数组ma首元素的首地址。
在代码段1中将ma赋值给数组指针p,则p指向多维数组ma的首元素ma[0],则p+1指向第二个元素ma[1]。
其中p是一个数组指针,它指向一个长度为3的数组,则指针p每次移动的偏移量为12。
可参考下图:
在代码2中对ma取地址并将其赋值给指针r。
r现在指向一个“第一维的大小为2,第二维的大小为3的数组”,则r+1将指向下一个这样的数组(尽管这样的数组并不存在)。
由此也可得知r每次的偏移量为24。
ma[0]和ma[1]都是一个长度为3的整型数组,现在以ma[0]为例进行说明。
ma[0]中包含三个元素ma[0][0],ma[0][1]和ma[0][2]。
在代码段3中将ma[0]赋值给t,则t指向数组ma[0]的第一个元素a[0][0],则t+1和t+2则依次指向第二个元素和第三个元素。
对多维数组ma的结构有了一定了解后,现在再看上述程序的运行结果:
viewsource
print?
01
edsionte@edsionte-laptop:
~/code/expertC$gccarray.c-oarray
02
edsionte@edsionte-laptop:
~/code/expertC$./array
03
sizeof(ma[0])=12
04
ma=0xbfdfaa6cp=0xbfdfaa6c
05
p+1=0xbfdfaa78
06
sizeof(ma)=24
07
&ma=0xbfdfaa6cr=0xbfdfaa6c
08
r+1=0xbfdfaa84
09
sizeof(ma[0][0])=4
10
ma[0]=0xbfdfaa6ct=0xbfdfaa6c
11
t+1=0xbfdfaa70
注意在结果中,p,r和t的值均相同,但是所指向的数据却不同。
更具体的说,这三个指针每次移动时的偏移量不同。
多维数组的初始化
数组的初始化只能在对数组进行声明(具体为定义型声明)时进行。
一维数组的初始化很简单,只要将所有初始值放在一个大括号中即可。
如果声明数组时未指定数组的长度,则编译器会根据初始值的个数来确定数组的长度。
viewsource
print?
01
#include
02
03
intmain()
04
{
05
intm[]={1,2,3};
06
intn[]={1,2,3,};
07
08
printf("length(m)=%d\n",sizeof(m)/sizeof(m[0]));
09
printf("length(n)=%d\n",sizeof(n)/sizeof(n[0]));
10
return0;
11
}
12
13
/*编译并运行*/
14
edsionte@edsionte-laptop:
~/code/expertC$gccinit_array.c-oinit_array
15
edsionte@edsionte-laptop:
~/code/expertC$./init_array
16
length(m)=3
17
length(n)=3
注意,在最后一个初始值后面可以继续加一个逗号也可以省略,这并不影响数组的长度。
对于多维数组而言,通常使用嵌套的大括号进行多维数组的初始化。
由于多维的数组其实是有若干个一维数组构成的,则每个大括号都代表一个一维数组。
对于多维数组而言只能省略最左边下标的长度。
viewsource
print?
01
#include
02
03
intmain()
04
{
05
intb[][3]={1,2,1,1};
06
intc[][3]={{1,2,1},{1,2,3},};
07
08
printf("length(b)=%d\n",sizeof(b)/sizeof(b[0]));
09
printf("length(c)=%d\n",sizeof(c)/sizeof(c[0]));
10
return0;
11
}
12
13
/*编译并运行*/
14
edsionte@edsionte-laptop:
~/code/expertC$gccinit_array.c-oinit_array
15
edsionte@edsionte-laptop:
~/code/expertC$./init_array
16
length(b)=2
17
length(c)=2
可以看到,不使用大括号也可以对多维数组进行初始化,只不过代码可读性较差。
它总是迷惑你!
一旦涉及到多维数组,总有些让你迷惑的地方。
比如:
viewsource
print?
1
charma[2][3][2]={
2
{{1,2},{2,3},{3,4}},
3
{{3,5},{4,5},{3,3}}
4
};
5
6
sizeof(ma[0,1,1])=?
对于上面的代码,我们最后的迷惑点都可能落在ma[0,1,1]上。
难道多维数组可以这样使用吗?
如果ma[0,1,1]和ma[0][1][1]等价,那么sizeof(ma[0,1,1])的值就是1。
很可惜这样的猜测是不正确的,正确答案为6。
再比如下面的代码:
viewsource
print?
1
charma[3][2]={
2
(1,2),(3,4),(5,3)
3
};
4
5
ma[0][0]=?
上述代码是为数组ma进行初始化,那么ma[0][0]的值是多少?
恐怕很多人都会认为是1。
不过正确答案是2。
这两个问题都涉及到了逗号表达式。
如果你对逗号表达式有基本的了解,那么也就没有上述那种莫名其妙的迷惑了。
根据逗号表达式的运算,对于举例1中的ma[0,1,1]实际上等价于ma[1];对于举例2中的初始化其实等价为charma[3][2]={2,4,3}。
参考:
《C专家编程》人民邮电出版社;(美)林登(LinDen.P.V.D)著,徐波译;
指针和数组的可交换性
2011年2月7日
指针和数组是不相同的,但“很多时候”我们总认为指针和数组等价的。
不可否认,这两者在某种情况下是可以相互替换的,但并不能就因此而认为在所有情况下都适合。
《指针和数组不是一回事儿》系列文章将逐步深入分析指针和数组的不同之处,并解释什么时候指数组等价于指针。
本文属于《指针和数组不是一回事儿》系列文章之三。
虽然前面两篇文章已经说明了数组和指针的不同,但不可否认的是,指针和数组某些可相互交换的用法仍然令人混淆。
本文将给出指针和数组可交换的情景,并且分析可交换的原因。
“指针和数组可以交换!
”
说出这句话并不是毫无根据的,因为在下面的两个举例中使用数组形式和指针形式都可以达到相同的结果。
举例1:
viewsource
print?
01
#include
02
03
intmain()
04
{
05
char*p="edsionte";
06
charstr[]="edsionte";
07
08
printf("p[1]=%c*(p+1)=%c\n",p[1],*(p+1));
09
printf("str[1]=%c*(str+1)=%c\n",str[1],*(str+1));
10
11
return0;
12
}
13
14
/*编译并运行程序*/
15
edsionte@edsionte-laptop:
~/code/expertC$gcctmp.c-otmp
16
edsionte@edsionte-laptop:
~/code/expertC$./tmp
17
p[1]=d*(p+1)=d
18
str[1]=d*(str+1)=d
在举例1中,指针p指向一个匿名的字符串“edsionte”,这个匿名字符串的占用的内存空间为9个字节;与p指向一个匿名字符串不同,数组str内存储着字符串“edsionte”,占用了9个字节的空间。
现在分别要访问’d',则方法如下。
对于指针p,分别可以通过指针形式*(p+1)和数组形式p[1]来访问其所指的数据;对于数组str,分别可以通过指针形式*(str+1)和数组形式str[1]来访问数组内的元素。
我们已经知道指针和数组在内存构造和访问方式上都不同,但为什么它们都分别可以通过指针的方式和数组的方式进行访问?
举例2:
viewsource
print?
01
#include
02
03
voidgetStr_pointer(char*str)
04
{
05
printf("%s\n",str);
06
printf("getStr_pointer():
sizeof(str)=%d\n",sizeof(str));
07
}
08
09
voidgetStr_array(charstr[100])
10
{
11
printf("%s\n",str);
12
printf("getStr_array():
sizeof(str)=%d\n",sizeof(str));
13
}
14
15
intmain()
16
{
17
charstr[]="Iamedsionte!
";
18
19
getStr_pointer(str);
20
getStr_array(str);
21
printf("main():
sizeof(str)=%d\n",sizeof(str));
22
}
23
24
/*编译并运行程序*/
25
edsionte@edsionte-laptop:
~/code/expertC$gcctmp2.c-otmp2
26
edsionte@edsionte-laptop:
~/code/expertC$./tmp2
27
Iamedsionte!
28
getStr_pointer():
sizeof(str)=4
29
Iamedsionte!
30
getStr_array():
sizeof(str)=4
31
main():
sizeof(str)=15
在举例2中,getStr_pointer函数和getStr_array函数的功能都是显示一条字符串。
但不同的是,前者传入的参数是一个指针,后者传入的参数是一个数组。
在主函数中分别调用这两个函数,传入的参数都是数组str。
既然数组和指针不同,但为什么作为函数的形参,charstr[]和char*str相同?
上述举例所引出的这两个问题正是本文讨论的重点,它们分别对应着“指针和数组是相同”的两种情况。
下面将分别进行讨论。
1.表达式中的数组名就是指针
表达式中的数组名其实就是数组首元素的首地址。
对于编译器而言,a[i]其实就是*(a+i)的形式,因此以数组形式访问数组元素总是可以写成“数组首元素首地址加上偏移量”的形式。
取下标符号[]其实可以看成一种运算规则,即指向T类型的指针和一个整数相加,最终产生的结果类型为T。
这里的指针就为数组首元素首地址,而整数即为数组的偏移量。
这里必须说明一下偏移量,它是指针每次移动的步长。
对于数组而言,偏移量即数组元素的大小;对于指针而言,它的偏移量即为指针所指类型的大小。
在对指针进行移动时,编译器负责计算每次指针移动的步长。
因此,str[i]和*(str+i)两种形式其实是等价的。
因为编译器总是将数组形式的访问自动转换成指针形式的访问。
上面的分析都是针对数组而言,其实对指针以数组和指针形式访问的原理也是如此。
只不过此时的访问是对指针所指向数据的访问。
结合数组和指针访问方式的不同,下面对举例1的代码做详细分析:
1.1.以指针的形式和以数组的形式访问数组
从符号表中得到符号str的地址即为数组首元素的首地址。
以数组的形式:
str[1]。
从符号表中得到str符号的地址,即数组首元素的首地址;编译器将数组形式转化为*(str+1),在首元素首地址上加一个偏移量得到新地址;从这个新地址中读取数据,即为’d';
以指针的形式:
*(str+1)。
从符号表中得到str的地址,即数组首元素的首地址;在此地址上加一个偏移量得到新地址;从这个新地址中读取数据,即为’d';
1.2.以指针的形式和以数组的形式访问指针
不管以何种方式访问,我们应该清楚p始终是一个指针。
从编译器符号表中得到符号p的地址为指针p的地址。
以指针的形式:
*(p+1)。
首先从符号表中得到p的地址;从该地址中得到指针p;对指针p加上1个偏移量得到新地址;从这个新地址中读取数据,即为’d';
以数组的形式:
p[1]。
首先从符号表中得到p的地址;从该地址中得到指针p;编译器将数组形式转化成*(p+1),对p加一个偏移量得到新地址;从这个新地址中读取新数据,即为’d';
分析至此,你应该了解到以数组形式和以指针形式访问只是写法上的不同而已,其本质对内存的访问过程是一样的。
2.作为函数参数的数组名等同于指针
当作为函数形参时,编译器会将数组改成指向数组首元素的指针。
此时的数组就等价于指针。
之所以将传递给函数的数组形参转化为指针是处于效率的考虑。
在C语言中,所有非数组的实参数据都是以传值形式传递给函数的,即将实参的一份拷贝传递给调用函数中的形参,调用函数对这份拷贝(也就是形参)的修改不影响实参本身的值。
如果按照这样的道理,传递数组时就必须拷贝整个数组空间,这样必然会产生很大的开销。
并且,大部分时候并不会访问到数组中所有的元素而只是其中的几个。
考虑到上述的原因,数组作为实参传递给调用函数时,只需将数组名传递给函数即可;而形参会被编译器该成指针的形式。
因此,作为形参的数组既可以写成数组也可以写成指针。
现在再回到举例2中的代码,对于形参中的charstr[]和char*str也就感到不再奇怪了。
事实上,即便将形参写成charstr[]或charstr[100],编译器仍然会将它们改成char*str的形式。
既然任何数组作为形参时候都等价于一个指针,那么在函数内对“数组”的一切操作都等价于对指针的操作。
验证这一点的很好例证就是举例2中对数组str求长度。
在主函数中,sizeof(str)的值为15,这个结果毫无争议,它就是数组str的长度。
而在getStr_pointer()和getStr_array()中,sizeof(str)的值都为4,也就验证了作为形参的数组str在调用函数中就是一个指针!
在上述情况1中,虽然表达式中数组名也被认为是指针,但是数组仍然是数组(main函数中sizeof的结果就是很好的验证),而此部分数组就是指针。
这也是数组等价于指针的唯一情况。
换句话说,虽然在将数组作为形参的函数中,你可以继续以数组的形式使用这个参数,但实际上你跟不可能找到数组的踪影!
总结
关于指针和数组之间的异同需要反复的思考和总结,才能搞清关系。
下面对指针和数组之间的可交换性再作义简单的总结。
1.在表达式中以a[i]这样的形式对数组进行访问时,编译器总将其解释为*(a+i)的形式;
2.在数组作为函数的形参时,编译器将数组改写成指针,这个指针即为数组首元素的首地址。
这也是数组等价指针的唯一情形;
3.由于2的原因,一个数组作为函数的形参时,既可以将数组定义成数组,也可以将数组定义成指针;
4.指针和数组永远是两码事,因此在不同文件中的声明和定义必须匹配,但却始终都能写成指针的形式和数组的形式(这完全是写法的不同)。
参考:
《C专家编程》人民邮电出版社;(美)林登(LinDen.P.V.D)著,徐波译;
《C语言深度解剖》北京航空航天大学出版社;陈正冲著;
没有评论»
发表在C语言的那些事儿
Tags:
C编程C语言的那些事儿指针数组
指针和数组的访问方式
2011年2月5日
指针和数组是不相同的,但“很多时候”我们总认为指针和数组等价的。
不可否认,这两者在某种情况下是可以相互替换的,但并不能就因此而认为在所有情况下都适合。
《指针和数组不是一回事儿》系列文章将逐步深入分析指针和数组的不同之处,并解释什么时候指数组等价于指针。
本文属于《指针和数组不是一回事儿》系列文章之二。
前文从内存结构的角度说明了指针和数组的不同,本
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 语言 那些 事儿