C语言指针基础语法

如果程序在定义中定义了一个变量,在对程序进行编译时,系统就会给这个变量分配内存单元。编译系统根据程序中定义的变量类型,分配一定长度的空间。例如,一般为整型变量分配2个字节,对单精度分配4个字节,对字符分配1个字节。内存区的每一个字节有一个编号,这就是"地址"。……

1、地址和指针的概念

如果程序在定义中定义了一个变量,在对程序进行编译时,系统就会给这个变量分配内存单元。编译系统根据程序中定义的变量类型,分配一定长度的空间。例如,一般为整型变量分配2个字节,对单精度分配4个字节,对字符分配1个字节。内存区的每一个字节有一个编号,这就是"地址"。然而,存储在单元中的数据就是该单元的内容。在程序中一般是通过变量名来对内存单元进行存取操作的。其实程序经过编译后已经将变量名转换为变量地址,对变量值的存取都是通过地址进行的。这种按照变量地址存取变量值的方法称为"直接访问"。还可以采用另一种称之为"间接访问"的方式,即将一个变量的地址存放到另一个变量中,取值时先找到存放变量地址的变量,然后通过变量地址取出变量的值。如:i、j其中j存放了i的地址,i的值为3。此时,通过取出j中i的地址,再根据取出地址取出i的内容。所谓指向是通过地址来实现的,假设j=2000,然而i的地址也为2000,则j指向了i,通过j可以取出i变量中的内容。一个变量的地址称为该变量的"指针"。如果一个变量专门用来存放另一个变量的地址(即指针),则称为"指针变量"。指针变量的值是一个地址(指针)。指针是一个地址,而指针变量是存放地址的变量。

2、变量的指针和指向变量的指针变量

如前所述,变量的指针就是变量的地址。存放变量地址的变量是指针变量,它是用来指向另一个变量。在程序中为了表示指针变量和他所指向的变量之间的联系,使用‘*’符号表示指向。如果定义了指针变量i_pointer,那么(*i_pointer)是i_pointer所指向的变量。

i=3 <==> *i_pointer=2(表示将3赋值给指针变量i_pointer指向的变量)。

C语言中任何变量在使用前必须进行声明且明确指定变量的类型。指针变量也不列外,语法如下:基本类型 *指针变量名

如:

int i, j;
int *pointer_1, *pointer_2;

定义了两个指向整型的指针变量,左端的int是在定义指针变量是必须指定的"基类型"。指针变量的基类型用来指定该指针变量可以指向的变量类型。如上面的pointer_1和pointer_2只能指向int型。

可以使用赋值语句使一个指针变量得到另一个变量的地址,从而使它指向一个该变量。如:

pointer_1 = &i; // 指向i
pointer_2 = &j; // 指向j

在定义指针变量是要注意两点:

a、指针变量前面的‘*’表示该变量的类型为指针型变量。指针名称是pointer_1和pointer_2,而不是*pointer_1和*pointer_2。

b、在定义指针变量是必须指定基类型。

3、指针变量的引用

请牢记,指针变量中只能存放地址,不要将一个整数赋给一个指针变量。

&:取地址运算符

*:指针运算符(或称"间接访问"运算符),取其指向的内容。

如:

int a = 3;
int *pointer_a = &a;

取出a变量的地址送pointer_a 

*pointer_a取出pointer_a 指向单元的内容

【实例】通过指针变量访问整型变量

#include <stdio.h>
void main()
{
    int a, b;
    int *p1, *p2;
    a = 100;
    b = 200;
    p1 = &a;
    p2 = &b;
    printf("a=%d\tb=%d\n", a, b);
    printf("p1=%d\tp2=%d\n", *p1, *p2);
}

&和*运算符:

a、&*p1的含义是什么? &和*运算符的优先级相同,但按照自右而左方向结合。因此先进行*p1的运算,它就是变量a,再执行&运算。因此,&*p1与&a相同。

b、*&a的含义是什么?先进性&a运算,得a的地址,再进行*运算,即&a所指向的变量。*&a和*p的效果一样。

c、(*p)++相当于a++。注意括号是必要的,如果没有括号,就成为了*p++,++和*的优先级别是一样的,而结合方向是自右而左,因此相当于*(p++)。由于++在p的右边,是"后加",因此先对p的原值进行*运算,得到a的值,然后是p的值改变,这样p不再指向a了。

【例】输入a和b两个整数,按先大后小顺序输出a和b

#include <stdio.h>
void main()
{
    int a, b;
    int *p1, *p2, *p;
    scanf("%d%d", &a, &b);
    p1 = &a;
    p2 = &b;
    if (a < b) 
    {
        p = p1;
        p1 = p2;
        p2 = p;
    }
    printf("p1=%d\tp2=%d\n", *p1, *p2);
}

4、指针变量作为函数参数

函数的参数不仅可以是整型、浮点型、字符型等数据类型,还可以是指针类型。它的作用是将一个变量的地址传送到另一个函数中。

【例】输入a和b两个整数,按先大后小顺序输出a和b,用函数实现。

#include <stdio.h>
void main()
{
    void swap(int *p1, int *p2);
    int a, b;
    int *p1, *p2;
    scanf("%d%d", &a, &b);
    p1 = &a;
    p2 = &b;
    if (a < b) 
    {
        swap(p1, p2); // 或swap(&a, &b);
    }
    printf("p1=%d\tp2=%d\n", *p1, *p2);
}

void swap(int *p1, int *p2)
{
    int temp;
    temp = *p1;
    *p1 = *p2;
    *p2 = temp;
}

【例】输入a、b、c这3个整数,按大小顺序输出。

#include <stdio.h>
void main()
{
    void change(int *q1, int *q2, int *q3);
    int a, b, c;
    int *p1, *p2, *p3;
    scanf("%d%d%d", &a, &b, &c);
    p1 = &a;
    p2 = &b;
    p3 = &c;
    change(p1, p2, p3);
    printf("p1=%d\tp2=%d\tp3=%d\n", *p1, *p2, *p3);
}

void change(int *q1, int *q2, int *q3) 
{
    void swap(int *p1, int *p2);
    if (*q1 < *q2) swap(q1, q2);
    if (*q1 < *q3) swap(q1, q3);
    if (*q2 < *q3) swap(q2, q3);
}

void swap(int *p1, int *p2)
{
    int temp;
    temp = *p1;
    *p1 = *p2;
    *p2 = temp;
}

5、数组与指针

所谓数组元素的指针就是数组元素的地址。引用数组元素可以使用下标法,也可以使用指针法,即通过指向数组元素的指针找到所需的元素。使用指针法能使目标程序质量高(占内存少,运行速度快)。

定义一个指向数组元素的指针变量的方法,与前面一样。如:

int a[10];

int *p; 

注意,如果数组为int型,则指针变量的基类型也应该为int型。如下:

p=&a[0];

C语言规定数组名代表数组中首元素的地址。因此,下面两个语句等价:

p = &a[0];

p = a;

注意数组名a不代表整个数组,上述"p=a;"的作用是"把a数组的首元素的地址赋给指针变量p",而不是"把数组a各元素的值赋给p"。

在定义指针变量时可以给变量赋初始值。如:

int *p = &a[0];

等效与下面两行:

int *p;

p = &a[0];

当然也可以写成:

int *p = a;

6、通过指针引用数组元素

假设p已定义为一个指向整型数据的指针变量,并已经给它赋了一个整型数组元素的地址,使它指向某一个数组元素。如下:

*p=1; 表示将1赋给p当前所指向数组元素。

按照C语言规定:如果指针变量p已经指向数组中的一个元素,则p+1指向同一个数组的下一个元素,而不是简单的加1。如果:数组元素是float型,每个元素占4个字节,则p+1意味着使p的值(是地址)加4个字节。p+1代表的地址实际上是p+1*d,d是数组元素所占字节数。

如果p的初始值为&a[0],则:

(1)p+i和a+i就是a[i]的地址,它们指向a数组的第i个元素。注意:a代表数组的首地址

(2)*(p+i)或*(a+i)是p+i或a+i所指向的数组元素,即a[i]。例如:*(p+5)或*(a+5)就是a[5]。即*(p+5)、*(a+5)、a[5]三者等价。实际上,在编译时,对数组元素a[i]就是按照*(a+i)处理的,即按照数组首元素的地址加上相对位移量得到要找的元素的地址,然后找出该单元中的内容。

(3)指向数组的指针变量也可以带小标。如:p[i]与*(p+i)等价

【例】输出数组中的元素

(1)、下标法

#include <stdio.h>
void main()
{
    int a[10];
    int i;
    // 给数组赋值
    for (i = 0; i < 10; i ++) 
    {
        a[i] = i+10;
    }
    // 输出数组的值
    for (i = 0; i < 10; i ++) 
    {
        printf("a[%d] = %d\n", i, a[i]);
    }
}

(2)、通过数组名计算数组元素地址

#include <stdio.h>
void main()
{
    int a[10];
    int i;
    // 给数组赋值
    for (i = 0; i < 10; i ++) 
    {
        a[i] = i+10;
    }
    // 输出数组的值
    for (i = 0; i < 10; i ++) 
    {
        printf("a[%d] = %d\n", i, *(a+i));
    }
}

(3)、用指针变量指向数组元素

#include <stdio.h>
void main()
{
    int a[10];
    int i, *p;
    // 给数组赋值
    for (i = 0; i < 10; i ++) 
    {
        a[i] = i+10;
    }
    // 输出数组的值
    for (i = 0, p = a; p < (a+10); p ++, i ++) 
    {
        printf("a[%d] = %d\n", i, *p);
    }
}

使用指针变量指向数组元素时需要注意的问题:

a、可以通过改变指针变量的值指向不同的元素。如上使p++来指向不同的数组元素是合法的,但是使用数组名a++是不正确的,因为a是数组的首地址,是一个指针常量,在程序运行期间是固定的。

b、要注意指针变量的当前值。如下:

#include <stdio.h>
void main()
{
    int a[5];
    int i, *p;
    p = a;
    // 给数组赋值
    for (i = 0; i < 5; i++) 
    {
        printf("a[%d] = ", i);
        scanf("%d", p++);
    }
    p = a; // 这句是必须的,因为上一个循环将p+9,此时需要将p从新指向数组首地址
    // 输出数组的值
    printf("\n输出数组a的值:\n");
    for (i = 0; i < 5; i++, p++) 
    {
        printf("a[%d] = %d\n", i, *p);
    }
}

c、从上例可以看出,定义的数组包含10个元素,并用p指向某个数组元素。实际上指针变量p可以指向数组以后的内存单元。如引用了a[10],C编译器不认为非法,系统把它按*(a+10)处理,但是a+10的内存单元是不可知的,所以数据是未知的,这种错误很隐蔽,不以发现,要避免使用。

d、注意指针变量的运算。如果先使p指向数组a的首地址(p=a)。分析:

 1> p++或p+=1。使p指向下一个元素,即a[1]。若执行*p,则取得下一个元素a[1]的值

 2> *p++。由于*和++同优先级,结合方向为自右而左,因此等价*(p++)

 3> *(p++)和*(++p)作用不同。前者是先取*p的值,然后使p加1。后者是先使p加1,再取*p。若p初值为a(即&a[0]),

       则    *(p++)为a[0],则*(++p)为a[1]

 4> (*p)++表示p所指向的元素值加1,如果p=a,则(*p)++相当于(a[0])++。注意:是元素值加1,不是指针加1

 5>  如果p当前指向a数组中第i个元素,则:

    *(p--)相当于a[i--],先对p进行*运算,再使p自减

    *(++p)相当于a[++i],先使p自加,再作*运算

    *(--p)相当于a[--i],先使p自减,再作*元算

7、用数组名作函数参数

当数组名作为函数参数,那么当形参中各元素的值发生变化,实参数组元素的值随之变化。前面介绍过,实参数组名代表该数组的首元素的地址,而形参是用来接收实参传递过来的数组首元素地址的。因此,形参应该是一个指针变量。实际上,C编译器都是将形参数组名作为指针变量来处理的。如:

f(int arr[],int n) 等价于 f(int *arr,int n)

该函数在被调用时,系统会建立一个指针变量arr,用来存放从主调函数传递过来的实参数组首元素的地址。可以使用sizeof(arr)测试该形参的长度。

需要说明

C语言调用函数时虚实结合的方法都是采用"值传递"方式,当用变量名作为函数参数时传递的是变量的值,当用数组名作为函数参数时,由于数组名代表数组首元素地址,因此传递的值是地址,所以要求形参为指针变量。

注意

实参数组名代表一个固定的地址,或者说是指针常量,但形参数组并不是一个固定的地址值,而是作为指针变量,在函数调用开始时,它的值等于实参数组首元素地址,在函数调用期间,它可以被再次赋值。

【例】将数组a中n个整数按相反顺序存放

#include <stdio.h>
void main()
{
    void inv(int x[], int n);
    int i, a[10]={3,7,9,11,0,6,7,5,4,2};
    printf("The original array: \n");
    for (i = 0; i < 10; i ++) 
    {
        printf("%d, ", a[i]);
    }
    printf("\n");
    inv(a, 10);
    printf("The array has been inverted: \n");
    for (i = 0; i < 10; i ++) 
    {
        printf("%d, ", a[i]);
    }
    printf("\n");
}

void inv(int x[], int n)
{
    int temp, i, j, m=(n-1)/2;
    for (i = 0; i <= m ; i ++) 
    {
        j = n-1-i;
        temp = x[i];
        x[i] = x[j];
        x[j] = temp;
    }
    return;
}

void inv2(int *x, int n) 
{
    int *p, temp, *i, *j, m=(n-1)/2;
    i = x;
    j = x+n-1;
    p = x+m;
    for (; i <= p; i++, j--) 
    {
        temp = *i;
        *i = *j;
        *j = temp;
    }
}

如果想要在数组中改变此数组中的元素的值,实参和形参的对对应关系有一下4种情况:

a、形参和实参都是数组名

b、实参用数组名,形参用指针变量

c、实参和形参都用指针变量

d、实参是指针变量,形参为数组名

【例】用选择法对10个整数按由大到小顺序排序

#include <stdio.h>
// 定义BOOL类型
#define BOOL int
#define TRUE 1
#define FALSE 0
void main()
{
    void sort1(int x[], int n);
    void sort2(int x[], int n);
    void sort3(int *x, int n);
    int *p, i, a[10]={3,7,9,11,0,6,7,5,4,2};
    for (p = a, i = 0; i < 10; i ++) 
    {
        printf("%d, ", *(p++));
    }
    printf("\n");
    sort3(a, 10);
    for (p = a, i = 0; i < 10; i ++) 
    {
        printf("%d, ", *(p++));
    }
    printf("\n"); 
}
void sort1(int x[], int n)
{
    int i, j, k, temp;
    for (i=0; i<n-1; i++) 
    {
        k = i;
        for (j=i+1; j<n; j++) {
            if (x[j] > x[k]) k = j;
                printf("%d=%d,", i, k);
        }
        printf("\n");
        if (k != i)
        {
            temp = x[i];
            x[i] = x[k];
            x[k] = temp;
        }
    }
}
void sort2(int x[], int n)
{
    int i, j, temp;
    // 定义布尔变量
    BOOL flag = TRUE;
    for (i=0; i<n-1; i++)
    {
        for (j=i+1; j<n; j++)
        {
            if (x[j]>x[i])
            {
                temp = x[j];
                x[j] = x[i];
                x[i] = temp;
                flag = FALSE;
            }
        }
        if (flag) break;
    }
}
void sort3(int *x, int n)
{
    int i, j, k, t;
    for (i=0; i<n-1; i++) 
    {
        k = i;
        for (j=i+1; j<n; j++)
            if(*(x+j) > *(x+k))
                k = j;
        if (k!=i)
        {
            t = *(x+i);
            *(x+i) = *(x+k);
            *(x+k) = t;
        }
    }
}

8、多维数组与指针

用指针可以指向一维数组的元素,也可以指向二维数组元素。二维数组就是一维数组的数组。如:

int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 0, 1, 2}}; // 定义了一个二维数组

a[0] = {1, 2, 3, 4}

a[1] = {5, 6, 7, 8}

a[2] = {9, 0, 1, 2} 从二维数组的角度来看,a代表二维数组首元素的地址,现在的首元素不是一个简单的整型元素,而是一个由4个整数所组成的一维数组,因此a代表的是首行(即第0行)的首地址。a+1代表第1行的首地址,如果数组首地址是2000,a+1为2008,因为第0行有四个整型,因此a+1的含义是a[1]的地址,即a+4*2=2008。a[0]、a[1]、a[2]既然是一维数组名,而C语言又规定数组名代表 数组首元素地址,因此a[0]代表一维数组a[0]中第0列元素的地址,即&a[0][0]。a[1]=&a[1][0], a[2]=&a[2][0]。怎样表示a[0][1]?a[0]+1。

a[0]+0 ==> a[0][0]    &a[0][0]

a[0]+1 ==> a[0][1]    &a[0][1]

a[0]+2 ==> a[0][2]    &a[0][2]

a[0]+3 ==> a[0][3]    &a[0][3]

如前所述,a[0]和*(a+0)等价、a[1]和*(a+1)等价、a[i]和*(a+i)等价。因此,a[0]+1和*(a+0)+1都是&a[0][1]、a[1][2]和*(a+1)+2的值都是&a[1][2]。请不要将*(a+1)+2写成*(a+1+2),后者变成了*(a+3)了,相当于a[3]。怎样获取a[0][1]的值呢?既然a[0]+1和*(a+0)+1是a[0][1]的地址,那么,*(a[0]+1)就是a[0][1]的值。同理,*(*(a+0)+1)或*(*a+1)也是a[0][1]的值。

*(a[i]+j)或*(*(a+i)+j)是a[i][j]的值。务必记住*(a+i)和a[i]是等价的。

【例】用指针变量输出二维数组元素的值。

#include <stdio.h>
void main()
{
    int a[3][4] = {1,3,5,7,9,2,4,6,8,10,12,14};
    int *p;
    for (p=a[0]; p<a[0]+12; p++)
    {
        if ((p-a[0])%4 == 0)
            printf("\n");
        printf("%4d", *p);
    }
    printf("\n");
}

以上是顺序输出二维数组的值,比较简单。现在我们要取出a[2][3]单元的元素值。因为a[2][3]对a[0][0]的相对位移量为2*4+3=8。即

a[i][j]的地址为&a[0][0]+i*m+j(i表示当前元素前面有几行、m表示每一行有多少列、j表示当前元素在当前行前面共计多少个元素)。

【例】输出二维数组任意一行任意一列元素的值

#include <stdio.h>
void main()
{
    int a[3][4] = {1,3,5,7,9,2,4,6,8,10,12,14};
    int (*p)[4], i, j;
    p = a;
    scanf("i=%d,j=%d", &i, &j);
    printf("a[%d,%d]=%d\n", i, j, *(*(p+i)+j));
}

int (*p)[4];表示p是一个指针变量,它指向包含4个整数元素的一维数组。*p两侧的括号不可缺少,如果写成*p[4],由于方括号[]运算级别高,因此p先与[4]结合,p[4]是定义数组的形式,然后再与前面的*结合,*p[4]就是指针数组。此时的p指针变量只能指向一维数组的首地址,不能指向一维数组中的元素。

一个不注意小事情的人,永远不会成功大事业。——戴尔·卡耐基
0 不喜欢
说说我的看法 -
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号