文章篇幅较长,如有需要请先收藏???
一、指针是什么
指针是编程语言中的一个对象,利用地址,它的值将指向电脑存储器中另一个地方的值。并且可以通过地址能找到所需的变量单元,可以说,地址指向该变量单元。通过指针可以找到以它为地址的内存单元
二、指针常见的错误
1.未初始化就使用
代码如下(示例):
#include<stdio.h>
int main()
{
int* p;
*p = 3;
return 0;
}

2.指针越界访问
代码如下(示例):
int main()
{
int a[10] = { 0 };
int* p = a;
for (int i = 0; i <= 10; i++)
{
*p = 1;
p++;
}
return 0;
}
这里注意了,指针越界了其实不会造成影响,但是这里修改了指针指向内容的值会报错
3.有趣的代码+习题
(内含对栈区的一些理解) 猜猜代码是在哪出错 1.
#include<stdio.h>
int* test()
{
int a = 10;
return &a;
}
int main()
{
int* p = test();
*p = 20;
return 0;
}

所以这里当然在main的时候p就已经是一块随机的空间,可能已经被分配给其他使用,这里在win测试的,这个内存图实际上没多大意义,vs2019,x64中验证的,vs编译器本身为了安全,会对地址进程重新分配,但是要记住以下几点:1.栈区内存的使用习惯:先使用高地址空间,在使用低地址空间;2.数组随着下标的增长地址是由低到高变化的(Linux标准的,也是编译器没有经过任何加工的布局,下道题我们用x86验证,就可以模拟上述规则了), 指针的加减与指针的类型有关,加减1次走过指针的类型大小 void*是不能解引用和进行加减的
2.刚提了一嘴这个类型的题目,那接下来再看一道 来猜猜看结果会如何
#include <stdio.h>
int main()
{
int i = 0;
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
for (i = 0; i <= 12; i++)
{
arr[i] = 0;
}
return 0;
}
答案:死循环,(vs2019 x86) 3.回归主题,这里回到指针 自行做一下,对一下答案 牢记:sizeof里面得到的结果是类型属性,不是值属性,是不会去计算的。 sizeof(数组名) - 数组名表示整个数组的 - 计算的是整个数组的大小 &数组名 – 数组名表示整个数组,取出的是整个数组的地址 此外的所有数组名都是数组的首元素大小 知道这些后,可以尝试写一下题,答案在后面的注释,超详细,这里地址以32位的计算啦
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(a + 0));
printf("%d\n", sizeof(*a));
printf("%d\n", sizeof(a + 1));
printf("%d\n", sizeof(a[1]));
printf("%d\n", sizeof(&a));
printf("%d\n", sizeof(*&a));
printf("%d\n", sizeof(&a + 1));
printf("%d\n", sizeof(&a[0]));
printf("%d\n", sizeof(&a[0] + 1));
 //strlen 的作用是从给的地址往后找\0,计算之前的字符个数
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr + 0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr + 1));
printf("%d\n", sizeof(&arr[0] + 1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr + 0));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr + 1));
printf("%d\n", strlen(&arr[0] + 1));
 
char arr[] = "abcdef";,
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr + 0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr + 1));
printf("%d\n", sizeof(&arr[0] + 1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr + 0));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr + 1));
printf("%d\n", strlen(&arr[0] + 1));
char* p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p + 1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p + 1));
printf("%d\n", sizeof(&p[0] + 1));
printf("%d\n", strlen(p));
printf("%d\n", strlen(p + 1));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p + 1));
printf("%d\n", strlen(&p[0] + 1));

p的地址加一跳过一个p的大小  看完这张图,让我们再来做做下面 的题
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(a[0][0]));
printf("%d\n", sizeof(a[0]));
printf("%d\n", sizeof(a[0] + 1));
printf("%d\n", sizeof(*(a[0] + 1)));
printf("%d\n", sizeof(a + 1));
printf("%d\n", sizeof(*(a + 1)));
printf("%d\n", sizeof(&a[0] + 1));
printf("%d\n", sizeof(*(&a[0] + 1)));
printf("%d\n", sizeof(*a));
printf("%d\n", sizeof(a[3]));
这边解释一下sizeof,代码在下面
short s = 5;
int a = 4;
printf("%d\n", sizeof(s = a + 6));
printf("%d\n", s);
三.动态内存管理
1.malloc,free,calloc,realloc的基本使用
2.malloc的使用
 翻译出来就是在内存当中申请一块连续可用的空间,返回这块空间的指针 *如果开辟成功,则返回一个指向开辟好空间的指针。 *如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。 返回值的类型是 void ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器 👀这里我们写一个代码看看基本使用
int main()
{
int* ptr = (int*)malloc(sizeof(int) * 4);
if (ptr == NULL)
{
return 1;
}
else
{
for (int i = 0; i < 4; i++)
{
ptr[i] = i;
printf("%d ", ptr[i]);
}
}
free(ptr);
ptr = NULL;
return 0;
}
在这里说明一下在NULL在c语言是定义成地址为0,在c++则是一个整数0 
2.calloc的使用
calloc 
这里可以看出参数size_t num为我们要的元素数量,size_t size为每个元素的大小,我们也写一段简单的代码
int main()
{
int* ptr = (int*)malloc(sizeof(int) * 10);
if (ptr == NULL)
{
return 1;
}
else
{
for (int i = 0; i < 4; i++)
{
ptr[i] = i;
printf("%d ", ptr[i]);
}
}
free(ptr);
ptr=NULL;
return 0;
}
calloc开辟出来的空间也是在堆区上,同然使用完之后要对内存进行释放,同理也要判断是否能开出了来,并置成NULL 👀这里给大家写一个开不出来的情况  这里我在开2g内存的时候ptr返回NULL,如果这里我们没有对返回值做判断,使用的话就会造成错误,当然,想要开出2g或4g空间,只需要把x86改成64位就可以开辟出来了。
3.realloc
realloc函数的出现让动态内存管理更加灵活。 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存, 我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整  其中参数ptr是要调整的内存地址,size为调整之后的大小,注意,realloc有两种情况 第一种:原空间后面有足够大的空间 第二种:原空间后面没有足够大的空间 第一种: 第二种: 👀👀👀当然,如果realloc的内存过大开不了也是会返回NULL,所以我们依旧要对返回值进行处理,以下写一个常用的用法
#include<stdlib.h>
#include<math.h>
int main()
{
int* ptr = (int*)malloc(sizeof(int)* 4);
if (ptr == NULL)
{
return 1;
}
else
{
for (int i = 0; i < 4; i++)
{
ptr[i] = i;
printf("%d ", ptr[i]);
}
}
int* tmp = realloc(ptr, sizeof(int) * 10);
if (tmp == NULL)
{
printf("开辟空间失败\n");
return 1;
}
else
{
ptr = tmp;
}
free(ptr);
ptr=NULL;
return 0;
}
当然,还有一个小tip,就是把ptr写成NULL,可以当成malloc使用 
3.常见的动态内存泄露
1.对NULL指针的解引用

2.对动态开辟的内存进行越界访问
#include< LIMITS.H >
int main()
{
int* ptr = (int*)malloc(sizeof(int) * 4);
if (ptr == NULL)
{
return 1;
}
else
{
for (int i = 0; i <= 10; i++)
{
ptr[i] = i;
printf("%d ", ptr[i]);
}
}
return 0;
}
3.对非动态开辟的内存使用free
#include< LIMITS.H >
int main()
{
int a[10] = { 0 };
free(a);
return 0;
}
4.对同一块动态内存多次释放
#include<stdlib.h>
#include< LIMITS.H >
int main()
{
int* p = (int*)malloc(sizeof(int));
free(p);
free(p);
return 0;
}
当然我们只要养成free完手动置成NULL就没问题 对NULL进行释放是没有问题的
5.动态开辟内存未释放
这个就是我们常见的内存泄露了,一定记得释放!!!
#include<stdlib.h>
#include< LIMITS.H >
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
}
int main()
{
test();
}
尤其是这种在局部作用域开辟的空间,如果没有释放甚至连指针都已经找不到了,所以在开辟内存的时候我们要小心再小心
文件指针
每个被使用的文件在内存当中都开辟了一个相应的文件信息区,用来存放文件的相关信息,以下是VS2008编译环境提供的stdio.h头文件中有以下的文件类型申明
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
FILE* pf;
不同编译器都大同小异,知道了这个我们要操作文件只需要FILE* pf(文件指针)来操作就可以找到和他关联的文件啦; 当然,文件的读写之前应该先做什么么,首先肯定要打开文件,使用完之后也要关闭文件!! 因为打开文件的数量是有限的,打开了 不关闭就会造成资源的浪费,接下来写一段简单的代码说明一下使用
#include<stdlib.h>
#include< LIMITS.H >
int main()
{
FILE* pf = fopen("test.dat", "w");
if (pf == NULL)
{
printf("打开失败\n");
return 1;
}
else
{
fputs("hello world", pf);
fclose(pf);
}
return 0;
}
注意:文件若不存在,以“w”(写的)方式则会创建该文件,在进行写入,若文件存在,则会把文件格式化,再重新写入内容,当然如果是一次打开关闭中重复使用fputs是可以向后写的,如果想在原有内容追加,请使用“a”  常见的读写函数 fseek定义文件指针的位置和偏移量,使用 其中的origin
#include<stdlib.h>
#include< LIMITS.H >
int main()
{
FILE* pFile;
pFile = fopen("example.txt", "wb");
fputs("abcdef.", pFile);
fseek(pFile, 0, SEEK_SET);
fputs(" z" ,pFile);
fclose(pFile);
return 0;
最后介绍一下sscanf和sprintf  sscanf就是从s这个字符数组当中读取信息给后面的format
#include <stdio.h>
typedef struct Peo
{
char name[20];
int age;
}Peo;
int main()
{
Peo peo = { 0 };
char str[20]="zhangsan 12";
sscanf(str, "%s %*s %d", peo.name, &(peo.age));
printf("%s",str);
return 0;
}
sprintf则相反
#include <stdio.h>
typedef struct Peo
{
char name[20];
int age;
}Peo;
int main()
{
Peo peo = {"zhangsan",12 };
char str[20] = {0};
sprintf(str, "%s,%d", peo.name, peo.age);
printf("%s",str);
return 0;
}
总结
c语言阶段的指针使用基本就此告一段落,有任何问题可以在评论区或私信,欢迎大家的讨论和批评,喜欢的不妨一键三连????
|