目录
一、内存与地址
1.1 内存
在我们生活中住宿楼里每个房间都有一个门牌号可以方便我们能够快速的找到想要去的房间,内存与此相似,我们知道计算上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,内存空间会把内存划分为一个个内存单元方便高效管理,每个内存单元的大小取1个字节。
(计算中常见的单位换算)
⼀个比特位可以存储⼀个2进制的位1或者0。
每个内存单元也都有⼀个编号(这个编号就相当于房间的门牌号),有了这个内存单元的编号,CPU就可以快速找到⼀个内存空间。生活中我们把房间门牌号也叫地址,在计算机中我们把内存单元的编号也称为地址。C语言中给地址起了新的名字叫:指针。
所以我们可以理解为: 内存单元的编号==地址==指针
1.2 编址
CPU访问内存中的某个字节空间,必须知道这个字节空间在内存的什么位置,而因为内存中字节 很多,所以需要给内存进行编址。计算机中的编址,并不是把每个字节的地址记录下来,而是通过硬件设计完成的。计算机内是有很多的硬件单元,而硬件单元是要互相协同⼯作的,所谓的协 同,至少相互之间要能够进⾏数据传递,每个硬件是相互独立的需要通过“线”来连接。而CPU和内存之间也是有大量的数据交互的,所以两者必须也⽤线连起来。
二、指针变量与地址
2.1 取地址操作符(&)
在C语⾔中创建变量其实就是向内存申请空间,
#include <stdio.h>
int main()
{
int a = 10;
return 0;
}
比如,上述的代码就是创建了整型变量a,内存中申请4个字节,用于存放整数10,其中每个字节都 有地址,上图中4个字节的地址分别是:
想要获得a的地址就需要用到取地址操作符(&)
#include <stdio.h>
int main()
{
int a = 10;
&a;//取出a的地址
printf("%p\n", &a);
return 0;
}
这个代码会打印出:006FFD70
&a取出的是a所占4个字节中地址较小的字节的地址。
(变量在内存中的存储)
2.2 指针变量和解引用操作符(*)
2.2.1 指针变量
我们通过取地址操作符(&)拿到的地址是⼀个数值,这个数值有时候也是需要存储起来,方便后期再使用,我们就会把它存储在指针变量中,指针变量是一种用来存放地址的变量,存放在指针变量的值可以理解为地址。
2.2.2 解引用操作符(*)
我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针) 指向的对象,这里就要知道解引用操作符(*)。
#includue<stdio.h>
int main()
{
int a = 100;
int* pa = &a;
*pa = 0;
return 0;
}
上面代码中第7行就使用了解引用操作符, *pa 的意思就是通过pa中存放的地址,找到指向的空间, *pa其实就是a变量了;所以*pa=0,这个操作符是把a改成了0.
2.3 指针变量的大小
前⾯的内容我们了解到,32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后 是1或者0,那我们把32根地址线产生的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4个字节才能存储。 如果指针变量是用来存放地址的,那么指针变的大小就得是4个字节的空间才可以。,同理64位机器,假设有64根地址线,⼀个地址就是64个⼆进制位组成的⼆进制序列,存储起来就需要 8个字节的空间,指针变的大小就是8个字节。
• 32位平台下地址是32个bit位,指针变量大小是4个字节
• 64位平台下地址是64个bit位,指针变量大小是8个字节
• 注意指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的。
三、const修饰指针
• const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本⾝的内容可变。
• const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指 向的内容,可以通过指针改变。
四、指针运算
指针的基本运算有三种,分别是:
• 指针+-整数
• 指针-指针
• 指针的关系运算
4.1 指针+-整数
因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸瓜就能找到后面的所有元素。
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; i++)
{
printf("%d ", *(p+i));//p+i 这⾥就是指针+整数
}
return 0;
}
4.2 指针-指针
#include <stdio.h>
int my_strlen(char *s)
{
char *p = s;
while(*p != '\0' )
p++;
return p-s;
}
int main()
{
printf("%d\n", my_strlen("abc"));
return 0;
}
4.3 指针的关系运算
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
while(p<arr+sz) //指针的⼤⼩⽐较
{
printf("%d ", *p);
p++;
}
return 0;
}
五、野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的),会因为指针未初始化,指针越界访问,指针指向的空间释放等问题产生。
指针初始化,小心指针越界,指针变量不再使用时,避免返回局部变量的地址,及时置NULL,指针使用之前检查有效性,尽量规避野指针。
六、指针的传值调用和传址调用
#include <stdio.h>
void Swap(int x, int y)
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("交换前:a=%d b=%d\n", a, b);
Swap(a, b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
把变量本身直接传递给了函数,这种叫传值调用。实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参,所以这种调用不会改变a,b的值。
#include<stdio.h>
void Swap(int*px, int*py)
{
int tmp = 0;
tmp = *px;
*px = *py;
*py = tmp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("交换前:a=%d b=%d\n", a, b);
Swap(&a, &b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
传址调用,可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量。
所以函数中只是需要主调函数中的变量值来实现计算,就可以采用传值调用。如果函数内部要修改主调函数中的变量的值,就需要传址调用。
以上就是关于指针的一些基础知识,希望对各位有帮助,如果有错误欢迎指正。