一. 指针是什么
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语言小白,有错误的话请大家私信我,我会改正,之后的几章章节梳理我也会陆续写出来