一.内存的理解:
内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。所以为了有效的使用内存,就把内存划分成一个个小的内存单元,为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。
问题:
一个小的内存单元到底是多大?(1个字节)
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电 平(低电压)就是(1或者0);那么32根地址线产生的地址就会是:
这些数字总共产生2^32次方个地址。计算机上有 bit byte kb mb gb tb pd 这些单位。
1byte=8bit 1 KB = 1024 Bytes 1 MB = 1024 KB 1 GB = 1024 MB
假设每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB == 2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB) 所以有4G的空闲内存进行编址。
#include <stdio.h>
//指针变量的大小取决于地址的大小
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{
printf("%d\n", sizeof(char*));
printf("%d\n", sizeof(short*));
printf("%d\n", sizeof(int*));
printf("%d\n", sizeof(double*));
return 0;
}
我们可以看到他们的指针大小一样
二.什么是指针:
1. 指针是内存中一个最小单元的编号,也就是地址
2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常指的是指针变量
三.指针和指针类型:
定义指针:
int num = 10;
int *p=#//p为一个整形指针变量
指针的简单使用:
#include <stdio.h>
int main()
{
int num = 10;
int *p = #
*p = 20;// * 解引用操作符,通过p找到所指向的对象,即num
return 0; }
指针的类型:
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
从中可以看到指针的定义是:数据类型 + *
而且这些指针的大小在32位平台都是4个字节,在64位平台都是是8个字节
既然这些指针在同一台机器上的大小都一样,那么指针类型的意义是什么呢?
接下来我们看一下这段代码:
#include <stdio.h>
int main()
{
int a = 0x11223344;
//整型指针
/*int* pa = &a;
*pa = 0;*/
//字符型指针
char* pc = &a;
*pc = 0;
//printf("%p\n", pa);
printf("%p\n", pc);
return 0;
}
对于整型指针,我们打开调试窗口的内存进行调试:
可以看到,如果指针类型是int型,可以访问四个空间的大小,那么修改值的时候修改了四个空间大小的值,char的数据类型仅仅修改了一个空间的值。
可以知道:当我们需要修改值的时候,需要选择不同类型的指针进行修改,才能达到我们需要的数据.
四.指针+-整数:
指针加减一个整数,内存地址会发生什么变化呢?
int main()
{
int a = 0x11223344;
int* pa = &a;
char* pc = &a;
printf("%p\n", pa);
printf("%p\n", pa+1);
printf("%p\n", pc);
printf("%p\n", pc+1);
return 0;
}
我们可以知道:指针的类型决定了指针向前或者向后走一步有多大(距离)
案例:将一个空间大小为10的int型变量的值全部改为1
分别使用整型指针,字符型指针来进行修改
int main()
{
int arr[10] = { 0 };
int* p = arr;//数组名-首元素的地址
//char*p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = 1;
}
return 0;
}
对于下图 int 型的指针可以全部改为1
对与下图使用char型的指针仅仅修改了两个半字节大小的值
从图中可以知道使用int类型的指针可以完全修改,使用char类型的指针进行修改的话仅仅修改10个字节大小的空间,而arr是40个字节大小的空间。
五.指针的解引用:
#include <stdio.h>
int main()
{
int n = 0x11223344;
char *pc = &n;
int *pi = &n;
*pc = 0; //重点在调试的过程中观察内存的变化。
*pi = 0; //重点在调试的过程中观察内存的变化。
return 0;
}
六.野指针:
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
int main()
{
//int a;//局部变量不初始化,默认是随机值
int *p;//局部的指针变量,就被初始化随机值
*p = 20;
return 0;
}
6.2指针的越界访问
int main()
{
int arr[10] = { 0 };
int *p = arr;
int i = 0;
for (i = 0; i < 12; i++)
{
p++;
}
//当指针指向的范围超出数组arr的范围时,p就是野指针
return 0;
}
6.3
指针指向的空间释放
int* test()
{
int a = 10;
return &a;
}
int main()
{
int *p = test();//这里调用了test函数,test函数返回了a(局部变量)的地址,那么当调用函数结束的时候,系统为a变量开辟的内存空间会自动释放归还给系统
//此时a的内存空间不属于当前的程序,而系统可能会将a的内存分配给其他空间,此时就会造成野指针的情况
printf("%d\n",* p);
return 0;
}
注意:从上面可知要避免使用临时变量从而规避野指针
我们如何才能避免野指针?
1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放即使置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
3. 指针指向空间释放即使置NULL
当你不想使用一个指针的时候,可以将其置为NULL
int main()
{
//int b = 0;
//int a = 10;
//int*pa = &a;//初始化
//int* p = NULL;//NULL- 用来初始化指针的,给指针赋值
int a = 10;
int *pa = &a;
*pa = 20;
//
pa = NULL;//不再使用pa指针,不能再访问a了
if (pa != NULL)//指针使用之前检查有效性
{
}
return 0;
}
七.指针的运算:
7.1指针+-整数
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);//计算数组的长度
int* p =arr;
for (i = 0; i < sz; i++)
{
printf("%d ", *p);
p++;
}
return 0;
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = &arr[9];
for (i = 0; i < 5; i++)
{
printf("%d ", *p);
p-=2;
}
return 0;
}
指针-指针=指针之间元素的个数
但是指针相减必须是同一块数组空间(同一类型的)
int main()
{
char ch[5] = {0};
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
printf("%d\n", &arr[9] - &arr[0]);//等于9 即指针之间的元素个数
//printf("%d\n", &arr[9] - &ch[0]);//err 不同类型的指针不能相减
return 0;
}
案例:使用指针计算字符串的字符个数
int my_strlen(char* str)
{
char* start = str;//数组首元素地址
char* end = str;
while (*end != '\0')
{
end++;
}
return end - start;//返回字符个数
}
int main()
{
//
//strlen - 求字符串长度
//递归 - 模拟实现了strlen- 计数器的方式1, 递归的方式2
//
char arr[] = "bit";
int len = my_strlen(arr);//数组名即数组的首元素地址
printf("%d\n", len);
return 0;
}
7.2指针的关系运算
#define N_VALUES 5
float values[N_VALUES];
float* vp;
int main()
{
for (vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
return 0;
}
八.指针与数组:
有两种特殊的情况:
//1. &arr- &数组名- 数组名不是首元素的地址-而表示整个数组 -所以 &数组名 取出的是整个数组的地址
//2. sizeof(arr) - sizeof(数组名) - 数组名表示的整个数组- sizeof(数组名)计算的是整个数组的大小
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);//地址-首元素的地址
printf("%p\n", arr+1);
printf("%p\n", &arr[0]);//
printf("%p\n", &arr[0]+1);
printf("%p\n", &arr);//整个数组的地址
printf("%p\n", &arr + 1);
//1. &arr- &数组名- 数组名不是首元素的地址-数组名表示整个数组 - &数组名 取出的是整个数组的地址
//2. sizeof(arr) - sizeof(数组名) - 数组名表示的整个数组- sizeof(数组名)计算的是整个数组的大小
return 0;
}
九.二级指针:
int main()
{
int a = 10;
int * pa = &a;
int* * ppa = &pa;//将pa的地址存放到ppa上,ppa就是二级指针 从右往左,第一个*说明ppa为指针,第二个*说明前面指向的指针类型为int型
**ppa = 20;//将a修改为20
printf("%d\n", **ppa);
printf("%d\n", a);
//int** * pppa = &ppa;//三级指针
return 0;
}
二级指针的运算:
ppa变量存放的是pa的地址,*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa
int a = 20;
*ppa = &a;//等价于 pa = &a;
**ppa 先通过 *ppa 找到 pa ,然后再对 pa 进行解引用操作: *pa ,那找到的是 a .
**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;
十.指针数组:
int main()
{
int a = 10;
int b = 20;
int c = 30;
//int* pa = &a;
//int* pb = &b;
//int* pc = &c;
//整形数组 - 存放整形
//字符数组 - 存放字符
//指针数组 - 存放指针
//int arr[10];
int* arr2[3] = {&a, &b, &c};//指针数组
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d ", *(arr2[i]));//找到对应的值
}
return 0;
}
字符数组和整型数组:
int arr1[5];//arr1是一个数组,有五个元素,每个元素是一个int型
char arr2[6];
指针数组:
int* arr3[5];