数组名本身,没有方括号和下标,它实际上是地址,表示数组起始地址。
整型数组的数组名本身得到一整型地址,字符数组的数组名得到一字符型地址。
可以把数组起始地址赋给一指针,通过移动指针(加减指针)来对数组元素进行操作。
指针iPtr被初始化为数组起始地址,因此,指针定义语句“int* iPtr=iArray;”中,左右两边的类型是匹配的。
它可以写成:
int* iPtr;
iPtr=iArray;
其中,“iPtr=iArray;”还可以改写成:
iPtr=&iArray[0]; //同样表示数组的第一个元素的地址
在程序例中,循环重复10次,指针遍历数组每个元素。
假设在16位机器上,数组起始 地址是Ox00000100,则指针iPtr的值似乎是:
0x00000100
0x00000101
0x00000102
0x00000103
0x00000104
但我们知道16位机器中, 整数是占2个字节的,所以数组元素的地址应该是:
0x00000100
Ox00000102
0x00000104
0x00000106
0x00000108
...
由于指针是具有某个数据类型的地址,所以指针运算都是以数据类型为单位展开的。
即,iPtr是个整型指针,iPtr++使指针指向下一个整型数。因而,iPtr的地址值其实增加了2,见图8-3。
图8—3 指针iPtr++的移动
数组名可以拿来初始化指针,数组名就是数组第一个元素地址。 即*iArray 就是 iArray[0]
即对于数组a,有:a等于&a[0]。此外,对于:
int a[l00];
int* iPtr=a;
我们有第i个元素: i 从0起
a[i] 等价于 *(a+i) 等价于 iPtr[i] 等价于 *(iPtr+i)
a[i]表示数组的第i个元素的值。而a+i表示第i个元素的地址, 对其间接访问,
即 *(a+i)就表示第i个元素的值。另外,下标操作是针对地址而不仅仅是针对数组名的,
所以iPtr[i]也表示第i个元素的值。 上面4个式子等价的事实使得数组与指针相互转换非常灵活。
不但如此,相应的,我们还有第i个元素的地址:
&a[i] 等价于 a+i 等价于 iPtr+i 等价于 &iPtr[i)
数组名本身是一指针,它的类型是指向数组元素的指针。
&a[i]表示数组第i个元素的地址。该4式的等价与前面4式的等价是对应的。
数组名是指针常量,区别于指针变量,所以,给数组名赋值是错误的。
int a[100];
int *iPtr = a;
iPtr指向第一个元素的地址
&a[i]
iPtr+i
a+i
&iPtr[i]
int sum1, sum2, sum3, sum4, sum5;
int iArray[] = {1, 4, 5,2, 7, 12, 21, 26, 30};
void main()
{
int size, n
size = sizeof(iArray)/sizeof(*iArray);
for(n=0; n<size; n++) // plan 1
sum1 += iArray[n];
int *iPtr=iArray; //plan 2
for(n=0; n<size; n++)
sum2 += *iPtr++;
iPtr=iArray; //plan 3
for(n=0; n<size; n++)
sum3 += *(iPtr + n);
iPtr = iArray; //plan 4
for(n=0; n<size; n++)
sum4 += iPtr[n];
for(n=0; n<size; n++) //plan 5
sum5+= *(iArray+n);
}
堆允许程序在运行时(而不是在编译时),申请某个大小的内存空间。
需要在程序运行时从系 统中获取内存:
程序在编译和连接时不予确定这种在运行中获取的内存空间,
这种内存环境随着程序运行的进展而时大时小,这种内存就是堆内存,
所以堆内存是动态的。堆内存也称动态内存。
获得堆内存
函数malloc()是C程序获得堆内存的一种方法,它在alloc.h头文件中声明。malloc()函数的原型为:
void* malloc(size_t size);
size_t即unsigned long。
该函数从堆内存中“切下”一块size大小的内存,将指向该内存的地址返回。该内存中的内容是未知的。
例如,下面的程序从堆中获取一个整数数组,赋值并打印:
//*********************
//** ch8_7.cpp **
//*********************
#include <iostream.h>
#include <alloc.h>
void main()
{
int arraysize; //元素个数
int *array;
cout <<"please input a number of array:/n";
cin >>arraysize;
array=(int*) malloc(arraysize*sizeof(int));
//堆内存分配
for(int count=0; count<arraysize; count++)
array[count]=count*2;
for(int count=0; count<arraysize; count++)
cout <<array[count] <<" ";
cout <<endl;
}
运行结果为:
C>ch8_7
please input a number Of array elements:
10
0 2 4 6 8 10 12 14 16 18
程序编译和连接时,在栈中分配了arraysize整型变量和array整型指针变量空间:程序运行中,
malloc()函数在堆中寻找未被使用的内存,找够所需的字节数后返回该内存的起始地址。
因为malloc()函数并不知道用这些内存干什么,所以它返回一个没有类型的指针(见8.6节)。
但对整数指针array来说,malloc()函数的返回值必须显式转换成整数类型指针才能被接受(ANSIC++标准)。
一个拥有内存的指针完全可以被看作为一个数组,
而且位于堆中的数组和位于栈中的数组结构是一样的。
表达式“array[count]=count*2;”正是这样应用的。
上例中并没有保证一定可以从堆中获得所需内存。
有时,系统能提供的堆空间不够分配,这时系统会返回一个空指针值NULL。
这时所有对该指针的访问都是破坏性的,因此调用malloc()函数更完善的代码应该如下:
if( ( array=(int *)malloc(arraysize*sizeof(int)) )==NULL )
{
cout<<"can't allocate more memory,terminate./n";
exit(1);
}
释放堆内存
我们把堆看作是可以按要求进行分配的资源或内存池。程序对内存的需求量随时会增大或缩小。程序在运行中可能经常会不再需要由malloc()函数分配的内存,而且程序还未运行结束,这时就需要把先前所占用的内存释放回堆以供程序的其它部分所用。
函数free()返还由malloc()函数分配的堆内存,其函数原型为:
void free(void*);
free()参数是先前调用malloc()函数时返回的地址。把其它值传给free()很可能会造成灾难性的后果。
例如,下面的程序完善程序ch8_7.cpp:
//*********************
//** ch8_8.cpp **
//*********************
#include <iostream.h>
#include <alloc.h>
void main()
{
int arraysize; //元素个数
int *array;
cout <<"please input a number of array:/n";
cin >>arraysize;
if((array=(int*)malloc(arraysize*sizeof(int)))==NULL){
cout<<"can't allocate more memory,terminating./n";
return ;
}
for(int count=0; count<arraysize; count++)
array[count]=count*2;
for(int count=0; count<arraysize; count++)
cout <<array[count] <<" ";
cout <<endl;
free(array); //释放堆内存
}
运行结果为:
C>ch8_8
please input a number Of array elements:
10
0 2 4 6 8 10 12 14 16 18
4.new与delete
new和delete是C++专有的操作符,它们不用头文件声明。new类似于函数malloc(),分配堆内存,但比malloc()更简练。new的操作数为数据类型,它可以带初始化值表或单元个数。new返回一个具有操作数之数据类型的指针。返回delete类似于函数free(),释放堆内存。delete的操作数是new返回的指针,当返回的是new分配的数组时,应该带[]。
例如,下面的程序是程序ch8_8.cpp的新版:
//*********************
//** ch8_9.cpp **
//*********************
#include <iostream.h>
//#include <alloc.h> //无须头文件
void main()
{
int arraysize; //元素个数
int *array;
cout <<"please input a number of array:/n";
cin >>arraysize;
if((array=new int[arraysize])==NULL){ //分配堆内存
cout<<"can't allocate more memory,terminating./n";
return ;
}
for(int count=0; count<arraysize; count++)
array[count]=count*2;
for(int count=0; count<arraysize; count++)
cout <<array[count] <<" ";
cout <<endl;
delete[]array; //释放堆内存
}
运行结果为:
C>ch8_9
please input a number Of array elements:
10
0 2 4 6 8 10 12 14 16 18
上例中可以看到,Hew的返回值无须显式转换类型, 直接赋给整数指针array;new的操作数是int[arraysize],它只要指明什么类型和要几个元素就可以了,它比函数malloc()更简洁。事实上,new和delete比函数malloc()和free()具有更丰富的功能。在第14章将进一步展开new和delete的讨论。
常量指针定义"const int *pi=&a;”告诉编译,*pi是常量,不能将,pi作为左值进行操作。 ????
在指针定义语句的指针名前加const,表示指针本身是常量。
2.指针常量
在指针定义语句的指针名前加const,表示指针本身是常量。例如:
char *const pc="asdf"; //指针名前加const定义指针常量 在定义指针常量时必须初始化,就像常量初始化一样
pc="dfgh"; //error:指针常量不能改变其指针值
*pc='b'; //ok:pc内容为,'bsdf',
*(pc+1)='c'; //ok:pc内容为'bcdf',
*pc++='y'; //error:指针常量不能改变其指针值 修改*pc的同时也修改了指针值。
const int b=28;
int* const pi=&b; //error:不能将const int*转换成int*
pc是指针常量,在定义指针常量时必须初始化,就像常量初始化一样。这里初始化的 值是字符串常量的地址,见图8_5
图8_5 指向变量的指针常量图
由于pc是指针常量,所以不能修改该指针值。“pc="dfgh";”将引起一个“不能修改常量对象”(Cannot modify a const object)的编译错误。
pc所指向的地址中存放的值并不受指针常量的约束,即*pc不是常量,所以“*pc='b';”和“*(pc+1)='c';”的赋值操作是允许的。
但“*pc++=y;”是不允许的,因为该语句修改*pc的同时也修改了指针值。
由于此处*pi是不受约束的,所以,将一个常量的地址赋给该指针“int* const pi=&b;”是非法的,它将导致一个不能将const int *转换成int *的编译错误,因为那样将使修改常量(如:“* pi=38;”)合法化。
指针常量定义"int *const pc=&b;”告诉编译,pc是常量,不能作为左值进行操作,但是允许修改间接访问值,即*pc可以修改。
3.指向常量的指针常量(常量指针常量)
可以定义一个指向常量(const )的指针常量( * const ),它必须在定义时进行初始化。例如:
const int c=7;
int ai;
const int * const cpc=&ci; //指向常量的指针常量
const int * const cpi=&ai; //ok
cpi=&ci; //error:指针值不能修改
*cpi=39; //error:不能修改所指向的对象
ai=39; //ok
cpc和cpi都是指向常量的指针常量,它们既不允许修改指针值,也不允许修改*cpc的值,见图8-6。
图8—6 指向常量的指针常量
如果初始化的值是变量地址(如&ai), 那么不能通过该指针来修改该变量的值。也即“*cpi=39;”是错误的,将引起“不能修改常量对象”(Cannot modify a const object)的编译错误。但“ai=39;”是合法的。
常量指针常量定义“coastint * coastcpc=&b;”告诉编译,cpc和* cpc都是常量,它们都不能作为左值进行操作。
1.传递数组的指针性质
一旦把数组作为参数传递到函数中,则在栈上定义了指针,可以对该指针进行递增、递减操作。
例如,下面的代码传递一个数组,并对其进行求和运算:
//**********************
//** ch8_11.cpp **
//**********************
#include <iostream.h>
void Sum(int array[],int n)
{
int sum = 0;
for(int i=0;i<n; i++){
sum += *array;
array++; //ok:array是一个指针,可以作为左值
}
cout <<sum <<endl;
}
void main()
{
int a[10]={1,2,3,4,5,6,7,8,9,10};
Sum(a,10);
}
运行结果为:
55
在主函数中,对Sum()函数进行了调用。传递的数组参数在Sum()中,实质上是一个指针, 所以声明Sum(int array[],int n)与Sum(int *array,int n)是等价的。调用的示意见图8-7。
图8-7 数组作为参数的函数调用
由于形参army是指针变量而不是数组,所以它所占的空间是指针变量大小而不是数组空间大小。不能用sizeof(array)/sizeof(*array)来求取数组元素个数,这就是第二参数n(表示数组元素个数)必须要给的原因。
尽管形参中说明的形式是array[]数组的形式,但C++已经明确告诉作为参数传递的数组就是指针变量,所以arayy可以作为左值进行array++运算。
指针函数
返回指针的函数称为指针函数。指针函数不能把在它内部说明的具有局部作用域的数据地址作为返回值。
字符串常量的类型是指向字符的指针(字符指针char *),它与字符数组名同属于一种类型。字符串常量在内存中以,'/0',结尾。这种类型的字符串称为C字符串,或ASCIIZ字符串。
当编译器遇到一字符串常量时,就把它放到字符串池(data区的const区)中,以,'/0',作结束符,记下其起始地址,在所构成的代码中使用该地址。这样,字符串常量就“变成”了地址。
由于字符串常量的地址属性,所以两个同样字符组成的字符串常量的地址是不相等的.
例如,下面的程序不会输出"equal"字符串:
//**********************
//** ch8_16.cpp **
//**********************
#include <iostream.h>
void main()
{
if("join"=="join")
cout <<"equal/n";
else
cout <<"not equal/n";
}
运行结果为:
not equal
程序中两个字符串的比较实质上是两个地址的比较。
在编译时,给了这两个字符串不同的存放地点,所以两个"join"字符串的地址是不同的。
要使两个字符串真正从字面上进行 比较,可以用库函数strcmp(),见稍后的描述。
3.字符指针
字符串常量,字符数组名,字符指针均属于同一种数据类型。
例如,下面的程序描述了字符指针的操作:
#include <iostream.h>
void main()
{
char buffer[10]="ABC ";
char* pc;
pc="hello"; //ok: 将字符串常量的首地址赋给指针
cout <<pc <<endl; // hello
pc++;
cout <<pc <<endl; // ello
cout <<*pc <<endl; // e
pc=buffer;
cout <<pc; // ABC
}
运行结果为:
hello
ello
e
ABC
buffer初始化为"ABC",buffer[3]='/0',buffer[4]~buffer[9]的内容不确定。
pc是字符指针,定义时分配该变量空间但没有初始化,之后将"hello"赋给pc。
由于字符串常量是地址,所以“pc="hello"”;”语句完全合法。
pc实际上指向"hello"中的'h',字符。当pc++时,pc就指向这个字符串中的'e'字符。
输出字符指针就是输出字符串。所以输出pc时,便从'e',字符的地址开始,直到遇到 '/0',结束。
输出字符指针的间接引用,就是输出单个字符。当输出*p,时,便是输出pc所指向的字符,见图8-11。
图8—11 字符指针指向字符串常量
Buffer是字符数组名,所以“pc=buffer;”也是合法的。之后的输出只输出字符数组中的前3个字符,遇到,'/0',就结束了。
当pc指向buffer后,字符串常量"hello"仍逗留在内存的data区,但是再也访问不到该字符串(数据丢失)了。
所以对于字符串常量赋给字符指针的情形,一般指针不再重新赋值。
4.字符串比较
我们已经知道,两个字符串常量的比较是地址的比较。同理,两个数组名的比较也是地址的比较。
例如,下面的代码不会输出”equal”:
//**********************
//** ch8_18.cpp **
//**********************
#include <iostream.h>
void main()
{
char buffer1[10]="hello";
char buffer2[10]="hello";
if(buffer1==buffer2)
cout <<"equal/n";
else
cout <<"not equal/n";
}
运行结果为:
not equal
因为bufferl和buffer2都是地址,bufferl与buffer2是不同的数组,占有不同的内存空间,所以其地址也不同,即bufferl与buffer2不相等。那么,要进行字符串比较,应该怎么办?
字符串比较应该是逐个字符一一比较,通常使用标准库函数strcmp(),它在string.h头文件中声明,其原型为:
int strcmp(const char* strl,const char* str2);
其返回值如下:
(1)当strl串等于str2串时,返回值0;
(2)当strl串大于str2串时,返回一个正值;
(3)当strl串小于str2串时,返回一个负值。
例如,下面的程序修改了程序ch8_18.cpp,使之成功地进行字符串比较:
//**********************
//** ch8_19.cpp **
//**********************
#include <iostream.h>
#include <string.h>
void main()
{
char buffer1[10]="hello";
char buffer2[10]="hello";
if(strcmp(buffer1,buffer2)==0)
cout <<"equal/n";
else
cout <<"not equal/n";
}
运行结果为:
equal
5.字符串赋值
C++中不能直接对字符数组赋予一个字符串,原因是数组名是常量指针,不是左值。
例如,下面的代码不能通过编译:
char buffer[10];
buffer="hello"; //error
可以使用标准函数strcpy()来对字符数组进行赋值。strcpy()的声明在头文件string.h 中,它的原型为:
char'strcpy(char* dest,const char* src);
strcpy()函数的返回值为dest值,一般都舍弃该值。
例如,下面的代码对字符数组进行赋值:
char buffer1[10];
char buffer2[10];
strcpy(bufferl,"hello");
strcpy(buffer2,bufferl);
函数strcpy()仅能对以,'/0',作结束符的字符数组进行操作。若要对其他类型的数组赋值可调用函数memcpy():
int intarray1[5]={l,3,5,7,9};
int intarray2[5];
memcpy(intarray2,intarray1,5*sizeof(int));
传递数组给函数就是传递指针给函数。传递指针数组给函数就是传递二级指针给函数。
NULL与void*是不同的概念,
NULL是一个值,一个指针值,任何类型的指针都可赋予该值。
而void*是一种类型,是一种无任何类型的指针。s