天天看点

C语言基础 — ( C语言的炁体源流——指针)前言一、指针是什么?二、指针变量三、总结

欢迎小伙伴的点评✨✨ 本篇章系列是对C语言的深度思考和总结、关于C语言内容会持续更新

文章目录

  • 前言
  • 一、指针是什么?
  • 二、指针变量
    • 2.1、怎样定义指针变量
    • 2.2、怎样引用指针变量
    • 2.3、指针变量作为函数参数
    • 2.4、数组元素的指针
  • 三、总结

前言

指针是C语言中的一个重要概念,也是C语言的一个重要特色。正确而灵活地运用它,可以使程序简洁、紧凑、高效。每一个学习和使用C语言的人,都应当深入地学习和掌握指针。可以说,不掌握指针就是没有掌握C的精华。

指针的概念比较复杂,使用也比较灵活,因此初学时常会出错,务请在学习本章内容时十分小心,多思考,多比较,多上机,在实践中掌握它。

一、指针是什么?

为了说清楚什么是指针,必须先弄清楚数据在内存中是如何存储的,又是如何读取的。

如果在程序中定义了一个变量,在对程序进行编译时,系统就会给这变量分配内存单元。

编译系统根据程序中定义的变量类型,分配一定长度的空间。内存区的每一个字节有一个编号 ,这就是 地址 ,它相当于旅馆中的房间号。在地址所标志的内存单元中存放的数据则相当于旅馆房间中居住的旅客。

由于通过地址能够找到所需的变量单元,可以说, 地址指向该变量单元 。 打个比方,一个房间的门口挂了一个房间号 2008 ,这个2008 就是房间的地址,或者说,2008 指向 该房间。 因此 ,将 地址形象化地称为指针 。 意思是通过它能找到以它为地址的内存单元。

说明: 对计算机存储单元的访问比旅馆要复杂一些,在C语言中,数据是分类型的,对不同类型的数据,在内存中分配的存储单元大小(字节数)和存储方式是不同的(如整数以补码形式存放,实数以指数形式存放)。如果只是指定了地址1010,希望从该单元中调出数据,这是做不到的,虽然能找到所指定的存储单元,但是,无法确定是从1个字节中取信息(字符数据),还是从2个字节取信息(短整型),抑或是从4个字节取信息(整型)。也没有说明按何种存储方式存取数据(整数和单精度实数都是4个字节,但存储方式是不同的)。因此,为了有效地存取一个数据,除了需要位置信息外,还需要有该数据的类型信息(如果没有该数据的类型信息,只有位置信息是无法对该数据进行存取的)。C语言中的地址包括位置信息(内存编号,或称纯地址)和它所指向的数据的类型信息,或者说它是 带类型的地址 如 &a ,一般称它为 变量a的地址 ,确切地说, 它是 整型变量a的地址 。后面提到的 地址 ,都是这个意思。

请务必弄清楚存储单元的 地址 和存储单元的 内容 这两个概念的区别 ,假设程序已定义了3个整型变量 i , j , k , 在程序编译时,系统可能分配地址为 2000 ~ 2003 的4个字节给变量 i , 2004 ~ 2007 , 的4个字节给 j ,2008 ~2011 的4个字节给k (不同的编译系统在不同次的编译中,分配给变量的存储单元的地址是不相同的),在程序中一般是通过变量名来引用变量的值,例如:

printf(" %d\n",i);

由于在编译时,系统已为变量 i 分配了按整型存储方式的4个字节,并建立了变量名和地址的对应表,因此在执行上面语句时,首先通过变量名找到相应的地址,从4个字节中按照整型数据的存储方式读出整型变量 i 的值,然后按十进制整数格式输出。

注意: 对变量的访问都是通过地址进行的。

假如有输入语句

scanf(" %d",&i);

在执行时,把键盘输入的值送到地址为2000 开始的整型存储单元中。如果有语句

k = i + j;

则从2000 ~ 2003 字节取出 i 的值 (3) ,再从2004 ~2007 字节取出 j 的值(6) ,将它们相加后再将其和(9) 送到 k 所占用的2008 ~ 2011 字节单元中。

这种直接按变量名进行的访问,称为 直接访问 方式。

还可以采用另一种称为 间接访问 的方式,即将变量 i 的地址存放在另一变量中,然后通过该变量来找到变量 i 的地址 ,从而访问 i 变量。

在C语言程序中,可以定义整型变量、浮点型(实型)变量、字符变量等,也可以定义一种特殊的变量,用它存放地址。假如定义了一个变量 i_pointer (变量名可任意取),用来存放整型变量的地址。可以通过下面语句将 i 的地址(2000)存放到 i_pointer 中。

i_pointer = &i ; //将 i 的地址存放到 i_pointer中

这时,i_pointer 的值就是2000(即变量 i 所占用单元的起始地址)。

要存取 变量 i 的值,即可以用直接访问的方式,也可以采用间接访问的方式:先找到存放 变量 i 的地址 的变量 i_pointer , 从中取出 i

的地址(2000) ,然后到2000 字节开始的存储单元中存取 i 的值。

指针的 指向 是通过 地址 来体现的。假设 i_pointer 中的值是变量 i 的地址 (2000) ,这样就在 i_pointer 和变量 i 之间建立起一种联系,即通过 i_pointer 能知道 i 的地址,从而找到变量 i 的内存单元。

由于通过地址能找到所需的变量单元,因此说,地址 指向 该变量单元(如同说,一个房间号 指向 某一房间一样)。将地址形象化地称为 指针 。意思是通过它能找到以它为地址的内存单元(如同根据地址 2000 就能找到变量 i 的存储单元一样)。

如果有一个变量专门用来存放另一变量的地址(即指针),则它称为 指针变量 。上述的 i_pointer 就是一个指针变量。指针变量就是地址变量,用来存放地址,指针变量的值是地址 (即指针)。

注意: 区分 指针 和 指针变量 这两个概念。例如 ,可以说变量 i 的指针是 2000 ,而不能说 i 的指针变量是 2000 ,

指针是一个地址 ,而指针变量是存放地址的变量。

二、指针变量

2.1、怎样定义指针变量

定义指针变量的一般形式为

类型名 *指针变量名;

如:

int *pointer_1 , *pointer_2;

左端的 int 是在定义指针变量时必须指定的 基类型 。 指针变量的基类型用来指定此指针变量可以指向的变量的类型。

说明: 前面介绍过基本的数据类型(如 int , char , float 等),既然有这些类型的变量,就可以有指向这些类型变量的指针,因此,指针变量是基本数据类型派生出来的类型,它不能离开基本数据类型而独立存在。

下面都是合法的定义:

float *pointer_3; // pointer_3 是指向float 型变量的指针变量,简称 float 指针

char *pointer_4; // pointer_4 是指向字符型变量的指针变量,简称 char 指针

可以在定义指针变量时,同时对它初始化,如:

int *pointer_1 = &a , *pointer_2 = &b ; // 定义指针变量 pointer_1 , pointer_2 , 并分别指向 a , b

说明: 在定义指针变量时要注意:

(1) 指针变量前面的 ✳ 表示该变量为指针型变量。指针变量名是 pointer_1 和 pointer_2 ,而不是 ✳pointer_1 , ✳pointer_2 。

这是与定义整型或实型变量的形式不同的。

(2) 在定义指针变量时必须 指定基类型 ,要知道不同类型的数据在内存中所占的字节数和存放方式是不同的。指向一个整型变量和指向一个实型变量,其物理上的含义是不同的。从另一角度分析,指针变量是用来存放地址的,C的地址信息包括存储单元的位置(内存编号)和类型信息。指针变量的属性应与之匹配。例如:

int a , ✳p ;

p = &a ;

&a不仅包含变量a的位置(如编号为2000的存储单元),还包括 存储的数据是整型 的信息。现在定义指针变量 p 的基类型为 int ,即它所指向的只能是整型数据。这时 p 能接收 &a 的信息。如果改为

float ✳p ;

p = &a ;

&a 是 整型变量 a的地址 在编译时就会出现一个警告(warning):

把一个 int * 型数据转换为 float * 数据 。在赋值时,编译系统会把&a 的基类型自动改换为float 型,然后赋给p 。 但是p 不能用这个地址指向整型变量。

从以上可以知道指针或地址是包含有类型信息的。应该使赋值号两侧的类型一致,以避免出现意外结果。

一个变量的指针的含义包括两个方面,一是以存储单元编号表示的纯地址(如编号为2000 ),一是它指向存储单元的数据类型(如int , char , float 等)。

(3) 如何表示指针类型。 指向整型数据的指针类型表示为 int* ,读作 指向 int 的指针 或 简称 int 指针 。

(4) 指针变量中只能存放地址(指针),不要将一个整数赋给一个指针变量。如:

pointer_1 = 100; // pointer_1 是指针变量,100是整数,不合法

原意是想将地址100 赋给指针变量 pointer_1 , 但是系统无法辨别它是地址,从形式上看100是整常数,而整常数只能赋给整型变量,而不能赋给指针变量,判为非法。在程序中是不能用一个数值代表地址的,地址只能用地址符 & 得到并赋给一个指针变量,如 p = &a ;

2.2、怎样引用指针变量

在引用指针变量时,可能有3种情况:

(1) 给指针变量赋值。如:

p = &a ; // 把 a 的地址赋给指针变量p

指针变量 p 的值是变量 a 的地址,p 指向 a 。

(2) 引用指针变量指向的变量。

如果已执行 p = &a ; ,即指针变量 p 指向了整型变量 a ,则

printf(" %d " , *p);

其作用是以整数形式输出指针变量 p 所指向的变量的值,即变量 a 的值。

如果有以下赋值语句:

*p = 1;

表示将整数 1 赋给 p 当前所指向的变量,如果 p 指向变量 a ,则相当于把 1 赋给 a ,即 a = 1 ; 。

(3) 引用指针变量的值。如:

printf(“%o”, p);

作用是以八进制数形式输出指针变量 p 的值,如果p指向了 a ,就是输出了 a 的地址,即&a 。

注意: 要熟练掌握两个有关的运算符。

(1) & 取地址运算符。 &a 是变量 a 的地址 。

(2) * 指针运算符(或称 间接访问 运算符) ,*p 代表指针变量 p 指向的对象。

2.3、指针变量作为函数参数

函数的参数不仅可以是整型、浮点型、字符型等数据,还可以是指针类型。它的作用是将一个变量的地址传送到另一个函数中。

为了使在函数中改变了的变量值能被主调函数 main 所用,应该用指针变量作为函数参数,在函数执行过程中使用指针变量所指向的变量值发生变化,函数调用结束后,这些变量值的变化依然保留下来,这样就实现了 通过调用函数使变量的值发生变化,在主调函数 (如 main 函数)中可以使用这些改变了的值的目的。

如果想通过函数调用得到n个要改变的值,可以这样做:

① 在主调函数中设 n 个变量,用 n 个指针变量指向它们;

② 设计一个函数,有 n 个指针形参。在这个函数中改变这 n 个形参的值;

③ 在主调函数中调用这个函数,在调用时将这 n 个指针变量作实参,将它们的值,也就是相关变量的地址传给该函数的形参;

④ 在执行该函数的过程中,通过形参指针变量,改变它们所指向的 n 个 变量的值;

⑤ 主调函数中就可以使用这些改变了值的变量。

注意: 不能企图通过改变指针形参的值而使指针实参的值改变。

注意: 函数的调用可以(而且只可以) 得到一个返回值(即函数值),而使用指针变量作参数,可以得到多个变化了的值。如果不用指针变量是难以做到这一点的。要善于利用指针法。

2.4、数组元素的指针

一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。指针变量既然可以指向变量,当然也可以指向数组元素(把某一元素的地址放到一个指针变量中)。 所谓数组元素的指针就是数组元素的地址。

可以用一个指针变量指向一个数组元素 。例如:

int a[10] = {0,1,2,3,4,5,6,7,8,9}; // 定义a 为包含10个整型数据的数组

int *p ; // 定义p为指向整型变量的指针变量

p = &a[0]; // 把a[0] 元素的地址赋给指针变量 p

printf(“%d\n”,p[3]) ; // 使用指针访问数组元素,把a[3] 元素的数组打印出来

以上是使指针变量 p 指向 a 数组的第 0 号元素

引用数组元素可以用 下标法 (如 a[3]) ,也可以用 指针法 ,即通过指向数组元素的指针找到所需的元素。使用指针法能使目标程序质量高(占内存少,运行速度快)。

在C语言中,数组名(不包括形参组名)代表数组中首元素(即序号为0的元素)的地址。因此,下面两个语句等价:

p = &a[0]; // p的值是 a[0] 的地址

p = a; // p的值是数组a 首元素(即 a[0])的地址

注意: 程序中的数组名不代表整个数组,只代表数组首元素的地址。 上述 p = a ; 的作用是 把a数组的首元素的地址赋给指针变量 p ,而不是 把数组 a 各元素的值赋值给 p 。

在定义指针变量时可以对它初始化,如:

int *p = &a[0];

它等效于下面两行:

int *p ;

p = &a[0]; // 不应写成 *p = &a[0];

当然定义时也可以写成

int *p = a;

它的作用是将 a 数组首元素(即 a[0])的地址赋给指针变量 p(而不是赋给 *p) 。

三、总结

指针是地址。

通过地址能找到所需的变量单元,可以说, 地址指向该变量单元。

因此,将地址形象化地称为指针。

继续阅读