1、指针是什么

指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)

一个单元是1个字节大小

  • 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
  • 那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。

指针的大小在32位平台是4个字节,在64位平台是8个字节

2、指针和指针类型

#include <stdio.h>
int main()
{
  printf("%d\n", sizeof(char*));
  printf("%d\n", sizeof(short*));
  printf("%d\n", sizeof(int*));
  printf("%d\n", sizeof(double*));
  return 0;
}
//结果全是4(32位平台),打印的是指针类型
//2个16进制位是1个字节,1个16进制位是4个2进制位,8个2进制位是1个字节
#include <stdio.h>
int main()
{
  int a = 0x11223344;//占4个字节
  int* pa = &a;
  char* pc = &a;
  printf("%p\n", pa);
  printf("%p\n", pc);
  return 0;
}
//4个字节两个都能很好存放,但存放不同的类型会报警告
#include <stdio.h>
int main()
{
  int a = 0x11223344;//占4个字节
  int* pa = &a;
  *pa = 0;//&a变成了00000000
  char* pc = &a;
  *pc = 0;//&a变成了11223300
  return 0;
}

指针的意义:

(1)指针类型决定了指针进行解引用操作的时候,能够访问空间的大小。

  • int* p: *p 能够1访问4个字节
  • chat* p: *p 能够1访问1个字节
  • double* p: *p 能够1访问8个字节

指针 +- 整数

#include <stdio.h>
int main()
{
  int a = 0x11223344;
  int* pa = &a;
  char* pc = &a;
  printf("%p\n", pa);    //000000A26E6FF924
  printf("%p\n", pa + 1);//000000A26E6FF928
  printf("%p\n", pc);    //000000A26E6FF924
  printf("%p\n", pc + 1);//000000A26E6FF925
  return 0;
}

(2)指针类型决定了:指针走一步走多远(指针的步长,单位:字节)

  • int* p: p + 1 -> 4 (跳过4个字节)
  • chat* p: p + 1 -> 1
  • double* p: p + 1 -> 8
#include <stdio.h>
int main()
{
  int a = 0x11223344;
  int* pa = &a;
  char* pc = &a;
  printf("%p\n", pa);    //000000A26E6FF924
  printf("%p\n", pa + 1);//000000A26E6FF928,加4
  printf("%p\n", pc);    //000000A26E6FF924
  printf("%p\n", pc + 1);//000000A26E6FF925,加1
  return 0;
}
#include <stdio.h>
int main()
{
  int arr[10] = {0};
  //int* p = arr;//数组名-首元素的地址
  //这样最终能将数组中所有元素全改为1
  char* p = arr;
  //不能全改,因为它一次只走1个字节,相当于能改2.5个数组元素
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    *(p + 1) = 1;
  }
  return 0;
}

3、野指针

概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

3.1 野指针成因

3.1.1 指针未初始化

#include <stdio.h>
int main()
{
  int a;//局部变量不初始化,默认是随机值
  int *p;//局部的指针变量未初始化,默认为随机值
  *p = 20;//非法访问
  return 0;
}

3.1.2 指针越界访问

//指针指向的范围超出数组arr的范围
#include <stdio.h>
int main()
{
  int arr[10] = {0};
  int* p = arr;
  int i = 0;
  for (i = 0; i < 12; i++)
  {
    *p = i;
    p++;
    //以上两句可以合并为一句 *p++ = i;
  }
  return 0;
}

3.1.3 指针指向的空间释放

#include <stdio.h>

int* test()
{
  int a = 10;//a是局部变量出了test函数就销毁没有了,空间释放
  return &a;
}
int main()
{
  int* p = test();//把a的地址接收了,但此时a的空间以及还给系统了,所以没有内容
  printf("%d\n", *p);//非法访问,这块空间已经不属于我们。但仍能打印出来因为空间还没有被新征用但有警告
  *p = 20;//再放20时它指向了一块已经释放的内存块
  return 0;
}

3.2 如何规避野指针

  1. 指针初始化
//NULL
#include <stdio.h>
int main()
{
  int a = 10;
  int* pa = &a;//初始化
  int* p = NULL;//当你不知道该给指针变量初始化什么,那就给他一个空指针
  //NULL - 用来初始化指针的,给指针赋值
  return 0;
}
  1. 小心指针越界
  2. 指针指向空间释放即使置NULL(立即使其设置为空)
#include <stdio.h>
int main()
{
  int a = 10;
  int* pa = &a;//初始化
  *pa = 20;
  pa = NULL;//当不想让一个指针指向其它地方,或者它指向的地方已经还给操作系统,置空
  return 0;
}
  1. 避免返回局部变量的地址
  2. 指针使用之前检查有效性

当指针为空则不能用,不为空则说明有意义可以用

#include <stdio.h>
int main()
{
  int a = 10;
  int* pa = &a;//初始化
  *pa = 20;
  pa = NULL;
  if (pa != NULL)
  {
    *pa = 5;
  }
  return 0;
}

4、指针运算

  • 指针 +- 整数
  • 指针 - 指针
  • 指针的关系运算(指针比较大小)

4.1 指针+-整数

//打印数组元素
#include <stdio.h>
int main()
{
  int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  int i = 0;
  int sz = sizeof(arr) / sizeof(arr[0]);
  int* p = arr;
  /*for (i = 0; i < sz; i++)
  {
    printf("%d ", *p);
    p = p + 1;
    //p++也可以
  }*/
  
  for (i = 0; i < 5; i++)
  {
    printf("%d ", *p);
    //p += 2;//1 3 5 7 9
    //若int* p = &arr[9];p -= 2;//10 8 6 4 2
  }
  return 0;
}
#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
     *vp++ = 0;//先使用后++
}

4.2 指针-指针

//大指针-小指针,得指针间元素个数
//两个指针要指同一块内存空间
#include <stdio.h>
int main()
{
  char ch[5] = {0};
  int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  printf("%d\n", &arr[9] - &arr[0]);
  //指针减去指针,得到指针之间的元素个数//9
  printf("%d\n", &arr[0] - &arr[9]);//-9
  //printf("%d\n", &arr[9] - &ch[0]);//结果不可预知
  return 0;
}
//指针 - 指针求字符串长度
#include <stdio.h>
int my_strlen(char* str)
{
  char* start = str;
  char* end = str;
  while(*end != '\0')
    end++;
  return end-start;
}
int main()
{
  char arr[] = "bit";
  int len = my_strlen(arr);
  printf("%d\n", len);
  return 0;
}

4.3 指针的关系运算

#define N_VALUES 5
float values[N_VALUES];
float *vp;
for(vp = &values[N_VALUES]; vp > &values[0];)
{
    *--vp = 0;
}

(不建议这样写)另一种写法:

for(vp = &values[N_VALUES-1]; vp >= &values[0]; vp--)
{
    *vp = 0;
}

实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。

标准规定:

允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

5. 指针_指针类型

和p1进行比较,允许用p2,不允许用p3

5、指针和数组

区分 arr, &arr, &arr[0],见第三节数组最后一部分

数组可以通过指针来很好地访问

#include <stdio.h>
int main()
{
  int arr[10] = {0};
  int* p = arr;
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    printf("%p ===== %p\n", p + i, &arr[i]);
  }
  return 0;
}
#include <stdio.h>
int main()
{
  int arr[10] = {0};
  int* p = arr;
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    *(p + i) = i;//指针形式放入数值
  }
  for (i = 0; i < 10; i++)
  {
    //printf("%d ", arr[i]);//数组形式打印
    printf("%d ", *(p + i));//指针形式打印
  }
  return 0;
}

6、二级指针

#include <stdio.h>
int main()
{
  int a = 10;
  int * pa = &a;
  int* * ppa = &pa;//ppa是二级指针
  //*ppa是pa的内容,即a的地址,**ppa是a的内容,即为10
  printf("%d\n", **ppa);//10
  **ppa = 20;
  printf("%d\n", **ppa);//20
  printf("%d\n", a);//20
  //int** * pppa = &ppa;//pppa是三级指针
  return 0;
}

5. 指针_二级指针_02

7、指针数组

数组指针 - 指针

指针数组 - 数组 - 存放指针的数组

#include <stdio.h>
int main()
{
  int a = 10;
  int b = 20;
  int c = 30;
  
  /*int* pa = &a;
  int* pb = &b;
  int* pc = &c;*/
  //整型数组 - 存放整型
  //字符数组 - 存放字符
  //指针数组 - 存放指针
  //int arr[10];
  
  int* arr2[3] = {&a, &b, &c};//指针数组
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    printf("%d ", *(arr2[i]));
  }
  return 0;
}