2009-7-29 1
C++大学基础教程第 6章 指针和引用
2009-7-29 -2-
指针( Pointer)是 C++和 C的一种数据类型。很多其他高级语言也有类似的数据类型。引用
( Reference)则是 C++所特有的一种数据类型。指针和引用在概念上和使用上都有相似之处,但是也有重要的差别。
2009-7-29 -3-
第六章 指针和引用
6.1 指针的概念
6.2 指针的运算
6.3 指针和函数
6.4 指针和字符串
6.5 通过指针访问数组
6.6 指针访问动态内存
6.7 引用概念
2009-7-29 4
6.1 指针的概念
2009-7-29 -5-
6.1.1 指针和指针变量指针是变量的地址。或者说是在内存中,存放某种类型变量的地址。
例如,定义了整型变量 a,a的地址就是一个指针。
也可以不定义任何变量,只是指定内存某个地址开始(如 0x00430100)的 4个字节存放整型变量,这样的地址也是指针。
存放指针的变量就是指针变量。
2009-7-29 -6-
6.1.1 指针和指针变量当声明了一个指针变量后,确定了三件事:
变量本身在内存中所分配的地址和字节数,指针变量总是占有 4个字节;
系统允许的地址范围,以及地址单元内可以存放的内容;
指针变量可以进行的运算。
访问指针变量时,只能看到地址。只有通过这个地址,才能访问地址单元中的内容。这样的访问称为对于内存单元的 间接访问 。
2009-7-29 -7-
6.1.2 指针变量的声明和初始化指针变量声明的格式是:
<类型名 > *变量名 1,*变量名 2;
例如:
int *va1,*va2;
char *ch1,*ch2;
指针变量在声明后,变量的值(地址)是随机的。 这样的指针变量是不能安全的使用的 。因为其中的随机地址完全可能不是有效的数据地址。
2009-7-29 -8-
6.1.2 指针变量的声明和初始化在声明指针变量时的? *?,有两个含义:
声明变量 va1,va2,ch1,ch2都是指针变量;
说明变量 va1和 va2的类型是( int *)型,即 指向整型变量的指针 。 va1和 va2所指定的地址单元中,
只能存放整型数据。类似地,ch1和 ch2的类型是
( char *)型,它们所指定的地址单元中,只能存放字符。
指针变量都是有类型的,指针变量的类型就是它所指定的地址单元中存放的数据的类型 。
2009-7-29 -9-
6.1.2 指针变量的声明和初始化指针变量的初始化有两种方法:在声明时的初始化和声明后的初始化。
声明指针变量时就进行初始化的格式是:
<类型名 > *指针变量名 =&变量名 ;
其中的变量名应该是已经声明或定义的同类型变量名。例如:
char ch1=’Y’,ch2=’A’;
char *pch1=&ch1,*pch2=&ch2;
2009-7-29 -10-
6.1.2 指针变量的声明和初始化也可以在声明指针变量后,用赋值的方式对它们进行初始化。例如:
int i1=’Y’,i2=’A’;
int *pi1,*pi2;
pi1=&i1;
pi2=&i2;
没有初始化指针变量是不可以使用的。编译带有这样的指针变量的程序,编译系统会给出警告,而运行时会出现错误。
2009-7-29 -11-
6.2 指针的运算
2009-7-29 -12-
6.2 指针的运算表 6.1 指针的运算
2009-7-29 -13-
6.2.1 指针的赋值运算指针的赋值运算一定是地址的赋值。用来对指针变量赋值的可以是:
同类型变量的地址;
同类型的已经初始化的指针变量;
其他同类型的指针。
此外,也可以用 0或者 NULL对指针变量赋值。使得变量包含的是? 空指针?,即不指向任何的内存物理地址。
必须注意:不同类型的指针是不可以互相赋值的。在指针赋值时,不存在类型自动转换的机制。
2009-7-29 -14-
例 6.1 观察以下指针赋值运算的结果。如果将注释去掉,结果将如何?
#include <iostream>
using namespace std;
void main()
{int va1=100,*pva1;
float vf1='A',*pvf1,*pvf2;
int *pva2=NULL;
cout<<"value of pva2 is "<<pva2<<endl;
pva1=&va1;
pvf1=pvf2=&vf1;
cout<<pva1<<" "<<&va1<<endl;
cout<<pvf1<<" "<<pvf2<<endl;
//pvf1=pva1;
}
value of pva1 is 0x00000000
0x0012FF7C 0x0012FF7C
0x0012FF74 0x0012FF74
注释去掉会出现编译错误
2009-7-29 -15-
6.2.2 间接引用运算间接引用运算符? *? 是一种一元算符,它和指针变量连用,对指针所指向的内存地址单元进行间接访问。使用的格式是:
*指针变量如果指针变量 iptr指向整型变量 va,*iptr就是变量 va的内容
2009-7-29 -16-
例 6.2 对变量的直接访问和间接访问:写出以下程序运行结果。
#include <iostream>
using namespace std;
void main()
{char ch1='a',*ch;
int k1=100;
ch=&ch1; //指针 ch指向变量 ch1
cout<<"*ch="<<*ch<<endl; //间接访问
*ch='B';
cout<<"ch1="<<ch1<<endl; //直接访问
ch1=k1;
cout<<"*ch="<<*ch<<endl; //间接访问
}
运行结果,
*ch=a
ch1=B
*ch=d
2009-7-29 -17-
例 6.3 定义指向指针的指针变量。观察对这种指针变量间接访问的结果。
#include <iostream>
using namespace std;
void main()
{ int va=100,*pva,**ppva; //ppva是指向指针的指针
int k1=100;
pva=&va;
cout<<"*pva="<<*pva<<endl; //间接访问结果是整型数
ppva=&pva;
cout<<"*ppva="<<*ppva<<endl; //间接访问结果是地址
cout<<"pva="<<pva<<endl; //就是指针 pva的内容
}
运行结果,
*pva=100
*ppva=0x0012FF7C
pva=0x0012FF7C
2009-7-29 -18-
6.2.2 间接引用运算
2009-7-29 -19-
6.2.3 指针的算术运算指针可以进行的算术运算只有加法和减法。
指针可以和一个整数 n做加法或者减法运算。
指针 p和整数 n相加 (相减 )的含义是指向当前指向位置 p的前方或后方第 n个数据的地址。
2009-7-29 -20-
例 6.4 通过指针的间接访问,输出下标为偶数的数组元素的值 。
#include<iostream>
usingnamespace std;
voidmain()
{ int k1[10]={11,24,37,44,58,66,79,86,93,108},*k;
k=&k1[0];
for(inti=0;i<10;i=i+2)
cout<<"k1["<<i<<"]="<<*(k+i)<<" ";
cout<<endl;
} 运行结果,
K1[0]=11 k1[2]=37….
数组第一个元素(下标为
0)的地址赋值给指针 k每次循环,指针加 2
2009-7-29 -21-
6.2.3 指针的算术运算指针和指针的直接加法是没有意义的,
也是不允许的。
指针和指针的减法是可以进行的,其意义是求出两个指针之间可以存放几个指定类型的数据。
不允许用一个整数减一个指针。
2009-7-29 -22-
6.2.4指针的关系运算和逻辑运算相同类型的指针可以进行各种关系运算。比较两个指针相等还是不相等。
进行指针? 大于?,? 小于? 的比较,只是要判定指针在内存中的相对位置。
指针和一般的整数比较是没有意义的,也是不允许的。惟一可以和指针比较的整数是 0。通过指针和 0的比较来判定指针本身是不是空指针。
2009-7-29 -23-
6.2.5 void类型指针
void类型的指针就是? 无类型? 指针。声明的方式如下:
void *<指针名 >;
void类型的指针变量中存放的也是内存的地址,
但是不指定这个地址单元内的数据的类型。
2009-7-29 -24-
6.2.5 void类型指针
void类型的指针的使用:
任何其他类型的指针都可以赋值给 void指针。必须注意,这样赋值后的 void指针的类型仍然是 void。
void类型指针不可以直接赋值给任何其他类型的指针。
无论何时,void指针都不可以通过间接引用来访问内存中的数据。
要通过 void类型指针访问内存的数据,必须进行指针类型的强制转换,才可以通过指针间接引用访问内存数据。
2009-7-29 -25-
例 6.5 使用 memcpy通用复制函数复制数组 。
#include<iostream>
usingnamespacestd;
#include<string.h>
void main()
{ char src[10]="012345678";
chardest[10];
char* pc=(char*)memcpy(dest,src,10);
cout <<pc<<endl;
int s1[3]={1,2,3};
int d1[3];
int *pi=(int*)memcpy(d1,s1,12);
cout<<*pi<<" "<<*(pi+1)<<" "<<*(pi+2)<<endl;
}
运行结果,
012345678
1 2 3
复制字符数据,10个字节复制整型数据,12个字节
2009-7-29 -26-
6.2.5 void类型指针
void类型指针还有一个具体的应用:显示字符指针的内容。除了字符指针外,其他指针都可以直接用 cout语句来输出地址值。但是,用
cout输出字符指针时,则是输出它所指向的字符串。可以将字符指针强制转换为 void指针,
再用 cout语句输出,就可以看到地址值。如:
char *pch="Hello C++";
cout<<pch<<endl;
cout<<(void*)pch<<endl;
2009-7-29 -27-
6.3 指针和函数
2009-7-29 -28-
6.3 指针和函数在程序设计中,指针有很多应用。其中之一就是用指针作为函数的参数,从而形成了 C++函数调用中的另一种调用方式:
地址调用。
2009-7-29 -29-
6.3.1 指针作为函数的参数用指针作为函数参数,实现地址调用,必须满足以下条件:
函数的形式参数是指针变量;
函数的实参数是内存的地址,具体来说可以是数组名、变量的地址、用变量地址初始化的指针;
形参指针类型和实参地址类型必须相同。
2009-7-29 -30-
6.3.1 指针作为函数的参数满足以上条件后,这样的函数调用在使用上有以下的特点:
实参传递给形参的是内存的地址,所以形参指针指向实参变量;
形参指针通过间接引用,直接访问实参变量,
包括改变实参变量的值;
函数调用后,可以保留对实参变量的操作结果,
如果有多个实参,就可以有多个实参变量在函数调用中得到修改。
2009-7-29 -31-
例 6.6 编写数据交换的函数 。 在 main中调用这个函数,
交换 main中定义的变量 。
#include<iostream>
usingnamespacestd;
voidSwap(int*a,int *b);
voidmain()
{ int x(5),y(10);
cout<<"主函数变量的值,x="<<x<<" y="<<y<<endl;
Swap(&x,&y);
cout<<"返回后变量的值,x="<<x<<" y="<<y<<endl;
}
voidSwap(int*a,int *b)
{ int t;
t=*a;
*a=*b;
*b=t;
cout<<"函数中完成了交换,*a="<<*a<<" *b="<<*b<<endl;
}
运行结果,
主函数变量的值,x=5 y=10
函数中完成了交换,*a=10 *b=5
返回后变量的值,x=10 y=5
变量的地址作为实参数指针变量作为形式参数
2009-7-29 -32-
6.3.1 指针作为函数的参数程序中用变量 x和 y的地址作实参,传递给指针
a和 b,如图 6.1(a)。 通过间接引用 *a和 *b进行交换,实际上就是 x和 y进行交换,如图 6.1(b)。
2009-7-29 -33-
例 6.7 指针变量指向一个数组 。 用指针变量作为实参调用一个函数 。 在函数中指针指向数组的第二个元素 。 观察函数返回后,实参指针值有无变化 。
#include<iostream>
usingnamespacestd;
voidMove(int *a);
voidmain()
{ int x[5]={10,20,30,40,50},*px=x;
cout<<"调用前的 *px="<<*px<<endl;
Move(px);
cout<<"调用后的 px";
if(px==x)cout<<"没有变化,*px还是 "<<*px<<endl;
elsecout<<"也向前移动,*px变为 "<<*px<<endl;
}
voidMove(int *a)
{ a=a+1;
cout<<"函数中完成了指针移动,*a="<<*a<<endl;
}
运行结果,
调用前的 *px=10
函数中完成了指针移动,*a=20
调用后的 px没有变化 *px还是 10
指针作为实参数指针变量作为形式参数
2009-7-29 -34-
6.3.2 指针的指针作为函数的参数如果一定要改变实参指针的值,可以用指针变量的地址作实参,形式参数就应该是指针的指针。
2009-7-29 -35-
例 6.8 修改例 6.7,使得实参指针返回后,指向新的地址 。
#include<iostream>
using namespace std;
void Move(int **a);
void main()
{ int x[5]={10,20,30,40,50},*px=x;
cout<<"调用前的 *px="<<*px<<endl;
Move(&px);
cout<<"调用后的 px";
if(px==x)cout<<"没有变化,*px还是 "<<*px<<endl;
else cout<<"也向前移动,*px变为 "<<*px<<endl;
}
void Move(int **a)
{ *a=*a+1;
cout<<"函数中完成了指针移动,**a="<<**a<<endl;
}
运行结果,
调用前的 *px=10
函数中完成了指针移动,**a=20
调用后的 px也向前移动,*px变为
20
指针的地址作为实参数指针的指针作为形式参数
2009-7-29 -36-
6.3.3 传递参数的保护,指针和常量通过数组名的地址调用,可以改变实参数组的内容。
但是,并不是所有以数组名作为实参的函数调用,都需要改变数组的值。例如,在调用一个求数组最大值的函数时,就不希望数组的值发生变化。希望在函数中能够限制对数组元素的修改。
使用常指针可以达到这个目的。
2009-7-29 -37-
6.3.3 传递参数的保护,指针和常量常指针是指向常量的指针( Pointer to
Constant data) 的习惯说法。就是规定指针所指向的内容不可以通过指针的间接引用来改变。
常指针说明的格式是:
const <类型名 > *<指针名 >;
例如,const int *ptint;
指针 ptint的类型是 (const int *),也就是指向一个恒定的整型数。
2009-7-29 -38-
例 6.10 常指针示例 。 观察以下程序的运行 。
#include<iostream>
usingnamespacestd;
voidmain()
{int ia=10,ib=20;
const int *ptint;
ptint=&ia; //用 ia地址初始化
cout<<*ptint<<endl;
ptint=&ib; //改变为 ib的地址
ib=ib+100; //ib本身仍然可以改变
cout<<*ptint<<endl;
//*ptint=100; //语句错误:左值是常量
}
运行结果,
10
120
常指针声明注释去掉会出现编译错误
2009-7-29 -39-
6.3.3 传递参数的保护,指针和常量指针常量( Pointer constant)。
指针本身的内容是个常量,不可以改变。
指针常量声明的格式是:
<类型名 > *const <指针名 >=<初值 >;
例如:
char ch,*const ptch=&ch;
数组名就是数组的首地址。现在可以说,数组名就是一个指针常量 。
2009-7-29 -40-
例 6.11 指针常量示例 。 指出以下程序的错误 。
#include<iostream>
usingnamespace std;
voidmain()
{ int a=10,b=100;
int *constpa=&a; //pa是指针常量
cout<<*pa<<endl;
*pa=20; //指针常量的间接引用是允许的
cout<<a<<endl;
pa=&b;
constint c=50;
int *constpc=&c;
}
错误语句注释掉后运行结果,
10
20
语句有错:常量不能当左值语句有错,地址类型不同
2009-7-29 -41-
例 6.12 用常指针作形参,函数 printString可以输出数组的内容,不可以对数组修改 。
#include<iostream>
usingnamespacestd;
void printString(constchar* );
void main()
{ charphrase[] = "C++ is a modern programminglanguage";
cout << "The string is:\n";
printString(phrase);
cout << endl;
} // main函数结束
void printString(constchar*Ptarray)
{ while(*Ptarray)
cout<< *Ptarray++;
}
不使用常指针也是可以完成打印。
但是没有保护了。
数组名作实参数常指针作形式参数
2009-7-29 -42-
6.3.4 指针函数如果一个函数的返回值是指针,则这样的函数称为指针函数。如:
int *func01(int k);
函数 func01返回一个指向整型数据的指针。
返回指针,实际就是返回一个内存的地址。
要注意,不能返回函数中局部变量的地址。 这样的地址处于内存的堆栈区,是所有函数公用的区域。
其中的数据是不稳定的,可能因为其他函数的执行而发生改变。
2009-7-29 -43-
例 6.13 返回不同地址的指针函数 。 观察运行的结果 。
#include<iostream>
usingnamespacestd
int va=100; //全局变量
int*get_int(int*pt,int j) //指针函数
{ int value=300,*pti=pt;
if(j==0||j==1) return pti;
else if(j==2)returnpti=&value;
}
void main()
{ int *ptint,vb=200;
for(inti=0;i<3;i++)
{if(i==0)ptint=get_int(&va,i);
else if(i==1)ptint=get_int(&vb,i);
else if(i==2)ptint=get_int(&va,i);
cout<<"i="<<i<<"*ptint="<<*ptint<<endl;
}
}
运行结果
i=0 *ptint=100
i=1 *ptint=200
i=2 *ptint=-858993460
返回全局变量或者 vb的地址返回函数局部变量的地址
2009-7-29 -44-
6.4 指针和字符串
2009-7-29 -45-
6.4.1 字符串处理的两种方式
C++字符串常量是用双引号括起的字符序列,
并以字符 ‘ \0’ 作为结束标志。如
"This is a string"。
字符串常量存放在内存的某个区域,有自己固定的首地址。
如果将字符串常量的首地址看成是指针,这种指针既是常指针,也是指针常量。
2009-7-29 -46-
6.4.1 字符串处理的两种方式
C++处理字符串有两种方式:数组方式和指针方式。
数组方式是将字符串存入字符数组后,再进行处理。一般可以在声明数组的时候用字符串来初始化:
char string_array[]="What’s a nice day!";
指针方式是用字符串常量来初始化一个字符指针:
char *string_pt="What’s a nice day!";
2009-7-29 -47-
6.4.1 字符串处理的两种方式常量不能放在等式左边运行时会出错
2009-7-29 -48-
6.4.2 字符串操作函数调用这些函数时,原则上可以使用字符数组名、已经初始化的字符指针作为实参数。字符串常量还可以作为源字符串的实参数。 目的串必须是可写的。
2009-7-29 -49-
例 6.14 strcpy和 strncpy的比较 。
#include<iostream>
#include<string>
usingnamespacestd;
voidmain()
{ int n;
char*array1= "HappyBirthdaytoYou";
chararray3[ 15];
chararray2[ 25];
strcpy(array2,array1);
cout <<"Thestringinarray1is," <<array1
<<"\nThestringinarray2is," <<array2<<'\n';
/*strcpy(array3,array1);
cout<<array3<<endl; */
n=sizeof(array3);
strncpy(array3,array1,n-1); //复制 array1的 n-1个字符到 array3
array3[14] = '\0'; //添加 '\0'到 array3
cout <<"Thestringinarray3is," <<array3<<endl;
}
不包括提示的运行结果
Happy Birthday to You
Happy Birthday to You
Happy Birthday
复制 array1到 array2,
没有问题复制 array1到
array3,空间不够,
有运行错误按实际数组大小,
复制 array1到
array3,没有问题
2009-7-29 -50-
6.5 通过指针访问数组
2009-7-29 -51-
6.5 通过指针访问数组指针和数组有天然的联系。因为数组名本身就是地址,也就是某种类型的指针。将指针和数组名联系起来,访问数组就多了一种方法。
虽然一维数组名和二维数组名都是地址,
都可以看作是某种指针,但是指针的类型是不同的。因此,通过指针访问一维数组和二维数组的方法是不同的。
2009-7-29 -52-
6.5.1 通过指针访问一维数组要通过指针访问一维数组,必须首先声明一个和数组类型相同的指针,并且用数组名来对指针初始化,如:
int A[10],*pa=A;
然后,就可以用多种方式访问数组元素:
数组名和下标,如 A[0],A[4];
指针和下标,如 pa[0],pa[4];
指针加偏移量的间接引用,如 *(pa+4);
数组名加偏移量的间接引用,如 *(A+4);
指针自加后的间接引用,如 *pa++。
2009-7-29 -53-
例 6.15 求数组内所存放的字符串的长度 。
#include<iostream>
usingnamespacestd;
voidmain()
{ charChArray[]="Thisis a string.",*ptch;
int i,j,k,offset1,offset2;
ptch=ChArray; //指针初始化
for(i=0;ChArray[i]!='\0';i++);
cout<<"Thelengthofthestringis:"<<i<<endl;
for(j=0;ptch[j]!='\0';j++);
cout<<"Thelengthofthestringis:"<<j<<endl;
for(offset1=0;*(ChArray+offset1)!='\0';offset1++);
cout<<"Thelengthofthestringis:"<<offset1<<endl;
for(offset2=0;*(ptch+offset2)!='\0';offset2++);
cout<<"Thelengthofthestringis:"<<offset2<<endl;
for(k=0;*ptch++!='\0';k++);
cout<<"Thelengthofthestringis:"<<k<<endl;
}
运行结果都相同方式 1,数组名和下标 方式 2:指针和下标方式 3,数组名加偏移量的间接引用方式 4:指针加偏移量的间接引用方式 5:指针自加的间接引用
2009-7-29 -54-
例 6.16 求整型数组的平均值,显示数组元素和平均值 。
#include<iostream>
usingnamespacestd;
voidmain()
{ int intArray[10]={8,11,23,34,45,56,65,78,86,97},*ptint;
int i,num,sum;
float average;
ptint=intArray;
sum=0;
num=sizeof(intArray)/sizeof(*intArray);
for(i=0;i<num;i++)
sum=sum+*ptint++;
average=(float)sum/num;
ptint=intArray;
cout<<"数组元素是,\n";
for(i=0;i<num;i++)
cout<<*ptint++<<" ";
cout<<"\n平均值是,"<<average<<endl;
}
数组元素是:
8 1123 344556657886 97
平均值是,50.3
指针初始化求数组元素的数目求平均值指针再次初始化输出数组元素和它们的平均值
2009-7-29 -55-
6.5.2 通过指针访问二维数组二维数组可以看成是一维数组的一维数组。二维数组名虽然也是地址(指针),但是却和一维数组名有不同的类型。
对一维数组 A[5],数组名 A的地址,就是数组第一个元素 A[0]的地址。 指针的类型是指向数组元素的指针 。 A+1就是元素 A[1]的地址。
2009-7-29 -56-
6.5.2 通过指针访问二维数组对二维数组 B[3][4],数组名 B的地址,则是其中第一个一维数组 B[0]的地址。指针的类型是指向一维数组的指针。 B+1就是下一个一维数组 B[1]的地址。如图 6.3所示。
2009-7-29 -57-
6.5.2 通过指针访问二维数组在定义指向一维数组的指针时,还必须指出一维数组的大小。
声明指向一维数组的指针的格式如下:
<类型名 > (*指针变量名 )[一维数组大小 ];
例如,和图 6.3中两个二维数组所对应的指向一维数组的指针定义如下:
char (*ptchb)[4],(*ptchc)[2];
ptchb=B; ptchc=C;
2009-7-29 -58-
6.5.2 通过指针访问二维数组对于指向一维数组的指针,具有以下的特征:
二维数组名是指向一维数组的指针,而不是指向数组元素的指针。
指向一维数组指针加 1 的结果,是指向下一个一维数组的指针。
指向一维数组的指针的间接引用的结果仍然是地址,
即 *ptchb仍然是地址。只是地址的类型变了。变为一维数组 B[0]第一个元素 B[0][0]的地址。
因为 *ptchb是数组元素的地址,**ptchb就是数组元素的值。 用指向一维数组指针访问二维数组第 i行第 j列元素的一般公式是 *(*(指针名 +i)+j)。
2009-7-29 -59-
例 6.17 比较指向一维数组的指针和指向数组元素的指针 。
#include<iostream>
usingnamespacestd;
voidmain()
{ short B[3][4],C[3][2];
short(*ptshb)[4],(*ptshc)[2];
ptshb=B; ptshc=C;
cout<<"比较不同的指向一维数组指针的差别 \n";
cout<<"ptshb的地址是,"<<ptshb<<"\n";
cout<<"ptchb+1的地址是,"<<ptshb+1<<"\n";
cout<<"ptchc的地址是,"<<ptshc<<"\n";
cout<<"ptchc+1的地址是,"<<ptshc+1<<"\n";
比较不同的指向一维数组指针的差别
ptshb的地址是,0x0012FF68
ptchb+1的地址是,0x0012FF70
ptchc的地址是,0x0012FF5C
ptchc+1的地址是,0x0012FF60
B的第 0行地址
B的第 1行地址
C的第 0行地址
C的第 1行地址
2009-7-29 -60-
例 6.17 比较指向一维数组的指针和指向数组元素的指针 。
cout<<"不同类型的指针 \n";
cout<<"ptshb的地址是,"<<ptshb<<endl;
cout<<"*ptshb的地址是,"<<*ptshb<<endl;
cout<<"*ptshb+1的地址是,"<<*ptshb+1<<endl;
cout<<"B[0][1]的地址是,"<<&B[0][1]<<endl;
//cout<<"ptchb和 *ptchb相等吗? "<<(ptchb==*ptchb)<<endl; // 有语法错误
cout<<"*ptshb+1和 &B[0][1]相等吗? ";
if(*ptshb+1==&B[0][1]) cout<<"Yes"<<endl;
}
不同类型的指针
ptshb的地址是,0x0012FF68
*ptshb的地址是,0x0012FF68
*ptshb+1的地址是,0x0012FF6A
B[0][1]的地址是,0x0012FF6A
*ptshb+1和 &B[0][1]相等吗? Yes
B的第 0行地址
B的第 0行第 0列元素的地址
B的第 0行第 1列元素的地址
B的第 0行第 1列元素的地址
2009-7-29 -61-
例 6.18 用单循环程序,求二维数组元素的平均值 。
#include<iostream>
usingnamespacestd;
voidmain()
{ int dArray[3][4]={32,42,12,25,56,76,46,53,76,89,96,82},(*pt)[4];
int sum,j;
float average;
sum=0;
pt=dArray;
j=sizeof(dArray)/ sizeof (**dArray);
for(inti=0;i<j;i++)
sum=sum+*(*pt+i);
average=(float)sum/j;
cout<<"数据的平均值等于,"<<average<<endl;
} 运行结果:
数据的平均值等于 57.0833
指向一维数组指针的初始化 求数组元素的数目,**dArray就是元素
dArray[0][0]
数组求和求平均值输出平均值
2009-7-29 -62-
6.5 程序开发过程
2009-7-29 -63-
6.5.3 指针数组若数组元素是某种类型的指针,这样的数组称为指针数组。
指针数组声明的格式如下:
<类型 > *<数组名 >[常量表达式 ];
例如:
char *member_name[10];
注意和声明指向一维数组指针的差别:
char (*member_pointer)[10];
2009-7-29 -64-
6.5.3 指针数组指向字符的指针:
char *member_name[]={"Merry“,"John","Hill"};
指向这样的指针数组的指针:
char **arr=member_name;
或者:
char **arr; arr=member_name;
这样定义后,*arr就是指向字符串的指针,以下语句
cout<<*arr<<endl;
将显示数组中的第一个字符串 "Merry"。
2009-7-29 -65-
例 6.19 将若干字符串存入指针数组,并以 NULL表示结束 。
将这些字符串按升序排序,输出排序后的字符串 。
#include<string>
#include<iostream>
usingnamespacestd;
voidPrint(char*[]);
voidString_sort(char*[]);
voidmain()
{ char*pn[]={"George","Bill","Wendy","Abraham","Bruce",NULL};
//指针数组
String_sort(pn);
Print(pn);
}
函数原型函数原型指针数组求平均值输出平均值
2009-7-29 -66-
voidString_sort(char*arr[])
{ char**p1,**p2,*p3;
p1=arr;
p2=arr+1;
while(*(p1+1)!=NULL)
{ while(*p2!=NULL)
{if(strcmp(*p1,*p2)>0)
{p3=*p1;
*p1=*p2;
*p2=p3;}
p2++;}
p1++;
p2=p1+1; }
}
voidPrint(char*arr[])
{ cout<<"Sortedstringsare:"<<endl;
while(*arr!=NULL)
{cout<<*arr<<endl;
arr++; }
}
Sortedstrings are:
Abraham
Bill
Bruce
George
Wendy
逐次取出每个指针 和其余的每个指针所指的字符串比较如果字符串 *p1大于 *p2
交换地址重新设置 p2
2009-7-29 -67-
6.5.4 命令行参数命令行参数是 main函数的参数。带有命令行参数的 main函数的原型是:
<类型 > main(int argc,char *argv[]);
argc,整数,存放命令行参数的数目。
argv[],指针数组,存放所输入的命令行参数。其中 argv[0]是所执行的程序名,
argv[argc-1]是最后一个输入的参数字符串,
argv[argc]中自动的存入 NULL,表示输入结束。
2009-7-29 -68-
例 6.20显示命令行参数的程序 。
#include<iostream>
usingnamespace std;
voidmain( intargc,char *argv[])
{ cout <<"Thecommand lineargumentsare:\n";
for ( int i = 0; i < argc;++i)
cout<<argv[i ] <<' ';
}
从第 0个到第 argc-
1个参数显示参数
2009-7-29 -69-
例 6.21对命令行输入的字符串进行排序并输出排序结果 。
#include<string>
#include<conio.h>
#include<iostream>
usingnamespace std;
voidPrint(char*[]);
voidString_sort(char*[]);
voidmain(intargc,char *argv[])
{ String_sort(argv+1);
Print(argv+1);
getche();
}
如在命令行输入:
E:\myfile\proj01\debug\proj01.exe bcd
abcaabbbd
回车后运行并显示:
Sortedstrings are:
aab
abc
bbd
bcd
从第 1个参数开始排序
2009-7-29 -70-
6.6 指针访问动态内存
2009-7-29 -71-
6.6 指针访问动态内存动态内存是在程序执行时才可以申请,使用和释放的内存 。 也就是存放动态数据的内存区域 。
存放动态数据的区域称为? 堆?,动态内存也称为堆内存 。
动态内存不能通过变量名来使用,而只能通过指针来使用 。
2009-7-29 -72-
6.6.1 动态内存的申请和释放
C++中通过运算符 new申请动态内存,运算符
delete释放动态内存 。
动态内存申请运算符 new的使用格式:
new <类型名 > (初值 )
运算的结果:如果申请成功,返回指定类型内存的地址;如果申请失败,返回 NULL指针 。
动态内存使用完毕后,要用 delete运算来释放 。
delete运算符使用格式:
delete <指针名 >;
2009-7-29 -73-
6.6.2 动态数组空间的申请和释放申请动态一维数组时,要在 new表达式中加上申请数组的大小:
new <类型名 >[常量表达式 ] ;
注意:在动态申请数组空间时,不可以对数组进行初始化 。
也可以申请二维数组的空间:
int (*pi_marray)[4];
pi_marray = new int[3][4];
释放动态数组空间都用相同的表达式:
delete []<指针名 >;
2009-7-29 -74-
6.6.3 内存泄漏和指针悬挂内存泄漏是指动态申请的内存空间,没有正常释放,但是也不能继续使用的情况 。 如:
char *ch1;
ch1 = new char('A');
char *ch2 = new char;
ch1=ch2;
原来为 ch1所申请的存放字符 A的空间就不可能再使用了,产生了内存泄漏 。
2009-7-29 -75-
6.6.3 内存泄漏和指针悬挂让指针指向一个已经释放的空间,即所谓的指针悬挂 (Dangling)。 如:
char *ch1,*ch2;
ch1 = new char;
ch2 = ch1;
*ch2 = 'B';
delete ch1;
指针 ch2就是指向了一个已经释放的地址空间,形成指针悬挂 。 如果还要用 delete ch2;语句来释放 ch2
所指向的空间,就会出现运行错误 。
2009-7-29 -76-
6.7 引用概念
2009-7-29 -77-
6.7 引用概念引用 ( Reference) 是 C++中新引入的概念,也是 C
语言中不存在的数据类型 。
引用是变量或者其他编程实体 ( 如对象 ) 的别名 。
因此,引用是不可以单独定义的 。 如图 6.4(a)所示,
变量 A在内存中有自己的地址,而 A的引用 B实际上就是变量 A,只是 A的另外一个名字 。
2009-7-29 -78-
6.7.1 引用的声明和使用引用是通过运算符 &来定义的,定义的格式如下:
<类型名 > &引用名 = 变量名 ;
其中的变量名必须是已经定义的,并且和引用的类型必须相同 。 例如:
int someInt;
int &refInt = someInt;
必须注意:引用必须在声明的时候就完成初始化,不可以先声明引用,然后再用另一个语句对它初始化 。
2009-7-29 -79-
6.7.1 引用的声明和使用引用有以下的特点:
引用不能独立存在,它只是其他变量的别名;
引用必须在声明的同时就初始化;
引用一旦定义,引用关系就不可以更改,即 B若是 A的引用,就不可能是其他变量的引用;
引用的类型就是相关的变量的类型,引用的使用和变量的使用相同 。
2009-7-29 -80-
例 6.22引用的使用 。 观察以下程序的结果 。
#include<iostream>
usingnamespace std;
voidmain()
{ int intA=10;
int& refA=intA;
cout<<"引用的值和相关变量值相同,refA="<<refA<<endl;
refA=5;
cout<<" 引 用 的 变 化,则 相 关 变 量 也 变 化,
intA="<<intA<<endl;
cout<<"引用的地址和相关变量地址相同,intA的地址=
"<<&intA<<endl;
cout<<"引用的地址和相关变量地址相同,refA的地址=
"<<&refA<<endl;
}
引用的值和相关变量值相同,refA=10
引用的变化,则相关变量也变化,intA=5
引用的地址和相关变量地址相同,intA的地址= 0x0012FF7C
引用的地址和相关变量地址相同,refA的地址= 0x0012FF7C
2009-7-29 -81-
6.7.1 引用的声明和使用如果不希望通过引用来改变相关的变量的值,
则可以定义常引用 。 常引用定义的格式:
const <类型名 > &引用名 =变量名 ;
例如:
int someInt = 10;
const int &const_refA = someInt;
此时,const_refA就是常引用 。 不可以通过
const_refA来改变 someInt变量的值 。
2009-7-29 -82-
例 6.23 指针的引用 。
#include<iostream>
usingnamespace std;
voidmain()
{ int intA=10,intB=20;
int*pti=&intA;
int*&refi=pti;
cout<<" 指 针 的 引 用 可 以 访 问 指 针 所 指 的 变量,*refi="<<*refi<<endl;
cout<<"指针变量原来的值,pti="<<pti<<endl;
refi=&intB;
cout<<" 引 用 的 变 化,则 相 关 指 针 也 变 化,
pti="<<pti<<endl;
cout<<" 指 针 所 指 的 变 量 值 也 发 生 变 化,* pti=
"<<*pti<<endl;
}
指 针 的 引 用可 以 访问 指 针所 指 的 变量,*refi=10
指针变量原来的值,pti=0x0012FF7C
引用的变化,则 相关 指 针也 变 化,
pti=0x0012FF78
指针所指的变量值也发生变化,*pti= 20
2009-7-29 -83-
6.7.2 通过 引用传递函数参数引用使用最多的场合是作为函数的形式参数 。
引用作为函数的形式参数具有以下的特点:
引用作为形式参数时,实参数是相同类型的变量;
引用作为形式参数,参数传递属于地址传递;
引用作为形式参数时,在函数中并不产生实参数的副本,形式参数的引用和实参数的变量实际上是同一个实体;
函数对引用的操作,也是对实参变量的操作,函数调用可以改变实参数的值 。
2009-7-29 -84-
例 6.24 用引用作为形式参数,通过函数调用,交换两个实参数 。
#include<iostream>
usingnamespace std;
voidswap_1(int&x,int&y) //引用作为形式参数
{ int j;
j=x;
x=y;
y=j;
}
voidmain()
{ int a=12345,b=54321;
cout<<" 函数调用前,a=" <<a<<" b="<<b<<endl;
swap_1(a,b); //变量作为实参数
cout<< " 函数调用后,a=" <<a<< " b="<<b<<endl;
}
函数调用前,a=12345b=54321
函数调用后,a=54321b=12345
2009-7-29 -85-
6.7.2 通过 引用传递函数参数使用引用作为形式参数还需要注意:
如果实参数需要保护,可以使用? 常引用? 作为形式参数;
用引用作形参和用变量作形参是有区别的,但是,
对于这两种情况,实参数可能相同 。 例如,函数
swap(int a,int b)和 swap(int &a,int&b)看起来是两个可以区分的重载函数 。 但是,都可以用整型变量 x和 y来调用,swap(x,y),因此,实际上是不可区分的,函数 swap(int a,int b)和 swap(int
&a,int&b)不是可以区分的重载函数 。
2009-7-29 -86-
6.7.3 用 引用作为函数的返回值返回引用有以下需要注意的地方:
返回引用需要在函数的返回值类型中加以说明,形式为:
<类型名 > &函数名 (形式参数表 )
返回引用的返回语句就是,return 变量名 ;
返回引用实际是返回地址。在使用上,或者直接使用这个地址;或者使用这个地址单元的数据。
返回的引用可以作为左值继续操作,而返回的变量值是不可以继续运算的。这是返回引用和返回变量值在使用上的主要区别。
2009-7-29 -87-
例 6.25 引用作为函数返回值 。
#include<iostream>
usingnamespacestd;
int &fun(int &pf);
voidmain()
{int pa=10,pb;
cout<<"输出 1,pb="<<(pb=fun(pa))<<endl;
int &pc=fun(pa);
cout<<"输出 2,pc="<<pc<<endl;
cout<<"输出 3,"<<++fun(pa)<<endl;
}
int &fun(int &pf)
{pf=pf+10;
cout<<pf<<endl;
returnpf;
}
返回引用的函数原型使用返回引用的变量值使用返回引用的地址返回的引用继续加
1
返回引用的函数程序执行后显示:
20
输出 1,pb=20
30
输出 2,pc=30
40
输出 3,41
2009-7-29 -88-
总结指针变量的特点是可变性,即一个指针变量内的地址是可变的 。 所以,通过一个指针变量,就可以访问一个数组 。 而引用的特点是不变性,一个变量的引用就只能和这个变量联系在一起 。 彼此随着对方的变化而变化 。 本章还介绍了函数调用的另一种方式:地址调用 。 具体又分为指针调用和引用调用 。 动态内存的使用也是本章的重点之一 。