在C++中一般我们尽量不使用数组,如果不清楚元素的确切个数,请使用vector。c++中也尽量不使用指针,一般用于进行低级操作
目录
前言
一、定义和初始化内置数组
显式初始化数组元素
字符数组的特殊性
不允许拷贝和赋值
理解复杂的数组声明
二、访问数组元素
检查下标的值
三、指针
定义与初始化
使用指针访问数组
指针和const限定符
四、数组与指针
总结
数组是一种类似于标准库类型vector的数据结构,但是在性能和灵活性的权衡上又与vector有所不同。与vector相似的地方是,数组也是存放类型相同的对象的容器,这些对象本身没有名字,需要通过其所在位置访问。与vector不同的地方是,数组的大小确定不变,不能随意向数组中增加元素。因为数组的大小固定,因此对某些特殊的应用来说程序的运行时性能较好,但是相应地也损失了一些灵活性。
数组是一种复合类型。数组的声明,形如 a [d],其中 a是数组的名字,d是数组的维度。维度说明了数组中元素的个数,因此必须大于0。数组中元素的个数也属于数组类型的一部分,编译的时候维度应该是已知的。也就是说,维度必须是一个常量表达式:
默认情况下,数组的元素被默认初始化。
和内置类型的变量一样,如果在函数内部定义了某种内置类型的数组,那么默认初始化会令数组含有未定义的值。
定义数组的时候必须指定数组的类型,不允许用auto关键字由初始值的列表推断类型。另外和 vector一样,数组的元素应为对象,因此不存在引用的数组。
可以对数组的元素进行列表初始化,此时允许忽略数组的维度。如果在声明时没有指明维度,编译器会根据初始值的数量计算并推测出来;相反,如果指明了维度,那么初始值的总数量不应该超出指定的大小。如果维度比提供的初始值数量大,则用提供的初始值初始化靠前的元素,剩下的元素被初始化成默认值。
#include
#include
using namespace std;
unsigned get_size()
{
int a = 100;
int b = 200;
return a + b;
}
int main()
{
int a[100];
const unsigned buf_size = 512, max_files = 20;
int staff_size = 27;
const unsigned sz = get_size();
char input_buffer[buf_size];
string fileTable[max_files + 1];
const unsigned array_size = 3;
int ia[array_size] = { 23,4,24 };
cout << ia[0] << endl;
cout << ia[0] << "," << ia[1] << "," <
字符数组有一种额外的初始化形式,我们可以用字符串字面值对此类数组初始化。当使用这种方式时,一定要注意字符串字面值的结尾处还有一个空字符,这个空字符也会像字符串的其他字符一样被拷贝到字符数组中去。
不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值。
一些编译器支持数组的赋值,这就是所谓的编译器扩展(compiler extension)。但一般来说,最好避免使用非标准特性,因为含有非标准特性的程序很可能在其他编译器上无法正常工作。
和vector一样,数组能存放大多数类型的对象。例如,可以定义一个存放指针的数组。又因为数组本身就是对象,所以允许定义数组的指针及数组的引用。在这几种情况中,定义存放指针的数组比较简单和直接,但是定义数组的指针或数组的引用就稍微复杂一点了。
默认情况下,类型修饰符从右向左依次绑定。对于ptrs来说,从右向左理解其含义比较简单:首先知道我们定义的是一个大小为10的数组,它的名字是ptrs,然后知道数组中存放的是指向int的指针。
但是对于Parray来说,从右向左理解就不太合理了。因为数组的维度是紧跟着被声明的名字的,所以就数组而言,由内向外阅读要比从右向左好多了。由内向外的顺序可帮助我们更好地理解 Parray的含义:首先是圆括号括起来的部分,*Parray意味着Parray是个指针,接下来观察右边,可知道Parray是个指向大小为10的数组的指针,最后观察左边,知道数组中的元素是int。这样最终的含义就明白无误了,Parray是一个指针,它指向一个int数组,数组中包含10个元素。同理,(&arrRef)表示arrRef是一个引用,它引用的对象是一个大小为10的数组,数组中元素的类型是int。
当然,对修饰符的数量并没有特殊限制。
按照由内向外的顺序阅读上述语句,首先知道arry是一个引用,然后观察右边知道, arry引用的对象是一个大小为10的数组,最后观察左边知道,数组的元素类型是指向int 的指针。这样,arry就是一个含有10个int 型指针的数组的引用。
与标准库类型vector和 string一样,数组的元素也能使用范围for语句或下标运算符来访问。数组的索引从0开始,以一个包含10个元素的数组为例,它的索引从0到9,而非从1到10。
在使用数组下标的时候,通常将其定义为size_t类型。size_t是一种机器相关的无符号类型,它被设计得足够大以便能表示内存中任意对象的大小。在 cstddef头文件中定义了size_t类型,这个文件是C标准库stddef.h头文件的C++语言版本。
#include
using namespace std;
int main()
{
const size_t array_size = 7;
int a[] = { 1,2,3,4,5,6,7 };
int b[array_size];
for (size_t ix = 0; ix != 7; ++ix)
{
cout << a[ix] << endl;
}
return 0;
}
数组除了大小固定这一特点外,其他用法与vector基本类似。与vector和 string一样,当需要遍历数组的所有元素时,最好的办法也是使用范围for语句。
因为维度是数组类型的一部分,所以系统知道数组中有多少个元素,使用范围for语句可以减轻人为控制遍历过程的负担。
注意:检查数组下标值,不要越界。
与vector和string一样,数组的下标是否在合理范围之内由程序员负责检查,所谓合理就是说下标应该大于等于0而且小于数组的大小。要想防止数组下标越界,除了小心谨慎注意细节以及对代码进行彻底的测试之外,没有其他好办法。对于一个程序来说,即使顺利通过编译并执行,也不能肯定它不包含此类致命的错误。
大多数常见的安全问题都源于缓冲区溢出错误。当数组或其他类似数据结构的下标越界并试图访问非法内存区域时,就会产生此类错误。
在C++语言中,指针和数组有非常紧密的联系。就如即将介绍的,使用数组的时候编 译器一般会把它转换成指针。简单来讲,指针就是一个字符串的地址。
通常情况下,使用取地址符来获取指向某个对象的指针,取地址符可以用于任何对象。数组的元素也是对象,对数组使用下标运算符得到该数组指定 位置的元素。因此像其他对象一样,对数组的元素使用取地址符就能得到指向该元素的指针:
string nums[]=("one","two","three");//数组的元素是string对象
string *p = &nums[0]; // p指向nums的第一个元素
然而,数组还有一个特性:在很多用到数组名字的地方,编译器都会自动地将其替换为一个指向数组首元素的指针:
string *p2 = nums;//等价于p2 = &nums[0]
在大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针。
指向数组第一个元素:
访问任意元素:
//相同含义
ip = &ia[0];
ip = ia;
指针的算术操作:
指针减法:表示两个指针所指向的元素之间的距离
指针的解引用:得到指针所指向的数据
//int last = *ip2;
int last = *(ia + 4);//这个括号必须加上
cout << last << endl;
解引用的优先级比较高
//没有加括号表示,先解引用再进行运算
int last = *ia + 4;
cout << last << endl;
指针方括号:
使用for循环,指针是数组的迭代器。
#include
#include
using namespace std;
int main()
{
const size_t arr_size = 5;
int arr[arr_size] = { 1,2,3,4,5 };
int* p2 = arr;
int* p3 = p2 + arr_size;//超出末端的指针
for (int *ptr = p2 ; ptr != p3; ++ptr)
cout << *ptr << endl;
return 0;
}
在C++语言中,指针和数组有非常紧密的联系。使用数组的时候编译器一般会把它转换成指针。
通常情况下,使用取地址符来获取指向某个对象的指针,取地址符可以用于任何对象。数组的元素也是对象,对数组使用下标运算符得到该数组指定位置的元素。因此像其他对象一样,对数组的元素使用取地址符就能得到指向该元素的指针。
然而,数组还有一个特性:在很多用到数组名字的地方,编译器都会自动地将其替换为一个指向数组首元素的指针。
在大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针。
数组和指向数组元素的指针在一个较低的层次上实现了与标准库类型string和vector类似的功能。一般来说,应该优先选用标准库提供的类型,之后再考虑C++语言内置的低层的替代品数组或指针。
以上就是本次笔记的所有的内容。