【第九节】C语言的指针

目录

前言

一、什么是指针

二、指针的基本使用方式

2.1 定义指针变量

2.2 给指针变量赋值

2.3指针变量的引用

三、指针与函数传参

四、指针的运算

4.1 指针加或减整数

4.2 指针减指针

五、指针与一维数组

六、指针与二维数组

七、指针与多维数组

八、指针数组和数组指针

8.1 指针数组

8.2 指针数组和二维数组的区别

8.3 指针数组和数组指针的区别


前言

        C语言的指针是由其自身的内存管理机制而来的。在早期的C语言中,内存管理主要由程序员负责,每个变量都会分配一段内存,变量的值存储在该地址的字节中。这使得通过直接操作内存地址来操作变量成为可能。这种通过直接操作内存地址来访问或操作数据的方式,就是指针的基本概念。

指针在C语言中有许多用途:

  1. 用于变量的存储和访问:通过指针,程序可以直接访问和操作内存中的数据,这在需要动态分配和回收内存的场景中尤其重要。
  2. 用于数组操作:指针允许程序直接操作数组的每一个元素,而无需使用循环。
  3. 用于函数参数传递:通过指针,函数可以间接地访问和修改其参数。
  4. 用于结构体操作:指针允许程序直接访问结构体的字段,无需逐一访问。
  5. 用于动态内存分配:使用指针,可以动态地分配和回收内存,这在一些需要大量内存的场景中非常有用。

        需要注意的是,由于指针直接操作内存,如果不正确地使用,可能会导致程序崩溃或者数据丢失。因此,理解和使用指针需要对C语言的内存管理有深入的理解。总之,指针是C语言的一个重要特性,它提供了对内存的直接访问和操作的能力,使得程序员可以更灵活地编写高效、可靠的代码。

一、什么是指针

        计算机内存是以字节为单位的存储空间,内存的每一个字节都有一个唯一的编号,这个编号就称为地址。当C程序中定义一个变量时,系统就分配一个带有唯一地址的存储单元来存储这个变量,程序对变量的读取操作 (即变量的引用),实际上是对变量所在存储空间进行写入或取出数据。通常我们引用变量时是通过变量名来直接引用变量,例如赋值运算 b=55,系统自动将变量名转换成变量的存储位置(即地址),然后再将数据 55 放入变量  的存储空间中。这种引用变量的方式称为变量的“直接引用”方式。
        此外,C 语言中还有另一种称为“间接引用”的方式。它首先将变量A 的地址存放在一个变量B(存放地址的变量成为指针变量)中,然后通过存放变量地址的这个变量B 来引用变量A。
        一个变量的地址称为该变量的指针。用来存放一个变量地址的变量称为指针变量。当指针变量 p的值为某变量的地址时,可以说指针变量 p 指向该变量。
        如果上面的话说的不清楚,那么我们总结一下:
        1. 定义变量的本质:开辟出一块空间,并用变量名代表那一片空间。
        2. 内存空间的最小单位是字节,每一个字节都有一个编号,这个编号我们称之为地址。
        3. 有一种特殊的变量,专门存储地址,这种变量叫做指针变量,一般我们也简称为指针。

二、指针的基本使用方式

指针的使用一般分为三步:
1)定义指针变量
2)给指针变量赋值
3)指针解引用
在这个过程中你需要使用到两个运算符:&和*

2.1 定义指针变量

指针变量定义的一般形式为
类型名 *指针变量名;
如  int *pNums;

注意:
A. 变量名前面的“*”是一个说明符,用来说明该变量是指针变量,这个“*”是不能省略的,但是它不是变量名的一部分
B.类型名表示指针变量所指向的变量的类型,而且只能指向这种类型的变量。

指针变量允许在定义时进行初始化,例如:

int nNumA, nNumB;
int *pA=&nNumA, *pB=&nNumB;

表示:定义了两个指向 int 型变量的指针变量 pA 和 pB,pA 的初值为变量 nNumA 的地址&nNumA ,pB 的初值为变量 nNumB 的地址& nNumB ,不是表示*pA的初值为&nNumA ,*pB的初值为&nNumB。

2.2 给指针变量赋值

一般我们会用到&运算符,用于取一个对象的地址。通常情况下我们应该把一个变量的地址赋值给指针,类似于这样:

int main(){
    int nNum =100;
    int* pl;
    pl = &nNum;
    return 0;
}

2.3指针变量的引用

指针变量有两个有关的运算符
1)& 取地址运算符
2)* 指针运算符

例如:

int a =12;
int *p=0;
p = &a;
int temp = *p; //等价于 temp = a;
*p = 200; //等价于 a= 200;

&a 表示变量 a的地址
*p 表示指针变量 p指向的变量

注意:
指针变量是用来存放地址的,不要给指针变量赋常数值,例如:
int *p= 1000;  //错误
指针变量没有指向确定地址前,不要对它所指的对象赋值,例如:
int nNum=5, *p=nNum; // 类型不匹配

总结:

1)&操作符用于获取变量的内存地址。
2)*操作符用于声明指针变量(在这种情况下,它表示该变量是一个指针),以及解引用指针(在这种情况下,它表示获取指针指向的值)。

三、指针与函数传参

指针是间接引用,可以利用这一特性进行参数传递,使得函数内值的变化能够影响到函数外部:

void swap(int x, int y)
{
    int tmp = x;
    x = y;
    y = tmp;
}

int main(void)
{
    int a;
    int b;
    swap(a, b);
}

在上面代码例子中,x与y的改变,不会影响到a和b的值。
然而在下面代码例子中,p和q 指向了main 函数中的a与b,故而在 swap 函数内部实际上访问到了a 和b所处的内存空间,间接的修改了 a 和b,但这并不意味着形参改变了实参,因为形参是 px与 py,实参是 a 的地址,b 的地址,很明显,a 的地址没有改变,b 的地址也没有改变。

void swap(int*px, int*py)
{
    int tmp = *px;
    *px = *py;
    *py = tmp;
}

int main()
{
    int a;
    int b;
    swap(&a, &b);
}

四、指针的运算

        指针是可以有数学运算的但是指针的运算和它的类型息息相关。指针能够支持一些算数运算,不过含义和普通的数据的算数运算非常不同。首先指针只能进行加法和减法运算,比如+,-,++,--,+=,-=这些。并且只有两种形式:
1.指针加或减整数
2.指针减指针,而不能指针加指针


比如如下代码示例:

int a = 100;
int *pl = &a;
pl + 1; //不是将地址+1,而是将地址加到下一个 int 型的位置, pl + 1 等同于(int)&a + 4
//p1++的话地址值会自增 4;
short a = 20;
short *p2 = &a;
p2 + 1; //将地址加到下一个 short 型的位置,p2++的话地址值会自增 2;

4.1 指针加或减整数

        指针加或者减一个整数的话,得到的数据类型还是一个指针。得到的数据值是+(-)整数*sizeof(数据类型)。比如:
        int*p=0;
然后p+5 得到的数据类型还是整型指针?得到的数据0+5*4就是20。

继续看下面的代码示例:

#include <stdio.h>

int main() {
    //下面是一个结构体数据结构,后面补充知识点
    struct TEST
    {
        int a;
        int b;
        int c;
        int d;
        int e;
        int f;
    };

    int Num = 0;
    int* pl = &Num;
    pl = (int*)1;
    printf("%d\n", pl);//1
    printf("%d\n", pl + 1);//5
    printf("%d\n", pl + 2);//9
    printf("%d\n", pl + 5);//21
    double* p2 = NULL;
    p2 = (double*)1;

    //printf("%d", *p2);//运行会报错,因为这个地址不能访问,但是不耽误编译。
    printf("%d\n", p2);//1
    printf("%d\n", p2 + 1);//9
    printf("%d\n", p2 + 2);//17
    printf("%d\n", p2 + 5);//41
    TEST * p3 = NULL;

    p3 = (TEST*)1;//这个结构体是 24 个字节
    printf("%d\n", p3);//1
    printf("%d\n", p3 + 1);//25
    printf("%d\n", p3 + 2);//49
    printf("%d\n", p3 + 5);//121

    return 0;
}

4.2 指针减指针

指针减指针得到的是这两个地址间能够存放多少个这种类型的数据。例如下面代码:

double* p4=(double*)10;
double* p5 = (double*)30;
printf("%d", p5 - p4);

上面得到的结果是 2。

五、指针与一维数组

        为何指针的运算的特性是这样的?这主要是为了数据访问的便利性,这和数组也有一段渊源:
一维数组名其实是地址,并且一维数组名这个地址,它也是有类型的,就是一级指针类型。

例如代码示例:

int Array[5] = (2, 4, 6, 8, 10);
int *p = Array;
printf("%d", p[0]); //和打印Array[0]一样2
printf("%d", p[1]); //和打印Array[1]一样4
printf("%d", p[2]); //和打印Array[2]一样6
printf("%d", p[3]); //和打印Array[3]一样8
printf("%d", p[4]); //和打印Array[4]一样10

//更令人惊奇的是:
printf("%d", *(Array+0)); //和打印*(p+0)一样
printf("%d", *(Array+1)); //和打印*(p+1)一样
printf("%d", *(Array+2)); //和打印*(p+2)一样
printf("%d", *(Array+3)); //和打印*(p+3)一样
printf("%d",*(Array+4)); //和打印*(p+4)一样

        这可能很令人诧异,但请大家牢记这种用法,后面我们会更深入的剖析指针和数组的关系,还有指针各种各样的特性。指针只是一个变量,这个变量是用来存储地址的,指针的复杂性在于它具有多种多样的类型。

数组名就是数组的起始地址,也就是第一个元素的地址。数组名是个常量指针。
例如:
int a[] = {1,2,3,4, 5);
int* p = a;// 这里数组名 a,可以看成整型指针。


p是第一个元素1的地址,p+1就是第一个元素2的地址,以此类推。

类型的变化规律:
1)对变量取地址,得到的是地址,类型为一级指针类型
2)数组名,也是一级指针类型
3)对一级指针解引用,得到的是相应的数据类型
4)对一级指针类型使用下标运算符,得到的是相应数据类型


例如:

int a[] ={1,2,3,4,5};
int* p=a;

这里数组名a,可以看成整型指针类型。
a+2,&a[2],p+2,&p[2]表示同一个一级指针类型的同一地址,
*(a+2)、*&a[2]、*(p+2)、 *&p[2]都表示int 型,数据是a[2]。

使用示例如下:

int main()
{
    int a[5] = {1, 2, 3, 4, 5};
    int i;
    int* p;
    for (i = 0; i < 5; i++)
    {
        printf("a[%d]=%d, *(a+%d)=%d\n", i, a[i], i, *(a + i));
        printf("&a[%d]=%p, a+%d=%p\n", i, &a[i], i, a + i);
    }
    for (p = a; p < a +5; p++)
    {
        printf("address:%p,value:%d\n", p, *p);
    }
}

六、指针与二维数组

二维数组名也是一个指针类型,叫做一维数组指针类型
我们自己定义一维数组指针:类型名 (*变量名)[数组长度]
注意:
对于数组指针,应该给出所指向数组的长度
例如: int (*p)[10];char (*p)[10];
使用示例:

int a[3][4];
int (*p)[4];
p = a;

这里的a或p的类型是 int (*)[4];

类型变化规律:
1) 对一维数组指针解引用会降维,变成 1级指针。
2) 对一维数组指针使用下标运算符也会降维,变成1级指针。
3) 对于一维数组指针+1,会得到下一排起始地址,类型还是数组指针
4) 对1一维数组名取地址,会变为二维数组指针,数值不变。

例如:
对于inta[3][4];
a[i]和*(a+i)等价,都是第 i 行第0列元素的地址,那么 a[i]+j、*(a+i)+i、&a[0][0]+4*i+j都是第 i 行第j列元素的地址。对于 int a[3];
&a,它的类型为 int (*)[3]。即数组指针类型。

使用代码示例如下:

#include <stdio.h>
int main(void)
{
    int a[4][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9},
        {10, 11, 12} 
    };

    int(*p)[3] = a;
    int i, j;

    for (i = 0, j = 0; j < 3; j++)
    {
        printf("%d\t",*(*p + j));
    }
    putchar('\n');

    for (i = 1, j = 0; j < 3; j++)
    {
        printf("%d\t", * (p[i] + j));
    }
    putchar('\n');

    for (i = 2, j = 0; j < 3; j++)
    {
        printf("%d\t", (*(p + i))[j]);
    }
    putchar('\n');

    for (i = 3, j = 0; j < 3; j++)
    {
        printf("%d\t", *(&p[0][0] + i * 3 + j));
    }
    putchar('\n');

    return 0;
}

七、指针与多维数组

        多维数组指的是一维以上的数组,其数组名为多维数组指针类型,数组指针也自然有多维数组指针。
例如:

int Array[2][3][4][5] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
int (*p)[3][4][5] = Array;

转换规律:
1)对多维数组指针解引用会降维,变成降一维的数组指针,直到降成一级指针。
2)对数组指针使用下标运算符也会降维,直到降维成一级指针。
3)对于数组指针+1,会得到下一个数组的起始地址,类型不变。
4)对多维数组名取地址,会变为更高维度的数组指针,数值不变。

八、指针数组和数组指针

8.1 指针数组

指针数组就是其元素为指针的数组

每一个元素都是指针变量
说明指针数组的语法格式为:
数据类型 *指针数组名[常量表达式]

示例代码:

#include <stdio.h>
int main(void)
{
    //demo1
    char* color1[] = {"RED", "GREEN", "BLUE"};
    int i1;
    for (i1 = 0; i1 < 3; i1++)
        puts(color1[i1]);

    //demo2
    char color2[][6] = {"RED", "GREEN", "BLUE"};
    char(*pcolor1)[6] = color2;
    int i2;
    for (i2 = 0; i2 < 3; i2++)
        puts(pcolor1[i2]);

    //demo3
    char color3[][6] = {"RED", "GREEN", "BLUE"};
    char* pcolor2[3];
    int i3; 
    for (i3 = 0; i3 < 3; i3++)
        pcolor2[i3] = color3[i3]; 
    for (i3 = 0; i3 < 3; i3++)
        puts(pcolor2[i3]);

    return 0;
}

指针数组内存储的数据:之前我们演示的示例,指针数组中的指针都是一级指针,其实可以是任意指针类型,例如:

int *p[5]; //一级指针数组
int **p[5]; //二级指针数组
int (*p2[5])[10]; //数组指针数组

8.2 指针数组和二维数组的区别

指针数组存储的是指针,二维数组存储的是数据

如下图示例:

8.3 指针数组和数组指针的区别

在C语言中,指针数组和数组指针是两个不同的概念,它们在内存中的存储方式以及访问方式有所不同。

1)指针数组:指针数组是一个数组,其每个元素都是一个指针。即,这个数组存储的是指针,这些指针指向了内存中的某个位置。

例如,以下是一个指针数组的声明:

int *ptr_array[5];

在这个例子中,ptr_array是一个包含5个整型指针的数组。每个指针都指向一个整型变量的位置。

2)数组指针:数组指针是一个指针,它指向一个数组。即,这个指针指向的是一个连续的内存区域,这个区域存储了一个数组。

例如,以下是一个数组指针的声明:

int (*arr_ptr)[5];

在这个例子中,arr_ptr是一个指向包含5个整型的数组的指针。这个指针指向的是一个连续的内存区域,这个区域存储了一个数组。

两者的主要区别在于:

1. 指针数组的每个元素都是一个指针,这些指针可以指向不同的内存位置。而数组指针指向的是一个数组,即一个连续的内存区域。
2. 通过指针数组访问内存中的数据需要先访问指针,再通过指针访问数据。而通过数组指针访问内存中的数据可以直接访问数据,因为数组指针指向的就是数据所在的内存区域。

数组指针的代码示例:

#include <stdio.h>

int main() {
    int arr[] = { 1, 2, 3, 4, 5 };
    int* ptr = arr; // 数组指针,指向数组的首元素

    // 使用数组指针遍历数组
    for (int i = 0; i < sizeof(arr) / sizeof(int); i++) {
        printf("%d ", *(ptr + i));
    }

    // 使用数组指针访问特定元素
    printf("\nThe third element is: %d\n", *(ptr + 2));

    // 使用数组指针修改特定元素
    *(ptr + 2) = 10;
    printf("After modification, the third element is: %d\n", *(ptr + 2));

    return 0;
}

  • 11
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

攻城狮7号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值