欢迎来sobercq的博客喔,本期系列为【C语言】指针的入门篇,深入理解指针和指针变量
图文讲解指针的知识,带大家理解指针和内存的关系,以及指针的用法,感谢观看,支持的可以给个赞哇。
目录
一、内存和地址
二、变量和地址
三、指针变量
四、指针变量的用法(一)
五、指针变量的大小
五、指针变量的用法(二)
六、const关键字
七、指针变量的用法(3)
八、assert断言
补充:计算机数据存储常见的单位
内存,电脑组成的一部分,在这一部分中,我们的cpu在运转的时候从内存中读取数据。那内存的大小又从1G-16G不等等,那它是如何管理的呢?
电脑简易组成:
我们把内存划分为一个个的空间大小只有1字节的内存单元,为了找到内存单元,每个内存单元都会有一个内存编号,这样的内存编号在计算机中如果用十六进制表示就如图所示0x00000001……,内存编号我们就把它称作地址。在C语言里,地址就是指针。
内存管理:
了解完内存管理,那么变量的本质其实就是向内存申请空间,空间的大小由数据类型来确定。例如int a = 5;实际上就是向内存申请了一个空间大小为int(4字节),用来存放数据五。那么每个内存单元都会有相对应的地址。
那如何取出对应的a地址呢?我们就需要用到取地址操作符&。&这个操作符取出的地址是较小的地址,也可以说是第一个字节的地址。
#include
int main()
{
int a = 5;
&a;
printf("%p\n",&a);
return 0;
}
我们通过取地址操作符(&)拿到的地址是⼀个数值,例如上图的:010FFCB4,这个数值有时候也是需要存储起来,那我们把这样的地址值存放在指针变量中。
#include
int main()
{
int a = 5;
&a;
int* ptr = &a;
return 0;
}
如何理解上述代码?
prt的类型是int*,a的地址是0x010FFCB4,我们通过取地址操作符把a的地址拿出来放到p的空间里。
我们将地址保存起来,那如何去使用地址呢?
我们用*来对指针变量解引用,获得p存放的地址指向变量的值。我们把*也叫做解引用操作符,或者是间接引用操作符。
#include
int main()
{
int a = 5;
int* p = &a;
*p = 0;
printf("%d\n", *p);
return 0;
}
此时*p就相当于a,通过修改*p,我们能修改a的值。
关键字sizeof可以用来计算指针变量的大小,来看下面一段代码:
#include
int main()
{
int a = 5;
int *p = &a;
char c = 'w';
char* pc = &c;
printf("%d ",sizeof(p));
printf("%d ",sizeof(pc))
}
如果编译器在x86(32位)的环境下,那么此时两个值都为4,即是4个字节,如果是x64(64位),那么二者的值都为8,即八个字节。
指针变量是为了存放地址,所以其大小跟数据类型无关,32位平台下地址是32个bit位,指针变量大小是4个字节,64位平台下地址是64个bit位,指针变量大小是8个字节,只要指针类型的变量,在相同的平台下,大小都是相同的。
首先是看以下类型,在不同环境下的内存大小
x64:
x86:
此时num的地址:
此时p的地址:
请读者思考以下代码
1.p指针变量能否存放下以下地址
2.p指针变量解引用后修改几个字节?
#include
int main()
{
int num = 0x11223344;
char* p = #
*p = 0;
return 0;
}
答案:
p能存放下num的地址,都是4字节,但其修改时只修改了一个字节。因此类型的作用便是让p在解引用的时候操作的空间大小,例如char操作一个字节,int操作四个字节。
指针加减整数
#include
int main()
{
int num = 0x11223344;
int* pn = #
char* p = #
printf("&num = %p\n", &num);
printf("pn地址 = %p\n", pn);
printf("pn+1地址 = %p\n", pn + 1);
printf("p地址 = %p\n",p);
printf("p+1地址 = %p\n", p + 1);
return 0;
}
结果如下所示:
指针加减整数的距离(空间大小)取决于指针类型,即int走四个字节,char走一个字节。
首先我们来回顾一下数组下标访问
因为这里我们定义的是整形数组,所以我们要是想用指针访问数组的话,就必须使用整型指针,即int*。
#include
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
//指针访问
int* p = arr;
for (i = 0; i < sz; i++)
{
printf("%d " , *(p + i));
}
return 0;
}
p+i是我们走的步长,比如我们从开始走,一次访问越过四个字节,如下图所示
我们对p+i进行解引用后,得到就是数组存储的值;
地址是从低到高变化的。
我们知道变量是可以修改的,如果我们不希望这个值被修改,我们要对它进行限制,那const关键字的用法就是限制变量不被修改
例如以下代码
#include
int main()
{
const int i = 6;
printf("%d ", i);
i = 4;
return 0;
}
编译器提示我们,表达式左边必须是可修改的。但是我们现在学习了指针,我们可以绕过const这个门卫。
但是我们现在也不希望任何情况都能修改i的值呢?
接下来我们看const修饰指针,const修饰指针分两种情况,一种是在*左边,一种是在*右边。
假设我们把const放在*左边
#include
int main()
{
int n = 5;
int m = 50;
const int* p = &n;
*p = 4;
p = &m;
return 0;
}
假设我们把const放在左边,修改*p和p的值,来验证const有什么效果,编译器提示我们
所以const如果放在*的左边,限制的是指针所指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本⾝的内容可变。
同理验证const如果放在*右边,
所以const如果放在*的右边,限制的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针所指向的内容,可以通过指针改变。
如果*两边都有const呢?
那它就会集齐两者,使其变成完全不能修改的p。
我们说指针就是地址,所以指针减去指针其实就是地址减去地址,但注意指,针减去指针是有前提的,必须在一段空间内才能进行相减,也就是两个指针必须指向同一段空间。
#include
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int ret = &arr[9] - &arr[0];
printf("%d ", ret);
ret = &arr[0] - &arr[9];
printf("%d ", ret);
return 0;
}
我们得到的结果是九和负九
所以指针减去指针,其的绝对值就是元素的个数。
指针的关系运算,就是地址的关系运算,地址相互比较。
我们可以利用关系运算来打印数组
#include
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
while (p < arr + sz)
{
printf("%d ", *(p++));
}
return 0;
}
结果如下:
概念:野指针说的是指针指向的位置是未知的,不清楚的,(随机的、不正确的、没有明确限制的)
第一种情况是,指针未初始化,造成野指针
第二种情况是,指针越界访问
第三种情况是,指针指向的空间释放
#include
int* test()
{
int n = 5;
return &n;
}
int main()
{
//指针未初始化
int* p1;//野指针
*p1 = 20;
//指针越界访问
int arr[10] = { 0 };
int* p2 = &arr[0];
int i = 0;
for (i = 0; i <= 11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p2++) = i;
}
//指针指向的空间释放
//局部变量出作用域会销毁,如果此刻返回那么指针就是野指针
int* p3 = test();
return 0;
}
为了规避野指针必须注意的几个点:
1.用NULL赋值指针,初始化指针
2.在写前思考清楚,避免越界访问
3.当指针变量不再使⽤时,及时置NULL,指针使用之前检查有效性
NULL 是C语言中定义的⼀个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址会报错。
最经典的就是交换函数中的两个值了
void Swap(int* n1,int* n2)
{
int tmp = *n1;
*n1 = *n2;
*n2 = tmp;
}
int main()
{
int a = 5;
int b = 10;
Swap(&a, &b);
printf("%d %d", a, b);
return 0;
}
结果如下:
指针加减整数,也向前文讲述的差不多,因为数组在内存中是连续存储的,所以我们通过知道数组首元素地址,就可以知道后面的数组元素值。指针加减整数,它所走的空间大小就是类型的大小。
在这里说明一下p+i等价于&arr[i], *(p+i)==arr[i],
例:字符数组的打印
#include
int main()
{
char arr[] = "abcdef";
char* pc = arr;
while (*pc != '\0')
{
printf("%c ", *(pc++));
}
return 0;
}
C语言提供了一个宏,在头文件assert.h 中定义了宏 assert(),用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为“断言”。
assert(p);
assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值非0), assert()不会产生任何作用,程序继续运行。如果该表达式为假(返回值为零), assert() 就会报错。我们常常使用它来检查指针是否非0。
当p为空指针,则这里会提示以下报错。
如果我们想在debug的调试下关闭assert断言,我们需要用下列代码
#define NDEBUG
计算机常见的存储单位:bit, Byte, KB, MB, GB, TB, PB,……
换算关系:
感谢各位同伴的支持,本期指针就讲解到这啦,如果你觉得写的不错的话,可以给个赞,若有不足,欢迎各位在评论区讨论。