liuchuanwu的个人博客分享 http://blog.sciencenet.cn/u/liuchuanwu

博文

C语言的数组和指针

已有 8691 次阅读 2014-1-13 13:08 |个人分类:C语言|系统分类:科研笔记| C语言, 地址, 数组, 指针, 内存

做个笔记,关于C语言中变量、数组、函数和指针间的关系:


一,变量和指针

引用一段作者hex108的精彩论述:

===============

   一个内存地址存着一个对应的值,这是比较容易理解的。

   如果程序员必须清楚地知道某块内存存着什么内容和某个内容存在哪个内存地址里了,那他们的负担可想而知。
   汇编语法对“一个内存地址存着一个对应的数”,作了简单的“抽象”:把内存地址用变量名代替
了,对内存地址的取值和赋值方式不变。
   c语言对此进行了进一步的抽象:变量 <==> (一个内存地址,对应的值)(这里忽略类型等信息)。

   把C语言中的基本类型(int,long,float等),指针,数组等还原为(一个内存地址,对应的值)后,就能更清淅地理解它们了。

   内存就相当于(addr,val)的大hash表,c语句的语义基本就是改变hash值。

   为了下文的方便,特定义如下语义(遵循C的标准语义):

   var  <==>  (addr, val)  (var为一个变量名,addr为var在内存中的首地址,val为var 的值)
   &var <==> addr
   var  <==> var作为左值出现(即等式左边)时,var等价于 addr;
              var作为右值出现(即等式左边)时,var等价于 val;
   *var <==> val

   注:符号"<==>" 右边出的等式 x = y(x是一个内存地址,y是一个值); 表示将内存地址为x的内容置为值y,如addr = 3表示置内存addr里的值为3

===================

C语言中的变量一般具有带有两个性质,名字和值。当声明一个变量如

int k;

时,发生了两件事,一方面根据int,系统分配了(一般)2个字节的内存用以存放一个整数数值,另外设置了变量名表,加入了符号k和所分配的地址。之后遇到如

k = 2;

时,系统把k所对应的内存地址的置设置为2。所以我们说C语言的变量(例如名为var)具有一对特征值(内存地址addr,变量内容val)有时也分别称为(lvalue, rvalue)。lvalue在赋值号(=)左侧,而rvalue在赋值号右侧。一个变量名在赋值号左侧时表示地址,右侧时表示变量值。赋值语句val1 = val2,相当于左侧变量的地址内容设定为右侧变量的值。可以&var得到对应的地址addr,用*addr得到对应的内容va。内存中每一个字节具有一个地址,系统给不同类型变量分配的内存(称为一个memory cell)大小不同,如char型含有一个字节。而一个short型可能含有2个字节,一个long型可能含有4个字节。一个变量的地址定义为分配的内存区域的首字节地址。声明变量时要声明变量类型,相当于告知系统变量内存大小如何利用地址存取变量内容。系统为变量分配地址为一常数不可改变,但变量所存内容可以改变。


变量可以是结构体或数组。变量名是结构体或数组的首地址,即第一个元素的地址。变量内容也可以是另一个地址。这样的变量就是指针,如

int *p;

其中int *声明p为int *型变量,即p的rvalue是可存储一个整型变量的内存地址。*p则表示p所指的内存区域的值 p也有地址,p的lvalue即&p就是p的地址。此处p尚未赋值,因此*p未知或错误,所以一般将未有指向的一个指针赋值为NULL,即指向0地址。而C语言的0地址具有特殊意义,不会被其他变量使用。指针的赋值使用

p = &k;


void型指针可以作为各类指针的一般性声明。



二,数组和指针:

关于C语言数组首先注意两条:

(1)C语言只有一维数组,且数组大小必须在编译时作为常数确定下来,不过C99标准已经允许可变大小数组,在GCC编译器已经实现。C语言数组的元素可以是任意类型,因此可以是另一个数组,这样可以模拟出一个高维数组。

(2)对于一个数组,我们只能做两件事:确定数组大小,以及获得指向首个元素的地址的指针。其他关于数组的操作都是通过指针进行的。


C语言的数组为一顺序存储的线性表,在数组元素在内存中连续存储。数组名为数组的首地址,也就是第一个元素的地址。如:

int i;

int a[10];

int *p;

p = a;

此时p,a和&a[0]同为数组首地址。数组元素地址通过计算地址位移量(以数据类型存储空间为单位)取得,C语言的数组元素下标从0开始(后面所称第i个元素从第0个开始),使i等于位移量。指针和整数相加,整数自动按指针所指类型进行scale。p+i, a+i(i可以是负数)是第i号元素的地址(不是地址的二进制数值加i或者后面第i个内存位置)即&a[i]。*(p+i), *(a+i)就是第i号元素的值即a[i],即先计算元素地址,再取值。实际上C编译系统里a[i]就是*(a+i)的等价写法。因此,[]在这里可看成按位移量读取数值的运算符。所以此处指针变量也可带下标,p[i]与a[i]等价。因为a+i和i+a的含义一样,因此a[i]和i[a]含义一样。当i的值大于组大小时,以上各语句编译合法,但可能得到不可料的内存结果。指针和数组名不同之处在于1)因为p是指针变量,而a是数组地址(可以看作一个指针常量),所以p++合法而a++不合法。2)指针变量具有自己的存储空间。


指针的操作包括同类型指针赋值pa = pa;,指针加减整数p+i,p-j外,还包括同类型指针相减。q-p+1可以得到从p到q的特定种类的数据存储个数。这些操作一般要求在同一个数组内。


补充关于数组的初始化。1)数组在声明同时初始化时,数组大小不可为变量。2)全局数组和static数组,元素初始值为0,函数内定义的一般数组元素初始值不确定。


三,多维数组和指针。

多维数组是元素也为数组的一维数组。以二维数组为例。如一个5行3列数组:

int a[5][3] = {{A00, A01, A02},

                    {A10, A11, A12},

                    {A20, A21, A22},

                    {A30, A31, A32},

                    {A40, A41, A42}};//Aij为整型数据。

数组的元素存储先低维后高维。在C语言中数组的维数右侧为低维,左侧为高维(或称按行存储),而在Fortran中左侧维数为地位,右侧为高维(或称按列存储)。所以此数组a含有5个元素,每个元素是包含3个元素(而不是数组a包含3个各含有5个元素的数组型元素)的数组。a是指向指针数组的指针,它的值为首个元素数组的地址。a的元素是5个一维数组a[i](0<=i<=4)。所以a是第一元素的地址即a[0]的地址,(即a=a[0]=&a[0][0]),a+i就是第i个元素的地址,a[i]或*(a+i)第i个元素,因为他们是数组名,所以实际上也是地址 (也等于&a[i][0])。同理a[i]+j(0<=j<=2)就是第i行第j号元素的地址。因此i行j列元素的值a[i][j]也可表示为*(*(a+i)+j),*(a[i]+j),(*(a+i))[j]等形式。



四、结构体和指针

结构体作为一种变量类型,其地址就是结构体在内存中分配的空间的岂是地址。结构体类型指针可以指向结构体。如

struct Student

{int num;

 ...;

}; //构造Student结构体类型


struct Student stu_1; //定义Student类型结构体stu_1

struct Student *p; //定义指向Student类型结构体的指针p

p = &stu_1;

(*p).num = 100; //也可写成p->num

注意*p两侧括号不可省。因为成员运算符.优先于*运算符。*p.num相当于*(p.num)。



五、函数的指针

C语言在编译时为定义的函数分配了一段存储空间,用来存储函数的指令。函数名就是所分配的内存起始首地址(入口地址)。可以定义指向函数的指针。

int num;

int max(int x, int y); //定义参数为int和int的函数,返回值为int

int (*p)(int x, int y); //定义指针p,可指向参数为两个整型变量的整型函数

p = max;

num = (*p)(10, 20);

注意第三行代码的*p两边的括号不能少,否则变成声明返回值为指针类型的函数p。函数型指针也可以作为函数的参数。



六、指向数组的指针。

讨论如下代码

int *p;

int a[5][3];

p = a;

这段代码是非法的(但在的编译器是可以通过的!但意义不同: *p为数值,p[3]实际上是a[0][3]),因为p是指向整型变量的指针。而a是指向整型数组的指针。故需要声明p为指向整型数组的指针:

int (*p)[3];

不能写成

int *p[3];//表示声明整型指针数组


七、指向指针的指针。注意以下两个声明的不同:

char a[][6] =  {"First", "Second", "Third", "Fourth", "Fifth"};

char *b[] = {"First", "Second", "Third", "Fourth", "Fifth"};

前者是二维数组,a和a+i的类型为char (*)[6],a的每个元素是(大小为6的)字符数组。a[i]的类型是char *,是字符的存储地址,a[i][j]则是字符,整个数组a存储在动态存储区 。而后者的是指针数组,b和b+i的类型是char **,他的每个元素b[i]是字符指针(指向字符串常量),b的5个指针元素位于动态存取区,而每个元素指向连续存储在静态存储区的字符串常量。


所以指向a的指针应该是char (*)[6]型,而指向b的指针应该是char **型:

char (*pa)[6];

char **pb; //不能将p声明成 char *p否则为p指向字符而不是指针

pa = a;

pb = b; //p指向元素指针。或p = a + i指第i个元素指针



八、数组作为函数的形式参数。

当声明数组作为函数的形式参数时,等价于声明数组名为指针,即数组名是不再是指针常量而变成指针变量。因为函数的形式参数是需要接收实参的值,这里是数组的首地址,而纯粹的数组名是不能进行赋值的。下面几个函数:

void f(int a[]);

void f(int a[1]);

void f(int a[10]);

void f(int *a);

完全等价。在这几个函数中可以对a进行指针操作。



总结:

(1)一个变量有地址和值。一个变量可以占有多个内存空间,它的地址就是内存的起始地址。

(2)变量的值可以是规定类型的地址,这就是指针。通过指针可以对其指向的变量进行操作。

(3)多个同型变量构成一个数组。数组在内存中顺序存储,数组名是数组首地址能。

(4)指针的操作同类型指针赋值、指针加或减整数(根据指针类型自动scale)和同数组内的指针相减。

(5)函数名是函数指令所分配空间的起始地址(?)。

(6)可以定义指向数组的指针(如char (*p)[N];) 和指向指针的指针(如char **p;)。

(7)当数组作为函数的形式参数时,数组名作为指针处理。


部分内容是我个人的理解,或有错误。


参考:

【1】 谭浩强著. C程序设计(第四版). 北京:清华大学出版社,2010

【2】 Brian W. Kernighan & Dennis M. Richie. The C programming language (Second Edition).

【3】 Andrew Koening. 高巍译. C陷阱与缺陷. 北京:人民邮电出版社,2008


2014年1月17日



https://blog.sciencenet.cn/blog-1005104-758700.html

上一篇:寻你----到王洛宾的歌里去流浪
下一篇:C语言的字符串和指针
收藏 IP: 128.250.55.*| 热度|

0

该博文允许注册用户评论 请点击登录 评论 (0 个评论)

数据加载中...

Archiver|手机版|科学网 ( 京ICP备07017567号-12 )

GMT+8, 2024-4-25 06:55

Powered by ScienceNet.cn

Copyright © 2007- 中国科学报社

返回顶部