1.指针是什么
2.指针的类型
3.野指针的情况
4.指针与数组
5.二级指针
6.指针数组
一. 指针是什么
1.指针是内存中一个最小单元的编号,也就是地址
2.我们平时口语所说的指针,通常指的是指针变量,用来存放内存地址
一句话来说,指针就是地址,我们口语常说的指针是指针变量
我们不妨借表格来理解指针变量
内存 | |
一个字节 | 0xFFFFFFFF |
一个字节 | 0xFFFFFFFE |
... | |
一个字节 | 0x00000002 |
一个字节 | 0x00000001 |
我们可以通过&符将变量的内存以及地址,并将其存放在一个变量中,这个变量就是指针变量。
#include<stdio.h>
int main()
{
int a=20;//在内存中找到一块空间
int *p=&a;//用取地址操作符取出变量a的地址
//a变量占用四个字节的空间,这里是将a的四个字节的第一个字节的地址存放在p变量中,
//p成了一个指针变量
return 0;
}
总而言之,指针变量是用来存放地址的变量,我们不由地提出问题
1.一个小的内存单元应该是多大?
2.如何编址?
我们假设一个内存单元是1比特,那么一个字节的变量就需要8个内存空间,这是十分不方便的。
所以,我们将一个内存单元设为1字节
对于一般的32位的机器,假设有32根地址线,每根地址线在寻址时产生高电平和低电平(1或者0),那么32根地址线产生的地址为
00000000 00000000 00000000 00000000 |
00000000 00000000 00000000 00000001 |
... |
11111111 11111111 11111111 11111111 |
就产生了2^32次方个地址。
每个地址是一字节的内存空间,计算可得2^32Byte == 2^32/1024KB == 2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB
即我们可以给4G的空间编址
比葫芦画瓢,在64位的机器上,我们可以给8G的空间编址。
二,指针的类型
我们都知道,变量有不同的类型,指针有没有呢?答案是肯定的
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
char* pa = NULL;
int* pb = NULL;
short* pc = NULL;
long* pd = NULL;
float* pe = NULL;
double* pf = NULL;
return 0;
}
可以看到,指针的定义方式是“类型+*”
类似与定义变量,int *类型的指针是为了存放int类型变量的地址,double *类型的指针是为了存放double类型变量的地址。
2.1.指针类型的意义
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int n = 10;
char* a = (char*)&n;
int* b = &n;
printf("%p\n", &n);
printf("%p\n", a);
printf("%p\n", a + 1);
printf("%p\n", b);
printf("%p\n", b + 1);
return 0;
}
运行结果:
00EFF930
00EFF930
00EFF931
00EFF930
00EFF934
D:\Program Files (x86)\Microsoft Visual Studio\class109\Project114\Debug\Project114.exe (进程 20716)已退出,代码为 0。
按任意键关闭此窗口. . .
总而言之,指针类型决定了指针向前或向后走一步有多大距离
2.3.指针解引用
对于不同的指针类型,指针解引用的权限也不同
例如,char*的指针解引用只能访问一个字节,而int*可以访问4个字节
三.野指针
成因:
1.未初始化
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化
*p = 20;
return 0;
}
2.指针越界访问
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a[10] = { 0 };
int i = 0;
int* p = &a[0];
for (i = 0;i <= 11;i++)
{
*(p++) = i;//当指针超出数组的范围时,p就是野指针
}
return 0;
}
3.2.如何避免野指针
1.不忘记指针初始化
2.不超出数组范围,不越界
3.使用前检查指针有效性
四.指针与数组
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a[10] = { 1,2,3,4,5,6,7,8,9,0 };
printf("%p\n", a);
printf("%p\n", &a[0]);
return 0;
}
运行结果:
009BF9E4
009BF9E4
可以看到结果是一致的。所以数组名表示的是数组首元素的地址
那么,我们这样写代码是可行的
int a[10] = { 1,2,3,4,5,6,7,8,9,0 };
int* p = a; //*p存放的是首元素的地址
既然可以将数组名当成地址存到指针中,那我们直接用指针访问也成了可能
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int a[] = { 1,2,3,4,5,6,7,8,9,0 };
int* p = a; //指针存放数组首元素的地址
int sz = sizeof(a) / sizeof(a[0]);
for (int i = 0; i < sz; i++)
{
printf("&a[%d] = %p <====> p+%d = %p\n", i, &a[i], i, p + i);
}
return 0;
}
运行结果:
&a[0] = 010FFE80 <====> p+0 = 010FFE80
&a[1] = 010FFE84 <====> p+1 = 010FFE84
&a[2] = 010FFE88 <====> p+2 = 010FFE88
&a[3] = 010FFE8C <====> p+3 = 010FFE8C
&a[4] = 010FFE90 <====> p+4 = 010FFE90
&a[5] = 010FFE94 <====> p+5 = 010FFE94
&a[6] = 010FFE98 <====> p+6 = 010FFE98
&a[7] = 010FFE9C <====> p+7 = 010FFE9C
&a[8] = 010FFEA0 <====> p+8 = 010FFEA0
&a[9] = 010FFEA4 <====> p+9 = 010FFEA4
所以p+i其实计算的是数组a下标为i的地址
我们可以直接通过指针来访问数组
#include<stdio.h>
int main()
{
int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int *p = a; //指针存放数组首元素的地址
int sz = sizeof(a) / sizeof(a[0]);
int i = 0;
for (i = 0; i<sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
五.二级指针
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
return 0;
}
*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa .
int b = 20; *ppa = &b;//等价于 pa = &b;
而**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a .
**ppa = 30; //等价于*pa = 30; //等价于a = 30
六.指针数组
指针数组是数组,而且是存放指针的数组
我们已经知道的数组有整型数组和字符数组
int a1[5];
char a2[5];
a1 int int int int int
a2 char char char char char
而指针数组是这样的
int *a3[5];//整形指针数组
a3 int * int * int * int * int
c语言小白,有错误的话请大家私信我,我会改正,之后的几章章节梳理我也会陆续写出来