第五章 指针 5.1 指针的概念和定义 5.2 指针运算 5.3 指针和数组 5.4 字符串指针 5.5 指针数组 5.6 指向指针的指针 5.7 应用举例
5.1指针的概念和定义 5.1.1 指针的概念: 如果我们将抽屉看作计算机的内存单元,那么,抽屉的编号就是内存单元的地址,存放地址的内存单元就对应程序中的变量,这类特殊的变量在C中称指针变量。可见,pointer是指针变量,2000是pointer指针变量的值
5.1指针的概念和定义 5.1.2 指针的定义及初始化: 指针:就是地址,一个变量的指针就是该变量的地址。 指针变量:专门存放地址的一类特殊的变量。 下面的语句定义了一个名为pointer的指针,且被初始化为0,该指针“指向”的目标类型为整型。 int *pointer = 0 ;
指针变量的常见类型 简单的指针变量分为: 名称 定义 指向具体变量 含义 整型指针变量 int i; long a; 存放整型变量的地址 int *p1=0; long *p2=0; int i; long a; p1=&i; ( p1指向i ) p2=&a; ( p2指向a ) 存放整型变量的地址 字符型指针变量 char *p3=0; char ch; P3=&ch; ( p3指向ch ) 存放字符型变量的地址 实型指针变量 float *p4=0; double *p5=0; float x;double y; p4=&x;p5=&y; ( p4指向x, p5指向y ) 存放实型变量的地址 通用指针 void *p=0; p=0; (初始化为空指针) 存放任何类型变量的地址
5.1指针的概念和定义 5.1.3 指针的赋值: 例:有如下变量定义语句: int i, j; int *i_pointer = 0; 指针的赋值就是让指针变量指向某个具体的内存单元。一般有2种方式给指针变量赋值: 1、使用地址运算符(&)将变量的地址取出赋给指针变量,这样,就让指针变量指向了某一具体的变量。 例:有如下变量定义语句: int i, j; int *i_pointer = 0; 2000 以上语句经过编译后(编译后,每个变量名对应一个地址,在内存中不再出现变量名而只有地址) ,内存分配如左图所示: i_pointer = &i ; 执行以上变量赋值语句后, i_pointer 就指向了变量 i 代表的内存单元。
5.1指针的概念和定义 5.1.3 指针的赋值(续): 例:有如下变量定义语句: int i = 123; 指针的赋值就是让指针变量指向某个具体的内存单元。一般有2种方式给指针变量赋值: 5.1.3 指针的赋值(续): 2、将一个已有具体指向的指针变量赋值给另一个指针变量。 例:有如下变量定义语句: int i = 123; int *p1 = 0 , *p2 = 0; p1 = &i ; 执行以上变量赋值语句后, p1 就指向了变量 i 代表的内存单元。 p2 = p1 ; 执行以上变量赋值语句后, p1、p2 就指向了相同的内存单元。
5.1指针的概念和定义 5.1.3 指针的赋值(续): ①直接访问:通过变量名或变量的地址访问。 有了指针的概念后,程序中对变量(内存)的访问就有两种方式: ①直接访问:通过变量名或变量的地址访问。 例:printf(“%d”,i); scanf(“%d”,&i); ②间接访问:通过指针变量(i_pointer)中存放的值,找到最终要访问的变量。 例: scanf(“%d”, i_pointer); 在现有的微机系统中指针变量一般占4个字节。 指针变量在使用前必须指向某个具体的内存单元,没有具体指向的指针变量叫悬空指针,使用是非法的。 定义指针并将其初始化为0是一个值得提倡的好习惯。
5.2 指针运算 关于指针的两个运算符 含义:取变量x的地址。 *p: ( p 只能是指针变量 ) 含义:p所指向的变量。 取地址运算符:& 指向运算符: * &x: ( x 可以是任何类型的变量 ) 含义:取变量x的地址。 *p: ( p 只能是指针变量 ) 含义:p所指向的变量。 应当注意的是,在变量说明语句中的“*”号意味着“指向……的指针”,而在表达式语句中的“*”号是表示“间接”存取变量的值。 例: int x = 1, y = 2, z[10]; int *ip = 0; ip = &x; //ip is now points to x y = *ip; // y = x; *ip = 0; // x = 0; ip = &z[0]; // ip is now points to z[0]
5.2 指针运算(续) 关于指针运算的注意事项 例:int i = 0 , a[3] = {0}; -&i ,&a[0]合法。 地址运算符&可用于取变量或数组元素的地址,但&不能用于非左值表达式 和常数,因为它们没有地址的概念。 例:int i = 0 , a[3] = {0}; -&i ,&a[0]合法。 -&a非法(因为数组名a本身就代表数组的首地址,是一个常数)。 -&(i + 5)非法(因为i + 5不是一个左值表达式,它不能代表存储域)。 -&i = 123也是非法的赋值表达式,因为&i表示的是一个常数(i的地址)。
5.2 指针运算(续) 关于指针使用的示例 *&i和i等价; (*p)++和i++等价; *p++等价于*(p++); 原则:先定义后使用。 例5.1 原则:先定义后使用。 关于指针使用的示例 //* 程 序 名:5_1.cpp //* 主要功能:通过指针间接访问内存单元 //*************************************** int main() { …… int i = 0; int *p = 0; p = &i; …… } 说明: 如果已执行了程序第12行的语句:“p=&i;”,则&*p和&i等价; *&i和i等价; (*p)++和i++等价; *p++等价于*(p++);
5.2 指针运算(续) 关于指针使用的示例 原则:先定义后使用。 //* 程 序 名:5_2.cpp 例5.2 原则:先定义后使用。 关于指针使用的示例 //* 程 序 名:5_2.cpp //* 主要功能:测试交换两个指针变量的指向//*************************************** int main() { …… p_max = &C_score; p_min = &math_score; if(math_score > C_score) { 16 p = p_max, p_max = p_min, p_min = p; } ……
5.3 指针和数组 5.3.1 指针与一维数组 &a[0] + 偏移量 &a[1] 数组的首地址也就是数组中第1个元素的地址; 例5.3 5.3.1 指针与一维数组 数组的首地址也就是数组中第1个元素的地址; 在内存中数组a的元素的地址是连续递增的; 通过数组a的首地址,加上偏移量就可以依次得到其它元素的地址; &a[0] + 偏移量 &a[1] 这里的偏移量就是一个数组元素所占的字节数。编译程序会根据数组元素的类型,自动确定出不同的偏移量
5.3 指针和数组 5.3.1 指针与一维数组(续) 有了数组的地址,我们现在有另外一种引用一维数组元素的方法—— 地址引用法。 例5.4 5.3.1 指针与一维数组(续) 有了数组的地址,我们现在有另外一种引用一维数组元素的方法—— 地址引用法。 图5-3-2 数组地址和元素的关系 score[ 0] *score score[ 1] *(score +1) score[ 2] *(score +2) score +2 内存数据区 地址 score score +1 元素 90 80 70 … 例5.4
5.3 指针和数组 指向一维数组元素的指针: 例:int score[3] = {0} , *p = score; 例5.5 指向一维数组元素的指针: 例:int score[3] = {0} , *p = score; 以上定义还可以写成: 例:int score[3] = {0} , *p = &score[0]; 由于p是一个指针变量,因此可以修改它,使之指向其他地方。与数组名(score)不同,p不必被固定为指向数组score的第一个元素。在需要时,可以修改它,使之指向score数组的其他元素。 例:int score[3] = {0} , *p = &score[1];
5.3 指针和数组 指向一维数组元素的指针(续): int a[10]; int* p; p=a;或 p=&a[0]; …… a[i]=0; 数组元素的下标访问方式 int a[10]; int* p; p=a;或 p=&a[0]; …… a[i]=0; for(int k=3;k<10;k++) scanf(“%d”,a+k); 数组名作地址值的直接地址访问 *(a+i)=0; p[i]=0; *(p+i)=0; scanf(“%d”,p+k); 将指针变量看作数组名以后的下标变量方式 指针加偏移量的访问方式
5.3 指针和数组 指向一维数组元素的指针(续): 可以对指向数组元素的指针变量进行自增(或自减)运算,表示指针向后(向前)移动一个数组元素。例如,假设p的当前指向为a[k],则p++指向a[k+1]. *p++ 和*( p++) 等价于 *p, p++ (先引用p当前指向的元素a[k] ,再使p指向下一个元素a[k+1] ) *(++ p)等价于 p++, *p (先使p指向a[k+1] ,再引用p指向的元素a[k+1] ) (*p)++表示p当前指向的元素的值加1,即a[k]++ 例5.6
5.3 指针和数组 指向同一数组的两个指针之间可以进行的运算: 应该避免如下几种错误 : (1)比较运算,即比较它们所存储的地址,可以反映出它们在数组中的前后顺序。 (2)数值加减运算,加上某个整型量表示指针向后移动相应个数组元素。同样地,减去某个整型量表示指针向前移动相应个数组元素。这种运算用的最多的是自增和自减运算。 (3)指针的减法运算的结果为它们之间的数组元素的个数。 应该避免如下几种错误 : (1)对不指向数组的指针进行算术运算。 (2)把不指向同—数组的两个指针相减或比较。 (3)指针算术运算的结果超出了数组的范围。 例5.7
考虑:1.能否用scanf(“%c”,str++)代替 scanf(“%c”,p++)? 例: char str[10]; char* p; p=str; for(int k=0;k<10;k++) scanf(“%c”,p++); × scanf(“%c”,str++); p str[0] str[9] p++ 考虑:1.能否用scanf(“%c”,str++)代替 scanf(“%c”,p++)? 不行!! 2.循环结束后p指向哪儿?
5.3 指针和数组 5.3.2 指针与结构数组: StudentInfo stu; StudentInfo *pStuInfo = 0; struct StudentInfo { char no[20]; int sexy; double height; Date birthday; }; 5.3.2 指针与结构数组: 让pStuInfo指向结构变量stu: pStuInfo = &stu; 声明一个该结构类型的变量: 例5.8 图5-3-6 指向结构的指针 … &stu.sexy 内存数据区 地址 结构成员 pStuInfo stu.no[0] stu.sexy &stu stu.no &stu.height &stu.birthday stu.birthday.year StudentInfo stu; 声明一个指向 StudentInfo结构的指针: StudentInfo *pStuInfo = 0; 三种方式引用结构变量stu的成员: stu.no = "1443011101"; (*pStuInfo).no = "1443011101"; pStuInfo -> no = "1443011101";
5.4 字符串指针 5.4.1 用字符数组存放字符串数据: char name[] = "王芳"; char name[20] =""; 无论哪种方式,编译器都自动在该字符串的末尾加上—个空字符。请记住,字符串必须以空字符结尾。操纵字符串时均需通过查找空字符来确定字符串的尾部。如果遗漏了空字符,程序会认为该字符串一直延续到内存中的下一个空字符。 5.4.1 用字符数组存放字符串数据: 在C中,没有为字符串数据提供专门变量来存放,而是用字符数组来处理字符串,字符数组就是用来存放和操作字符串数据的。常用的将字符串数据存放进字符数组的方式有两种: (1)定义字符数组时用字符串常量初始化: char name[] = "王芳"; (2)从键盘输入一个字符串给字符数组: char name[20] =""; cin >> name; //输入wangfang则该字符串进入name数组
5.4 字符串指针 字符串指的是在程序中经常要处理的一类数据。我们先了解C中提供的字符串的处理的几种基本技术 。 5.4.2 通过字符数组操作字符串: 字符串放入字符数组存放后,就可以通过操作字符数组来操作字符串了。 例5.9
5.4 字符串指针 5.4.3 使用指向字符数组的指针操作字符串: 字符串指的是在程序中经常要处理的一类数据。我们先了解C中提供的字符串的处理的几种基本技术 。 5.4.3 使用指向字符数组的指针操作字符串: 字符串是由字符数组的名称和字符'\0'定义的。字符数组的名称是一个char指针常量,它指向字符串的开头,而字符'\0'标记了字符串的末尾。由于标记了字符串的末尾,因此要操作某个字符串,可以指定一个指向该字符串开头的指针变量,通过操作该指针变量就可访问字符串中的每一个字符。 例5.10
5.4 字符串指针 5.4.4 指向字符串常量的指针: char *pStr = "Hello,World”; 当编译这条语句时,字符串“Hello,World”(包括一个结尾的空字符)将被存储在内存的某个地方,而指针pStr将被初始化为指向该字符串存放的内存的第一个单元。不用关心该字符串被存储在内存的什么地方,这是由编译器自动处理的。—旦被定义,pStr便是一个指向存储该字符串的内存空间的开头的指针。
5.4 字符串指针 字符串指的是在程序中经常要处理的一类数据。我们先了解C中提供的字符串的处理的几种基本技术 。 例5.11 5.4.4 指向字符串常量的指针(续): #include <stdio.h> /*程序5-11. cpp */ void main( ) { char *p = "abcdefg"; for(; *p != '\0'; p++) if(*p >='a' && *p <='z') printf("%c", *p-32); else printf("%c", *p); } putchar('\n'); if(*p >='a' && *p <= 'z') *p = *p - 32; printf("%c", *p);
5.5 指针数组 5.5.1 指针数组的概念: 指针数组也是数组; 其数组元素中存放的是内存单元的地址 。即:数组中的元素为指针; 而这些指针必须指向同一种数据类型; 指针数组的数据类型是其所有元素(指针)所指向的变量的数据类型。 实际上,指针数组存在的目的就是用来处理多个有关系的字符串数据。
5.5 指针数组 5.5.2 指针数组的定义及初始化: 定义三个存放字符串数据的字符数组: char str1[ ] = "China"; 例5.12 5.5.2 指针数组的定义及初始化: 定义三个存放字符串数据的字符数组: char str1[ ] = "China"; char str2[ ] = "America"; char str3[ ] = "German"; 定义指针数组并初始化指向上面三个字符串数据: char *pStr[3] = { str1, str2, str3 }; 图5-5-1 指针数组 str1 str2 str3 pStr[0] pStr [1] pStr [2] 指针数组pStr 元素 C h i n a \0 A m e r c G 元素值
5.6 指向指针的指针 由于指针变量本身是—个变量,它被存储在计算机内存特定某处。因此可以创建指向指针的指针变量—— 双重指针变量,其值为一个指针变量的内存存放地址 int a = 123; int *p_a = &a; // p_a 现在指向了变量 a int **p = &p_a; // p 指向了变量 p_a 图5-6-1 双重指针 &p_a &a ... 123 ××× &p 声明和使用指向指针的指针被称为多重间接。图5-6-1说明了变量、指针和指向指针的指针之间的关系。对于多重间接的层数没有任何限制。但通常多于两层时无疑是自找麻烦
5.6 指向指针的指针 由于指针变量本身是—个变量,它被存储在计算机内存特定某处。因此可以创建指向指针的指针变量—— 双重指针变量,其值为一个指针变量的内存存放地址 二维数组和双重指针: 二维数组与一维数组一样,二维数组名也是一个首地址,指向该二维数组对应的内存空间的开始,即:指向二维数组的第一个元素。二维数组可以看做一个特殊的一维数组。其特殊性在于:该一维数组的每一个元素又是一个一维数组名(指向另外的一维数组)。实际上,这个特殊的一维数组就是一个指针数组。所以,二维数组名可以看作是一个双重指针。
5.6 指向指针的指针 二维数组和双重指针(续) : 由于指针变量本身是—个变量,它被存储在计算机内存特定某处。因此可以创建指向指针的指针变量—— 双重指针变量,其值为一个指针变量的内存存放地址 例5.13 二维数组和双重指针(续) : char name[3][20] = {"China", "America", "German"}; 上面语句定义了名为name的数组,它包含3个一维数组,name[0],name[1],name[2],每个一维数组又是存放字符串的字符数组。
5.6 指向指针的指针 二维数组和双重指针(续) : 数组名:name 例5.13 数组名:name 元素: name[0], name[1], name[2](每一个元素都是指向一个字符串的指针) name是一个地址,指向这个含3个元素的指针数组的第一个元素name[0]。 name[0]本身是一个指针,指向字符串”China” 所以,name是一个指向指针的指针,即:双重指针。既然name, name[0],&name[0],&name[0][0]都指向同一个内存单元,当然他们的值是一样的。只不过意义不一样。
5.8 本章小结 指针变量存储的是另一个变量的地址。给指针赋值变量就是将一个内存地址装入指针变量。如果这个内存地址是某变量的地址,则该指针就指向了该变量。 指针变量的类型是指针所指向的变量的类型。 在定义一个指针变量时,将其初始化为空(NULL)是一个好习惯。 对指针赋值是将它所指向的变量的地址赋给指针变量。这时要用到取地址运算符&。&a表示取变量a的地址。 在赋值语句中可使用间接访问运算符*
5.8 本章小结(续) 数组名可视为常量指针,将数组名赋给指针变量,则该指针就指向了该数组的首地址(即数组中的第一个元素所在地址)。 数组中的元素为指针的数组叫指针数组。这时数组中的元素不是普通的数值,而是内存中的地址。 字符数组就是字符串。字符数组只有在定义时才允许整体赋值。C库函数中有字符判断函数和字符串相关函数,在编程时可以选用。 如果对指针使用地址运算符,将得到指针的地址。指针也是—个变量,存储的是它指向的变量的地址。存放指针变量地址的变量叫双重指针。
本章作业 5.3题 5.4题 5.5题