C语言-第11章-指针

本文详细介绍了C语言中的指针概念,包括变量的地址、指针变量的声明与赋值、NULL地址的使用,以及如何通过指针在函数间传递和修改变量值。还探讨了指针与数组的关系,包括指针算术运算、通过指针处理数组,以及数组形式参数实为指针参数。此外,讲解了const修饰符在指针中的应用,区分了常量指针和指针常量。最后,提到了指针在处理字符串和二维数组中的应用。
摘要由CSDN通过智能技术生成

11.0 导入例子

void swap(int x, int y){
   
  int temp;
  temp = x;
  x = y;
  y = temp;
}
int main(){
   
  int x = 10, y = 20;
  swap(x, y);
  printf("x = %d, y = %d\n", x, y);
  getchar();
  return 0;
}

在这里插入图片描述
函数章节中已经指出上述代码无法交换main函数中x和y的值,因为swap函数中的x和y与main函数中的x和y不一对不同的变量,swap函数中的x和y交换了值,但是对main函数中的x和y没有影响。怎样定义一个可以交换main函数中x和y的值的函数呢?这就需要用到指针变量,让它们指向x和y,这样这些指针变量就可以直接访问x和y了。

void swap(int *p, int *q){
     // p指向x,q指向y,*p就是x的别名,*q就是y的别名
  int temp;
  temp = *p;
  *p = *q;
  *q = temp;
}
int main(){
   
  int x = 10, y = 20;
  swap(&x, &y); // 将x和y的地址传给函数
  printf("x = %d, y = %d\n", x, y);
  getchar();
  return 0;
}

在这里插入图片描述

11.1 指针基本概念

指针变量用来存储变量的地址,用指针变量可以对变量进行间接访问,相当于给变量起了别名。

11.1.1 变量的地址

每个变量都有一个内存位置,各变量的内存可能有多个字节组成,首字节(地址值最小的字节)的地址称为变量的地址。比如,下图中变量i占的地址为从2000到2003共4个字节,所以变量i的地址是2000。在这里插入图片描述
&称为取地址运算符,可以得到变量的地址:

&变量名

printf输出地址值需要使用转换说明符%p:

#include <stdio.h>

int main ()
{
   
   int  var1;
   float var2;

   printf("var1 变量的地址: %p\n", &var1  );
   printf("var2 变量的地址: %p\n", &var2  );

   getchar();
   getchar();
   return 0;
}

在这里插入图片描述
注意,每次运行程序变量的地址可能会变化。

注意,不能对表达式取地址,比如

int x = 0;
printf("%p", &(x + 1));  // 编译报错

11.1.2 指针变量

指针变量是专门存储变量地址值的一类变量。需要根据预计存储的地址值是属于哪种类型的变量,而声明该类型的指针变量。比如,声明存储整型变量地址的int指针变量,

int *p;

声明存储双精度浮点类型变量地址的double指针变量,

double *q;

指针变量可以和其它变量一起出现在声明中:

int i, j, a[10], *p;

int类型的指针变量可以也只能保存某个int变量的地址:

int i, *p;
...
p = &i;

p存储了i的地址,也可以说p指向了i。输出指针变量的值同样使用转换说明%p。

...
printf("%p\n", p);

在这里插入图片描述
也可以在声明指针变量的同时对它初始化

int i;
int *p = &i;

甚至可以把i的声明和p的声明合并,但是需要先声明i:

int i, *p = &i;

一旦p指向了某个变量,比如i,那么就可以使用间接寻址(引用)运算符*来访问那个变量(存储在变量中的值)。

int i, *p = &i;
i = 10;
printf("%d\n", *p);  // 显示10

实际上,只要p指向i,那么*p就是i的别名(小名),*p和i是等价的“变量名”。
在这里插入图片描述

int i, *p = &i; // p指向了i,*p是i的别名
i = 10;
printf("%d\n", *p);  // 显示10
i = 20;
printf("%d\n", *p);  // 显示20
*p = 30;
printf("%d\n", i);  // 显示30

注意,C语言要求每个指针变量只能指向一种特定类型的变量:

int *p;  // 只能指向int类型变量
double *q;  // 只能指向double类型变量
char *r;  // 只能指向char类型变量

可能有人会说不都是变量的地址吗,这些地址值都是整数,为什么不能用int整型变量来保存各种类型变量的地址呢?首先地址范围和整型范围可能是不同的,其次,更重要的是指针变量最重要的作用是通过地址访问它指向的变量,因为不同类型的变量有不同的处理机制,所以指针变量要知道它指向的变量的类型,故指针变量本身是有类型的。

下面代码是在声明指针变量p的同时给它赋初值

int i;
int *p = &i;

相当于

int i, *p;
p = &i;

在p已经被声明过后,并且p已经指向了某个变量后,*p就是那个变量的别名。再出现类似的赋值就是一种含义上的错误。

int i, j;
int *p = &i;
*p = &j;  // 含义上的错误
*p = 10;  // 等价于将10赋值给i

注意,下面声明

int *p, q;

声明了指向整型变量的指针变量p和普通整型变量q。

int* p, q;

如果把*和int放在一起,声明的两个变量p和q,仍然p是指针变量,q是整型变量。因为本质上*是用来修饰p的,指针变量的声明

int *p;

不论*是紧邻nt还是p,都应该被解读为,*说明p是指针变量,而前面的int说明p是用来指向int变量的。这与数组的声明有点类似,

int a[10];

如果要声明两个指针变量,需要在每个指针变量前放置*

int *p, *q;

11.1.3 指针变量的赋值

如果两个指针变量类型相同,可以相互赋值。假设声明如下,

int i, j, *p = &i, *q;

赋值语句

q = p;

将p的值(i的地址)赋值给q,效果是q和p同指向i
在这里插入图片描述
由于p和q都指向i,可以通过p和q来访问i

*p = 1;

// 图

*q = 2;

在这里插入图片描述
不要把

q = p;

*q = *p;

混淆了。第一句话是指针赋值,第二句话不是,它是指针引用的变量之间的赋值。比如,

p = &i;
q = &j;
i = 1;

在这里插入图片描述

*q = *p;

在这里插入图片描述
上述赋值相当于

j = i;

11.1.4 用NULL地址来安全的使用指针变量

就像普通变量没有赋初值的时候不能访问(读取它的值),以及访问数组元素时下标不能越界(访问了未申请的地方),指针变量也是这样,

int *p;
printf("%d\n", *p);

因为p还没有初始化,它的值可能是乱七八糟的值,或者说它指向了某个未知的地方,对这个地方的访问会导致未定义的行为。
如果未初始化的p的值恰好是有效的内存地址,下面的赋值会试图修改存储在该地址的数据

int *p;
*p = 1;  // 错误

对内存中未知位置的修改是危险的,可能会导致未定义的行为或者系统崩溃。只有让指针指向某个声明了的变量后,对p的间接引用才是安全的,因为此时实际上是对那个变量的引用。
为了安全的访问指针变量,在没有明确p应该指向哪个变量之前(对p进行实际的初始化),可以用地址值NULL来初始化指针变量

int *p = NULL;

NULL 指针是定义在头文件stdio.h中的值为零的常量。

#define NULL    0  // 在stdio.h头文件中

在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。如果指针变量的值为0则明确的表明该指针不指向一个有效的(可访问的)内存位置,或者说它还没有指向某个变量,不能对它进行访问(使用间接寻址运算符*),如果对它访问,则系统一定检测出异常。

#include <stdio.h>

int main ()
{
   
   int  *ptr = NULL;
   printf("ptr 的地址是 %p\n", ptr  );
   
   *ptr = 1;  // 导致程序检测出异常
   
   getchar();
   return 0;
}

在这里插入图片描述
这比只声明指针变量p,但是没有对它初始化,即让它的值是完全未知的好,因为万一未初始化的p的值恰好指向有效的内存地址,上面的赋值语句会试图修改存储在该地址的数据。而此时系统可能检查不出来这样的问题。这是程序很大的隐患。

为了进一步提高程序的安全性,还可以在使用指针变量前,判断它的值是否为空:

int *p = NULL;
...  // 在使用p之前,应该让p指向某个整型变量
if (p != NULL){
   
  // p已经指向了某个变量,可以使用*p了
}else{
   
  // p没有指向某个变量,不能使用*p
}

11.1.4 常见问题

问题1. 只能给变量或者说“左值”取地址,表达式不可以。

int i = 0, *p;
p = &i;  // 正确,i是变量,即左值,有明确的地址
p = &(i + 10);  // 错误,i+10是表达式,不属于“左值”,不能对它取地址

问题2. 指针变量还未初始化就访问

int *p;
*p = 10;  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值