系列文章目录
C语言小白急救 表达式求值(两千字教程)
C语言小白急救 操作符详解(8千字保姆级教程)
C语言小白急救 扫雷游戏(万字保姆级教程)
C语言小白急救 使用C语言编写‘三子棋‘
文章目录
- 系列文章目录
- [C语言小白急救 表达式求值(两千字教程)](https://editor.csdn.net/md/?articleId=132312494) [C语言小白急救 操作符详解(8千字保姆级教程)](https://editor.csdn.net/md/?articleId=132269381) [C语言小白急救 扫雷游戏(万字保姆级教程)](https://editor.csdn.net/md/?articleId=132259410) [C语言小白急救 使用C语言编写‘三子棋‘](https://mp.csdn.net/mp_blog/creation/editor/132240688)
- 前言
- 一、指针介绍
- 1.指针介绍
- 2.指针变量
- 二、 指针和指针类型
- 1.指针的解引用
- 2.指针与整数
- 三、野指针
- 1. 野指针成因
- 2.如何规避野指针
- 四、指针和数组
- 五、指针的运算
- 1.指针+- 整数
- 2.指针-指针
- 3.指针的关系运算
- 六、二级指针
- 七、指针数组
- 总结
前言
一、指针介绍
1.指针介绍
(1)指针是内存中一个最小内存单元(字节)的一个编号,也就是地址(我们平常所用的二进制位就是其中的一种表现形式)
(2)我们平常口语中所说的指针一般指的是指针变量,是用于存放地址的
总结:指针就是地址,我们平常口语中所说的指针其实是指针变量
2.指针变量
我们可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个变量就是指针变量
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
总结:
指针变量是用来存放地址的,地址是唯一标示一个内存单元的。
指针的大小在32位平台是4个字节,在64位平台是8个字节
二、 指针和指针类型
我们都知道,变量有不同的类型,整形,浮点型等。那指针有没有类型呢?
准确的说:有的。
那么,为什么要有指针类型呢,它有什么作用?
1.指针的解引用
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如:
int a = 0x11223344;//将a的地址设置为11223344int* m = &a;//设置一个指针变量来存储a的地址*m = 0;//解应用m,对m中的地址进行操作,使地址变为0
当Int* 改为 char* 时
int a = 0x11223344;//将a的地址设置为11223344char* m = (char*)&a;//设置一个指针变量来存储a的地址//因为a是int型的若想要用char作为指针变量,需要转换*m = 0;//解应用m,对m中的地址进行操作,使地址变为0
所以说,不同的指针类型,对指针解引用的时候的权限也不同
2.指针与整数
指针的类型决定了指针向前或者向后走一步有多大(距离)。
比如:
int main()
{int n = 10;char *pc = (char*)&n;int *pi = &n;printf("%p\n", &n);printf("%p\n", pc);printf("%p\n", pc+1);printf("%p\n", pi);printf("%p\n", pi+1);return 0;
}
从结果(16进制)可以看出,char* 类型的,向后移动了1个字节;int* 类型的向后移动了4个字节。这样的区别在于我们对元素进行比如遍历之类的操作中十分有用
比如:
int arr[] = { 1,2,3,4 };int* m = arr;char* n = (char*)arr;int i = 0;for (i=0;i<4;i++){printf("%p ",m + i);}printf("\n");for (i = 0; i < 4; i++){printf("%p ", n + i);}
除打印首元素地址外,一共执行了3次,char* 类型的,向后移动了3个字节,离到第二个元素的位置还差一个字节;int* 类型的向后移动了8个字节,已经到了第三个元素的位置;如果是对元素进行遍历,那么肯定int* 类型更快。
三、野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
1. 野指针成因
(1)指针未初始化
int main()
{ int *p;//局部变量指针未初始化,默认为随机值*p = 20;return 0;
}
这种情况编译器会报错,无法执行
(2) 指针越界访问
int arr[10] = { 0 };int* p = arr;for (int i=0;i<=10;i++)//循环了11次,但数组中只有十个数{*p = 1;p++;}
这种情况下属于越界访问,编译器会报错,无法执行
(3) 指针指向的空间释放
int* tset()
{int a = 10;return &a;
}int main()
{int* p = tset();printf("%d\n",*p);return 0;
}
函数 test() 在作用后以及将空间释放,当时你还知道他的地址,这就属于非法访问,也是野指针的一种。比如说:一个陌生人不知道从哪里知道了你的电话号码,整天打电话给你。
2.如何规避野指针
- 指针初始化
- 小心指针越界
- 指针指向空间释放,及时置NULL
- 避免返回局部变量的地址
- 指针使用之前检查有效性(指针没有明确指向是设为空NULL)
四、指针和数组
大部分情况下数组名表示的是数组首元素的地址
2个例外:
(1)sizeof(数组名),数组名单独放在sizeof内部,此时数组名表示整个数组,计算的是整个数组的大小,单位是字节
(2)&数组名,此时数组名表示整个数组,取出的是整个数组的地址,数组的地址和数组首元素地址,值是一样的,但是类型和意义不一样
例1:数组名表示的是数组首元素的地址
int arr[] = {1,2,3,4,5 };
printf("%p\n",arr);
printf("%p\n",&arr[0]);
打印出的地址一样,所以数组名是数组首元素的地址。
例2:sizeof(数组名)
int arr[] = { 1,2,3,4,5 };printf("%zd\n",sizeof(arr));
例3:&数组名
int arr[] = { 1,2,3,4,5 };printf("%p\n", &arr);printf("%p\n", &arr+1);
五、指针的运算
1.指针± 整数
指针±整数是跳过多少个元素
int arr[] = { 1,2,3,4,5 };int* p = arr;//数组名的是数组首元素的地址//arr+i == p+i//*(arr+i) == *(p+i) == arr[i]//*(arr+i) == arr[i]//*(i+arr) == i[arr]for (int i=0;i<5;i++){printf("%d ",*(p+i));//p+i 是数组中下标为i的值//p+i 是跳过了i*sizeof(int)个字节}
2.指针-指针
指针相减的前提:指向同一块区域,指针类型相同
指针相减的结果的绝对值是中间的元素个数
int arr[10] = { 0 };//指针相减的前提:指向同一块区域,指针类型相同printf("%d",&arr[9]-&arr[0]);//指针相减的结果是中间的元素个数
3.指针的关系运算
size_t My_strelen(char* str)
{char* a = str;//设置一个指针来存储首地址while (*str != '\0')//这个地址存储的值不等于'\0'{str++;//指针+1}return str-a;
}int main()
{char arr[] = "abcdef";size_t count=My_strelen(arr);printf("%zd ",count);return 0;
}
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
简单点讲就是尽量从前往后比较,不要从后往前
六、二级指针
二级指针就是用来存储一级指针的地址的
int a = 10;int* p = &a;//p是指针变量,一级指针变量int** pp = &p;//pp是指针变量,二级指针变量//二级指针变量前面有两颗* 是指这个二级指针变量指向一个指针变量的//二级指针需要解引用两次
七、指针数组
指针数组是数组,里面存放的是指针,根据数组内存放的不同的指针类型,可以分为不同类型的指针数组;如int * char* 等
应用:
int arr1[] = { 1,2,3,4 };int arr2[] = { 4,5,6,7 };int arr3[] = { 7,8,9,10 };int* arr[] = {arr1,arr2,arr3};for (int i = 0; i < 3;i++)//访问arr1 arr2 arr3{for (int j = 0; j < 4;j++)//访问三数组中的元素{printf("%d ",arr[i][j]);}printf("\n");}