C语言系列7-指针-野指针-二级指针-指针运算-指针与数组(适合期末考指针知识点复习)


前言

在 C 语言中,指针是一个很重要的概念,它是用来存储内存地址的变量,本质上是一个整数。指针可以被声明为指向特定类型的指针变量,这样就可以通过指针来访问内存中具有特定类型的数据对象。


一、指针是什么?

指针理解的2个要点:

  1. 指针是内存中一个最小单元的编号,也就是地址
  2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
    总结:指针就是地址,口语中说的指针通常指的是指针变量。
    **指针变量:**我们可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个变量就是指针变量
    下面用一段代码举例
#include <stdio.h>
int main()
{
	 int a = 10;//在内存中开辟一块空间
	 int *p = &a;//这里我们对变量a,取出它的地址,使用&操作符。
	    		//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
				//中,p就是一个之指针变量。
	 return 0;
}

以上就是一个简单的整形指针,我们可以通过它来修改原来整形变量的值

二、指针和指针类型

1.指针的类型

char  *pc = NULL;
int   *pi = NULL;
short *ps = NULL;
long  *pl = NULL;
float *pf = NULL;
double *pd = NULL;

指针的类型决定了指针做加减运算时一步的大小
下面用一段代码帮助理解

#include <stdio.h>
//演示实例
int main()
{
	 int n = 10;
	 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;
}

在这里插入图片描述

2.指针的解引用

下面讲解一段代码帮助理解

#include <stdio.h>
int main()
{
	int n = 0x11223344;//用16进制表示整型,每两位代表1个字节,共四个字节
	printf("%d\n", n);
	char* pc = (char*)&n;
	int* pi = &n;
	*pc = 0;
	printf("%d\n", n);
	*pi = 0;
	printf("%d\n", n);
	return 0;
}

在这里插入图片描述
通过结果可以发现,通过char类型的指针对n进行修改,n减少了68,这是因为仅修改了第一个字节,也就是指针解引用能操作几个字节是由指针的类型决定的,这也同时验证了这是一台小端机(如果对小端机的概念有所遗忘,可以回去看C语言系列6),因为最低位为十六进制数44,换算成十进制数为68,可以发现n的第一个字节被修改为0。同理,用整型指针pi进行修改,直接将4个字节都改成0了,打印结果自然是0。
总结: 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。 比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。


三、野指针

在C语言中,野指针是指未初始化或未赋值的指针变量,它们指向一个不确定的内存位置,可能是一个无效的地址,也可能指向已释放的内存区域。野指针问题指的是在程序中错误地使用了野指针,从而导致程序崩溃或出现不可预知的结果。下面介绍一些导致野指针问题的常见情况

1.指针未初始化

#include <stdio.h>
int main()
{
	int* p;//局部变量指针未初始化,默认为随机值
	*p = 20;
	return 0;
}

此时一般会发生编译错误,因为使用了未初始化的局部变量“p”

2. 指针越界访问

#include <stdio.h>
int main()
{
    int arr[10] = {0};
    int *p = arr;
    int i = 0;
    for(i=0; i<=11; i++)
   {
        //当指针指向的范围超出数组arr的范围时,p就是野指针,此时的后果无法预知
        *(p++) = i;
   }
    return 0;
}

3. 指针指向已释放的内存区域

int *ptr = malloc(10 * sizeof(int));
free(ptr);
*ptr = 20;     //野指针

避免野指针问题的方法是始终将指针初始化为NULL或有效内存地址,并在使用指针之前进行有效性检查。例如:

int *ptr = NULL;    //初始化为NULL
ptr = malloc(10 * sizeof(int));
if (ptr != NULL)    //检查指针是否有效
{
    *ptr = 20;
}

此外,还应注意在使用指针时遵循以下原则:
在使用指针之前,检查它是否为NULL。
永远不要访问未初始化或未分配的指针。
指针指向的内存区域已释放后,将指针设置为NULL,防止继续使用野指针。
综上所述,野指针问题是C语言编程中常见的错误,可以通过始终初始化指针变量,检查指针的有效性和遵守使用指针的原则来避免。


四、 指针运算

1. 指针+-整数

+ 运算符用于将指针的值增加指定的字节数,以便指向另一个内存地址。例如,如果 p 是一个指向整数的指针,则 p+1 将返回指向下一个整数的指针。
- 运算符用于将指针的值减去指定的字节数,以便指向前一个内存地址。例如,如果 p 是一个指向整数的指针,则 p-1 将返回指向前一个整数的指针。

2.指针-指针

两个指针之间的减法运算将返回它们之间的地址差。这个地址差的值是一个整数,表示第一个指针所指向的内存地址与第二个指针所指向的内存地址之间的字节数。

3.指针的关系运算

指针比较运算符包括等于()、不等于(!=)、大于(>)、小于(<)、大于等于(>=)和小于等于(<=)运算符。
特别需要注意的有一下几个点
1.只有指向同一数组或同一对象的指针才能进行比较运算。如果两个指针指向不同的数组或对象,则其比较结果是未定义的。
2.当两个指针指向同一个数组或对象时,指针的比较结果取决于它们的内存地址值。指针的比较运算是基于指针所指向的内存地址进行的,地址值较小的指针被认为是“小于”地址值较大的指针,即地址值越小,指针的“大小”越小。
3.除了相等运算符(
)和不等运算符(!=)外,其他指针比较运算符的结果在大多数情况下都是未定义的。这是因为指针之间的距离不是固定的,取决于它们所指向的类型和机器的字长等因素。

int a[10];
int *p1 = &a[0];
int *p2 = &a[1];
if (p1 == p2) {
    printf("p1 and p2 are equal.\n");
} else if (p1 < p2) {
    printf("p1 is less than p2.\n");
} else {
    printf("p1 is greater than p2.\n");
}

在这个示例中,指针 p1 和 p2 分别指向数组 a 中的第一个和第二个元素。由于它们指向同一个数组,因此可以使用比较运算符来比较它们。如果 p1 等于 p2,则输出“p1 and p2 are equal.”,否则如果 p1 小于 p2,则输出“p1 is less than p2.”,否则输出“p1 is greater than p2.”。

需要注意的是,在使用指针比较运算符时需要特别小心,以避免未定义的行为和错误的结果。比较之前必须确保指针指向有效的内存区域,并且它们指向同一个数组或对象。


五、指针和数组

数组名在大部分情况下表示的是首元素地址,所以通过指针可以访问数组元素,下面给出一段代码

#include <stdio.h>
int main()
{
    int arr[] = {1,2,3,4,5,6,7,8,9,0};
    int *p = arr; //指针存放数组首元素的地址
    int sz = sizeof(arr)/sizeof(arr[0]);
    for(int i=0; i<sz; i++)
   {
        printf("&arr[%d] = %p   <====> p+%d = %p\n", i, &arr[i], i, p+i);
   }
    return 0;
}

在这里插入图片描述

int main()
{
	 int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	 int *p = arr; //指针存放数组首元素的地址
	 int sz = sizeof(arr) / sizeof(arr[0]);
	 int i = 0;
	 for (i = 0; i<sz; i++)
	 {
	 	printf("%d ", *(p + i));
	 }
	 return 0;
}

六、二级指针

二级指针是指向指针的指针,也称为指向指针的指针,被用于进行多层间接引用
二级指针可以用于需要传递指针的函数中,这样可以更灵活地处理指针变量,并且在进行多重间接存储时也非常方便。下面是一个使用二级指针的示例代码:

#include <stdio.h>

void fun(int **p)
{
    **p = 10;
}

int main()
{
    int a = 0;
    int *p = &a;

    fun(&p);

    printf("a = %d\n", a);

    return 0;
}

七、字符指针

int main()
{
    const char* pstr = "hello world.";//这里是把一个字符串放到pstr指针变量里了吗?
    printf("%s\n", pstr);
    return 0;
}

这里把字符串首元素的地址放到pstr中


八、指针数组

指针数组是由指针元素组成的数组,其中每个元素都是一个指向另一个数据类型的指针。每个指针元素都存储着指向另一个数据类型的地址,因此可以通过指针访问需要的数据。

下面是一个简单的指针数组的例子:

int a = 1, b = 2, c = 3;
int *arr[3] = {&a, &b, &c};

在这个例子中,arr 是一个指针数组,包含了三个指针元素,每个指针元素都指向一个整型变量。具体来说,第一个指针指向变量 a,第二个指针指向变量 b,第三个指针指向变量 c。

使用指针数组,我们可以遍历每个元素,并使用指针访问该元素指向的数据类型:

for (int i = 0; i < 3; i++) 
    printf("%d\n", *arr[i]);

这里的 * 用于解除指针的引用,使我们能够访问指针指向的数据类型。在本例中,它将分别打印 1、2 和 3。


九、数组传参

1.一维数组传参

void test(int arr[])
{}
void test(int arr[10])
{}
void test(int* arr)
{}
int main()
{
	int arr[10] = { 0 };
	test(arr);//三种test均等价
}

2. 二维数组传参

void test(int arr[3][5])
{}
void test(int arr[][5])
{}
void test(int* arr[5])//ok?
{}
void test(int(*arr)[5])//ok?
{}
void test(int** arr)//ok?
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。这样才方便运算。


总结

本文讲解了一级指针、二级指针,以及对应的传参

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值