C语言指针详解

1.指针的概念

简单的来说,指针就是地址---数据的地址---数据在内存中的地址。

在计算机中,输入和读取数据都要对内存进行操作。现在我们的内存有2G,4G,8G,16G,32G,这是内存的空间,也就是程序运行的时候,CPU读取和存储数据的地方,实际上,只要我们的系统在运行,就会占用内存。再比如幻灯片桌面,循环播放图片,幻灯片桌面的程序在运行的时候,会对硬盘中的图片进行扫描,首先就是内存要从硬盘中拿到对应的图片的数据放在自己的某一块区域内,然后让cpu处理。那么计算机是由很多程序一起有条不紊的运行的,那么内存读/取数据必定是有规则的,即对应的数据都应该由对应的内存区域进行读取操作,就像仓库,或者说数据中转站,也可以用快递站点的货架形容。(或者门牌号)

快递货架有它自己的编号,叫取货码。内存空间也有它的编号,叫地址/指针。

快递货架有取货码,一个取货码只能取一个快递;指针/地址的单位是字节,最小一个字节只能放一个数据(char类型/字符)。

在计算机中,32位,64位,实际上是32根地址线,或者64根地址线的说法,(单片机51---8位///stm-32---32位)每一根地址线有0/1两种状态,即高电平/低电平两种状态。

32位的机器能产生2^32种不同的地址即下面左图从0X00000000——>0XFFFFFFFF,换成2进制就是00000000 00000000 00000000 00000000——>11111111 11111111 11111111 11111111。每个地址的大小是1字节(8bit)。

每次cpu存/取数据都要经过内存,通过地址/指针找到内存中的小格子,然后对这个小格子空间进行存/取数据的处理。

在C语言中,通常把地址叫做 指针。

1.1取地址操作符&

c语言中,创建变量的本质就是向内存申请一块空间,然后往这个变量里放数据。

在用scanf输入的时候,我们会用到取地址 & 操作符,实际上就是拿到某个变量的地址,然后往这个变量里存如你输入的数据。

一个char变量的大小是1字节,1个int变量的大小是4字节,也就是1个char变量需要1个内存单元来存储数据,int变量需要4个内存单元。用%p与&可以以16进制的形式打印地址。并且得到的地址是第一个字节的地址。下面的int a,占了4个字节,它的地址实际上是这4个字节中第一个字节的地址。

1.2指针变量与解引用(指针类型)

我们拿到的地址/指针是个数值(内存编号),指针是可以保存在变量中方便后续使用的。

既然地址也是一串数据,那么我们可以用指针变量来存储这个地址。

即储存指针/地址的变量叫做指针变量。

例如下图,我们用&a拿到了a的地址,存入了指针变量p中。其中,p是变量名,*代表p是个指针,int代表指针变量p所指的内存单元中的数据是int类型,这是整形指针变量,另外还有字符指针变量char * ,数组指针变量int * [],指针数组变量int (*)[],结构体指针变量。

存入指针变量之后,可以通过  *  解引用操作符来取出这个指针变量中的数据,即通过指针变量中存储的地址来找到这个内存单元中的数据。

在函数中,函数的形参都是临时变量,是实参的一份临时拷贝,想要改变函数中某个变量的值,就必须用到指针变量,通过内存编号修改内存中的数据。

1.3指针变量的大小

在32位系统和64位系统中,分别有2^32与2^64种不同的地址,分别需要32个和64个二进制位来表示地址,每8个二进制位表示一个字节,,那么32位和64位系统的指针(变量)大小分别就是32/8==4字节与64/8==8字节。

所有类型的指针变量大小相同,只是在不同系统环境下不同而已。

1.4指针的类型与指针的加减+-运算

指针变量是可以进行计算的。int * 是整形指针类型,指向的空间是4个字节,那么int *类型的指针变量+1,它会跳过4个字节。下图中int *类型的p加上1,跳过了1个int大小的空间,即跳过了4个字节。

将int * p强制转换为char*类型,此时的指针变量p指向的是1个内存单元---1字节的空间,那么它加1,跳过了一个char,即1个字节的空间。

扩展(两个指针相减)

已知,int * 类型的指针加上1,会跳过一个int大小的空间,即这个地址+4,那么两个int类型的指针变量相减,得到的就是这两个地址之间相距几个int的空间。

(注:数组名代表数组首元素的地址,&数组名代表整个数组的地址)

1.5指针的越界与置NULL以及assert断言

指针的越界是指指针跑到申请的内存空间之外去了,也就是指针指向了一块未知的内存空间,如果这块内存空间存的是系统运行中很关键的一个数据,修改了这个指针所指向的内存空间的数据,可能会对系统造成损坏。所以应该尽量避免指针越界(指针的自增自减容易越界)。

指针变量用完后,应该及时将指针变量赋值NULL,否则这个指针变量会成为野指针

例如指针变量运算完毕,此时指针变量指向了一块未知的区域,这样就很危险,所以将这个指针变量赋NULL的值,来将这个指针变量保护起来。

assert(     )断言,意思是——我断言括号里的表达式为真,否则就报错停止。需要包含assert.h头文件。

1.6指针模拟实现strlen

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
int strlen_plus(char * a)
{
	assert(a);    //保证传过来的不是空指针
	char* b = a;  //将a存入b,不改变a的值,只对b进行操作
	while (*b)    //字符串末尾有个\0,当b不为\0的时候循环
	{
		b++;      //每次循环,b向后跳过一个char的空间
	}
	return b-a;   //两个同类型指针相减,即为两指针之间的类型数量
}                 //函数结束后,指针变量b自动销毁,不会成为野指针
int main()
{
	char a[]="lty520";
	printf("%d\n", strlen_plus(a));
	return 0;
}

2.数组指针&&指针数组

2.1一维数组(字符数组)指针

数组指针就是数组的指针,数组名代表首元素地址,&数组名代表整个元素的地址。

数组名+1,代表首元素的地址+1,即首元素跳过1个元素类型大小的空间。

&数组名+1,代表跳过整个数组大小的空间。

由下图可以得知,一维数组数组名就是首元素地址,&a拿到的是整个数组的地址。

sizeof(a),拿到的是整个数组的地址。

在一维数组中,&a与sizeof(a)拿到的是整个元素的地址,除此之外的数组名,就是首元素的地址。

数组指针与字符数组指针是相同的。

在函数的数组的传参中,传的就是数组首元素的地址。

2.2冒泡排序模拟实现

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
void paixu(int * a, int b)//接收一个数组首元素地址和数组的元素个数
{
	assert(a);        //判断不为空指针
	int q = 0;
	int w = 0;
	int e = 0;
	for (q = 0; q < b-1; q++)
	{
		int flag = 1;  
		w = 0;
		for (; w < b - q - 1; w++)
		{
			if (a[w] > a[w + 1])
			{
				e = a[w];
				a[w] = a[w + 1];
				a[w + 1] = e;
				flag = 0;     //如果交换了,说明数组不是升序的
			}
		}
		if (flag == 1)    //如果一轮内循环都没有交换,那么这个数组就是已经排序好的
		{
			break;        //跳出循环
		}
	}
}

int main()
{
	int lty[] = { 5,3,1,6,2,9 };
	int size = sizeof(lty) / sizeof(lty[0]);
	printf("排序前:");
	for (int m = 0; m < size; m++)
	{
		printf("%d ", lty[m]);
	}
	paixu(lty, size);
	printf("\n排序后:");
	for (int m = 0; m < size; m++)
	{
		printf("%d ", lty[m]);
	}
	return 0;
}

2.3指针数组

根据标题名可知,是指针的数组,也就是数组中的元素是指针,即存放指针的数组。它的每个元素类型都是指针类型,包括int*,char*等等。

2.4二维数组的指针

二维数组的本质是一个一维数组中,存放了多个一维数组。

在下图中可以看到,a[3][4]二维数组实际上是3个拥有4个元素的一维数组的集合。

二维数组的地址最后分别为8c,9c,ac,说明二维数组中各个一维数组是连续存放的。

对于二维数组来说,数组名代表的是它的首元素的地址,即第一个一维数组的地址。数组名+1跳过的是一个一维数组的大小的空间。下图的0x002efca0与0x002efcb0,相差16个字节,即4个整形的空间。

&数组名拿到的则是整个二维数组的地址。

我们已经知道了数组指针的加减规则和二维数组数组名的含义,那么二维数组数组名加上一个整数n,指针就跳过n个一维数组的空间大小,拿到的是跳过n个一维数组之后的那个数组的指针,拿到的是整个一维数组的指针,也就是相当于&了这个一维数组的数组名。&(取地址)数组名,再*(解引用)数组名,拿到的是数组名/首元素地址,它的类型是int *,拿到首元素地址后再加上一个整数m,即跳过m个整形,拿到了     *    (   *(二维数组数组名+n)   )   +m即  [n][m]。

回顾一维数组,一维数组中数组名+n是指针跳过n个元素空间大小,数组名[n]是拿到第n个元素。

可以得知,*(数组名+n)==数组名[n].由此可以推出在二维数组的指针使用规则(下图).

2.5指针模拟二维数组

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>

int main()
{       //下标: 0 1 2
	int a0[] = { 1,2,3 };
	int b1[] = { 4,5,6 };
	int c2[] = { 7,8,9 };   //数组名就是数组首元素地址,是int *类型的
	int* arr[] = { a0,b1,c2 };//arr数组的每个元素为int *,整形指针变量
	       //下标: 0  1  2
	for (int i = 0; i < 3; i++)
	{
		for (int u = 0; u < 3; u++)
		{
			printf("%d ", arr[i][u]);   //1  2  3
		}                               //4  5  6
		printf("\n");                   //7  8  9
	}
	return 0;
}

2.6数组指针变量与指针数组

首先我们先要明确 [ ] 的优先级比 * (解引用操作符)高。

int (*p)[10]     *p代表p是个指针,它有10个元素,它的每个元素类型是int。

int * p [10]     p先与 [10] 结合,代表p是个数组,p[10],这个数组有10个元素,每个元素是int*类型。

前者是数组指针(数组的指针),p是个指针,指向一个数组。

后者是指针数组(指针的数组),p是个数组,每个元素是指针。

拓展:函数中二维数组的传参

二维数组在函数中传参,传的是第一个一维数组的地址。

int p[2][3]与int(*p)[3]是相同的。

拓展:字符串指针

字符串本质上来说就是一个字符数组,只不过必须以\0结尾,它的变量名也是首元素地址,&变量名拿到的是整个字符串的地址。字符串指针变量在数组指针变量的基础上只需要将int改为char。

(数组的两种初始化方式,” “初始化的是常量字符串,内容不可更改,末尾隐藏\0;{}初始化必须在结尾加上\0)

3.函数指针变量

函数在调用的时候会创建一块空间,这块空间的起始地址就是函数的指针/地址。

它的类型是  返回类型---指针变量---函数参数    

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>

void test(int (*p)[10])
{
	printf("%p\n", p);
	printf("%p\n\n", p+1);
}

int main()
{ 
	printf("%p\n", test);   //001713CF
	void (*p)(int(*)[10]) = test;
	printf("%p\n", p);      //001713CF
	return 0;
}

3.1函数指针数组

首先,明确是  函数指针 的  数组,它是个数组,根据指针数组的含义可知,函数指针数组,这个数组的每个元素是函数指针。

那么它的类型是 返回类型---指针变量(数组)---参数类型

a[2]是个有2个元素的数组,它的每个元素是void(*)(int(*)[10])类型的。

拓展:转移表

为什么要有函数指针数组呢?

拿计算器程序举例(转移表)

它可以增加程序的可读性,维护难度,如果需要增加功能,只需要增加新的函数,在函数指针数组中增加一个元素。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>

int jiafa(int a, int b)   //定义多个函数,函数返回类型和参数都相同
{
	return a + b;
}
int jianfa(int a, int b)
{
	return a - b;
}
int chengfa(int a, int b)
{
	return a * b;
}
int main()
{ 
	int (*a[4])(int,int) = {0,jiafa,jianfa,chengfa};//将各相同类型的函数指针放到同一个数组里
	int lty = 1;                                    //前面多加一个0,可以让选项和下表对齐
	int e = 0;       //                                                   l
	int r = 0;       //      l————————————————————————————————————————————l
	while (lty)      //      l                                               
	{                //      l
		printf("请输入模式:\n1.加法\n2.减法\n3.乘法\n0.退出\n");
		scanf("%d", &lty);
		if (lty > 0 && lty < 4)
		{
			printf("请输入两个操作数\n");
			scanf("%d %d", &e, &r);
			printf("%d\n",a[lty](e,r)); //a[lty]拿到的是对应下标的函数地址。
                                        //将输入的两个参数传给这个地址(函数),打印它的返回值
		}
	}
	return 0;

拓展:回调函数

回调函数,就是通过函数指针在函数中调用另一个函数;

4.多级指针,二级指针——1000级指针

有时候,我们需要把指针变量存起来方便后续使用,那么就要存到二级指针变量里去,二级变量又可以存到3级指针里去,具体可以存多少层,我也不知道,你可以试试;

二级指针的写法是这样的

int main()
{
	int a = 0;
	int* aa = &a;       //1级指针
	int** aaa = &aa;    //2级指针
	int*** aaaa = &aaa; //3级指针
	//int*****………… aaaaaa…………  //99999999999999级指针
	return 0;
}

通常情况下,我们是绝对不会用到999999999999级指针的,1级,2级比较常用。

100.回调函数的拓展应用——回调博客地址

100.1(对任何类型进行排序)qsort函数的模拟

//回调CSDN博客的地址⬇

模拟qsort———数组/结构体的排序-CSDN博客

库函数qsort排序-CSDN博客

指针笔试题例题-CSDN博客

  • 36
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: "C语言指针详解.pdf" 是一份详细介绍C语言指针概念和使用的PDF文档。C语言中,指针是一种特殊的变量类型,用于存储其他变量的内存地址。 该PDF文档首先详细介绍了指针的定义和声明。指针的声明需要指定指针变量的类型和名称,并使用星号(*)来表示该变量是一个指针指针变量名的前面加上一个星号,可以获取所指向的变量的值,这被称为"解引用"。 文档还介绍了指针的运算。指针可以进行自增和自减运算,指针之间可以进行相减操作,返回的结果表示它们之间的距离或者偏移量。此外,还可以将指针赋值给另一个指针,或者将指针赋值给一个变量,反之亦然。 除了基本的指针概念,文档还详细介绍了指针的常见应用场景。这包括指针作为函数参数,用于在函数内部对传入的变量进行修改。还有通过指针来实现动态内存分配和释放,以及使用指针实现数据结构(如链表和树)等。 此外,该文档还包含一些常见的指针错误和问题的解决方案。这些错误包括空指针引用、野指针引用以及内存泄漏等。文档指出了这些错误的影响以及如何避免它们。 总的来说,"C语言指针详解.pdf" 是一份详细介绍C语言指针概念、使用和常见问题解决方案的文档,对于学习和理解C语言指针的人们是一份宝贵的资料。 ### 回答2: 《C语言指针详解.pdf》是一本关于C语言指针的详细解析的电子书。在这本书中,作者详细介绍了C语言指针的概念、用途和基本语法。 首先,指针C语言中非常重要的概念,它是一种数据类型,用于存储和操作内存地址。指针可以指向各种数据类型,如整数、字符、数组和结构体等。 在《C语言指针详解.pdf》中,作者详细讲解了指针的声明和初始化,以及如何通过指针来访问和修改变量的值。作者还介绍了指针与数组的关系,以及指针和函数之间的关联。 此外,书中还涵盖了指针的高级应用,如指针的算术运算、指向指针指针指针数组等。作者通过丰富的例子和代码来帮助读者理解这些概念和技巧。 《C语言指针详解.pdf》不仅适合C语言初学者,也适合有一定编程基础的读者。通过阅读此书,读者将能够更深入地理解C语言指针的功能和用法,掌握指针在编程中的灵活运用。 总之,《C语言指针详解.pdf》是一本内容详尽且易于理解的C语言指针教程。读者通过阅读此书,可以提高自己在C语言编程中的指针应用能力,从而更好地实现程序的设计和开发。 ### 回答3: 《C语言指针详解.pdf》是一本介绍C语言指针概念和使用方法的详细手册。C语言中的指针是一种非常重要和特殊的数据类型,它提供了直接访问内存地址的能力,使得C语言具有了更高的灵活性和效率。 这本手册首先会介绍指针的基本概念,包括指针变量的定义和声明、指针的初始化和赋值。它会详细讲解指针和变量之间的关系,以及指针的运算规则和使用方法。读者可以学习到如何通过指针操作变量的值和地址,以及如何利用指针实现函数的参数传递和返回值。 接下来,手册会介绍指针和数组之间的关系。C语言中,数组名本质上是一个指向数组首元素的常量指针,因此可以通过指针来操作数组。手册将详细讲解指针和数组的指针算术运算,以及指针和多维数组的关系。 此外,手册还会介绍指针和字符串之间的关系。C语言中,字符串本质上是以空字符结尾的字符数组,可以通过指针来操作字符串。手册将详细讲解指针和字符串的操作,包括字符串的输入输出、字符串的比较和拷贝。 最后,手册还会介绍指针和结构体之间的关系。C语言中,结构体是用户自定义的复合数据类型,可以通过指针来操作结构体。手册将详细讲解指针和结构体的操作,包括结构体指针的定义和使用,以及结构体指针作为函数参数的传递方式。 总之,《C语言指针详解.pdf》是一本深入浅出的指针教程,对于想更深入理解C语言指针的读者来说,是一本非常实用的参考书。无论是初学者还是有一定基础的读者,都可以从中获得很多宝贵的知识和技巧。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值