c++语言提供了两种类似于vector和迭代器类型的低级符合类型——数组和指针。与vector类似,数组也可以保持某种类型的一种对象,而他们的区别在于,数组的长度是固定的,数组一经创建,就不允许添加新的元素,指针则可以像迭代器一样遍历和检查数组中的元素。
现代c++程序应尽量使用vector和迭代器两种类型,而避免使用低级的数组和指针,设计良好的程序只有在强调速度时才在类内部使用数组和指针!
数组是C++中类似于标准库vector的内置数据结构。与vector类似,数组也是一种存储单一数据类型对象的容器,其中每个对象都没有单独的名字,而是通过它在数组中的位置来访问。
与vector类型相比,数组的显著缺陷在于:数组的长度是固定不可变的,而且程序员无法直接知道给定数组的长度,因为数组没有获取其容量大小的size操作,也不提供push_back()操作,如果需要更改数组长度,程序员只能创建一个更大的新数组,然后把原数组的所有元素复制到新数组的空间中去。
与使用标准vector类型的程序相比,依赖于内置数组的程序更容易出错而且难于调试。
然而,在将来一段时间内,原来依赖于数组的程序仍大量存在,因此,c++程序员还是必须掌握数组的使用方法。
至于数组和指针的具体使用,与C语言中的使用方法大相径同。
(一)数组
数组的定义和初始化
数组的维数必须用>=1的常量来表示,此常量只能包含整型字面值常量、枚举常量或用const限定的整型常量。非const常量以及要到运行阶段才能知道其值的变量都不能用于定义数组的维数。
const int buffer=512,max_files=20;
int staff_size=27;
const unsigned sz=get_size(); //get_size为一返回值为整型的函数
char intput_buffer[buffer]; //0k;
string fileTable[max_files+1]; //ok;max_files为常量表达式,编译时即可计算出max_files+1的值
double salaries[staff_size]; //error! 运行时才能知道其值!
int test_scores[get_size()] //error! 运行时才能知道其值!
int vals[sz]; //error!
1.显式初始化数组元素
const unsigned array_size=3;
int ia[array_size]={0,1,2};
int ia[]={0,1,2}; //编译器在编译时会根据元素个数来确定数组大小,此用法为正确用法!
注意一点:
如果没有显式提供元素初值,则数组元素会像普通变量一样初始化:
*在函数体外定义的内置数组,其元素初始化为0;
*在函数体内定义的内置数组,其元素未初始化;
*无论在哪里定义的类类型的数组,均会自动调用该类的默认构造函数来进行初始化,如果该类没有默认构造函数,则必须为该数组的元素提供显式初始化。
#include<string>
using std::string;
int array[10]; //数组中每个元素初始化为0;
char str[10]; //数组初始化为空串;
string str1[10]; //数组中每个元素初始化为空串;
int main()
{
int array1[10]; //数组未初始化;
char str2[10]; //数组未初始化;
string str3[10]; //数组中每个元素初始化为空串;
return 0;
}
2.不允许数组直接复制和赋值
与vector不同,一个数组不能用另外一个数组初始化,也不能将一个数组赋值给另外一个数组,这些操作都是非法的。
int ia[]={0,1,2};
int ia2[](ia) //error!
int main()
{
const unsigned array_size=3;
int ia3[array_size]; //ok;
ia3=ia //error!
return 0;
}
3.数组操作
与vector一样,数组元素可用下标来访问,在用下标来访问时,vector使用vector<T>::size_type 来访问,而数组下标的正确形式为size_t;
int main()
{
const size_t array_size=10;
int ia[array_size];
for(size_t ix=0;ix!=array_size;ix++)
{
ia[ix]=ix;
}
return 0;
}
(二)指针
存放变量地址的变量,其本身也是变量,使用*操作符可获得该地址所对应的内存中的实际值。
int num=1024;
int* pi=#
cout<<pi<<endl; //输出num的地址
cout<<*pi<<endl; //输出num的实际值1024;
1.指针声明的两种风格:
string *str; //符号*紧挨着变量名放置,强调声明的对象是一个指针
string* str; //符号*紧挨着类型名放置,强调这个声明语句定义的是一个指针
但要注意下面情况:
string* str,str1; //str为一个指针变量,str1为一个一般变量
string* str,*str1; //str1和str均为指针变量
两种风格不能说哪个是错的,但重要的是选择一种并坚持使用!
2.避免使用未初始化的指针
使用未初始化的指针总会导致运行时的崩溃,而导致这样崩溃的原因总是很难被发现。对大多数的编译器来说,如果使用未初始化的指针,会将指针中存放的不确定值视为地址,然后操作该内存地址中存放的位内容。使用未初始化的指针相当于操作这个不确定地址中存储的基础数据。因此,在对未初始化的指针进行解引用时,通常会导致程序崩溃!
由于C++语法无法检测指针是否初始化,建议程序员在使用之前初始化所有变量,尤其是指针!
3.void* 型指针
C++提供了一种特殊的指针类型void*,它可以保存任何类型对象的地址;
double obj=3.14;
double* pd=&obj;
void* pv=&obj; //事实上,obj可以是任何类型的指针
pv=pd; //pd可以使任何类型的指针
void*指针只支持几种有限的操作:
a.与另一个指针进行比较;
b.向函数值传递void*指针或从函数返回void*指针;
c.给另一个void*指针进行赋值。
4.指向指针的指针
c++使用**操作符指派了一个指针指向另一个指针。
int ival=1024;
int *pi=&ival;
int **ppi=π
cout<<&ival<<endl; //输出ival的地址
cout<<pi<<endl; //输出pi中的内容,实质上为ival的地址
cout<<ppi<<endl; //输出ppi中的内容,实际为pi的地址
cout<<*ppi<<endl; //输出ppi所指向的对象pi中的内容,实质上位ival的地址
cout<<**ppi<<endl; //输出的结果为1024;
事实上,ppi指向的是pi,对ppi进行解引用操作*ppi则获得pi中的内容,即ival的地址,再次进行解引用操作**ppi,则获得ival的实际值1024。
5.指针与const限定符
指向const对象的指针,const指针,和指向const对象的const指针的区别:
#include<iostream>
using std::cout;
using std::endl;
int main()
{
int num=1024;
const int* pi=#
cout<<*pi<<endl;
// *pi=1200; //error!指向const对象的指针不允许通过解引用操作符来修改其所指向的对象的值
int num1=1200;
pi=&num1; //ok!指向const对象的指针允许通过改变其地址来改变指针所指向的对象
int *const ppi=#
cout<<*ppi<<endl;
// ppi=&num1; //error!const指针的值不能修改!
*ppi=1200; //ok!可以修改const指针所指向的对象的值
const int const* pppi=#
// pppi=&num1; *pppi=1200; //error!两种用法均错误,指向const对象的指针既不允许改变指针的值,
//也不允许改变针所指向的对象的值!
return 0;
}