IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> [C语言]详解指针合集 -> 正文阅读

[C++知识库][C语言]详解指针合集

文章篇幅较长,如有需要请先收藏???


一、指针是什么

指针是编程语言中的一个对象,利用地址,它的值将指向电脑存储器中另一个地方的值。并且可以通过地址能找到所需的变量单元,可以说,地址指向该变量单元。通过指针可以找到以它为地址的内存单元

二、指针常见的错误

1.未初始化就使用

代码如下(示例):

#include<stdio.h>
int main()
{
	int* p;//局部变量未初始化是随机值
	*p = 3;//这里访问了未初始化的空间报错
	return 0;
}

在这里插入图片描述

2.指针越界访问

代码如下(示例):

int main()
{
	int a[10] = { 0 };
	int* p = a;
	//这个地方 a表示数组名,数组名sizeof(a)和单独&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;
		//printf("%d\n", arr[i]);
	}
	return 0;
}

答案:死循环,(vs2019 x86)在这里插入图片描述
3.回归主题,这里回到指针
自行做一下,对一下答案
牢记:sizeof里面得到的结果是类型属性,不是值属性,是不会去计算的。
sizeof(数组名) - 数组名表示整个数组的 - 计算的是整个数组的大小
&数组名 – 数组名表示整个数组,取出的是整个数组的地址
此外的所有数组名都是数组的首元素大小

知道这些后,可以尝试写一下题,答案在后面的注释,超详细,这里地址以32位的计算啦

	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));//16
	printf("%d\n", sizeof(a + 0));//这里的a就是首元素地址,是地址就是4字节(32位)
	printf("%d\n", sizeof(*a));//这里的a也是首元素地址,1的地址被解引用找到1,int的大小就是4
	printf("%d\n", sizeof(a + 1));//4*这里的a也是首元素地址,这里是第二个元素的地址,地址的大小是多少就不用说了吧
	printf("%d\n", sizeof(a[1]));//4 --2的大小,那肯定是4个字节
	printf("%d\n", sizeof(&a));//4 --这里就是整个元素的地址,满足&数组名
	printf("%d\n", sizeof(*&a));//16 -- 这里就是整个数组解引用,拿到整个数组的元素
	printf("%d\n", sizeof(&a + 1));//4  --看下面那张图  &a + 1数组后面的空间的地址
	printf("%d\n", sizeof(&a[0]));//4   -- a[0]是第一个元素,&a[0]第一个元素的地址
	printf("%d\n", sizeof(&a[0] + 1));//4 --a[0]+1是第二个元素,&a[0] + 1第二个元素的地址
	

在这里插入图片描述
//strlen 的作用是从给的地址往后找\0,计算之前的字符个数

//字符数组
	char arr[] = { 'a','b','c','d','e','f' };//6个字符
	printf("%d\n", sizeof(arr));//6  --整个元素的地址单独放在sizeof内部,满足要求,计算整个数组的总大小
	printf("%d\n", sizeof(arr + 0));//4--arr为首元素地址,即第一个元素的地址,字符的地址也是4字节
	printf("%d\n", sizeof(*arr));//1-- 第一个元素'a'放在sizeof内部 
	printf("%d\n", sizeof(arr[1]));//1 --第二个元素 'b' 
	printf("%d\n", sizeof(&arr));//4--整个数组的地址 
	printf("%d\n", sizeof(&arr + 1));//4--下一个整个数组的地址 (类似上面那张图的情况)
	printf("%d\n", sizeof(&arr[0] + 1));//4--&arr[0]的指针类型是int*,加1跳过一个整形,‘b’的地址
	
	printf("%d\n", strlen(arr));//随机值 --无\0
	printf("%d\n", strlen(arr + 0));//同理
	//printf("%d\n", strlen(*arr));//'a' -- 出错 这里相当于strlen一个整数,错误的strlen的参数是(const char * str),这里把一个整数当成地址,代码出错 
	//printf("%d\n", strlen(arr[1]));//同理
	printf("%d\n", strlen(&arr));//随机值 ,整个数组的地址,值与第一个元素的地址相同,但意义不同,图如下
	printf("%d\n", strlen(&arr + 1));//&arr的类型为char(*)[6],随机值-6 ,整个数组的地址,图如下
	printf("%d\n", strlen(&arr[0] + 1));//&arr[0]的类型为char*,'b'的地址找到\0就停 ,值为:前面的随机值-1

在这里插入图片描述
在这里插入图片描述

	char arr[] = "abcdef";//这里是用常量区的"abcdef"初始化了arr,在栈区上开辟了一块空间
	printf("%d\n", sizeof(arr));//7  --算的是整个数组的大小加'\0'
	printf("%d\n", sizeof(arr + 0));//4 --算的是首元素地址的大小
	printf("%d\n", sizeof(*arr));//1 --arr首元素地址,这里计算'a'的大小
	printf("%d\n", sizeof(arr[1]));//1--‘b'的大小
	printf("%d\n", sizeof(&arr));//4--整个数组的地址的大小
	printf("%d\n", sizeof(&arr + 1));//4--f后面的地址,地址的大小
	printf("%d\n", sizeof(&arr[0] + 1));//地址的大小 4字节
	
	printf("%d\n", strlen(arr));//6 
	printf("%d\n", strlen(arr + 0));//6
	//printf("%d\n", strlen(*arr));//err
//	printf("%d\n", strlen(arr[1]));//err
	printf("%d\n", strlen(&arr));//6  指针有类型差异,结果和 strlen(arr)一样,上面解释过相似的题目
	printf("%d\n", strlen(&arr + 1));//随机值
	printf("%d\n", strlen(&arr[0] + 1));//5
char* p = "abcdef";//"abcdef"是放在常量区当中的,p是在栈区的,p的值指向a的首元素地址
	printf("%d\n", sizeof(p));//*  地址4字节,这里的p是存储这个常量字符串的首元素地址
	printf("%d\n", sizeof(p + 1));//p是char*类型的,加减跳过一个字符,'b'地址4字节 'b'的地址
	printf("%d\n", sizeof(*p));//1 p指向a,解引用拿到a
	printf("%d\n", sizeof(p[0]));//1 'a'的大小
	printf("%d\n", sizeof(&p));//4 * 首元素a的地址的地址
	printf("%d\n", sizeof(&p + 1));//4-- 地址的的大小,这里指向的是指针的指针后面--画图
	printf("%d\n", sizeof(&p[0] + 1));//4--地址的的大小
	
	printf("%d\n", strlen(p));//6
	printf("%d\n", strlen(p + 1));//5 ,p是char*,往后走一个字节为b的地址
	///printf("%d\n", strlen(*p));//err	
	//printf("%d\n", strlen(p[0]));//err
	printf("%d\n", strlen(&p));//随机值,指针的地址
	printf("%d\n", strlen(&p + 1));//随机值,指针的地址跳过char(*)[6],如下图
	printf("%d\n", strlen(&p[0] + 1));//随机值,指针的地址,**下图第二个**

在这里插入图片描述

p的地址加一跳过一个p的大小
在这里插入图片描述
看完这张图,让我们再来做做下面 的题

	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));//48
	printf("%d\n", sizeof(a[0][0]));//4
	printf("%d\n", sizeof(a[0]));//a[0]为第一行的数组名单独放在sizeof内部 * 16
	printf("%d\n", sizeof(a[0] + 1));//a[0]没有单独放在sizeof内部,所以是第一行第一个元素的地址,这答案是第一行第二个元素的地址 4
	printf("%d\n", sizeof(*(a[0] + 1)));//第一行第二个元素的大小  *4
	printf("%d\n", sizeof(a + 1)); // a这里没有放在sizeof内部,这里表示二维数组的首元素地址(二维数组的首元素是第一行数组的地址) 这里是第二行整体的地址   *4
	printf("%d\n", sizeof(*(a + 1)));//这里相当于a[1]单独放在sizeof内部,就是计算第二行所有元素的大小16
	printf("%d\n", sizeof(&a[0] + 1));//第二行的全部元素,&a[0] + 1就是第二行的地址 4
	printf("%d\n", sizeof(*(&a[0] + 1)));//第二行的全部元素,&a[0] + 1就是第二行的地址,解引用拿到整个元素  *16
	printf("%d\n", sizeof(*a));//16 第一行的全部元素
	printf("%d\n", sizeof(a[3]));//sizeof内部不会真的去计算,a[3]是数组名 16

这边解释一下sizeof,代码在下面

short s = 5;
	int a = 4;
	printf("%d\n", sizeof(s = a + 6));//这里的表达式就是不会真的去计算a+6的值,所以这里的值是2
	printf("%d\n", s);//5 不计算的话这里自然不会改变

三.动态内存管理

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在free之后仍然指向开辟的空间,但开辟的空间已经还回去了,所以我们要手动置成NULL;
	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在free之后仍然指向开辟的空间,但开辟的空间已经还回去了,所以我们要手动置成NULL;
	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在free之后仍然指向开辟的空间,但开辟的空间已经还回去了,所以我们要手动置成NULL;
	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);//这里就是对栈上空间free了,是错误的
	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;
//文件结果: zcdef.

最后介绍一下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;
	//答案:zhangsan 12
}

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;
}
//结果zhangsan,12

总结

c语言阶段的指针使用基本就此告一段落,有任何问题可以在评论区或私信,欢迎大家的讨论和批评,喜欢的不妨一键三连????

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-28 07:31:54  更:2021-07-28 07:32:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/29 9:36:54-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码