360°网站标签旋转显示特效,文山网站建设兼职,网站基本信息设置,上海网站建设免第七章#xff0c;指针的学习 目录
前言
一、指针的概念
二、指针的类型
三、野指针
四、指针的运算
五、指针和数组的关系以及使用
六、指针数组
七、二级指针
总结 前言
这章主要学习的是指针方面的知识#xff0c;这节只是简单了解一下指针#xff0c;并不会深…第七章指针的学习 目录
前言
一、指针的概念
二、指针的类型
三、野指针
四、指针的运算
五、指针和数组的关系以及使用
六、指针数组
七、二级指针
总结 前言
这章主要学习的是指针方面的知识这节只是简单了解一下指针并不会深入的学习如果要学习指针更深的知识点会在后面指针详解这章去讲。我们这章主要从以下方面学习指针的概念、指针类型、野指针、指针的简单运算、指针和数组、指针数组、以及二级指针。 一、指针的概念
指针就是一个变量用来存放其他变量在内存中的地址。地址是内存中最小单元的编号最小单元的地址单位为字节byte。指针实际就是地址。
内存中的地址的大概模型 上面代码中变量p就是一个指针变量a就是取出变量a的地址然后将a的地址赋值给p这样变量p存放的就是a的地址%p就是以地址的形式打印。
下面我们来了解一下计算机是如何对内存进行编址
计算机分为32位机器和64位机器现在一般的电脑都是64位。这32位和64位都表示有多少根地址线每根地址线的状态分为两种0、1。在32位机器上地址编址的范围中最小值和最大值分别为32根地址线同时为0和同时为1单位为字节。在32位机器上我们就可以给 2^32Byte 2^32/1024KB 2^32/1024/1024MB2^32/1024/1024/1024GB 4GB 4G的空闲进行编址那么编址的范围就是0-4GB64位机器的编址范围跟32位机器一样的。
指针变量开辟的空间大小是多少呢通过上面对计算机内存编址进行了解之后我们知道32位机器的内存地址的编号就是由32个0或1组成的64位机器的内存编号就是64个0或1组成的。指针变量就是用来存放这些地址编号所以计算机只需要开辟对应的大小就行了开辟大了就会造成内存浪费。结论分为两种情况32位机器上的指针大小位32bit4Byte64位机器上的指针大小为64bit8Byte。
二、指针的类型
上面我们对指针的概念有一定的了解之后下面我们就来学习一下指针的类型。
有人可能想问指针的大小不就是固定的4字节或者8字节吗那还需要什么类型来限定指针。有这个想法的铁子们请继续往下看你的答案就在下面。
我们先看一下代码指针的大小会不会随着指针的类型不同而发生变化代码是基于64位操作平台运行。
int main()
{int a 20;double b 13.23;int* p a;double* q b;printf(%d\n,sizeof(p)); //8printf(%d\n, sizeof(q)); //8return 0;
}
不管是double类型的指针还是int类型的指针他们的大小都是8字节。因为不管是什么样类型的指针都是用来存放内存地址的编号内存地址的编号始终都是由64位0或1组成所以存放他们只需要8字节大小的空间。
既然指针的大小一直为8字节那不同类型的指针有什么区别请看下面的代码。 int a 10;float b 10.12f;int* p a;float* q b;printf(a %p\n,a);printf(p %p\n,p);printf(p1 %p\n, p1);printf(b %p\n, b);printf(q %p\n, q);printf(q1 %p\n, q 1); 上面的代码执行的结果是否跟铁子们想的结果一样呢如果一样的话说明铁子已经对不同类型的指针有所了解了。如果不一样的话我就画图给铁子们讲讲讲完之后就会明白为什么要去定义不同的数据类型指针。
在画图之前先给铁子们讲一下不同数据类型指针的意义。
比如int *p a;这行代码表示定义了一个指针变量p指针变量存放的是int数据类型变量a的地址。
double *q b;这行代码表示定义了一个指针变量q指针变量存放的是double数据类型变量b的地址。 图中的内存地址与代码运行结果不一样因为我画图只是举例说明而且代码每次运行的结果都会发生变化。
p1实质是p指向地址加了4个字节为什么加1就加了4个字节这是因为指针指向的数据类型导致的int数据类型的大小为4所以指向int数据类型的指针加1就会向后加4个字节。q1也是一样的操作float数据类型的大小也为4。
结论就是指针的数据类型决定指针1或者-1指针向前或者向后移动几个字节的距离。
到这里上面的问题的答案就出来了。所以铁子们知道为什么要定义指针数据类型了吧。
还有一种方式可以证明指针数据类型的用处那就是指针解引用操作‘*’前面章节已经对C语言操作符学习了一遍如果没有看过前面的章节可以去看看。下面我们就来看看解引用操作符是如何让指针数据类型变得有意义的。 int n 0x11223344;char* pc (char*)n;int* pi n;*pc 0;*pi 0;
第一步首先定义一个int类型的变量a然后a的值为0x11223344因为在内存中是以十六进制显示的所以用十六进制给a赋值更容易在内存中看出来。a在内存中的地址为0x00000009FC1BF544。 第二步定义了一个char类型的指针变量pc当我执行*pc 0的时候内存中a的低8位却被改为了0a此时在内存中的值为0x11223300。 第三步定义一个int类型的指针变量pi当执行*pi 0的时候内存中a的32位全部为0。a此时在内存中的值为0x00000000。 通过上面的代码我们又发现了一个结论我们在对指针变量解引用的时候能操作的字节大小取决于指针的数据类型大小。
所以指针变量的类型是很有必要的因为它可以决定我们在进行加减整数的时候移动多大的距离或者在解引用的时候我们能操作多大的空间。
三、野指针
我们学会了指针简单的操作现在还看不出来指针有多大的用处等后面学的更深入了就知道指针的强大之处了但先在这里提醒铁子们后期使用指针的时候需要注意的点。指针的唯一的坏处就是用得不恰当就会出现野指针下面就讲一些野指针是如何造成的以及如何避免野指针。
野指针的概念指针指向的位置是不可知的不知道指向什么位置这个位置具有随机性、不正确的。
存在野指针的几个点
1、指针未初始化。 这是因为定义一个局部变量时如果不进行初始化系统会默认给它赋一个随机值我们也不知道系统分配的这个地址是否能正常访问如果不能正常访问然后我们对它进行解引用操作编译器肯定会报错。
2、指针越界访问。 运行这段代码的时候编译器会报错因为arr数组只有5个元素下标从0到4而代码却去访问了下标为5的元素下标为5的元素的地址就不是系统分配给我们的地址我们却去强行访问它。
3、指针指向空间释放。 malloc函数就是动态在堆区开辟空间free就是释放在堆区开辟的空间。当执行到free(pc)的时候pc指向的那段空间就被系统回收了被拿去给其他程序用了然后又让q指向被释放的那块空间这种情况肯定不允许的也会造成野指针。
既然在用指针的时候会出现这么多野指针那我们如何避免呢
避免野指针的方式有以下几种
1、定义指针时初始化
int *pi NULL;//如果不清楚指针最开始指向的地址就开始先让它指向一个NULL地址。
2、防止指针越界访问 int arr[5] { 0 };int* p arr;for (int i0;i5;i) {*(p i) i; //访问数组时不要超过数组下标的最大值}
3、指针指向空间释放及时置NULL char a A;char* q a;char* pc (char *)malloc(sizeof(char));free(pc);pc NULL; //释放之后要及时置NULL防止后面的指针指向pc
4、避免返回局部变量地址。
int* add(int a ,int b)
{int ret ab;return ret; //error
}int main()
{int *p add();*p 20;
}
为什么不能返回局部变量地址因为局部变量的生命周期就是在add函数内add函数执行完之后系统就会释放变量ret的地址然而把释放掉的地址返回到main函数中然后指针变量p又去接收被系统释放的地址最后对释放的地址进行解引用操作这种操作肯定是不允许的。所以我们在使用函数的时候不要返回局部变量的地址。
5、指针使用之前检查有效性。 int* p NULL;int a 50;p a;if (p ! NULL) //在使用指针之前先判断这个指针变量是否为NULL{*p 100;}
四、指针的运算
指针的运算顾名思义就是对指针变量进行运算不是所以运算指针变量都支持的指针变量只支持下面几种运算。
1、指针-整数
只要掌握了前面的指针类型的作用之后这里原理就一清二楚了。 //指针-整数float arr[10] { 0 };float* fp arr;for (int i0;i10;i){*(fp) i 0.12;}for (int i 0; i 10; i){printf(%f\n,arr[i]);} 2、指针-指针
char arr[] hello world;
char* s arr;
while (*s ! \0)
{s;
}
printf(%d\n,s-arr); 指针-指针返回的是一个数字并不是地址。比如0x00001122 - 0x00001113 15。如果是高地址减低地址则为一个正整数低地址减高地址则为一个负整数。这种情况通常用于数组中求数组中元素的个数。但多数是用我们前面章节所讲的sizeof(arr)/sizeof(arr[0])来求数组元素个数。
3、指针的关系运算 //指针的关系运算int arr[5] { 1,2,3,4,5 };int* p NULL;
//这个for循环是正确的写法
for (parr[5];parr[0];)
{*--p 0;
}//这种for循环的写法能正常运行for (p arr[4]; p arr[0]; p--){*p 0;}for (int i 0;i 5;i){printf(%d\n,arr[i]);} 代码中能正常运行的那个for循环只是在大部分编译器上可以运行成功但是C语言的标准并不支持这种写法。
C语言的标准规定允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。
建议如果要进行指针关系运算铁子们还是用第一种for循环的方式写。因为这种写法是C语言标准规定的移植性更好。
五、指针和数组的关系以及使用
在之前的章节我们学习了数组的使用现在就来看看指针与数组的联系以及如何配合使用。
我们先来复习一下数组的使用
int main()
{//数组int arr[5] { 1,2,3,4,5 };//访问数组的方式一for (int i 0;i 5;i){printf(%d ,arr[i]);}printf(\n);//访问数组的方式二for (int i 0;i 5;i){printf(%d ,*(arri));}return 0;
} 访问数组的第一种方式其实就是通过数组下标访问操作符进行访问的。而第二种方式使用的是指针的方式去访问这里为什么指针也能访问数组呢等我们学完数组与指针的关系后铁子们就知道为什么能这样写了。
我们先来看一下数组名表示的是什么。 int arr[5] { 1,2,3,4,5 };printf(%p\n,arr); //数组名printf(%p\n,arr[0]); //第1个元素的地址 从上面代码的执行结果来看arr和arr[0]是同一个地址这里就可以知道其实数组名就是数组首元素地址但是有两个例外的情况不是表示首元素地址1、当数组名放在sizeof()操作符中时arr就不表示首元素地址而表示的是整个数组sizeof(arr)求的是整个数组的大小。2、取数组名地址的时候arr也不表示首元素地址arr求的整个数组的地址。如果要详细了解数组的知识就去这篇博客学习数组的定义以及使用的详细教程一维数组、二维数组等等-CSDN博客。
既然数组名表示首元素地址那我们是不是就可以用一个指针变量去存储首元素地址呢。 现在指针指向数组首元素地址并且指针的类型是int我们前面也学习了指针-整数那么我们是不是就可以通过指针-整数去访问数组了呢 int arr[5] { 1,2,3,4,5 };int* p arr;for (int i 0;i 5;i){printf(arr[%d] %p ---- p%d %p\n, i,arr[i],i,pi);} 从上面代码的结果来看指针pi的指向的地址就是数组下标为i的地址。那我们再对指针指向的地址解引用不就是访问到数组的元素了吗。
所以讲到这里铁子们现在知道最开始代码中的第二种访问方式的原因了吧。
我们为什么要用指针变量去访问数组元素呢不能直接用数组名访问数组吗下面通过代码来看看有些地方指针可以使用但是数组名就不行。 代码中的两中方式明明都是指向首元素地址为什么p就可以而arr就报错呢这个问题的答案详细讲解在这里C语言的探索之数组名自增自减-CSDN博客。铁子们可以去了解一下。
六、指针数组
我们学完了指针和数组现在怎么又来个指针数组这个玩意哈哈哈哈是不是这些名词绕来绕去后面还会学习数组指针等等。只要了解深入之后这些再怎么饶都是那些东西。现在我们就先来学习一下指针数组。
那指针数组到底是指针还是数组呢如果不清楚没关系。那字符数组、整型数组这些是字符还是数组这些铁子们肯定知道是数组吧我们所有的字符数组并不是表示字符而是表示数组只不过数组里面的元素类型为字符类型。其实指针数组与字符数组的理解一样首先指针数组是一个数组然后数组里面存放的元素类型为指针类型。我们了解这个概念之后就来具体实现一下。
char arr[5]; //字符数组
char* arr2[5]; //指针数组
这样铁子们不是很理解里面的原理那就通过画图更容易理解。 从画图中可以看出来char arr[5]中的每个元素的类型为char而char* arr2[5]中的每个元素为char*。
我们下面就简单学习一下定义和使用后面的章节会出详细教程。 //字符数组和指针数组的定义char arr[5] { a,b,c,d,e };char* arr2[5] {hello, world, nihao, luck, strive};//字符数组for (int i 0;i 5;i){printf(%c ,arr[i]);}printf(\n);//指针数组的使用for (int i 0;i 5;i){printf(%s ,arr2[i]);} 为什么指针数组打印的时候要用%s的形式因为指针数组存放的是常量字符串的首元素地址所以想要打印字符串则需要用%s。
七、二级指针
二级指针又是什么呢其实本质还是一个指针只不过比我们上面讲的指针高级一点点上面所讲的指针是一级指针口头说的时候把一级给去掉了。
二级指针的作用呢我们定义一级指针的时候是不是也要在内存中开辟一个8字节大小的空间来存储变量的地址编号既然指针变量也会在内存中开辟空间是不是也有地址编号那这个地址编号用什么来存储呢那就是用我们此时此刻学习的二级指针变量来存储一级指针变量的地址那二级指针变量的地址编号就要用三级指针来存储。其实在C语言中最多就使用二级指针就够了很少情况会用到三级指针。
int a 30;
int* p a;
int** pp p; //等价于 *pp a 等价于 p aprintf(a %d\n,a);
printf(*p %d\n, *p);
printf(**pp %d\n, **pp); 这里我们只要了解解引用操作符的使用就知道代码的结果为什么是这个。
*p表示访问指针p指向内存地址的内容。
*pp表示访问指针pp指向内存地址的内容这个内容就是指针变量p的地址。
**pp表示访问指针变量p指向内存地址的内容。
可能光这样看代码不是很好理解他们之间的关系那下面就给我亲爱的铁子们画图理解一下。 如果我们要通过pp二级指针去更改变量a的内容改如何操作呢 所以二级指针还是蛮简单的只要知道二级指针是用来存放一级指针的地址以及他们之间的关系这样就不会觉得二级指针很难了。 总结
本章主要学习了指针相关的知识本章重点就是理解指针的类型因为指针的类型决定了访问权限以及移动的距离其次就是指针和数组之间相互使用的方法。我们要了解在写指针的时候如何避免出现野指针。本章只略提了一下指针数组后面的章节中会详细讲解。
谢谢大家的支持~
铁子们一起加油努力~
如果阅读中发现问题请我亲爱的铁子们一定要指出来因为你的意见对我很重要也是我继续写下去的动力。感谢铁子们。