您的位置:首页 > 编程语言 > C语言/C++

C++基础知识学习:数组

2016-06-15 00:00 447 查看
摘要: 数组是在C和C++中都存在的一种复合数据类型。本文讲述了一般数组与动态数组的定义和初始化问题等。

一、数组

1. 定义数组

数组是由类型名,标识符和维数组成的复合数据类型。其中,类型名可以是内置类型或类类型,除引用之外,数组元素的类型还可以是任意的复合类型。

数组的维数必须用大于等于1的常量表达式定义。在定义数组时,可为其元素提供一组用逗号分隔的初值,这些初值用花括号{}括起来,称为初始化列表:

[code=language-cpp]const unsigned array_size = 3;
int ia[array_size] = {0, 1, 2};

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

对于数组元素的显式初始化应当注意两点:

第一,显式初始化的数组不需指定数组维数值,例如

[code=language-cpp]int ia[] = {2, 22, 222};

第二,如果指定维数大于列出的元素初值数,对剩余元素来说,内置类型初始化为零,类类型调用默认构造函数初始化。例如

[code=language-cpp]double coords[3] = {}; // all elements default initialized to 0
int nums[5] = {0, 1, 2}; // nums[3] and nums[4] default initialized to 0
// lables[1] and labels[2] default initialized to empty string
string labels[3] = {"Hello"};

总结来看,内置类型的数组,在定义后要进行初始化,对于显式初始化方式,一旦指定了其维数可确保所有元素都会被初始化,即使初始化列表中指定的元素初值数不足。而对于类类型的数组来说,在定义时就会调用默认构造函数初始化,如果显式初始化,则调用相应构造函数进行初始化。例如下面自己写的一个简单类数组:

[code=language-cpp]#include
using namespace std;

class Apple
{
public:
Apple(int startVer):m_version(startVer)
{
}
int GetVersion()
{
return m_version;
}

private:
int m_version;
};

int main(void)
{
Apple iphones[4] = {4, 5, 6, 7};
int latest = sizeof(iphones)/sizeof(iphones[0])-1;
cout<<"iphone "<<iphones[latest].GetVersion()<<endl;

return 0;
}

不管是内置类型数组(包括字符数组),还是类类型数组,都不支持直接复制与赋值操作。

一般通过下标操作符来访问数组的元素。在用下标访问元素时,vector使用vector::size_type作为下标的类型,而数组下标的正确类型是size_t。程序员必须对数组下标的使用负责,防止缓冲区溢出(buffer overflow)错误。

2. 数组下标与指针

在表达式中使用数组名时,该名字会自动转换为指向数组第一个元素的指针。使用指针的算术操作(pointer arithmetic)来获取指定内容的存储地址相比下标操作更加方便。例如:

[code=language-cpp]int ia[] = {0, 2, 4, 6, 8};
int last = *(ia +4); // ok: last initialized to value of ia[4]
int *ip = ia; // ip points to ia[0]
int *ip2 = ip+4; // ok: ip2 points to ia[4]

使用下标访问数组时,实际上是对指向数组元素的指针做下标操作。只要指针指向数组元素,就可以对它进行下标操作

[code=language-cpp]int *p = &ia[2];
int j = p[1]; // ok: p[1] equivalent to *(p+1)
int j = p[-2]; //ok: p[-2] equivalent to *(p-2), same to ia[0]


二、动态数组

数组类型的变量有三个重要的限制:

数组长度固定不变

在编译时必须知道其长度

数组只在定义它的块语句内存在

动态分配的数组不必在编译时知道其长度,通常在运行时才确定其数组长度,与数组变量不同,动态分配的数组将一直存在,直到程序显式释放它为止。

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

1. 动态数组的定义与初始化

动态分配数组只需指定类型和数组长度,不必为数组对象命名,new表达式返回指向新分配数组的第一个元素的指针:

[code=language-cpp]int *pia = new int[10]; // array of 10 uninitialized ints
inr *pia2 = new int[10](); // 10 elements of ints array initialized to 0

new表达式需要指定指针类型以及在方括号中给出的数组维数,该维数可以是任意的复杂表达式。在动态分配数组时,若想初始化内置类型,则只能采用值初始化方式,而类类型不管有没有值初始化都会调用默认构造函数初始化。总的来说,动态分配的数组,其数组元素只能初始化为元素类型的默认值,而不能采用初始化列表的方式提供初值。

2. 允许动态分配空数组

[code=language-cpp]size_t n = get_size();
int *p = new int
;
for(int *q = p; q != p + n; ++q)
/* process the array *

上例中,即使n = 0(get_size返回0)时,程序仍能正常运行。C++虽然不允许定义长度为0的数组变量,但使用new动态创建长度为0的数组是合法的。

对数组为零的动态数组允许的操作包括:比较运算,指针加减零运算。

3. 动态空间的释放

如果不再需要使用动态创建的数组,程序员必须显式地将其占用的存储空间返还给自由存储区。C++中释放指针所指向的数组空间:

[code=language-cpp]delete [] pia;

关键字delete与指针间的空方括号是必不可少的,否则将产生内存泄漏(memory leak)。

三、字符数组与C风格字符串

1. 字符数组

字符数组既可以用一组由花括号括起来,逗号隔开的字符字面值进行初始化,也可以用一个字符串字面值进行初始化。需注意的是字符串字面值包含一个额外的空字符(null)用于结束字符串,例如:

[code=language-cpp]char chs1[] = {‘C’, '+', '+'};
char chs2[] = {'C', '+', '+', '\0'};
char chs3[] = "C++"; // null terminator added automatically

其中,字符数组chs2和chs3值相同,并且维数都是4。

动态字符数组的定义与初始化:

[code=language-cpp]char *pNewChars = new char[newSize]; // uninitialized
if(pNewChars != NULL)
{
char *iter = pNewChars;
while(iter != (pNewChars + newSize))
*iter++ = '\0';
}

2. C风格字符串(C-style character string)

实际上,C风格字符串既不能确切地归结为C语言的类型,也不能归结为C++语言的类型,可以说它是以空字符null结束的字符数组,例如上面的ch2和ch3,字符串字面值是该类型的实例。

C++语言中一般通过(const) char*类型的指针来操纵C风格字符串。需要注意的是,C风格字符串一定是以null结束的,如果发现char*的数组中不是以null结束,那么它就不是C风格字符串,这样来看,C风格字符串其实是字符数组的一个子集。

[code=language-cpp]const char* cp = "some value"; // null terminated

标准库类string

string类型支持长度可变的字符串。要使用string类对象,必须包含相关头文件:

[code=language-cpp]#include <string>

表1 几种初始化string对象的方式
string s1; 默认构造函数,空字符串
string s2(s1); 将s2初始化为s1的一个副本
string s3("Value"); 将s3初始化为一个字符串字面值副本
string s4(n, 'c'); 将s4初始化为字符'c'的n个副本
表中的第三种初始化方式就是使用字符串字面值(属于C风格字符串类型)进行初始化string类对象的,但需要注意的是string类型不符合C风格字符串的特征,例如:

[code=language-cpp]string str("Hi, C++");
cout<<"string size = "<<str.size()<<endl; // size = 7

string对象会自动将字符串字面值末尾null结束符去掉,因此得到的string对象size = 7。但string类提供了一个名为c_str的成员函数来转换得到C风格字符串:

[code=language-cpp]const char *cStr = str.c_str(); //ok

注意:c_str函数返回的指针是指向const char类型的数组,需要赋给const char*类型

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

C语言标准库提供了一系列处理C风格字符串的库函数,需要包含C头文件:

[code=language-cpp]#include <cstring>

cstring是string.h头文件的C++版本,string.h文件则是C语言的标准库。

表2 C风格字符串的标准库函数
strlen(s) 返回s的长度,不包括字符串结束符null
strcmp(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++中推荐尽量使用标准库类型string来代替这些操作,提高安全性和效率。

Reference book: C++ primer
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C++数组