今天我们来学习C语言中分量很重的部分————指针
学什么
1. 指针是什么
2. 指针和指针类型
3. 野指针
4. 指针运算
5. 指针和数组
6. 二级指针
7. 指针数组
指针是什么
指针理解的2个要点:
1. 指针是内存中一个最小单元的编号,也就是地址
2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
首先我们把内存想象成一个楼房,把楼房分成有限的区域,每一个区域就是一个字节,然后每个字节设置一个编号,用的时候直接引用他的编号,这个编号就叫指针。

是不是相当通俗易懂。
把内存划分为一个个的内存单元,一个内存单元的大小为一字节,每一个字节有一个唯一的标号,我们称为地址,也就是指针。
2.指针变量
我们可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个
变量就是指针变量
#include<stdio.h>
int main()
{
int a=10;//a是整形,占用四字节的内存空间,每个字节都有对应的地址,得到的是a所占内存中第一个字节的地址。
int* pa=&a//&a得到的是a的地址,pa是指针变量
return 0;
}
总结:
指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电
平(低电压)就是(1或者0);
那么32根地址线产生的地址就会是:
2的32次方个地址。
每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB ==
2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB) 4G的空间进行编址。
指针和指针类型
我们都知道,变量有不同的类型,整形,浮点型等。那指针有没有类型呢?
准确的说:有的。
当有这样的代码:
int num = 10;
p = #
要将&num(num的地址)保存到p中,我们知道p就是一个指针变量,那它的类型是怎样的呢?
我们给指针变量相应的类型。
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
这里可以看到,指针的定义方式是: type + * 。
其实:
char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
那指针类型的意义是什么?
指针+整数
看这一段代码,我们给他调试一下

开始调试

可以看到,第一行存放的确实是我们输入的11223344
接着调试代码到*pa=0这一步

四个字节都变成了0
下面我们代码变一变

开始调试

和之前一样,11223344都在内存中了,还是调试到*pa=0这一步

区别出来了,这一次只改变了一个字节,问题出在哪?
答:类型的区别,第一次调试的是int,第二次调试的是char
划重点:指针类型决定了在解引用指针的时候能访问几个字节
再来看一段代码
#include <stdio.h>
int main()
{
int n = 10;
char *pc = (char*)&n;
int *pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc+1);
printf("%p\n", pi);
printf("%p\n", pi+1);
return 0;
}

对于&a的存贮数值,无论是int指针还是char指针是一样的

但是加1之后,int型加了4个字节,char型加了1个字节
总结:指针的类型决定了指针向前或者向后走一步有多大(距离)
学习这些有什么用,给大家一个实例
写代码
<利用指针写数组>
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = &arr[0];
int i = 0;
for ( i = 0; i < 10; i++)
{
*p = i + 1;
p++;
}
for (i = 0; i <10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}

野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针成因
1. 指针未初始化
比如:
#include <stdio.h>
int main()
{
int* p;
*p=20;
return 0;
}
这就是指针未初始化。
2. 指针越界访问
#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;
}
数组只有十个数,指针访问了第十一个数,越界了,越界的部分就是野指针。
如何规避野指针
1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放,及时置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
指针运算
指针+- 整数
指针-指针
指针的关系运算
指针+整数
上代码:
#define N_VALUES 5
float values[N_VALUES];
float *vp;
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
第二行创建了一个数组,第三行我们创建并定义了一个指针,在第四行,使得它等于数组的首元素地址,
,*vp++ =0;因为使用的是后置++,所以数组首元素被初始化为0,随着循环进行,数组的五个元素都变成了0。
指针-整数
上代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int n = &arr[9] - &arr[0];
printf("%d ", n);
return 0;
}
定义一个数组,取首元素和末尾元素地址,后者减前者,结果会是什么?
运行结果

指针减指针,得到的是两个指针之间的元素个数。
所以我们以后要得到数组元素个数的时候,就多了一个方法
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
char arr[10] = "sacasfa";
char* str = arr;
char* start = str;
while (*str != '\0')
{
str++;
}
printf("%d", str - start);
return 0;
}

指针关系运算
上代码:
#define N_VALUES 5
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
总结:指针加整数等于指针,指针减指针等于整数
指针和数组
指针就是指针,不是数组。
数组就是数组,不是指针。
指针的大小永远都是4或8个字节,这取决于编译器。
数组大小取决于元素个数和类型。
指针可以指向数组元素的
因为指针可以运算,所以借助于指针可以访问数组。
#include<stdio.h>
int main()
{
int arr[10];
int* p=arr;
int i=0;
//存放
for(i=0;i<10;i++)
{
*p=i+1;
p++;
}
//打印
p=arr;
for(i=0;i<10;i++)
{
printf("%d",*(p+1));
}
return 0;
}
二级指针
#include<stdio.h>
int main()
{
int a=10;
int *pa=&a;
return 0;
}
这一串代码,是pa存放a的地址。
#include<stdio.h>
int main()
{
int a=10;
int *pa=&a;
int ** paa=&pa;
return 0;
}
这一串代码就是二级指针,仔细看两串代码的不同。
指针数组
看代码:
#include<stdio.h>
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]));
}
return 0;
}
顾名思义,数组里存放的都是指针,就叫指针数组,看代码就可以明白。