既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
| 第二字节 | 0x0012FF41 |
| 第一字节 | 0x0012FF40(指针变量存储) |
| . . . | . . . |
| 一个字节 | 0x00000002 |
| 一个字节 | 0x00000001 |
| 一个字节 | 0x00000000 |
4.存储编址原理:
经过上面实例的演示和讲解,我们就能理解内存中的存储与指针的使用原理了,我们也知道了指针变量就是用来存储地址的变量(存储在指针变量中的值都将被作为地址进行处理)。那么在内存中我们的计算机到底是如何进行编址的呢?
对于32位的机器,我们可以假设它有32根地址线,那么假设每根地址线在进行寻址时,都产生高电平(高电压)与低电平(低电压),则每一位上都将是对应的1或0,这也就是计算机内存进行数据存储采用二进制的原理了。且此时32根地址线产生的地址就将是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
00000000 00000000 00000000 00000010
… … … …
11111111 11111111 11111111 11111111
共32位,且每一位上均为1或0两种可能,则在内存中将存在2^32个地址。
同时我们也都知道8比特位为1个字节,而每个地址32位又恰好是四个字节,即一个整型变量的大小。并且我们在第三课中讲解数据类型时为各位小伙伴们介绍过,整形int(分正负,有符号位)的取值范围是-2147483648到2147483647,而这32位除去最前面的一位最作为符号位外共有31位,每一位上有1或0两种可能,则共可以表示2^31个数字即共2147483648个,又恰好对应了0到2147483647的取值范围,负值部分同样如此,对应了-1到-2147483648的2147483648个数字。它们相互印证,不谋而合。
每一个地址标识一个字节,则我们就可以给2^32Byte即4GB大小的空间进行编址:
2^32Byte = 2^32/1024KB = 232/10242MB = 232/10243GB = 4GB
则使用同样的编码方式,在64位的机器上,有64根地址线,就可以对的空间进行编址。
到这里我们就明白:
★在32位机器上,地址为32个0或1组成的二进制序列,则地址就需要32比特位即4个字节的空间来进行存储。即在32位机器中一个指针变量的大小为4个字节
★在64位机器上,地址为64个0或1组成的二进制序列,则地址就需要64比特位即8个字节的空间来进行存储。即在64位机器中一个指针变量的大小为8个字节
而由这个原理所得出的结论也与我们之前所学习的相关知识相一致。
总结:
·指针变量是用来存放地址的,地址是唯一标示一个内存单元的。
· 指针变量的大小在32位平台是4个字节,在64位平台是8个字节。
二、指针和指针类型:
我们都知道变量有许多中不同的类型,例如整形、浮点型等等,那么指针有没有类型之分呢?准确的来说:有的。
不同类型的指针变量的定义方式相同,均为 type + * :
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
char* pc = NULL;
int* pi = NULL;
short* ps = NULL;
long* pl = NULL;
float* pf = NULL;
double* pd = NULL;
return 0;
}
即在一般情况下,不同类型的指针变量用于存放不同类型数据的地址,但不管何种类型的指针变量内部,存储的都是数据的地址。那么既然都是用来存放地址的,这么多不同种类的指针类型存在的意义是什么呢?
其实不同类型的指针存在的意义主要是以下两点:
1.决定了指针的步长:
我们来看下面这段代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int n = 10;
int* pi = &n;
char* pc = (char*)&n;
//强制类型转换,n原本为int类型
printf("int型 n 的存储地址为:%p\n", &n);
printf("\n");
printf(" pi 指向的存储地址为:%p\n", pi);
printf("pi+1指向的存储地址为:%p\n", pi + 1);
printf("\n");
printf(" pc 指向的存储地址为:%p\n", pc);
printf("pc+1指向的存储地址为:%p\n", pc + 1);
return 0;
}
在这段代码中,我们看到,整型指针 pi 和经过强制类型转换后的字符型指针 pc 都获取并存储了变量 n 的存储地址,在这一点上没有区别。但是我们还看到,将 pi 与 pc 分别+1后,即分别让两个指针各向后“走一步”时,它们所跳过的内存空间不同,整型指针跳过了四个字节,而字符型指针只跳过了一个字节的空间:
这就是不同指针类型的第一个意义:决定了指针的步长。
2.决定了对指针进行解引用时的权限大小:
同样的我们结合代码实例来进行研究:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int n = 0x11223344;
int m = 0x11223344;
//此处不是书写错误,开头的0x表示十六进制整型
//int类型每次访问四个字节
int* pi = &n;
char* pc = (char*)&m;
*pi = 0;
//使用解引用操作符修改指针pi内存放地址对应地址内的数据
printf("变量 n 的值为:%d\n", n);
*pc = 0;
//使用解引用操作符修改指针pc内存放地址对应地址内的数据
printf("变量 m 的值为:%d\n", m);
return 0;
}
在这段代码中,指针 pi 为整型指针变量,pc 为字符型指针变量,变量n与变量m中存储的数据完全相同,均为十六进制的11223344,转换为十进制为287454020,接着我们让整型指针pi获取并存存储变量n的地址,让字符型指针获取并存储变量m的地址,然后我们通过使用解引用操作符,来修改不同类型指针变量 pi 与 pc 中存储的地址所对应地址内存储的数据。最后将被修改过的变量n与变量m的值打印出来进行反馈。我们观察到,n的值被修改为0,而m则被修改为了287453952(十六进制的11223300),即整个int类型的四个字节中仅有一个字节44被修改为了00。所以我们就知道,不同的指针类型决定了在进行一次操作时所能操作的空间大小(字节数):
这就是不同指针类型的另一个意义:决定了对指针进行解引用时的权限大小。
三、野指针:
顾名思义,野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。
1.野指针的成因:
①.指针未初始化:
指针未经初始化极易引发错误:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int* p;
//局部变量未初始化,默认为随机值
*p = 20;
return 0;
}
②.指针越界访问:
当我们使用数组时,若未仔细考量在数组使用中的指针指向问题,很有可能会导致指针越界的访问:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int arr[5] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i <= 5; i++)
{
*(p + i) = i;
//当指针指向的范围超出数组啊让人的范围时,p将变为野指针
}
return 0;
}
③.指针指向的空间被释放:
这也是一类常见的野指针成因,由于牵扯到部分我们没有接触到过的知识,过这里仅做了解,将会在学习动态内存开辟时进行讲解。
2.如何规避野指针:
我们都知道野指针危害巨大,会对我们的程序造成巨大的负面影响,那么我们在便携性程序时就应当多多注意,尽可能地避免要求指针的出,我们主要从以下五个方面入手:
①.使用前及时将指针初始化
②.使用数组时,时刻小心指针越界访问
③.指针指向空间被释放,应及时置空
④.避免返回局部变量的地址
⑤.指针使用之前应当检查其有效性
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int* p1 = NULL;
int* p2 = NULL;
//及时进行初始化
int a = 10;
int arr[5] = { 0 };
p1 = &a;
p2 = arr;
int i = 0;
for (i = 0; i < 5; i++)
//使用数组时,时刻小心指针越界访问
{
*(p2++) = i;
printf("数组元素 arr[%d] 的值为:%d\n", i, arr[i]);
}
if (p1 != NULL)
//指针使用前检查其有效性
{
*p1 = 20;
}
printf("变量 a 的值为:%d\n", a);
return 0;
}
当每一个细节都注意到后,我们的程序成功且正确运行的可能性就大了许多:
四、指针运算:
指针的运算主要可以分为三类:指针+/-整数、指针-指针、指针的关系运算。
1.指针+/-整数:
我们再来看下面这一段代码:
#define _CRT_SECURE_NO_WARNINGS 1
#define N_VALUES 5
#include<stdio.h>
int main()
{
float values[N_VALUES];
float* vp;
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
//++的优先级高于*,且为后置++
//这里的起到的作用即为将每个数组元素均置0
}
int i = 0;
for (i = 0; i < N_VALUES; i++)
{
printf("数组元素 values[%d] 的值为:%d\n", i, values[i]);
}
return 0;
}
在这段代码中,置零功能的每次循环中的***vp++,即表示在每次使用并执行后**,使指针vp继续向后按照float类型的步长指向一步:
即指针+/-整数表示:该指针按照当前指针类型所对应的步长,向后/前指向一(或多)步。
2.指针-指针:
指针-指针有一个前提要求:两只真均指向同一块内存空间。
有了这个前提,我们来看代码:
#define _CRT_SECURE_NO_WARNINGS 1
#define N_VALUES 5
#include<stdio.h>
int main()
{
int arr[N_VALUES] = { 0 };
int* p1 = &arr[0];
int* p2 = &arr[4];
//两指针指向同一块内存空间
printf("指针p2 - 指针p1的值为:%d", p2 - p1);
return 0;
}
在这段代码中我们使指针p1与指针p2均指向了数组arr在内存中所占的空间,只不过指针p1指向数组arr的首元素地址,而p2则指向了数组arr的尾元素地址,并将两指针相减后的结果打印反馈给了我们:
即指针-指针表示:得到的是两指针间的数据元素个数。
3.指针的关系运算:
通过指针的关系运算,我们可以将我们的代码进行简化:
#define _CRT_SECURE_NO_WARNINGS 1
#define N_VALUES 5
#include<stdio.h>
int main()
{
float values[N_VALUES];
float* vp;
for (vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
//通过指针的关系运算,可以将我们的代码简化为:
for (vp = &values[N_VALUES - 1]; vp > &values[0]; vp--)
{
*vp = 0;
}
return 0;
}
上述这段代码中两个循环语句的不同不仅仅在于指针进行减一操作的书写位置不同,更导致造成了需要对指针vp进行关系运算。
而实际上,这段代码在绝大部分编译器上都是可以顺利完成任务的,但我们在书写代码时还是应当尽可能的避免这样去书写,因为标准并不保证其可行:
★标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
五、指针和数组:
为了研究指针与数组间的关系,我们来看一个例子:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int arr[5] = { 1,2,3,4,5, };
int* p1 = &arr;
int* p2 = &arr[0];
printf(" arr 指向的地址为:%p\n", arr);
printf("&arr[0] 指向的地址为:%p\n", &arr[0]);
printf("\n");
printf("指针 p1 指向的地址为:%p\n", p1);
printf("指针 p2 指向的地址为:%p\n", p2);
return 0;
}
运行结果显示,数组名与数组首元素地址是一样的,即数组名表示的就是数组首元素的地址(有两种特殊情况,在数组章节已经讲解过了):
而既然可以把数组名作为地址存放到一个指针当中,那我们使用指针来访问数组元素就成为了可能:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int arr[5] = { 1,2,3,4,5 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (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 的地址。那么我们就可以直接通过指针来访问数组:
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
);
}
return 0;
}
运行结果验证了**我们的想法是正确可行**的:
![](https://i-blog.csdnimg.cn/blog_migrate/c461d21a8708e88c54ef8ab7e413f0f1.png)
所以上述示例中的 **p+i** 就**计算的是数组arr下标为 i 的地址**。那么我们就可以**直接通过指针来访问数组**:
[外链图片转存中...(img-8cvECbEK-1715716582079)]
[外链图片转存中...(img-Qv0Jy0s9-1715716582079)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**