04指针与数组

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_33487044/article/details/82155958

04指针与数组

指针与地址

通常的机器都有一系列连续编号或编址的存储单元,这些存储单元可以单个进行操纵,也可以连续成组的方式操纵。通常情况下,机器的一个字节可以存放一个 char 类型的数据,两个相邻的字节存储单元可存储一个 short(短整型)类型的数据,而4个相邻的字节存储单元可存储一个 long(长整型)类型的数据。指针是能够存放一组存储单元(通常是两个或4个字节)。

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

p = &c;

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

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

下面代码说明了如何在程序中声明指针以及如何使用运算符 & 和 *:

int x = 1, y = 2, z[10];
int *ip; //p是指向int类型的指针
int *iq;
ip = &x; //ip指向变量x的地址
y = *ip; //将ip指向变量x的值赋给y y = 1
*ip = 0; // x = 0
ip = &z[0]; //ip指向数组z的第一个元素的地址

y = *ip + 1;//把ip指向的对象的值取出并加1,然后将结果赋值给y
*ip += 1; //将ip指向的对象的值加1

iq = ip; //把ip中的值拷贝到iq中,这样,指针iq也将指向ip指向的对象

++*ip; //将ip指向的对象的值加1
(*ip)++ //将ip指向的对象的值加1

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

指针与函数参数

void swap(int *px, int *py) {
    int temp;

    temp = *px;
    *px = *py;
    *py = temp;
}

通过调用上述函数swap(&a,&b)用来交换 a 和 b 的值,由于一元运算符 & 用来取变量的地址,这样 &a 就是一个指向变量 a 的指针。swap 函数的所有参数都声明为指针,并且通过这些指针来间接访问它们指向的操作数。

指针与数组

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

声明

int a[10];

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

c_lesson_5_1

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

int *pa;

则说明它是一个指向整型对象的指针,那么,赋值语句

pa = &a[0];

则可以将指针 pa 指向数组 a 的第 0 个元素,也就是说,pa 的值为数组元素a[0]的内存地址。

c_lesson_5_2

这样,赋值语句

x = *pa

将把数组元素 a[0]中的内容复制到变量 x 中。

如果 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]的内容.

c_lesson_5_3

根据定义,数组类型的变量或表达式的值是该数组第0个元素的地址。执行赋值语句

pa = &a[0];

pa 和 a 具有相同的值。因为数组名所代表的就是该数组最开始一个元素的地址,所以,赋值语句 pa = &a[0] 也可以写成下列形式:

pa = a;

对数组元素a[i]的引用也可以写成*(a + i)这种形式。实际上,在计算数组元素 a[i] 的值时, C 语言先将其转换为*(a + i)的形式,然后在进行求值,因此在程序中这两种形式是等价的。如果对这两种的表示形式分别使用地址运算符 & ,便可以得出这样的结论:&a[i] 和 a + i 的含义也是相同的。a + i 是 a 之后第 i 个元素的地址。

数组传递

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

//计算一个字符串的长度
int strlen(char *s) {

    int n;

    for(n = 0; *s != '\0',s++) {
        n++;
    }

    return n;
}

因为 s 是一个指针,所以对其执行自增运算是合法的。执行 s++ 运算不会影响到 strlen函数的调用者中的字符串,它仅对该指针在strlen函数中的私有副本进行自增运算。下面的函数调用都是合法的:

strlen("hello, world");
strlen(array); //char array[100];
strlen(ptr); // char *ptr

在函数定义中,形式参数 char s[]char *s 是等价的。如果将数组名传递给该函数,函数可以根据情况判定是按照数组处理还是指针处理,随后根据相应的方式操作该参数。也可以将指向子数组起始位置的指针传递给函数,这样,就将数组的一部分传递给了函数。例如,如果 a 是一个数组,那么下面两个函数调用

f(&a[2]);

f(a + 2);

都将把起始于 a[2] 的子数组的地址传递给函数f。在函数f中,参数的声明形式可以为

void f(int arr[]); //数组传递
void f(int *arr); //数组名传递

地址算术运算

如果 p 是一个指向数组中某个元素的指针,那么 p++ 将对 p 进行自增运算并指向下一个元素,而 P += i 将对 p 进行加 i 的增量运算,使其指向指针 p 当前所指向的元素之后的第 i 个元素。

如果指针 p 和 q 指向用一个数组的成员,那么它们之间就可以进行类似于 ==、!=、<、>=的关系比较运算。如果 p 指向的数组元素的位置在 q 指向的数组元素位置之前,那么关系表达式p < q 的值为真。

如果 p 和 q 指向相同数组的中的元素,且 p < q,那么q - p + 1就是位于 p 和 q 指向的元素之间的元素的数目。由此,我们可以编出函数 strelen 的另一个版本,如下所示:

int strlen(char *s) {

    char *p = s;

    while(*p != '\0')
        p++;
    return p -s;
}

在上述程序段的声明中,指针 p 被初始化为指向 s,即指向该字符串的第一个字符。while 循环语句将依次检查字符串中的每个字符,直到遇到标识字符数结尾的字符'\0'为止。由于 p 是指向字符的指针,所以每执行一次 p++,p 就将指向下一个字符的地址,p - s 则表示已经检查过的字符数,即字符串的长度。(字符串中的字符数有可能超过 int 类型所能表示的最大范围。可以使用 size_t 作为函数 strlen 的返回值类型)。

字符指针与函数

字符串常量是一个字符数组,例如:I am a String

在字符串内部表示中,字符数组以空字符'\0'结尾,所以,程序可以通过检查空字符找到字符数组的结尾。字符串常量占据的存储单元数也因此比双引号内的字符数大1。

假定指针pmessage的声明如下:

char *pmessage;

那么,语句

pmessage = "now is the time";

将把一个指向该字符数组的指针赋值给 pmessage。该过程并没有进行字符串的复制,而只是涉及到指针的操作。C 语言没有提供将整个字符串作为一个整体进行处理的运算符。

下面两个定义之间有很大的差别:

char amessage[] = "now is the time"; //定义一个数组
char *pmessage = "now is the time"; //定义一个指针

amessage 是一个仅仅足以存放初始化字符串以及空字符'\0'的一维数组。数组中的单个字符可以进行修改,但 amessage 始终指向同一个存储位置。

pmessage 是一个指针,其初值指向一个字符串常量,之后它可以被修改以指向其他地址,但如果试图修改字符串的内容,结果是没有定义的。

下面给出两种实现字符串复制的方式:

//数组实现字符串的复制
void strcpy(char *s, char *t) {

    int i;
    i = 0;
    while((s[i] = t[i]) != '\0')
        i++;
}
//指针实现字符串的复制
void strcpy(char *s, char *t) {

    while((*s = *t) != '\0')
        s++;
        t++;
}

void strcpy(char *s, char *t) {

    while((*s++ = *t++) != '\0')
        ;
}

void strcpy(char *s, char *t) {

    while(*s++ = *t++)
        ;
}

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

指针数组的声明:

char *lineptr[MAXLINES] = {“Hello, world!”,"Just do it!"};

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

linepter本身是一个数组名,因此,可按照前面例子中相同的方法将其作为指针使用,可以写一个函数来打印 lineptr 数组。

void write_lines(char *linePtr[],int n) {
    while (n > 0) {
        printf("%s\n",*linePtr++);
        n--;
    }

}

循环开始时,*lineptr 指向第一行,每执行一次自增运算都使得 lineptr 指向下一行,同时对 n 进行自减运算。

多维数组

在 C 语言中,二维数组实际上是一种特殊的一组数组,它的每个元素也是一个一维数组。因此,数组下标应该写成

int daytab[i][j];

指针声明二维数组,如下

int *daytab[13];

上述声明了一个数组,该数组有 13 个元素,其中每个元素都是一个指向整型对象的指针。一般来说,除数组的第一维(下标)可以不指定大小外,其余各维都必须明确指定大小。

指针数组的初始化

指针数组的初始化语法和其它类型对象的初始化语法类似:

char *name[] = {"January","February","March","April","May","June"};

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

指针与多维数组

假如有下面两个定义:

int a[10][20];
int *b[10];

a 是一个真正的二维数组,它分配了 200 个 int 类型长度的存储空间,并且通过常规的矩阵下标计算公式 20 * row * col计算得到 a[row][col] 的位置。

对 b 来说,该定义仅仅分配了 10 个指针,并且没有对它们初始化,它们的初始化必须以显式的方式进行,比如静态初始化或通过代码初始化。假定 b 的每个元素都指向一个具有 20 个元素的数组,那么编译器就要为它分配 200 个 int 类长度的存储空间以及 10 个指针的存储空间。指针数组的一个重要优点在于,数组的每一行长度可以不同,也就是说,b 的每个元素不必都指向一个具有 20 个元素的向量,某些元素可以指向具有 2 个元素的向量,某些元素可以指向具有 50 个元素的向量,而某些元素可以不指向任何向量。

命令行参数

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

指向函数的指针

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

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

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

int *comp(void *,void*)

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

#include <stdio.h>

int sum(int, int);

int (*FunPtr)(int, int);

int main() {

    int a,b;
    FunPtr = sum;

    a = sum(3,7);
    b = FunPtr(3,7);

    printf("a = %d, b = %d\n",a,b);

    return 0;
}

int sum(int x, int y) {
    return x + y;
}

猜你喜欢

转载自blog.csdn.net/qq_33487044/article/details/82155958