前段时间去旅游了,回来继续写。
12、指针、数组和指针算术
对上一篇进行的补充
#include <iostream>
int main()
{
using namespace std;
double wages[3] = { 10000.0,20000.0,30000.0 };
short stacks[3] = { 3,2,1 };
double* pw = wages;
short* ps = &stacks[0];
cout << "pw= " << pw << ", *pw= " << *pw << endl;
pw += 1;
cout << "add 1 tp the pw pointer:" << endl;
cout << "pw= " << pw << ", *pw= " << *pw << endl << endl;
cout << "ps= " << ps << ", *ps= " << *ps << endl;
ps += 1;
cout << "add 1 tp the ps pointer:" << endl;
cout << "ps= " << ps << ", *ps= " << *ps << endl << endl;
cout << "access two elements with array natation" << endl;
cout << "stack[0]= " << stacks[0]
<< ", stacks[1] = " << stacks[1] << endl;
cout << "access two elements with pointer natation" << endl;
cout << "*stacks= " << *stacks
<< ",*(stacks+1)= " << *(stacks + 1) << endl;
cout << sizeof(wages) << " =size of wages array" << endl;
cout << sizeof(pw) << " =size of pw pointer" << endl;
return 0;
}
12.1、程序说明
在一般的情况下,C++将数组名解释为数组第一个元素的地址。因此下面的语句将pw声明为指向double类型的指针。
double* pw=wages;
存在下面等式:
wages=&wages[0]=第一个元素的地址
该程序在表达式&stacks[0]中显示地使用地址运算符来将ps指针初始化为stacks数组的第一个元素。
接着来分析pw和*pw的值:前者为地址,后者为存储在该地址的值。上面提到ps指针指向数组的第一个元素,所以*pw的值为10000。接着程序将pw加1,数字地址的值将加8,这使得pw的值为第二个元素的地址,因此*pw的值是20000。
现在来看一下数组表达式stacks[1]。C++编译器将该表达式看作是*(stacks+1),这意味着先计算数组第二个元素的地址,然后找到存储在那里的值。(运算符优先级要求使用括号,如果不使用括号,将给*stacks加1而不是stacks+1)
12.2、指针小结
12.2.1、声明指针
要声明指向特定类型的指针,请使用下列格式:
typeName * pointerName;
double *pn;
char * pc;
其中pn和pc都是指针,而double*和char*是指向double的指针和指向char的指针。
12.2.2、给指针赋值
可以对变量名使用&运算符,来获得被命名的内存的地址,new运算符返回未命名的内存的地址。
double * pn;
double * pa;
char *pc;
double bubble=1.6;
pn=&bubble;
pc=new char;
pa=new double[10];
12.2.3、对指针解除引用
对指针解除引用意味着获得指针指向的值。对指针应用解除引用或间接值运算符(*)来解除引用。就像上面的代码一样,pn是指向bubble的指针,而*pn是指向的值。
另一种对指针解除引用的方法是使用数组表示法,例如,pn[0]与*pn是一样的。
12.2.4、区分指针和指针指向的值
如果pt是指向int的指针,则*pt不是指向int的指针,而是完全等同于一个int类型的变量。
12.2.5、数组名
在大多情况下,C++将数组名视为数组的第一个元素的地址。
12.2.6、指针算术
在文章开头就介绍过了,这里再总结一下:C++允许将指针和整数相加。加1的结果等于原来的地址值加上指向的对象占用的总字节数。还可以一个指针将去另一个指针,获得两个指针的差。后一种运算将得到一个整数,仅当两个指针指向同一个数组才有效,得到的是两个元素的间隔。
12.2.7、数组的动态联编和静态联编
使用数组声明来创建数组时,将采用静态联编,即数组的长度在编译时设置:
int tacos[10];
使用new[ ]运算符来创建数组时,将采用动态联编,即将在运行时为数组分配空间,其长度也将在运行时设置。使用完这些数组后,应使用delete[ ]释放其占用的内存:
int size;
cin>>size;
int *pz=new int [size];
…
delete [] pz;
12.2.8、数组表示法和指针表示法
使用方括号数组表示法等同于对指针解除引用,细节看上面12.1所写。
12.3、指针和字符串
数组和指针的特殊关系可以拓展到C风格字符串。
char flower[10]="rose";
cout<<flower<<"s are red"<<endl;
数组名是第一个元素的地址,因此cout语句中的flower是包含字符r的char元素的地址。cout对象认为char的地址是字符串的地址,因此它打印该地址处的字符,然后打印后面的字符,直到遇到空字符(\0)为止。如果给cout提供一个字符的地址,则它将从该字符开始打印,直到遇到空字符。
这里关键不在于flower是数组名,而在于flower是一个char的地址,这意味着可以将指向char的指针变量作为cout的参数,因为它也是char的地址。
如果flower是字符串第一个字符的地址,则后面的表达式"s are red"是什么呢?为了与cout对字符串输出的处理保持一致,这个用引号括起来的字符串也应当是一个地址。在C++中,用引号括起来的字符串像数组名一样,也是第一个元素的地址。
注:在cout和多数C++表达式中,char数组名、char指针以及用引号括起来的字符串常量都被解释为字符串第一个字符的地址。
下面给一段程序,使用了两个函数,函数strlen()(返回字符串的长度)和函数strcpy()(将字符串从一个位置复制到另一个位置),两个都位于头文件cstring里。
int main()
{
using namespace std;
char animal[20] = "bear";
const char* bird = "wren";
char* ps;
cout << animal << " and "
<< bird << endl;
cout << "Enter a kind of animal: ";
cin >> animal;
ps = animal;
cout << ps << "!" << endl;
cout << "Before using strcpy(): " << endl;
cout << animal << " at " << (int*)animal << endl;
cout << ps << " at " << (int*)ps << endl;
ps = new char[strlen(animal) + 1];
strcpy(ps, animal);
cout << "After using strcpy(): " << endl;
cout << animal << " at " << (int*)animal << endl;
cout << ps << " at " << (int*)ps << endl;
return 0;
}
程序说明
程序创建了一个char数组(animal)和两个指向char的指针变量(bird和ps)。该程序首先将animal数组初始化为字符串"bear",就像初始化数组一样。然后将char指针初始化为指向一个字符串:
const char* bird="wren";
”wren“实际表示的是字符串的地址,因此这条语句将”wren”的地址赋给bird指针。字符串字面值是常量,以这种方式使用const意味着可以用bird来访问字符串,但不能修改它。
对于cout来说,使用数组名和指针bird是一样的,毕竟两个都是字符串的地址,cout将显示存储在这两个地址上的两个字符串。
ps = animal;
cout << ps << "!" << endl;
cout << "Before using strcpy(): " << endl;
cout << animal << " at " << (int*)animal << endl;
cout << ps << " at " << (int*)ps << endl;
在这段代码里,一般来说,如果给cout一个指针,它将打印地址。如果指针的类型是char*,则cout将显示指向的字符串。如果要显示的是字符串的地址,则必须将这种指针强制转换为另一种指针类型,如int*。因此ps显示的是字符串"fox",而(int *)ps显示为该字符串的地址。
注:将animal赋给ps并不会复制字符串,而只是赋值地址。这样,这两个指针将指向相同的内存单元和字符串。
要获得字符串的副本,还要做其他工作:首先需要分配内存来存储该字符串,这可以通过声明另一个数组或new来完成。后一种方法是的能够根据字符串的长度来指定所需空间:
ps = new char[strlen(animal) + 1];
字符串“fox”不能填满整个animal数组,因此这样做浪费了空间。上述代码使用strlen()来确定字符串的长度,并将它加1来获得包含空字符时该字符串的长度。随后程序使用new来分配刚好足够存储该字符串的空间。
接下来需要将animal数组中的字符串复制到新分配的空间中。将animal赋给ps是不行的!!因为这样只能修改存储在ps中的地址,从而失去程序访问新分配空间的唯一途径。需要使用库函数strcpy():
strcpy(ps, animal);
strcpy()函数接受两个参数,第一个目标地址,而第二个是要复制的字符串的地址。
注:应使用strcpy(),而不是赋值运算符来将字符串赋给数组。
12.4、使用new创建动态结构
new既然能用于数组,也能用于结构。将new用于结构有两步组成:创建结构和访问其成员。例如,要创建一个未命名的inflatable类型,并将其地址赋给一个指针,可以这样做:
inflatable * ps=new inflatable;
比较棘手的是访问成员,创建动态结构时,不能将成员运算符句点用于结构名,因为这种结构没有名称,只知道它的地址。C++专门为这种情况提供了一个运算符:箭头成员运算符(->)。该运算符由连字符和大于号组成,可用于指向结构的指针,就像点运算符可用于结构名一样,后面会进行演示。
另一种访问成员的方法是:如果ps是指向结构的指针,则*ps就是结构本身。由于*ps是一个结构,因此(*ps).price是该结构的price成员。
#include <iostream>
struct inflatable
{
char name[20];
float volume;
double price;
};
int main()
{
using namespace std;
inflatable* ps = new inflatable;
cout << "Enter name of inflatable item: ";
cin.get(ps->name,20);
cout << "Enter volume in cubic feet: ";
cin >> (*ps).volume;
cout << "Enter price:$";
cin >> ps->price;
cout << "Name: " << (*ps).name << endl;
cout << "Volume: " << ps->volume << " cubic feet" << endl;
cout << "Price:$" << ps->price << endl;
delete ps;
return 0;
}
12.4.1、一个使用new和delete的实例
下面给一段程序,该程序定义了一个函数getname(),该函数返回一个指向输入字符串的指针。该函数将输入读入一个大型的临时数组,然后使用new[ ]创建一个刚好能够存储输入字符串的内存块,并返回一个指向该内存块的指针。可以省略大量的内存。
#include <iostream>
#include <cstring>
using namespace std;
char* getname(void);
char* getname()
{
char temp[80];
cout << "Enter last name: ";
cin >> temp;
char* pn = new char[strlen(temp) + 1];
strcpy(pn, temp);
return pn;
}
int main()
{
char* name;
name = getname();
cout << name << " at " << (int*)name << endl;
delete[]name;
name = getname();
cout << name << " at " << (int*)name << endl;
delete[]name;
return 0;
}
程序说明:
getname()函数使用cin将输入的单词放到temp数组中,然后使用new分配新内存。获得空间后,getname()使用strcpy()将temp中的字符串复制到新的内存块中,然后返回指针pn。
在main()中,返回值被赋给指针name,它指向getname()函数中分配的内存块。然后程序打印该字符串及其地址。
12.5、自动存储、静态存储和动态存储
12.5.1、自动存储
在函数内部定义的常规变量使用自动存储空间,被称为自动变量。这意味这它所属的函数被调用时自动生成,在该函数结束时消亡。例如上面程序的temp数组。
实际上,自动变量是一个局部变量,其作用域为包含它的代码块(指被包含在方括号中的一段代码)。自动变量通常存储在栈中。这意味这执行代码块时,其中的变量将依次加入到栈中,而在离开代码时将按相反的顺序释放这些变量。(可以看看我前面关于栈的文章)
12.5.2、静态存储
静态存储是整个程序执行期间都存在的存储方式。使变量变为静态的方式有两种:一是在函数外面定义它;另一种是在声明函数时使用关键字stastic。
12.5.3、动态存储
new和delete运算符提供了一个比前面两个存储更灵活的方法,它们管理一个内存池,在C++中称为自由存储空间或堆。该内存池时与前面两个内存是分开的,不受空间限制,譬如在不同的函数间使用。