天天看点

TCPL(The C Programming Language)读书笔记 第五章 指针与数组

1、指针与地址

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

机器的一个字节可以存放一个char类型的数据,两个相邻的字节存储单元可存储一个short(短整型)类型的数据,而4个相邻的字节存储单元可存储一个long(长整型)类型的数据。指针是能够存放一个地址的一组存储单元(通常是两个或4个字节)。

一元运算符&可用于取一个对象的地址,它只能应用于内存中的对象,即变量与数组元素。不能作用于表达式、常量或register类型的变量。

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

我们应该注意,指针只能指向某种特定类型的对象,也就是说,每个指针都必须指向某种特定的数据类型。(一个例外情况是指向void类型的指针可以存放指向任何类型的指针,但它不能间接引用其自身。)      

例子:将ip指向的对象的值加1,可以写成

*ip += 1;

++*ip;

(*ip)++;

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

最后说明一点,由于指针也是变量,所以在程序中可以直接使用,而不必通过间接引用的方法使用。如,若iq是另一个指向整型的指针,那么 iq = ip;将把ip中的值拷贝到iq中,这样,指针iq也将指向ip指向的对象。

from《C和指针》:变量的值存储于计算机的内存中,每个变量都占据一个特定的位置。每个内存位置都由地址唯一确定并引用,就像一条街道上的房子由它们的门牌号码标识一样。指针只是地址的另一个名字罢了。指针变量就是一个其值为另外一个(一些)内存地址的变量。

另外,ANSI C则声明如果对一个字符串常量进行修改,其效果是未定义的。它也允许编译器把一个字符串常量存储于一个地方,即使它在程序中多次出现。这就使得修改字符串常量变得极为危险,因为对一个常量进行修改可能殃及程序中其他字符串常量。因此,许多ANSI编译器不允许修改字符串常量,或者提供编译时选项,让你自行选择是否允许修改字符串常量。在实践中,请尽量避免这样做。如果你需要修改字符串,请把它存储于数组中。

记得常量就是不能更改的固定值就行了。一个常量存在一个内存空间中,不能试图去修改它,除非重新分配内存空间。

strcpy(char *,”asdsfj”)  //right

strcpy(“asdsfj”,char *)  //wrong

2、指针与函数参数

由于C语言是以传值的方式将参数传递给被调用函数。因此,被调用函数不能直接修改主调函数中变量的值。例如,

void swap(int x,int y)

{  ...   }

则在主函数中,

int a = 3,b = 2;

swap(a,b)

达不到交换目的。因为,参数传递采用传值方式,因此swap函数不会影响到调用它的例程中的参数a和b的值。该函数仅仅交换了a和b的副本的值。实际上两个a、b不是一个变量,一个是实参一个是形参,在内存里是分别分配两个内存空间的,形参交换了也没用,等swap函数运行结束,里边所有的临时变量全部销毁了!

3、 指针与数组

通过数组下标所能完成的任何操作都可以通过指针来实现。

int a[10];

int *pa;

pa = &a[0];(等价语句:pa = a;)

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

无论数组a中元素的类型或数组长度时什么,上面结论都成立。“指针加1”就意味着,pa+1指向pa所指想想的对象的下一个对象。

两个等价的表达方式:&a[i]和a+i的含义是相同的,是a之后的第i个元素的地址;相应的,如果pa是个指针,那么,在表达式中也可以在它的后面加上下标,即pa[i]和*(pa+i)是等价的。简而言之,一个通过数组和下标实现的表达式可等价地通过指针和偏移量实现。

    注意:数组名和指针的不同之处,指针是一个变量,因此,在C语言中,语句pa = a 和pa++都是合法的,但数组名不是变量,因此,a=pa和a++是非法语句。

    在函数定义中,形式参数 char s[];和char *s;是等价的。也可以将指向子数组起始位置的指针传递给函数,这样,就将数组的一部分传递给了函数。例如,a是一个数组,那么f(&a[2])与f(a+2)都是将把起始于a[2]的子数组的地址传递给函数f。

4、 地址算术运算

指针与整数不能互相转换,但0是唯一的例外:常量0可以赋值给指针,指针也可以和常量0进行比较。程序中经常用符号常量NULL代替常量0,这样便于更清晰地说明常量0是指针的一个特殊值。

有效的指针运算包括相同类型指针之间的赋值运算;指针同整数之间的加法或减法运算;指向相同数组中元素的两个指针间的减法或比较运算;将指针赋值为0或指针与0之间的比较运算。

其他所有形式的指针运算都是非法的,例如两个指针间的加法、乘法、除法、移位或屏蔽运算;指针同float或double类型之间的加法运算;不经强制类型转换而直接将指向一种类型对象的指针赋值给指向另一种类型对象的指针的运算(两个指针之一是void *类型的情况除外)。

5、 字符指针与函数

无。

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

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

char *p[MAXNUM];

它表示p是一个具有MAXNUM个元素的一维数组,其中数组的每个元素是一个指向字符类型对象的指针。也就是说,p[i]是一个字符指针,而*p[i]是该指针指向的第i个文本行的首字符。

7、 多维数组

在C语言中,二维数组实际上是一种特殊的一维数组,它的每个元素也是一个一维数组。数组元素按行存储,因此,当按存储顺序访问数组时,最右边的数组下标(即列)变化的最快。

如果将二维数组作为参数传递给函数,那么在函数的参数声明中必须指明数组的列数。数组的行数没有太大关系,因为前面已经讲过,函数调用时传递的是一个指针,它指向由行向量构成的一维数组。

例如:

f(int t[2][13]){ ... }

f(int t[][13]) { ... }

f(int (*t)[13]) { ... } //参数是一个指针,它指向具有13个整型元素的一维数组

8、 指针数组的初始化

在此举例说明:

static char *name[] = {

“Illegal month”,”January”,”February”,”March”,

”April”,”May”,”June”,”July”,”August”,”September”,

”October”,”November”,”December”

};

name是一个一维数组,数组的元素为字符指针。name数组的初始化通过一个字符串列表实现,列表中的每个字符串赋值给数组相应位置的元素。第i个字符串的所有字符存储在存储器中的某个位置,指向它的指针存储在name[i]中。由于上述声明中没有指明name的长度,因此,编译器编译时将对初值个数进行统计,并将这一准确数字填入数组的长度。

9、 指针与多维数组

指针与二维数组之间的区别。假如有下面两个定义:

int a[10][20];

int *b[10];

a是一个真正的二维数组,它分配了200个int型长度的存储空间,并通过常规的矩阵下标计算公式20*row+col计算得到元素a[row][col]的位置。但是对b来说,该定义仅仅分配了10个指针,并且没有对它们初始化,它们的初始化必须以显式的方式进行,比如静态初始化或通过代码初始化。

指针数组的一个重要优点是,数组的每一行长度可以不同,甚至某些元素可以不指向任何向量。所以指针数组最频繁的用处是存放具有不同长度的字符串。

10、命令行参数

在支持C语言的环境中,可以在程序开始执行时将命令行参数传递给程序。调用主函数main时,它带有两个参数。第一个参数(习惯上称为argc,用于参数计数)的值表示运行程序时命令行中参数的数目;第二个参数(称为argv,用于参数向量)是一个指向字符串数组的指针,其中每个字符串对应一个参数。

  //该函数的功能是显示命令行参数

#include<stdio.h>

int main(int argc,char *argv[])

{

int i;

for(i = 1;i<argc;i++)

printf("%s%s",argv[i],(i < argc - 1)?" ":"");

printf("/n");

return 0;

}

比如你生成的exe文件是e:/myproject/project1.exe

进入命令行提示符(开始--程序--附件里面)

然后打命令

e:

cd myproject

project1 字符串1 字符串2

就可以执行了。

本节内容不太能理解!

11、指向函数的指针

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

任何类型的指针都可以转换为void*类型,并且在将它转换回原来的类型时不会丢失信息。

int (*comp)(void*,void*)

它表明comp是一个指向函数的指针,该函数具有两个void*类型的参数,其返回值类型为int。

如果写成下面的形式:

int *comp(void*,void*)

则表明comp是一个函数,该函数返回一个指向int类型的指针。

12、复杂声明

关于复杂声明,需理解:

char **argv

    argv:pointer to char

int (*daytab)[13]

    daytab:pointer to array[13] of int

int *daytab[13]

    daytab:array[13] of pointer to int

void *comp()

    comp:function returning pointer to void

void (*comp)()

    comp:function returning pointer to void

char(*(*x())[])()

x:function returning pointer to array[] of pointer to function returning char

    char(*(*x[3])())[5]

x:array[3] of pointer to function returning pointer to array[5] of char

指针使用:

①使用之前重新定义个同类型指针作为该指针的头指针,以防使用过程中指针位置变化后找不到头指针的位置。

② 要使用的指针最好作为一个不变的量存在,可以定义一个同类型指针指向它,然后在程序中使用。