【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!)

这篇具有很好参考价值的文章主要介绍了【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!),c语言,数据结构,开发语言

初阶指针

一、指针的概念

  • 指针??是??
  1. 指针是内存中一个最小单元的编号,也就是地址
  2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量

所以说:指针就是地址,人们口中的指针变量也是指针。

  • 指针变量??是??

我们可以通过&(取地址操作符)取出变量的内存与实地址,把地址可以存放到一个变量中,这个变量就是指针变量。

int main()
{
	int c = 520;    //将520赋给c
	int* ch = &c;	//将整形c的地址取出来放在ch中

	printf("%p", ch);
	return 0;
}
  • 指针变量的大小??是??

指针的大小在32位平台是4个字节,在64位平台是8个字节。

二、指针和指针类型

1. 为什么指针有多种类型??

  • 变量有多种类型,那么指针也会也有多种类型吧??

是的,不同类型的变量的地址就应该放在对应的指针变量中。

int main()
{
	int a = 0;
	int* pa = &a;

	double b = 0;
	double* pb = &pb;

	char c = 'w';
	char* pc = &c;
	return 0;
}

2. 指针±整数的意义是什么??

  • 明明都是地址,为什么要区分是什么类型的呢??

那是因为不同类型的指针±整数所跳过的字节数不同。

int main()
{
	int a = 0;
	int* pa = &a;
	printf("%p %p\n", pa, pa + 1);

	double b = 0;
	double* pb = &pb;
	printf("%p %p\n", pb, pb + 1);

	char c = 'w';
	char* pc = &c;
	printf("%p %p\n", pc, pc + 1);

	return 0;
}

【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!),c语言,数据结构,开发语言
从上图中我们可以看出:
int*类型的指针 + 1 是跳过四个字节
double*类型的指针 + 1 是跳过八个字节
char*类型的指针 + 1 是跳过一个字节
诶,看着很眼熟??
int 类型变量的大小是 四个字节?
double类型变量的大小是八个字节?
char类型变量的大小是一个字节?
难道?难道?没错!!!就是你猜想的那样…
指针±整数对应的是指针向前/向后移动的大小(指针指向变量类型大小 * 整数)


3. 指针±指针有什么意义??

  • 指针加整数有意义,那么指针 + - 指针呢??也有意义吗??

你猜对了!!一半 !!
指针 + 指针是没有意义的,但是指针 - 指针是有意义的哟!!
指针 - 指针的结果是两个指针之间所隔的元素个数,这种操作通常用于计算数组中两个元素之间的距离。


4. 得到了变量的地址有什么用呢??(指针解引用)

指针的作用就是通过地址取访问指针指向的变量。
指针的类型决定了指针解引用能够访问的字节数。
例如上面的int*类型的指针,解引用能访问四个字节,double*类型的指针可以访问八个字节,char*类型的指针能够访问一个字节


三、野指针

1. 野指针是什么??

野指针是指指向未知内存位置或者已经释放内存的指针。

2. 什么情况会造成野指针??

引用未初始化的指针、访问已释放内存、数组边界越界等行为都可能导致野指针。

  • 解引用未初始化的指针
int main()
{
	int* pa = NULL;
	printf("%d", *pa);
	return 0;
}
  • 数组的越界访问
int main()
{
	char ch[4] = "Love";
	for (int i = 0; i <= 4; i++)
	{
		printf("%c ", ch[i]);
	}
	return 0;
}

【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!),c语言,数据结构,开发语言

  • 使用已经释放的指针
#include <stdio.h> 
#include <stdlib.h>
#include <string.h>

int main()
{
	//动态申请4个char类型大小的空间
	char* ch = (char*)malloc(sizeof(char) * 4);
	if (ch == NULL)
	{
		return 0;
	}
	for (int i = 0; i < 4; i++)
	{
		ch[i] = 'a' + i;
	}
	
	//申请来的ch释放,内存还给操作系统
	//这时候访问ch中的元素就会造成野指针,打印出随机值
	free(ch);
	printf("%c", ch[0]);
	return 0;
}

【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!),c语言,数据结构,开发语言


3. 如何能够防止野指针的出现??

  1. 指针初始化
  2. 小心指针越界
  3. 指针指向空间释放,及时置NULL
  4. 避免返回局部变量的地址
  5. 指针使用之前检查有效性

四、指针和数组

首先先展示一下指针和数组的例子

int main()
{
	int arr[] = { 5 , 2 , 0 };
	int* pa = &arr[0];
	printf("%p\n", arr);
	printf("%p\n", pa);

	return 0;
}

【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!),c语言,数据结构,开发语言
从上面的结果中我们可以发现:
arr是数组名,pa是首元素的地址,而两者却相等。
结论:数组名大多数情况都是首元素地址。
例外:(数组的地址和数组首元素的地址的区别后面会讲)

  • sizeof(数组名),这里的数组名是数组的地址
  • &(数组名),这里的数组名也是数组的地址

那么指针(首元素地址)±整数与&数组名[下标]有什么关系呢??

int main()
{
	int arr[] = { 1 , 3 , 1 , 4 , 5 , 2 , 0 };
	int* pa = &arr[0];
	for (int i = 0; i < 7; i++)
	{
		printf("&arr[%d]: %p   <--->   pa + %d: %p\n", i, &arr[i], i, pa + i);
	}

	return 0;
}

【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!),c语言,数据结构,开发语言
从上面的图中我们可以得出:指针(首元素地址)±整数与&数组名[下标]是相同的。


那么能用下标遍历数组,能用指针(地址)±整数遍历吗??

int main()
{
	int arr[] = { 1 , 3 , 1 , 4 , 5 , 2 , 0 };
	int* pa = &arr[0];
	for (int i = 0; i < 7; i++)
	{
		printf("arr[%d]: %d   <--->   *(pa + %d): %d\n", i, arr[i], i, *(pa + i));
	}

	return 0;
}

【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!),c语言,数据结构,开发语言
从上图中可以得知:下标和指针(地址)±整数都能够遍历数组。

我们又知道pa是首元素地址,数组名也是首元素地址,那么数组名±整数能遍历数组吗??

int main()
{
	int arr[] = { 1 , 3 , 1 , 4 , 5 , 2 , 0 };
	int* pa = &arr[0];
	for (int i = 0; i < 7; i++)
	{
		printf("arr[%d]: %d   <--->   *(arr + %d): %d\n", i, arr[i], i, *(arr + i));
	}

	return 0;
}

【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!),c语言,数据结构,开发语言
从上图中可以得出:下标和数组名±整数都可以遍历数组。


数组名可以代替首元素地址,那么首元素地址能够代替数组名吗??

int main()
{
	int arr[] = { 1 , 3 , 1 , 4 , 5 , 2 , 0 };
	int* pa = &arr[0];
	for (int i = 0; i < 7; i++)
	{
		printf("pa[%d]: %d  <--->   *(pa + %d): %d\n", i, pa[i], i, *(pa + i));
	}

	return 0;
}

【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!),c语言,数据结构,开发语言
从上图中我们可以得出:数组首元素地址可以代替数组名。

结论:(我们可以通过指针直接访问数组)

  • 指针(首元素地址)+-整数&数组名[下标]是相同的。
  • 下标和指针(首元素地址)+-整数都能够遍历数组。
  • 下标和数组名+-整数都可以遍历数组。
  • 数组首元素地址可以代替数组名。
  • arr[i] = *(arr + i) = *(pa + i) = pa[i]

五、二级指针

变量有地址,指针变量也是变量,那么指针变量也有地址吗??

当然,指针变量也有地址,而存储指针的变量叫做二级指针。

int main()
{
	int arr[] = { 1 , 3 , 1 , 4 , 5 , 2 , 0 };
	int* pa = &arr[0];
	int** ppa = &pa;
	printf("ppa : %p\n", ppa);
	printf(" pa : %p\n", pa);
	printf("*ppa: %p\n", *ppa);

	return 0;
}

【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!),c语言,数据结构,开发语言

二级指针存储的是一级指针的地址,而指针解引用可以找到被指针指向的变量,那么这里二级指针解引用也可以找到一级指针。


进阶指针

一、字符指针

对于字符指针我们常见的使用方法是先创建一个字符变量,再创建一个字符指针变量,将字符变量的的地址赋给字符指针变量。

int main()
{
	char c = 'v';
	char* ch = &c;

	printf("%p", ch);
	return 0;
}

另一种使用方法则是创建一个字符指针变量,将字符串的首元素地址赋给他。
注意:这里是将字符串中的首元素地址存在 ch 中,而非字符串的地址

int main()
{
	char* ch = "chineseperson04";

	printf("%p", ch);
	return 0;
}

二、指针数组

1. 指针数组的定义

指针数组是一种存储指针的数组。

int main()
{
	int arr1[] = { 5 , 2 , 1 , 1 , 3 , 1 , 4 };
	int arr2[] = { 5 , 2 , 0 , 1 , 3 , 1 , 4 };
	int arr3[] = { 9 , 4 , 2 , 0 , 0 , 0 , 0 };
	int* arr[] = { arr1 , arr2 , arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%p\n", arr[i]);
	}
	return 0;
}

【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!),c语言,数据结构,开发语言


2. 指针数组的使用

指针数组是存储一级指针的数组,而一级指针指向的是数组,有没有发现指针数组的使用与二维数组很想,但两者却不能看成一个东西。

int main()
{
	int arr1[] = { 5 , 2 , 1 , 1 , 3 , 1 , 4 };
	int arr2[] = { 5 , 2 , 0 , 1 , 3 , 1 , 4 };
	int arr3[] = { 9 , 4 , 2 , 0 , 0 , 0 , 0 };
	int* arr[] = { arr1 , arr2 , arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		for (int j = 0; j < 7; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!),c语言,数据结构,开发语言


三、数组指针

1. 如何定义数组指针??

数组指针是用来存放数组地址的指针。
数组指针变量如何定义:(假设这里的指针变量是 p ,数组存放的 int 类型的变量)
首先它是指针那么就不能与[ ]先结合----(*p)
其次它指向的内容是数组 ---- (*p)[ ] ----[ ]中为数组的元素个数
最后它指向数组存储变量的类型为什么 ---- int(*p)[]

int main()
{
	int arr[10] = { 0 , 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int(*p)[10] = &arr;
	printf("%p", p);
	return 0;
}

2. 数组的地址和数组首元素的地址的区别

区别:( &arr 与 arr )
那么取出数组的地址和数组首元素的地址有什么区别呢??
这里我们用代码测试一下!!

int main()
{
	int arr[10] = { 0 , 1, 2, 3, 4, 5, 6, 7, 8, 9 };

	printf("%p\n", &arr);       //取出数组的数组
	printf("%p\n", arr);        //取出数组首元素的地址
	printf("%p\n", &arr[0]);    //取出数组首元素的地址
	return 0;
}

【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!),c语言,数据结构,开发语言

这里取出数组的地址和数组首元素的地址好像没什么区别,那么它们就是一样的吗??
这里我们进入下一个测试环节,让它们取出来的地址 +1 试试。

int main()
{
	int arr[10] = { 0 , 1, 2, 3, 4, 5, 6, 7, 8, 9 };

	printf("%p\n", &arr);       //取出数组的数组
	printf("%p\n", arr);        //取出数组首元素的地址
	printf("%p\n", &arr[0]);    //取出数组首元素的地址
	printf("\n");
	printf("%p\n", &arr + 1);       
	printf("%p\n", arr + 1);        
	printf("%p\n", &arr[0] + 1);    

	return 0;
}

【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!),c语言,数据结构,开发语言
诶?这里 +1 得到的地址不同,分析一下。
(1)数组首元素地址+1 :这里的地址在十六进制的状态下,相比之下地址的大小增加了 4十进制下也是 4 ,数组储存变量是 int 内存大小也是 4 个字节 ,这里我们可以得到数组首元素地址 +1 ,也就是跳过了一个变量大小的字节数
(2)数组地址+1 :这里的地址在十六进制的状态下,相比之下地址的大小增加了 28十进制下就是 40 ,数组储存变量是 int 内存大小也是 4 个字节并且数组元素个数10 个元素 ,这里我们可以得到数组首元素地址 +1 ,也就是跳过了一个数组大小的字节数

结论:

  • 数组首元素地址 +1 ,跳过一个变量的大小。
  • 数组地址 +1 , 跳过一个数组的大小

四、函数指针

1. 函数指针的定义

函数指针是一种指向函数的指针变量,它存储着函数的地址。函数指针的类型由函数的返回值类型和参数类型组成,可以用以下语法定义:

返回值类型(*指针变量名)(变量列表)

这里写一个例子:

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int (*pf)(int, int) = &Add;
	int a = 10, b = 30;
	int c = 0;
	c = (*pf)(a, b);
	printf("%d", c);
	return 0;
}

2. &(函数名) vs(函数名)

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int (*pf)(int, int) = &Add;

	printf("%p\n", &Add);
	printf("%p\n", Add);
	return 0;
}

【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!),c语言,数据结构,开发语言
这里我们得到 &(函数名) vs (函数名) 是一样的。
那么这里就可以进行下面的推理:

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int (*pf)(int, int) = &Add;
	int a = 10, b = 30;
	int add1 = 0 , add2 = 0;
	int add3 = 0,  add4 = 0;

	//add1 通过函数名掉用函数
	add1 = Add(a, b);
	printf("%d\n", add1);

	//add2 通过函数指针存储函数的地址,再解引用找到函数,调用函数
	add2 = (*pf)(a, b);
	printf("%d\n", add2);

	//由于上面得到 (&Add) 与 Add 相同
	//那么这里 ( pf 对应 &Add ) 与 (  (*pf) 对应 ADD   )
	//所以这里可以得到这里的 * 是没有用的,下面的测试也证明了这一点
	add3 = pf(a, b);
	printf("%d\n", add3);

	add4 = (******pf)(a, b);
	printf("%d\n", add4);

}

【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!),c语言,数据结构,开发语言

五、函数指针数组

1. 函数指针数组的定义

函数指针数组是一个数组用来存储函数指针的。
如何定义一个函数指针数组:假设数组存储的是 int (*)(int ,int)
首先是一个数组:那么就要先于[]结合 ---- pf[]
然后数组存储的是函数指针:int(*pf[])(int ,int) ----方块中的是元素个数


2. 函数指针数组的使用

这里使用函数指针数组完成一个简易版的计算器。

int add(int x, int y)
{
	return x + y;
}

int sub(int x, int y)
{
	return x - y;
}

int mul(int x, int y)
{
	return x * y;
}

int div(int x, int y)
{
	return x / y;
}

calculate(int (*pf)(int, int))
{
	int x = 0, y = 0;
	printf("请输入两个操作数\n");
	scanf("%d%d", &x, &y);
	printf("%d\n", pf(x, y));
}

int main()
{
	int (*pf[4])(int, int) = { add , sub , mul , div };
	int option = 0;
	while (1)
	{
		printf("*************************\n");
		printf("****  1:add    2:sub ****\n");
		printf("****  3:mul    4:div ****\n");
		printf("*************************\n");
		printf("请选择:>");
		scanf("%d", &option);
		switch (option)
		{
		case 1:
			calculate(add);
		case 2:
			calculate(sub);
		case 3:
			calculate(mul);
		case 4:
			calculate(div);
		default :
			printf("输入错误,请重新选择\n");
		}
	}
	return 0;
}

六、指向函数指针数组的指针

函数指针数组的指针的定义

如何定义一个函数指针数组的指针:假设数组存储的是 int (*)(int ,int)
首先是一个指针:那么就要先于*结合 ---- (*pf)
然后指针指向的是函数指针数组:(*pf)[]----方块中的是元素个数
最后数组存储的元素类型是:int(*(*pf)[])(int ,int) ----方块中的是元素个数

int add(int x, int y)
{
	return x + y;
}

int sub(int x, int y)
{
	return x - y;
}

int mul(int x, int y)
{
	return x * y;
}

int div(int x, int y)
{
	return x / y;
}

int main()
{
	int (*pf[4])(int, int) = { add , sub , mul , div };
	int	(*(*ppf)[4])(int, int) = &pf;
	return 0;
}

七、回调函数

1. 回调函数的定义

回调函数是一种函数,它作为参数传递给另一个函数,并且在其它函数执行完特定操作后被调用。


2. 回调函数的使用

这里使用回调函数实现 qsort() 函数的模拟(原理不同,这里使用冒泡排序的底层原理)

void Bubble_sort(void* base , size_t num , size_t width, 
	int (*Cmp)(const void * p1 , const void* p2))       //这里函数返回值类型需要按需求改
{
	
	size_t i = 0;
	for (i = 0; i < num - 1; i++)
	{
		for (size_t j = 0; j < num - i - 1; j++)
		{
			if (Cmp((char*)base + j * width , (char*)base + (j + 1) * width)  > 0 )
			{
				Swap((char*)base + j * width, (char*)base + (j + 1) * width , width);
			}
		}

	}
}

结尾

如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。
希望大家以后也能和我一起进步!!🌹🌹
如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹
【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!),c语言,数据结构,开发语言文章来源地址https://www.toymoban.com/news/detail-601657.html

到了这里,关于【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数据结构排序——详细讲解归并排序(c语言实现递归及非递归)

    上次是快排和冒泡:数据结构排序——详解快排及其优化和冒泡排序(c语言实现、附有图片与动图示意) 今天为大家带来归并排序 归并排序是一种分治算法,它将序列分成两个子序列,分别对 子序列进行排序 ,然后将排序好的子序列 合并起来 。这个过程可以 递归 地进行,

    2024年01月22日
    浏览(67)
  • 【C语言】指针的基本知识详细讲解(指针数组、数组指针、函数指针....

    接着上次的函数的基本知识,今天我们来讲一讲🔍指针 目录 一、指针的概念 二、指针变量 三、野指针 四、字符指针 五、指针与数组 六、指针数组 七、数组指针  八、指针与函数 总结 一、指针的概念 1.1、变量和地址 所谓指针,也就是内存的地址;所谓指针变量,也就是

    2023年04月08日
    浏览(41)
  • C语言之指针篇【超详细讲解,带你层层深入理解指针】

    目录 一、关于指针 二、指针类型 1、整型指针的访问权限说明: 2、字符指针的访问权限说明: 3、指针的类型决定向前或向后一步走了多大距离 三、野指针相关知识 1、野指针的成因 ①指针未初始化 ②指针的越界访问 ③指针所指向的空间释放了 2、如何规避野指针 ①指针

    2024年02月02日
    浏览(38)
  • C语言/c++指针详细讲解【超详细】【由浅入深】

    指针,是内存单元的编号。 内存条分好多好多小单元,一个小单元有 8 位,可以存放 8 个 0 或 1;也就是说,内存的编号不是以位算的,而是以字节算的,不是一个 0 或 1 是一个编号,而是 8 个 0 或 1 合在一起是一个编号。这个编号,就是地址。 内存条就分为好多小格子,一

    2024年01月21日
    浏览(47)
  • 初始C语言(7)——详细讲解有关初阶指针的内容

     第一章 “C“浒传——初识C语言(1)(更适合初学者体质哦!)  第二章 初始C语言(2)——详细认识分支语句和循环语句以及他们的易错点   第三章 初阶C语言(3)——特别详细地介绍函数   第四章 初始C语言(4)——详细地讲解数组的内容以及易错点   第五章

    2024年02月11日
    浏览(37)
  • 探索数据结构世界之排序篇章(超级详细,你想看的都有)

    - 文章开头必看 1.!!!本文排序默认都是排 升序 2.排序是否稳定值指指排完序之后相同数的相对位置是否改变 3.代码相关解释我都写在注释中了,方便对照着看 插入排序是一种高效的简单排序算法,它的工作原理是将一个未排序的元素插入到一个已排序的列表中,并保持列

    2024年02月08日
    浏览(47)
  • 【c语言指针详解】复杂数据结构的指针用法

    目录 一、动态内存分配 1.1 使用malloc和free函数进行内存的动态分配和释放 1.2 内存泄漏和野指针的概念和解决方法 二、复杂数据结构的指针用法 2.1 结构体指针和成员访问操作符 2.2 指针数组和指向指针的指针 2.2.1 指针数组 2.2.2 指向指针的指针 2.3 动态内存分配与结构体

    2024年02月04日
    浏览(50)
  • 【数据结构】双向链表 超详细 (含:何时用一级指针或二级指针;指针域的指针是否要释放)

    目录 一、简介 二. 双链表的实现 1.准备工作及其注意事项 1.1 先创建三个文件 1.2 注意事项:帮助高效记忆 1.3   关于什么时候 用 一级指针接收,什么时候用 二级指针接收? 1.4 释放节点时,要将节点地址 置为NULL,难道 节点内部的 指针域的指针 就不用置为 NULL吗?  2.双链

    2024年02月20日
    浏览(88)
  • 数据结构-线性表的顺序表基本操作代码实现(超级详细清晰 C++实现)

    顺序表是用一段 物理地址连续的存储单元 依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。 顺序表: 可动态增长的数组,要求数据是连续存储的 特点: 随机访问 顺序既可以 静态分配 ,也可以 动态分配 。在静态分配时,由于数组

    2024年02月07日
    浏览(56)
  • 【数据结构】顺序表 | 详细讲解

    在计算机中主要有两种基本的存储结构用于存放线性表:顺序存储结构和链式存储结构。本篇文章介绍采用顺序存储的结构实现线性表的存储。 线性表的顺序存储结构,指的是一段地址连续的存储单元依次存储链性表的数据元素。 线性表的(,……)的顺序存储示意图如下

    2024年02月04日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包