#C语言——学习攻略:深挖指针路线(一)--指针变量、地址、意义与指针运算

🌟菜鸟主页:@晨非辰的主页

👀学习专栏:《C语言学习》

💪学习阶段:C语言方向初学者

⏳名言欣赏:"你今天的Bug,会成为明天的最优解。"


目录

1. 内存和地址

1.1 内存

1.2  编址的理解

2. 指针变量和地址

2.1 取地址操作符(&)

2.2 指针变量和解引用操作符

2.2.1 指针变量

2.2.2 理解指针类型

2.2.3 解引用操作符

2.3 指针变量的大小

3. 指针变量类型的意义

3.1 指针的解引用

3.2 指针加减整数

3.3 void*指针

4. 指针运算

4.1 指针加减整数

4.2 指针减指针

4.3 指针的关系运算


1. 内存和地址

--在说指针前,先来了解一下基础内容:内存、地址。

1.1 内存

--在生活中进行类比:比如在宿舍楼,想找到自己的宿舍房间就需要房间号;

--同理,计算机上CPU在处理数据时,需要在内存中读取,完后再放回内存;所以在对内存进行管理时(宿舍楼总人数),将内存分为一个个内存单元(宿舍房间),每单元大小取一个字节-8比特位(每房间人数)。


        --补充计算机中的常见单位:

bit -比特位                1Byte = 8bit
Byte -字节                1KB = 1024Byte                
KB                            1MB = 1024KB
MB                           1GB = 1024MB
GB                            1TB = 1024GB
TB                             1PB = 1024TB
PB


 --每个单元理解为宿舍,一字节空间放8个比特位,像宿舍住8人,每人就是1比特;

--而每个单元有一个编号(相当于门牌号),利于CPU进行寻找内存空间;

--在生活中,将门牌号叫做地址,计算机中也将内存单元编号叫做地址,在C语言中,又将地址叫做指针

 -- 内存单元的编号==地址==指针

1.2  编址的理解

--CPU在访问内存中字节空间时,需知道在内存的什么位置,因为内存中字节很多,就需要给内存进行编址(就如同宿舍很多,需要给宿舍编号一样)。

--计算机中的编址,并不是把每个字节的地址记录 下来,而是通过硬件设计完成的。

--要知道计算机内有许多硬件单元,并且想回之间协同工作,所以它们之间就存在着相互连接的"线",本次主要关心"地址总线"

--简单来说:32位机器有32跟地址总线。每根线只有两态,表示0,1【电脉冲有无】,那一根线能表示两种含义,2根线就是4种,2根地址线就能表示2^32种含义,每一种含义都代表一个地址。

--总的来说:地址信息被下达给内存,在内存上,可以找到该地址对应数据,将数据通过数据总线传入CPU内寄存器。


2. 指针变量和地址

2.1 取地址操作符(&)

--理清了内存和地址之间的关系,回到C语言中,创建变量就是向内存申请空间

int main()
{
    int a = 10;
    return 0;
}

         --这串代码就是创建了整型变量,向内存申请了4个字节存放整数10;当然·,每个字节都有自己的地址。

        --想要获取a的地址就用到了取地址操作符——&

#include <stdio.h>
int main()
{
	int a = 10;
	printf("%p\n", &a);
	//输出指针,%p
	return 0;
}
//输出:000000606C8FF704

        --对于到底输出哪个字节的地址呢?:

                --C语言中,&取出的是a所占字节中字节地址最小的,知道了首字节,那么剩余的就跑不了了!

2.2 指针变量和解引用操作符

2.2.1 指针变量

--当我们想将&取出的地址存起来时,我们就需要一个变量来操作,这个变量就是指针变量

int main()
{
	int a = 10;
	//printf("%p\n", &a);
	//输出指针,%p

	int* pa = &a;//&取出a的地址并存在指针变量pa中
	return 0;
}

--指针变量也属于变量,它是专门来存放地址的,任何存在指针变量中的数值都被视为地址。

--输出指针用%p

2.2.2 理解指针类型

--从上面的代码看出,指针变量的类型是int*:

int a = 10;
int * pa = &a;

        --"*"代表变量pa是指针变量,int则是说明 pa指向的是int类型的对象。p表示指针缩写--pointer

--同理,char类型的:

char ch = 'w';
char *  pc = &ch;

2.2.3 解引用操作符

--将地址保存起来,是要使用地址来找到它指向的对象的,那怎么操作?这里就又涉及到一种操作符:解引用(*)

        --解引用操作符(*)用于通过指针访问或修改其指向的内存地址存储的值。它的核心作用是将指针转换为它所指向的实际数据

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

	*pa = 0;
	return 0;
}

        --代码中,*pa的意思就是就是通过指针变量pa中存放的地址,找到指向的空间(存放的数字据);就是说*pa相当于a,下面*pa = 0;就将a的值变为0.

2.3 指针变量的大小

--已经知道,32位机器假设有32根地址总线,每根线出来的电信号换成数字信号就是1或0,那将32根总线产出的2进制序列当作一个地址,就是32bit位,要4个字节存放;意味着指针变量大小就是4字节;在64为环境下,就是8字节大小。

int main()
{
	printf("%zd\n", sizeof(char*));
	printf("%zd\n", sizeof(short*));
	printf("%zd\n", sizeof(int*));
	printf("%zd\n", sizeof(double*));
	return 0;
}

--指针变量的大小取决于地址的大小;

--32位平台下地址是32个bit位(即4个字节);

--64位平台下地址是64个bit位(即8个字节);


3. 指针变量类型的意义

3.1 指针的解引用

--对比下面两段代码,在调试时观察内存的变化:

//代码1
int main()
{
	int n = 0x11223344;
	int* pi = &n;
	*pi = 0;
	return 0;
}


//代码2
int main()
{
	int n = 0x11223344;
	char* pc = (char*)&n;
	*pc = 0;
	return 0;
}


--通过调试观察其内存变化我们可以发现,代码1将n的4个字节全部改为0,但是代码2只第一个字节改为0.

--结论:指针的类型决定了,对指针解引用的时候有多大的权限(一次能操作几个字节)。

--比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。

3.2 指针加减整数

--同样,观察下面代码的调试:

int main()
{
	int n = 10;
	printf("%p\n", &n);
	char* pc = (char*)&n;
	printf("char * 类型\n");
	printf("%p\n", pc);
	printf("%p\n", pc + 1);
	int* pi = &n;
	printf("int * 类型\n");
	printf("%p\n", pi);
	printf("%p\n", pi + 1);
	return 0;
}

 --我们可以看出,char*类型的指针变量+1跳过一个字节,int*类型的指针变量+1跳过了4个字节。这就是指针变量类型差异带来的变化。指针+1,其实就是跳过1个指针指向的元素。指针可以+1,那也可以-1。

--结论:指针的类型决定了指针向前或者向后走一步有多大(距离)。 

3.3 void*指针

--指针类型有一种特殊类型是void*类型,理解为无具体类型的指针(或泛型指针),这种类型的指针可以用来接受任意类型地址。但也有局限性,void*类型的指针不能直接进行指针的加减整数和解引用的运算。

--举例:

#include <stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;
	char* pc = &a;//err
	return 0;
}

 --在代码中,将一个int类型的变量的地址赋值给一个char*类型的指针变量。编译器给出警告;因为类型不兼容,使用void*类型就不会有这样的问题。

#include <stdio.h>
 
int main()
{
	int a = 10;
	void* pa = &a;
	void* pc = &a;
 
	*pa = 10;
	*pc = 0;
	return 0;
}

        --发现,void*类型指针可以接收不同类型的地址,但无法进行指针运行算。

--对于void*类型指针,它的使用在函数参数的部分,用来接收不同类型的地址,这就实现了泛型编程效果。

--让函数处理多种类型数据,请期待后续博客的分享!


4. 指针运算

--指针的基本运算有三种:

  • 指针+ -整数
  • 指针-指针
  • 指针的关系运算

4.1 指针加减整数

--在学习数组时,了解到数组在内存中是连续存放的;那是不是可以用指针运算的特性来实现打印数组呢?

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int* pa = &arr[0];
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for(i = 0; i < sz; i++)	
	{
		printf("%d ", *(pa + i));
	}
	return 0;
}

--对于指针减整数的操作类似于逆序打印数组,大家感兴趣可以试一试。

4.2 指针减指针

--计算条件:两指针必须指向同一块空间(如同一个数组或动态分配的内存块),才能相加减;

-- |指针-指针| = 两个指针之间的元素个数

--下面,使用指针来实现strlen函数的功能:

//定义函数
size_t my_strlen(char* p)
//函数最后返回长度>=0,类型以是无符号整型;size_t属于无符号整型
//传的参数是地址,形参为指针变量	
{
	char* start = p;
	
	while (*p)//
	{
		p++;
	}
	return p - start;
}

int main()
{
	char arr[] = "abcdef";
	//传入的实参:数组名就是数组首元素的地址
	//arr = &arr[0]
	printf("%zu\n", my_strlen(arr));

	return 0;
}

--注意点:

--传入的实参:数组名就是数组首元素的地址;

--在 ASCII 编码表中:字符 '\0' 对应的整数值就是 0


4.3 指针的关系运算

--通过比较指针的大小来打印输出,来感受一下:

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;//数组首元素的地址
	int sz = sizeof(arr) / sizeof(arr[0]);
	//因为数组只包含十个元素的地址,超出后地址无效
	//就可将第11位地址(首元素地址+10)为界限
	while (p < (arr + 10))
	{
		printf("%d ", *p);
		p++;
	}
	return 0;
}

结语:本篇内容就到这里了,主要分享了指针的一些基础内容,后续仍会分享指针的相关知识;指针的内容需要反复研读 ,如果这篇文章对你的学习有帮助的话,欢迎一起讨论学习,你这么帅给个三连吧~~~

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值