目录
一、指针是什么?
- 指针是内存中一个最小单元的编号,也就是地址。
- 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。
要想理解指针,就要先了解内存。
- 内存其实是一块比较大的空间,而为了很好的管理和使用内存,将一大块内存空间划分为多个小的内存单元,而每个内存单元就像我们现实生活中的房间一样。
- 一个基本的内存单元大小是一个字节。
- 生活中像学校的宿舍,每一间宿舍都是有宿舍号的,比如在一楼宿舍号就是101,102,103……。而每个内存单元也是有编号的,这个编号被称为地址。这个地址是以十六进制的数字表示的。
简单说一下内存单元编号的产生
补充
在32位机器下 每个地址表示一个字节,那我们就可以给
2^32 Byte == 2^32 / 1024 KB == 2^32 / 1024 / 1024 MB==2^32 / 1024 / 1024 / 1024 GB == 4GB
4G的空间进行编址。
同样的方法,64位机器,给64根地址线,那能编址多大空间?感兴趣的朋友可以自己算一下。
内存单元的编号就是地址,而地址就是通常所说的指针。所以,编号、地址、指针,这三个名称其实一回事。所以说指针是内存中一个最小单元的编号,也就是地址。
创建一个变量:
int a = 10;
整型变量占4个字节,每个字节都有一个地址,通常取出来的地址是第一个字节的地址。
内存中存放的是二进制序列,但是当前窗口展示的十六进制数。
- 10的二进制数:
00000000 00000000 00000000 00001010
- 转换成十六进制:4个二进制位可以转换成一个16进制位
00 00 00 0a(1010转换成十六进制是 a)
- 从图中可以看出数在内存中是倒着放进去的。至于为什么会倒着存放,这个问题会后面的文章讲述。
- 这里只是说明一个整型变量在内存中占4个字节,每个字节都有地址。
上面的图是显示4列的内存地址,如果看的不太明白也可以看一下显示1列的内存地址(将每个字节的地址都显示出来)
可以打印一下a的地址,使用%p打印变量的地址,打出来的地址是以十六进制的形式输出的。
#include<stdio.h>
int main()
{
int a = 10;
printf("%p\n", &a);
return 0;
}
运行结果
因为每次一下内存开辟的空间都是不一样的,所以每次打印的地址也是不同的。
使用指针变量保存变量的地址
int a = 10;
int* pa = &a; //pa是指针变量
- pa存放变量a的地址(&a拿到a的地址),pa是一个变量。
- 因为pa存放的是地址,而地址又称为指针,所以pa是一个指针变量。
总结: 指针就是地址,口语中说的指针通常指的是指针变量。
补充:指针的大小
1 字节 = 8 比特位
- 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以 一个指针变量的大小就应该是4个字节。
- 在64位机器上,地址是64个0或者1组成二进制序列,那地址就得用8个字节的空间来存储,所以 一个指针变量的大小就应该是8个字节。
总结:
指针是用来存放地址的,地址是唯一标示一块地址空间的。
指针的大小在32位平台是4个字节,在64位平台是8个字节。
指针的大小我在之前的文章中有叙述过。 文章中有代码举例说明,感兴趣的朋友可以看一下。指针的大小
二、指针和指针类型
我们都知道,变量有不同的类型,整形,浮点型等。指针(指针变量)也是变量,指针也是有类型的。
指针的定义方式是: 类型 * 变量名
例如:
int a = 10;
int* pa = &a; //pa是指针变量
pa是一个指针变量,pa的类型是 int*。
* 表示pa是一个指针变量,* 前面的 int 表示pa指向的a是int型变量 [ pa指向a 表示pa存放变量a的地址 ]。
char* pc;
short* ps;
long* pl;
float* pf;
double* pd;
总结:
- char* 类型的指针是为了存放 char 类型变量的地址。
- short* 类型的指针是为了存放 short 类型变量的地址。
- int* 类型的指针是为了存放 int 类型变量的地址。
- float* 类型的指针是为了存放 float 类型变量的地址。
指针类型的意义
前面已经知道了指针的大小,不管是什么类型的指针在32位平台下大小都是4,在64位平台下大小都是8。
既然指针的大小已经确定了,那为什么还要对指针定义出这多的类型呢?接下来了解一下指针类型的意义。
1、指针的解引用
总结:
指针的类型决定了,在指针解引用的时候一次能访问几个字节(指针的权限)。
- char* 的指针解引用就只能访问 1 个字节。
- int* 的指针的解引用就能访问 4 个字节。
- double* 的指针的解引用就能访问 8 个字节。
2、指针+-整数
#include <stdio.h>
//演示实例
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;
}
运行结果
总结:
指针类型决定了指针向前或者向后走一步,走多大距离(单位是字节)
了解了以上两个指针类型的意义,那么对内存中的数据操作起来就非常方便。
例如:创建一个整型数组,10个元素。使用指针初始化数组的内容是1~10,打印数组。
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int i = 0;
int* p = arr;//数组名表示数组首元素的地址
//对数组进行初始化
for (i = 0; i < 10; i++)
{
*(p + i) = i + 1;
}
//倒着打印数组中的内容
int* q = &arr[9];
for (i = 0; i < 10; i++)
{
printf("%d ", *q);
q--;
}
return 0;
}
运行结果
三、野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。
1、野指针成因
🌴 (1)指针未初始化
在定义变量的时候如果未对变量进行初始化,编译器会给该变量一个随机值,但是在使用该变量时编译器会报错。
在定义指针的时候如果未对指针进行初始化,指针就会指向一个随机的地址形成野指针,在使用该指针时编译器会报错。
🌴(2)指针越界访问
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i <= 10; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*p = i;
p++;
}
return 0;
}
代码分析
运行后会报错
🌴 (3)指针指向的空间释放
#include<stdio.h>
int* test()
{
int a = 10;
return &a;
}
int main()
{
int* p = test();
printf("%d\n", *p);
return 0;
}
运行结果
虽然打印出来的结果是10,但是这段代码是有问题的。
代码分析
如果在打印*p的前面再打印一条语句,打印出来的就不是10。
对于野指针的形成还有很多的原因,这里就不再去过多的介绍。了解了野指针的形成,在写代码的时候要做的就是避免产生野指针。
2、如何规避野指针
- 指针初始化
- 小心指针越界
- 指针指向空间释放及时置NULL
- 避免返回局部变量的地址
- 指针使用之前检查有效性
对指针初始化,明确指针指向的内存空间,如果不知道该指针指向的是哪块空间就赋值为NULL(空指针)。
#include<stdio.h>
int main()
{
int a = 10;
//明确指针的指向
int* pa = &a;
//不确定指针的指向
int* p = NULL;
return 0;
}
在使用指针之前要检查指针的有效性,只有当指针不为NULL的时候才可以使用。
#include<stdio.h>
int main()
{
int a = 10;
//明确指针的指向
int* pa = &a;
//不确定指针的指向
int* p = NULL;
if (pa != NULL)
{
*pa = 20;
}
printf("%d\n", *pa);
return 0;
}
补充:为什么要将不明确指向的指针赋值为NULL(空指针)
- 本质上将指针初始化为NULL就是初始化为0,只不过写成NULL更容易理解被初始化的变量是一个指针。
- 当有一个指针不再去使用时,及时的将其赋值为NLLL(空指针)。
四、指针运算
1、指针 +- 整数
#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 ", *(p + i));
}
printf("\n");
//指针 - 整数
int* q = arr+9;
for (i = 0; i < 10; i++)
{
printf("%d ", *(q - i));
}
return 0;
}
运行结果
2、指针 - 指针
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%d\n", &arr[9] - &arr[0]);
printf("%d\n", &arr[0] - &arr[9]);
return 0;
}
运行结果
- 指针 - 指针得到的是指针和指针(两个地址)之间元素的个数(取其结果的绝对值)。
- 指针 - 指针的前提是:两个指针指向同一块连续的空间。
求字符串长度
#include<stdio.h>
int my_strlen(char* arr)
{
char* p = arr;
while (*arr != '\0')
{
arr++;
}
return arr - p;
}
int main()
{
char arr[] = "abcdef";
int ret = my_strlen(arr);
printf("%d\n", ret);
return 0;
}
运行结果
3、指针的关系运算
#include<stdio.h>
int main()
{
int arr[5];
int* p;
for (p = &arr[5]; p > &arr[0];)
{
*--p = 0;
}
return 0;
}
代码简化, 这将代码修改如下:
#include<stdio.h>
int main()
{
int arr[5];
int* p;
for (p = &arr[4]; p >= &arr[0];p--)
{
*p = 0;
}
return 0;
}
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。
五、指针和数组
数组和指针本身是两种事物,二者的联系是:可以通过指针来访问数组。
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p + i);
}
return 0;
}
运行结果
所以 p+i 其实计算的是数组 arr 下标为i的地址。 那我们就可以直接通过指针来访问数组。
#include<stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9 };
int* p = arr; //数组名表示数组首元素的地址
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", p[i]); //p[i] --> *(p+i) --> arr[i]
}
return 0;
}
运行结果
六、二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在二级指针。
#include<stdio.h>
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;//ppa是一个二级指针
int*** pppa = &ppa;//pppa是一个三级指针
return 0;
}
以此类推多级指针也是存在的,这里只讨论二级指针。
可以通过二级指针ppa可以找到a:
#include<stdio.h>
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
**ppa = 20;
printf("%d\n", a);
return 0;
}
运行结果
七、指针数组
指针数组是指针还是数组?
答案:指针数组是数组。是存放指针的数组。
#include<stdio.h>
int main()
{
int arr[5];//整型数组 - 存放整型的数组
char ch[5];//字符数组 - 存放字符的数组
//指针数组 - 存放指针的数组
int a = 10;
int b = 20;
int c = 30;
int* parr[5] = { &a,&b,&c };//存放整型指针的数组
//指针数组不完全初始化时,默认为NULL(空指针)
return 0;
}
通过指针数组打印元素:
#include<stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = 30;
int* parr[5] = { &a,&b,&c };
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d ", *(parr[i]));
}
return 0;
}
运行结果
八、总结
此文章只是详细介绍了C语言中指针的基本用法,后期会继续介绍指针剩下的内容。