【C语言学习笔记】《C程序设计语言》 第5章(指针与数组)

Warning:
为了避免非零基础人群感到身体不适、头晕恶心、易怒及粗口,请不要查看以下内容。

在开始本章前,经过认真思考。感觉前面的笔记有点照搬原文,理解并不透彻。个人感觉本书翻译上还有很大的改善空间,知识点结构排版也存在问题…反正对于我这种小白来说,这本书对我很不友好…很多后面的知识点莫名其妙的出现了…感觉晦涩难懂…看的云里雾里…没办法,谁让人家是经典名著…跪着也要啃完这本书。

本章开始的学习笔记中会尝试多用自己的理解和语言来写笔记。使自己对知识掌握更加透彻。本章开始就要接触C语言中令人闻风丧胆的 指针。不过,如果熟练掌握,指针并不是想象中的这么难。用好指针会为程序设计节省大量时间和精力;不过,如果错误使用,发生从错误可以令你纠结几天了。

第5章 函数与程序结构

指针是一种保存变量地址的变量。

  • 指针常常是表达某个计算的唯一途径
  • 使用指针可以生成更高效、更紧凑的代码

因此,指针的使用非常广泛。它与数组有密切关系。本章我们来学习它们的关系,并讨论如何利用。

指针和goto语句一样会使程序难以理解。

  • 如果读者谨慎使用,可以利用它写出简单、清晰的程序
  • 如果读者粗心,指针很容易会指向错误的地址

ANSI C 明确的制定了操纵指针的规则。ANSI C 使用类型void *(指向void的指针)代替char *作为通用指针类型

5.1 指针与地址

通常的机器有一系列连续编号或编址的存储单元,这些存储单元可以单个进行操纵,也可以以连续成组的方式操纵。指针是能够存放一个地址的一组存储单元。

一元运算符 & 可以用于取一个对象的地址,因此,下列语句:

p = &c;

将把c的地址赋值给变量p,我们称p为指向c的指针。地址运算符&只能应用于内存中的对象,即变量与数组元素。它不能作用于表达式、常量或register类型的变量。

一元运算符 * 是间接寻址或间接引用运算符。当它作用于指针时,将访问指针所指向的对象。

我们在这里假定x与y是整数,而ip是指向int类型的指针。下面的例子可以很好的运用这两个运算符:

int x = 1, y = 2, z[10];
int *ip; /* ip是指向int类型的指针 */

ip = &x; /* ip现在指向x */
y = *ip; /* y的值现在为1 */
*ip = 0; /* x的值现在为0 */
ip = &z[0]; /* ip现在指向z[0] */

一元运算符 * &的优先级比算术运算符高,因此,赋值语句:

y = *ip +10;

将把*ip指向的对象的值取出来并加10,然后再将结果赋值给y,而下列赋值语句:

*ip += 1;

则将ip指向的对象加1,它等同于:

++*ip;

或:

(*ip)++

语句(*ip)++中的圆括号是必要的,否则,该表达式将对ip进行加一运算,而不是对ip指向的对象进行运算。
这是因为,类似于*和++这样的一元运算符遵循从左至右的结合顺序。

最后说明一点,由于指针也是变量,所以在程序中可以直接使用,而不必通过间接引用的方法使用。

5.2 指针与函数参数

C语言是以传值的方式将参数值传递给调用函数,因此,被调用函数不能直接修改主调函数中变量的值。但是,学习了数组以后,我们可以使主调函数将指向所要交换的变量指针传递给被调用函数。

指针参数使得被调用函数能够访问和修改主调函数中对象的值,

5.3 指针与数组

通过数组下标能完成的事情都可以通过指针来实现。一般来说,用指针编写的程序比用数组下标编写的程序执行起来速度快,但另一方面,用指针实现的程序理解起来较为困难。

声明:

int a[10];

定义了一个长度为10的数组a,换句话说,它定义了一个由10个对象组成的集合。这10个对象存储在相邻的内存区域中,名字分别为:a[0]、a[1]、a[2]…、a[9]

a[i]表示该数组的第i+1个元素。如果pa的声明为:

int *pa;
pa = &a[0];

它是一个指向整型对象的指针,指针pa指向数组a的第1个元素,也就是说,pa的值是a[0]的地址。

如果pa指向数组中某个特定的元素,那么,根据指针运算的定义,pa+1将指向下一个元素,pa+i将指向pa所指向数组元素之后的第i个元素,而pa-i将指向pa所指向数组元素之前的第i个元素。因此,如果指针pa指向a[0],那么*(pa+1)引用的是数组元素a[1]的内容,pa+i是数组元素a[i]的地址,*(pa+i)引用的是数组袁术a[i]的内容。

下标和指针运算之间有着密切的联系。根据定义,数组类型的变量或表达式的值是该数组第0个元素的地址。
因此,下面2种表达方式是等效的:

pa = &a[0];
pa = a;

对数组元素a[i]的引用也可以写成*(a+i)的形式。

但是,我们必须记住,数组名和指针之间有一个不同之处。指针是一个变量,因此,在C语言中,语句pa = a和pa++都是合法的。但数组名不是变量,因此,类似于a = pa和a++的形式的语句是非法的。

5.4 地址算术运算

C语言中的地址算术运算方法是一致且有规律的,将指针、数组和地址的算术运算集成在一起是C语言的一大优点。为了说明这一点,我们来看一个不完善的存储分配程序。它由2个函数组成,第一个函数alloc(n)返回一个指向n个连续字符存储单元的指针,第二个函数afree§释放已分配的存储空间。之所以说这个程序是不完善的,是因为对afree函数的调用次序必须与调用alloc的次序相反。换句话说,alloc与afree以栈的方式(即先进后出的列表)进行存储空间管理。

最容易的实现方法是让alloc函数对一个大字符数组allocbuf中的空间进行分配。该数组是alloc和afree两个函数的私有数组。由于函数alloc和afree处理的对象是指针而不是数组下标,因此,其它函数无需知道该数组的名字。这样,可以在包含alloc和aree的源文件中将该数组声明为static类型,使得它对外不可见。

#define ALLOCSIZE 1000 /* 可用空间大小 */

static char allocbuf[ALLOCSIZE]; /* alloc使用的存储区 */
static char *allocp = allocbuf; /* 下一个空闲位置 */

char *alloc(int n) /* 返回指向n个字符的指针 */
{
    if(allocbuf + ALLOCSIZE - allocp >= n){ /* 有足够的空闲空间 */
          allocp += n; 
          return allocp - n; /* 分配前的指针p */
       } else /* 空闲空间不够 */
           return 0;
}

void afree(char *p) /* 释放p指向的存储区 */
{
     if(p >= allocbuf && p < allocbuf + ALLOCSIZE)
             alloc = p;
}

一般情况下,同其它类型的变量一样,指针也可以初始化。通常,对指针有意义的初始化值只能是0或者表示地址的表达式,对后者来说,表达式所代表的地址必须是在此前已定义的具有适当类型的数据的地址。
例如,声明:

static char *allocp = allocbuf;

将allocp定义为字符类型的指针,并将它初始化为allocbuf的起始地址,该起始地址是程序执行时的下一个空闲位置。

指针与整数之间不能相互转换,但0是唯一的例外:常量0可以赋值给指针,指针也可以和常量0进行比较。程序中常用符号NULL代替常量0,这样便于更清晰的说明常量0是指针的一个特殊值。符号常量NULL定义在标准头文件<stddef.h>中。

指针的算术运算具有一致性:如果处理的数据类型是比字符型占据更多存储空间的浮点类型,并且p是一个指向浮点类型的指针,那么在执行p++后,p将指向下一个浮点数的地址。因此,只需要将alloc和afree函数中所有的char类型替换为float类型,就可以得到一个适用于浮点类型而非字符型的内存分配函数。所有的指针运算都会自动考虑它所指向的对象的长度。

5.5 字符指针与函数

字符串常量是一个字符数组,例如:
“I am a string”
在字符串的内部表示中,字符数组以空字符 ‘\0’ 结尾。所以,程序可以通过检查空字符找到字符数组的结尾。字符串常量占据的存储单元数也因此比双引号内的字符数大1。

5.6 指针数组以及指向指针的指针

由于指针本身也是变量,所以它们也可以像其它变量一样存储在数组中。
初学阶段,这里暂时不作讲述。

5.7 多维数组

C语言提供了类似于矩阵的多维数组,但实际上它们并不像指针数组使用的那样广泛。

我们考虑到一个日期转换的问题:把某月某日这种日期表示形式转换为某年中第几天的表示形式。反之亦然。例如,3月1日是非闰年的第60天,是闰年的第61天。在这里,我们定义下列两个函数以进行日期转换:函数day_of_year将某月某日的日期表示形式转换为某一年中第几天的表示形式,函数month_day则执行相反的转换。因为后面的函数要返回两个值,所以在month_day中,月和日这两个参数使用指针的形式:

month_day(1988, 60, &m, &d)

将把m的值设置为2,把d的值设置为29。

这些函数都要用到一张记录每月天数的表。对闰年和非闰年来说,每个月的天数不同,所以,将这些天数分别存放在一个二维数组的两行中比在计算过程中判断2月有多少天容易,。该数组以及执行日期转换函数如下:

static char daytab[2][13] = {
     {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
     {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
} 
/* day_of_year函数:将某月某日的日期表示形式转换为某年中第几天的表示形式 /*
int day_if_year(int year, int month, int day)
{
     int i, leap;
      
     leap = year%4 == 0 && year%100 !=0 || year%400 == 0;
     for(i=1; i < month; i++)
         day += daytab[leap][i];
     return day;
}

void month_day(int year, int yearday, int *pmonth, int *pday)
{
    int i, leap;
     
    leap = year%4 == 0 && year%100 != 0 || year%400 == 0;
    for(i = 1; yearday > daytab[leap][i]; i++)
       yearday -= daytab[leap][i];
       *pmonth = i;
       *pday = yearday;
}

5.8 指针数组的初始化

指针数组的初始化和普通数组初始化基本一致,不再详细讲述。
初学阶段,这里暂时不作讲述。

5.9 指针与多维数组

初学阶段,这里暂时不作讲述。

5.10 命令行参数

初学阶段,这里暂时不作讲述。

5.11 指向函数的指针

初学阶段,这里暂时不作讲述。

5.12 复杂声明

初学阶段,这里暂时不作讲述。

总结

由于本书为经典著作,书中的知识点比较全面,同时也比较深入。后面的几节确实有部分难度,对于初学阶段,暂时不深入研究。先将C语言大体框架进行学习,后期结合其它书籍和资料深入研究,并回来补充未学习的部分。

本章学习了指针和数组,对于初学者来说,指针确实令人迷惑。但是熟悉之后,合理的利用指针会使程序设计事半功倍。我们将继续学习后面的知识点,尽快结束本书学习。

发布了14 篇原创文章 · 获赞 0 · 访问量 493

猜你喜欢

转载自blog.csdn.net/AtomTeam/article/details/104346766