目录
一 . 指针是什么?
1.指针是内存中最小的单元(byte)的编号,即地址(地址就是编号)
2.平常口语所说的指针指的是指针变量,指针变量是用来存放内存地址的变量
一个小的单元到底是多大?
1字节
如何编址?
对于32位机器,假设有32根地址线,寻址时会产生高电平和低电平(1或0),那么32根地址线就会产生2^32个地址,每个地址标识一个字节,那我们就可以给(2^32/1024/1024/1024 = )4GB的空闲进行编址:
同样的64位机器会产生2^64个地址,可以给(2^64/1024/1024/1024/1024/1024/1024 = )16GB的空闲进行编址。
总结:指针是用来标识地址的,地址是唯一用来标识一块地址空间的
指针的大小在32位平台是4个字节,在64位平台是8个字节
二 . 指针和指针类型
存放相应类型的变量需要相应类型的指针变量,但是不同指针类型的大小都是4字节
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char* pa = NULL;
short* pb = NULL;
int* pc = NULL;
double* pd = NULL;
printf("%zu\n", sizeof(pa));
printf("%zu\n", sizeof(pb));
printf("%zu\n", sizeof(pc));
printf("%zu\n", sizeof(pd));
//sizeof返回的值的类型是无符号整型,即unsigned int,用%zu返回
return 0;
}
a.指针类型的意义
1.指针的第一个意义
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 0x11223344;
int* pa = &a;
*pa = 0;
//这时a在内存中的四个字节就由44332211变为00000000
//这次改动,改变了四个字节
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 0x11223344;
char* pa = (char*)&a;
//指针变量的大小都是4个字节
*pa = 0;
//这时a在内存中四个字节由44332211就变为00332211
//这次改动,改变了一个字节
return 0;
}
结论:
指针类型决定了指针在被解引用时访问几个字节
如果是int*类型的指针,解引用访问4个字节
如果是char*类型的指针,解引用访问2个字节
推广到其他类型
2.指针的第二个意义
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 0x11223344;
int* pa = &a;
char* pb = (char*)&a;//强制类型转换
printf("pa = %p\n",pa);
printf("pa+1 = %p\n",pa+1);
printf("pb = %p\n",pb);
printf("pb+1 = %p\n",pb+1);
return 0;
}
结论2:
指针的类型决定了指针 +-1 操作的时候,跳过几个字节
决定了指针的步长
当指针解引用可以访问的字节数相同时,例如
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 0;
int* pa = &a;
//pa解引用访问4个字节,pa+1跳过4个字节
float* pf= &a;
//pf解引用访问4个字节,pf+1也是跳过4个字节
//那么int* 和float*是否可以通用?
//不能
//*pa = 100;
//现在a的四个字节存储了64 00 00 00
//*pf = 100.0;
//现在a的四个字节存储了00 00 c8 42
//虽然float* 和int* 访问内存的权限是相似的,但是两者存到内存的数据类型完全不同,存储方式不同,不能混用
return 0;
}
三 . 野指针
野指针:野指针就是指针指向的位置是不可知的(随机的,不正确的,没有明显限制的)
a.野指针成因
1.指针未初始化
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int* pa;
//此时*pa未初始化,就意味着没有明确的指向
//一个局部变量如果不初始化,放的就是一个随机值
*pa = 10;//非法访问内存,这里的pa就是野指针
return 0;
}
2.指针越界访问
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a[10] = 0;
int* pa = arr;//&arr[0]
int i = 0;
for(i = 0 ;i <= 10 ;i++)
{
*pa = 0;
pa++;
}
return 0;
}
//指针越界访问
3,指针指向的空间释放
int* test()
{
int a = 10;
return &a;
}
//a是局部变量,出了函数就被销毁,把内存还给操作系统,无法使用
int main()
{
int*pa = test();
return 0;
}
//这里*pa储存了a的地址,可以找到a的地址,但是无法访问使用a的空间
4.如何避免出现野指针
指针初始化,小心指针越界,指针指向空间释放及时置NULL,避免返回局部变量的地址,指针使用前检查其有效性
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
//指针初始化
//如果知道指针应该赋何值,就给指针初始化时赋该值
int a = 10;
int* p = &a;
//如果不知道指针应该赋何值,就把指针初始化为空指针
int* p2 = NULL;
//空指针不能直接赋值
// *p2 = 100; 会导致程序直接崩溃
//正确写法
if(p2 != NULL)
{
*p2 = 100;
}
return 0;
}
四.指针的运算
a.指针加减整数
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int mian()
{
#define N_VALUES 5
float values[N_VALUES];
float *vp;
for(vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
//可以看作两步
//*vp = 0;
//vp++;
}
return 0;
}
注:*vp++和(*vp)++的区别
*vp++可以看作:
*vp,vp++
是对vp的地址作出改变
而(*vp)++是先找到vp所指向的对象,对vp指向的对象++
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
//数组下标的写法
int arr[10] = {0};
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
//方法一:
/* for(i = 0; i < sz; i++)
{
arr[i] = 1;
}*/
//方法二:
/*int* vp = arr;
for(i = 0; i < sz; i++)
{
*vp = 1;
vp++;
}*/
//方法三:
int* pa = arr;
for(i = 0; i < sz; i++)
{
*(pa+i) = 1;
}
return 0;
}
b.指针减指针
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
//指针减指针
int arr[10] = {0};
printf("%d\n",&arr[9]-&arr[0]);
//指针减指针得到是指针之间的元素个数
//不是所有的指针都能相减,指向同一块空间的2个指针才能相减
//错误示例
//int arr[10] = { 0 };
//char ch[5] = { 0 };
//printf("%d", &arr[10] - &ch[0]);
return 0;
}
应用
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int my_strlen(char* str)
{
char* start = str;
while(*str != '\0')
{
str++;
}
return (str - start);
}
int main()
{
int len = my_strlen("abcdef");
printf("%d\n",len);
return 0;
}
//新方法,用来统计字符串所含字符个数
指针+指针 = 地址+地址,暂时没有意义
c.指针的关系运算
使数组values[N_VALUES]的各元素变为0
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define N_VALUES 5
int main()
{
float values[N_VALUES];
float *vp;
for(vp = &values[N_VALUES];vp > &values[0];)
{
*--vp = 0;
}
//或
//for(vp = &values[N_VALUES - 1]; vp >= &values[0]; vp--)
//{
// *vp = 0;
//}
//后者不一定可行,不符合标准
return 0;
}
后者在绝大多数编译器上可以完成任务,但我们还是应该避免这样写,因为标准不保证它可行
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针进行比较,,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
五,指针和数组
数组:一组相同类型的元素的集合
指针变量:用来存放地址的变量
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
//指针可以存放数组首元素的地址,可以通过指针来访问数组
//int* pa = &arr[0];
int* pa = arr;
//打印首元素地址
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
可见数组名和数组首元素地址是相同的
结论:数组名是数组首元素地址
(有两种例外情况,在数组部分)
那么我们就可以通过指针来访问数组
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int* pa = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
int i = 0;
for(i = 0; i < sz; i++)
{
printf("%d ", *(pa+i));
}
return 0;
}
如果指针指向数组首元素地址,那么指针p+i的地址和数组元素的arr[i]
的地址一样
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int* pa = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
int i = 0;
for(i = 0; i < sz; i++)
{
printf("%p--------------%p",&arr[i],p+i);
}
return 0;
}
这三者的写法是相同的
//printf("%d",arr[i]);
//printf("%d",*(p+i));
//printf("%d",*(arr+1));
数组传参的指针写法
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test(int* pa,int sz)
{
int i = 0;
for(i = 0; i < sz; i++)
{
printf("%d ",*(pa+i));
}
}
int main()
{
int arr[10] = { 0 };
test(arr, 10);
return 0;
}
六.二级指针
二级指针变量是用来存放一级指针变量的地址
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a;//pa是一个指针变量,一级指针变量
int** ppa = &pa;//ppa是一个二级指针变量
//*ppa解引用找到pa,再对pa解引用找到a,也就是通过**ppa可以找到a
**ppa = 20;
//*pa = 20;
//printf("%d\n",a);
return 0;
}
int i = 10;
int* pa = &i;
//int告诉我们pa指向的对象是int型
//*告诉我们pa是指针
int* *ppa = &pa;
//后面的*说明ppa是指针
//前面的*说明ppa指向的对象的类型是int*
七.指针数组
指针数组是存放指针的数组
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = 3;
int* pa = &a;
int* pb = &b;
int* pc = &c;
int* arr[4] = { &a, &b, &c };
int i = 0;
for(i = 0; i < 3 ; i++ )
{
printf("%d ", *(arr[i]));
}
return 0;
}
用指针数组模拟二维数组
二维数组
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int arr[3][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 };
//1,2,3,4
//2,3,4,5
//3,4,5,6
int i = 0;
int j = 0;
for(i = 0; i < 3; i++)
{
for(j = 0; j < 4; j++)
{
printf("%d ", arr[i][j]);
}
}
return 0;
}
模拟二维数组
#define _CRT_SECUER_NO_WARNINGS
#iclude <stdio.h>
int main()
{
int arr1[4] = { 1,2,3,4 };
int arr2[4] = { 2,3,4,5 };
int arr3[4] = { 3,4,5,6 };
int* prr[3] = { arr1, arr2, arr3 };
int i = 0;
int j = 0;
for(i = 0; i < 3; i++)
//这里的i指prr里的三个元素
{
for(j = 0; j < 4; j++)
//这里的j指prr里的数组的元素下标
{
printf("%d", prr[i][j]);//为什么不解引用呢?因为prr[i]等价于(*(prr+i))
}
printf("\n");
}
return 0;
}