一、指针的基本知识

(1)指针是什么

  • 我们把内存划分为一个个的内存单元,这个内存单元的大小是1个字节
  • 每个字节都给一个唯一的编号,这个编号我们称为地址,地址再C语言中也叫指针,指针也就是内存中最小内存的编号【编号 == 地址 == 指针】
  • 指针就是地址,口语中说的指针通常指的是指针变量

(2)指针的创建

int main(){int a = 0x11223344; //a是整形,占用4个字节的内存空间,每个字节都有对应的地址int* p = &a; //&a得到的是a的地址(指针),其实得到的是a所占内存中4个字节中第一个字节的地址,p是指针变量 p = 10; //10会被当成地址return 0;}

(3)指针的大小

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

二、指针类型

(1)指针 + – 整数

  • 指针类型决定了指针进行+1、-1的时候,一步走多远

(2)指针的解引用

  • 指针类型决定了在解引用指针的时候能访问几个字节
  • 比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节

三、野指针

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

1、野指针成因

(1)指针未初始化

int main(){int* p; //局部变量指针未初始化,默认为随机值*p = 20;return 0;}

(2)指针越界访问

int main(){int arr[10] = { 0 };int* p = arr;int i = 0;for (i = 0; i <= 11; i++) //当指针指向的范围超出数组arr的范围时,p就是野指针{*(p++) = i;}return 0;}

(3)指针指向的空间释放

int* test(){int a = 10;return &a;}int main(){int* p = test();*p = 100; //我们想通过*p来改a的值,但是当出了test()函数,a就被销毁了,所以这里的p是野指针return 0;}

2、如何规避野指针

  • 指针要初始化,不知道初始化什么的时候,就初始化为空指针
  • 小心指针越界
  • 指针指向空间释放,及时置为空指针NULL
  • 避免返回局部变量的地址
  • 指针使用之前检查有效性(if 判断 或 assert 断言)
#include int main(){//if判断int* p = NULL; //指针初始化int a = 10;p = &a;if (p != NULL) //检查指针的有效性{*p = 20;}return 0;}
#include #include int main(){//assert断言int* p = NULL; //指针初始化int a = 10;p = &a;assert(p); //检查指针的有效性*p = 20;return 0;}

四、指针运算

(1)指针 + – 整数

  • 用指针 + – 整数来模拟实现 strlen 函数
#define _CRT_SECURE_NO_WARNINGS 1#include int my_strlen(char* str) //模拟实现 strlen 函数{int count = 0;while (*str != '\0'){count++;str++; //str = str + 1;【指针 + - 整数】}return count;}int main(){int len = my_strlen("abcdef");printf("%d\n", len); //6return 0;}

注:
这里讲一下 (*p++) 的运算顺序:由于 ++ 的优先级高于 *,所以先计算 p++,但是此处 ++ 是后置 ++,所以 p 的值先不变,然后表达式执行 *p,最后再执行 p+1 。运用(*p++)可优化上述模拟 strlen 函数的代码:

int my_strlen(char* str){int count = 0;while (*str++){count++;}return count;}

(2)指针 – 指针(地址 – 地址)

在求数组元素个数时,可以用 sizeof(arr)/sizeof(arr[0]),也可以用 指针 – 指针

  • 两个指针指向同一块空间,指针的类型是一致的。
  • 指针 – 指针 得到的是两个指针之间的元素个数
  • 指针 + 整数 = 指针;指针 – 指针 = 整数
int main(){int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int n = &arr[9] - &arr[0]; //大地址 - 小地址int m = &arr[0] - &arr[9]; //小地址 - 大地址printf("%d %d", n, m); //9 -9return 0;}

  • 用 指针 – 指针 实现 strlen 函数求字符串长度
int my_strlen(char* str){char* start = str; //首地址while (*++str){;}return str - start;}int main(){int len = my_strlen("abcdef");printf("%d\n", len); //6return 0;}

(3)指针的关系运算

int main(){int arr[5];int* p;for (p = &arr[5]; p > &arr[0];){*--p = 0;}return 0;}

//化简后int main(){int arr[5];int* p;for (p = &arr[4]; p >= &arr[0]; p--){*p = 0;}return 0;}

注:❗❗❗

  • 化简后的代码实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
  • 标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

五、指针和数组

  • 指针就是指针,不是数组;数组就是数组,也不是指针
  • 指针的大小:4/8字节
  • 数组的大小:取决于数组元素个数和每个元素的类型

1、arr = &arr[0]

  • 数组名代表的是数组首元素的地址

2、p + i = &arr[i]

  • 若 p 为数组首地址,p+i 其实计算的是数组 arr 下标为 i 的地址。
#define _CRT_SECURE_NO_WARNINGS 1#include int main(){int arr[10] = { 0 };int* p = arr; //&arr[0]//存放for (int i = 0; i < 10; i++){*p = i + 1;p++;}//打印p = arr;for (int i = 0; i < 10; i++){printf("%d ", *(p + i)); //1 2 3 4 5 6 7 8 9 10}return 0;}

3、数组访问的等价

(1)一维数组

  • *(arr+i) = *(p+i) = arr[i]
  • arr[i] = *(arr+i) = *(i+arr) = i[arr]
  • 在编译时, i[arr] 会转化为 *(i+arr);arr[i] 会转化为 *(arr+i)
int main(){int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = arr;//*(arr+i) = *(p+i) = arr[i]//arr[i] = *(arr+i) = *(i+arr) = i[arr]for (int i = 0; i < 10; i++){printf("%p = %p\n", &arr[i], p+i);}return 0;}

(2)二维数组

  • arr[i][j] = (*(arr + i))[j] = *(*(arr + i) + j)
//访问二维数组时:【三种解引用方法互相等价】int arr[3][5];//arr[i][j]//(*(arr + i))[j]//*(*(arr + i) + j)

六、二级指针

七、指针数组

  • 指针数组:是存放指针的数组
  • 当我们想访问指针数组元素的值时,只需要将指针数组元素的内容进行解引用

(1)一维指针数组

int main(){int a = 10;int b = 20;int c = 30;int* arr[] = { &a, &b, &c };for (int i = 0; i < 3; i++){printf("%d ", *arr[i]); //10 20 30}return 0;}

(2)二维指针数组

  • arr[i] 找到指针数组的元素 arr1 或 arr2 或 arr3,然后再 [j] 访问到数组里面的每个元素,就相当于一个二维数组
int main(){int arr1[4] = { 1,2,3,4 };int arr2[4] = { 5,6,7,8 };int arr3[4] = { 9,10,11,12 };int* arr[3] = { arr1,arr2,arr3 };for (int i = 0; i < 3; i++){for (int j = 0; j < 4; j++){printf("%d ", arr[i][j]);//arr[i]找到指针数组的元素arr1或arr2或arr3//然后再[j]访问到数组里面的每个元素,就相当于一个二维数组}printf("\n");}return 0;}

八、const 修饰指针

  • (1)const 放在 * 的左边, *p 不能改了,也就是 p 指向的内容,不能通过 p 来改变了,但是 p 是可以改变的,p 可以指向其他的变量 【左定值】
  • (2)const 放在 * 的右边,限制的是 p,p 不能改变,但是 p 指向的内容 *p 是可以改变的 【右定向】

(1)左定值

int main(){int n = 100;const int m = 0;//m = 20; //errconst int * p = &m;//*p = 20; //errp = &n; //okreturn 0;}

(2)右定向

int main(){int n = 100;const int m = 0;//m = 20; //errint* const p = &m;*p = 20; //ok//p = &n; //errprintf("%d\n", m); //20return 0;}