🌟菜鸟主页:@晨非辰的主页
👀学习专栏:《C语言学习》
💪学习阶段:C语言方向初学者
⏳名言欣赏:"你今天的Bug,会成为明天的最优解。"
目录
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;
}
结语:本篇内容就到这里了,主要分享了指针的一些基础内容,后续仍会分享指针的相关知识;指针的内容需要反复研读 ,如果这篇文章对你的学习有帮助的话,欢迎一起讨论学习,你这么帅给个三连吧~~~