[C语言]详解指针合集

Posted ^jhao^

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[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]);
		}
	以上是关于[C语言]详解指针合集的主要内容,如果未能解决你的问题,请参考以下文章

C 语言行指针与列指针详解

C语言函数指针详解

C语言---指针变量详解2

函数指针及其定义和用法,C语言函数指针详解

C语言指针的问题

C++编程知识:什么是万能指针?详解C语言万能指针的妙用