数组
数组也是存储单一数据类型对象的容器,与vector相比,它的长度固定,也没有获取长度的操作。现代C++多用vector代替数组。
数组的维数必须用常量表达式定义。非const变量以及运行阶段才能知道值的const变量都不能定义数组的维数。例如:
const unsigned buf_size=512,max_files=50;
int staff_size=27;
const unsigned sz=get_size(); //直到运行get_size()才能获得sz的值
char buffer[buf_size]; //OK
string fileTable[max_file+1]; //OK
double salaries[staff_size]; //NO
int score[get_size()]; //NO,非常量表达式
int val[sz]; //NO,sz直到运行时才能知道
初始化时可以进行显示初始化,例如:
const unsigned sz=3;
int ia[]={0,1,2,4}; //默认长度为4
int ib[sz]={1,2,3};
sting ic[sz]={“hi”,”boy”}; //默认为{“hi”,”boy”,””}
如果没有显示初始化,那么数组元素会像普通变量一样初始化:在函数体外,元素默认为0;在函数体内,无初始化;如果元素类型为类类型,无论数组在什么位置,元素都会自动调用默认构造函数进行初始化,如果类没有默认构造函数,那么就必须显示初始化。
字符数组即可以用用花括号进行初始化,还可以用字符串字面值。
char ca1[]={‘C’,’+’,’+’}; //长度为3
char ca2[]={‘C’,’+’,’+’,’\0’}; //长度为4
char ca3[]=“C++”; //长度为4
char ca4[3]=“C++”; //错误
与vector不同,数组不能用别的数组初始化,也不能将一个数组赋值给另外一个数组。
int ia[]={1,2,3};
int ib[]={0,1,2};
ib=ia; //错误
用下标访问元素时,vector使用vector<>::size_type作为下标的类型,而数组的下标类型为size_t。
int main(){
const size_t array_size=10;
int arr[array_size];
for(size_t ix=0;ix!=array_size;++ix)
return 0;
}
指针
vector的遍历可以用下标或者迭代器实现,而数组的遍历可以用下标或者指针实现。指针用于指向对象,与迭代器一样,提供对所致的对象的间接访问,只是指针结构更通用一些。
与迭代器不同的是,指针指向单个对象,迭代器只能访问容器里面的元素。
指针保存的是对象的地址。每个指针都有一个与之关联的数据类型,这个类型决定了指针所指向对象的类型。
vector<int> *vec; //vec指向了一个vector<int>类型的数据。
int* ip; //ip指向了一个int型数据
string* sp; //sp指向了一个string类型的数据。
double da,*db; //da是double型,db是double型指针
一个有效的指针必然是以下三个状态之一:保存一个特定对象的地址、指向某个对象后面的另一个对象、0值。没有初始化的指针是无效的,直到指针赋值以后才能够使用它。
int ival=1204;
int *pi=0; //pi赋给了0值,等价于pi=NULL,有效。
int *p1=&ival; //有效
int *p2; //没有初始化
int *p3=p1; //有效
p3=0; //现在p3不指向任何对象
避免使用没有初始化的指针。指针的赋值情况有:0值常量(0或者0值的const量)、类型匹配的对象的地址、另一对象之后的下一个地址、同类型的另一个有效地址。
void*指针可以保存任何对象的地址。void*表明该指针与一地址值相关,但不清楚存储在该地址上的对象的类型。void*指针只支持几种有限的操作:与另一指针进行比较、像函数传递void*指针、函数返回void*指针、给另一个void*指针赋值。不允许使用void*指针操纵它所指向的对象。
可以使用指针访问数组元素。
int ia[]={0,2,4,6,8};
int *ip=ia;
ip=&ia[4];
int *ip2=ia+2;
std::ptrdiff_t=ip2-ip; //两个指针之间的距离:-2
使用下标访问数组时,只要下标操作指向的是数组元素就可以进行。
int ia[]={0,2,4,6,8};
int *p=&ia[2];
int j=p[1]; //ok,p[1]等价于*(p+1), *(ia+3),ia[3]
int k=p[-2]; //ok,p[-2]即为ia[0]
指针是数组的迭代器。以下pend指针超出了数组的范围,它不能进行引用运算,但是可以进行比较。这个循环非常类似于vector中的迭代器的操作:for(vector<int>::iterator iter=vec.begin;iter!=vec.end();iter++)。
const size_t sz=5;
int arr[sz]={0,2,4,6,8};
for(int *pbegin=arr,*pend=arr+sz;pbegin!=pend;pbegin++)
cout<<*pbegin<<endl;
const与指针限定符有两种形式:const double *ptr和double * const ptr。
第一种的含义是不可用指针修改对象的内容。但是可以改变指针本身的内容,即指针可以赋给一个新的地址。如果所指对象不是const类型,那么不能通过指针修改这个对象,但可以用其它途径修改。如果指针所指内容是const,那么这个指针必须声明成第一种形式,否则不能通过编译。
const double pi=3.14;
double *ptr=π //error,ptr is a plain pointer
const double *ptr=π //ok,a pointer to const
const void *pv=π //ok
void * ppv=π //error
double dal=3.14;
const *dptr=&dal; //ok,but cannot change dal through dptr
*dptr=3; //error
dptr=ptr; //ok
第二种是const指针,它的值不能改变,即不能再使它指向其它对象。
int a=5;
int * const pa=&a;
pa=pa; //error
*pa=6; //ok
还可以定义不可用指针修改所指内容的const对象。
int a=9;
const int* const pa=&a;
typedef中使用指针往往带来意外的结果。例如:
typedef string *pstring; //将指向string的指针定义为pstring
const pstring cstr;
那么这里定义的cstr是什么类型?非常容易误解为const string* cstr,事实上,const修饰的是cstr,
这个定义等价于string * const cstr。与string const s等价于const string s一样,这个语句也定价于pstring constcstr。
c风格字符串
以下中,cst1和cst2外,其余都是c风格字符串。
char cst1[]={‘C’,’+’,’+’};
char *cst2=cst1;
char cst3[]={‘C’,’+’,’+’,’\0’};
char *cst4=cst3;
char *cst5=”C++”;
char cst6[]=”C++”;
const char *cst7=”C++”;
事实上,c风格字符串的类型为const char *类型。
char *cp="C++";
cp[1]='-'; //错误,内容是不可改变的
char cal[]={'C','+','+'};
cp=cal;
cp[1]='-'; //正确
C++中用const char*类型来处理C风格字符串。可以用如下方式遍历字符串:
const char *cp=”what a great day!”;
while(*cp)
{do sth whith *cp; cp++;}
处理C风格字符串的头文件为<cstring>。传入库中函数的指针必须非空且指向以null结束的字符串中。char ca[]={‘C’,’+’,’+’};调用strlen(ca)将发生错误。
库函数有:strlen(s)——s的长度,不包含null;strcmp(s1,s2)——比较s1与s2的大小,s1大于s2时返回正数;strcat(s1,s2)——s2链接到s1并返回s1;strcpy(s1,s2)——s2复制到s1并且返回s1;strncat(s1,s2,n)——s2的前n个连接到s1并返回s1,注意要包含null;strncpy(s1,s2,n)——s2的前n个复制到s1并且返回s1。当然C++中提供简单的字符串比较方式:p1>p2。
尽管C++支持C风格字符串,但是应避免使用这种类型。
创建动态数组
虽然数组的长度是固定的,但是动态分配的数组不必在编译时知道其长度,可以在运行时才确定其长度。用于动态分配的对象的存储空间称为自由存储区或者堆。
int *pia=new int[10]; //new分配了含有10个int的数组,并将第一个指针返回
delete[] pia; //将分配的内存返回给自由存储区
动态分配数组时,如果是类类型,数组元素调用默认构造函数,如果是内置类型,不初始化,但可以添加括号进行默认初始化,int *p=new int[10]()。
动态数组是允许运行时才确定长度的。
size_t n=getSize(); //即使n=0,任然可以运行,new返回一个非0指针
int *p=new int[n];
for(int* q=p;q!=p+n;q++)
多维数组
C++中没有多维数组,多维数组指的是数组的数组。int ia[3][4]={{1,2,3,4},{5,6,7,8},{9}}
多维数组名是一个指针,它指向了一个内层数组。int (*ip)[4],ip是一个指针,指向了int[4]。
int ia[3][4];
typedef int arr[4];//arr是int[4]类型,即arr是int *const
arr *p=ia; //等价于int (*p)[4]=ia;p指向的数据是int *const,
//那么p[0]不能被赋值,而p可以
arr m=&ia[2]; //error,等价于int m[4]=ia[2];m被赋值不能
p[0]=ia[0]; //error,p和ia是指向同一对象,但是p[0]不能赋值