C语言指针

指针让c语言更像是结构化的底层语言,所有在内存中的数据结构均可用指针来访问

1. 认识内存

1.1. 线性的内存

内存是线性的,内存的线性是物理基础
32位主机最大的寻址能力就是32G
在这里插入图片描述内存是以字节为单位进行编址的,内存中的每个字节对应一个地址,通过地址才能找到每个字节。

1.2. 变量的地址与大小

1.2.1. 变量的地址

变量对应内存中的一段存储空间,该段存储空间占用一定的字节数,可能是1个字节,也可能是4或8个字节,用这段存储空间的第一个字节地址来表示变量的地址,即低位字节的地址.
变量的地址,可以通过&引用运算符取得,在此可以称为取地址运算符

int main(void)
{
  int a;
  int b;
  printf("&a = %p\n", &a); // &a = 0x16b0bf318
  printf("&b = %p\n", &b); // &b = 0x16b0bf314
  return 0;
}
1.2.2. 地址的大小
int main(void)
{
  char a;
  short b;
  int c;
  long d;
  float e;
  double f;
  printf("&a = %p\n", &a); // &a = 0x16d28b31b
  printf("&b = %p\n", &b); // &b = 0x16d28b318
  printf("&c = %p\n", &c); // &c = 0x16d28b314
  printf("&d = %p\n", &d); // &d = 0x16d28b308
  printf("&e = %p\n", &e); // &e = 0x16d28b304
  printf("&f = %p\n", &f); // &f = 0x16d28b2f8

  // 类型不同,大小相同,均为4字节
  printf("sizeof(&a) = %d\n", sizeof(&a)); // sizeof(&a) = 4
  printf("sizeof(&b) = %d\n", sizeof(&b)); // sizeof(&b) = 4
  printf("sizeof(&c) = %d\n", sizeof(&c)); // sizeof(&c) = 4
  printf("sizeof(&d) = %d\n", sizeof(&d)); // sizeof(&d) = 4
  printf("sizeof(&e) = %d\n", sizeof(&e)); // sizeof(&e) = 4
  printf("sizeof(&f) = %d\n", sizeof(&f)); // sizeof(&f) = 4
  return 0;
}

1.3. 间接访问内存

除了变量,还可以通过指针的方式间接访问内存,*解引用运算符,在此可以称为取内容运算符

int main()
{
  char a = 1;
  short b = 2;
  int c = 3;
  long d = 4;
  float e = 5.0;
  double f = 6.5;

  printf("&a = %p\n", &a); // &a = 0x16d2e731b
  printf("&b = %p\n", &b); // &b = 0x16d2e7318
  printf("&c = %p\n", &c); // &c = 0x16d2e7314
  printf("&d = %p\n", &d); // &d = 0x16d2e7308
  printf("&e = %p\n", &e); // &e = 0x16d2e7304
  printf("&f = %p\n", &f); // &f = 0x16d2e72f8

  printf("a = %d\n", *(&a)); // a = 1
  printf("b = %d\n", *(&b)); // b = 2
  printf("c = %d\n", *(&c)); // c = 3
  printf("d = %d\n", *(&d)); // d = 4
  printf("e = %f\n", *(&e)); // e = 5.000000
  printf("f = %f\n", *(&f)); // f = 6.500000
  return 0;
}

2. 指针常量

2.1. 指针是有类型的地址常量

// 上述输出等价于
printf("a = %d\n", *((char *)0x16d2e731b));
printf("b = %d\n", *((short *)0x16d2e7318));
printf("c = %d\n", *((int *)0x16d2e7314));
printf("d = %d\n", *((long *)0x16d2e7308));
printf("e = %f\n", *((float *)0x16d2e7304));
printf("f = %f\n", *((double *)0x16d2e72f8));

在这里插入图片描述
也就是说,&a进行取地址,取出来的地址是有类型的。
所以,指针其实就是一个有类型的4字节的整形常量
指针的类型,决定了该指针的寻址能力。即从指针所代表的地址处的寻址范围。

3. 指针变量

一个指针是一个有类型地址,是一个有类型的常量
指针变量:用以存放指针的量
一个指针变量可以被赋予不同的指针值,可以通过指针变量改变指向和间接操作
指针既可以指指针常量,也可以指指针变量,但通常说的指针,是指针变量

3.1. 指向/被指向/更改指向

通常说的谁指向了谁,就是一种描述指针的指向关系,指向谁,就是保存了谁的地址。
在这里插入图片描述在这里插入图片描述

3.3. NULL

3.3.1. 野指针

野指针:一个指针变量,如果指向一段无效的空间,那么该指针就称为野指针
常见的野指针:一种是未初始化的指针;一种是指向一种已经被释放的空间
对野指针的写入成功,造成的后果是不可估量的,对野指针的读写操作,是危险且无意义的

3.3.2. NULL指针

NULL俗称空指针,等价于(void *)0。
C标准中定义的NULL指针:define NULL ((void *)0)
因此,常用NULL来给临时不需要初始化的指针变量来初始化,或对已经被释放指向内存空间的指针赋值。
NULL用于作标志位来使用

3.3.3. void本质

void即无类型,可以赋给任意类型的指针,本质代表内存的最小单位,32位机上等同于char

4. 指针运算

指针运算本质是指针中存储地址的运算。

4.1. 赋值运算

注意:不兼容的类型赋值会发生类型丢失。为了避免隐式转化可能出现的错误,最好用强制转化显示的区别。

4.2. 算术运算+,-,++,—

指针的算术运算,是数值加类型运算,将指针加上或减去某个整数值(以n*sizeof(T))为单位进行操作的。

int main()
{
  int a = 0x0001;
  printf("a = %#x a+1 = %#x\n", a, a + 1); // a = 0x1 a+1 = 0x2
  int *p = (int *)0x0001;
  printf("p = %#x p+1 = %#x\n", p, p + 1); // p = 0x1 p+1 = 0x5
  int aa = 0x0010;
  printf("aa = %#x aa-1 = %#x\n", aa, aa - 1); // aa = 0x10 aa-1 = 0xf
  int *q = (int *)0x0010;
  printf("q = %#x q-1 = %#x\n", q, q - 1); // q = 0x10 q-1 = 0xc
  return 0;
}

注意:只有当指针指向一串连续的存储单元时,指针的移动才有意义,才能将一个指针变量与一个整数n做加减运算。

4.3. 关系运算==,>,<

int main()
{
  int *ptrnum1, *ptrnum2;
  int value = 1;
  ptrnum1 = &value;
  value += 10;
  ptrnum2 = &value;
  if (ptrnum1 == ptrnum2)
    printf("\n 两个指针指向同一个地址\n"); // 打印
  else
    printf("\n 两个指针指向不同的地址\n");
  return 0;
}

总结:

  • 指针的运算只能发生在同类型或整形之间,否则会报错或是警告
  • 指针的运算,除了数值以外,还有类型在里面

5. 数组遇上指针

5.1. 一维数组的访问方式

5.1.1. 传统方式(下标/偏移法)

数组名是数组的唯一标识符,数组名代表数组首元素的地址。可以用下标的方式对数组进行访问。
除此之外,还可以用本质方法进行访问。

int main()
{
  int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
  for (int i = 0; i < 10; i++)
  {
    printf("array[%d] = %d\n", i, array[i]);
  }
  printf("+++++++\n");
  for (int i = 0; i < 10; i++)
  {
    printf("array[%d] = %d\n", i, *(array + i));
  }
  // 输出
  // array[0] = 1
  // array[1] = 2
  // array[2] = 3
  // array[3] = 4
  // array[4] = 5
  // array[5] = 6
  // array[6] = 7
  // array[7] = 8
  // array[8] = 9
  // array[9] = 0
  return 0;
}
5.1.2. 数组名是常量指针

数组名是常量,才可以唯一的确定数组元素的起始地址

5.1.3. 一维数组名跟一级指针的关系

数组除了可以用下标法和本质法访问以外,还可以用指针法去访问。
能用数组名解决的问题,都可以用指针来解决,而能用指针来解决问题的,并不一定能用数组名来解决。

5.1.4. 指针访问方式
int main()
{
  int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
  printf("array = %p\n", array);         // array = 0x16d0832f0
  printf("&array[0] = %p\n", &array[0]); // &array[0] = 0x16d0832f0
  // array代表首元素地址,array[0]就是数组的首元素,类型是int类型,其地址类型就是int *,故可将一维数组跟一级指针联系在一起
  int *p = array;
  for (int i = 0; i < 10; i++)
  {
    printf("array[%d] = %d\n", i, /* p[i] */ *(p + i));
  }

  /* printf("*********\n");
  for (int i = 0; i < 10; i++)
  {
    printf("array[%d] = %d\n", i, *p);
    p++;
  } */

  /* printf("*********\n");
  for (int i = 0; i < 10; i++)
  {
    printf("array[%d] = %d\n", i, *(p++));
  } */
  return 0;
}

5.1.5. 小结

  1. 数组名是一个常量,不允许重新赋值
  2. 指针变量是一个变量,可以重新赋值
  3. p+i和a+i均表示数组元素,a[i]的首地址,均指向a[i]
  4. *(p+i)和*(a+i)均表示p+i和a+i所指对象的内容a[i]
  5. *p++:等价于*(p++)。其作用:先得到*p,再使p=p+1
  6. (*p)++:表示将p所指向的变量(元素)的值加1。即等价于a[i]++
  7. 指向数组元素的指针也可以表示成数组的形式,即允许指针变量带下标,如*(p+i)可以表示成p[i]

5.2. 二维数组的访问方式

5.2.1. 下标法

数组元素的表示方法是:数组名称[行][列]

5.2.2. 本质偏移法

从a到a[0][0]都经历了什么
在这里插入图片描述
在这里插入图片描述

int main()
{
  int arr[3][4] = {1, 2, 3, 4, 10, 20, 30, 40, 100, 200, 300, 400};
  for (int i = 2; i >= 0; i--)
  {
    for (int j = 3; j >= 0; j--)
    {
      printf("%#x\n", &arr[i][j]);
    }
  }

  printf("&arr[0] = %#p &arr[0] + 1 = %#x &arr[0] + 2 = %#x\n", &arr[0], &arr[0] + 1, &arr[0] + 2); // &arr[0] = 0x16bb4b2e8 &arr[0] + 1 = 0x6bb4b2f8 &arr[0] + 2 = 0x6bb4b308
  printf("arr = %#p arr[0] + 1 = %#x arr[0] + 2 = %#x\n", arr[0], arr[0] + 1, arr[0] + 2);          // arr = 0x16bb4b2e8 arr[0] + 1 = 0x6bb4b2ec arr[0] + 2 = 0x6bb4b2f0
  printf("arr[0] = %#p arr[0] + 1 = %#x arr[0] + 2 = %#x\n", arr[0], arr[0] + 1, arr[0] + 2);       // arr[0] = 0x16b3432e8 arr[0] + 1 = 0x6b3432ec arr[0] + 2 = 0x6b3432f0
  printf("%d\n", arr[1][1]);                                                                        // 20
  printf("%d\n", *(*(arr + 1) + 1));                                                                // 20

  return 0;
}

总结:
arr[i][j]
(arr+i)第i行的地址
*(arr+i)第i行第0列的地址
*(arr+i)+j第i行第j列的地址
*(*(arr+i)+j)第i行第j列的内容

  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Qi妙代码

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

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

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

打赏作者

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

抵扣说明:

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

余额充值