Post on 06-Jan-2016
description
C++ 语言程序设计
第六章指针、引用和动态空间管理(第一讲)
学习目标1. 掌握定义各种指针的方法,掌握指针的
基本操作;2. 了解指针与数组的关系,掌握利用指针
访问数组元素的方法;3. 了解指针与函数的关系,掌握利用指针
传递数据参数和函数参数的方法;4. 了解引用的概念和定义各种引用的方法,
掌握利用引用传递数据参数;5. 掌握动态空间的申请和释放的方法,会
利用指针操纵动态空间。
第一讲主要内容
指针的概念 指针变量的定义 常值指针 指针的基本操作
指针变量的定义 ..— 定义格式:类型修饰符 *变量名〖 =指针表达式 〗 ;
例如:例如:int *pnint *pn,, *ph;*ph;double d,*pd1=&d,*pd2=pd1;double d,*pd1=&d,*pd2=pd1;char *s="This is a string";char *s="This is a string";void *pd3=NULL,*pd4=0;void *pd3=NULL,*pd4=0;long *pd5=NULL; long *pd5=NULL;
指针变量的定义ddpd1pd1
pd2pd2
ss
This is a string\0This is a string\0
图图 6-26-2 指针图示方法示例指针图示方法示例
pd3pd3
pd4pd4
pd5pd5
指针的概念空指针:地址值为 0 ,专用于表示未指向任何
数据;地址值 0 可用 NULL 表示;无类型指针( void 指针):无类型限制的指
针,可以用来指向任何类型的数据。
指针的基本操作 ..• —— 指针赋值:操作符 =
同类型指针之间可以互相赋值任何类型的指针可赋值给 void 指针,但反
过来却不行,例如:char c=’X’,*s; s=&c;void *p1,*p2=NULL;p1=s; // 允许s=p2; // 不允许
指针的基本操作 ..
—— 取变量的地址:操作符 &例如: int k,*p; p=&k; //p 指向变量 k
上面两个语句可以合并成一个: int k,*p=&k; //p 指向变量 k
指针的基本操作 ..• —— 间接访问:操作符 *
通过 1 元操作符 * 可以存取指针所指向的数据,例如: int *pd,d; pd=&d; // 使 pd 指向 d *pd=99; cout<<*pd<<' '<<d; 程序输出 99 99
指针的基本操作 .. * 和 & 是两个互逆的操作,当这两个
操作符碰到一起时,其作用相互抵消,例如 *&k=3与 k=3效果完全相同
常值指针 ..
— 含义之一:所指向的数据为常值定义这样指针时须将 const 放在 * 号之前,
例如: const char *s="Hello!";
或 char const *s="Hello!";
常值指针 ..定义这样指针时不必初始化,如:
const char *s; s="Hello!";
s 所指向的数据不可改变,但 s 本身可改变,例如: s="Hi!"; // 正确! *s='Y'; // 错误!
常值指针 ..• —— 含义之二:指针本身为常值
定义这样指针须将 const 放在变量名之前,而且必须初始化。如: char * const s="Hello!";
s 本身不可改变,但所指向的数据可以改变,例如: s="Hi!"; // 错误! *s='Y'; // 正确!
常值指针• — 常值也可以是上述两种含义的综合
,如: const char * const s="Hello!";
或 char const * const s="Hello!";
指针的基本操作 ..• —— 判断一指针是否是空指针:
操作符 == 和 != “ 如果 p 是空指针,则 ...”
if(p==0)...
if(p==NULL)...
if(!p)... // C++ 风格
指针的基本操作 ..“ 如 果 p 不 是 空 指 针 , 则 ...”
if(p!=0)...
if(p!=NULL)...
if(p)... // C++ 风格 也可利用 > < >= <= 等操作符比较两个指
针的大小,但很少用到。
指针的基本操作 ..—— 计算两地址间数据单元的个数:操作符 – 同类型的两指针相减,其结果是一个整数,表示两地址之间可容纳的相应类型数据的个数,例如:int n,m[12],*p1=&m[5],*p2=&m[10];n=p2-p1; //n==5
指针的基本操作 ..
m
11109876543210
p1
p2
指针的基本操作 ..—— 指针移动:移动 n 个单位格式:
指针表达式 + n 指针变量 += n指针表达式 - n 指针变量 -= n
移动的实际字节数 = n*sizeof( 指针的类型 )
指针的基本操作 ..• —— 指针移动:移动 1 个单位
格式: ++指针变量 指针变量 ++ --指针变量 指针变量 --执行 int k,*pk=&k; cout<<endl<<++pk; cout<<endl<<pk;显示的是两个相同的地址
指针的基本操作 ...执行
int k,*pk=&k; cout<<endl<<pk++; cout<<endl<<pk;显示的是两个不同的地址
• —— 指针表达式也有与数值表达式类似的副作用问题
指针的基本操作 ..操作名称 操作符 第一操作对象 副作用 操作结果
前增 1 ++
变量有
视同变量
前减 1 --
赋值 =
复合赋值 += 、 -= 、 *= 等
后增 1 ++ 视同常量
后减 1 --
取地址 &
无间接访问 *
任意视同变量
其他 + 、 - 、 / 、== 等 视同常量
指针的基本操作 ..• —— 指针类型的强制转换
格式:( 类型修饰符 *) 指针表达式
举例 :short i=258; //00000001 00000010
char *p=(char *)&i;
cout<<endl<<int(*p)<<int(*(p+1));
输出是: 21
指针的基本操作 ..—— 正确理解下列组合操作的含义:
*p++
(*p)++
*++p
++*p
指针的基本操作 ..*p++ :取 p 所指向单元的数据作为表达式的值,然后使 p 指向下一个单元;
执行 *p++ 后:
( *p++ 的值: 3 )
d 3 6 9
p
3 6 9初始状态: d
p
指针的基本操作 ..(*p)++ :取 p 所指向单元的数据作为表达式的值,然后使该单元的数据值增1 ;
执行 ( *p)++ 后:
( (*p)++ 的值: 3)
d 4 6 9
p
3 6 9初始状态: d
p
指针的基本操作 ..*++p :使 p 指向下一个单元,然后取该单元的数据作为表达式的值;
执行 * ++ p 后:
( * ++ p 的值: 6)
d 3 6 9
p
3 6 9初始状态: d
p
指针的基本操作 ..++*p :将 p 所指向单元的数据值增 1并作为表达式的值。
执行 ++ *p 后:
( ++ *p 的值: 4)
d 4 6 9
p
3 6 9初始状态: d
p
C++ 语言程序设计
第六章指针、引用和动态空间管理(第二讲)
第二讲主要内容
数组的指针访问方式 关于“指向数组的指针” 字符指针与字符串 指针数组 数组参数
数组的指针访问方式 ..
—— 一维数组元素的指针访问方式一维数组名是指向该数组首元素的的常值指针 A[i] ←→ *(A+i) A[0] ←→ *(A+0) ←→ *A int s[]={0,1,2,3,4,5},*p=s; cout<< *p << p[1] << *(p+2) << s[3] << p[4] << *(s+5);
数组的指针访问方式—— 二维数组元素的指针访问方式 一维数组的情况可以推广到二维数组和更多
维的数组:二维数组名是指向该数组首行一维数组的一
根指针 B[i][j] *(B[i]+j) ↓ ↑ A[j] ←→ *(A+j)
因此 : B[i][j] ←→ *(B[i]+j) ←→ *(*(B+i)+j)
数组的指针访问方式 ..当 i 、 j 之一为 0 或均为 0 时:B[i][0] ←→ *(B[i]+0) ←→ *B[i] ←→ **(B+i)
B[0][j] ←→ (*(B+0))[j] ←→ (*B)[j] ←→ *(*B+j)
B[0][0] ←→ *(B[0]+0) ←→ *B[0] ←→ **(B+0) ←→ **B
数组的指针访问方式 ..
—— 数组的下标操作就是一种特定形式的指针操作: A[i]是 *(A+i)的另一种表示形式。
关于“指向数组的指针” ..—— 一维数组的例子:
int s[5];int (*ps1)[5]=&s; int *ps2=s; // 或 =&s[0];
关于“指向数组的指针” ..
ps1ps1(&s)(&s)
ps2ps2 (s (s 或 或 &s[0])&s[0])
关于“指向数组的指针” ..
—— 二维数组的例子: int w[3][4];int (*p1)[3][4]=&w; int (*p2)[4]=w; // 或 =&w[0]; int *p3=w[0]; // 或 =&w[0][0];
关于“指向数组的指针” ..p1
( &w )
p2
( w 或 &w[0] )
p3
( w[0] 或 &w[0][0] )
关于“指向数组的指针” ..
—— 直接计算数组单元地址二维数组 i 行 j 列单元地址 =
首元素地址+ i× 列数+ j
例 6.1 :设计函数 show_matrix ,它显示参数传来的任意规格的整型二维数组。
关于“指向数组的指针” .. 1: #include<iomanip.h> 2: 3: void show_matrix(int *Array, int row,int col,int width){ …… 7: for(int i=0;i<row;i++){ 8: cout<<endl; 9: for(int j=0;j<col;j++) cout<<setw(width) <<*(Array+i*col+j); 10: } 11: }
关于“指向数组的指针” 12:
13:void main(){ 14: int s[][3]={{ 1, 2, 3}, { 4, 5, 6}, { 7, 8, 9}, {10,11,12}};
15: show_matrix(s[0],4,3,5);
16: cout<<endl;
17: }
也可以是:也可以是:&s[0][0]&s[0][0]
字符指针与字符串 ..—— 程序中的字符串常量就是指向该字符串的指针,例如: char *p="string";
或 char *p;
P="string";
字符指针与字符串 ..——(a) char *p="string";
和 (b) char p[]="string";的区别(b) 是p[]={'s','t','r','i','n','g','\0'};
的简略表示形式, (a)没有类似的表示形式
字符指针与字符串 .. (a)中的字符串存储于静态数据区,( b)中
的字符串的存储特性由相应的变量决定,可以是静态的或自动的
(a)中的字符串一般不应改变
字符指针与字符串 ..—— 例 6.2 设计函数 STRLEN ,模拟标准函数 strlen 。
int STRLEN(const char *d){ int p=0; while( *d++ ) p++; return p;}
字符指针与字符串 ..—— 例 6.3 设计函数 STRCAT ,模拟标准函数 strcat 。 char *STRCAT(char *s, const char *d){ char *p=s; while(*p) p++; do *p++=*d; while(*d++); return s;}
指针数组 ..
—— 若数组的每个元素是一个指针,则称为指针数组,例如:
int *ip[10];double *dp[5][8];
int a,b,c,d;
int *s[]={&a,&b,&c,&d};
指针数组 ..—— 指针数组应用:构造字符串符号表
例如: char *MONTH[]={
NULL,"January","Feburay",
"March","April","May","June",
"July", "August","September",
"October","November","December"
};
指针数组 ..—— 指针数组应用:获取命令行参数信息命令行参数实例:C:\>attrib +r readme.txt
主函数形参的含义:int main(int argc,
char *argv[])
命令行参数的个命令行参数的个数(至少是数(至少是 11 ))
存放命令行参数存放命令行参数的“符号表”的“符号表”
指针数组 .. argv[0] 指向被运行程序的全路径程序文件名。
argv[1] 指向命令行程序名后的第一个字符串。
argv[2] 指向命令行程序名后的第二个字符串。
……argv[argc-1]
指向命令行程序名后的最后一个字符串。argv[argc]
空指针( NULL )。
指针数组 .. 1: // 例 6.4 ,文件名 doscomm.cpp: 演示 2: #include <iostream.h> 3: #include <stdlib.h> 4: 5: int main(int argc, char *argv[]){ 6: int i; 7: cout<<" 有 "<<argc <<" 个命令行参数: "<<endl<<endl; 8: for (i=0;i<argc;i++) 9: cout<<"argv["<<i<<"]: " <<argv[i]<<endl; 10: return 0; 11: }
指针数组 ..—— 指针数组应用:间接排序例 6.5 :设计函数模版
template<class T,int size >void show_in_order(T data[]);
它将数组 data 中的 size 个数据按由小到大的顺序显示输出,但不改变 data 中数据的原有存储顺序。
指针数组 ..template<class T,int size> void show_in_order(T data[]){ T *pd[size]; int m; for(m=0;m<size;m++) pd[m]=&data[m]; for(m=0;m<size-1;m++){ int j=m; for(int i=m+1;i<size;i++) if(*pd[i]<*pd[j]) j=i; cout<<*pd[j]<<' '; if(j>m) pd[j]=pd[m]; } cout<<*pd[size-1]; }
指针数组 ..void main() //演示{ int data[]={12,23,9,34,45,7,78,-33,59,3}; #define SIZE (sizeof(data)/sizeof(data[0])) int m; cout<< endl<<"排 序 前: "; for(m=0;m<SIZE;m++) cout<<data[m]<<' '; cout<< endl<<"排序效果: "; show_in_order<int,SIZE>(data); cout<< endl<<"排 序 后: "; for(m=0;m<SIZE;m++) cout<<data[m]<<' ';}
指针数组如果颠倒两个模板参数的顺序 :template< int size, class T >
void show_in_order(T data[]);
则主函数中的调用show_in_order<int,SIZE>(data);
可改为show_in_order<SIZE>(data);
演示
数组参数 ..—— 形参表中的数组参数一般不限定第一维的大小,如: int sum(int array[],int size);
和 void sumAll2(int data[][5], int result[],int rows);
数组参数 ..—— 形参表中的数组参数实际上就是指针参数,因此前例也可说明为: int sum(int *array,int size);
和 void sumAll2(int (*data)[5], int *result,int rows);
数组参数—— 也即,在形参表中存在着这样的等效关系: A[] ←→ *A 或 (*A) —— 数组参数是指针参数的另一种表示形式。在函数主要以下标方式对参数指针所指向的数据进行操作的情况下,将指针说明为“数组”可使程序更容易理解。
C++ 语言程序设计
第六章指针、引用和动态空间管理(第三讲)
第三讲主要内容
指针与函数 引用 动态空间管理
指针与函数 ..—— 指针参数 作用 1 :通过将数据区的地址传递给函数,使函
数能够改动该地址处的数据,例如:
int addTo(int data,int *agg) { return *agg+=data; }
可以如下方式使用函数:
int total=10; cout << addTo(5,&total);
指针与函数 ..作用 2 :减少参数传递过程中的数据复制
量如果仅以“作用 2” 为目的,应将参数说明为常值指针,如void show(const int *p,int n){ for(int i=0;i<n;i++) cout << *p++ << ',";}
也可以是也可以是p[i]p[i]
指针与函数 ..如果参数是指向数组的指针,也可以按数组
方式进行说明,例如:
void show(const int p[],int n){
for(int i=0;i<n;i++)
cout << p[i] << ',";
}
一般不限定第一般不限定第一维大小一维大小
也可以是也可以是*p++*p++
指针的基本操作 ..例 6.6 :设计函数 char *nextWord(char **pp); 它从一特定位置开始扫描一字符串,返回所遇见的第一个词,即第一个不含空格的连续的字符序列,如字符串 "What is your name?"中就包含了 4 个词。
指针的基本操作 .. 主函数: void main() { char s[]= "What is your name?", *ps=s; do cout<<nextWord(&ps) <<endl; while(*ps); }
指针的基本操作 ..main
W h a t i s y o u r n a m ? s
ps
nextWord
W h a t word
pw pp
e
指针的基本操作char *nextWord(char **pp)
{
static char word[81];
while(**pp==' ')(*pp)++;
char *pw=word;
while(**pp && **pp!=' ') *pw++=*(*pp)++;
*pw='\0';
return word;
}
指针与函数 ..• —— 指针函数:返回指针值的函数 定义格式:
类型修饰符 *函数名 (形式参数表 )函数体例如:去掉参数字符串 s 的尾部空格char *trim(char *s){ char *p=s+strlen(s)-1; while(p-s>=0 && *p==' ') p--; *(p+1)='\0'; return s;}
指针与函数 ..函数所返回的指针不能指向返回后即不存在的对象,如:
int *fun(int a,int b){
if(a>b) return &a;
return &b;
}
不要把参数变量和自动变量的地址作为函数的返回值
指针与函数 ..• —— 函数指针:指向函数的指针
对于函数:类型修饰符 函数名 ( 形式参数表 );
指向该函数的指针应定义为:类型修饰符 (* 变量名 )( 形式参数表 )
〖 = 函数名〗 ;
指针与函数 ..对于函数:
类型修饰符 *函数名 ( 形式参数表 );
指向该函数的指针应定义为:类型修饰符 *(* 变量名 )( 形式参数表 )
〖 = 函数名〗 ;
指针与函数 ..• 例如:• int f1(int n){……}• char *f2(int n,char *s){……}• int (*pf1)(int);• pf1=f1;• // 或: int (*pf1)(int)=f1;• char *(*pf2)(int,char *);• pf2=f2; • // 或: char *(*pf2)(int,char *)=f2;
指针与函数 .. 1:#include<iostream.h> 2: // 例 6.7 3:int add(int a,int b){ return a+b;} 4: 5:void main(){ 6: int (*p)(int,int); 7: p=add; 8: cout<<add(3,5); 9: cout<<(*p)(3,5); 10: cout<<p(3,5); 11: cout<<(*add)(3,5); 12:}
指针与函数 ..函数指针的主要用途:将一函数作为参数传
递给另一函数,供该函数调用
例:设计计算定积分的函数 integ ,它根据给定的上、下限和精度要求,计算并返回给定函数的定积分值。
指针与函数 ..n=5 时近似计算
的原理示意
2 4 6-2
2468
101214
0
4
51
2
.
dxx
指针与函数 ..double integ(double (*f)(double),
double a,double b,int n){
double s=0.0,span;
double gap=(b-a)/n;
for( span=a+gap/2.0;span<b;span+=gap)
s+=gap*f(span);
return s;
}
作为参数,也可以声明为作为参数,也可以声明为double f(double)double f(double)
指针与函数 ..• double sqr(double x){ return x*x;}• • double cubic(double x) { return x*x*x;}
• • void man()• {• cout<<integ(sqr,2.0,5.0,100);• cout<<endl;• cout<<integ(cubic,2.0,7.5,100);• }
引用 ...• —— 引用变量
定义一个引用( reference )就是为一个变量、函数等对象规定一个别名。
定义引用变量的一般格式是:类型修饰符 &别名 = 所代表的对象 ;
引用 .. 例如:int i=0;int &ir=i; ir=2; // 等同于 i=2;int *p=&ir; // 等同于 int *p=&i;
引用 ..进一步的例子:int a[10],*p=a;
int &ra1=a[6]; //代表 a[6]
int (&ra2)[10]=a; //代表数组 a
int *&rp1=p; //代表指针变量 p
int &rp2=*p;
//代表 p 所指向的那个对象,//即数组元素 a[0]
引用 ..—— 引用参数形参表中的引用变量的初始化不是在定义时
完成的,而是在调用时完成的:类型修饰符 &形参变量
引用参数具有指针参数类似的作用,但无需进行指针操作;
引用 ..—— 指针参数与引用参数的比较指针参数例子:template<class T>void swap(T *a,T *b){ T c = *a; *a = *b; *b = c;}调用方式: int x=3,y=5; swap(&x,&y);
引用 ..引用参数例子:template<class T>
void swap(T &a,T &b){
T c = a;
a = b;
b = c;
}
调用方式: int x=3,y=5;
swap(x,y);
动态空间管理 ..—— 数据利用内存空间的几种情况静态空间:静态变量所用的空间,在编译时分配,生存期:应用程序运行的整个期间;
自动空间(堆空间):自动变量所用的空间,在运行到定义该变量所在的块时分配,但在编译时即已确定大小,生存期:从定义处开始到块结束处为止;
动态空间管理 ..栈空间:函数参数和函数返回值所用的空间,
在函数被调用时分配,但在编译时即已确定大小,生存期:函数被调用的整个期间;
动态空间(自由空间):动态数据所用的空间,用 new 申请时分配,生存期:从申请时开始,直到用 delete 释放时为止。
动态空间管理 ..#include<iostream.h>int a;void show(int n){ static int b; cout<<b<<; b=n; }void main(){ int c=9,*d=new int(8); show(c); show(*d); delete d;} // 数据利用各种内存空间示意
静态空间
静态空间
自动空间
动态空间
栈空间
动态空间管理 ..—— 何时需要动态空间?编译时无法确定数据的多少编译时无法确定每个数据所需空间的大
小编译时无法确定数据的生存期数据需要复杂的存储结构
动态空间管理 ..—— 非数组动态空间 申请空间的格式:new 类型说明〖 (表达式 ) 〗
释放空间的格式: delete 指针表达式【 ,指针表达式】 ;
动态空间管理 ..示例:int *p1,**p2;
p1=new int(5);
p2=new (int *);
*p2=new int(7);
cout<<endl<<*p1<<' '<<**p2;
delete p1,*p2,p2;
动态空间管理 ..
*p1
**p2*p2
p1
5
p2
7
动态空间管理 ..必须用一个由足够生存期的指针指向申请到
的动态空间也可以用一个引用来代表申请到的动态空间,
如int &d=*(new int);
delete &d;
动态空间管理 ..—— 数组动态空间申请空间的格式:new 类型说明 [元素个数 ] new 类型说明 [行数 ][列数 ]
释放空间的格式: delete []指针表达式【 ,[]指针表达式】 ;
动态空间管理 ..示例:int *ap=new int[10];
double (*Matrix)[20]= new double[20][20];
……
delete []ap,[]Matrix;
动态空间管理 ..第一维的说明可以是任意表达式,而从第二维开始就必须是常量表达式。例如(假定 m,n 都是变量):
int *p1=new int[n]; // 正确int (*p2)[6]=new int[n][6];// 正确int (*p3)[n]=new int[m][n]; // 错int (*p4)[n]=new int[10][n];// 错
动态空间管理 ..
—— 动态空间应用实例改进例 6.5中的函数模版:template<class T,int size >
void show_in_order(T data[]);
使得需要排序输出的数组的大小可以在运行时动态确定。
动态空间管理 template<class T,int size>void show_in_order(T data[]){ T *pd[size]; ……} template<class T>template<class T>
void show_in_order(T data[],void show_in_order(T data[], int size){ int size){ T **pd=new(int *)[size]; T **pd=new(int *)[size]; …… …… delete []pd; delete []pd;}}
C++ 语言程序设计
第六章讲完再见