今天作者又来更新好文了,我们今天来讲指针方面的知识,指针可以说对于我们编程是特别重要的一个部分,因为他涉及到内存跟地址有关系,那指针到底是什么呢?我们接下来从以下几个方面给大家细细的讲解(没有特别说明的情况下,都是在32位平台下讲解的):
指针:
我们电脑有硬盘,有内存,通常硬盘就是你的存储空间,内存任务在运行的时候所占的空间,一般电脑,500GB硬盘大小,8/16GB内存大,我们可以打开我们的任务管理器:
那我们到底是怎么使用内存空间的呢?
内存的最小单位就是一个字节,那内存一共有多少个字节组成呢?这时候就需要引用地址的概念。
地址是什么呢?
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0);
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
…
11111111 11111111 11111111 11111111
这里就有2的32次方个地址。每个地址是四个字节。
这时候我们有了2的32次方地址了,我们把这个地址所对应的数值,当成一个字节单位编号,这个时候我们内存就有2的32次方个字节。
每个地址标识一个字节,那我们就可以给 (2^32Byte = = 2^32/1024KB = =232/1024/1024MB==232/1024/1024/1024GB == 4GB)4G的空闲进行编址。
同样的方法,那64位机器,如果给64根地址线,那能编址多大空间,自己计算。
我们内存就可以这样表示
2.我们怎么使用指针呢?
我们可以通过&(取地址操作符)取出变量在内存起始地址,把地址可以存放到一个变量中,这个变量就是指针变量
#include
int main()
{
int a = 10;//在内存中开辟一块空间
//在内存的存储应该是原码的形式
//00000000 00000000 00000000 00001010
int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
中,p就是一个之指针变量。
return 0;
}
我们通过图理解了一遍,也发现地址的编号是连续的。我们在深入内存中来看看:
p里面放的是a的地址起始地址(0x004FF760),我们看到下一个内存的地址是(0x004FF764),编号相差4,就相差4个字节,这个我相信大家很容易搞混,作者自己也是理解了很长时间。
int*p=&a;,p是指针变量存放地址的,*是告诉我们p是指针变量,不是普通变量,int是指针所指向的类型,这个下一节会详细介绍
指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
1.这里我们就明白:
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
2.总结:
指针是用来存放地址的,地址是唯一标识一块地址空间的。指针的大小在32位平台是4个字节,在64位平台是8个字节
这里我们在讨论一下:指针的类型
我们都知道,变量有不同的类型,整形,浮点型等。那指针有没有类型呢?
准确来说是有的
对于普通变量来说,int是四个字节,char是一个字节等等,那么对于指针,我们在32位平台下,指针都是代表地址,都是四个字节,那么intp,charp不都是四个字节啊,那为什么指针还需要类型呢?
那指针类型的意义是什么呢?
我们来看一个代码
int a=0x11223344;
int b=0x11223344;
int *p1=&a;
char*p2=&b;
*p1=0;//解引用得到地址里面的内容
*p2=0;
我们通过这个例子可以说明,指针类型决定了指针进行解引用操作的时候一次访问几个字节(访问权限受到类型的控制)
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
我们再来看一个代码:
int a=0x11223344;
int b=0x11223344;
int *p1=&a;
char*p2=&b;
printf("%p\n",p1+1);
printf("%p\n",p2+1);
我们看到结果了,说明指针的类型决定了指针向前或者向后走一步有多大(距离)
通过上面两个特点,我们是不是就可以对内存中任意数进行修改,只要找到地址,进行解引用操作即可,
来看代码
int a=0x11223344
char*p1=(char*)&a;//强制类型转换一下
我们想把22这个数字成0,我们应该怎么做
*(p1+2)=0;
通过上面几个例子,我们知道了指针类型其实就是提供了不同视角去观看和访问内存。
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
#include
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;//p里面放的是随机的地址,你访问的地址万一是重要的数据就坏了大事
return 0;
}
#include
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p+i) = i;
}
return 0;
}
4.返回局部变量的地址
#include
int * test()
{
int a=0;
return &a;
}
int main()
{
int *p=test();
printf("%d",*p);
return 0;
}
我们在之前的函数部分讲到函数体内的变量只在函数体里有效,函数体内的变量执行之后就被销毁了,这时候这个变量的地址就不是变量的地址了,是操作系统的了,讲这个地址返回出去,在进行解引用会出现对指针的乱用,所以这样是错误的。
5.指针的有效性的检查
int*p=NULL:
*p=10;
对空指针进行解引用,会出现错误,NULL实际就是0,不能对0地址进行访问,因为内存中有些地址是给操作系统使用的这设计到内核方面的知识,我们这里所使用的内存是用户内存。
我们来看看正确的写法:
#include
int main()
{
int *p = NULL;
//....
int a = 10;
p = &a;
if(p != NULL)
{
*p = 20;
}
return 0;
}
所以我们尽量使用正确的代码!!
1.指针± 整数
2.指针-指针
3.指针的关系运算
#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
++的优先级较高,这是后置++,所以先使用*vp,在vp++;
我在之前的一篇博客专门介绍了怎么模拟实现strlen函数,可能大家当时还不太理解,今天我将在来好好的讲解一下。
#include
int my_strlen(char *s)
{
char *p = s;
while(*p != '\0' )
p++;
return p-s;//出循环就指向\0前面的地址
}
int main()
{
char arr[10]={};
gets(arr);
my_strlen(arr);
return 0;
}
#define N_VALUES 5
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;
}
最后比较是vp–跳到数组前面的一个地址,和第一个元素的地址进行比较。即使在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
指针和数组我在数组那一节已经讲解了一次,这里在补充一次。
我们看一个例子:
#include
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
可见数组名和数组首元素的地址是一样的。
结论:数组名表示的是数组首元素的地址。(2种情况除外,数组章节讲解了)
那么这样写代码是可行的:
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;//把首元素地址存储在p指针里面,并且数组里面的元素地址是连续的
既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为可能。
例如:
#include
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p+i);
}
return 0;
}
所以 p+i 其实计算的是数组 arr 下标为i的地址。
那我们就可以直接通过指针来访问数组。
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i<sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
这个为接下来的指针数组做铺垫。
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
这就是二级指针 。
我们通过一级指针来理解一下
int a=10;
int *pa=&a;
int** ppa=&pa;
printf("%d",*(*ppa))
我们先对ppa进行解引用,找到ppa里面的内容,在进行解引用找到pa里面的内容,就得到10了。
二级指针我们就讲到这里,后面我们在进行指针的进阶的时候我在细讲。
指针数组是指针还是数组?
答案:是数组。是存放指针的数组。
数组我们已经知道整形数组,字符数组。
int arr1[5];
char arr2[6];
我们讲一些整型的数或者字符型的数放在一个数组里面,就是整型和字符型数组,那我们是不是可以把医学相同的指针变量放到一个数组里面就叫指针数组,我们俩举个例子:
int a=10;
int b=20;
int c=30;
int*pa=&a;
int*pb=&b;
int*pc=&c;
int *arr[]={pa,pb,pc};
printf("%d %d %d",*arr[0],*arr[1],*arr[2]);
arr就是一个指针数组,把相同类型的指针变量放到同一个数组里面,arr[i]就是获取数组里面的元素,这里相当于指针变量,在进行解引用就可以得到里面的内容了。
但是这个代码我们一般不会这么使用,太啰嗦了,没有并要,我们一般可以这样去使用指针数组:
我们使用一维数组来模拟实现一个三行四列的二维数组
我们的设计思路如上图,我们在上代码更好的理解一下
int a[4]={1,2,3,4};
int b[4]={2,3,4,5};
int c[4]={3,4,5,6};
int*arr[3]={a,b,c};//a,b,c代表首元素地址
那我们已更改怎么使用他呢??
其实指针数组我们要学习的东西还是特别多的,因为我们是浅谈指针,所以博主不会深入的讲指针的。
今天指针的部分博主就分享到这里了,相信你们在遇到一些常规的关于指针的问题的时候,不会在一窍不通了,今天讲的知识点有许多和之前写的博客相同,可能讲的不太细致,希望读者见谅,你们也可以去看看我之前写的博客,温故而知新,下一讲我将讲解结构体方面的知识,也是浅谈,我们学习知识需要一点点的来,循序渐进。