前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >第七节(指针)

第七节(指针)

作者头像
冷影玺
发布2023-10-11 19:34:49
1930
发布2023-10-11 19:34:49
举报
文章被收录于专栏:冷影玺

本次将详细介绍指针,是C语言中的一 个重要部分。

在程序中,指针提供强大而灵活的方法来操纵数据。

本次将介绍以下内容:

●指针的定义

●指针的用途

●如何声明和初始化指针

●如何将指针用于简单变量和数组

●如何用指针给函数传递数组

使用指针有两方面的优势:

其一,用指针能更好地完成某些任务;

其二,有些任务只能用指针才能完成。

一.什么是指针:

在学习什么是指针之前,必须先了解计算机如何在内存中储存信息的基本知识。

下面,将简要地介绍计算机的存储器。

1.1 计算机的内存

计算机的内存(RAM) 由数百万个顺序存储位置组成,每个位置都有唯一的地址。

计算机的内存地址范围从0开始至最大值(取决于内存的数量)。

运行计算机时,操作系统要使用一些内存。

运行程序时,程序的代码(执行该程序中不同任务的机器语言指令)和数据(该程序使用的信息)也要使用一些内存。

这里讨论的是储存程序数据的内存。

在C程序中声明一个变量时,编译器会预留一个内存位置来储存该变量,此位置有唯一的地址。

编译器把该地址与变量名相关联。当程序使用该变量名时,将自动访问正确的内存位置。

虽然程序使用了该位置的地址,但是对用户是隐藏的,不必关心它。

下面用图来帮助你理解。程序声明了一个名为rate的变量,并将其初始化为100。

编译器已经在内存中将地址为1004的位置留给了该变量,并将变量名rate与地址1004 相关联。

第七节(指针)_运算符
第七节(指针)_运算符
1.2 创建指针

注意,rate变量或任何其他变量的地址都是一个数字(类似于C语言的其他数字)。

如果知道一个变量的地址,便可创建第2个变量来储存第1个变量的地址。

第一步,先声明一个变量(命名为p_rate)储存rate变量的地址。

此时,p_rate尚未初始化。编译器已经为p_rate 分配了储存空间,但是它的值待定,如图所示。

第七节(指针)_运算符_02
第七节(指针)_运算符_02

第二步,把rate变量的地址储存到p_rate 变量中。

由于现在p_rate 变量中储存的是rate变量的地址,因此p_rate 指明了rate被储存在内存中的具体位置。

用C语言的说法是: P_rate指向rate,或者p_rate是指向rate的指针。如图所示。

第七节(指针)_数组_03
第七节(指针)_数组_03

综上所述,指针是储存其他变量地址的变量。接下来,我们进一步学习如何在C程序中使用指针。

二.指针和简单变量:

在上面的示例中,指针变量指向一个简单(即,非数组)变量。

本次介绍如何创建并使用指向简单变量的指针。

2.1 声明指针:

指针是一个数值变量,和所有变量类似,必须先声明才能使用。

指针变量名遵循与其他变量名相同的命名规则,而且指针变量名必须唯一。

指针的声明形式如下:

代码语言:javascript
复制
类型名 * 指针名;

类型名可以是任意C语言的变量类型,它指明该指针所指向变量的类型。

星号( * )是间接运算符,表明指针名是一个指向类型名类型的指针,不是类型名类型的变量。

下面是一些示例:

代码语言:javascript
复制
char *ch1, *ch2;   //ch1和ch2都是指向char类型的指针

float *value, percent;    //value 是指向float类型变量的指针,percent是普通的float类型变量

*号可用作间接运算符和乘法运算符。

不用担心编译器会混淆两者。编译器通过星号上下文提供的信息,完全清楚该星号是间接运算符还是乘法运算符。

2.2初始化指针:

现在,你已经声明了一个指针,可以用它来做什么?

它在指向某些内容之前什么也做不了。与普通变量类似,使用未初始化的指针会导致无法预料的结果和潜在的危险。

没有储存变量地址的指针是没用的。

变量的地址不会自动“变”进指针中,必须在程序中使用取址运算符(& )获得变量的地址,然后将其存入指针才行。

把取址运算符放在变量名前,便会返回该变量的地址。因此,以下面的形式初始化指针:

代码语言:javascript
复制
指针 = &变量;

参看上图所示的例子,要让p_rate变量指向rate变量,应该这样写:

代码语言:javascript
复制
P_rate = &rate;      //把rate的地址赋值给P_rate

该语句把rate的地址赋值给p_rate 。初始化之前,p_rate未指向任何内容;初始化之后,p_rate是指向rate的指针。

2.3使用指针:

现在,你已经学会声明和初始化指针,一定很想知道如何使用它。

这里,又要用到间接运算符(* )。把*放在指针名前,该指针便引用它所指向的变量。

在上一个例子中,已经初始化了p_rate指针,使其指向rate变量。如果写*p_rate ,该指针变量则引用rate变量的内容。

因此,要打印rate的值(该例中,rate的值是100 ),可以这样写:

代码语言:javascript
复制
printf ("%d", rate);

也可以这样写

代码语言:javascript
复制
printf("%d", *p_rate);

在C语言中,以上两条语句是等价的。

通过变量名访问变量的内容,称为直接访问 ;

通过指向变量的指针访问变量的内容,称为间接访问或间接取值。

下面图解释了将间接运算符放在指针名前,引用的是指针所指向变量的值。

第七节(指针)_数组_04
第七节(指针)_数组_04

仔细思考一下上述内容。指针是C语言不可或缺的部分,必须要理解指针。许多人都不太明白指针,如果你理解起来也有困难,别担心。也许下面总结的内容会让你更好的进行理解。

假设声明一个名为ptr的指针,已将其初始化为指向var变量,以下的说法都正确:

●*ptr和var都引用var的内容(即,程序储存在该位置的任何值) ;

●*ptr和&var都引用var的地址。

因此,不带间接运算符的指针名访问指针本身储存的值(即,指针所指向的变量的地址)。

下面程序清单演示了指针的基本用法。请输入、编译并运行这个程序。

输入:

代码语言:javascript
复制
//指针的基本用法示例

#include <stdio.h>

//声明并初始化一个int类型的变量

int var = 1;

// 声明一个指向int类型变量的指针

int *ptr;

int main(void)
{
  //让ptr指向var
  
  ptr = &var;
  
  // 直接和间接访问var
  
  printf("\nDirect access, var = %d", var);
  printf("\nIndirect access, var = %d", *ptr);
  
  // 以两种方式显示var的地址
  
  printf("\n\nThe address of var = %p", &var);
  printf("\nThe address of var = %p\n", ptr);
  
  return 0;
}

输出:

第七节(指针)_运算符_05
第七节(指针)_运算符_05

解析:

代码语言:javascript
复制
该程序清单声明了两个变量。
第7行声明了一个int类型的变量var并初始化为1。
第11行,声明了一个指向int类型变量的指针ptr。
第17行,使用取址运算符(& )将var的地址赋值给指针ptr程序的其余部分负责将这两个变量的值打印在屏幕上。
第21行打印var的值,
第22行打印ptr指向的位置中所储存的值。
在本例中,这两个值都是1。
第26行在var前使用了取址运算符,该行打印var的地址。
第27行打印指针变量ptr的值,与第26行打印的值相同。
该程序清单在我认为是一个很好的例子。它表现了变量、变量的地址、指针、解引用指针之间的关系。

注意:要理解什么是指针以及指针工作的原理。

要把地址赋给指针之前,不要使用未初始化的指针否则可能会凉凉

三.指针和变量类型:

前面的讨论都没有考虑不同类型的变量占用不同数量的内存。

对于较常用的计算机操作系统,一个short类型的变量占2字节,一个float类型的变量占4字节,等等。

内存中的每个字节都有唯一 的地址,因此,多字节变量实际上占用了多个地址。

那么,指针如何储存多字节变量的地址?

实际上,变量的地址是它所占用字节的首地址(最低位的地址)。

下面声明并初始化3个变量来说明:

代码语言:javascript
复制
short vshort = 12252;
char vchar = 90;
float vfloat = 1200.156004;

这些变量都储存在内存中,如下图所示。图中,short类型的变量占2字节,char 类型的变量占1字节,float类型的变量占4字节。

第七节(指针)_运算符_06
第七节(指针)_运算符_06

接下来,声明并初始化3个指针分别指向这3个变量:

代码语言:javascript
复制
int *p_vshort = &vshort;
char *P_vchar = &vchar;
float *p_vfloat = &vfloat;   /*其他代码已略去*/

指针中储存的是它所指向变量的第1个字节地址。

因此,p_vshort 的值是1000,

p_vchar的值是1003,

p_vfloat的值是1006。

因为每个指针都被声明指向某种类型的变量

因此编译器知道:指向short类型变量的指针指向2字节中第1个地址;

指向float类型变量的指针指向4字节中的第1个地址,等等。

如下图所示:

第七节(指针)_数组_07
第七节(指针)_数组_07

如图和上图所示,3个变量之间都有一些空的内存存储位置。

这样做是为了方便理解。实际操作时,大多数C编译器都会把这3个变量储存在相邻的内存位置,不会像图中所示那样。

四.指针和数组:

在C语言中,指针和数组之间的关系很特殊。

下面将详细讲解其中的原理。

4.1数组名

数组名(不带方括号)是指向数组第1个元素(即,首元素)的指针。

因此,如果声明一个数组data[],那么data中储存的是数组第1个元素的地址。

你可能会问:“等等, 不需要取址运算符来获取地址?”是的,不需要。

当然,通过&data[0]表达式来获取数组首元素的地址也没问题。在C语言中,(data == &data[0]) 为真。

数组名不仅是指向数组的指针,而且是指针常量,它在程序的执行期间保持不变且不能被改变。

这很好理解:如果能改变它的值,它就会指向别处,而不是指向原来的数组(该数组位于内存中某固定的位置)。

但是,可以声明一个指针变量并将其初始化以指向该数组。

例如,下面的代码声明并初始化指针变量p_array ,把array数组首元素的地址储存在p_array中

代码语言:javascript
复制
int array [100];
int *p_array = array;   
/*其他代码已省略*/

因为p_array 是一个指针变量,所以可修改它的值让其指向别处。

与数组名(array )不同,p_array 并未被锁定指向array[]的第1个元素。

因此,可以改变它的值,使其指向array[] 的其他元素。

如何做?

首先,要了解一下如何在内存中储存数组元素。

4.2:储存数组元素

在前面笔记中介绍过,数组元素按顺序被储存在内存位置上。

第1个元素在最低位地址上,随后的数组元素(那些数组下标大于0的元素)被依次储存在较高位地址上。

能储存到多高位,取决于数组的数据类型(char、int、float等)。

以short类型的数组为例。一个short 类型的变量占用2字节的内存。

因此,每个数组元素与它前一个元素的间隔是2字节,每个数组元素的地址都比它上一个元素的地址高2。

对于float类型而言,一个float类型的变量占用4字节的内存,每个元素与它前一个元素的间隔是4字节,其地址比它上一个元素的地址高4。

下面图解释了如何在内存中储存不同类型的数组(分别是,包含6个short类型元素的数组和包含3个float类型元素的数组),以及数组中各元素地址之间的关系。

第七节(指针)_初始化_08
第七节(指针)_初始化_08

从图7可知,下列关系为真:

代码语言:javascript
复制
x == 1000
&x[0] == 1000
&x[1] == 1002
expenses == 1250
&expenses[0] == 1250
&expenses[1] == 1254

第1行,不带数组方括号的x是数组首元素的地址(&x[0] )。

第2行,x[0]位于1000 的地址上,可以这样读:“ 数组x的首元素的地址是1000”。

第3行表示第2个元素(在数组中的下标是1 )的地址是1002,

如上图所示。实际上,第4、5、6行与第1、2、3行几乎分别相同。

区别在于,在short类型的数组x中,每个元素占2字节,而在float类型的数组expenses 中,每个元素占4字节。

如何使用指针访问这些连续的数组元素?

从上述例子可知,指针的值(即指针中储存的地址)以2递增就能访问short类型数组连续的元素,以4递增指针就能访问float类型数组连续的元素。可将其概括为:要访问某种数据类型数组连续的元素,必须以sizeof(数据类型)递增指针的值。第3节中学过sizeof()运算符以字节为单位返回C语言数据类型的大小。

程序清单中通过声明short、float、double类型的数组并依次显示数组元素的地址,演示了不同类型数组的元素和地址之间的关系。

输入:

代码语言:javascript
复制
// 该程序演示了不同数据类型数组的元素和
// 地址之间的关系

#include <stdio.h>

// 声明一个计算器变量和3个数组
int ctr;
short array_s[10];
float array_f[10];
double array_d[10];

int main(void)
{
  //打印的表头
  
  printf("\t\tShort\t\tFloat\t\tDouble");
  
  printf("\n=================================");
  printf("=========================");
    
    //打印各数组素的地址
    
    for (ctr = 0; ctr < 10; ctr++)
      printf("\nElement %d: \t%ld\t\t%ld\t\t%ld", ctr,
             &array_s[ctr], &array_f[ctr], &array_d[ctr]);
  
  printf("\n=================================");
  printf("=============================\n");
  
  return 0;
}

输出:

第七节(指针)_初始化_09
第七节(指针)_初始化_09

解析:

代码语言:javascript
复制
运行程序后显示的地址会与上面输出示例中的地址不同,但是它们之间的关系相同。
在本例的输出中,相邻两个short类型的元素的间隔是2字节,相邻两个float类型的元素的间隔是4字节,相邻两个double类型的元素的间隔是8字节。
注意:
某些计算机的变量类型大小与本例不同。如果你的计算机与本例的不同,输出显示的元素间隔就不同。
然而,数组中相邻元索的间隔是一致的。
第16行和第24行都在调用printf()时使用了制表转义字符(\t )格式化表格以对齐各纵列的内容。

仔细查看程序,第8、 9、10行分别声明了3个数组。
第8行声明了一个short类型的数组array_s,
第9行声明了一个float类型的数组array_f,
第10行声明了一个double类型的数组array_d。
第16行打印表格的列标题。
第18、19行和第27、28行打印表顶部和底部的短划线。
以这种风格打印的表格比较美观。
第23、24、25行是一个for循环,打印表格的每一行。
首先打印ctr元素的编号,然后分别打印3个数组中该元素对应的地址。
4.3 指针算术:

假设有一个指向数组第1个元素的指针,该指针必须以该数组中储存的数据类型大小来递增。

如何通过指针表示法访问数组元素?

答案是:指针算术

指针算术非常简单。只需关注两种指针运算:递增和递减。

(1)指针递增

递增指针时,递增的是指针的值。

例如,将指针递增1,指针算术将自动地递增指针的值,使其指向数组的下一个元素。

也就是说,C编译器(查看指针声明)知道指针所指向的数据类型,并以数据类型的大小递增指针中储存的地址。

假设ptr_to_short 是指向short类型数组中某个元素的指针变量,如果执行下面的语句:

代码语言:javascript
复制
ptr_to_short++;

ptr_to_short的值将递增short类型的大小(通常是2字节),而且ptr_to_short现在指向该数组的下一个元素。

同理,如果ptr_to_float指向float类型数组中某个元素,执行下面的语句:

代码语言:javascript
复制
ptr_to_float++;

ptr_to_float的值将递增float类型的大小(通常是4字节)。

递增大于1的值也是如此。

如果给指针加上n,那么C编译器将递增该指针的值是n与相应数据类型大小的乘积(即,如果指针加上n,则该指针指向后续第n个元素)。因此执行下面的语句:

代码语言:javascript
复制
ptr_to_short += 4;

ptr_to_short 的值将递增8 ( 假设short是2字节),即该指针指向后续的第4个元素。同理,如果执行下面的语句:

代码语言:javascript
复制
ptr_to_float += 10;

ptr_to_float的值将递增40 (假设float是4字节),即该指针指向后续的第10个元素。

(2)指针递减:

指针递减的原理和指针递增类似。

递减实际上是递增的特殊情况,即增加的值为负。如果通过--或-=运算符递减指针,指针算术将自动根据数组元素的大小来调整。

输入:

程序ptr_math.c:使用指针算术和指针表示法访问数组元素

代码语言:javascript
复制
/* ptr_math.c -- 使用指针算数
                通过指针表示法访问数组元素 */

#include <stdio.h>
#define MAX 10

// 声明初始化一个整型数组

float i_array[MAX] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// 声明一个指向int类型变量的指针,和一个int类型的变量

int *i_ptr, count;

// 声明并初始化float类型的数组

float f_array[MAX] = { .0, .1, .2, .3, .4, .5, .6, .7, .8, .9 };

// 声明一个指向float类型变量的指针

float *f_ptr;

int main(void)
{
  // 初始化指针
  
  i_ptr = i_array;
  f_ptr = f_array;
  
  // 打印数组元素
  
  for (count = 0; count < MAX; count++)
    printf("%d\f\n", *i_ptr++, *f_ptr++);
  
  return 0;
}

输出:

第七节(指针)_运算符_10
第七节(指针)_运算符_10

解析:

代码语言:javascript
复制
在该程序中,第5行将自定义的常量MAX设置为10,整个程序清单都可以使用该常量。
第9行,MAX用于设置int类型的数组i_array 的元素个数。
在声明数组时已经初始化了数组的所有元素。
第13行声明了两个int变量,第1个是名为i_ptr的指针变量(因为变量名前使用了间接运算符* ),第2个是名为count的普通int类型变量。
第17行定义并初始化了第2个数组,该数组的类型是float,包含MAX个值,所有元素都已被初始化为float类型的值。
第21行声明了一个指向float类型变量的指针,名为f_ptr。
第23~36行是main()函数。
第27行和第28行将两个数组的首地址分别赋给两个指向各数组的指针。
第32行和33行的for语句使用int类型的变量count来计数(从0至MAX的值)。
每次计数,第33行都在调用printf()函数时解引用两个指针,并打印它们的值。
然后通过递增运算符分别递增每个指针,以指向数组的下一个元素。随后继续迭代下一轮for循环。
你可能认为,用数组下标表示法也能很好地运行该程序。的确如此。像这样简单的编程任务,发挥不出指针表示法的优势。
然而,在你开始编写更复杂的程序时,会发现指针的众多好处。
注意:不能递增或递减指针常量(数组名就是指针常量)。
记住,操纵指向数组元素的指针时,C编译器不会记录数组的开始和结束。
如果不慎,很可能在递增或递减指针时,使其指向内存的别处(数组前面或后面的位置)。
这些位置上可能储存了其他数据,但并不是数组的元素。你应该密切关注指针的动向,以及指针所指向的内容。

(3)其他指针运算:

使用指针时,除了递增和递减,还会用到求差,即两个指针相减。

如果有两个指针指向相同数组的不同元素,便可将两指针相减得出它们的间隔。

再次提醒注意,指针算术会根据指针所指向数组元素的个数自动伸缩。

因此,如果ptr1和ptr2指向(任意类型)数组的两个元素,下面的表达式可以得出两个元素相隔多远:

代码语言:javascript
复制
ptr1 - ptr2

除此之外,当两个指针都指向相同数组时,可以对这两个指针进行比较操作。

注意,只有在这种情况下,==、!=、>、<、>=、<=这些关系运算符才能正常工作。

较低数组元素(即,其数组下标较小的元素)比较高数组元素的地址低。

因此,假设ptr1 和ptr2都指向相同数组的不同元素,下面的表达式:

代码语言:javascript
复制
ptr1 < ptr2

如果ptr1指向的元素在ptr2指向的元素之前,以上关系成立(即,为真)。

许多对普通变量执行的算术运算( 如乘法、除法),都不能用在指针上。C编译器不允许对指针执行这些操作。

例如,假设ptr是一个指针,如果执行下面的语句,编译器会报错:

代码语言:javascript
复制
ptr *= 2;

表格总结了可用于指针的所有操作。这些操作也都介绍过。

指针运算表

运算

描述

赋值

可以给指针赋值,这个值必须是通过取址运算符(&) 获得的地址或者是指针常量(数组名) 中储存的地址。

间接取值

间接运算符( * )返回储存在指针所指向位置上的值(这通常称为解引用)

取址

可以使用取址运算符找到指针的地址,因此,有指向指针的指针。

递增

可以给指针加一个整数,使其指向不同的内存位置

递减

可以给指针减去一个整数,使其指向不同的内存位置

求差

将两个指针相减,得出两者的间距

比较

只有指向相同数组两个指针才能进行比较

五.指针的注意事项:

如果编写的程序中要用到指针,千万不要在赋值表达式语句的左侧使用未初始化的指针。

例如,下面声明 了一个指向int类型变量的指针:

代码语言:javascript
复制
int *ptr;

该指针尚未被初始化,因此它未指向任何内容。更确切地说,该指针并未指向任何已知内容。

未初始化的指针中有某些值,你并不知道是什么。大多数情况下是零。如果在赋值表达式语句中使用未初始化的指针,

如:

代码语言:javascript
复制
*ptr = 12;

12被储存在ptr指向的地址上。

该地址可以是内存中的任意位置一可能是储存操作系统或其他程序代码的地方。

将12储存于此会擦写某些重要的信息,这可能导致奇怪的程序错误,甚至整个系统崩溃。

在赋值表达式语句左侧使用未初始化的指针非常危险。

在程序的其他地方使用未初始化的指针也会导致其他错误(尽管这些错误没那么严重)。

必须自己多留心,不要奢望编译器能帮你检查出来。

记住,对指针做加法或减法时,编译器是根据指针指向的数据类型大小来改变指针的值,不是直接把指针的值与加上或减去的值做加法或减法(除非指针指向1字节的字符,那么指针加1,则是给指针的值加1)。

要熟悉你的计算机中变量类型的大小。在操纵指针和内存时必须要知道变量的大小。

不要用指针做乘法、除法运算。但是,可以用指针做加法(递增)和减法(递减)运算。

不要递增或递减数组变量。但是,可以将数组的首地址赋值给指针,然后递增该指针。

六.数组下标表示法和指针:

数组名是一个指向该数组首元素的指针。

因此,可以使用间接运算符访问数组的第1个元素。

如果声明了一个数组array[],那么,array表达式就是该数组的第1个元素, (array + 1)则是该数组的第2个元素,以此类推。

如果推广至整个数组,下面的关系都为真:

代码语言:javascript
复制
* (array) == array[0]
* (array + 1) == array[1]
* (array + 2) == array[2]
...
* (array + n) == array[n]

这说明了数组下标表示法与数组指针表示法等价,可以在程序中任意互换这两种表示法。

C编译器将其看作是使用指针访问数组数据的不同方式。

七.给函数传递数组:

本次已经讨论了C语言中指针和数组之间的特殊关系,在将数组传递给函数时会用得上。

只有用指针才能将数组传递给函数。

实参是主调函数(或程序)传递给被调函数的一个值。

这个值可以是int、float 或任意简单的数据类型,但必须是单独的数值——可以是单个数组元素,但不能是整个数组。

那么,如果要给函数传递整个数组怎么办?

别忘了指向数组的指针,该指针就是一个数值(即,数组首元素的地址)。

如果将该值传递给一个函数,该函数就知道了待传递数组的地址,便可用指针表示法访问该数组的其他元素。

考虑另一个问题。如果你想编写一个能处理不同大小数组的函数

例如,用该函数找出整型数组中最大的元素。这样的函数如果只能处理固定大小的数组就用处不大。

如果只把数组的地址传递给函数,该函数如何知道数组的大小?

记住,传递给函数的是指向数组首元素的指针。该指针的值可能是包含10个元素的数组首地址,也可能是包含10000个元素的数组首地址。

有两种方法可以让函数知道数组的大小。

可以在数组的最后一个元素中储存一个特殊的值作为数组末尾的标志。函数在处理数组时,会查看每个元素的值。当函数发现这个特殊的值时,就意味着到达数组的末尾。这个方法的缺点是,必须预留一个值作为数组末端的指示符,在储存实际数据时不太灵活。

另一个方法相对灵活和直接,也是我采用的方法:将数组大小作为实参传递给函数。数组大小就是一个简单的int值。因此,需要给函数传递两个实参:一个是指向数组首元素的指针,一个是指定该数组元素个数的整数。

下面程序清单接受用户提供的一系列值,并将其储存在数组中。然后调用largest()函数,并将数组(指向该数组的指针和数组大小)传递给它。该函数在数组中找出最大值并将其返回主调函数。

输入:

代码语言:javascript
复制
// arraypass.c   给函数传递一个数组

#include <stdio.h>

#define MAX 10

int array[MAX], count;

int largest(int num_array[], int length);

int main(voif)
{
  // 通过键盘输入MAX个值
  
  for (count = 0; count < MAX; count++)
  {
    printf("Enter an integer value: ");
    scanf("%d", &array[count]);
  }
  
  // 调用函数,并显示返回值
  printf("\n\nLargest value = %d\n", largest(array, MAX));
  
  return 0;
}
// largest()函数
//该函数返回一个整型数组中的最大值

int largest(int num_array[], int length)
{
  int count, biggest;
  
  for (count = 0; count < length; count++)
  {
    if (count == 0)
      biggest = num_array[count];
    if (num_array[count] > biggest)
      biggest = num_array[count];
  }
  
  return biggest;
}

输出:

第七节(指针)_运算符_11
第七节(指针)_运算符_11

解析:

代码语言:javascript
复制
该程序示例中调用的largest()函数接受一个指向数组的指针。
第9行是该函数的原型,除了末尾有分号,其他部分与第29行的函数头完全一样。
你应该能看懂第29行函数头中的大部分:largest()函数给调用它的程序返回一个int值。
该函数的第2个参数是int值,由形参length表示。
这里只有一个新内容,即函数的第1个形参: intnum_array[],它表明第1个参数是指向int类型数组的指针,由形参num_array表示。
也可以这样写函数声明和函数头:

int largest(int *num_array, int length);

这与程序中使用的第一种形式等价。int num_array[] 和int*num_array的意思都是“指向int的指针”。
第一种形式更合适,因为带方括号提醒你,该形参代表的是指向数组的指针。
当然,指针本身并不知道它指向的是一个数组,但是函数会将其视为指向数组的指针。
现在来看largest()函数。当程序调用它时,形参num_array储存第1个实参的值,因此,它是指向数组第1个元素的指针。
在largest()中,第37行和第38行使用下标表示法访问数组的元素。也可以使用指针表示法,重写if循环:

for (count = 0; count < length; count++ )
{
if (count == 0)
  biggest = * (num_array+count);
if (* (num_array+count) > biggest)
  biggest = * (num_array+count);
}

下面程序清单用另一种方式将数组传递给函数。

代码语言:javascript
复制
// arraypass2.c   以另一种方式将一个数组传递给函数

#include <stdio.h>

#define MAX 10

int array[MAX + 1], count;

int largest(int num_array[]);

int main(void)
{
  // 通过键盘输入MAX个值
  
  for (count = 0; count < MAX; count++)
  {
    printf("Enter an integer value: ");
    scanf("%d", &array[count]);
    
    if (array[count] == 0)
      count = MAX;      // 将会退出for循环
  }
  array[MAX] = 0;
  
  // 调用函数,并显示返回值
  printf("\n\nLargest value = %d\n", largest(array));
  
  return 0;
}
// largest() 函数
// 该函数返回整型数组中的最大值

int largest(int num_array[])
{
  int count, biggest;
  
  for (count = 0; num_array[count] != 0; count++)
  {
    if (count == 0)
      biggest = num_array[count];
    if (num_array[count] > biggest)
      biggest = num_array[count];
  }
  
  return biggest;
  }

输出:

第七节(指针)_运算符_12
第七节(指针)_运算符_12

解析:

代码语言:javascript
复制
该程序清单中largest()函数的功能与上面程序清单中的功能完全相同。两个程序的不同在于,该程序使用了数组标记。
第37行的for循环不断查找最大值,直至元素的值是0 (0 表明己到达数组的末尾)。
该程序的前面部分与上面程序清单不同。
首先,第7行在数组中增加了一个额外的元素用于储存标记数组末尾的值。
第20行和第21行,添加了一个if语句检查用户是否输入了0 (0表明用户已输入完成)。
如果输入0,count将被设置为最大值,以便正常退出for循环。
第23行确保用户输入最大数量值(MAX)后最后一个元素是0。
在输入数据时,通过添加额外的if语句,可以让largest()函数可用于任意大小的数组。
如果忘记在数组末尾输入0,会发生什么情况?
largest()函数将继续越过数组的末尾,比较内存中的值,直至找到0为止。
你也看到了,给函数传递一个数组也不太困难。传递一个指向数组首元素的指针很容易。
在大多数情况下,还要传递数组中元素的个数。
在函数中,可以通过下标表示法或指针表示法,通过指针来访问数组元素。
警告:给函数传递一个普通变量时,传递的是该变量的副本。
该函数使用传入的值,不会改变原始变量,因为它无法访问原始变量。
但是,给函数传递一个数组时,情况有所不同——传递给函数的是数组的地址,不是数组中值的副本。
函数使用的是真正的数组元素,因此可以在函数中修改储存在该数组中的值。

八:小结

本次介绍了C语言的重点内容一一指针。 指针是储存其他变量地址的变量。指针“指向”它所储存的地址上的变量。学习指针要用到两个运算符:取址运算符(& )和间接运算符(* )。把取址运算符放在变量名前,返回变量的地址。把间接运算符放在指针名前,返回该指针指向变量的内容。

指针和数组有特别的关系。数组名是指向该数组首元素的指针。通过指针的运算特性,可以很方便地使用指针来访问数组元素。实际上,数组下标表示法就是指针表示法的特殊形式。

本次还介绍了通过传递指向数组的指针来将数组作为参数传递给函数。函数一旦知道数组的地址和数组的元素个数,便可使用指针表示法或下标表示法访问数组元素。

问答题

1:为什么在C语言中,指针很重要?

通过指针能更好地控制数据。当使用函数时,指针能让你改变被传递变量的值(无论这些值在哪里)。

2:编译器如何知道*指的是乘法、解引用还是声明指针?

编译器根据星号出现的上下文来确定是哪一种用法。如果声明的开始是变量的类型,编译器就假定该星号用于声明指针。如果星号与已声明为指针的变量一起使用,却不在变量声明中,编译器则将该星号假定为解引用。如果星号出现在数学表达式中,但是没有和指针变量一起使用,编译器则将其假定为乘法运算符。

3:如果对指针使用取址运算符会怎样?

这样做得到的是指针变量的地址。记住,指针也是变量,只不过它储存的是它所指向变量的地址。

4:同一个变量是否都储存在相同的位置?

不是。每次运行程序时,其中的变量都储存在不同的地址上。千万不要把常量地址赋给指针。

5:确定变量的地址要使用什么运算符?

取址运算符是&

6:通过指针确定它所指向位置上的值,要使用什么运算符?

要使用间接运算符*。在指针名前写上*,引用的是该指针所指向的变量。

7:什么是指针?

指针是储存其他变量地址的变量。

8:什么是间接取值(indirection ) ?

间接取值指的是,用指针向变量指针访问变量的内容。

9:在内存中,如何储存数组的元素?

数组元素被顺序存储在内存中,下标越小的元素存储的地址位置越低。

10:用两种方式获得数组data[]的第1个元素的地址。

代码语言:javascript
复制
&data[0]和data

11:如果要给函数传递一个数组,有哪两种方式让函数知道已到达数组的末尾?

一种方法是,把数组的长度作为参数传递给函数。

另一种方法是,在数组中加入一个特定值(如,NULL),表面已达数组末尾。

12:本次介绍了哪6种可用于指针的运算?

赋值,间接取值,取址,递增,相减和比较

13:假设有两个指针,第1个指针指向int类型数组的第3个元素,第2个指针指向该数组的第4个元素。如果让第2个指针减去第1个指针,会得到多少?(假设整型的大小是2字节)

将两个指针相减得到他们之间之间的元素个数,在这种情况下答案为1,与数组元素实际大小无关。

14:假设上一题的数组类型是float。两个指针相减得到多少?(假设整型的大小是2字节)

还是1

练习题

1.声明一个指向char 类型变量的指针。指针名是char_ptr

代码语言:javascript
复制
char *char_ptr;

2.假设有一个int类型的变量cost ,声明并初始化一个指向该变量的指针p_cost 。

下面声明了一个cost的指针,然后将cost的地址(&cost)赋值给该指针:

代码语言:javascript
复制
int *p_cost;
p_cost = &cost;

3.根据练习题2,使用直接访问和间接访问两种方式将100赋值给变量cost 。

直接访问:cost = 100;

间接访问:*p_cost = 100;

4.根据练习题3,打印指针的值和它所指向的值。

代码语言:javascript
复制
printf("Pointer value: %p, points atvalue: %d", p_cost, *p_cost);

5.将float类型变量radius 的地址赋值给一个指针。

代码语言:javascript
复制
float *variable = &radius;

6.用两种方式将100赋值给数组data[]的第3个元素。

代码语言:javascript
复制
data[2] = 100;
*(data + 2) = 100;

7.编写一个名为sumarrays()的函数,接受两个数组作为参数,将两个数组中所有的值相加,并将计算结果返回给主调函数。

代码语言:javascript
复制
#include<stdio.h>
#define MAX1 5
#define MAX2 8
int array1[MAX1] = { 1, 2, 3, 4, 5 };

int array2[MAX2] = { 1, 2, 3, 4, 5, 6, 7, 8 };

int total;

int sumarrays(int x1[], int len_x1, int x2[], int len_x2);
int main(void)
{
  total = sumarrays(array1, MAX1, array2, MAX2);
  printf("The total is %d\n", total);
  return 0;
}
int sumarrays(int x1[], int len_x1, int x2[], int len_x2)
{
  int total = 0, count = 0;

  for (count = 0; count < len_x1; count++)
    total += x1[count];

  for (count = 0; count < len_x2; count++)
    total += x2[count];
  return total;
}
第七节(指针)_运算符_13
第七节(指针)_运算符_13
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-09-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一.什么是指针:
    • 1.1 计算机的内存
      • 1.2 创建指针
      • 二.指针和简单变量:
        • 2.1 声明指针:
          • 2.2初始化指针:
            • 2.3使用指针:
            • 三.指针和变量类型:
            • 四.指针和数组:
              • 4.1数组名
                • 4.2:储存数组元素
                  • 4.3 指针算术:
                  • 五.指针的注意事项:
                  • 六.数组下标表示法和指针:
                  • 七.给函数传递数组:
                  • 八:小结
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档