c++数组类型

2011-08-18 15:17:27|  分类: c++ |  标签: |字号 订阅

数组是一种单一数据类型的集合,数组中的元素可以通过索引访问(下标访问):

int a[10]; //定义了一个数组,但是数组元素还没有初始化

定义数组维数时,必须是常量或常量表达式,变量不能作为数组维数来定义数组,数组的维数必须是大于等于1的正整数:

const int a = 10;

int b = 5;

int a_array[a];  //正确

int b_array[b];  //错误,变量不能定义数组维数

int c_array[10]; //正确

int d_array[a*5 -10]; //正确

int e_array[a - 10]; //错误,数组维数小于1

数组可以显式地用一组数据来初始化,数据用逗号隔开,初始化列表中的元素个数不能超过数组维数,如果初始化列表中的元素个数少于数组维数,那么其余的元素将被初始化为默认值,被显示初始化的数组不需要指定维数:

int a_array[10] = {0,1,2,3,4,5,6,7,8,9};//正确

int b_array[10]= {0,1,2,3,4,5,6,7,8,9,10}; //错误,初始化列表中元素个数超出了数组的维数

int c_array[10]={0,1,2,3,4,5}; //正确,其余的数组元素将被初始化为0

int d_array[]={0,1,2,3,4};  //正确,数组的维数为5

字符串数组初始化有下面两种方式,但他们不是等价的:

char ca1={'C','+','+'};  //数组维数为3

char ca2 = "C++";  //数组维数为4,因为还有一个字符串结束符号'\0'

一个数组不能被另一个数组初始化,也不能赋值给另一个数组。而且,c++中不允许定义引用数组:

int a,b,c;

int *d []= {&a,&b,&c};  //指针数组

int &e[] = {a,b,c}; //错误,不能定义引用数组

int a_array []= {0,1,2,3};

int b_array[] = a_array;  //错误,不能用另一个数组初始化

在c++中多维数组的索引访问要求想要访问的数组元素的每个索引都有一个方括号。

下面是一个简单的模板定义,这个模板定义了一个循环打印一个任意类型的数组中的所有数组元素的方法,pbegin为数组的第一个数组元素的地址,pend为数组的最后一个数据元素的下一个地址:

template <class elemType> void print(elemType *pbegin,elemType *pend){

            while(pbegin != pend){

               cout << *pbegin<< "  ";

               pbegin++;

                }

}


利用指针的指针建立任意二维数组:

float **vertex; //float类型的指针的指针

void initTwoDimensionArray(){

vertex = new float*[3]; //二维数组的一维维数为3
 vertex[0] = new float[18];  //vertex[0]的维数为18
 for (int i = 0; i < 18; i++) {
  vertex[0][i] = 1.0f + rand();
 }
 vertex[1] = new float[12];//vertex[1]的维数为12
 for (int i = 0; i < 18; i++) {
  vertex[1][i] =  2.0f + rand();

 }
 vertex[2] = new float[6];//vertex[2]的维数为6
 for (int i = 0; i < 12; i++) {
  vertex[2][i] = 3.0f + rand();

 }

}

与 vector 类型相比,数组的显著缺陷在于:数组的长度是固定的,而且程序员无法知道一个给定数组的长度。数组没有获取其容量大小的 size 操作,也不提供 push_back 操作在其中自动添加元素。如果需要更改数组的长度,程序
员只能创建一个更大的新数组,然后把原数组的所有元素复制到新数组空间中去。

数组
数组是由类型名、标识符和维数组成的复合数据类型,类型名规定了存放在数组中的元素的类型,而维数则指定数组中包含的元素个数。数组定义中的类型名可以是内置数据类型或类类型;除引用之外,数组元素的类型还可以是任意的复合类型。没有所有元素都是引用的数组。

 

数组的定义和初始化
数组的维数必须用值大于等于 1 的常量表达式定义。此常量表达式只能包含整型字面值常量、枚举常量或者用常量表达式初始化的整型 const 对象。非 const 变量以及要到运行阶段才知道其值的 const变量都不能用于定义数组的维。
数组的维数必须在一对方括号 [] 内指定:

const unsigned buf_size = 512, max_files = 20;      // both buf_size and max_files are const
int staff_size = 27;                                                   // nonconst
const unsigned sz = get_size();                              // const value not known until run time
char input_buffer[buf_size];                                   // ok: const variable
string fileTable[max_files + 1];                              // ok: constant expression
double salaries[staff_size];                                  // error: non const variable
int test_scores[get_size()];                                 // error: non const expression
int vals[sz];                                                        // error: size not known until run time

 

显式初始化数组元素
在定义数组时,可为其元素提供一组用逗号分隔的初值,这些初值用花括号{}括起来,称为初始化列表:
const unsigned array_size = 3;
int ia[array_size] = {0, 1, 2};

如果没有显式提供元素初值,则数组元素会像普通变量一样初始化:

? 在函数体外定义的内置数组,其元素均初始化为 0。
? 在函数体内定义的内置数组,其元素无初始化。
? 不管数组在哪里定义,如果其元素为类类型,则自动调用该类的默认构造函数进行初始化;如果该类没有默认构造函数,则必须为该数组的元素提供显式初始化。
除非显式地提供元素初值,否则内置类型的局部数组的元素没有初始化。此时,除了给元素赋值外,其他使用这些元素的操作没有定义。
显式初始化的数组不需要指定数组的维数值,编译器会根据列出的元素个数来确定数组的长度:
int ia[] = {0, 1, 2}; // an array of dimension 3

如果指定了数组维数,那么初始化列表提供的元素个数不能超过维数值。如果维数大于列出的元素初值个数,则只初始化前面的数组元素;剩下的其他元素,若是内置类型则初始化为0,若是类类型则调用该类的默认构造函数进行初始化:
const unsigned size = 5;
int ia[size] = {0, 1, 2};                  // Equivalent to int ia[size] = {0, 1, 2, 0, 0} ,

                                                  //ia[3] and ia[4] are default initialized to 0


string str_arr[size] = {"hi", "bye"};     // Equivalent to string str_arr[size] = {"hi", "bye", "", "", ""}
                                                       // str_arr[2] through str_arr[4] default initialized to the empty string      

 

特殊的字符数组
字符数组既可以用一组由花括号括起来,逗号隔开的字符字面值进行初始化,也可以用一个字符串字面值进行初始化。然而,要注意这两种初始化形式并不完全相同,字符串字面值包含一个额外的空字符(null)用于结束字符串。当使用字符串字面值来初始化创建的新数组时,将在新数组中加入空字符:
char ca1[] = {'C', '+', '+'};                   // not have null charactor
char ca2[] = {'C', '+', '+', '\0'};            // explicitly add a null charactor
char ca3[] = "C++";                         // null charactor added automatically

const char ch3[6] = "Daniel";          // error: Daniel is 7 elements

不允许数组直接复制和赋值.与 vector 不同,一个数组不能用另外一个数组初始化,也不能将一个数组赋值给另一个数组,这些操作都是非法的。

 

数组操作

在用下标访问元素时,vector 使用 vector::size_type 作为下标的类型,而数组下标的正确类型则是 size_t。

在下面的例子中,for 循环遍历数组的 10 个元素,并以其下标值作为各个元素的初始值:
int main()
{
const size_t array_size = 10;
int ia[array_size];                        // 10 ints, elements are uninitialized
for (size_t ix = 0; ix != array_size; ++ix)      // loop through array, assigning value of its index to each element
ia[ix] = ix;
return 0;
}

 

——————————————————————————————————

指针的引入

指针保存的是另一个对象的地址:
string s("hello world");
string *sp = &s; // sp holds the address of s

 

指针的定义和初始化

每个指针都有一个与之关联的数据类型,该数据类型决定了指针所指向的对象的类型。例如,一个 int 型指针只能指向 int 型对象。

C++ 语言使用 * 符号把一个标识符声明为指针:
vector<int> *pvec;                // pvec can point to a vector<int>
int *ip1, *ip2;                        // ip1 and ip2 can point to an int
string *pstring;                     // pstring can point to a string
double *dp;                         // dp can point to a double

一个有效的指针必然是以下三种状态之一:保存一个特定对象的地址;指向某个对象后面的另一对象;或者是0 值。若指针保存0 值,表明它不指向任何对象。未初始化的指针是无效的,直到给该指针赋值后,才可使用它。下列定义和赋值都是合法的:
int ival = 1024;
int *pi = 0;                       // pi initialized to address no object
int *pi2 = & ival;             // pi2 initialized to address of ival
int *pi3;                         // ok, but dangerous, pi3 is uninitialized
pi = pi2;                       // pi and pi2 address the same object: ival
pi2 = 0;                       // pi2 now points no object

避免使用未初始化的指针。如果必须分开定义指针和其所指向的对象,则将指针初始化为 0。因为编译器可检测出 0 值的指针,程序可判断该指针并未指向一个对象。

对指针进行初始化或赋值只能使用以下四种类型的值:
1. 0 值常量表达式,例如,在编译时可获得 0 值的整型 const对象或字面值常量 0。
2. 类型匹配的对象的地址。
3. 另一对象末的下一地址。
4. 同类型的另一个有效指针。

把 int 型变量赋给指针是非法的,尽管此 int 型变量的值可能为 0。但允许把数值 0 或在编译时可获得 0 值的 const 量赋给指针:
int ival;
int zero = 0;
const int c_ival = 0;
int *pi = ival;                 // error: pi initialized from int value of ival
pi = zero;                     // error: pi assigned int value of zero
pi = c_ival;                  // ok: c_ival is a const with compile-time value of 0
pi = 0;                        // ok: directly initialize to literal constant 0
除了使用数值0 或在编译时值为 0 的 const 量外,还可以使用 C++ 语言从 C 语言中继承下来的预处理器变量 NULL,该变量在 cstdlib头文件中定义,其值为 0。如果在代码中使用了这个预处理器变量,则编译时会自动被数值 0 替换。因此,把指针初始化为 NULL 等效于初始化为 0 值:
int *pi = NULL;            // ok: equivalent to int *pi = 0;

 

指针只能初始化或赋值为同类型的变量地址或另一指针:
double dval;
double *pd = &dval;                // ok: initializer is address of a double
double *pd2 = pd;                  // ok: initializer is a pointer to double
int *pi = pd;                           // error: type of pi and pd is different
pi = &dval;                            // error: attempt to assign address of a double to int *

 

void* 指针

C++ 提供了一种特殊的指针类型 void*,它可以保存任何类型对象的地址:

double obj = 3.14;
double *pd = &obj;

void *pv = &obj;          // obj can be an object of any type
pv = pd;                     // pd can be a pointer to any type

void* 表明该指针与一地址值相关,但不清楚存储在此地址上的对象的类型。void* 指针只支持几种有限的操作:与另一个指针进行比较;向函数传递void* 指针或从函数返回 void* 指针;给另一个 void* 指针赋值。不允许使用void* 指针操纵它所指向的对象。

 

指针操作

对指针进行解引用可访问它所指的对象,* 操作符(解引用操作符)将获取指针所指的对象:

string s("hello world");
string *sp = &s;                // sp holds the address of s
cout <<*sp;                     // prints hello world

解引用操作符返回指定对象的左值,利用这个功能可修改指针所指对象的值:
*sp = "goodbye"; // contents of s now changed
因为 sp 指向 s,所以给 *sp 赋值也就修改了 s 的值。也可以修改指针 sp 本身的值,使 sp 指向另外一个新对象:
string s2 = "some value";
sp = &s2; // sp now points to s2

 

指针和引用的比较
虽然使用引用(reference)和指针都可间接访问另一个值,但它们之间有两个重要区别。第一个区别在于引用总是指向某个对象:定义引用时没有初始化是错误的。第二个重要区别则是赋值行为的差异:给引用赋值修改的是该引用所
关联的对象的值,而并不是使引用与另一个对象关联。引用一经初始化,就始终指向同一个特定对象(这就是为什么引用必须在定义时初始化的原因)。考虑以下两个程序段。第一个程序段将一个指针赋给另一指针:
int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2;                // pi now points to ival2
赋值结束后,pi 所指向的 ival 对象值保持不变,赋值操作修改了 pi 指针的值,使其指向另一个不同的对象。现在考虑另一段相似的程序,使用两个引用赋值:
int &ri = ival, &ri2 = ival2;
ri = ri2; // assigns ival2 to ival
这个赋值操作修改了 ri 引用的 ival 对象,而并非引用本身。赋值后,这两个引用还是分别指向原来关联的对象,此时这两个对象的值相等。

 

指向指针的指针

指针本身也是可用指针指向的内存对象。指针占用内存空间存放其值,因此指针的存储地址可存放在指针中。下面程序段:
int ival = 1024;
int *pi = &ival;           // pi points to an int
int **ppi = &pi;          // ppi points to a pointer to int
定义了指向指针的指针。C++ 使用 ** 操作符指派一个指针指向另一指针。这些对象可表示为:
对 ppi 进行解引用照常获得 ppi 所指的对象,在本例中,所获得的对象是指向 int 型变量的指针 pi:
int *pi2 = *ppi;            // ppi points to a pointer

 

使用指针访问数组元素
C++ 语言中,指针和数组密切相关。特别是在表达式中使用数组名时,该名字会自动转换为指向数组第一个元素的指针:
int ia[] = {0,2,4,6,8};
int *ip = ia;                 // ip points to ia[0]
如果希望使指针指向数组中的另一个元素,则可使用下标操作符给某个元素定位,然后用取地址操作符 & 获取该元素的存储地址:
ip = &ia[4]; // ip points to the last element in ia

 

指针的算术操作

通常,在指针上加上(或减去)一个整型数值 n 等效于获得一个新指针,该新指针指向指针原来指向的元素之后(或之前)的第 n 个元素。

如果一指针具有 0 值(空指针),则在该指针上加 0 仍然是合法的,结果得到另一个值为 0 的指针。也可以对两个空指针做减法操作,得到的结果仍是 0。

 

指针和const 限定符

指向const 对象的指针

到目前为止,我们使用指针来修改其所指对象的值。但是如果指针指向const 对象,则不允许用指针来改变其所指的 const 值。为了保证这个特性,C++ 语言强制要求指向 const 对象的指针也必须具有 const 特性:
const double *cptr;          // cptr may point to a double that is const
这里的 cptr 是一个指向 double 类型 const 对象的指针,const 限定了cptr 指针所指向的对象类型,而并非 cptr 本身。也就是说,cptr 本身并不是const。在定义时不需要对它进行初始化,如果需要的话,允许给 cptr 重新赋值,使其指向另一个 const 对象。但不能通过 cptr 修改其所指对象的值:
*cptr = 42;          // error: *cptr might be const

把一个 const 对象的地址赋给一个普通的、非 const 对象的指针也会导致编译时的错误:
const double pi = 3.14;
double *ptr = &pi;                      // error: ptr is a common pointer
const double *cptr = &pi;          // ok: cptr is a pointer to const

不能使用 void* 指针保存 const 对象的地址,而必须使用 const void* 类型的指针保存 const 对象的地址:
const int universe = 42;
const void *cpv = &universe;              // ok: cpv is const
void *pv = &universe;                         // error: universe is const
允许把非 const 对象的地址赋给指向 const 对象的指针,例如:
double dval = 3.14;                     // dval is a double; its value can be changed
cptr = &dval;                               // ok: but can't change dval through cptr

尽管 dval 不是 const 对象,但任何企图通过指针 cptr 修改其值的行为都会导致编译时的错误。cptr 一经定义,就不允许修改其所指对象的值。如果该指针恰好指向非 const 对象时,同样必须遵循这个规则。

在实际的程序中,指向 const 的指针常用作函数的形参。将形参定义为指向 const 的指针,以此确保传递给函数的实际对象在函数中不因为形参而被修改。

 

const 指针

除指向 const 对象的指针外,C++ 语言还提供了 const 指针——本身的值不能修改:
int errNumb = 0;
int *const curErr = &errNumb;      // curErr is a constant pointer
与其他 const 量一样,const 指针的值不能修改,这就意味着不能使 curErr 指向其他对象。任何企图给 const 指针赋值的行为(即使给curErr 赋回同样的值)都会导致编译时的错误:
curErr = curErr;                 // error: curErr is const
与任何 const 量一样,const 指针也必须在定义时初始化。

curErr 指向一个普通的非常量 int 型对象 errNumb,则可使用 curErr 修改该对象的值。如果curErr 指向一个常量 int 型对象 ,则不可以使用 curErr 修改该对象的值。

 

指向const 对象的 const 指针

还可以如下定义指向 const 对象的 const 指针:
const double pi = 3.14159;              

const double *const pi_ptr = &pi;        // pi_ptr is const and points to a const object
本例中,既不能修改 pi_ptr 所指向对象的值,也不允许修改该指针的指向(即不能给pi_ptr重新赋值)。

 

指针和 typedef

在 typedef中使用指针往往会带来意外的结果。下面是一个几乎所有人刚开始时都会答错的问题。假设给出以下语句:
typedef string *pstring;
const pstring cstr;
请问 cstr 变量是什么类型?简单的回答是 const pstring 类型的指针。进一步问:const pstring 指针所表示的真实类型是什么?很多人都认为真正的类型是:
const string *cstr; // wrong interpretation of const pstring cstr
也就是说,const pstring 是一种指针,指向 string 类型的 const 对象,但这是错误的。错误的原因在于将 typedef 当做文本扩展了。声明 const pstring 时,const 修饰的是 pstring 的类型,这是一个指针。因此,该声明语句应该是把cstr 定义为指向 string 类型对象的 const 指针,这个定义等价于:
string *const cstr; // equivalent to const pstring cstr
——————————————————————————————————

C 风格字符串

字符串字面值的类型就是const char 类型的数组。C++ 从 C 语言继承下来的一种通用结构是C 风格字符串,而字符串字面值就是该类型的实例。实际上,C 风格字符串既不能确切地归结为 C 语言的类型,也不能归结为 C++ 语言的类型,而是以空字符 null 结束的字符数组:
char ca1[] = {'C', '+', '+'};              // not have null charactor, not C-style string
char ca2[] = {'C', '+', '+', '\0'};       // explicitly add null charactor ,is C-style string
char ca3[] = "C++";                    // null charactor added automatically,is C-style string
const char *cp = "C++";             // null charactor added automatically,is C-style string
char *cp1 = ca1; // points to first element of a array, but not C-style string
char *cp2 = ca2; // points to first element of a null-charactor char array,is C-style string
ca1 和 cp1 都不是 C 风格字符串:ca1 是一个不带结束符 null 的字符数组,而指针 cp1 指向 ca1,因此,它指向的并不是以 null 结束的数组。其他的声明则都是 C 风格字符串,数组的名字即是指向该数组第一个元素的指针。于是,ca2 和 ca3 分别是指向各自数组第一个元素的指针。

 

C 风格字符串的使用
C++ 语言通过(const)char*类型的指针来操纵 C 风格字符串。

 

C 风格字符串的标准库函数

cstring 是 string.h 头文件的 C++ 版本,而 string.h 则是 C 语言提供的标准库。这些标准库函数不会检查其字符串参数。

C 风格字符串的标准库函数
strlen(s): 返回 s 的长度,不包括字符串结束符 null;
strcmp(s1, s2): 比较两个字符串 s1 和 s2 是否相同。若 s1 与 s2 相等,返回 0;若 s1 大于 s2,返回正数;若 s1 小于 s2,则返回负数;
strcat(s1, s2) :将字符串 s2 连接到 s1 后,并返回 s1;
strcpy(s1, s2): 将 s2 复制给 s1,并返回 s1;
strncat(s1,s2,n):将 s2 的前 n 个字符连接到 s1 后面,并返回 s1;
strncpy(s1,s2, n):将 s2 的前 n 个字符复制给 s1,并返回 s1;
传递给这些标准库函数例程的指针必须具有非零值,并且指向以 null 结束的字符数组中的第一个元素。其中一些标准库函数会修改传递给它的字符串,这些函数将假定它们所修改的字符串具有足够大的空间接收本函数新生成的字符,
程序员必须确保目标字符串必须足够大。

在使用处理 C 风格字符串的标准库函数时,牢记字符串必须以结束符 null结束。

————————————————————————————————————

创建动态数组

数组类型的变量有三个重要的限制:数组长度固定不变,在编译时必须知道其长度,数组只在定义它的块语句内存在。虽然数组长度是固定的,但动态分配的数组不必在编译时知道其长度,可以(通常也是)在运行时才确定数组长度。与数组变量不同,动态分配的数组将一直存在,直到程序显式释放它为止。

每一个程序在执行时都占用一块可用的内存空间,用于存放动态分配的对象,此内存空间称为程序的自由存储区或堆。C 语言程序使用一对标准库函数malloc 和 free 在自由存储区中分配存储空间,而 C++ 语言则使用 new 和delete 表达式实现相同的功能。

数组变量通过指定类型、数组名和维数来定义。而动态分配数组时,只需指定类型和数组长度,不必为数组对象命名,new 表达式返回指向新分配数组的第一个元素的指针:
int *pia = new int[10]; // array of 10 uninitialized ints

动态分配数组时,如果数组元素具有类类型,将使用该类的默认构造函数实现初始化;如果数组元素是内置类型,则无初始化:

string *psa = new string[10];            // array of 10 empty strings
int *pia = new int[10];                      // array of 10 uninitialized ints

也可使用跟在数组长度后面的一对空圆括号,对数组元素做值初始化:

int *pia2 = new int[10] ();          // array of 10 uninitialized ints
圆括号要求编译器对数组做值初始化,在本例中即把数组元素都设置为0。

对于动态分配的数组,其元素只能初始化为元素类型的默认值,而不能像数组变量一样,用初始化列表为数组元素提供各不相同的初值。

 

const 对象的动态数组

如果我们在自由存储区中创建的数组存储了内置类型的 const 对象,则必须为这个数组提供初始化:因为数组元素都是 const 对象,无法赋值。实现这个要求的唯一方法是对数组做值初始化:
const int *pci_bad = new const int[100];        // error: uninitialized const array
const int *pci_ok = new const int[100]();        // ok: value-initialized const array
C++ 允许定义类类型的 const 数组,但该类类型必须提供默认构造函数:
const string *pcs = new const string[100]; // ok: array of 100 empty strings
在这里,将使用 string 类的默认构造函数初始化数组元素。

 

允许动态分配数组

之所以要动态分配数组,往往是由于编译时并不知道数组的长度。我们可以编写如下代码
size_t n = get_size();              // get_size returns number of elements needed
int* p = new int[n];
for (int* q = p; q != p + n; ++q)
/* process the array */ ;
计算数组长度,然后创建和处理该数组。有趣的是,如果 get_size 返回 0 则会怎么样?答案是:代码仍然正确执
行。C++ 虽然不允许定义长度为 0 的数组变量,但明确指出,调用 new 动态创建长度为 0 的数组是合法的:
char arr[0];                                   // error: cannot define zero-length array
char *cp = new char[0];               // ok: but cp can't be dereferenced
用 new 动态创建长度为 0 的数组时,new 返回有效的非零指针。该指针与new 返回的其他指针不同,不能进行解引用操作,因为它毕竟没有指向任何元素。而允许的操作包括:比较运算,因此该指针能在循环中使用;在该指针上加(减)0;或者减去本身,得 0 值。

 

 

动态空间的释放

动态分配的内存最后必须进行释放,否则,内存最终将会逐渐耗尽。如果不再需要使用动态创建的数组,程序员必须显式地将其占用的存储空间返还给程序的自由存储区。C++ 语言为指针提供 delete [] 表达式释放指针所指向的数组
空间:
delete [] pia;
该语句回收了 pia 所指向的数组,把相应的内存返还给自由存储区。在关键字 delete 和指针之间的空方括号对是必不可少的:它告诉编译器该指针指向的是自由存储区中的数组,而并非单个对象。如果遗漏了空方括号对,这是一个编译器无法发现的错误,将导致程序在运行时出错。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值