C语言入门 —— 指针和数组

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

ANSI C使用类型void*(指向void的指针)代替char *作为通用指针的类型。

1、指针与地址

32位机器中指针的长度为2字节,64位机器中指针的长度为4字节。

一元运算符&可用于取一个对象的地址,

c变量的地址赋值给pp为指向c的指针。

地址运算符&只能应用于变量和数组元素,不能作用于表达式、常量或者register类型的变量。

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

技巧: 变量可以和函数同时申明。

表达式中,*dpatof(char *)的值都是double类型的。

一元运算符*&的优先级比算数运算符的优先级高。

2、指针与函数参数

上面一个函数是错误的,无法达到2个数对换的效果,只有将其改成传变量的指针才可以。

这样是正确的。

3、指针与数组

在C语言中,指针和数组之间关系十分密切。通过数组的下标所能完成的任何操作都可以通过指针来实现。
一般来说,用指针编写的程序比用数组下标编写的程序执行速度快。

根据指针运算的定义,pa+1指向下一个元素。

pa = &a[0];也可以写成pa = a;

当把数组名传递给一个函数时候,实际上传递的是数组的第一个元素的地址,在被调用函数中,该参数是一个局部变量,因此,数组名参数必须是一个指针。

如上面的程序中,我们编写了一个myStrlen的函数。因为s是一个指针,所以,对其执行的自增运算时合法的。我们不仅可以传入字符串,也可以传入指针和数组。

并且函数的形参可以定义成char *s,也可以定义成char s[]

4、地址算术运算

之前我们描述了指针可以通过+-去运算。除了加减运算,指针还可以进行比较运算。

上面这段代码主要作者用是模拟了allocafree这2个函数。主要通过指针之间的比较,去判断是否有足够大的内存去分配。

这段的意思是:
初始内存块 + 最多申领内存 – 当前已被申领内存 >= 本次需要申领的内存

转换一下就是说

剩余内存 是否大于等于 本次需要申领的内存,大于就可以申领,反之不可以。

有效的指针运算包括:

  • 相同类型指针之间的赋值运算
  • 指针同整数之间的加减运算
  • 指向相同数组中元素的两个指针的剑法活着比较运算
  • 将指针赋值为0或者与0之间的比较运算

5.5 字符指针与函数

字符串常量是一个字符数组,例如:

在字符串内部表示 中,字符数组以空字符\0结尾,所以,程序可以通过检查空字符找到字符数组的结尾。

上述声明中,amessage是一个仅仅足以存放初始化字符串以及空字符串\0的一维数组。数组中的单个字符是可以进行修改的。

pmessage是一个指针,其初始值指向一个字符串常量,之后他可以修改指向其他地址,但是不能修改字符串里面的字符,否则将会报错没有定义。

例子:
1、 复制字符串

2、 字符串比较是否相等

6、 指针数组以及指向指针的指针

由于指针本身也是变量,所以他们也可以像其他变量一样存储在数组中。

如上,就是指针数组。

上面这段代码,主要描述了通过快速排序法,将字符串指针数组,进行排序。
由于符串指针数组指向的字符串常量,所以我们并不能修改字符串的值,只能通过交换指针(swap方法)进行排序。

这种实现方法消除了因为移动文本本身所带来的复杂的存储管理和巨大的内存开销这两个问题,所以效率会很高。

7、 多维数组

C语言提供了类似矩阵的多维数组,但实际上他们并不像指针数组一样使用的那样广泛,所以了解即可。

我们可以如下定义多维数组,下面简单的描述了润平年,每月的天数。

我们可以省略多维数组中的第一维的维数(行数)

如果将多维数组以参数的形式传给数组的时候,可以写成:

f(int (*daytab)[13]) { /*...*/ }这种声明形式表明参数是一个指针,它指向具有13个整型元素的一维数组。

因为方括号[]的优先级高于*,所以上述声明中必须使用圆括号。不过不加上圆括号的话,int *daytab[13],相当于声明了一个数组,该数组有13个元素,每个元素都是一个指向整型对象的指针。

8、 指针数值的初始化

9、 指针与多维数组

对于初学者来说,很容易混淆二位数组和指针数组之间的区别。假如有下面两个定义:

很明显int a[10][20]是二位数组,int *b[10]是指针数组。

从语法角度上来看,a[3][4]b[3][4]都是一个合法的引用。但是a是一个真正的二维数组,内存需要为他分配200个int类型长度的存储空间,并且通过公式20*row+col计算得到元素a[row][col]的位置。

但是对b来说,该定义仅仅分配了10个指针,并且没有对他们进行初始化。他们的初始化必须以显示的方式进行。指针数组的优势在于,数组的每一行的长度可以不一样,也就是说有的行可以是2个元素,有的行可以是20个元素。

以图形的方式来表示,

指针数组:

二维数组:

10、 命令行参数

在支持C语言的环境中,可以在程序开始执行时,将命令行参数传递给函数。调用主函数main时候,它带有2个参数,argcargv,分别表示参数个数,和指向字符串数组的指针,其中每一个字符串对应一个参数。

上面这段代码模拟了echo方法,通过编译,调用传参,将打印传入的参数。如下,

按照C语言的约定,argv[0]的值是启动该程序的程序名,因此argc的值至少为1。上面的例子中,argc为3,argv[0]argv[1]argv[2]分别对应了echohelloworld。另外按照ANSI的要求,argv[argc]的值必须为空指针。

11、 指向函数的指针

C语言中,函数本身不是变量, 但是可以定义指向函数的指针,这种类型的指针可以被复制、存放在数组中、传递给函数以及作为返回值返回。

函数的首地址存储在某个函数指针变量中。这样,我就可以通过这个函数指针变量来调用所指向的函数了。

整个函数指针变量的声明格式如同函数dump的声明处一样,只不过我们把dump改成(*funP)而已,这样就有了一个能指向dump函数的指针了。当然,这个(*funP)指针变量也可以指向所有其它具有相同参数及返回值的函数。

void foo(void (*func)(int x), int x);就是将void (*func)(int x)作为参数传入函数foo,在foo函数中,我们可以可以通过(*func)(x);的方式进行调用。

void (* bar()) (int);的表达意思是返回变量(函数指针)类型为:void(*)(int)。感觉比较绕。

12、 复杂声明

C语言的声明不能从左至右阅读,而且使用了太多的圆括号。

我们来看2个声明:

是一个函数,它返回一个指向int类型的指针

是一个指向函数的指针。该函数返回一个int类型的对象。
*是一个前缀运算符,优先级低于(),所以必须使用()来保证正确的结合顺序。

尽管实际中很少会用到复杂的声明,但是,懂得如何理解、运用这些复杂声明是非常重要的。另一个比较好的解决方法是使用typedef,会在后续的博客中来写到。

接下来我们可以看一下一些复杂声明的案例:

最后2个声明简直就是操蛋啊,但是静下心来我们可以按照规则进行分析。

规则就是,从左至右解读,圆括号括起来的内容优先级高。

那么先对char (*(*x())[])()进行解读:
第一步,*x()表示x函数返回一个指针。
第二步,*(*x())[]表示x函数返回一个指针指向的是一个指针数组。
第三步,char (*(*x())[])()表示x函数返回一个指针指向的是一个指针数组,数组的类型是返回char类型的函数。

接下来解读char (*(*x[3])())[5]
第一步,*x[3]表示x是一个指针数组。
第二步,*(*x[3])()表示x是一个指针数组,数组的内容是返回指针的函数。
第三步,char (*(*x[3])())[5]表示x是一个指针数组,数组的内容是返回指针的函数,函数返回的指针指向了char类型的数组。