指针
1.指针是什么
在计算机科学中,指针是编程语言中的一个对象,利用地址,他的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为"指针"。意思是通过它能找到以它为地址的内存单元。
内存
如何使用内存呢?
生活中,房子对应编号,编号-就为地址,将内存单元划分为一个一个格子,给每个格子编号
1.一个内存单元该是多大的空间?
计算机的单位:
bit 比特位 1个比特位存一个二进制位1/0
Byte—字节
1Byte=8bit
1KB=1024Byte
1MB=1024KB
1GB=1024MB
1TB=1024GB
1PB=1024TB
1Kb=1024bit=128Byte(这里要注意大写B和小写b是有区别的,B代表Byte,b代表bit)
计算机处理的是二进制的指令 0/1
32位机器上产生232个地址,假设一个bit位给一个地址,则管理232个bit位的空间,如下方图,232个bit位空间对应十进制位4294967296个bit空间,最后换算为gb为0.5gb,是比较小的。
我们都知道char为1个字节=8bit,如果用一个bit位代表一个地址,就需要8个地址,有点浪费
如果一个字节给一个地址,则管理2^32个字节的空间=4GB,比较合理,所以,最后把一个内存单元定为一个字节。
2.内存单元的编号怎么产生?
32位电脑
电脑有32根地址线/数据线,电线-通电-(正电)-1/(负电)-0
如果32根地址线都通电,具有32个01组成序列:
00000000000000000000000000000000
00000000000000000000000000000001
00000000000000000000000000000010
……
10000000000000000000000000000000
10000000000000000000000000000001
11111111111111111111111111111111
具有2^32种序列,一个格子为一个内存单元,将每一个这样的序列编号对应一个格子,编号称为内存单元的地址。
int main()
{
char ch ='w';//向内存申请1个字节的空间存放'w'
printf("%p\n",&a);//取地址
char* pc=&ch;
int a=10;//向内存申请4个字节的空间,存放10这个数字
int* pa=&a;//地址也是一个值,可以存储到pa变量中 - pa就是指针变量
*pa=20;//解引用操作符
printf("%d\n",a);//20,通过解引用找到a改为20
return 0;
}
指针就是变量,用来存放地址的变量
32位机器上的地址:
32bit的地址-4byte–指针就是地址 - 一个指针变量的大小就是4个字节
64位机器上的地址:
64bit的地址-8byte–指针就是地址 - 一个指针变量的大小就是8个字节
2.指针类型
指针类型的意义
1.指针进行解引用操作时候,能一次性访问几个字节
int mian()
{
int a=0x11223344;
//16进制数字一位相当于4位2进制数字,11对应为8位二进制数字-对应1个字节,所以第一个字节存储的是11,依次类推,内存如下图
int* pa=&a;
*pa=0;//访问4个字节
char* pc=&a;
*pc=0;//访问1个字节 当为int*时,解引用将四个字节内容都改为0;当为char*时只改变了一个字节的内容
return 0;
}
int类型指针,将a的存入pa中,内容存储如下
int类型指针*pa找到a,改a的值为0,能访问4个字节
char类型指针*pa找到a,改a的值为0,能访问1个字节
2.指针进行±整数的时候,步幅不一样,指针的类型决定了指针向前走一步或向后走一步有多大
int main()
{
int a=0x11223344;
int* pa=&a;
char* pc=&a;
printf("pa:%p\n",pa);
printf("pa+1:%p\n",pa+1);
printf("pc:%p\n",pc);
printf("pc+1:%p\n",pc+1);
return 0;
}
pa与pa+1相差了4个字节,pc与pc+1相差了一个字节。
下面我们看一个例子
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int* pa=arr;
int i=0;
for(i=0;i<10;i++)
{
printf("%d\n",*(pa+i));
}
return 0;
}
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,10};
char* pa=arr;
int i=0;
for(i=0;i<10;i++)
{
printf("%d\n",*(pa+i));
}
return 0;
}
当把int类型指针改为char类型时,pa+i时,每次加的是一字节,而数组类型为int,pa+1时访问的是数组中1的地址加1字节的地址,而1的地址四个字节的存储分别为01,00,00,00;2的地址四个字节的存储分别为02,00,00,00;3的地址四个字节的存储分别为03,00,00,00;所以以下访问到的只能是以下屏幕输出。
总结:
char*+1 跳过一个字节
short*+!跳过2个字节
int*+1 跳过4个字节
float*+1 跳过4个字节
double*+2 跳过16个字节
3.野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不明确的、不正确的)
野指针成因
1.指针未初始化
int main()
{
int *p;//p是指针变量,也是局部变量,局部变量指针未初始化,默认为随机值
*p=20;//随机一个地址放在p中,给p里面放个20
return 0;
}
2.指针越界访问
int main()
{
int arr[10]={1,2,3,45,6,7,8,9,10};//越界访问了
int *p=arr;
int i=0;
for(i=0;i<=10;i++)
{
*p=0;
p++;//当指针指向的范围超过数组arr的范围时,p就是野指针
}
return 0;
}
3.指针指向的空间释放
int* test()
{
int a=10;//a是局部变量,进函数创建,出函数应该销毁,销毁之前将地址返回
//假设a的地址为0x0012ff44
return &a;
}
int main()
{
int*p=test();//p就是一个野指针
//p指向a的地址0x0012ff44,a已经销毁了,非法访问
return 0;
}
如何规避野指针
1.指针初始化
int main()
{
int a=10;
int*p=&a;//初始化
int*pa=NULL;//当前指针不知道初始化为什么时,先初始化为NULL
return 0;
}
(1)当前指针不知道初始化为什么时
初始化为NULL
(2)明确知道初始化的值
初始化为确定变量的地址
2.小心指针越界
4.指针使用之前检查有效性
int main()
{
int *p=NULL;
if(p!=NULL)
{
*p=20;
}
return 0;
}
4.指针运算
1.指针±整数
举个例子
int main()
{
char arr[]={'a','b','c','d'};
//0 1 2 3
char* p=arr;
int i=0;
for(i=0;i<4;i++)
{
printf("%c\n",*(p+i);//p+0指向下标为0的位置
//p+1指向下标为1的位...p+3指向下标为3的位置
}
//另一种方法
/*int i=0;
for(i=0;i<4;i++)
{
printf("%c\n",*p++);
}*/
return 0;
}
2.指针-指针
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,10};
printf("%d\n",&arr[9]-&arr[0]);//9
return 0;
}
指针-指针求字符串长度
int my_strlen(char* str)
{
char* start=str;
while(*str!='\0')
{
str++;
}
return str-start;
}
int main()
{
char arr[]="abcdef";
int len=my_strlen(arr);
printf("%d\n",len);//6
return 0;
}
指针-指针 得到的是指针和指针之间的元素个数
前提:两个指针指向同一块空间的
求字符串长度
//1.函数求字符串长度
int main()
{
char arr[]="abcdef";
int len=strlen(arr);
printf("%d\n",len);//6
return 0;
}
//2.计数求字符串长度
int my_strlen(char* str)
{
int count=0;
while(str!='\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[]="abc";
int len=my_strlen(arr);
printf("%d\n",len);//3
return 0;
}
//3.指针-指针求字符串长度
int my_strlen(char* str)
{
char* start=str;
while(*str!='\0')
{
str++;
}
return str-strart;
}
int main()
{
char arr[]="abcdef";
int len=my_strlen(arr);
printf("%d\n",len);//6
return 0;
}
//4.递归求字符串长度
int my_strlen(char* str)
{
while (*str != '\0')
{
return 1 + my_strlen(str + 1);
}
return 0;
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n", len);//6
return 0;
}
3.指针的关系运算(比较大小)
int main()
{
int arr[]={1,2,3,4,5,6,7,8,9,10};
int i=0;
for(i=0;i<10;i++)
{
printf("%d\n",&arr[i]);
}
}
由输出可见,随着下标的增长,地址由低到高变化
#define N_VALUES 5
float values[N_VALUES];
float *vp;
for(vp=&values[N_VALUES];vp>&values[0];)
{
*--vp=0;
}
改进后:
#define N_VALUES 5
float values[N_VALUES];
float *vp;
for(vp=&values[N_VALUES-1];vp>=&values[0];vp--)
{
*vp=0;
}
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
5.指针和数组
int main()
{
int arr[]={1,2,3,4,5,6,7,8,9,10};
int* p=arr;
int i=0;
for(i=0;i<10;i++)
{
printf("%d",*(p+i));
}
printf("%p\n",arr);
printf("%p\n",&arr[0]);//打印第一个元素的地址
//绝大部分情况下:数组名就是数组首元素的地址
//数组名不是数组首元素的地址的情况:
//1.&数组名
//2.sizeof(数组名)
return 0;
}
绝大部分情况下:数组名就是数组首元素的地址
数组名不是数组首元素的地址的情况:
1.&数组名
2.sizeof(数组名)
数组是指针吗?NO
指针是数组吗?NO
数组是可以通过指针来访问的
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10};
int* p = arr;
printf("%d\n", p[2]);//p[2]-->*(p+2)
printf("%d\n", 2[arr]);
//arr[2]-->*(arr+2)-->*(2+arr)-->2[arr]
//arr[2]<==>*(arr+2)<==>*(p+2)<==>*(2+p)<==>*(2+arr)==2[arr]
//*(2 + arr) == 2[arr]
return 0;
}
6.二级指针
int main()
{
int a=10;
int*pa =&a;
int**ppa=&pa;//ppa就是二级指针,ppa用来存放pa的地址
return 0;
}
//对于二级指针的运算:
//1、*ppa通过对ppa中的地址进行解引用,这样找到的是pa,*ppa其实访问的就是pa
//2、**ppa先通过*ppa找到pa,然后对pa进行解引用操作:*pa,找到的是a。
7.指针数组
int main()
{
int arr[10];//整形数组--存放整形的数组
char ch[10];//字符数组--存放字符的数组
int* parr[5];//存放整形指针的数组 数组名为parr 元素类型为int*
return 0;
}
指针数组
存放指针的数组
int* arr[10];
char* arr2[5];
整形数组
int arr[10];//存放整形
字符数组
char ch[6];//存放字符
int main()
{
int a=10;
int b=20;
int c=30;
int* arr[5]={&a,&b,&c};
int i=0;
for(i=0;i<3;i++)
{
printf("%d",*(arr[i]));//arr[i]访问到地址,解引用访问到a,b,c
}
return 0;
}