第一个程序:演示了如何使用new来创建动态数组以及使用数组表示法来访问元素,还指出了指针和真正的数组名之间的差别
#include "stdafx.h"
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
double * p3=new double[3];//space for 3 doubles
p3[0]=0.2;
p3[1]=0.5;
p3[2]=0.8;
cout<<"p3[1] is "<<p3[1]<<endl;
p3=p3+1;//increment the pointer
cout<<"Now p3[0] is "<<p3[0]<<" and ";
cout<<"p3[1] is "<<p3[1]<<endl;
p3=p3-1;//point back to beginning
delete[] p3;
return 0;
}
不能修改数组名的值。但指针是变量,因此可以修改它的值。请注意将p3加1的效果,表达式p3[0]现在指的是数组的第2个值。因此,将p3加1导致它指向第2个元素而不是第1个。将它减1后,指针将指向原来的值,这样程序便可以给delete[]提供正确的地址。
第二个程序:指针算术,C++将数组名解释为地址
#include "stdafx.h"
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
double wages[3]={10000.0,20000.0,30000.0};
short stacks[3]={3,2,1};
//Here are two ways to get the address of an array
double * pw=wages; //name of an array = address
short * ps=&stacks[0]; //or use address operator
//with array element
cout<<"pw = "<<pw<<", *pw = "<<*pw<<endl;
pw=pw+1;
cout<<"pw = "<<pw<<", *pw = "<<*pw<<"\n\n";
cout<<"ps = "<<ps<<", *ps = "<<*ps<<endl;
ps=ps+1;
cout<<"add 1 to the ps pointer:\n";
cout<<"ps = "<<ps<<", *ps = "<<*ps<<"\n\n";
cout<<"access two elements with array notation\n";
cout<<"stacks[0] = "<<stacks[0]<<", stacks[1] = "<<stacks[1]<<endl;
cout<<"access two elements with pointer notation\n";
cout<<"*stacks = "<<*stacks<<", *(stacks+1) = "<<*(stacks+1)<<endl;
cout<<sizeof(wages)<<" = size of wages array\n";
cout<<sizeof(pw)<<" = size of pw pointer\n";
return 0;
}
在多数情况下,C++将数组名解释为数组第1个元素的地址。
记住:将指针变量加1后,其增加的值等于指向的类型占用的字节数。
在很多情况下,可以相同的方式使用指针名和数组名。对于它们,可以使用数组方括号表示法,也可以使用解除引用操作符(*)。在多数表达式中,它们都表示地址。区别之一是,可以修改指针的值,而数组名是常量。
pointername = pointername+1; //valid
arrayname = arrayname + 1; //not allowed
对数组应用sizeof操作符得到的是数组的长度,而对指针应用sizeof得到的是指针的长度,即使指针指向的是一个数组。
第三个程序:如何使用不同形式的字符串
#include "stdafx.h"
#include <iostream>
#include <cstring> //declare strlen(),strcpy()
using namespace std;
int main(int argc, char* argv[])
{
char animal[20] = "bear"; //animal holds bear;
const char * bird = "wren"; //bird holds address of string
char * ps; //uninitialized
cout<<animal<<" and "; //display bear
cout<<bird<<"\n"; //display wren
cout<<"Enter a kind of animail: ";
cin>>animal;
ps=animal;
cout<<ps<<"s!\n";
cout<<"Before using strcpy();\n";
cout<<animal<<" at "<<(int *)animal<<endl;
cout<<ps<<" at "<<(int *)ps<<endl;
ps = new char[strlen(animal)+1];//get new storage
strcpy(ps,animal);//copy string to new storage
cout<<"After using strcpy():\n";
cout<<animal<<" at "<<(int *)animal<<endl;
cout<<ps<<" at "<<(int *)ps<<endl;
delete [] ps;
return 0;
}
数组和指针的特殊关系可以扩展到C-style string,看下面代码:
char flower[10] = "rose";
cout<<flower<<"s are red\n";
数组名是第一个元素的地址,因此cout语句中的flower是包含字符r的char元素的地址。cout对象认为char的地址是字符串的地址,因此它打印该地址处的字符,然后继续打印后面的字符,直到遇到空字符(\0)为止。总之,如果给cout提供一个字符的地址,则它将从该字符开始打印,直到遇到空字符为止。
在C++中,用引号括起的字符串像数组名一样,也是第一个元素的地址。上述代码不会将整个字符串发送给cout,而只是发送该字符串的地址。
这意味着对于数组中的字符串、用引号括起的字符串常量以及指针所描述的字符串,处理的方式是一样的,都将传递它们的地址。与逐个传递字符串中的所有字符相比,这样做的工作量确实要少。
记住:在cout和多数C++表达式中,char数组名、指向char的指针以及用引号括起的字符串常量都被解释为字符串第一个字符的地址。 上述的程序中,函数strlen(),返回字符串的长度。函数strcpy()将字符串从一个位置复制到另一个位置。
const char * bird = "wren";
记住,“wren”实际表示的是字符串的地址,因此这条语句将“wren”的地址赋给了bird指针(一般来说,编译器在内存留出一些空间,以存储程序源代码中所有用引号括起来的字符串,并将每个被存储的字符串与其地址关联起来)。这意味着可以像使用字符串“wren”那样使用指针bird。
字符串字面值是常量,这就是为什么代码在声明中使用关键字const的原因。以这种方式使用const意味着可以用bird来访问字符串,但不能修改它。
C++不能保证字符串字面值被唯一地存储。也就是说,如果在程序中多次使用了字符串字面值“wren”,则编译器将可能存储该字符串的多个副本,也可能只存储一个副本。如果是后面一种情况,则将bird设置为指向一个“wren”,将使它只是指向该字符串的唯一一个副本。将值读入一个字符串可能会影响被认为是独立的、位于其他地方的字符串。无论如何,由于bird指针被声明为const,因此编译器将禁止改变bird指向的位置中的内容。
试图将信息读入ps指向的位置将更糟。由于ps没有被初始化,因此并不知道信息将被存储在哪里,这甚至可能改写内存中的信息。幸运的是,要避免这种问题很容易——只要使用足够大的char数组来接受输入即可。请不要使用字符串常量或未被初始化的指针来接受输入。为避免这些问题,也可以使用std::string对象,而不是数组。
注意,将animal赋给ps并不会复制字符串,而只是复制地址。这样,这两个指针将指向相同的内存单元和字符串。
要获得字符串的副本:首先,需要分配内存来存储该字符串,可以通过声明另一个数组或使用new来完成。接下来,需要将animal数组的字符串复制到新分配的空间中。将animal赋给ps是不可行的,因为这样只能修改存储在ps中的地址,从而失去程序访问新分配内存的唯一途径。需要使用库函数strcpy():strcp()函数接受2个参数,第一个是目标地址,第二是要复制的字符串的地址。应确定,分配了目标空间,并有足够的空间来存储副本。
经常需要将字符串放到数组中。初始化数组时,使用=操作符。否则应使用strcpy()或strncpy()。
如果数组的大小比字符串小,函数将字符串中剩余的部分复制到数组后面的内存字节中,可能会覆盖程序正在使用的其他内存。要避免这种问题,使用strncpy()。该函数还接受第3个参数——要复制的最大字节数。不过,要注意的是,如果该函数在到达字符串结尾之前,目标内存已经用完,则它将不会添加空字符串。因此,应该这样使用该函数:
strncp(food,"a picnic basket filled with many goodies",19);
food[19]='\0';
第四个程序:一个使用new和delete的范例
下面介绍一个使用new和delete来存储通过键盘输入的字符串的。该程序定义了一个函数getname(),该函数返回一个指向输入字符串的指针。该函数将输入读入到一个大型的数组中,然后使用new[]创建一个刚好能够存储该输入字符串的内存块,并返回一个指向该内存块的指针。对于读取大量字符串的程序,这种方法可以节省大量的内存。
另外,可以使用new根据需要的指针数量来分配空间,就目前而言,这有点不切实际。下面程序演示了一些技巧。
#include "stdafx.h"
#include <iostream>
#include <cstring> //or string.h
using namespace std;
char * getname(void); //function prototype
int main(int argc, char* argv[])
{
char * name;//create pointer but no storage
name=getname();//assign address of string to name
cout<<name<<" at "<<(int*)name<<"\n";
delete[] name;//memory freed
name=getname();//reuse freed memory
cout<<name<<" at "<<(int*)name<<"\n";
delete[] name;//memory freed again
return 0;
}
char * getname() //return pointer to new string
{
char temp[80];//temporary storage
cout<<"Enter last name: ";
cin>>temp;
char * pn=new char[strlen(temp)+1];
strcpy(pn,temp);//copy string into smaller space
return pn;//temp lost when function ends
}
C++不保证新释放的内存就是下一次使用new时选择的内存。从程序的运行结果可知,确实不是。
在上面程序中,getname()分配内存,而main()释放内存。将new和delet放在不同的函数中通常并不是一个好办法,因为这样很容易忘记使用delete。