第一章 初识C语言
第二章 变量
第三章 常量
第四章 字符串与转义字符
第五章 数组
第六章 操作符
第七章 指针
第八章 结构体
第九章 控制语句之条件语句
第十章 控制语句之循环语句
第十一章 控制语句之转向语句
第十二章 函数基础
第十三章 函数进阶(一)(嵌套使用与链式访问)
第十四章 函数进阶(二)(函数递归)
第十五章 数组进阶
第十六章 操作符(详解及注意事项)
第十七章 指针进阶(1)
在前面的章节中,我们对指针有了一个初步的认知,那么在本章节中我们将对指针进行深度地学习。希望大家能够对指针的了解更加深刻。
提示:以下是本篇文章正文内容,下面案例可供参考
我们以下面的代码为例:
int a = 0x11223344;
int* pa = &a;
*pa = 0;
我们运行上述的代码:
当我们代码执行到创建变量a的时候,我们打开监视和内存窗口,便能够查看该变量所在空间的地址,以及空间内的内容。
继续向下调试,我们发现通过一个指针类型为整型的指针变量能够修改a变量空间内4个字节的内容。也就是说,指针类型为整型的指针的访问权限是4个字节。
我们对上述代码稍作修改:
int a = 0x11223344;
char* pa = &a;
*pa = 0;
将整型数据a的地址存储在指针类型为字符型的指针变量中,再次通过指针来修改A中的值。
我们再次监视其内部存储的变化。我们发现,我们用字符型的指针变量去间接的访问这个变量,我们仅仅将整型数据a中的1个变量改成了0。也就是说,我们的访问权限只有一个字节。
由这两个例子,我们能够得到如下的结论:
指针变量在解引用操作时,其访问的权限大小取决于其指针类型的所占空间大小。
我们观察下面的代码:
int a=10;
int*p1=&a;
char*p2=&a;
printf("p1=%p\n",p1);
printf("p2=%p\n",p2);
printf("p1+1=%p\n",p1+1);
printf("p2+1=%p\n",p2+1);
根据代码运行的结果我们得知,当我们将整型数据类型的指针变量向后偏移一个单位的时候,其偏移了四个字节的长度。
当我们将字符型数据类型的指针变量向后偏移一个单位的时候,其偏移了一个字节的长度。
由上述分析,我们能够得到的结论是:
不同数据类型的指针一次偏移的字节数是不同的,其偏移量由指针的数据类型决定。
野指针是指:指针指向的位置是随机的、不正确的或者没有明确限制的。
int*p;
*p=10;
当我们运行上述代码的时候,系统会出现报错。由于我们未初始化指针变量,所以我们访问的是一段我们没有申请过的内存空间,也就是说我们对这段内存空间没有访问的权限。这种情况下的指针p就是野指针,而解引用野指针去访问一个空间时,该操作属于非法操作。
当我们创建一个数组后,我们可以通过指针的偏移来访问其中元素,倘若我们的指针偏移量过多,就会出现数组的越界访问,那么这个时候该指针指向的就是一段没有访问权限的空间,这个指针便称为了野指针。
int* test01()
{
int number=10;
return &number;
}
int main()
{
int*p=test01();
*p=20;
return 0;
}
根据前面章节的学习,我们了解到,test01函数内创建的变量属于一个局部变量。当该函数体中的代码执行结束后,该局部变量的生命周期就结束了,该变量就会被释放。释放后,我们对该空间就失去了访问权限。
所以,我们主函数内的指针p就变成了野指针。
我们在创建一个指针变量的时候,最好对其进行初始化操作。倘若一开始我们并不知道这个指针应该指向哪里,我们可以利用空指针进行初始化。
int a=0;
int*p=&a;
int*p1=NULL;
空指针的作用一般是用来初始化指针的。
当指针指向的空间被释放时,及时将其指向空指针(NULL)。
在前面的三点中,我们会及时将其指向空指针。但是空指针也是无法直接访问的,如果强行访问程序会崩。那么在使用之前我们要检查这个指针是否指向实际的一段地址,也就是说检查一下这个指针是否是有效的。我们可以采用以下的代码进行检查。
int*p=NULL;
if(p!=NULL)
{
printf("%d\n",*p);
}
这个是非常基础的指针运算,在我们访问数组中的元素时经常使用,这里就不做过多的解释了。
int arr[5]={0};
for(int*p=&arr[5];p>&arr[0];)
{
*--p=0;
}
我们初始化这个指针,将这个指针指向数组的最后一个元素。而一个数组中的元素地址是随着下标的增加而增大的。也就是说我们的指针初始化为了一个高地址。我们将这个指针指向的高地址和数组首元素的地址(低地址)作比较,满足条件则循环。
一开始符合条件,那么开始执行循环体内的代码,由于前置递减的优先级高,所以优先进行前置减减运算,即先将地址减1,向低地址偏移四个字节,由第五个元素的地址变成第4个元素的地址。然后再对其进行解引用操作。
或许有人会感到疑惑,我们创建的数组只有5个元素,但是为什么在一开始指向第6个元素空间的时候,没有发生越界访问。
其实原因很简单,越界访问的前提是我们要访问该空间。但是单纯的指向该空间并不非法。就好像你进宿舍的门时,需要寻找门牌号。倘若走到了你隔壁,你看了一眼并记住了隔壁的门牌号并不违法。但是你进隔壁的房间则是越界行为。
我们仔细分析上述的代码,我们会发现指针p会从arr[5]的地址逐渐偏移到arr[0]的地址。
但上述代码并不好理解,我们可以进行如下的优化:
int arr[5]={0};
for(int*p=&arr[4];p>&arr[0];p--)
{
*p=0;
}
进行上述优化后,虽然仅仅变动了一部分,但是循环体内部的代码运算顺序发上了改变,刚才是先偏移后访问,现在是先访问后偏移。刚才从数组末尾结束后多余的空间的地址到第一个元素地址。现在是从最后一个元素地址到第一个元素前未的地址。
虽然整体的效果相同,但是并不推荐优化后的代码。
因为:
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
指针减去指针存在,但是指针加指针是不存在的。其实非常好理解,指针减去指针可以计算出两个地址间的元素个数,而指针加指针计算出的数据毫无意义。
int arr[10]={0};
printf("%d\n",&arr[0]-&arr[9]);//9
printf("%d\n",&arr[9]-&arr[0]);//-9
两个指针相减的前提是:指针指向的同一块连续的空间。
示例:求字符串长度:
法一:循环
int my_strlen(char*p)
{
int count = 0;
while(*p!='\0')
{
p++;
count++;
}
return count++;
}
法二:递归
int my_strlen(char*p)
{
if(*p!='\0')
{
return 1+ my_strlen(p+1);
}
else
{
return 0;
}
}
法三:指针相减
int int my_strlen(char*p)
{
char*p1=p;//记录初始位置
//遍历每一个元素
while(*p1!='\0')
{
p1++;
}
return p1-p;//指针相减
}
int a =10;
int*p=&a;
int**pp=&p;
*p=20;
**pp=30;
我们用一个指针记录了整型变量a的地址,根据前面的知识我们能够了解到,指针也是变量的一种,那么指针p也会占用4或8个字节的内存空间,那么我们将这个指针所占空间的地址记录在另外一个指针内,那么这个记录指针地址的指针就是二级指针。
顾名思义,指针数组的意思就是存放指针的数组。
int a = 1;
int b = 2;
int c = 3;
int* arr1[3] = {&a,&b,&c};//存放整型指针的数组
for (int i = 0; i < 3; i++)
{
printf("%d ", *(*(arr1 + i)));//方式1
printf("%d ", *(arr1[i]));//方式2
}
//创建四个一维数组
int arr1[]={1,2,3,4,5,6,7,8,9};
int arr2[]={1,2,3,4,5,6,7,8,9};
int arr3[]={1,2,3,4,5,6,7,8,9};
int arr4[]={1,2,3,4,5,6,7,8,9};
//模拟二维数组
int*arr[]={arr1,arr2,arr3,arr4};
我们用指针数组的方式模拟了一个二维数组,我们可以用画图的方式理解一下该模拟的二维数组的内部物理结构:
那么我们如何访问呢?
for (int i=0;i<4;i++)
{
for(int j=0;j<9;j++)
{
printf("%d ",arr[i][j]);
}
printf("\n");
}
我们执行上述代码:
完美地得到了我们心目中的效果,那么我相信大家一定会对一个地方感到困惑,就是为什么在访问这个数组中的元素时,采用的形式和真正的二维数组一模一样。在进行该处讲解时,我们先回顾一下数组和指针的内容:
int arr[12]={0};
int *p=arr;
*(p+1)=2;
arr[1]=2;
//p<-->arr
//p+1<-->arr+1<-->arr[1]<-->p[1]<-->1[p]<-->1[arr]
有了上述的等式关系,我们在来分析刚才的代码:
for(int i=0;i<4;i++)
{
*(arr+i);//访问指针数组中的指针元素
for(int j=0;j<9;j++)
{
*(*(arr+i)+j);//通过元素指针访问具体数组元素
}
}
本章在基本知识的基础上讲解了指针类型的意义,野指针以及指针的运算等知识。希望对大家有所帮助。