🎀 文章作者:二土电子
🐸 期待大家一起学习交流!
1 指针
1.1 指针的定义
指针是内存单元的地址,它可能是变量的地址、数组的地址,或者是函数的入口地址。存储地址的变量称为指针变量,简称为指针
。定义指针的格式如下
int* p;
char* p;
在使用指针时,需要明确两个概念,指针对象和指针指向的对象。指针对象
指的是明确命名的指针变量,也就是上面的p。指针指向的对象
是另一个变量,用*p来表示。
上面定义指针时,没有对指针进行初始化。未初始化的指针变量的值是随机的。在使用指针时,如果没有对指针进行初始化,可能会导致程序运行时出现非法指针访问错误,从而使程序异常终止。因此,在定义指针时需要对指针初始化
。
如果在定义指针时,并不能确定指针变量的初始值。可以将指针定义成空指针。
int* p = NULL;
或者
int* p = 0;
有一种特殊的指针类型void,void类型可以与任意的数据类型匹配。void 指针在被使用之前,必须转换为明确的类型。
1.2 “&”和“*”
“&”是地址运算符,它的作用是获取变量的地址。“*”是间接运算符,它的作用是获取指针所指向的变量。可以通过一个简单的程序,直观地了解一下这两个运算符的作用
#include<stdio.h>
int main()
{
int i = 0;
int* p = &i; // 获取i的地址
*p = 10; // 给i赋值
printf("**************************\n");
printf("i的值为:%d\n",i);
printf("**************************\n");
return 0;
}
输出内容为
**************************
i的值为:10
**************************
有一种特殊类型的指针void,void类型可以与任意的数据类型匹配。void 指针在被使用之前,必须转换为明确的类型
。
#include<stdio.h>
int main()
{
int i = 0;
void* p = &i; // 获取i的地址
// void 指针在被使用之前,必须转换为明确的类型
*(int*)p = 10; // 给i赋值
printf("**************************\n");
printf("i的值为:%d\n",i);
printf("**************************\n");
return 0;
}
1.3 指针与堆内存
堆内存能够被动态地分配和释放,在 C 程序中通过malloc(或 calloc、realloc) 和 fee 函数实现对内存的分配和释放。
malloc是动态内存分配函数,用于申请一块连续的指定大小的内存块区域,以void*类型返回分配的内存区域的首地址。如果内存分配失败,会返回NULL。malloc函数的输入值是要开辟的内存所占的字节数。在使用malloc函数时,需要声明malloc.h头文件。可以通过一个简单的程序来了解一下malloc函数的使用方法
#include <stdio.h>
#include <malloc.h>
int main()
{
// 分配一个4字节内存,p指向首地址
int* p = (int*)malloc(sizeof(int));
// 分配一个8字节内存,c指向首地址
char* c = (char*)malloc(8 * sizeof(char));
*p = 10; // 将10存储到第一个内存块
strcpy(c,"20230712"); // 将一串字符串存储到第二块内存块
printf("**************************\n");
printf("第一块内存内容为:%d\n",*p);
printf("第二块内存内容为:%s\n",c);
printf("**************************\n");
return 0;
}
输出结果为
**************************
第一块内存内容为:10
第二块内存内容为:20230712
**************************
因为内存资源是有限的,所以若申请的内存块不再需要就及时释放
。如果程序中存在未被释放(由于丢失其地址在程序中也不能再访问) 的内存块,则称为内存泄漏。持续的内存泄漏会导致程序性能降低,其至崩溃。释放内存块使用的是free函数。使用后该指针变量一定要重新指向NULL,防止悬空指针(失效指针)出现,有效规避错误操作
。
free (p); // 释放第一块内存
*p = 0; // 将p重新设置为空指针
free (c); // 释放第二块内存
*c = 0; // 将c重新设置为空指针
1.4 指针运算
指针变量可以加上或者减去整数值,叫做指针的运算。指针运算会在利用指针访问数组元素时用到,下面会介绍。
#include <stdio.h>
int main()
{
int* p = 0x20020;
p = p + 1;
printf ("p的地址为:%p",p);
return 0;
}
输出结果为
p的地址为:0x20024
加1的意思是指向下一个地址。注意这里的加1实际是往后推了4字节的内容。具体往后推几个字节,取决于指针变量的类型
。
1.5 常量指针与指针常量
1.5.1 常量指针
指针指向的对象时常量,这个指针叫做常量指针。
const int* p = 0; // 定义一个常量指针
在定义时,const修饰p,而p是指针指向的对象。它可以等效定义为
int const* p = 0; // 定义一个常量指针
常量指针由于指针指向的对象是常量,所以指针指向对象的值是不可以被改变的。但是指针变量本身的值可以改变。
1.5.2 指针常量
指针常量是指指针本身是一个常量,不能再指向其他对象。
int* const p = 0; // 定义一个指针常量
由于指针常量中,指针本身是一个常量,所以指针的值不能被改变。但是指针指向对象的值可以被改变。
1.6 函数指针
在C语言中,可以将函数地址保存在函数指针变量中,然后用该指针间接调用函数
。函数指针的定义方法是
函数的返回值类型(*指针名)(函数的参数列表类型)
看一个简单的例子
#include <stdio.h>
int add (int a,int b)
{
return a + b;
}
int main()
{
int i = 0;
int (*p)(int,int) = &add; // 定义函数指针
i = p(2,3);
printf ("i=%d",i);
return 0;
}
输出结果为
i=5
在定义函数指针时。&函数名,是函数的起始地址
。
2 指针与数组
可以利用指针去访问数组。看一个简单的例子
#include <stdio.h>
int main()
{
int temp = 0; // 临时循环变量
char a[10];
char* p = &a[0]; // 定义一个指针,指向数组起始地址
// 利用指针给数组成员赋值
for (temp = 0;temp < 10;temp ++)
{
*(p + temp) = temp;
}
// 利用指针读取数组成员的值
for (temp = 0;temp < 10;temp ++)
{
printf ("a[%d]=%d\n",temp,*(p + temp));
}
return 0;
}
输出结果为
a[0]=0
a[1]=1
a[2]=2
a[3]=3
a[4]=4
a[5]=5
a[6]=6
a[7]=7
a[8]=8
a[9]=9
3 指针与函数
指针可以作为函数的输入参数或者返回值。
这里看一个常见的函数,利用指针交换两个参数的值
#include <stdio.h>
void swap (int* x,int* y);
int main()
{
int i = 10;
int j = 100;
swap (&i,&j);
printf ("i=%d,j=%d",i,j);
return 0;
}
void swap (int* x,int* y)
{
int temp = *x;
*x = *y;
*y = temp;
}
输出结果为
i=100,j=10
其中指针x和y是函数swap的输入参数。
指针也可以作为函数返回值,但是把指针作为返回值不是很推荐
。
4 指针与链表
4.1 链表
什么是链表?链表是一种动态地进行存储分配的数据结构。链表中的每一个元素称为一个结点。每个结点都应该包含两个部分,分别是用户实际需要的数据和下一个结点的地址。链表有一个头指针变量,这个变量存放一个地址,这个地址指向一个元素。链表的最后一个元素称为表尾,它的地址部分存放NULL。
链表中各个元素的地址可以是不连续的。要找某一个元素,必须先找到该元素的上一个元素,根据上一个元素提供的地址来找到下一个元素。如果不知道头指针,那么整个链表都无法访问。链表的基本结构如下
4.2 链表的建立与输出
这里通过一个例子来展示一下链表的建立与输出。
#include<stdio.h>
struct student
{
char* name;
int age;
char* number;
struct student* next; // 下一个结点的地址
}student;
int main()
{
struct student a,b,c;
struct student* head; // 头指针
struct student* p; // 用来访问链表的指针
// 结构体成员赋值
a.name = "ertu";
a.age = 23;
a.number = "001";
b.name = "yingting";
b.age = 18;
b.number = "002";
c.name = "yitong";
c.age = 14;
c.number = "003";
head = &a; // 头指针
a.next = &b;
b.next = &c;
c.next = NULL; // 表尾
p = head;
// 输出链表内容
while (p != NULL)
{
printf("**************************\n");
printf("Name:%s,Age:%d,Number:%s\n",p->name,p->age,p->number);
printf("**************************\n");
p = p->next;
}
return 0;
}
输出结果为
**************************
Name:ertu,Age:23,Number:001
**************************
**************************
Name:yingting,Age:18,Number:002
**************************
**************************
Name:yitong,Age:14,Number:003
**************************