第 6章 指针与字符串第 6章 指针与字符串
6.1 指针的概念
6.2 指针型变量
6.3 指针与数组
6.4 指针与函数
6.5 指针与类、对象
6.7 动态内存分配与 new和 delete运算符
6.8 string类第 6章 指针与字符串
6.1 指针的概念指针既是 C++语言学习中的难点又是重点,要掌握
C++语言的精髓,必须学会熟练使用指针 。 本节先引入指针的概念 。
为了说明什么是指针,必须弄清楚数据在内存中是如何存储的,又是如何读取的。如果在程序中定义了一个变量,在编译时就给这个变量分配内存单元。
第 6章 指针与字符串系统根据程序中定义的变量类型,分配一定长度的空间 。 例如,许多计算机系统对整型变量分配两个字节,
对实型变量分配四个字节,对字符型变量分配一个字节,……。 内存区的每一个字节 ( 即单元 ) 有一个编号,
这就是,地址,,它相当于旅馆中的房间号 。 在地址所标识的单元中存放数据,这相当于旅馆中各个房间中住旅客一样 。
第 6章 指针与字符串在此,务必弄清楚一个内存单元的地址与内存单元的内容这两个概念的区别。如图 6-1所示,假设程序已定义了三个整型变量 i,j,k,编译时系统分配 2000和
2001两个字节给变量 i,2002,2003字节给 j,2004、
2005给 k。在内存中已没有 i,j,k这些变量名了,对变量值的存取都是通过地址进行的。
第 6章 指针与字符串图 6-1 内存单元分配示意第 6章 指针与字符串还可以采用另一种称之为“间接访问”的方式。这类似于在程序中定义整型、实型、字符等变量,也可以定义这样一种特殊的变量,它是用来存放地址的,即可以将变量 i的地址存放在另一个内存单元中。假设我们定义了变量 i_pointer是存放整型变量的地址的,它被分配为 3030,3031字节单元。可以通过下面的语句将 i
的地址存放到 i_pointer中:
i_pointer=&i;
这时,i_pointer的值就是 2000,即变量 i所占用单元的起始地址。
第 6章 指针与字符串要存取变量 i的值,也可以采用间接方式:先找到存放,i的地址,的单元地址 (3030,3031),从中获得 i
的起始地址 ( 2000),然后从 2000,2001字节中取出 i
的值 (3)。
简而言之,指针就是指向内存中某个单元的地址值 。 所有的变量,数组,对象都是保存在内存中的 。
凡是保存在内存中的信息,必须知道它在内存中的位置 。 在计算机中,引用了,地址,这个词来代表内存的某个位置 。 内存以字节为单位编址,称为内存地址 。
第 6章 指针与字符串
6.2 指针型变量
6.2.1指针型变量的声明在 C++语言中,任何一种数据类型都有对应的指针类型,甚至指针类型本身也有指针类型 。 要声明一种数据类型的指针型变量,必须在声明变量时将,*” 号加在数据类型的后面 。 定义指针变量的一般形式如下,
<类型标识符 >*指针变量名第 6章 指针与字符串定义中的,*” 表示所定义的变量是一个指针变量,
以示与其它变量的区别 。 指针变量名的命名应符合标识符的命名规则 。 指针变量的值是另一变量的指针 (即首地址 )。 定义中的数据类型是指针变量所指变量的数据类型 。
注意,一个指针变量只能指向定义所指定数据类型的变量,即存放定义所指定数据类型的变量的地址,
因此,指针变量所存放的地址是一个具有指定数据类型的地址。例如,
第 6章 指针与字符串
int*pnNumber; //pnNumber是用来保存一个指向 int型变量的指针的指针型变量
f1oad*pfRealNumber; //pfRealNumber是用来保存一个指向
float型变量的指针的指针型变量
BOOL*pbResult; //pbResult是保存一个指向 int型变量的指针的指针型变量,注意 BOOL型
//与 int型的共用特性第 6章 指针与字符串
char*ppBuffer[5]; //数组 ppBuffer是用来保存 5个指向
char型变量的指针的指针型变量数组
void*pAddress; //pAddress是一个保存内存地址的指针型变量,该内存地址中保存的数据
//类型无须知道
void*型指针是很特殊的一种指针,它不像 int*、
float*等指针那样,指向的内存单元中保存的是具体的一种数据类型的变量,void*只是一个地址,这个地址中保存的可能是 int型变量的起始地址,也可能是 f1oat
型变量的起始地址,也可能什么都不是 。
第 6章 指针与字符串声明指针型变量时,,*” 前后可以加空格,也可以不加,这完全取决于程序员的习惯 。
尽管大多数指针变量类型同其对应的普通数据类型之间表面上只差一个,*” 号,但它们之间的关系相当特殊 。 必须牢记这样的事实:指针型变量也是变量,
指针型变量保存的是地址值,这个值实际上就是一个有特殊意义的整数,称为指针 。
C++语言中定义了一个符号常数 NULL,用来代表空指针值。所谓空指针值是人为规定的一个数值,用来表示“无效”的指针值。在 VisualC++中,NULL被定义为 0。
第 6章 指针与字符串
6.2.2 指针的基本操作与指针有关的基本运算符有以下两个 。
(1)&变量名 。,&”为取地址运算符,用来获取变量的首地址 。
(2)*指针变量名 。,*” 为指向运算符,用来获取指针变量所指向变量的值 。
,&"和,*” 运算符都是单目运算符,其优先级高于所有双目运算符,采用从右到左的结合性 。
第 6章 指针与字符串例如:
inti=5,j,*phint; //定义整型变量 i,j;同时定义整型指针变量 phint
phint=&i://将 i的指针 (即首地址 )赋给 phint,使 phnti指向 i
j=*phint; //将 phint所指变量 i的值 (5)赋给变量 j
第 6章 指针与字符串说明,
① 指针变量是有类型的 。 要使指针变量 phint存放整型变量 i的地址,phint也必须定义为整型指针变量,
否则编译时将出错 。
② 要注意第 1句和第 3句中 *phint的区别:第 1句是指针变量定义语句,其中的,*” 表示 phint是一个指针类型变量;第 3句中的,*” 表示取出指针变量 phint的值 ( 即 phint所指内存单元中的内容 ) 。
第 6章 指针与字符串
6.2.3 指针变量的初始化与引用与其它变量一样,指针变量也可以在定义时对其赋初值 ( 即指针变量的初始化 ) 。 这里要注意的是,
所赋的初值必须是所定义数据类型的变量地址 。 对于上面的程序片段,我们也可以用以下两句来实现,
inti=5,j,*phint=&i;//用整型变量 i的首地址初始化整型指针变量 phint
j=*phint;
第 6章 指针与字符串在此还是要强调,指针变量定义语句中的,*” 只是一个标志,并没有间接存取内容的含义,不能把
int*phint=&i;理解为将 i的地址赋给 phint所指的内存单元,而应理解为将 i的地址赋给 phint本身。
除 void*型指针变量以外,其它指针型变量可以通过在变量名前加,*”号的方法访问该变量所指向的内存地址中所保存的相应数据类型变量的值。
第 6章 指针与字符串
【 例 6-1】 指针变量的使用例题 。
#include<iostream.h>
voidmain()
{
intm,n,*p1=&m,*p2=&n,*phint=NULL;
m=n=6;
cout<<"*p1="<<*p1<<",*p2="<<*p2<<endl;
cout<<"p1="<<p1<<",p2="<<p2<<endl;
cout<<"m="<<m<<",n="<<n<<endl;
cout<<"phint="<<phint<<endl;
第 6章 指针与字符串
*p1+=3;
p2=p1;
*p2*=4;
phint=p2;
cout<<"*p1="<<*p1<<",*p2="<<*p2<<endl;
cout<<"p1="<<p1<<",p2="<<p2<<endl;
cout<<"m="<<m<<",n="<<n<<endl;
cout<<"phint="<<phint<<endl;
}
第 6章 指针与字符串程序运行结果为
*p1=6,*p2=6
p1=0x0012FF7C,p2=0x0012FF78
m=6,n=6
pjint=0x00000000
*p1=36,*p2=36
p1=0x0012FF7C,p2=0x0012FF7C
m=36,n=6
pjint=0x0012FF7C
第 6章 指针与字符串说明,
① *p1,*p2表示指针变量 p1,p2所指变量的值,p1
表示 pl中存放的地址,p2表示 p2中存放的地址,具体的地址值与程序的运行环境有关 。
② *pl+=3;表示将 p1所指内存单元的内容 (即 m的值 6)
加 3后,再存入 p1所指内存单元 。
③ p2=p1;表示将 p1的内容 (即 m的地址 )赋给 p2,使
p2也指向 m。
第 6章 指针与字符串
④ *p2*=4;表示将 p2所指内存单元的内容乘以 4后存入 p2所指内存单元 。 其中最左边的,*” 代表指向操作
(即代表 p2所指的变量 ),中间的,*=”为复合赋值运算符 ( 其中的,*” 代表乘法 ) 。
⑤ 如果没有给指针变量 phint赋初值 NULL,编译时将会出现一警告错误 。
⑥程序的第 6行执行完毕后的情况见图 6-2(a),第
14行执行完毕后的情况见图 6-2(b)。
第 6章 指针与字符串第 6章 指针与字符串
6.2.4指针的运算对指针也可以进行赋值,算术和关系运算 。 同普通的表达式不同,指针的运算有它特有的规则,其中有些运算有特殊的意义,必须同普通的表达式分清 。
1.指针的赋值运算前面已经接触了对指针型变量赋值的简单情况,
这里将详细讨论指针赋值的问题 。
对指针型变量进行赋值必须遵循变量赋值的规则,
也就是说,只有与变量类型相同的值才能安全地赋予变量。这种规则在指针赋值时更为严格。
第 6章 指针与字符串对于普通变量的赋值运算,即使变量类型与值的类型不同,赋值运算也是允许的,只不过这种赋值会遵循某些转换规则进行 。 而指针型变量在赋值时,决不允许将与指针型变量类型不同的指针值赋予指针变量 。 例如,
intnValue;
charbyCh='A';
nValue=byCh;
第 6章 指针与字符串是允许的,尽管 nValue和 byCh是两种不同的数据类型 。 但是
int*pnValue;
charbyCh='A';
pnVaIue=&byCb;
是不允许的,因为 &byCh的类型是 char*,而
pnValue的类型是 int*。
在 C++语言中不允许将与指针型变量类型不同的指针值赋予指针变量的原因在于,不同类型的普通变量的存储宽度是不一样的。
第 6章 指针与字符串在某些特殊情况下,如果需要用同数据类型不兼容的指针指向该数据类型的变量,可以使用强制类型转换 。 例如:
int*pnVa1ue;
charbyCh='A';
pnValue=(char*)&byCh;
将指向一个 char型变量的指针强制转换为指向 int
型变量的指针后,就可以将值赋予 int*型指针变量
pnValue了 。
第 6章 指针与字符串特殊的 void*型指针由于没有任何一种数据类型同它对应,要将其它的任何一种指针值赋予 void*型指针变量都必须通过强制类型转换 。 例如,
void*nPointer;
int*pnValue;
nPointer=(void*)pnValue;
第 6章 指针与字符串由于指针型变量中保存的是一个代表内存地址的整型量,所以,通过强制类型转换,可以将指针型变量的值赋予一个整型变量,也可以将一个整型变量的值赋予一个指针型变量 。 例如:
intnValue;
void*nPointer;
nValue=(int)nPointer;
//将 void*型值强制转换成 int型
nPointer=(void*)nValue;
//将 int型值强制转换成 void*型第 6章 指针与字符串同理,通过强制类型转换,也可以将一个常量赋予一个指针型变量,
int*pPointer=(int*)0x98980;
但是,以下两种做法是错误的,
int*pPointer=&100;
int*pPointer=&(100*20+nValue);
第 6章 指针与字符串
2,指针的算术运算对于非 void*型的指针型变量,只能进行加一个整数,减一个整数和两个指针变量相减三种运算 。 void*
型指针变量不能做任何算术运算 。
将一个非 void*型的指针型变量做加上或减去一个整数的运算,实际上就是对地址进行加法或减法运算。
这种加法和减法运算按照某种单位进行。这种单位就是该指针所指向的变量的数据类型所占用的字节数。
第 6章 指针与字符串例如,对一个 char*型指针变量加 1,则结果就等于这个指针变量实际保存的整数值 (它代表地址 )加上 1;
而对一个 int*型指针变量加 1,其结果就等于这个指针变量实际保存的整数值 (它代表地址 )加上 2,因为一个
int型变量要占用两个字节 。
对两个指针型变量相减只能用在相同类型的指针型变量间,并且这两个指针变量不是 void*型。对两个指针型变量相减的结果将等于两个地址间的相对距离,
这是一个整型值。这个相对距离也是以该指针所指向的变量的数据类型所占用的字节数为单位的。
第 6章 指针与字符串两个 int*型指针相减的结果同两个 char*型指针相减的结果是不同的 。 例如,
intnValue;
int*pPointer1=&nValue,*pPointer2;
pPointer2=pPointer1+1;
假设 pPointer1的初始地址是 2000,则进行
pPointer2=pPointer1+1;运算后,pPointer2的地址值是
2002,如图 6-3所示。
第 6章 指针与字符串第 6章 指针与字符串对于指针的自加 ( ++) 与自减 ( --) 运算,显然很好理解,即
pPointer2=pPointer1+1;
相当于:
pPointer2=pPointer1++;
第 6章 指针与字符串
3,指针的关系运算指针间也可以进行关系运算 。 关系运算符 >,<,=、
>=,<=,!=可以用来连接两个指针型变量做关系运算 。
指针间的关系运算结果就是两个指针所指的地址值的大小的关系运算结果 。 由于内存地址是线性编码的,
所以,如果一个变量 m的保存位置在变量 n之前,则
&m<&n就等于 1,&m>&n就等于 0。 两个进行关系运算的指针一般要求是同一类型的指针 。
第 6章 指针与字符串
4,常指针 (const修饰符与指针 )
与 #define语句相比较,C++提供了一种更灵活,更安全的方式来定义常量,即使用 const修饰符来定义常量 。 例如:
constintLIMIT=100;
它类似于 #defineLIMIT100;语句,但此时这个常量是类型化的,它有自身的地址 。
const也可以与指针一起使用 。 它们的组合情况较复杂,可简单归纳为三种:指向常量的指针,常指针和指向常量的常指针 。
第 6章 指针与字符串
(1)指向常量的指针 。 指向常量的指针是指一个指向常量的指针变量 。 例如:
constchar*name="chen"; //声明指向字符常量的指针变量这个语句的含义是:声明一个名为 name的指针变量,它指向一个字符型常量,初始化 name为指向字符串 "chen"。
由于使用了 const,不容许改变指针所指的常量,
因此以下语句是错误的:
name[3]='a'; //即 name所指的数据不可修改但是,由于 name是一个指向常量的普通指针变量,
不是常指针,因此可以改变 name的值。
第 6章 指针与字符串
(2)常指针 。 常指针是指把指针本身,而不是它指向的对象声明为常量 。 例如:
char*constname="chen"; //常指针这个语句的含义是:声明一个名为 name的指针,该指针是指向字符型数据的常指针,用 "chen"的地址初始化该常指针 。 创建一个常指针,就是创建一个不能移动的固定指针,但是它所指的数据可以改变 。 例如,
name[3]='a' //合法
name="zhang"; //出错第一个语句改变了常指针所指的数据,这是允许的;
但第二个语句要改变指针本身,这是不允许的 。
第 6章 指针与字符串
(3)指向常量的常指针 。 指向常量的常指针是指这个指针本身不能改变,它所指向的数据值也不能改变 。 要声明一个指向常量的常指针,二者都要用 const修饰 。 例如,
constchar*constname="chen"; //指向常量的常指针这个语句的含义是:声明了一个名为 name的指针,
它是一个指向字符型常量的常指针,用 "chen"的地址初始化该指针 。 不难理解以下两个语句都是错误的:
name[3]='a'; //出错,不能改变指针所指的值
name="zhang"; //出错,不能改变指针本身第 6章 指针与字符串
5,引用型变量引用型变量是从指针型变量发展来的一种特殊类型的变量 。 引用变量的实质仍然是指针,但采用了不同于指针的表示方法,可以简化对函数有多个返回值时的处理,使得程序更加清晰可读 。
引用型变量是 C++的新增特性,是一种不具备自己的实际数据存储空间的变量,它必须与另一个同类型的变量共用一个空间 。 换句话说,它只是另一个目标对象的别名,其本身不是一个独立的变量 。 因此,引用只有声明,没有定义 。
第 6章 指针与字符串
【 例 6-2】 引用的声明和使用例题 。
#include<iostream.h>
voidmain()
{
inti,j=4,&ri=i; //定义 ri为整型变量 i的引用
i=3;
cout<<"i="<<i<<",ri="<<ri<<endl;
cout<<"i的地址是 "<<&i<<",ri的地址是 "<<&ri<<endl;
第 6章 指针与字符串
ri+=5;
cout<<"i="<<i<<",ri="<<ri<<endl;
cout<<"i的地址是 "<<&i<<",ri的地址是 "<<&ri<<endl;
ri=j;
cout<<"i="<<i<<",ri="<<ri<<",j="<<j<<endl;
cout<<"i的地址是 "<<&i<<",ri的地址是 "<<&ri<<",j的地址是 "<<&j<<endl;
ri=&j; //出错
}
第 6章 指针与字符串删除最后一句出错的语句后,程序的运行结果为
i=3,ri=3
i的地址是 0x0012FF7C,ri的地址是 0x0012FF7C
i=8,ri=8
i的地址是 0x0012FF7C,ri的地址是 0x0012FF7C
i=4,ri=4,j=4
i的地址是 0x0012FF7C,ri的地址是 0x0012FF7C,j的地址是 0x0012DD78
第 6章 指针与字符串这个例子清楚地表明:引用 ri与它的目标变量 i具有相同的内存地址,它们的值是同步变化的,即使引用 ri
被重新赋予变量 j的值,ri的值发生改变 (i的值也随之改变 ),但 ri仍以 i作为自己的目标,仍以 i的地址作为自己的地址,这一点不会改变。
第 6章 指针与字符串
6.3 指针与数组
6.3.1 指针与数组的关系数组的实质是内存中一块连续的空间,存储着同一类型变量的数据。要访问数组中的某个元素,可以通过“数组名 [下标 ]”这样的语法形式来实现。其中,
下标是要被访问的元素在数组中的位置。由此,不难得出这样的结论:如果知道数组第 1个 (下标为 0)元素的地址,将这个地址保存在某个指针变量中,那么对数组元素的访问就完全可以转换为指针操作。
第 6章 指针与字符串例如,已知指针型变量 int*pnPointer中保存了数组
intpnArray[10]的第 1个元素的地址,则通过对指针变量 pnPointer加上一个整数,就可以访问数组 pnArray中的任何一个元素,即,pnArray[n]与 *(pnPointer+n)是等同的 。
第 6章 指针与字符串实际上,C++语言就是这样对数组元素进行访问的。
在声明一个数组时,C++语言将在内存中开辟两个空间,
一个用于保存数组元素,另一个用于保存数组的第 1个元素的地址。数组名就是用于保存数组第 1个元素的地址的指针型常量。通过保存数组第 1个元素地址的数组名这个指针型常量,可以使用,[]”运算符访问数组元素,
也可以直接使用指针的运算规则访问数组元素。
第 6章 指针与字符串
intpnArray[10];
intnValue;
nValue=*pnArray; //nValue等于 prlArray的第 1个元素的值
nValue=pnArray[5]; //nValue等于 pnArray的第 6个元素的值
nValue=*(pnArray+5); // 与上一句 nValue=pnArray[5];
等价同样,对于指针型变量也可以使用,[]”运算符,将其作为数组来使用 。 如有
int*pnPointer=pnArray; //数组名 pnArray本身是指针型,
因此不必用取地址运算符 &
第 6章 指针与字符串则以下三条语句等价:
nValue=pnArray[5];
nValue=pnPointer[5];
nValue=*(pnPointer+5);
尽管指针与数组之间有着密切的联系,但也有区别。
第 6章 指针与字符串指针型变量是变量,是可以不断赋值的;而数组名虽然是指针,但它是一指针型常量,只能指向固定的内存地址,不能将一个指针值赋予一个数组名 。 以下语句在 C++语言中是错误的:
charcArray[10];
charch;
cArray=&ch;
//错误,不允许将一个指针值赋予数组名第 6章 指针与字符串
6.3.2 通过指针引用数组元素由于指针同数组的特殊关系,我们已经看到,数组与指针几乎可以互换使用 。 下面利用一个具体例子来说明通过指针引用数组元素的方法 。
【 例 6-3】 用指针的方法编写求一个数组中所有元素之和的程序 。
分析:此函数的参数可以写成指针型。由于数组名的实质就是指针,在调用该函数时,只要将数组名作为实际参数传递给函数即可。程序代码如下:
第 6章 指针与字符串
#include<iostream.h>
intSum(int*pPointer,intn)
{
intnSum=0;
while(n>0)
{
nSum+=*pPointer;
pPointer++;
n--;
}
第 6章 指针与字符串
returnnSum;
}
voidmain()
{
intpnArray[10]={6,7,8,9,5,4,3,2,10,1};
intsumArray;
sumArray=Sum(pnArray,10);
cout<<"数组各元素和,sum1="<<sumArray<<endl;
}
程序运行结果为数组各元素之和,sum1=55
第 6章 指针与字符串
【 例 6-4】 对上例 6-3,用指针的方法编写将数组各元素进行排序的函数程序 。
分析:参照例 3-5,用选择法进行排序 (降序 )。
#include<iostream.h>
voidSum(int*pPointer,intn)
{
inti,j,t,p;
for(i=0;i<n-1;i++)
{
p=i;
for(j=i+1;j<n;j++)
第 6章 指针与字符串
{
if(*(pPointer+p)<=*(pPointer+j))p=j;
}
t=*(pPointer+i);
*(pPointer+i)=*(pPointer+p);
*(pPointer+p)=t;
}
}
voidmain()
{
第 6章 指针与字符串
intpnArray[10]={6,7,8,9,5,4,3,2,10,1};
inti;
Sum(pnArray,10);
cout<<"排序后的数组为,"<<endl;
for(i=0;i<10;i++)
cout<<""<<pnArray[i]<<endl;
}
程序运行结果为排序后的数组如下:
10,9,8,7,6,5,4,3,2,1
第 6章 指针与字符串
6.3.3 指向多维数组的指针多维数组的元素在计算机中同一维数组的元素一样,是线性存储的 。 只要知道保存某个多维数组的内存区域的首地址,就可以通过指针访问多维数组中的任何一个元素 。
以二维数组 intfMatrix[3][4]为例 。 它的首地址应该是 &fMatrix[0][0],如果将这个地址值赋予指针型变量
int*pPointer,则二维数组 fMatrix中的任何一个元素使用 pPointer 这 个 指 针 来 访 问 的 规 则 是,
*(pPointer+m*4+n)等同于 fMatrix[m][n]。 其中,4是二维数组的列长度 。
第 6章 指针与字符串这是因为对于多维数组,C++语言采用按行顺序保存的规则 (列标优先变化 )。 对二维数组 fMatrix而言,在保存 fMatrix的存储区中排在前面的是第 1行 fMatrix[0][0]
到 fMatrix[0][3]这 4个元素,然后是第 2行 fMatrix[1][0]到
fMatrix[1][3]这 4个元素,最后是第 3行 fMatrix[2][0]到
fMatrix[2][3]这 4个元素 。
多维数组的数组名同样是个指针,但它不指向多维数组的最前一个被保存的元素的地址
(&fMatrix[0][0]),它指向的是存放下一维元素的首地址指针所在的地址。对于初学者来说,这很难理解。
仍以上述中的 fMatrix数组来说明这个问题。
第 6章 指针与字符串对二维数组 fMatrix[3][4]中关于一维数组名
fMatrix[m](0<m<3)所代表的含义,我们已在第 3章多维数组中进行过讨论。如果把 fMatrix[m]当成一个一维数组的数组名,则将 fMatrix[m]加上下标,就可以访问一维数组 fMatrix[m]中的各元素了,即将二维数组 fMatrix
看成了 3个一维数组。不难发现,这是一个保存了 3个指向一维数组的指针的数组。也就是说,此时一维数组 fMatrix中数组元素的数据类型是指针类型,其数组元素 fMatrix[m]保存的是下一维数组的第 1个元素的首地址 (&fMatrix[m][0])。
第 6章 指针与字符串于是,就有
fMatrix[0]等于 &fMatrix[0][0]
fMatrix[1]等于 &fMatrix[1][0]
fMatrix[2]等于 &fMatrix[2][0]
最后,再来看 fMatrix的含义。
第 6章 指针与字符串
【 例 6-5】 用指针的方法将二维数组的转置 (行列互换 )形式输出,并输出相应的指针值,进行观察,比较 。
#include<iostream.h>
voidmain()
{
intaMatrix[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
inti,j,*p;
cout<<"转置前的矩阵,"<<endl;
for(i=0;i<3;i++)
{
第 6章 指针与字符串
p=aMatrix[i]; //p存放各行的起始地址
for(j=0;j<4;j++)
cout<<""<<*(p+j);
cout<<endl;
}
cout<<"转置后的矩阵,"<<endl;
for(j=0;j<4;j++)
{
for(i=0;i<3;i++)
{
p=aMatrix[i];//p存放各行的起始地址 (注意所在的位置 )
第 6章 指针与字符串
cout<<""<<*(p+j);
}
cout<<endl;
}
cout<<"输出相应的指针值,"<<endl;
cout<<aMatrix<<""<<aMatrix[0]<<""<<&aMatrix[0][0]<
<endl;
for(i=0;i<3;i++)
cout<<aMatrix[i]<<""<<&aMatrix[i][0]<<endl;
}
程序运行结果为第 6章 指针与字符串转置前的矩阵:
1234
5678
9101112
转置后的矩阵:
159
2610
3711
4812
第 6章 指针与字符串输出相应的指针值 ( 以下地址值根据系统运行情况而定 ),
0x0012FF500x0012FF500x0012FF50
0x0012FF500x0012FF50
0x0012FF600x0012FF60
0x0012FF700x0012FF70
第 6章 指针与字符串
6.3.4 指针数组与多级指针
1,指针数组同一般数组一样,多个类型相同的指针变量也可以依数组的形式进行表示 。 声明指针型变量数组的形式同声明普通数组一样,只不过类型是指针类型 。 声明指针型变量的形式如下:
<类型标识符 >*指针数组名 [数组长度 ]
第 6章 指针与字符串例如,
int*ppArray[5]; //声明一个保存 5个 int*型元素的指针数组
char*ppString[6]; //声明一个保存 6个 char*型元素的指针数组指针型变量的数组中每个元素都是指针,同普通数组一样,这些指针按顺序保存在内存的某个空间中,数组名就是指向这个空间首地址的指针 。
由于指针数组中每个元素都是指针,那么从指针数组中取出的元素就具有指针的全部性质 。 例如,
*(ppString[0])代表数组元素 ppString[0]所指向的单元 (保存的地址值 )。
第 6章 指针与字符串
2,多级指针多级指针一般又称为指针的指针 。
如前所述,指针变量的内容是某种数据类型的地址,当然也包括指针类型的地址 。 一个指针型变量保存的是另一个指针型变量的地址,则这个指针型变量就是指针的指针 。 指针的指针类型的表示法是在原先的指针类型后再加一个,*” 号 。 由于多级指针比较复杂,
在此我们只以二级指针为例进行介绍 。
第 6章 指针与字符串定义二级指针变量的一般形式如下,
<类型标识符 >**二级指针变量名当一个指针变量 (如 pp)的内容是另一个指针变量
(如 p是一个指向非指针类型的基本数据类型,称为一级指针 )的地址 (称为 pp指向 p)时,pp就是二级指针变量,
或称为指向指针的指针变量。图 6-4说明了一级指针变量 p与二级指针变量 pp之间的区别和联系。
第 6章 指针与字符串第 6章 指针与字符串显然,如果我们希望通过二级指针变量 pp来访问整型变量 I,必须经过两次间接寻址 。 先由 pp取出它所指的内容 (p的地址 ),即 *pp,再由 *pp(即 i的地址 )取出变量 i的值,即 **pp,这里要两次使用取内容运算符
,*” 。 所以,i的值也可有 **pp与 *p两种形式表示 。
另外,由此可进一步理解二维数组名是一个二级指针的含义,实质是指向一维指针数组第 1个元素 (下标为 0)的指针型常量 。
第 6章 指针与字符串例如,对上例 6-5,若定义二级指针变量
int**pp;
则 pp=aMatrix;是合法的,且 **pp,**aMatrix、
*aMatrix[0]都可表示元素 aMatrix[0][0]
的值 。
指针的指针对初学者来说是个不易接受的概念,但只要明确以下两点,所有的问题都会迎刃而解:
① 指针的指针仍然是变量 。
②指针的指针仍然是指针型变量,只不过保存的地址指向的内存单元中保存的还是一个指针。
第 6章 指针与字符串
6.4 指针与函数
6.4.1 指针变量作为函数参数指针类型也可以作为函数的参数 。 通过指针型参数,可以将一个变量的地址传递给函数,而通过这个地址,函数体中的语句就可以修改函数以外的变量的值 。 为了使大家能够对普通变量作为函数参数与指针变量作为函数参数深入理解,下面我们以这两种不同的参数传递方式举例,以进行对比说明 。
第 6章 指针与字符串
(1)数值传递。所谓数值传递,即普通变量作为函数参数。主调函数中的实参内容是一个数值,其特点是,形参改变,实参不变。
【 例 6-6】 通过函数调用交换两个变量的值 。
#include<iostream.h>
voidchangFunction(int,int); //函数声明语句
voidmain()
{
第 6章 指针与字符串
inta=5,b=10;
cout<<"函数调用前,a="<<a<<",b="<<b<<endl;
changFunction(a,b); //以 a,b的值为实参调用函数
cout<<"函数调用后,a="<<a<<",b="<<b<<endl;
}
voidchangFunction(intm,intn)
{
inttemp;
temp=m;
m=n;
n=temp;
第 6章 指针与字符串
cout<<"函数中参数,m="<<m<<",n="<<n<<endl;
}
程序运行结果为函数调用前,a=5,b=10
函数中参数,m=10,b=5
函数调用后,a=5,b=10
第 6章 指针与字符串说明:由程序的运行结果可以看到,函数调用后变量 a与 b的值并未实现交换。其原因是,在调用函数时采用数值传递的方式,只是将实参 a和 b的值传送给了形参 m和 n,如图 6-5所示;而由于实参 a和 b与形参 m和
n占用不同的内存单元,即使被调函数中的 m和 n进行了交换,也无法将交换后的结果传回给实参 a和 b,函数调用结束后,形参 m和 n被撤消,实参 a和 b的值仍然保持原值不变。
第 6章 指针与字符串第 6章 指针与字符串
(2)地址传递 。 所谓地址传递,即指针变量作为函数参数 。 主调函数中实参的内容是某一变量的内存地址,被调函数的形参必须是与实参相同类型的指针变量,用以接受由实参传过来的地址 。 这样实参与形参指向相同的内存单元,一旦被调函数中形参所指内容发生变化,意味着实参所指内容也发生相应的变化,
函数返回后主调函数就可获得变化后的结果 。
第 6章 指针与字符串
【 例 6-7】 用传递地址的方法改写上例 6-6,我们就能实现变量 a和 b的内容交换,并对照图 6-6分析改进后程序的运行结果 。
#include<iostream.h>
voidchangFunction(int*,int*); //函数声明语句,
参数为指针类型
voidmain()
{
inta=5,b=10;
cout<<"函数调用前,a="<<a<<",b="<<b<<endl;
第 6章 指针与字符串
changFunction(&a,&b); //以 a,b的地址为实参调用函数
cout<<"函数调用后,a="<<a<<",b="<<b<<endl;
}
voidchangFunction(int*m,int*n)
{
inttemp; //互换用的变量
temp=*m;
*m=*n;
*n=temp;
cout<<"函数中参数,*m="<<*m<<",*n="<<*n<<endl;
}
第 6章 指针与字符串程序运行结果为函数调用前,a=5,b=10
函数中参数,*m=10,*n=5
函数调用后,a=10,b=5
第 6章 指针与字符串第 6章 指针与字符串由上例我们不难总结出指针变量作为函数参数的特点是,形参所指内容改变,实参所指内容也相应改变 。
为此编程时要注意两点:第一,实参必须是欲改变内容的变量的地址,形参必须是与实参类型相同的指针变量,
用以接受实参传来的地址;第二,在被调函数中直接通过形参指针变量修改它所指内存单元的内容 。
第 6章 指针与字符串
【 例 6-8】 编写一个复数加法函数 。
分析:复数的加法要求对参与运算的两个复数的实部和虚部分别进行加法运算,运算的结果也有实部和虚部两个部分 。 如果只利用函数的返回值,不能有效地表示一个复数的实部和虚部两个部分 。 所以采用指针操作,
通过指针将结果值赋予外部调用函数的变量 。
函数程序代码如下:
第 6章 指针与字符串
voidComplexPlus(floatfRealpartl,floatfVirtualpartl,floatfReal
part2,
floatfVirtualpart2,float*pfRealR,float*pfVirtualR)
{
*pfRealR=fRealpartl+fRealpart2;
*pfVirtualR=fVirtualpartl+fVirtualpart2;
}
第 6章 指针与字符串
6.4.2 指向函数的指针
1,指向函数指针变量的定义与使用在 C++语言中,函数的调用实际上是通过指针来完成的 。 同数据一样,程序也是保存在内存中的 。 CPU
在执行程序时,首先需要找到程序的入口 ( 开始 ) 地址,然后从内存中取出指令并逐条执行 。 显然,同程序一样,函数的指针是一个函数在内存中的入口地址 。
在调用一个函数时,系统从当前的位置跳到该函数的入口地址处执行指令;函数返回时,系统回到刚才调用函数的那条指令的位置继续执行程序 。
第 6章 指针与字符串由此可见,要调用一个函数,只需要知道被调用函数的入口地址,即函数的指针即可。函数名实际上就是一个指针,它指向函数的入口。我们可以定义一个指针变量,并赋予它函数名,这样该指针变量的内容就是该函数在内存的入口地址。这样的指针变量称为指向函数的指针变量,简称函数指针变量。由此,我们就可以通过函数指针变量来调用它所指向的函数。
第 6章 指针与字符串函数的指针还可以声明成变量 。 声明一个函数指针变量的形式如下,
<类型标识符 >(*函数指针变量名 )(参数表 )
注意,定义中的两对圆括号都不要遗漏,数据类型是函数指针变量所指函数的返回值的数据类型 。
第 6章 指针与字符串例如,若定义函数
voidFunctionA(intm,intn);
则对应于函数 FunctionA的指针型变量可以按如下方法声明,
void(*pFPointer)(int,int);
要用已经定义的函数指针变量来调用函数,需要分两步进行 。
第一步:将函数名赋给已定义的函数指针变量 。
采用的格式为函数指针变量名 =函数名第 6章 指针与字符串例如,可以将 FunctionA的入口地址值赋予
pFPointer这个函数指针变量,
pFpointer=FunctionA;
第二步:使用函数指针变量调用它所指的函数 。 采用的格式为
(*函数指针变量名 )(实参表 )或函数指针变量名 (实参表 )
事实上,由于 pFPointer和 FunctionA指向同一函数入口,除了可以使用函数名 FunctionA调用函数外,当然可以使用函数指针 pFPointer调用函数 。 例如:
FunctionA(3,4) 等价于
(*pFPointer)(3,4);或 pFPointer(3,4);
第 6章 指针与字符串
2,函数指针变量作为函数的参数函数指针变量的一个主要用途是在函数调用时把函数指针变量作为参数,也就是说传递的不是数值,
而是函数的入口地址,目的是实现对若干个不同函数的灵活调用 。 具体编程时要注意的是:主调函数的实参应设置成将被调用的函数名,被调函数的相应形参应设置成接受同类型函数入口地址的函数指针变量 。
第 6章 指针与字符串
【 例 6-9】 将函数指针变量作为函数参数例题 。
#include<iostream.h>
intadd(intx,inty)
{
returnx+y;
}
intsub(intx,inty)
{
returnx-y;
}
intfunct(int(*fun)(int,int),intx,inty)
第 6章 指针与字符串
{
intresult;
result=(*fun)(x,y); //等价于 result=fun(x,y);
returnresult;
}
voidmain()
{
inta=5,b=10;
cin>>a>>b;
cout<<"a+b="<<funct(add,a,b)<<endl;
cout<<"a-b="<<funct(sub,a,b)<<endl;
}
第 6章 指针与字符串程序运行结果为输入,32
3+2=5
3-2=1
第 6章 指针与字符串
6.4.3指针作为函数的返回类型如前所述,函数的数据类型是指函数返回值的类型,它可以是整型,字符型,浮点型或 void(无返回值 ),
也可以是指针类型,即函数的返回值是地址 。 返回值是地址的函数称为指针函数,其定义的一般形式如下:
<类型标识符 >*函数名 (形参表 )
{
函数体
}
第 6章 指针与字符串例如:
int*ip;
int*fFun1(intx)
{
...
returnip;
}
其中 fFun1是函数名,前面的,*” 表示 fFun1是一个指针函数 (注意与函数指针的区别 ),它的返回值是一个 int类型的地址 。 与此相匹配的是,在函数体内 return
语句中的 ip也必须是指向 int型的指针变量 。
第 6章 指针与字符串
【 例 6-10】 函数的返回值是指针类型例题 。
char*PointerCheck(char*pBuffer)
{
if(*pBuffer=='A')
returnNULL;
else
returnpBuffer; //pBuffer不是函数内部的局部变量
}
第 6章 指针与字符串
6.5 指针与类、对象
1,类的指针变量类的指针变量是一个用于保存该类对象在内存中存储空间首地址的指针型变量,同普通数据类型的指针变量有相同的性质 。
声明一个类的指针变量的语法如下,
<类名 >*指针变量名 ;
第 6章 指针与字符串
2,对象的指针对象的指针指的是一个对象在内存中的首地址 。
取得一个对象在内存中首地址的方法同取得变量在内存中首地址的方法一样,都是通过取地址运算符,&”。
例如,若有
CStudent*pStu,Stu1;
则
pStu=&Stu1;
表示表达式 &Stu1是取得对象 Stu1内存中的首地址,
其值的类型是 CStudent*,同类指针 pStu就指向对象
Stu1在内存中的首地址 。
第 6章 指针与字符串同样,已知一个对象的指针,要访问该对象,仍然可以使用指向运算符,*” 。 例如,*(pStu)就同 Stu1等价 。
如果已知一个对象的指针,要访问该对象中的成员,
其方法同结构体变量的指针是相似的,既可以使用,*”
运算符先取得该对象的实际“值”,然后再使用成员运算符,.”访问数据成员和成员函数,也可以使用,->”运算符直接访问该指针所指向的对象的数据成员和成员函数。
第 6章 指针与字符串例如,若有
CStudentStu1;
Cstudent*pStu=&Stu1;
则
*pStu.SetID; //其中 SetID是类 CStudent的数据成员
pStu->SetDispl("张建华 "); //其中 SetDispl是类 CStudent的成员函数都是合法的表达式。
第 6章 指针与字符串
3,this指针
this指针是每个对象中隐藏的指针 。 this指针是默认的 。 当一个对象生成后,这个对象的 this指针就指向内存中保存该对象数据的存储空间的首地址 。
在类的成员函数中使用这个 this指针,就好像 this指针是类的一个自动隐藏的私有成员一样 。 this指针可以形象地用如下定义来说明,
第 6章 指针与字符串
classMyClass
{
[private:]
[MyClass*this;]
...
public:
MyClass();
...
};
第 6章 指针与字符串实际上不必像上面那样定义 this指针 。 this指针对一个对象来说是系统自动生成的,主要用于在成员函数中需要把对象本身作为参数传递给另一个函数 。 我们通过以下例子来说明 this指针的作用 。
第 6章 指针与字符串
【 例 6-11】 this指针的作用例题 。
#include<iostream.h>
classThisSample
{
intn;
public:
ThisSample(){};
ThisSample(intm)
{
n=m;
};
第 6章 指针与字符串
voidaddvalue(intm) //定义一个非静态成员函数
{
ThisSampleq;
q.n=n+m; //此处 n相当于 this->n
*this=q; //将临时对象 q的值传回调用对象
};
voiddisp()
{
cout<<"n="<<n<<endl;
}
};
第 6章 指针与字符串
voidmain()
{
ThisSamples(10);
s.disp();
s.addvalue(5); //相当于函数调用,addvalue(&s,5);
s.disp();
}
程序运行结果为
n=10
n=15
第 6章 指针与字符串
6.6 指针与字符串
6.6.1 字符串指针第 3章我们已经介绍过,在 C++语言中,字符串是以
,\0”为结束标志的字符序列,是以字符数组的形式进行存储与处理的,而数组与指针又紧密相连 (数组名就保存着字符串在内存的起始地址 ),因此,字符串实质上与 char*型指针相对应 。
第 6章 指针与字符串等价于下面两行:
char*cString;
cString="IloveChina!";
由此总结出有关 char*型指针的两个结论如下,
① char*型指针变量可以在定义时进行初始化,其形式为
char*指针变量名 ="字符串 ";
例如:
char*myString="Thisisastring.";
这样就可以用 myString这个指针来访问字符串
,Thisisastring.”了。
第 6章 指针与字符串
② char*型的指针变量 (或函数参数 )既可以用于接收字符串常量,也可以接收字符型数组 。 例如,
charpString[]="IloveChina!";
char*myString="Thisisastring.";
myString=pString;
最后,我们以第 3章字符串的复制为例,进一步对字符串与指针的应用进行说明。
第 6章 指针与字符串
【 例 6-12】 字符串的复制 。
分析:字符串的复制就是将一个字符型数组 (源字符串 )的内容照原样复制到另一个字符型数组中 。 方法是,把源字符型数组中的字符依次赋予给目的字符型数组,直到碰到,\0”(或 0)为止 。
假设源字符串保存在数组 pSource中,目的字符型数组为 pDestination,则用 char*型指针进行字符串复制的程序如下:
第 6章 指针与字符串
#include<iostream.h>
voidcopy_string(char*from,char*to) //复制函数
{
for(;*from!='\0';from++,to++)
*to=*from;
*to='\0'; //赋值字符串结束标识
}
voidmain()
{
第 6章 指针与字符串
charpSource[]="Iamateacher.";
charpDestination[]="youareastudent."; //pDestination 字符串长度 ≥pSource字符串长度
copy_string(pSource,pDestination);
cout<<pSource<<endl;
cout<<pDestination<<endl;
}
第 6章 指针与字符串程序运行结果为
Iamateacher.
Iamateacher.
另外,下面给出复制函数 copy_string函数体的几种等价简化形式,请读者自行分析理解 。
(1)while((*to=*from)!='\0')
{
to++;
from++;
}
(2)while((*to++=*from++)!='\0');
第 6章 指针与字符串
(3)while(*from!='\0')
*to++=*from++;
*to='\0';
(4)while(*from) //注意,'\0'的 ASCII码值 =0(假 )
*to++=*from++;
*to='\0';
第 6章 指针与字符串
6.6.2 字符串标准库函数实际上,C++提供了许多操作字符串数据的标准库函数,如比较字符串,搜索字符串中的字符,确定字符串长度等,我们在程序设计时可直接引用 。 在此简要介绍字符串处理库 (标准库 )中常用的字符串操作函数
( 如表 6-1所示 ) 。
第 6章 指针与字符串表 6-1 C++字符串处理库 (标准库 )中常用的字符串操作函数第 6章 指针与字符串注意:
① 使用字符串处理库中的函数应在程序的开头添加包含,string.h”头文件的预处理命令 #include<string.h>。
②函数 strcpy将第 2个参数 (字符串 )复制到第 1个参数
(字符数组 )中,这个字符数组的长度应当足以放下字符串及其 NULL终止符。函数 strncpy与 strcpy相似,只是
strncpy指定了从字符串复制到字符数组的字符数。
第 6章 指针与字符串
【 例 6-13】 例 6-12字符串的复制可以直接用 C++的标准库函数 strcpy()来实现 。
#include<iostream.h>
#include<string.h>
voidmain()
{
charpSource[]="Iamateacher.";
第 6章 指针与字符串
charpDestination[]="youareastudent."; //pDestination 字符串长度 ≥pSource字符串长度
strcpy(pDestination,pSource); //等价于
strncpy(pDestination,pSource,16);
cout<<pSource<<endl;
cout<<pDestination<<endl;
}
第 6章 指针与字符串
6.7 动态内存分配与 new和 delete运算符动态内存分配是相对于静态内存分配而言的。静态内存分配是指在编译阶段就分配好存储单元空间,
这些空间的大小在程序运行过程中是不可更改的,如变量、数组等;动态内存分配则是指程序员在程序语句中通过调用内存分配函数或使用内存分配运算符取得存储空间,通过动态内存分配得到的空间的大小,
编译器是不知道的,完全由动态运行中的程序的当时情况决定。
第 6章 指针与字符串静态内存分配和动态内存分配在使用中的区别在于:通过静态内存分配取得的空间 (即变量,数组等所占用的空间 )程序员无须管理,编译器在对程序进行编译时已经自动将管理这些空间的代码加入到目标程序中,在作用域结束后,自动将空间归还给系统 ;而通过动态内存分配所取得的空间在使用完毕后,必须由程序员通过程序语句显式地将其归还给系统 。
第 6章 指针与字符串
6.7.1 new运算符
new运算符用于动态分配一块内存空间 。 new运算符的使用形式如下:
指针变量 =new<数据类型 >[长度 ]
其中数据类型可以是 C++语言的标准数据类型,也可以是结构体类型、共用体类型、类类型等;长度表示该内存空间可以容纳该数据类型的数据个数。 new运算符返回一个指针,这个指针指向所分配的存储空间的第 1个单元。
第 6章 指针与字符串返回的指针类型同所分配内存空间的数据类型有关 。 如果数据类型是 char,则返回的指针类型是 char*;
如果数据类型是 int,则返回的指针就是 int*。
例如,
char*CBuffer=newchar[256];//分配一个可以容纳
256个 char型数据的空间说明:
① 如果分配的空间长度为 1个单位,则可以省略
new运算符格式中的中括号和中括号中的整数 。 例如,
float*pNum=newfloat;与 float*pNum=newfloat[1];等价 。
第 6章 指针与字符串
② 使用 new运算符分配内存空间时,其空间长度可以是变量,也可以是数值表达式 。 例如:
intnSize=5;
int*nPInt=newint[nSize+5];//分配一个可以容纳
10个 int型数据的空间
③由 new分配的内存空间是连续的,可以通过指针的变化访问所分配空间的每一个元素。
第 6章 指针与字符串例如:
int*nPInt=newint[10];
nPInt[5]=100;
或
*(nPInt+5)=100;
④ 如果当前存储器无足够的内存空间可分配,则
new运算符返回 0(NULL)。
第 6章 指针与字符串
【 例 6-14】 改写字符串复制函数,要求能通过源字符串的大小动态分配目的字符串的存储空间,并返回所分配空间的首地址 。
#include<iostream.h>
char*toDest;
char*copy_string(char*from) //复制函数
{
intnSize=1;
while(from[nSize]!=0) //求源字符串的长度并加 1
第 6章 指针与字符串
//(注意 nSize的初值赋 1的作用 )
nSize++;
char*to=newchar[nSize]; //定义目的字符串并申请分配内存空间
toDest=to; //用非函数局部变量保存空间首地址
if(to!=NULL) //申请内存空间成功
{
for(;*from!='\0';from++,to++)
*to=*from;
*to='\0'; //赋值字符串结束标识
}
第 6章 指针与字符串
returntoDest;
}
voidmain()
{
charpSource[]="Iamateacher.";
char*pDest;
pDest=copy_string(pSource);
if(pDest!=NULL)
{
cout<<"源字符串,"<<pSource<<endl;
第 6章 指针与字符串
cout<<"目的字符串,"<<pDest<<endl;
}
else
cout<<"无内存空间进行复制操作 ! "<<endl;
}
程序运行结果为源字符串,Iamateacher.
目的字符串,Iamateacher.
第 6章 指针与字符串
6.7.2 delete运算符由 new运算符分配的内存空间在使用完毕后,应该使用 delete运算符释放 。 释放一块已分配的内存空间就是将这一块空间交还给系统 。 这是任何一个使用动态内存分配得到存储空间的程序都必须做的事 。 如果应用程序对有限的内存是只取不还,系统很快就会因为内存枯竭而崩溃 。 所以,使用 delete运算符释放由 new
分配的内存空间是程序员必须牢记的工作 。 凡是使用
new运算符获得的空间,一定要在使用完后使用 delete
释放 。
第 6章 指针与字符串
delete运算符的使用有两种形式,
delete指针及
delete[]指针
delete后所跟的指针是使用 new运算符分配内存时返回的指针,也可以是 NULL。 如果是 NULL,则 delete
运算符实际上什么也不做 。
其中不带中括号的 delete运算符用于释放空间长度为 1个单位的内存空间,而带中括号的 delete运算符用于释放空间长度大于 1的内存空间。
第 6章 指针与字符串
int*pInt=newint;
deletepInt;
int*pManyInt=newint[10];
delete[]pManyInt;
说明:
① 用 new运算符获得的内存空间,只许使用一次
delete,不允许多次对同一块空间进行多次释放,否则将会产生严重错误 。
② delete只能用来释放由 new运算符分配的动态内存空间,对于程序中的变量,数组的存储空间,不得使用 delete运算符去释放 。
第 6章 指针与字符串
6.7.3 动态内存分配应用举例 (链表 )
我们知道,数组是计算机根据事先定义好的数组类型与长度自动为其分配一连续的存储单元,相同数据元素的位置和距离都是固定的,也就是说,任何一个数组元素的地址都可以用一个简单的公式计算出来,
因此这种结构可以有效地对数组元素进行随机访问 。
但若对数组元素进行插入和删除操作,则会引起大量数据的移动,从而使简单的数据处理变得非常复杂,
低效 。 为了能有效地解决这些问题,一种称为,链表,
的数据结构得到了广泛应用 。
第 6章 指针与字符串
1,链表概述链表是一种动态数据结构,它的特点是用一组任意的存储单元 (可以是连续的,也可以是不连续的 )存放数据元素。一个简单的链表具有如图 6-7所示的结构形式。
图 6-7 简单链表的结构形式第 6章 指针与字符串链表中每一个元素称为,结点,,每一个结点都是由数据域和指针域组成的,每个结点中的指针域指向下一个结点 。 图 6-7中,head是,头指针,,表示链表的开始,用来指向第一个结点,而最后一个结点的指针域应为 NULL(空地址,图 6-7中是用,∧,表示的 ),
表示链表的结束 。
第 6章 指针与字符串可以看出,链表结构必须利用指针变量才能实现,
即一个结点中应包含一个指针变量,用来存放下一个结点的地址 。
实际上,链表中的每个结点可以有若干个数据和若干个指针 。 结点中只有一个指针的链表称之为单链表,这是最简单的链表结构 。
第 6章 指针与字符串在 C++中,实现一个单链表结构比较简单 。 例如,
可定义单链表结构的最简单形式如下,
structNODE
{
intdata;
NODE*next;
};
第 6章 指针与字符串这里用到了前面第 3章的结构体类型 。 其中,*next
是指针域,用来指向该结点的下一个结点; data是一个整型变量,用来存放结点中的数据 。 当然,data可以是任何数据类型,包括结构体类型或类类型 。
在此基础上,我们再定义一个链表类 List,其中包含链表结点的插入,删除,输出等功能的成员函数 。
第 6章 指针与字符串
classList
{
NODE*head;
public:
List(){head=NULL;}
voidInsertList(intaData,intbData); //链表结点的插入
voidDeleteList(intaData); //链表结点的删除
voidOutputList(); //链表结点的输出
NODE*Gethead(){returnhead;}
};
第 6章 指针与字符串
2,链表结点的访问由于链表中的各个结点是由指针链接在一起的,
其存储单元未必是连续的,因此,对其中任意结点的地址无法像数组一样,用一个简单的公式计算出来,
进行随机访问 。 只能从链表的头指针 (即 head)开始,用一个指针 p先指向第 1个结点,然后根据结点 p找到下一个结点 。 依此类推,直至找到所要访问的结点或到最后一个结点 (指针为空 )为止 。
第 6章 指针与字符串下面我们给出上述链表类的输出函数:
voidList::OutputList() //链表输出函数
{
NODE*current=head;
while(current!=NULL)
{
cout<<current->data<<"";
current=current->next;
}
cout<<endl;
}
第 6章 指针与字符串
3,链表结点的插入如果要在链表中的结点 a之前插入新结点 b,则需要考虑下列几种情况 。
① 插入前链表是一个空表,这时插入新结点 b后,
链表如图 6-8(a)所示 。 其中实线表示插入前的指针,虚线为插入后的指针 (下同 )。
② 若 a是链表的第 1个结点,则插入后,结点 b为第
1个结点,如图 6-8(b)所示 。
第 6章 指针与字符串
③ 若链表中存在 a,且不是第 1个结点,则首先要找出 a的上一个结点 ak,然后使 ak的指针域指向 b,再令 b
的指针域指向 a,即可完成插入,如图 6-8(c)所示 。
④ 若链表中不存在 a,则插在最后 。 先找到链表的最后一个结点 an,然后使 an的指针域指向结点 b,而 b
结点的指针域为空,如图 6-8(d)所示 。
第 6章 指针与字符串图 6-8 链表的插入第 6章 指针与字符串以下是链表类的结点插入函数,显然其也具有建立链表的功能 。
voidList::InsertList(intaData,intbData) //设 aData是结点
a中的数据,bData是结点 b中的数据
{
NODE*p,*q,*s;
s=(NODE*)new(NODE); //动态分配一个新结点
s->data=bData; //设 b为此结点
p=head;
第 6章 指针与字符串
if(head==NULL) //若是空表,使 b作为第 1个结点
{
head=s;
s->next=NULL;
}
else
if(p->data==aData) //若 a是第 1个结点
{
s->next=p;
head=s;
}
第 6章 指针与字符串
else
{
while(p->data!=aData&&p->next!=NULL) //查找结点 a
{
q=p;
p=p->next;
}
if(p->data==aData) //若有结点 a
{
q->next=s;
s->next=p;
第 6章 指针与字符串
}
else //若没有结点 a
{
p->next=s;
s->next=NULL;
}
}
}
第 6章 指针与字符串
4,链表结点的删除如果要在链表中删除结点 a并释放被删除的结点所占的存储空间,则需要考虑下列几种情况 。
① 若要删除的结点 a是第 1个结点,则把 head指向 a
的下一个结点,如图 6-9(a)所示 。
② 若要删除的结点 a存在于链表中,但不是第 1个结点,则应使 a的上一个结点 ak-1的指针域指向 a的下一个结点 ak+1,如图 6-9(b)所示 。
③空表或要删除的结点 a不存在,则不作任何改变。
第 6章 指针与字符串图 6-9 链表的删除第 6章 指针与字符串以下是链表类的结点删除函数 。
voidList::DeleteList(intaData) //设 aData是要被删除的结点 a中的数据成员
{
NODE*p,*q;
p=head;
if(p==NULL) //若是空表
return;
if(p->data==aData) //若 a是第一个结点
{
head=p->next;
第 6章 指针与字符串
deletep;
}
else
{
while(p->data!=aData&&p->next!=NULL) //查找结点
a
{
q=p;
p=p->next;
}
if(p->data==aData) //若有结点 a
第 6章 指针与字符串
{
q->next=p->next;
deletep;
}
}
}
第 6章 指针与字符串
【 例 6-15】 利 用 以 上 三 个 链 表 操 作 成 员 函 数
InsertList,DeleteList,OutputList,可形成如下的简单链表操作演示程序 。
#include<iostream.h>
structNODE
{ intdata;
NODE*next;
};
classList
{
第 6章 指针与字符串
NODE*head;
public:
List(){head=NULL;}
voidInsertList(intaData,intbData);
voidDeleteList(intaData);
voidOutputList();
NODE*Gethead(){returnhead;}
};
voidmain()
第 6章 指针与字符串
{
ListA,B; //定义两个链表对象
intdata[10]={25,41,16,98,5,67,9,55,1,121};
A.InsertList(0,data[0]); //建立链表 A首结点
for(inti=1;i<10;i++)
A.InsertList(0,data[i]); //顺序向后插入
cout<<"\n链表 A,";
A.OutputList();
A.DeleteList(data[7]);
cout<<"删除元素 data[7]后,\n";
第 6章 指针与字符串
A.OutputList();
B.InsertList(0,data[0]); //建立链表 B首结点
for(i=1;i<10;i++)
B.InsertList(B.Gethead()->data,data[i]);//在首结点处顺序向前插入
cout<<"\n链表 B,";
B.OutputList();
B.DeleteList(67);
cout<<"删除元素 67后,\n";
B.OutputList();
}
第 6章 指针与字符串程序运行结果为链表 A,254116985679551121
删除元素 data[7]后:
2541169856791121
链表 B,121155967598164125
删除元素 67后:
1211559598164125
第 6章 指针与字符串
6.8 string类在处理字符串方面,C++还提供了标准的模板类 —
—string类 。 我们用 string类将字符串定义为对象,然后利用 string类提供的赋值,连接,复制,查找,交换等字符串操作功能,即可方便地实现对字符串的各种处理 。 与字符数组和字符指针处理字符串不同的是,
string不一定要用,\0”来标识字符串的结束 。 下标运算符,[]”也可以用于访问字符串中的各个字符 。
第 6章 指针与字符串由于 string类的结构比较复杂,在此主要就其基本特点与用法进行介绍并举例加以说明,关于更多,更详细的内容,请读者自行参阅有关资料 。
1,string类对象的定义与初始化形式 1:
string对象名 [("字符串 ")]
或
string对象名 [="字符串 "]
形式 2:
string对象名 (n,'字符 '); //生成由 n个 '字符 '组成的字符串第 6章 指针与字符串
2,string类对象的操作
string类对象的操作,即实现对字符串进行赋值,
连接,复制,查找,交换等功能,主要通过 string类对象的成员函数调用与重载运算符 (>>,<<,+等 )来实现 。
其基本形式如下:
<对象名 >.<成员函数 >
例如,
strings1("Hello");
intlen=s1.length(); //函数 length()取得 s1对象的长度
cout<<s1.data(); //支持流输出运算符 <<,有的 C++版本还支持 cout<<s1;格式第 6章 指针与字符串
【 例 6-16】 string类的应用例题 。
请参照运行结果阅读,理解,并上机试验 。
#include<iostream.h>
#include<string>
usingnamespacestd;
voidmain()
{
第 6章 指针与字符串
strings1("Hello"),s2,s3,s4;
//定义 string对象 s1,s2,s3,s4,并对 s1初始化
//string对象的赋值与测长
s2=s1; //用 "="号进行赋值
s3.assign(s1); //调用成员函数 assign()进行赋值
cout<<"s1="<<s1.data()<<",s2="<<s2.data()<<",s3="<<s3.da
ta()<<endl;
第 6章 指针与字符串
cout<<"s1的长度 ="<<s1.length()<<endl;
//调用成员函数 length(),
//输出字符串长度
//string对象与字符数组
chara[]="China!",b[6];
s1=a;
//string对象 s1接收字符数组 a的赋值
cout<<"s1="<<s1.data()<<endl;
for(inti=0;i<5;i++)
第 6章 指针与字符串
b[i]=s2[i]; //此语句也可表示为,b[i]=s2.at(i);
b[5]='\0';
cout<<"字符数组 b="<<b<<endl;
//字符串的连接
s4=s2+""+s1; //用 "+"进行字符串的连接 (重载运算符 +)
s3=s2.append(s1);//s2与 s1连接并赋给 s3,注意连接后的 s2结果
cout<<"s2="<<s2.data()<<",s3="<<s3.data()<<",s4="<<s4.data(
)<<endl;
//字符串的比较第 6章 指针与字符串
intf=s3.compare(6,5,s4,7,5);
//将 s3的第 6个开始的 5个字符与 s4的第 7个开始的 5个字符进行比较
if(f==0)
cout<<"s3与 s4比较的部分相等 "<<"\n";
else
cout<<"s3与 s4比较的部分不相等 "<<"\n";
//取子字符串操作
stringsz=s2.substr(5,5);
//取 s2的第 5个开始的 5个字符第 6章 指针与字符串
cout<<"子字符串 sz="<<sz.data()<<endl;
//字符串的交换与查找
s1.swap(s4);//交换 s1与 s4
cout<<"s1="<<s1.data()<<",s4="<<s4.data()<<endl;
cout<<"字符串 China在 s1中的位置为:
"<<s1.find("China")<<endl;
//在 s1中查找字符串 "China"的位置 (找不到为 0)
//string字符串与 char*型指针的转换
intlen=s1.length();
第 6章 指针与字符串
char*pt=newchar[len+1];
//定义一个字符型指针变量并动态分配空间
s1.copy(pt,len,0);
//将 s1复制到 pt所指的数组 (相当于 pt=&s1[0];)
pt[len]='\0';
cout<<pt<<endl;
s2=pt;
//string类对象 s2接收字符型指针的赋值
cout<<s2.data()<<endl;
}
第 6章 指针与字符串程序运行结果为
s1=Hello,s2=Hello,s3=Hello
s1的长度 =5
s1=China!
字符数组 b=Hello
s2=HelloChina!,s3=HelloChina!,s4=HelloChina!
第 6章 指针与字符串
s3与 s4比较的部分相等子字符串 sz=China
s1=HelloChina!,s4=China!
字符串 "China"在 s1中的位置为 6
HelloChina!
HelloChina!
6.1 指针的概念
6.2 指针型变量
6.3 指针与数组
6.4 指针与函数
6.5 指针与类、对象
6.7 动态内存分配与 new和 delete运算符
6.8 string类第 6章 指针与字符串
6.1 指针的概念指针既是 C++语言学习中的难点又是重点,要掌握
C++语言的精髓,必须学会熟练使用指针 。 本节先引入指针的概念 。
为了说明什么是指针,必须弄清楚数据在内存中是如何存储的,又是如何读取的。如果在程序中定义了一个变量,在编译时就给这个变量分配内存单元。
第 6章 指针与字符串系统根据程序中定义的变量类型,分配一定长度的空间 。 例如,许多计算机系统对整型变量分配两个字节,
对实型变量分配四个字节,对字符型变量分配一个字节,……。 内存区的每一个字节 ( 即单元 ) 有一个编号,
这就是,地址,,它相当于旅馆中的房间号 。 在地址所标识的单元中存放数据,这相当于旅馆中各个房间中住旅客一样 。
第 6章 指针与字符串在此,务必弄清楚一个内存单元的地址与内存单元的内容这两个概念的区别。如图 6-1所示,假设程序已定义了三个整型变量 i,j,k,编译时系统分配 2000和
2001两个字节给变量 i,2002,2003字节给 j,2004、
2005给 k。在内存中已没有 i,j,k这些变量名了,对变量值的存取都是通过地址进行的。
第 6章 指针与字符串图 6-1 内存单元分配示意第 6章 指针与字符串还可以采用另一种称之为“间接访问”的方式。这类似于在程序中定义整型、实型、字符等变量,也可以定义这样一种特殊的变量,它是用来存放地址的,即可以将变量 i的地址存放在另一个内存单元中。假设我们定义了变量 i_pointer是存放整型变量的地址的,它被分配为 3030,3031字节单元。可以通过下面的语句将 i
的地址存放到 i_pointer中:
i_pointer=&i;
这时,i_pointer的值就是 2000,即变量 i所占用单元的起始地址。
第 6章 指针与字符串要存取变量 i的值,也可以采用间接方式:先找到存放,i的地址,的单元地址 (3030,3031),从中获得 i
的起始地址 ( 2000),然后从 2000,2001字节中取出 i
的值 (3)。
简而言之,指针就是指向内存中某个单元的地址值 。 所有的变量,数组,对象都是保存在内存中的 。
凡是保存在内存中的信息,必须知道它在内存中的位置 。 在计算机中,引用了,地址,这个词来代表内存的某个位置 。 内存以字节为单位编址,称为内存地址 。
第 6章 指针与字符串
6.2 指针型变量
6.2.1指针型变量的声明在 C++语言中,任何一种数据类型都有对应的指针类型,甚至指针类型本身也有指针类型 。 要声明一种数据类型的指针型变量,必须在声明变量时将,*” 号加在数据类型的后面 。 定义指针变量的一般形式如下,
<类型标识符 >*指针变量名第 6章 指针与字符串定义中的,*” 表示所定义的变量是一个指针变量,
以示与其它变量的区别 。 指针变量名的命名应符合标识符的命名规则 。 指针变量的值是另一变量的指针 (即首地址 )。 定义中的数据类型是指针变量所指变量的数据类型 。
注意,一个指针变量只能指向定义所指定数据类型的变量,即存放定义所指定数据类型的变量的地址,
因此,指针变量所存放的地址是一个具有指定数据类型的地址。例如,
第 6章 指针与字符串
int*pnNumber; //pnNumber是用来保存一个指向 int型变量的指针的指针型变量
f1oad*pfRealNumber; //pfRealNumber是用来保存一个指向
float型变量的指针的指针型变量
BOOL*pbResult; //pbResult是保存一个指向 int型变量的指针的指针型变量,注意 BOOL型
//与 int型的共用特性第 6章 指针与字符串
char*ppBuffer[5]; //数组 ppBuffer是用来保存 5个指向
char型变量的指针的指针型变量数组
void*pAddress; //pAddress是一个保存内存地址的指针型变量,该内存地址中保存的数据
//类型无须知道
void*型指针是很特殊的一种指针,它不像 int*、
float*等指针那样,指向的内存单元中保存的是具体的一种数据类型的变量,void*只是一个地址,这个地址中保存的可能是 int型变量的起始地址,也可能是 f1oat
型变量的起始地址,也可能什么都不是 。
第 6章 指针与字符串声明指针型变量时,,*” 前后可以加空格,也可以不加,这完全取决于程序员的习惯 。
尽管大多数指针变量类型同其对应的普通数据类型之间表面上只差一个,*” 号,但它们之间的关系相当特殊 。 必须牢记这样的事实:指针型变量也是变量,
指针型变量保存的是地址值,这个值实际上就是一个有特殊意义的整数,称为指针 。
C++语言中定义了一个符号常数 NULL,用来代表空指针值。所谓空指针值是人为规定的一个数值,用来表示“无效”的指针值。在 VisualC++中,NULL被定义为 0。
第 6章 指针与字符串
6.2.2 指针的基本操作与指针有关的基本运算符有以下两个 。
(1)&变量名 。,&”为取地址运算符,用来获取变量的首地址 。
(2)*指针变量名 。,*” 为指向运算符,用来获取指针变量所指向变量的值 。
,&"和,*” 运算符都是单目运算符,其优先级高于所有双目运算符,采用从右到左的结合性 。
第 6章 指针与字符串例如:
inti=5,j,*phint; //定义整型变量 i,j;同时定义整型指针变量 phint
phint=&i://将 i的指针 (即首地址 )赋给 phint,使 phnti指向 i
j=*phint; //将 phint所指变量 i的值 (5)赋给变量 j
第 6章 指针与字符串说明,
① 指针变量是有类型的 。 要使指针变量 phint存放整型变量 i的地址,phint也必须定义为整型指针变量,
否则编译时将出错 。
② 要注意第 1句和第 3句中 *phint的区别:第 1句是指针变量定义语句,其中的,*” 表示 phint是一个指针类型变量;第 3句中的,*” 表示取出指针变量 phint的值 ( 即 phint所指内存单元中的内容 ) 。
第 6章 指针与字符串
6.2.3 指针变量的初始化与引用与其它变量一样,指针变量也可以在定义时对其赋初值 ( 即指针变量的初始化 ) 。 这里要注意的是,
所赋的初值必须是所定义数据类型的变量地址 。 对于上面的程序片段,我们也可以用以下两句来实现,
inti=5,j,*phint=&i;//用整型变量 i的首地址初始化整型指针变量 phint
j=*phint;
第 6章 指针与字符串在此还是要强调,指针变量定义语句中的,*” 只是一个标志,并没有间接存取内容的含义,不能把
int*phint=&i;理解为将 i的地址赋给 phint所指的内存单元,而应理解为将 i的地址赋给 phint本身。
除 void*型指针变量以外,其它指针型变量可以通过在变量名前加,*”号的方法访问该变量所指向的内存地址中所保存的相应数据类型变量的值。
第 6章 指针与字符串
【 例 6-1】 指针变量的使用例题 。
#include<iostream.h>
voidmain()
{
intm,n,*p1=&m,*p2=&n,*phint=NULL;
m=n=6;
cout<<"*p1="<<*p1<<",*p2="<<*p2<<endl;
cout<<"p1="<<p1<<",p2="<<p2<<endl;
cout<<"m="<<m<<",n="<<n<<endl;
cout<<"phint="<<phint<<endl;
第 6章 指针与字符串
*p1+=3;
p2=p1;
*p2*=4;
phint=p2;
cout<<"*p1="<<*p1<<",*p2="<<*p2<<endl;
cout<<"p1="<<p1<<",p2="<<p2<<endl;
cout<<"m="<<m<<",n="<<n<<endl;
cout<<"phint="<<phint<<endl;
}
第 6章 指针与字符串程序运行结果为
*p1=6,*p2=6
p1=0x0012FF7C,p2=0x0012FF78
m=6,n=6
pjint=0x00000000
*p1=36,*p2=36
p1=0x0012FF7C,p2=0x0012FF7C
m=36,n=6
pjint=0x0012FF7C
第 6章 指针与字符串说明,
① *p1,*p2表示指针变量 p1,p2所指变量的值,p1
表示 pl中存放的地址,p2表示 p2中存放的地址,具体的地址值与程序的运行环境有关 。
② *pl+=3;表示将 p1所指内存单元的内容 (即 m的值 6)
加 3后,再存入 p1所指内存单元 。
③ p2=p1;表示将 p1的内容 (即 m的地址 )赋给 p2,使
p2也指向 m。
第 6章 指针与字符串
④ *p2*=4;表示将 p2所指内存单元的内容乘以 4后存入 p2所指内存单元 。 其中最左边的,*” 代表指向操作
(即代表 p2所指的变量 ),中间的,*=”为复合赋值运算符 ( 其中的,*” 代表乘法 ) 。
⑤ 如果没有给指针变量 phint赋初值 NULL,编译时将会出现一警告错误 。
⑥程序的第 6行执行完毕后的情况见图 6-2(a),第
14行执行完毕后的情况见图 6-2(b)。
第 6章 指针与字符串第 6章 指针与字符串
6.2.4指针的运算对指针也可以进行赋值,算术和关系运算 。 同普通的表达式不同,指针的运算有它特有的规则,其中有些运算有特殊的意义,必须同普通的表达式分清 。
1.指针的赋值运算前面已经接触了对指针型变量赋值的简单情况,
这里将详细讨论指针赋值的问题 。
对指针型变量进行赋值必须遵循变量赋值的规则,
也就是说,只有与变量类型相同的值才能安全地赋予变量。这种规则在指针赋值时更为严格。
第 6章 指针与字符串对于普通变量的赋值运算,即使变量类型与值的类型不同,赋值运算也是允许的,只不过这种赋值会遵循某些转换规则进行 。 而指针型变量在赋值时,决不允许将与指针型变量类型不同的指针值赋予指针变量 。 例如,
intnValue;
charbyCh='A';
nValue=byCh;
第 6章 指针与字符串是允许的,尽管 nValue和 byCh是两种不同的数据类型 。 但是
int*pnValue;
charbyCh='A';
pnVaIue=&byCb;
是不允许的,因为 &byCh的类型是 char*,而
pnValue的类型是 int*。
在 C++语言中不允许将与指针型变量类型不同的指针值赋予指针变量的原因在于,不同类型的普通变量的存储宽度是不一样的。
第 6章 指针与字符串在某些特殊情况下,如果需要用同数据类型不兼容的指针指向该数据类型的变量,可以使用强制类型转换 。 例如:
int*pnVa1ue;
charbyCh='A';
pnValue=(char*)&byCh;
将指向一个 char型变量的指针强制转换为指向 int
型变量的指针后,就可以将值赋予 int*型指针变量
pnValue了 。
第 6章 指针与字符串特殊的 void*型指针由于没有任何一种数据类型同它对应,要将其它的任何一种指针值赋予 void*型指针变量都必须通过强制类型转换 。 例如,
void*nPointer;
int*pnValue;
nPointer=(void*)pnValue;
第 6章 指针与字符串由于指针型变量中保存的是一个代表内存地址的整型量,所以,通过强制类型转换,可以将指针型变量的值赋予一个整型变量,也可以将一个整型变量的值赋予一个指针型变量 。 例如:
intnValue;
void*nPointer;
nValue=(int)nPointer;
//将 void*型值强制转换成 int型
nPointer=(void*)nValue;
//将 int型值强制转换成 void*型第 6章 指针与字符串同理,通过强制类型转换,也可以将一个常量赋予一个指针型变量,
int*pPointer=(int*)0x98980;
但是,以下两种做法是错误的,
int*pPointer=&100;
int*pPointer=&(100*20+nValue);
第 6章 指针与字符串
2,指针的算术运算对于非 void*型的指针型变量,只能进行加一个整数,减一个整数和两个指针变量相减三种运算 。 void*
型指针变量不能做任何算术运算 。
将一个非 void*型的指针型变量做加上或减去一个整数的运算,实际上就是对地址进行加法或减法运算。
这种加法和减法运算按照某种单位进行。这种单位就是该指针所指向的变量的数据类型所占用的字节数。
第 6章 指针与字符串例如,对一个 char*型指针变量加 1,则结果就等于这个指针变量实际保存的整数值 (它代表地址 )加上 1;
而对一个 int*型指针变量加 1,其结果就等于这个指针变量实际保存的整数值 (它代表地址 )加上 2,因为一个
int型变量要占用两个字节 。
对两个指针型变量相减只能用在相同类型的指针型变量间,并且这两个指针变量不是 void*型。对两个指针型变量相减的结果将等于两个地址间的相对距离,
这是一个整型值。这个相对距离也是以该指针所指向的变量的数据类型所占用的字节数为单位的。
第 6章 指针与字符串两个 int*型指针相减的结果同两个 char*型指针相减的结果是不同的 。 例如,
intnValue;
int*pPointer1=&nValue,*pPointer2;
pPointer2=pPointer1+1;
假设 pPointer1的初始地址是 2000,则进行
pPointer2=pPointer1+1;运算后,pPointer2的地址值是
2002,如图 6-3所示。
第 6章 指针与字符串第 6章 指针与字符串对于指针的自加 ( ++) 与自减 ( --) 运算,显然很好理解,即
pPointer2=pPointer1+1;
相当于:
pPointer2=pPointer1++;
第 6章 指针与字符串
3,指针的关系运算指针间也可以进行关系运算 。 关系运算符 >,<,=、
>=,<=,!=可以用来连接两个指针型变量做关系运算 。
指针间的关系运算结果就是两个指针所指的地址值的大小的关系运算结果 。 由于内存地址是线性编码的,
所以,如果一个变量 m的保存位置在变量 n之前,则
&m<&n就等于 1,&m>&n就等于 0。 两个进行关系运算的指针一般要求是同一类型的指针 。
第 6章 指针与字符串
4,常指针 (const修饰符与指针 )
与 #define语句相比较,C++提供了一种更灵活,更安全的方式来定义常量,即使用 const修饰符来定义常量 。 例如:
constintLIMIT=100;
它类似于 #defineLIMIT100;语句,但此时这个常量是类型化的,它有自身的地址 。
const也可以与指针一起使用 。 它们的组合情况较复杂,可简单归纳为三种:指向常量的指针,常指针和指向常量的常指针 。
第 6章 指针与字符串
(1)指向常量的指针 。 指向常量的指针是指一个指向常量的指针变量 。 例如:
constchar*name="chen"; //声明指向字符常量的指针变量这个语句的含义是:声明一个名为 name的指针变量,它指向一个字符型常量,初始化 name为指向字符串 "chen"。
由于使用了 const,不容许改变指针所指的常量,
因此以下语句是错误的:
name[3]='a'; //即 name所指的数据不可修改但是,由于 name是一个指向常量的普通指针变量,
不是常指针,因此可以改变 name的值。
第 6章 指针与字符串
(2)常指针 。 常指针是指把指针本身,而不是它指向的对象声明为常量 。 例如:
char*constname="chen"; //常指针这个语句的含义是:声明一个名为 name的指针,该指针是指向字符型数据的常指针,用 "chen"的地址初始化该常指针 。 创建一个常指针,就是创建一个不能移动的固定指针,但是它所指的数据可以改变 。 例如,
name[3]='a' //合法
name="zhang"; //出错第一个语句改变了常指针所指的数据,这是允许的;
但第二个语句要改变指针本身,这是不允许的 。
第 6章 指针与字符串
(3)指向常量的常指针 。 指向常量的常指针是指这个指针本身不能改变,它所指向的数据值也不能改变 。 要声明一个指向常量的常指针,二者都要用 const修饰 。 例如,
constchar*constname="chen"; //指向常量的常指针这个语句的含义是:声明了一个名为 name的指针,
它是一个指向字符型常量的常指针,用 "chen"的地址初始化该指针 。 不难理解以下两个语句都是错误的:
name[3]='a'; //出错,不能改变指针所指的值
name="zhang"; //出错,不能改变指针本身第 6章 指针与字符串
5,引用型变量引用型变量是从指针型变量发展来的一种特殊类型的变量 。 引用变量的实质仍然是指针,但采用了不同于指针的表示方法,可以简化对函数有多个返回值时的处理,使得程序更加清晰可读 。
引用型变量是 C++的新增特性,是一种不具备自己的实际数据存储空间的变量,它必须与另一个同类型的变量共用一个空间 。 换句话说,它只是另一个目标对象的别名,其本身不是一个独立的变量 。 因此,引用只有声明,没有定义 。
第 6章 指针与字符串
【 例 6-2】 引用的声明和使用例题 。
#include<iostream.h>
voidmain()
{
inti,j=4,&ri=i; //定义 ri为整型变量 i的引用
i=3;
cout<<"i="<<i<<",ri="<<ri<<endl;
cout<<"i的地址是 "<<&i<<",ri的地址是 "<<&ri<<endl;
第 6章 指针与字符串
ri+=5;
cout<<"i="<<i<<",ri="<<ri<<endl;
cout<<"i的地址是 "<<&i<<",ri的地址是 "<<&ri<<endl;
ri=j;
cout<<"i="<<i<<",ri="<<ri<<",j="<<j<<endl;
cout<<"i的地址是 "<<&i<<",ri的地址是 "<<&ri<<",j的地址是 "<<&j<<endl;
ri=&j; //出错
}
第 6章 指针与字符串删除最后一句出错的语句后,程序的运行结果为
i=3,ri=3
i的地址是 0x0012FF7C,ri的地址是 0x0012FF7C
i=8,ri=8
i的地址是 0x0012FF7C,ri的地址是 0x0012FF7C
i=4,ri=4,j=4
i的地址是 0x0012FF7C,ri的地址是 0x0012FF7C,j的地址是 0x0012DD78
第 6章 指针与字符串这个例子清楚地表明:引用 ri与它的目标变量 i具有相同的内存地址,它们的值是同步变化的,即使引用 ri
被重新赋予变量 j的值,ri的值发生改变 (i的值也随之改变 ),但 ri仍以 i作为自己的目标,仍以 i的地址作为自己的地址,这一点不会改变。
第 6章 指针与字符串
6.3 指针与数组
6.3.1 指针与数组的关系数组的实质是内存中一块连续的空间,存储着同一类型变量的数据。要访问数组中的某个元素,可以通过“数组名 [下标 ]”这样的语法形式来实现。其中,
下标是要被访问的元素在数组中的位置。由此,不难得出这样的结论:如果知道数组第 1个 (下标为 0)元素的地址,将这个地址保存在某个指针变量中,那么对数组元素的访问就完全可以转换为指针操作。
第 6章 指针与字符串例如,已知指针型变量 int*pnPointer中保存了数组
intpnArray[10]的第 1个元素的地址,则通过对指针变量 pnPointer加上一个整数,就可以访问数组 pnArray中的任何一个元素,即,pnArray[n]与 *(pnPointer+n)是等同的 。
第 6章 指针与字符串实际上,C++语言就是这样对数组元素进行访问的。
在声明一个数组时,C++语言将在内存中开辟两个空间,
一个用于保存数组元素,另一个用于保存数组的第 1个元素的地址。数组名就是用于保存数组第 1个元素的地址的指针型常量。通过保存数组第 1个元素地址的数组名这个指针型常量,可以使用,[]”运算符访问数组元素,
也可以直接使用指针的运算规则访问数组元素。
第 6章 指针与字符串
intpnArray[10];
intnValue;
nValue=*pnArray; //nValue等于 prlArray的第 1个元素的值
nValue=pnArray[5]; //nValue等于 pnArray的第 6个元素的值
nValue=*(pnArray+5); // 与上一句 nValue=pnArray[5];
等价同样,对于指针型变量也可以使用,[]”运算符,将其作为数组来使用 。 如有
int*pnPointer=pnArray; //数组名 pnArray本身是指针型,
因此不必用取地址运算符 &
第 6章 指针与字符串则以下三条语句等价:
nValue=pnArray[5];
nValue=pnPointer[5];
nValue=*(pnPointer+5);
尽管指针与数组之间有着密切的联系,但也有区别。
第 6章 指针与字符串指针型变量是变量,是可以不断赋值的;而数组名虽然是指针,但它是一指针型常量,只能指向固定的内存地址,不能将一个指针值赋予一个数组名 。 以下语句在 C++语言中是错误的:
charcArray[10];
charch;
cArray=&ch;
//错误,不允许将一个指针值赋予数组名第 6章 指针与字符串
6.3.2 通过指针引用数组元素由于指针同数组的特殊关系,我们已经看到,数组与指针几乎可以互换使用 。 下面利用一个具体例子来说明通过指针引用数组元素的方法 。
【 例 6-3】 用指针的方法编写求一个数组中所有元素之和的程序 。
分析:此函数的参数可以写成指针型。由于数组名的实质就是指针,在调用该函数时,只要将数组名作为实际参数传递给函数即可。程序代码如下:
第 6章 指针与字符串
#include<iostream.h>
intSum(int*pPointer,intn)
{
intnSum=0;
while(n>0)
{
nSum+=*pPointer;
pPointer++;
n--;
}
第 6章 指针与字符串
returnnSum;
}
voidmain()
{
intpnArray[10]={6,7,8,9,5,4,3,2,10,1};
intsumArray;
sumArray=Sum(pnArray,10);
cout<<"数组各元素和,sum1="<<sumArray<<endl;
}
程序运行结果为数组各元素之和,sum1=55
第 6章 指针与字符串
【 例 6-4】 对上例 6-3,用指针的方法编写将数组各元素进行排序的函数程序 。
分析:参照例 3-5,用选择法进行排序 (降序 )。
#include<iostream.h>
voidSum(int*pPointer,intn)
{
inti,j,t,p;
for(i=0;i<n-1;i++)
{
p=i;
for(j=i+1;j<n;j++)
第 6章 指针与字符串
{
if(*(pPointer+p)<=*(pPointer+j))p=j;
}
t=*(pPointer+i);
*(pPointer+i)=*(pPointer+p);
*(pPointer+p)=t;
}
}
voidmain()
{
第 6章 指针与字符串
intpnArray[10]={6,7,8,9,5,4,3,2,10,1};
inti;
Sum(pnArray,10);
cout<<"排序后的数组为,"<<endl;
for(i=0;i<10;i++)
cout<<""<<pnArray[i]<<endl;
}
程序运行结果为排序后的数组如下:
10,9,8,7,6,5,4,3,2,1
第 6章 指针与字符串
6.3.3 指向多维数组的指针多维数组的元素在计算机中同一维数组的元素一样,是线性存储的 。 只要知道保存某个多维数组的内存区域的首地址,就可以通过指针访问多维数组中的任何一个元素 。
以二维数组 intfMatrix[3][4]为例 。 它的首地址应该是 &fMatrix[0][0],如果将这个地址值赋予指针型变量
int*pPointer,则二维数组 fMatrix中的任何一个元素使用 pPointer 这 个 指 针 来 访 问 的 规 则 是,
*(pPointer+m*4+n)等同于 fMatrix[m][n]。 其中,4是二维数组的列长度 。
第 6章 指针与字符串这是因为对于多维数组,C++语言采用按行顺序保存的规则 (列标优先变化 )。 对二维数组 fMatrix而言,在保存 fMatrix的存储区中排在前面的是第 1行 fMatrix[0][0]
到 fMatrix[0][3]这 4个元素,然后是第 2行 fMatrix[1][0]到
fMatrix[1][3]这 4个元素,最后是第 3行 fMatrix[2][0]到
fMatrix[2][3]这 4个元素 。
多维数组的数组名同样是个指针,但它不指向多维数组的最前一个被保存的元素的地址
(&fMatrix[0][0]),它指向的是存放下一维元素的首地址指针所在的地址。对于初学者来说,这很难理解。
仍以上述中的 fMatrix数组来说明这个问题。
第 6章 指针与字符串对二维数组 fMatrix[3][4]中关于一维数组名
fMatrix[m](0<m<3)所代表的含义,我们已在第 3章多维数组中进行过讨论。如果把 fMatrix[m]当成一个一维数组的数组名,则将 fMatrix[m]加上下标,就可以访问一维数组 fMatrix[m]中的各元素了,即将二维数组 fMatrix
看成了 3个一维数组。不难发现,这是一个保存了 3个指向一维数组的指针的数组。也就是说,此时一维数组 fMatrix中数组元素的数据类型是指针类型,其数组元素 fMatrix[m]保存的是下一维数组的第 1个元素的首地址 (&fMatrix[m][0])。
第 6章 指针与字符串于是,就有
fMatrix[0]等于 &fMatrix[0][0]
fMatrix[1]等于 &fMatrix[1][0]
fMatrix[2]等于 &fMatrix[2][0]
最后,再来看 fMatrix的含义。
第 6章 指针与字符串
【 例 6-5】 用指针的方法将二维数组的转置 (行列互换 )形式输出,并输出相应的指针值,进行观察,比较 。
#include<iostream.h>
voidmain()
{
intaMatrix[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
inti,j,*p;
cout<<"转置前的矩阵,"<<endl;
for(i=0;i<3;i++)
{
第 6章 指针与字符串
p=aMatrix[i]; //p存放各行的起始地址
for(j=0;j<4;j++)
cout<<""<<*(p+j);
cout<<endl;
}
cout<<"转置后的矩阵,"<<endl;
for(j=0;j<4;j++)
{
for(i=0;i<3;i++)
{
p=aMatrix[i];//p存放各行的起始地址 (注意所在的位置 )
第 6章 指针与字符串
cout<<""<<*(p+j);
}
cout<<endl;
}
cout<<"输出相应的指针值,"<<endl;
cout<<aMatrix<<""<<aMatrix[0]<<""<<&aMatrix[0][0]<
<endl;
for(i=0;i<3;i++)
cout<<aMatrix[i]<<""<<&aMatrix[i][0]<<endl;
}
程序运行结果为第 6章 指针与字符串转置前的矩阵:
1234
5678
9101112
转置后的矩阵:
159
2610
3711
4812
第 6章 指针与字符串输出相应的指针值 ( 以下地址值根据系统运行情况而定 ),
0x0012FF500x0012FF500x0012FF50
0x0012FF500x0012FF50
0x0012FF600x0012FF60
0x0012FF700x0012FF70
第 6章 指针与字符串
6.3.4 指针数组与多级指针
1,指针数组同一般数组一样,多个类型相同的指针变量也可以依数组的形式进行表示 。 声明指针型变量数组的形式同声明普通数组一样,只不过类型是指针类型 。 声明指针型变量的形式如下:
<类型标识符 >*指针数组名 [数组长度 ]
第 6章 指针与字符串例如,
int*ppArray[5]; //声明一个保存 5个 int*型元素的指针数组
char*ppString[6]; //声明一个保存 6个 char*型元素的指针数组指针型变量的数组中每个元素都是指针,同普通数组一样,这些指针按顺序保存在内存的某个空间中,数组名就是指向这个空间首地址的指针 。
由于指针数组中每个元素都是指针,那么从指针数组中取出的元素就具有指针的全部性质 。 例如,
*(ppString[0])代表数组元素 ppString[0]所指向的单元 (保存的地址值 )。
第 6章 指针与字符串
2,多级指针多级指针一般又称为指针的指针 。
如前所述,指针变量的内容是某种数据类型的地址,当然也包括指针类型的地址 。 一个指针型变量保存的是另一个指针型变量的地址,则这个指针型变量就是指针的指针 。 指针的指针类型的表示法是在原先的指针类型后再加一个,*” 号 。 由于多级指针比较复杂,
在此我们只以二级指针为例进行介绍 。
第 6章 指针与字符串定义二级指针变量的一般形式如下,
<类型标识符 >**二级指针变量名当一个指针变量 (如 pp)的内容是另一个指针变量
(如 p是一个指向非指针类型的基本数据类型,称为一级指针 )的地址 (称为 pp指向 p)时,pp就是二级指针变量,
或称为指向指针的指针变量。图 6-4说明了一级指针变量 p与二级指针变量 pp之间的区别和联系。
第 6章 指针与字符串第 6章 指针与字符串显然,如果我们希望通过二级指针变量 pp来访问整型变量 I,必须经过两次间接寻址 。 先由 pp取出它所指的内容 (p的地址 ),即 *pp,再由 *pp(即 i的地址 )取出变量 i的值,即 **pp,这里要两次使用取内容运算符
,*” 。 所以,i的值也可有 **pp与 *p两种形式表示 。
另外,由此可进一步理解二维数组名是一个二级指针的含义,实质是指向一维指针数组第 1个元素 (下标为 0)的指针型常量 。
第 6章 指针与字符串例如,对上例 6-5,若定义二级指针变量
int**pp;
则 pp=aMatrix;是合法的,且 **pp,**aMatrix、
*aMatrix[0]都可表示元素 aMatrix[0][0]
的值 。
指针的指针对初学者来说是个不易接受的概念,但只要明确以下两点,所有的问题都会迎刃而解:
① 指针的指针仍然是变量 。
②指针的指针仍然是指针型变量,只不过保存的地址指向的内存单元中保存的还是一个指针。
第 6章 指针与字符串
6.4 指针与函数
6.4.1 指针变量作为函数参数指针类型也可以作为函数的参数 。 通过指针型参数,可以将一个变量的地址传递给函数,而通过这个地址,函数体中的语句就可以修改函数以外的变量的值 。 为了使大家能够对普通变量作为函数参数与指针变量作为函数参数深入理解,下面我们以这两种不同的参数传递方式举例,以进行对比说明 。
第 6章 指针与字符串
(1)数值传递。所谓数值传递,即普通变量作为函数参数。主调函数中的实参内容是一个数值,其特点是,形参改变,实参不变。
【 例 6-6】 通过函数调用交换两个变量的值 。
#include<iostream.h>
voidchangFunction(int,int); //函数声明语句
voidmain()
{
第 6章 指针与字符串
inta=5,b=10;
cout<<"函数调用前,a="<<a<<",b="<<b<<endl;
changFunction(a,b); //以 a,b的值为实参调用函数
cout<<"函数调用后,a="<<a<<",b="<<b<<endl;
}
voidchangFunction(intm,intn)
{
inttemp;
temp=m;
m=n;
n=temp;
第 6章 指针与字符串
cout<<"函数中参数,m="<<m<<",n="<<n<<endl;
}
程序运行结果为函数调用前,a=5,b=10
函数中参数,m=10,b=5
函数调用后,a=5,b=10
第 6章 指针与字符串说明:由程序的运行结果可以看到,函数调用后变量 a与 b的值并未实现交换。其原因是,在调用函数时采用数值传递的方式,只是将实参 a和 b的值传送给了形参 m和 n,如图 6-5所示;而由于实参 a和 b与形参 m和
n占用不同的内存单元,即使被调函数中的 m和 n进行了交换,也无法将交换后的结果传回给实参 a和 b,函数调用结束后,形参 m和 n被撤消,实参 a和 b的值仍然保持原值不变。
第 6章 指针与字符串第 6章 指针与字符串
(2)地址传递 。 所谓地址传递,即指针变量作为函数参数 。 主调函数中实参的内容是某一变量的内存地址,被调函数的形参必须是与实参相同类型的指针变量,用以接受由实参传过来的地址 。 这样实参与形参指向相同的内存单元,一旦被调函数中形参所指内容发生变化,意味着实参所指内容也发生相应的变化,
函数返回后主调函数就可获得变化后的结果 。
第 6章 指针与字符串
【 例 6-7】 用传递地址的方法改写上例 6-6,我们就能实现变量 a和 b的内容交换,并对照图 6-6分析改进后程序的运行结果 。
#include<iostream.h>
voidchangFunction(int*,int*); //函数声明语句,
参数为指针类型
voidmain()
{
inta=5,b=10;
cout<<"函数调用前,a="<<a<<",b="<<b<<endl;
第 6章 指针与字符串
changFunction(&a,&b); //以 a,b的地址为实参调用函数
cout<<"函数调用后,a="<<a<<",b="<<b<<endl;
}
voidchangFunction(int*m,int*n)
{
inttemp; //互换用的变量
temp=*m;
*m=*n;
*n=temp;
cout<<"函数中参数,*m="<<*m<<",*n="<<*n<<endl;
}
第 6章 指针与字符串程序运行结果为函数调用前,a=5,b=10
函数中参数,*m=10,*n=5
函数调用后,a=10,b=5
第 6章 指针与字符串第 6章 指针与字符串由上例我们不难总结出指针变量作为函数参数的特点是,形参所指内容改变,实参所指内容也相应改变 。
为此编程时要注意两点:第一,实参必须是欲改变内容的变量的地址,形参必须是与实参类型相同的指针变量,
用以接受实参传来的地址;第二,在被调函数中直接通过形参指针变量修改它所指内存单元的内容 。
第 6章 指针与字符串
【 例 6-8】 编写一个复数加法函数 。
分析:复数的加法要求对参与运算的两个复数的实部和虚部分别进行加法运算,运算的结果也有实部和虚部两个部分 。 如果只利用函数的返回值,不能有效地表示一个复数的实部和虚部两个部分 。 所以采用指针操作,
通过指针将结果值赋予外部调用函数的变量 。
函数程序代码如下:
第 6章 指针与字符串
voidComplexPlus(floatfRealpartl,floatfVirtualpartl,floatfReal
part2,
floatfVirtualpart2,float*pfRealR,float*pfVirtualR)
{
*pfRealR=fRealpartl+fRealpart2;
*pfVirtualR=fVirtualpartl+fVirtualpart2;
}
第 6章 指针与字符串
6.4.2 指向函数的指针
1,指向函数指针变量的定义与使用在 C++语言中,函数的调用实际上是通过指针来完成的 。 同数据一样,程序也是保存在内存中的 。 CPU
在执行程序时,首先需要找到程序的入口 ( 开始 ) 地址,然后从内存中取出指令并逐条执行 。 显然,同程序一样,函数的指针是一个函数在内存中的入口地址 。
在调用一个函数时,系统从当前的位置跳到该函数的入口地址处执行指令;函数返回时,系统回到刚才调用函数的那条指令的位置继续执行程序 。
第 6章 指针与字符串由此可见,要调用一个函数,只需要知道被调用函数的入口地址,即函数的指针即可。函数名实际上就是一个指针,它指向函数的入口。我们可以定义一个指针变量,并赋予它函数名,这样该指针变量的内容就是该函数在内存的入口地址。这样的指针变量称为指向函数的指针变量,简称函数指针变量。由此,我们就可以通过函数指针变量来调用它所指向的函数。
第 6章 指针与字符串函数的指针还可以声明成变量 。 声明一个函数指针变量的形式如下,
<类型标识符 >(*函数指针变量名 )(参数表 )
注意,定义中的两对圆括号都不要遗漏,数据类型是函数指针变量所指函数的返回值的数据类型 。
第 6章 指针与字符串例如,若定义函数
voidFunctionA(intm,intn);
则对应于函数 FunctionA的指针型变量可以按如下方法声明,
void(*pFPointer)(int,int);
要用已经定义的函数指针变量来调用函数,需要分两步进行 。
第一步:将函数名赋给已定义的函数指针变量 。
采用的格式为函数指针变量名 =函数名第 6章 指针与字符串例如,可以将 FunctionA的入口地址值赋予
pFPointer这个函数指针变量,
pFpointer=FunctionA;
第二步:使用函数指针变量调用它所指的函数 。 采用的格式为
(*函数指针变量名 )(实参表 )或函数指针变量名 (实参表 )
事实上,由于 pFPointer和 FunctionA指向同一函数入口,除了可以使用函数名 FunctionA调用函数外,当然可以使用函数指针 pFPointer调用函数 。 例如:
FunctionA(3,4) 等价于
(*pFPointer)(3,4);或 pFPointer(3,4);
第 6章 指针与字符串
2,函数指针变量作为函数的参数函数指针变量的一个主要用途是在函数调用时把函数指针变量作为参数,也就是说传递的不是数值,
而是函数的入口地址,目的是实现对若干个不同函数的灵活调用 。 具体编程时要注意的是:主调函数的实参应设置成将被调用的函数名,被调函数的相应形参应设置成接受同类型函数入口地址的函数指针变量 。
第 6章 指针与字符串
【 例 6-9】 将函数指针变量作为函数参数例题 。
#include<iostream.h>
intadd(intx,inty)
{
returnx+y;
}
intsub(intx,inty)
{
returnx-y;
}
intfunct(int(*fun)(int,int),intx,inty)
第 6章 指针与字符串
{
intresult;
result=(*fun)(x,y); //等价于 result=fun(x,y);
returnresult;
}
voidmain()
{
inta=5,b=10;
cin>>a>>b;
cout<<"a+b="<<funct(add,a,b)<<endl;
cout<<"a-b="<<funct(sub,a,b)<<endl;
}
第 6章 指针与字符串程序运行结果为输入,32
3+2=5
3-2=1
第 6章 指针与字符串
6.4.3指针作为函数的返回类型如前所述,函数的数据类型是指函数返回值的类型,它可以是整型,字符型,浮点型或 void(无返回值 ),
也可以是指针类型,即函数的返回值是地址 。 返回值是地址的函数称为指针函数,其定义的一般形式如下:
<类型标识符 >*函数名 (形参表 )
{
函数体
}
第 6章 指针与字符串例如:
int*ip;
int*fFun1(intx)
{
...
returnip;
}
其中 fFun1是函数名,前面的,*” 表示 fFun1是一个指针函数 (注意与函数指针的区别 ),它的返回值是一个 int类型的地址 。 与此相匹配的是,在函数体内 return
语句中的 ip也必须是指向 int型的指针变量 。
第 6章 指针与字符串
【 例 6-10】 函数的返回值是指针类型例题 。
char*PointerCheck(char*pBuffer)
{
if(*pBuffer=='A')
returnNULL;
else
returnpBuffer; //pBuffer不是函数内部的局部变量
}
第 6章 指针与字符串
6.5 指针与类、对象
1,类的指针变量类的指针变量是一个用于保存该类对象在内存中存储空间首地址的指针型变量,同普通数据类型的指针变量有相同的性质 。
声明一个类的指针变量的语法如下,
<类名 >*指针变量名 ;
第 6章 指针与字符串
2,对象的指针对象的指针指的是一个对象在内存中的首地址 。
取得一个对象在内存中首地址的方法同取得变量在内存中首地址的方法一样,都是通过取地址运算符,&”。
例如,若有
CStudent*pStu,Stu1;
则
pStu=&Stu1;
表示表达式 &Stu1是取得对象 Stu1内存中的首地址,
其值的类型是 CStudent*,同类指针 pStu就指向对象
Stu1在内存中的首地址 。
第 6章 指针与字符串同样,已知一个对象的指针,要访问该对象,仍然可以使用指向运算符,*” 。 例如,*(pStu)就同 Stu1等价 。
如果已知一个对象的指针,要访问该对象中的成员,
其方法同结构体变量的指针是相似的,既可以使用,*”
运算符先取得该对象的实际“值”,然后再使用成员运算符,.”访问数据成员和成员函数,也可以使用,->”运算符直接访问该指针所指向的对象的数据成员和成员函数。
第 6章 指针与字符串例如,若有
CStudentStu1;
Cstudent*pStu=&Stu1;
则
*pStu.SetID; //其中 SetID是类 CStudent的数据成员
pStu->SetDispl("张建华 "); //其中 SetDispl是类 CStudent的成员函数都是合法的表达式。
第 6章 指针与字符串
3,this指针
this指针是每个对象中隐藏的指针 。 this指针是默认的 。 当一个对象生成后,这个对象的 this指针就指向内存中保存该对象数据的存储空间的首地址 。
在类的成员函数中使用这个 this指针,就好像 this指针是类的一个自动隐藏的私有成员一样 。 this指针可以形象地用如下定义来说明,
第 6章 指针与字符串
classMyClass
{
[private:]
[MyClass*this;]
...
public:
MyClass();
...
};
第 6章 指针与字符串实际上不必像上面那样定义 this指针 。 this指针对一个对象来说是系统自动生成的,主要用于在成员函数中需要把对象本身作为参数传递给另一个函数 。 我们通过以下例子来说明 this指针的作用 。
第 6章 指针与字符串
【 例 6-11】 this指针的作用例题 。
#include<iostream.h>
classThisSample
{
intn;
public:
ThisSample(){};
ThisSample(intm)
{
n=m;
};
第 6章 指针与字符串
voidaddvalue(intm) //定义一个非静态成员函数
{
ThisSampleq;
q.n=n+m; //此处 n相当于 this->n
*this=q; //将临时对象 q的值传回调用对象
};
voiddisp()
{
cout<<"n="<<n<<endl;
}
};
第 6章 指针与字符串
voidmain()
{
ThisSamples(10);
s.disp();
s.addvalue(5); //相当于函数调用,addvalue(&s,5);
s.disp();
}
程序运行结果为
n=10
n=15
第 6章 指针与字符串
6.6 指针与字符串
6.6.1 字符串指针第 3章我们已经介绍过,在 C++语言中,字符串是以
,\0”为结束标志的字符序列,是以字符数组的形式进行存储与处理的,而数组与指针又紧密相连 (数组名就保存着字符串在内存的起始地址 ),因此,字符串实质上与 char*型指针相对应 。
第 6章 指针与字符串等价于下面两行:
char*cString;
cString="IloveChina!";
由此总结出有关 char*型指针的两个结论如下,
① char*型指针变量可以在定义时进行初始化,其形式为
char*指针变量名 ="字符串 ";
例如:
char*myString="Thisisastring.";
这样就可以用 myString这个指针来访问字符串
,Thisisastring.”了。
第 6章 指针与字符串
② char*型的指针变量 (或函数参数 )既可以用于接收字符串常量,也可以接收字符型数组 。 例如,
charpString[]="IloveChina!";
char*myString="Thisisastring.";
myString=pString;
最后,我们以第 3章字符串的复制为例,进一步对字符串与指针的应用进行说明。
第 6章 指针与字符串
【 例 6-12】 字符串的复制 。
分析:字符串的复制就是将一个字符型数组 (源字符串 )的内容照原样复制到另一个字符型数组中 。 方法是,把源字符型数组中的字符依次赋予给目的字符型数组,直到碰到,\0”(或 0)为止 。
假设源字符串保存在数组 pSource中,目的字符型数组为 pDestination,则用 char*型指针进行字符串复制的程序如下:
第 6章 指针与字符串
#include<iostream.h>
voidcopy_string(char*from,char*to) //复制函数
{
for(;*from!='\0';from++,to++)
*to=*from;
*to='\0'; //赋值字符串结束标识
}
voidmain()
{
第 6章 指针与字符串
charpSource[]="Iamateacher.";
charpDestination[]="youareastudent."; //pDestination 字符串长度 ≥pSource字符串长度
copy_string(pSource,pDestination);
cout<<pSource<<endl;
cout<<pDestination<<endl;
}
第 6章 指针与字符串程序运行结果为
Iamateacher.
Iamateacher.
另外,下面给出复制函数 copy_string函数体的几种等价简化形式,请读者自行分析理解 。
(1)while((*to=*from)!='\0')
{
to++;
from++;
}
(2)while((*to++=*from++)!='\0');
第 6章 指针与字符串
(3)while(*from!='\0')
*to++=*from++;
*to='\0';
(4)while(*from) //注意,'\0'的 ASCII码值 =0(假 )
*to++=*from++;
*to='\0';
第 6章 指针与字符串
6.6.2 字符串标准库函数实际上,C++提供了许多操作字符串数据的标准库函数,如比较字符串,搜索字符串中的字符,确定字符串长度等,我们在程序设计时可直接引用 。 在此简要介绍字符串处理库 (标准库 )中常用的字符串操作函数
( 如表 6-1所示 ) 。
第 6章 指针与字符串表 6-1 C++字符串处理库 (标准库 )中常用的字符串操作函数第 6章 指针与字符串注意:
① 使用字符串处理库中的函数应在程序的开头添加包含,string.h”头文件的预处理命令 #include<string.h>。
②函数 strcpy将第 2个参数 (字符串 )复制到第 1个参数
(字符数组 )中,这个字符数组的长度应当足以放下字符串及其 NULL终止符。函数 strncpy与 strcpy相似,只是
strncpy指定了从字符串复制到字符数组的字符数。
第 6章 指针与字符串
【 例 6-13】 例 6-12字符串的复制可以直接用 C++的标准库函数 strcpy()来实现 。
#include<iostream.h>
#include<string.h>
voidmain()
{
charpSource[]="Iamateacher.";
第 6章 指针与字符串
charpDestination[]="youareastudent."; //pDestination 字符串长度 ≥pSource字符串长度
strcpy(pDestination,pSource); //等价于
strncpy(pDestination,pSource,16);
cout<<pSource<<endl;
cout<<pDestination<<endl;
}
第 6章 指针与字符串
6.7 动态内存分配与 new和 delete运算符动态内存分配是相对于静态内存分配而言的。静态内存分配是指在编译阶段就分配好存储单元空间,
这些空间的大小在程序运行过程中是不可更改的,如变量、数组等;动态内存分配则是指程序员在程序语句中通过调用内存分配函数或使用内存分配运算符取得存储空间,通过动态内存分配得到的空间的大小,
编译器是不知道的,完全由动态运行中的程序的当时情况决定。
第 6章 指针与字符串静态内存分配和动态内存分配在使用中的区别在于:通过静态内存分配取得的空间 (即变量,数组等所占用的空间 )程序员无须管理,编译器在对程序进行编译时已经自动将管理这些空间的代码加入到目标程序中,在作用域结束后,自动将空间归还给系统 ;而通过动态内存分配所取得的空间在使用完毕后,必须由程序员通过程序语句显式地将其归还给系统 。
第 6章 指针与字符串
6.7.1 new运算符
new运算符用于动态分配一块内存空间 。 new运算符的使用形式如下:
指针变量 =new<数据类型 >[长度 ]
其中数据类型可以是 C++语言的标准数据类型,也可以是结构体类型、共用体类型、类类型等;长度表示该内存空间可以容纳该数据类型的数据个数。 new运算符返回一个指针,这个指针指向所分配的存储空间的第 1个单元。
第 6章 指针与字符串返回的指针类型同所分配内存空间的数据类型有关 。 如果数据类型是 char,则返回的指针类型是 char*;
如果数据类型是 int,则返回的指针就是 int*。
例如,
char*CBuffer=newchar[256];//分配一个可以容纳
256个 char型数据的空间说明:
① 如果分配的空间长度为 1个单位,则可以省略
new运算符格式中的中括号和中括号中的整数 。 例如,
float*pNum=newfloat;与 float*pNum=newfloat[1];等价 。
第 6章 指针与字符串
② 使用 new运算符分配内存空间时,其空间长度可以是变量,也可以是数值表达式 。 例如:
intnSize=5;
int*nPInt=newint[nSize+5];//分配一个可以容纳
10个 int型数据的空间
③由 new分配的内存空间是连续的,可以通过指针的变化访问所分配空间的每一个元素。
第 6章 指针与字符串例如:
int*nPInt=newint[10];
nPInt[5]=100;
或
*(nPInt+5)=100;
④ 如果当前存储器无足够的内存空间可分配,则
new运算符返回 0(NULL)。
第 6章 指针与字符串
【 例 6-14】 改写字符串复制函数,要求能通过源字符串的大小动态分配目的字符串的存储空间,并返回所分配空间的首地址 。
#include<iostream.h>
char*toDest;
char*copy_string(char*from) //复制函数
{
intnSize=1;
while(from[nSize]!=0) //求源字符串的长度并加 1
第 6章 指针与字符串
//(注意 nSize的初值赋 1的作用 )
nSize++;
char*to=newchar[nSize]; //定义目的字符串并申请分配内存空间
toDest=to; //用非函数局部变量保存空间首地址
if(to!=NULL) //申请内存空间成功
{
for(;*from!='\0';from++,to++)
*to=*from;
*to='\0'; //赋值字符串结束标识
}
第 6章 指针与字符串
returntoDest;
}
voidmain()
{
charpSource[]="Iamateacher.";
char*pDest;
pDest=copy_string(pSource);
if(pDest!=NULL)
{
cout<<"源字符串,"<<pSource<<endl;
第 6章 指针与字符串
cout<<"目的字符串,"<<pDest<<endl;
}
else
cout<<"无内存空间进行复制操作 ! "<<endl;
}
程序运行结果为源字符串,Iamateacher.
目的字符串,Iamateacher.
第 6章 指针与字符串
6.7.2 delete运算符由 new运算符分配的内存空间在使用完毕后,应该使用 delete运算符释放 。 释放一块已分配的内存空间就是将这一块空间交还给系统 。 这是任何一个使用动态内存分配得到存储空间的程序都必须做的事 。 如果应用程序对有限的内存是只取不还,系统很快就会因为内存枯竭而崩溃 。 所以,使用 delete运算符释放由 new
分配的内存空间是程序员必须牢记的工作 。 凡是使用
new运算符获得的空间,一定要在使用完后使用 delete
释放 。
第 6章 指针与字符串
delete运算符的使用有两种形式,
delete指针及
delete[]指针
delete后所跟的指针是使用 new运算符分配内存时返回的指针,也可以是 NULL。 如果是 NULL,则 delete
运算符实际上什么也不做 。
其中不带中括号的 delete运算符用于释放空间长度为 1个单位的内存空间,而带中括号的 delete运算符用于释放空间长度大于 1的内存空间。
第 6章 指针与字符串
int*pInt=newint;
deletepInt;
int*pManyInt=newint[10];
delete[]pManyInt;
说明:
① 用 new运算符获得的内存空间,只许使用一次
delete,不允许多次对同一块空间进行多次释放,否则将会产生严重错误 。
② delete只能用来释放由 new运算符分配的动态内存空间,对于程序中的变量,数组的存储空间,不得使用 delete运算符去释放 。
第 6章 指针与字符串
6.7.3 动态内存分配应用举例 (链表 )
我们知道,数组是计算机根据事先定义好的数组类型与长度自动为其分配一连续的存储单元,相同数据元素的位置和距离都是固定的,也就是说,任何一个数组元素的地址都可以用一个简单的公式计算出来,
因此这种结构可以有效地对数组元素进行随机访问 。
但若对数组元素进行插入和删除操作,则会引起大量数据的移动,从而使简单的数据处理变得非常复杂,
低效 。 为了能有效地解决这些问题,一种称为,链表,
的数据结构得到了广泛应用 。
第 6章 指针与字符串
1,链表概述链表是一种动态数据结构,它的特点是用一组任意的存储单元 (可以是连续的,也可以是不连续的 )存放数据元素。一个简单的链表具有如图 6-7所示的结构形式。
图 6-7 简单链表的结构形式第 6章 指针与字符串链表中每一个元素称为,结点,,每一个结点都是由数据域和指针域组成的,每个结点中的指针域指向下一个结点 。 图 6-7中,head是,头指针,,表示链表的开始,用来指向第一个结点,而最后一个结点的指针域应为 NULL(空地址,图 6-7中是用,∧,表示的 ),
表示链表的结束 。
第 6章 指针与字符串可以看出,链表结构必须利用指针变量才能实现,
即一个结点中应包含一个指针变量,用来存放下一个结点的地址 。
实际上,链表中的每个结点可以有若干个数据和若干个指针 。 结点中只有一个指针的链表称之为单链表,这是最简单的链表结构 。
第 6章 指针与字符串在 C++中,实现一个单链表结构比较简单 。 例如,
可定义单链表结构的最简单形式如下,
structNODE
{
intdata;
NODE*next;
};
第 6章 指针与字符串这里用到了前面第 3章的结构体类型 。 其中,*next
是指针域,用来指向该结点的下一个结点; data是一个整型变量,用来存放结点中的数据 。 当然,data可以是任何数据类型,包括结构体类型或类类型 。
在此基础上,我们再定义一个链表类 List,其中包含链表结点的插入,删除,输出等功能的成员函数 。
第 6章 指针与字符串
classList
{
NODE*head;
public:
List(){head=NULL;}
voidInsertList(intaData,intbData); //链表结点的插入
voidDeleteList(intaData); //链表结点的删除
voidOutputList(); //链表结点的输出
NODE*Gethead(){returnhead;}
};
第 6章 指针与字符串
2,链表结点的访问由于链表中的各个结点是由指针链接在一起的,
其存储单元未必是连续的,因此,对其中任意结点的地址无法像数组一样,用一个简单的公式计算出来,
进行随机访问 。 只能从链表的头指针 (即 head)开始,用一个指针 p先指向第 1个结点,然后根据结点 p找到下一个结点 。 依此类推,直至找到所要访问的结点或到最后一个结点 (指针为空 )为止 。
第 6章 指针与字符串下面我们给出上述链表类的输出函数:
voidList::OutputList() //链表输出函数
{
NODE*current=head;
while(current!=NULL)
{
cout<<current->data<<"";
current=current->next;
}
cout<<endl;
}
第 6章 指针与字符串
3,链表结点的插入如果要在链表中的结点 a之前插入新结点 b,则需要考虑下列几种情况 。
① 插入前链表是一个空表,这时插入新结点 b后,
链表如图 6-8(a)所示 。 其中实线表示插入前的指针,虚线为插入后的指针 (下同 )。
② 若 a是链表的第 1个结点,则插入后,结点 b为第
1个结点,如图 6-8(b)所示 。
第 6章 指针与字符串
③ 若链表中存在 a,且不是第 1个结点,则首先要找出 a的上一个结点 ak,然后使 ak的指针域指向 b,再令 b
的指针域指向 a,即可完成插入,如图 6-8(c)所示 。
④ 若链表中不存在 a,则插在最后 。 先找到链表的最后一个结点 an,然后使 an的指针域指向结点 b,而 b
结点的指针域为空,如图 6-8(d)所示 。
第 6章 指针与字符串图 6-8 链表的插入第 6章 指针与字符串以下是链表类的结点插入函数,显然其也具有建立链表的功能 。
voidList::InsertList(intaData,intbData) //设 aData是结点
a中的数据,bData是结点 b中的数据
{
NODE*p,*q,*s;
s=(NODE*)new(NODE); //动态分配一个新结点
s->data=bData; //设 b为此结点
p=head;
第 6章 指针与字符串
if(head==NULL) //若是空表,使 b作为第 1个结点
{
head=s;
s->next=NULL;
}
else
if(p->data==aData) //若 a是第 1个结点
{
s->next=p;
head=s;
}
第 6章 指针与字符串
else
{
while(p->data!=aData&&p->next!=NULL) //查找结点 a
{
q=p;
p=p->next;
}
if(p->data==aData) //若有结点 a
{
q->next=s;
s->next=p;
第 6章 指针与字符串
}
else //若没有结点 a
{
p->next=s;
s->next=NULL;
}
}
}
第 6章 指针与字符串
4,链表结点的删除如果要在链表中删除结点 a并释放被删除的结点所占的存储空间,则需要考虑下列几种情况 。
① 若要删除的结点 a是第 1个结点,则把 head指向 a
的下一个结点,如图 6-9(a)所示 。
② 若要删除的结点 a存在于链表中,但不是第 1个结点,则应使 a的上一个结点 ak-1的指针域指向 a的下一个结点 ak+1,如图 6-9(b)所示 。
③空表或要删除的结点 a不存在,则不作任何改变。
第 6章 指针与字符串图 6-9 链表的删除第 6章 指针与字符串以下是链表类的结点删除函数 。
voidList::DeleteList(intaData) //设 aData是要被删除的结点 a中的数据成员
{
NODE*p,*q;
p=head;
if(p==NULL) //若是空表
return;
if(p->data==aData) //若 a是第一个结点
{
head=p->next;
第 6章 指针与字符串
deletep;
}
else
{
while(p->data!=aData&&p->next!=NULL) //查找结点
a
{
q=p;
p=p->next;
}
if(p->data==aData) //若有结点 a
第 6章 指针与字符串
{
q->next=p->next;
deletep;
}
}
}
第 6章 指针与字符串
【 例 6-15】 利 用 以 上 三 个 链 表 操 作 成 员 函 数
InsertList,DeleteList,OutputList,可形成如下的简单链表操作演示程序 。
#include<iostream.h>
structNODE
{ intdata;
NODE*next;
};
classList
{
第 6章 指针与字符串
NODE*head;
public:
List(){head=NULL;}
voidInsertList(intaData,intbData);
voidDeleteList(intaData);
voidOutputList();
NODE*Gethead(){returnhead;}
};
voidmain()
第 6章 指针与字符串
{
ListA,B; //定义两个链表对象
intdata[10]={25,41,16,98,5,67,9,55,1,121};
A.InsertList(0,data[0]); //建立链表 A首结点
for(inti=1;i<10;i++)
A.InsertList(0,data[i]); //顺序向后插入
cout<<"\n链表 A,";
A.OutputList();
A.DeleteList(data[7]);
cout<<"删除元素 data[7]后,\n";
第 6章 指针与字符串
A.OutputList();
B.InsertList(0,data[0]); //建立链表 B首结点
for(i=1;i<10;i++)
B.InsertList(B.Gethead()->data,data[i]);//在首结点处顺序向前插入
cout<<"\n链表 B,";
B.OutputList();
B.DeleteList(67);
cout<<"删除元素 67后,\n";
B.OutputList();
}
第 6章 指针与字符串程序运行结果为链表 A,254116985679551121
删除元素 data[7]后:
2541169856791121
链表 B,121155967598164125
删除元素 67后:
1211559598164125
第 6章 指针与字符串
6.8 string类在处理字符串方面,C++还提供了标准的模板类 —
—string类 。 我们用 string类将字符串定义为对象,然后利用 string类提供的赋值,连接,复制,查找,交换等字符串操作功能,即可方便地实现对字符串的各种处理 。 与字符数组和字符指针处理字符串不同的是,
string不一定要用,\0”来标识字符串的结束 。 下标运算符,[]”也可以用于访问字符串中的各个字符 。
第 6章 指针与字符串由于 string类的结构比较复杂,在此主要就其基本特点与用法进行介绍并举例加以说明,关于更多,更详细的内容,请读者自行参阅有关资料 。
1,string类对象的定义与初始化形式 1:
string对象名 [("字符串 ")]
或
string对象名 [="字符串 "]
形式 2:
string对象名 (n,'字符 '); //生成由 n个 '字符 '组成的字符串第 6章 指针与字符串
2,string类对象的操作
string类对象的操作,即实现对字符串进行赋值,
连接,复制,查找,交换等功能,主要通过 string类对象的成员函数调用与重载运算符 (>>,<<,+等 )来实现 。
其基本形式如下:
<对象名 >.<成员函数 >
例如,
strings1("Hello");
intlen=s1.length(); //函数 length()取得 s1对象的长度
cout<<s1.data(); //支持流输出运算符 <<,有的 C++版本还支持 cout<<s1;格式第 6章 指针与字符串
【 例 6-16】 string类的应用例题 。
请参照运行结果阅读,理解,并上机试验 。
#include<iostream.h>
#include<string>
usingnamespacestd;
voidmain()
{
第 6章 指针与字符串
strings1("Hello"),s2,s3,s4;
//定义 string对象 s1,s2,s3,s4,并对 s1初始化
//string对象的赋值与测长
s2=s1; //用 "="号进行赋值
s3.assign(s1); //调用成员函数 assign()进行赋值
cout<<"s1="<<s1.data()<<",s2="<<s2.data()<<",s3="<<s3.da
ta()<<endl;
第 6章 指针与字符串
cout<<"s1的长度 ="<<s1.length()<<endl;
//调用成员函数 length(),
//输出字符串长度
//string对象与字符数组
chara[]="China!",b[6];
s1=a;
//string对象 s1接收字符数组 a的赋值
cout<<"s1="<<s1.data()<<endl;
for(inti=0;i<5;i++)
第 6章 指针与字符串
b[i]=s2[i]; //此语句也可表示为,b[i]=s2.at(i);
b[5]='\0';
cout<<"字符数组 b="<<b<<endl;
//字符串的连接
s4=s2+""+s1; //用 "+"进行字符串的连接 (重载运算符 +)
s3=s2.append(s1);//s2与 s1连接并赋给 s3,注意连接后的 s2结果
cout<<"s2="<<s2.data()<<",s3="<<s3.data()<<",s4="<<s4.data(
)<<endl;
//字符串的比较第 6章 指针与字符串
intf=s3.compare(6,5,s4,7,5);
//将 s3的第 6个开始的 5个字符与 s4的第 7个开始的 5个字符进行比较
if(f==0)
cout<<"s3与 s4比较的部分相等 "<<"\n";
else
cout<<"s3与 s4比较的部分不相等 "<<"\n";
//取子字符串操作
stringsz=s2.substr(5,5);
//取 s2的第 5个开始的 5个字符第 6章 指针与字符串
cout<<"子字符串 sz="<<sz.data()<<endl;
//字符串的交换与查找
s1.swap(s4);//交换 s1与 s4
cout<<"s1="<<s1.data()<<",s4="<<s4.data()<<endl;
cout<<"字符串 China在 s1中的位置为:
"<<s1.find("China")<<endl;
//在 s1中查找字符串 "China"的位置 (找不到为 0)
//string字符串与 char*型指针的转换
intlen=s1.length();
第 6章 指针与字符串
char*pt=newchar[len+1];
//定义一个字符型指针变量并动态分配空间
s1.copy(pt,len,0);
//将 s1复制到 pt所指的数组 (相当于 pt=&s1[0];)
pt[len]='\0';
cout<<pt<<endl;
s2=pt;
//string类对象 s2接收字符型指针的赋值
cout<<s2.data()<<endl;
}
第 6章 指针与字符串程序运行结果为
s1=Hello,s2=Hello,s3=Hello
s1的长度 =5
s1=China!
字符数组 b=Hello
s2=HelloChina!,s3=HelloChina!,s4=HelloChina!
第 6章 指针与字符串
s3与 s4比较的部分相等子字符串 sz=China
s1=HelloChina!,s4=China!
字符串 "China"在 s1中的位置为 6
HelloChina!
HelloChina!