1.指针是什么?
指针理解有两点:
1.指针是内存中最小单元的编号,也就是地址。
2.平时口语说的指针,通常指的是指针变量,是用来存放内存地址的变量。
我们日常生活中买的电脑有的有4G内存,也有8G,16G内存等等。计算机为了存储数据,内存被划分成一个一个字节的大小,同时每一个字节都有一个编号,每个编号来表示一个地址来存储需要存放的内容。
1.把内存划分为一个个的内存单元,这个内存单元的大小是1个字节。2.每个字节都给一个唯一的编号,这个编号我们称为地址,地址在C语言中也叫:指针 (编号==地址==指针)
指针变量
我们可以通过&(取地址符)取出变量的内存,把地址可以存放到一个变量中,这个变量就是指针变量。例如下面的int *p
int main()
{
int a = 10;//在内存中开辟一块空间
//a是整形,占用4个字节的内存空间,每个字节都有对应的地址
int* p = &a;
//&a 得到的是a的地址(指针)
//其实得到的是a所占内存中4个字节中第一个字节的地址pa指针变量
p=10;
return 0;
}
总结:指针变量,用来存放地址的变量(存放在指针的值都被当成地址处理)。
一个小的单元到底是多大?
经过计算是一个字节给一个对应的地址是比较合适的。
对于32位机器,假设有32根地址线,那么假设每根地址线在寻址的时候,产生高电平(高电压)和低电平(低电压)就是(1或者0);
那么32根地址线产生的地址就会是:由32个0和1组成的数字。共有2的32次方个地址。
每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB ==2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB) 4G的空间进行编址。
同样方法,那么64位机器,如果给64根地址线,那么能有16个G的空间进行编址。
所以:
在32位的机器上,有32个地址线,地址是32个0或者1组成的二进制序列,那么地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
在64位的机器上,有64个地址线,地址是64个0或者1组成的二进制序列,那么地址就得用8个字节的空间来存储,所以一个指针变量的大小就应该是8个字节。
总结:
指针变量是用来存放地址的,地址是唯一标示一个内存单元的。
指针的大小在32位平台是4个字节,在64位平台是8个字节。
2.指针和指针类型
指针在x86的环境下大小为4,在x64的环境下大小为8,相同环境下指针大小都是一样的。那么为什么还有不同的类型呢?不同类型的指针的作用是什么呢?
2.1指针类型的第一个意义是:指针类型决定了,在解引用指针的时候能访问几个字节
我们先看下面代码:
//代码1
int main()
{
int a = 0x11223344;
int* pa = &a;
*pa = 0;
return 0;
}
//代码2
int main()
{
int a = 0x11223344;
char* pc = &a;
*pc = 0;
return 0;
}
先从代码1来看,我们通过调试,首先取地址a,此时并未存放任何地址。
然后我们先对a进行赋值
当走到*pa=0时,我们发现有4位变成了0
我们再看代码2
与前者也是一样的,找到a的地址,起始值为0x112233
当走到*pa=0时,我们发现有1位变成了0
因此说明,int类型的指针访问了4个字节,char类型的指针访问了1个字节。所以,不同的指针类型带来的不同的访问大小。未来想用什么样的方式访问内存就要用什么样的指针来访问。
2.2指针类型的第二个意义,指针类型决定了,指针进行+1、-1的时候,一步走多远
int a = 10;
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;
代码结果为:
pa+n(n=n*sizeof(int));pb+n(n=n*sizeof(char))
总结:指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。 比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
3.野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
3.1野指针成因
1.指针未初始化
//例子1
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
2.指针越界访问
//例1
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
//例2
int* test()
{
int a = 10;
return &a;
}
int main()
{
int*p = test();
*p = 100;
return 0;
}
这段代码的问题也是出现了野指针,先进入函数test,函数中创建了一个变量a,返回值为a的地址。但是在结束调用函数后,a的这块地址返回给内存,此时*p里存放的地址是函数返回值(也就是当时的a的地址),但是现在a已经找不到了,此时给*p存放的地址是危险的,是无意义的,因此是野指针。
3. 指针指向的空间释放
(暂略)
3.2如何规避野指针
1.指针初始化
int a = 10;
int* p = &a;
2.小心指针越界(注意条件、注意范围等)
3.指针指向空间释放,及时置NULL
int* p = NULL;
(一个指针不知道应该指向哪里的时候,暂时可以初始化为NULL)
4.避免返回局部变量的地址
也就是刚刚的例题2代码,避免存入的地址没有意义。
5.指针使用之前检查有效性
可以添加if条件语句来进行校验指针的有效性。
if (p != NULL)
{
*p = 100;
}
4.指针运算
指针+- 整数
指针-指针
指针的关系运算
4.1指针+- 整数
#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
4.2指针-指针
前提:
1.两个指针指向同一块空间,所以,这两个指针的类型是一致的。
2.指针-指针得到的是指针和指针之间的元素个数
3.指针地址相减是有正负的,大地址减小地址为正,小地址减大地址为负。
//代码1
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int n = &arr[9] - &arr[0];
printf("%d\n", n);
return 0;
}//指针减指针得到的是指针与指针之间的元素个数
其实也就是说:指针加上整数等于指针,指针减去整数还是指针。
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int n = &arr[0] - &arr[9];
printf("%d\n", n);
return 0;
}//指针地址相减是有正负的
例如把上面的代码修改一下:应用一下指针-指针
int my_strlen(char* str)
{
char* start = str;
while (*str != '\0')
str++;
return (str - start);
}
int main()
{
int len = my_strlen("abcdef");
printf("%d", len);
}
4.3指针的关系运算
//代码1
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
虽然指针指到了内存以外的位置,但是并没有访问,所以不会存在野指针的问题。
代码简化,这将代码修改如下:
//代码2
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
注意:
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
5、指针与数组
指针与数组没有等价关系;指针的大小是4/8个字节,指针是存放地址的,地址的存放需要多大空间,指针变量的大小就是多少。数组的大小取决于元素的个数和每个元素类型。
复习引入,看以下例子:
可见数组名和数组首元素的地址是一样的。即:数组名表示的是数组首元素的地址。(有两个例外sizeof和&——第三章数组)
那么:
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;//p存放的是数组首元素的地址
所以:可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为可能。
例如:
#include <stdio.h>
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(int 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;
}
6.1、二级指针
int main()
{
int a = 10;//a是要在内存中申请4个字节的空间的
//一级指针
int* pa = &a;//0x0012ff40, pa是指针变量,用来存放地址,也得向内存申请,申请4/8
//二级指针
int** ppa = &pa;//0x0012ff48
int** * pppa = &ppa;
printf("%d\n", **ppa);
return 0;
}
输出和图例说明:
7、指针数组
指针数组是指针还是数组?答案:是数组。是存放指针的数组。
//指针数组
int main()
{
int a = 10;
int b = 20;
int c = 30;
//指针数组-存放指针的数组
int* arr[] = { &a, &b, &c };
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d ", *(arr[i]));
}
printf("\n");
//char* arr2[5];
//float* arr3[5];
return 0;
}
数组的存放,以整形和字符形数组为例:
指针数组的存放,以整形指针数组为例:
例题应用:
//通过一维数组模拟二维数组
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
int* ptr[] = {arr1, arr2, arr3};
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", ptr[i][j]);//-- +(p+i)=arr[i]
}
printf("\n");
}
return 0;
}