目录
Ⅰ、指针是什么?
总结:
Ⅱ、指针和指针类型
1 .指针+-整数
2.指针的解引用
Ⅲ、野指针
1 .野指针成因
2 如何规避野指针
Ⅳ、指针运算
1 .指针 +- 整数
2. 指针 - 指针
3. 指针的关系运算
Ⅴ、指针和数组
Ⅵ、二级指针
Ⅶ、指针数组
指针
1. 指针是什么2. 指针和指针类型3. 野指针4. 指针运算5. 指针和数组6. 二级指针7. 指针数组
Ⅰ、指针是什么?
1. 指针是内存中一个最小单元的编号,也就是 地址2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
内存中每个字节都有一个地址。
我们可以通过 & (取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个变量就是指针变量
#include <stdio.h>
int main()
{
int a = 10;//在内存中 栈区 开辟一块空间
int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
//中,p就是一个之指针变量。
return 0;
}
指针变量 ,用来存放 地址 的变量。(存放在指针中的值 都被当成地址 处理)。那这里的问题是:一个小的单元到底是多大?( 1 个字节)如何编址?经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。对于 32 位的机器,假设有 32根地址线 ,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1 或者 0 );那么 32 根地址线产生的地址就会是:00000000 00000000 00000000 0000000000000000 00000000 00000000 00000001...11111111 11111111 11111111 11111111这里就有 2 的 32 次方个地址。每个地址标识一个字节,那我们就可以给 ( 2^32Byte == 2^32/1024KB ==2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB ) 4G的空闲进行编址 。那 64 位机器,如果给 64 根地址线,那能编址多大空间,也是按照上面的算法进行计算。这里我们就明白:在 32 位的机器上,地址是 32 个 0 或者 1 组成二进制序列,那地址就得用 4 个字节的空间来存储,所以一个指针变量的大小就应该是4 个字节。那如果在 64 位机器上,如果有 64 个地址线,那一个指针变量的大小是 8 个字节,才能存放一个地址。
指针是用来存放地址的,地址是唯一标示一块地址空间的。指针的大小在 32 位平台是 4 个字节,在 64 位平台是 8 个字节。
总结:
Ⅱ、指针和指针类型
int num = 10 ;p = & num ;
char * pc = NULL ;int * pi = NULL ;short * ps = NULL ;long * pl = NULL ;float * pf = NULL ;double * pd = NULL ;
示例:
int main()
{
int a = 0x11223344;
//char* p = (char*) & a;//int*
//*p = 0;
int* p = &a;//int*
*p = 0;
return 0;
}
int main()
{
int a = 10;
int* pa = &a;
*pa = 20;
printf("%d\n", a);
printf("%d\n", *pa);
printf("%p\n", &a);
printf("%p\n", pa);
return 0;
}
运行结果:
通过这个运行结果可以看见指针类型变量pa里面存了a的地址后,打印出来的他跟a的地址一样;
32位下:
1 .指针+-整数
示例:
int main()
{
int a = 0;
int * pa = &a;
char* pc = &a;
printf("pa = %p\n", pa);
printf("pa+1 = %p\n\n\n", pa+1);
printf("pc = %p\n", pc);
printf("pc+1 = %p\n\n\n\n", pc+1);
return 0;
}
运行结果:
这里也可以看出不同指针类型的意义:
这里我们也经常使用这个指针加减整数,例如访问数组的时候:
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = &arr;
int i = 0;
while ((i++) < 10)
{
printf("%d ", *p);
p++;
}
return 0;
}
输出结果:
当然我们也可以从内存去看这个改变 :
示例:用short*指针修改int,可以清楚看到每次修改2个字节(动图)
用char*指针修改int,可以清楚看到每次修改一个字节(动图)
用int*指针修改int,可以清楚看到每次修改四个字节(动图)
2.指针的解引用
解引用就是可以对指针变量保存地址中的数据进行修改,取值等一系列操作:
示例:
//演示实例
#include <stdio.h>
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;
int *pi = &n;
*pc = 0; //重点在调试的过程中观察内存的变化。
*pi = 0; //重点在调试的过程中观察内存的变化。
return 0;
}
指针的类型决定了,对指针解引用的时候有多大的权限( 能操作几个字节 )。比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
Ⅲ、野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
1 .野指针成因
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
#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;
}
int* test()
{
int a = 10;//0x0012ff40
return &a;
}
int main()
{
//0x0012ff40
int *p = test();
//p就是野指针
printf("%d\n", *p);//
return 0;
}
这里的test()函数会返回一个int*的地址,也就是a的地址。但是变量a是函数test()在执行的时候创建的,函数运行完毕后,a的空间会随着函数栈帧的销毁而释放,在回到main()函数后,指针类型p接收到一个已经释放的变量a的空间,在使用的时候,也会造成野指针问题。
2 如何规避野指针
这里对于规避野指针有一些小建议:
1. 指针初始化2. 小心指针越界3. 指针指向空间释放即使置 NULL4. 避免返回局部变量的地址5. 指针使用之前检查有效性
NULL为0,我们用户在使用的时候不能使用。
Ⅳ、指针运算
指针 +- 整数指针 - 指针指针的关系运算
1 .指针 +- 整数
#define N_VALUES 5float values [ N_VALUES ];float * vp ;// 指针 +- 整数;指针的关系运算for ( vp = & values [ 0 ]; vp < & values [ N_VALUES ];){* vp ++ = 0 ;}
看下面代码,我们可以用多种方法对数组进行访问:
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
// 0 1 2 3 4 5 6 7 8 9
//使用指针打印数组的内容
int * p = arr;
int i = 0;
//arr-->p
//arr == p
//arr+i == p+i
//*(arr+i) == *(p+i) == arr[i]
//*(arr+i) == arr[i]
//*(i+arr) == i[arr]
for (i = 0; i < 10; i++)
{
//printf("%d ", *(p + i));
printf("%d ", *(arr + i));
//printf("%d ", arr[i]);
//printf("%d ", i[arr]);
}
return 0;
}
数组名是首元素地址,只要拿到首元素地址就可以访问后面的元素;
2. 指针 - 指针
指针-指针的前提:两个指针指向同一块区域,指针类型时相同的
指针-指针差值的绝对值,指针和指针之间的元素个数
指针 - 指针得到的值为两个指针之间的元素个数,如果是小地址减大地址则为元素个数的相反数;
int main()
{
int arr[10] = { 0 };
printf("%d\n", &arr[9] - &arr[0]);
printf("%d\n", &arr[0] - &arr[9]);
return 0;
}
执行结果:
下面的代码可以利用指针 - 指针求出某个字符串的长度:
int my_strlen(char *s)
{
char *p = s;
while(*p != '\0' )
p++;
return p-s;
}
int main()
{
char arr[] = "abcdef";
size_t len = my_strlen(arr);
printf("%zd\n", len);
return 0;
}
运行结果:
当然这里的代码中判断条件也可以去掉 ‘\0' 他的ascall码为0 0为假,也会跳出循环,例如:
size_t my_strlen(char* str)
{
char* start = str;
while (*str)
{
str++;
}
return str - start;
}
3. 指针的关系运算
int my_strlen(char *s)
{
char *p = s;
while(*p != '\0' )
p++;
return p-s;
}
代码简化, 这将代码修改如下:
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。
Ⅴ、指针和数组
示例:
#include <stdio.h>
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 种情况除外)
#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(i=0; i<sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p+i);
}
return 0;
}
运行结果:
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*p 中存放的是变量a的地址,指针变量 int**pp中存放的是一级指针变量 p的地址,当然,要存放pp的话就需要 int*** 类型的指针,也就是说要比自己高一级。有一点俄罗斯套娃的意思:
然后我们理解一下里面的 *
对于 int * p ,我们可以这样理解:* 表示的是 p 是一个指针,前面的int表示的是这个指针指向的数据是 int 类型的。
对于 int ** p,我们可以这样理解:靠近 pp 的那一个 * 表示 pp 是一个指针,前面的 int* 表示pp指针指向的数据是 int* 类型的;
以此类推理解3,4,5 ... ... n级指针
代码示例:
int main()
{
int a = 10;
int* p = &a;//p是指针变量,一级指针变量
int* * pp = &p;//pp指针变量,二级指针变量
**pp = 20;
printf("%d\n", a);//20
//int** * ppp = &pp;//pp是指针变量,三级指针变量
//...
return 0;
}
执行结果:
int b = 20 ;* ppa = & b ; // 等价于 pa = &b;
** ppa = 30 ;// 等价于 *pa = 30;// 等价于 a = 30;
Ⅶ、指针数组
int arr1 [ 5 ];char arr2 [ 6 ];
int* arr3 [ 5 ]; // 是什么?
下面我们用一维数组模拟一个二维数组:
int main()
{
//使用指针数组,模拟一个二维数组
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
//指针数组
int* arr[] = { arr1, arr2, arr3 };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
执行结果: