指针变量和地址
1. 取地址操作符(&)
作用:可以获取元素再内存当中的地址;如下图
(%p可以用来打印变量的地址)
2.引用操作符(*)
作用:通过取地址操作符(&)拿到一个变量的地址,而通过引用操作符(*)可过地址,找到其指向的空间。
3.指针变量
1)通过取地址操作符(&)拿到的地址是一个数值,比如:0x006FEC21,我们将此值储存在指针变量当中。因此,指针变量也是变量,用来储存变量的地址。
2)拆解指针变量
int a = 110;
int * p = &a;
例如:指针变量 int*p,
p左边写的是 int*,* 是在说明p是指针变量,而前前面的 int 是在说明p指向的是整型类型的对象。(char*类似);
3)指针变量的大小
32位平台下地址是32个bit位,指针变量大小是4个字节
64位平台下地址是64个bit位,指针变量大小是8个字节
注意:注意指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的
4)指针变量的作用
1}指针+-整数
例如:
#include <stdio.h>
int main()
{
int n = 110;
char *pc = (char*)&n;
int *pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc+1);
printf("%p\n", pi);
printf("%p\n", pi+1);
return 0;
}
其结果为:
我们可以看到第二行与第三行之间差一,第五行与第四行之间差四,这便是因指针变量引起的。char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。因此我们可以得出一个结论:指针的类型决定了指针向前或者向后走一步有多大(距离)。
2指针的解引用
例如:
通过上面两组数据我们可以知道不同指针变量类型之间的解引用是不一样的,而出现上图结果的原因便是因为int*会将n的4个字节全部改为0,但是char*只是将n的第⼀个字节改为0。(int有四个字节)。
因此我们可以又得出一个结论:指针的类型决定了,对指针解引用的时候有多大的权限即⼀次能操作几个字节)。 比如: char* 的指针解引用就只能访问⼀个字节,而 int* 的指针的解引用就能访问四个字节。
4.指针的运算
1)指针 - 指针
例如模拟strlen函数的实现刻印通过指针-指针的方式
//指针-指针(模拟实现strlen)
#include <stdio.h>
int strlen1(char* s)
{
char* p = s;
while (*p != '\0')
p++;
return p - s;
}
int main()
{
char ac[] = "abc";
printf("%d\n", strlen1(ac));
return 0;
}
其结果为: 3.
而如下图:
我们可以看到指针p与指针s之间相隔3个元素,而结果为3,通过这个例子我们可以得出
指针 - 指针的结果为两指针之间的元素个数
2)指针+-整数
因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸瓜就能找到后面的所有元素。因此遍历数组时我们可以用此方式。(数组名便是数组首元素的地址)
例如:
//指针+- 整数
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;
}
5.野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
出现的方式:
1)指针未初始化;
#include <stdio.h>
int main()
{
int *p;
*p = 20;
return 0;
}
此代码局部变量*p指针未初始化,默认为随机值,所以此指针指向的位置不确定,要解决应该使其指向一个明确的空间如:
int a = 1;
int *p = &a;
2)指针越界访问
如
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = &arr[0];
int i = 0;
for(i=0; i<=11; i++)
{
*(p++) = i;
}
return 0;
}
当指针指向的范围超出数组arr的范围时,p就是野指针,如当i = 11时边超出数组的界限,属于越界访问
而此时VS2022也会报错提醒;
3) 指针指向的空间释放
如
include <stdio.h>
int* test()
{
int n = 100;
return &n;
}
int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}
我们看似乎没有问题,但是其实也是不对的,test函数使用时在栈中创建,使用完后便销毁,而我们便没有使用此空间的权限,而n碰巧在函数当中,因此我们使用指针变量p接收n的地址是错的,
此代码的结果是随机值(也有可能不变,因为要有函数再次使用此空间,将n = 100的地址覆盖掉)
4)规避野指针
1:指针初始化
如果定义时不确定将此指针指向哪里,那么应该及时将此指针指向NULL;
注:NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址 会报错。
2:小心指针越界
程序申请了那些空间,则指针指针只能指向这些空间,超出便属于越界。
3:指针变量不再使用时,及时置NULL,指针使用之前检查有效性
当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使用这个指针访问空间的时候,我们可以把该指针置为NULL。而使用前我们可以通过assert断言来检查其有效性。
4:避免返回局部变量的地址
6.assert断言
其头文件为:<assert.h>
作用:用于在运行时确保程序符合指定条件,如果不符合,就报 错终止运行。这个宏常常被称为“断言”。(有利于程序员检查程序)
assert() 宏接受一个表达式作为参数。如果该表达式为真,其对于程序正常运行不会造成任何影响,而如果表达式为假,它便会报错。
注意: 如果已经确认程序没有问 题,不需要再做断言,就在 #include 语句的前面,定义⼀个宏 NDEBUG :
#define NDEBUG
#include<assert.h>