第五章 指针 5.1 指针的概念 5.2 指针与数组 5.3 字符串指针
5.1 指针的概念 地下工作者阿金接到上级指令,要去寻找打开密电码的密钥,这是一个整数。几经周折,才探知如下线索:密钥藏在一栋三年前就被贴上封条的小楼中。 一个风雨交加的夜晚,阿金潜入了小楼,房间很多,不知该进哪一间,正在一筹莫展之际,忽然走廊上的电话铃声响起。艺高人胆大,阿金毫不迟疑,抓起听筒,只听一个陌生人说:“去打开3010房间,那里有线索”。阿金疾步上楼,打开3010房间,用电筒一照,只见桌上赫然6个大字:“地址2000”。阿金眼睛一亮,迅速找到2000房间,取出重要数据98,完成了任务。
5.1 指针的概念 如果我们将房间看作计算机的内存单元,那么,房间的编号就是内存单元的地址,存放地址的内存单元就对应程序中的变量,这类特殊的变量在C中称指针变量。这里,pointer是指针变量,2000是pointer指针变量的值
5.1 指针的概念 1、数据藏在一个内存地址单元中,地址是2000。 2、地址2000又由pointer单元所指认,pointer单元的地址为3010。 3、98的直接地址是2000;98的间接地址是3010;3010中存的是直接地址2000。 4、称pointer为指针变量,2000是指针变量的值,实际上是有用数据藏在存储器中的地址。
5.1 指针的概念 指针的定义及初始化: int *pointer = 0 ; 指针:就是地址,一个变量的指针就是该变量的地址。 指针变量:专门存放地址的一类特殊的变量。 int *pointer = 0 ; 上面的语句定义了一个名为pointer的指针,且被初始化为0,该指针“指向”的目标类型为整型。 在定义指针时,将其初始化为0是一个好办法。
指针变量的常见类型 简单的指针变量分为: int *p, *q; // 定义p,q为指向整数类型变量的指针 float *point; // point为指向float型变量的指针 double *pd; // 定义pd为指向double型变量的指针 int (*pa)[10];// 定义pa为指向int型数组的指针 int **qq; // 定义qq为指向int型指针的指针 StudentInfo *pStu; // 定义指向结构的指针pStu
5.1 指针的概念 int *pointer = 0 ; 指针是一种特殊的变量,特殊性表现在类型和值上。 从变量讲,指针也具有变量的三个要素: (1)变量名,这与一般变量取名相同,由英文字符开始。 (2)指针变量的类型,是指针所指向的变量的类型,而不是自身的类型。 (3)指针的值是某个变量的内存地址。 从上面的概念可知,指针本身类型是int型,因为任何内存地址都是整型的。但是指针变量的类型却定义成它所指向的变量的类型。 int *pointer = 0 ;
5.1 指针的概念 指针的赋值: 1、使用取地址运算符(&)将变量的地址取出赋给指针变量 int i, j; int *i_pointer = 0; i_pointer = &i; 2000 执行以上变量赋值语句后, i_pointer 就指向了变量 i 代表的内存单元。
5.1 指针的概念 指针的赋值(续): 2、将一个已有具体指向的指针变量赋值给另一个指针变量。 int i = 123; int *p1 = 0 , *p2 = 0; p1 = &i ; p2 = p1; 执行以上变量赋值语句后, p1、p2 就指向了相同的内存单元。
5.1 指针的概念 变量(内存)的访问方式: ①直接访问:通过变量名访问。 i = 98; cout<<"i="<<i<<endl; ②间接访问:使用间接访问运算符(*),通过指针变量中存放的值,找到最终要访问的变量。 *i_pointer = 98; cout<<"i="<< *i_pointer <<endl;
有关指针变量的说明 ① 一个指针变量可以指向任何一个同类型的普通变量;但是, 在某一个时刻,它只能指向某一个同类型的变量 int *pa, *pb, a, b; pa = &a; pb = &b; pa = &b; pb = &a; ② 让指针变量指向某个同类型普通变量的方法是:把该普通 变量的地址保存到指针变量中。 ③ 必须对指针变量进行了正确合法的初始化后,才能使用该 指针变量访问它所指向的内存单元。没有具体指向的指针变量叫悬空指针,对它所指向的内存单元的使用是非法的。 int *pa, *pb, a, b; *pa = 100; 或 cout<<"*pa="<<*pa<<endl;
有关指针变量的说明 ④ 即使对指针变量进行了正确合法的初始化后,也只能用该 指针变量访问合法的允许访问的内存单元。不能使用该指针 变量去随意访问其它不确定的内存单元,否则,结果是不可 预料的。 int *pa, *pb, a, b; pa = &a; *pa = 100; pb = &b; *pb = 200; cout<<"value after a = "<<*(pa + 1)<<endl; Cout<<"value after b = "<<*(pb + 1)<<endl; *(pa + 1) = 1000; *(pb + 1) = 2000; 正确安全使用指针变量 错误使用指针变量 错误使用指针变量而且可能很危险
有关运算符*和&的说明 ① 假设有 int a, *pa; pa = &a; 则: &*pa 相当于 &a,因为 *pa 就相当于 a 由于 ++ 和 * 为同一运算级别,则根据结合性, *pa ++ 相当于*(pa ++) ,与(*pa) ++是完全不同的。 *(pa ++) :先得到pa当前指向的单元的值,再使pa自增 (*pa) ++ :是pa所指向的单元的值自增 pa 100 200 *(pa ++) :得到100,pa自增 (*pa) ++ :得到100,pa所指 向的单元变成101,而pa不变
5.2 指针和数组 5.2.1 指针与一维数组 数组的首地址也就是数组中第1个(下标0)元素的地址; 在内存中数组的元素的地址是连续递增的; 5.2.1 指针与一维数组 数组的首地址也就是数组中第1个(下标0)元素的地址; 在内存中数组的元素的地址是连续递增的; 通过数组的首地址,加上偏移量就可以依次得到其它元素的地址; &score[0]+偏移量 -> &score[1] 这里的偏移量就是一个数组元素所占的字节数。编译程序会根据数组元素的类型,自动确定出不同的偏移量
5.2 指针与数组 5.2.1 使用指针访问数组元素 #include <iostream> //预编译命令 using namespace std; int main() //主函数 { //函数体开始 int a[5]={1,3,5,7,9};//定义数组,赋初值 int *p=NULL; //定义指针变量 int i; //定义整型变量 p=a; //赋值给指针变量,让p指向a数组for(i=0;i<5;i=i+1) { //循环体开始 cout<<"a["<<i<<"]="<<*p<<endl; //输出a数组元素的值 p=p+1; //指针变量加1 } //循环体结束 return 0; } //函数体结束
*p a[0] *(p+1) a[1] *(p+2) a[2] *(p+3) a[3] *(p+4) a[4] 说明 (1) p=a; 这里数组名作为数组的起始地址,即a[0]的地址。因此 p=a 等效于 p=&a[0]; (2) p=p+1; 如p指向a[0], 则p=p+1之后,p指向a[1] (3) 如果p=a 等效于 p=&a[0]; 则 p=a+4 等效于 p=&a[4]; *p *(p+1) *(p+2) *(p+3) *(p+4) a[0] a[1] a[2] a[3] a[4] p p 等效 等效 等效 p+1 p+1 p+2 p+2
做下面的实验 #include <iostream> // 预编译命令 using namespace std; int main() // 主函数 { // 函数体开始 int a[5]={1,3,5,7,9}; // 定义数组,赋初值 int *p=NULL; // 定义指针变量 int i=0; // 定义整型变量,赋初值 for(p=a;p<a+5;p=p+1) // 让p指向a数组 { // 循环体开始 cout<<"a["<<i<<"]="<<*p<<endl; // 输出a数组元素的值 i=i+1; // 让i加1 } // 循环体结束 return 0; } // 函数体结束
5.2.2 数组名是一个常量指针,指向该数组的首地址. #include <iostream> using namespace std; int main() { char *p; // 定义指向字符类型的指针变量p char s[] = "abcdefgh";// 定义字符数组,并赋值 p=s; // 数组名是一个常量指针, // 它指向该数组首地址 while(*p != '\0') // 当p所指向的元素不为'\0'时 p=p+1; // 让指针加1 } cout<<"字符串长度为"<<p-s<<endl; return 0;
1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 s a b c d e f g h \0 a b c d e f g h \0 a b c d e f g h \0 p p p p p p p p p s 图中数组的首地址是s[0]的地址,即&s[0]。s可看作是指向s[0]的指针。s是不会动的,是常量指针。 P是指针变量,其值可以改变 执行p=p+1后,p后移一格,指向s[1] 至p指向'\0'为止
#include <iostream> using namespace std; int main() { 数组名是一个常量指针,指向该数组的首地址 #include <iostream> using namespace std; int main() { char shuzi[]="987654321";//定义数组, // 赋初值为数字字符串 char *p=&shuzi[8];// 让p指向shuzi[8]元素, // 该处是字符‘1’ for(;p>=shuzi;p--)// 当p>=shuzi时循环 cout<<*p; // 输出一个由p指向的字符 cout<<endl; // 换行 return 0; }
shuzi 9 8 7 6 5 4 3 2 1 \0 1 2 3 4 5 6 7 8 9 p p p p p p p p p p 说明: 1、字符串: 数字字符串。 2、p指向shuzi[8],即指向串中的字符’1’。 3、直到型循环,用putchar函数将shuzi[8]输出到屏幕;之后让p=p-1。 4、在while中,当p>=shuzi则继续执行循环体。一旦 p<shuzi 则退出循环。这种做法使输出结果为 123456789 5、在本例中数组名shuzi是一个常量指针,永远指向shuzi[0]的地址上。 思考:如何通过p和shuzi求该数字字符串的长度
5.2.3 指针数组 1. 指针数组的概念 指针数组也是数组,但它的各数组元素都是指针,且必须是指向同一种数据类型的指针。 2. 指针数组的定义和初始化 假设存在3个数组,定义如下 int ar0[]={0,1,2}; int ar1[]={1,2,3}; int ar2[]={2,3,4}; 下面的语句定义一个指针数组p,并初始化 int *p[]={ar0,ar1,ar2};
5.2.5 指针与结构 StudentInfo *pStuInfo = 0; StudentInfo stu; struct StudentInfo { char no[20]; int sexy; double height; Date birthday; }; 5.2.5 指针与结构 图5-3-6 指向结构的指针 … &stu.sexy 数据区 地址 结构成员 pStuInfo stu.no[0] stu.sexy &stu stu.no &stu.height &stu.birthday stu.birthday.year StudentInfo stu; StudentInfo *pStuInfo = 0; pStuInfo = &stu; 引用结构变量stu的成员: stu.no = "1443011101"; (*pStuInfo).no = "1443011101"; pStuInfo -> no = "1443011101";
输入 学生 信息 const int STU_NUM = 20; // 学生人数 StudentInfo stu[STU_NUM+1]; // 下标i处存储第i位同学的信息,stu[0]不使用 StudentInfo *p_stuInfo = 0; // 指向学生信息的指针,初始化为0 int i = 0, j = 0; // 循环变量 int changed = 0; // 交换标志 p_stuInfo = stu+1; //p_stuInfo指向stu[1] for(; p_stuInfo <= stu+STU_NUM; p_stuInfo++) { *p_stuInfo = readStudent(); // 输入当前学生信息 } 输入 学生 信息
按 身 高 排 序 p_stuInfo = stu; //p_stuInfo指向结构数组的开始 for(i =1; i < STU_NUM; i++) // 扫描n-1遍 { changed = 0; // 置交换标志为,表示未交换 for(j=1; j <= STU_NUM-i; j++) { // 每遍比较n-i次 if((p_stuInfo[j].height) > (p_stuInfo[j+1].height)) {// 交换stu[j]和stu[j+1] stu[0] = p_stuInfo[j]; p_stuInfo[j] = p_stuInfo[j+1]; p_stuInfo[j+1] = stu[0]; changed = 1; // 置交换标志为1 } if(changed == 0) {// 如果本遍排序中未交换,则退出循环 break; 按 身 高 排 序
输出排序后学生信息 cout << endl << "输入的学生信息为(按身高升序排列):" << endl; for(p_stuInfo++; p_stuInfo <= stu+STU_NUM; p_stuInfo++) { writeStudent(*p_stuInfo); // 依次输出个人信息 }
5.3 字符串指针 可以使用指针指向字符数组或字符串常量,通过指针来使用字符数组或字符串常量。 指向字符串的指针变量实际上就是一个指向字符型变量的指针变量,正如同指向整型数组的指针变量就是指向一个整型变量的指针变量一样,因为一个字符串就是一个字符数组。 指向字符串的指针变量,它保存的是字符串中第一个字符所在内存单元的地址。
通过指针变量访问字符串 能够通过指针变量访问字符串的前提是:已经定义了一个字符型指针变量,并且已经把某个字符串的指针赋值给它。 访问方法: 指针法或下标法 如果pstr的初值为字符串的第一个字符的地址,比如有 char *pstr = "Hello, World"; 则有以下事实: ① pstr + i 就是第 i 个字符的地址,即它指向字符串中的第 i 个字符 ② *(pstr + i) 或者 pstr[i] 就是它所指向的字符 ③ 指针变量也可以使用下标法,即 pstr[i] 和*(pstr + i) 是等价的
通过指针变量引用数组元素: ◆ 编程把字符串s2复制到字符串s1中 /* 方法1:用数组名和下标 */ #include <iostream> using namespace std; int main( ) { char s1[80] = "", s2[ ] = "hello, world"; int n; for(n = 0; s2[n] != '\0'; n ++) s1[n] = s2[n]; s1[n] = '\0'; cout<<"s1="<<s1<<"\ns2="<<s2<<endl; return 0; }
通过指针变量引用数组元素: ◆ 编程把字符串s2复制到字符串s1中 /* 方法2:用数组名+偏移量得到元素地址,访问元素*/ #include <iostream> using namespace std; int main( ) { char s1[80] = "", s2[ ] = "hello, world"; int n; for(n = 0; s2[n] != '\0'; n ++) *(s1 + n) = *(s2 + n); *(s1 + n) = '\0'; cout<<"s1="<<s1<<"\ns2="<<s2<<endl; return 0; } 特点: 数组名本身不变也不可能被改变
通过指针变量引用数组元素: ◆ 编程把字符串s2复制到字符串s1中 /* 方法3:用指针变量+偏移量得到元素地址,访问元素*/ #include <iostream> using namespace std; int main( ) { char s1[80] = "", s2[ ] = "hello, world"; int n; char *p1 = s1, *p2 = s2; for(n = 0; *(p2 + n) != '\0'; n ++) *(p1 + n) = *(p2 + n); *(p1 + n) = '\0'; cout<<"s1="<<s1<<"\ns2="<<s2<<endl; return 0; } 特点: 指针变量本身的值没有变化
通过指针变量引用数组元素: ◆ 编程把字符串s2复制到字符串s1中 /* 方法4:用指针变量自身变化得到元素地址,访问元素*/ #include <iostream> using namespace std; int main( ) { char s1[80] = "", s2[ ] = "hello, world"; int n; char *p1 = s1, *p2 = s2; for(n = 0; *p2 != '\0'; n ++) *(p1 ++) = *(p2 ++); *p1 = '\0'; cout<<"s1="<<s1<<"\ns2="<<s2<<endl; return 0; } 特点: 普通变量做循环控制变量
通过指针变量引用数组元素: ◆ 编程把字符串s2复制到字符串s1中 /* 方法5:用指针变量自身变化得到元素地址,访问元素 */ #include <iostream> using namespace std; int main( ) { char s1[80] = "", s2[ ] = "hello, world"; char *p1 = s1, *p2 = s2; for( ; *p2 != '\0'; p1 ++, p2 ++) *p1 = *p2; *p1 = '\0'; cout<<"s1="<<s1<<"\ns2="<<s2<<endl; return 0; } 特点: 指针变量做循环控制变量
指向字符串的指针变量的有关运算 如有: char str[80], *ps; ps = str; 或 ps = &str[0]; ① ps ++ 或 ps += 1,是下一个字符的地址(即 ps 指 向下一个字符) ps -- 或 ps -= 1,是上一个字符的地址(即 ps 指向 上一个字符) ② * ps ++ 等价于 *(ps ++) ,即先得到 ps 当前指向 的字符,然后再使 ps 自增,从而指向下一个字符 ③ * ++ ps 等价于 *(++ ps) ,即先使 ps 自增,指向 下一个字符,然后得到 ps 所指向的字符 ④ (* ps) ++ ,则是表示 ps 当前指向的字符加1