C++ Primer 笔记七 数组

C++ Primer 笔记七 数组


  数组是存放一组未命名的、类型相同的对象的容器。数组中的元素在内存中是类型存储的,因此通过索引来访问这些对象,索引值从0开始。
  数组的大小确定不变。数组是内置复合类型,由C++语言直接定义。
  数组的声明形式:

TYPE name[elements]

TYPE:必须指定数组类型,不允许用auto关键字推断数组的类型。数组的元素必须是对象,而引用不是对象,因此不存在引用的数组,指针本身就是一个对象,因此存在指针的数组。
elements:数组的维度,编译的时候应该是已知的,所以维度必须是一个常量表达式(列表初始化时允许忽略),默认情况下数组的元素被默认初始化(可能含未定义的值,如果是某种内置类型的数组)。

unsigned cnt = 32;
constexpr unsigned sz = 32;
int arr[10];				// 含10个整数的数组
int *parr[sz];				// 含32个整型指针的数组
double bad[cnt];			// 错误:cnt不是常量表达式
char strs[get_size()];		// 当get_size()是constexpr时正确;否则错误

 
数组的初始化

  • 默认情况下数组的元素被默认初始化——定义于任何函数体之外的变量被初始化为0,定义在函数体内部的内置类型变量将不被初始化。
  • 对数组元素进行列表初始化时允许忽略维度,未指明维度的情况下,编译器会根据初始值的数量推算维度;
  • 如果指明了维度,则初始值总数量不应该超出指定的大小,优先初始化靠前的元素,剩下元素(如果有的话)则初始化为默认值。
  • 可以用字符串字面值初始化字符数组,注意'\0'也会被拷贝到字符数组中。
  • 不允许用数组直接拷贝和赋值。(虽然有一些编译器支持数组赋值,但不建议使用)
const unsigned sz = 3;
int iarr[sz] = {1, 2, 4};		// 含三个元素的数组,元素值分别为1,2,4

在内存中的表示为:

value124
index012
int iarr1[] = {1, 2, 4};				// 维度是3
int iarr2[3] = {1, 2, 4, 8};			// 错误:初始值过多

char carr[] = {'C', '+', '+'};			// 列表初始化,没有空字符串
char carr1[] = {'C', '+', '+', '\0'};	// 列表初始化,含显式空字符串
char carr2[] = "C++";					// 字符串字面值初始化,自动在结尾处添加空字符串,维度是4

carr2在内存在的表示:

valueC++\0
index0123
// 下面操作将引发错误
// error: initializer-string for array of chars is too long
const char carr3[3] = "C++";		// 错误:没有空间可存放空字符

// error: array must be initialized with a brace-enclosed initializer
char carr4[] = carr;				// 错误:不允许使用一个数组初始化另一个数组

// error: invalid array assignment
carr4 = carr;						// 错误:不允许把一个数组直接赋值给另一个数组

 
指针的数组,数组的指针和数组的引用

  • 不可建立引用的数组!
  • 对于复杂声明的读法:类型修饰符由内向外,从右往左依次绑定。
int iarr[3] = {};
int *parr[3];			// 这是一个数组,元素是int *类型
						// 可以看成 (int*) parr[3] 

int (*aptr)[3] = &iarr;	// aptr是一个指针,指向一个含有3个元素的数组
						// 可以看成 (int[3]) *aptr 

int (&aref)[3] = iarr;	// aref是一个引用,绑定一个含有3个元素的数组
						// 可以看成 (int[3]) &aref
						// 注意不可写成 int &aref[3],不存在引用的数组

// 可能没什么用的一个比较复杂的声明
int *(&val)[3] = v;	
// 可以看成 (int*)[3] &val
// &val是一个引用,绑定一个 int *[3]
// int *[3] 是一个数组,元素是指针类型
// int * 指针指向int型元素

 
访问数组元素
  可以使用范围for或者下标运算符([])来访问数组中的元素。数组的下标运算符是由C++语言直接定义的,带符号,允许处理负值。
  数组的索引是从0开始的。通常将数组下标定义为size_t类型(机器相关,无符号,且被设计得足够大以表示内存中任意对象的大小),定义在cstddef头文件中。
  举个栗子:用数组记录各分数段成绩个数:

// scores.cpp
#include <iostream>

int main() {
	// 以10 为一个分数段进行统计: 0-9, 10-19, ..., 90-99, 100
	unsigned scores[11] = {};	// 列表初始化为0
	unsigned grade = 0;	

	// 使用下标访问数组元素	
	while(std::cin >> score) {
		if (grade <= 100) {
			++scores[grade / 10];	// 当前分数段计数加1
		}
	}

	// 使用范围for输出各分数段的个数
	for (auto i : scores) {
		std::cout << i << std::endl;
	}
	
	return 0;
}

编译运行结果:

$ ./score.out
12 56 78 34 90 33 98 67 E
0
1
0
2
0
1
1
1
0
2
0

  注意,编译器不负责检查数组下标。当数组的下标越界并尝试访问非法内存时,就会发生缓冲区溢出错误。

 
数组和指针
  在用到数组名字的地方,编译器会自动将其替换为一个指向数组首元素的指针。在一些情况下数组的操作实际上是指针的操作。
  使用指针可以遍历数组中的元素,前提是先获取到指向数组第一个元素的指针和指向尾元素下一个位置的指针。指向首元素的指针可以通过数组名字和首元素的地址获得,尾后指针通过以下方式获得:

int iarr[4] = {};
int *e = &iarr[4];

  尾后指针不指向具体元素,因此不可以对尾后指针执行解引用或递增操作。
  当使用一个数组作为一个auto变量的初始值时,推断的类型是指针而非数组。而当使用decltype()返回的是一个数组。

int iarr[4] = {};
auto iarr1(iarr);		// 实际上是执行了 auto iarr1(&iarr[0]),iarr1是一个int*

// 下面操作将发生错误
// error: invalid conversion from 'int' to 'int*'
iarr1 = 32;

decltype(iarr) iarr2;	// iarr2是一个含4个元素的数组
iarr2[2] = 3;

  数组的指针支持的运算:

  1. *ptr:解引用,获得ptr所指的对象
  2. ++ptr:指向ptr的下一个元素;
  3. --ptr:指向ptr的上一个元素;
  4. ptr1 == ptr2:ptr1和ptr2是否指向同一个位置或同一个尾后指针;
  5. ptr1 != ptr2:ptr1和ptr2是否指向同一个位置或同一个尾后指针;
  6. ptr + n:加上一个整数仍得到一个指针,新位置与原来相比向前移动了n个位置;
  7. ptr - n:减去一个整数仍得到一个指针,新位置与原来相比向后移动了n个位置;
  8. ptr += n:指针向前移动n个位置;
  9. ptr -n:指针向后移动n个位置;
  10. ptr1 - ptr2:指针相减得到指针之间的距离,类型为ptrdiff_t,类似size_t,ptrdiff_t也是定义在cstddef头文件中的机器相关的类型;
  11. ptr1 >(>=/</<=) ptr2:如果ptr1指向的位置在ptr2指向的位置之前,则ptr1小于ptr2;参与运算的两个指针必须指向同一个数组中的元素,或者尾元素的下一个位置。
int ival = 3, sz = 44;
int *ip = &ival, *e = sz;
while( p> e) {		// 未定义的:p和e无关,比较毫无意义
}

 
标准库函数begin和end(C++11)

template(class T, std::size_t N)
T* begin(T (&array)[N]);
T* end(T (&array)[N]);

auto n = end(arr) - begin(arr);

练习:利用指针将数组元素置0。

// setZero.cc
#include <iostream>

void display(int *arr, int n) {
	for(int i = 0; i != n; ++i) {
		std::cout << *arr << std::endl;
		++arr;
	}
}


int main() {
	int iarr[] = {3, 6, 7, 9};
	display(iarr, 4);

	// 将元素置0
	for(auto &i : iarr) {	
		i = 0;
	}
	
	display(iarr, 4);
	return 0;
}

编译运行结果:

$ ./setZero.cc
3
6
7
9
0
0
0
0

练习:比较两个数组对象是否相等。

// compare.cc
#include <iostream>

bool compare(int *a1, int n1, int *a2, int n2) {
	int n = n1 > n2 ? n1 : n2;
	bool isequal = true;

	for (int i = 0; i != n; ++i) {
		if (*a1 != *a2) {
			isequal = false;
			break;
		}
		++a1;
		++a2;
	}
}

int main() {
	int a1 = {1, 3, 5, 7};
	int a2 = {1, 3, 5};
	int a3 = {1, 2, 5, 7};
	int a4 = {1, 3, 5, 7};

	std::cout << "Is a1 equal to a2 : " << compare(a1, 4, a2, 3) << std::endl;
	std::cout << "Is a1 equal to a3 : " << compare(a1, 4, a3, 4) << std::endl;
	std::cout << "Is a1 equal to a4 : " << compare(a1, 4, a4, 4) << std::endl;
	reture 0;
}
$ ./compare.out
Is a1 equal to a2 : 0
Is a1 equal to a3 : 0
Is a1 equal to a4 : 1
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值