第6章 接触数组


数组 是由类似的数据项(称为“元素”)组成的数据结构,而且数据项的数量任意。

6.1 C++数组初探

如果要声明5个变量,我们可以这样:

double scores1, scores2, scores3, scores4, scores5;

这要打好多字,如果用数组,我们可以这样:

double scores[5];	//声明double类型的5个数据项

scores[0], scores[1], scores[2], scores[3], scores[4]引用这些数据项。方括号之间的数字称为“索引”。

语法:

类型 数组名[大小];

这将创建指定大小的数组。数组每个元素都具有指定类型。数组元素范围从数组名[0]到数组名[大小-1]。

6.2 初始化数组

可用以逗号分隔的初始化列表来初始化数组。该记法要求用到大括号和逗号。

double scores[5] = {0.0, 0.0, 0.0, 0.0, 0.0};
int ordinals[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

6.3 基于零的索引

C++ 数组的工作方式可能和你期望的有所出入。

不管怎么声明数组,最大索引编号总是比数组的大小小1.

C语言或C++语言数组的索引编号不是序号(位置编号)而是偏移量。也就是说,元素索引编号用于测量它到数组开头的距离。

  • N个元素的C++数组,索引从0到 N-1

为何使用基于零的索引?
有些语言如 FORTRAN,使用基于1的索引。
但程序不管用什么语言写,最终都要转换成CPU能实际执行的机器码。

……
在机器级别上,数组索引通过偏移量来处理:一个寄存器(CPU内部的特殊内存位置)包含数组地址(实际是数组第一个元素的地址)。另一个寄存器则包含偏移量(到目标元素的距离)。
……
第一个元素的偏移量和C++一样是零。
……
使用FORTRAN这样的语言,必须先将基于1的索引转换成基于0的索引(索引减1)。再乘以每个元素的大小获得索引为I的元素的地址:
元素I的地址 = 基本地址 + ((I - 1) * 每个元素的大小)
而C++这种基于0的语言不需要执行减法运算,能稍微提高一下效率:
元素I的地址 = 基本地址 + (I * 每个元素的大小)
……
虽然表面上只是稍微节省了一些CPU事件,但所有基于C的语言就是这个设计思路:做最接近CPU所做的事情。

例6.1:打印元素

// print_arr.cpp
# include <iostream>
using namespace std;

int main()
{
	double scores[5] = { 0.5, 1.5, 2.5, 3.5, 4.5 };
	for (int i = 0; i < 5; ++i)
	{
		cout << scores[i] << "  ";
	}
	return 0;
}
0.5  1.5  2.5  3.5  4.5

练习

练习 6.1.1

写程序初始化包含8个整数的数组,值分别是5,15,25,35,45,55,65,75.打印每个整数。提示:将循环条件从i < 5变成 i < 8,因为本例有8个元素。

// 练习6.1.1
# include <iostream>
using namespace std;

int main()
{
	int scores[8] = { 5, 15, 25, 35, 45, 55, 65, 75 };

	for (int i = 0; i < 8; ++i)
	{
		cout << scores[i] << "  ";
	}
	cout << endl;
	return 0;
}
5  15  25  35  45  55  65  75

例6.1.2

写程序初始化包含6个整数的数组,值分别是10,22,1399,4和5.打印每个整数,最后打印它们的和。提示:用一个变量保存累加值。

// 练习6.1.2
# include <iostream>
using namespace std;

int main()
{
	int sum = 0;
	int scores[6] = { 10, 22, 13, 99, 4, 5 };

	for (int i = 0; i < 6; ++i)
	{
		cout << scores[i] << "  ";
		sum += scores[i];
	}
	cout << endl << "它们的和是:" << sum << endl;
	return 0;
}
10  22  13  99  4  5
它们的和是:153

练习 6.1.3

写程序提示用户输入7个值并存储到数组,打印每个值,最后打印它们的和。要为该程序写两个for循环,一个收集数据,另一个求和并打印。

// 练习6.1.3
# include <iostream>
using namespace std;

int main()
{
	int sum = 0;
	int scores[7];

	for (int i = 0; i < 7; ++i)
	{
		cout << "请输入第" << i + 1 << "元素的值:";
		cin >> scores[i];
	}

	for (int i = 0; i < 7; ++i)
	{
		cout << "第" << i + 1 << "个元素的值是:" << scores[i] << endl;
		sum += scores [i];
	}

	cout << endl << "它们的和是:" << sum << endl;
	return 0;
}
请输入第1元素的值:45
请输入第2元素的值:63
请输入第3元素的值:25
请输入第4元素的值:15
请输入第5元素的值:94
请输入第6元素的值:34
请输入第7元素的值:211个元素的值是:452个元素的值是:633个元素的值是:254个元素的值是:155个元素的值是:946个元素的值是:347个元素的值是:21

它们的和是:297

例 6.2:是不是真的随机?

第3章介绍了如何使用所谓的随机数。
但真正的随机应该是不可预测的。计算机算法本质就是可预测,所以真正的随机理论上不可能。

但实际可不可能呢?如要求程序输出一系列数字,它们能表现出一个真正的随机数列应该具有的全部特征吗?

rand_0toN1 函数输出0到 N -1 的一个整数。其中N是传给函数的实参。可用该函数来获得0到9的一系列数字,并统计每个数字出现的次数。我们希望的结果如下所示。

  • 10个数字中,每个出现概率都应该是大约十分之一。
  • 但数字不应该以相同的概率出现,尤其是在试验次数较少的情况下。不过,随着增加试验次数,每个数字实际出现次数和预期出现次数(总试验次数的十分之一)之比应无限逼近1.0。

如果满足以上条件,就获得了一个证明实际能做到随机的很好的例子。这对大多数游戏程序就足够了。

// stats.cpp
# include <iostream>
# include <cstdlib>
# include <ctime>
using namespace std;

int rand_0toN1(int n);
int hits[10];

int main()
{
	int n = 0;		// 试验次数,提示用户输入
	int r = 0;		// 容纳随机值
	srand(time(nullptr));	// 设置随机数种子值
	cout << "输入试验次数并按Enter:";
	cin >> n;

	// 执行n次试验。每次都获取0到9的一个数
	// 然后使hits 数组对应的元素递增。
	for (int i = 0; i < n; ++i)
	{
		r = rand_0toN1(10);
		++hits[r];
	}

	// 打印hits数组的所有元素
	// 并打印hits 和预期hits (n / 10)的比值
	for (int i = 0; i < 10; ++i)
	{
		cout << i << " : " << hits[i] << "准确度:";
		double results = hits[i];
		cout << results / (n / 10.0) << endl;
	}

	return 0;
}

// 返回0到 N-1的随机整数
int rand_0toN1(int n)
{
	return rand() % n;
}
输入试验次数并按Enter:2000
0 : 203准确度:1.015
1 : 216准确度:1.08
2 : 198准确度:0.99
3 : 184准确度:0.92
4 : 208准确度:1.04
5 : 189准确度:0.945
6 : 178准确度:0.89
7 : 199准确度:0.995
8 : 215准确度:1.075
9 : 210准确度:1.05

(随着N值越来越大,准确度【一个数字实际出现的次数和预期出现次数的比值】会越来越逼近1.0)

声明:

int hits[10]

声明hits 数组会创建包含10个整数的一个数组,索引范围从0到9.由于是全局数组(在所有函数的外部声明),它的所有元素都自动初始化为0.

技术上说,数组之所以初始化为全零值,是因为它是一个静态存储类。局部变量也可声明为静态,从初始化到程序运行结束都一直存在,每次函数调用时的值都会保留,即使只在定义它的函数内可见。

练习

练习 6.2.1

不是在main中声明r,而是在循环内部声明。这样r就不必初始化为0,因为可直接赋一个有意义的值。(不仅仅是少打几个字,意义非凡)

// 练习6.2.1
# include <iostream>
# include <cstdlib>
# include <ctime>
using namespace std;

int rand_0toN1(int n);
int hits[10];

int main()
{
	int n = 0;	//试验次数,提示用户输入
	int r = 0;	// 容纳随机值
	srand(time(NULL));	// 设置随机数种子值
	cout << "输入试验次数并按Enter:";
	cin >> n;

	// 执行n次试验。每次都获取0到9的一个数,
	// 然后使hits数组对应的元素递增。
	for (int i = 0; i < n; ++i)
	{
		int r = rand_0toN1(10);
		++hits[r];
	}

	// 打印hits数组的所有元素
	// 并打印实际hits和预期hits (n / 10)的比值
	for (int i = 0; i < 10; ++i)
	{
		cout << i << ": " << hits[i] << "准确度:";
		double results = hits[i];
		cout << results / (n / 10.0) << endl;
	}

	return 0;
}

// 返回0到 N-1的随机整数
int rand_0toN1(int n)
{
	return rand() % n;
}

练习 6.2.2

修改例6.2,不是生成10个不同的值,而是生成5个。换言之,使用rand_0toN1函数随机生成0,1,2,3或4.执行用户指定次数的试验,检测这5个值是不是分别有1/5的出现概率。

// 练习6.2.2
# include <iostream>
# include <cstdlib>
# include <ctime>
using namespace std;

int rand_0toN1(int n);
int hits[5];

int main()
{
	int n = 0;
	srand(time(nullptr));
	cout << "输入试验次数并按Enter:";
	cin >> n;

	for (int i = 0; i < n; ++i)
	{
		int r = rand_0toN1(5);
		++hits[r];
	}

	for (int i = 0; i < 5; ++i)
	{
		cout << i << " : " << hits[i] << "准确度:";
		double results = hits[i];
		cout << results / (n / 5.0) << endl;
	}

	return 0;
}

int rand_0toN1(int n)
{
	return rand() % n;
}

练习6.2.3

修改例6.2,实现只需修改程序中的一个设置,即可处理不同数量的值。可在程序开始的地方使用一个#define预编译指令,指示编译器将一个符号名称(本例是VALUES)在代码中的所有实例替换成指定文本。
例如,为了生成5个不同的值(0到4),首先在代码开头添加以下指令:
#define VALUES 5
然后,在程序需要引用值数量的任何地方都使用符号名称VALUES。例如,可以像下面这样生命hits数组:
int hits [VALUES];
以后只需要更改#define那一行,然后重新编译程序,即可控制不同的数值量。这个方法的好处在于,只需要修改一行代码,即可改变程序的行为。

// 练习6.2.3
# include <iostream>
# include <cstdlib>
# include <ctime>
using namespace std;

#define VALUES 5

int rand_0toN1(int n);
int hits[VALUES];

int main()
{
	int n = 0;
	srand(time(nullptr));

	cout << "请输入试验次数并按Enter:";
	cin >> n;

	for (int i = 0; i < n; ++i)
	{
		int r = rand_0toN1(VALUES);
		++hits[r];
	}

	for (int i = 0; i < VALUES; ++i)
	{
		cout << i << " : " << hits[i] << "精确度:";
		double results = hits[i];
		double x = n;
		cout << results / (x / VALUES) << endl;
	}
	return 0;
}

int rand_0toN1(int n)
{
	return rand() % n;
}

练习6.2.4

修改main,使用和例5.2相似的循环,让用户运行会话任意次数,直到输入0退出。每次会话前都要将hits数组的所有元素重新初始化为0.在main中可直接用for循环将每个元素置0,也可调用一个包含此循环的函数。

// 练习6.2.4
# include <iostream>
# include <cstdlib>
# include <ctime>
using namespace std;

#define VALUES 10

bool run_program();
int rand_0toN1(int n);

int hits[VALUES];

// 在这个版本中,所有的main()会重复地
//调用run_program,直到用户想要退出
int main()
{
	srand(time(nullptr));
	while (run_program())
	{
		;
	}
	return 0;
}

bool run_program()
{
	int n = 0;
	cout << "请输入会话任意次数(0表示退出):";
	cin >> n;

	if (n < 1)
	{
		return false;
	}

	// 清空hits数组
	for (int i = 0; i < VALUES; ++i)
	{
		hits[i] = 0;
	}

	// 运行n会话。每次会话,得到一个
	//0到9的数,然后这个元素会添加到
	// hits数组
	for (int i = 0; i < n; ++i)
	{
		int r = rand_0toN1(VALUES);
		++hits[r];
	}

	// 打印hits数组的全部元素,以及
	// hits的比率(n / Values)
	// 请注意,必须修改(n / VALUES)以保证浮点除法。
	for (int i = 0; i < VALUES; ++i)
	{
		cout << i << " : " << hits[i] << "精确度:";
		double results = hits[i];
		double x = n;
		cout << results / (x / VALUES) << endl;
	}
	return true;
}

// 返回0到 N-1的随机整数
int rand_0toN1(int n)
{
	return rand() % n;
}

6.4 字节串和字符串数组

例如,下面这行代码打印消息:

cout << "What a good C++ am I.";

声明字符串变量和声明整数和浮点变量一样。有两种字符串(第8章会详细解释),一种是char*类型的传统C字符串;一种是C++string类/标准C++库支持后者已有多年。
例如,以下代码首先将字符串存储到message变量并打印。由于使用了string类,所以必须包含来提供支持。

include <string>
using namespace std;
...
string message = "What a good C++ am I.";
cout << message;

本章剩余部分将使用字符串数组。声明方式和声明任何类型的数组一样。例如:

string members[] = {"John", "Paul", "George", "Ringo"};

和其他任何数组一样,用索引访问单独的元素。例如:

cout << "The leader of the band is" << members[0];

将打印以下输出:
The leader of the band is John。
由于成员名称都存储在数组中,所以可用循环高效地打印全部。例如:

for (int i = 0; i < 4; ++i)
{
	cout << member [i] << endl;
}

将打印数组中存储的所有名字:
John
Paul
George
Ringo

例6.3:打印数组中存储的数字

//print_n_arr.cpp
# include <iostream>
# include <string> // 记住包含这个
using namespace std;

string tens_names[] = { "", "", "贰拾", "叁拾", "肆拾", "伍拾", "陆拾", "柒拾", "捌拾", "玖拾" };
string units_names[] = { "", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" };

int main()
{
	int n = 0;
	cout << "输入20到99的数:";
	cin >> n;
	int tens_digits = n / 10;
	int units_digits = n % 10;
	cout << "你输入的数是:";
	cout << tens_names[tens_digits];
	cout << units_names[units_digits] << endl;
	return 0;
}

和例3.3 比较,会注意到该版本有多简洁。

练习

练习 6.3.1

如用户输入20到99范围外的数会发生什么?虽然可能有错,但输入1到19无大碍。不过,99以上的值会造成灾难,因为会造成索引越界,这是程序员要尽力避免的错误。(注意:像Visual C++这样的托管环境会通过抛出异常并立即冻结程序来限制损害。)修改代码只接受20到99的值。理想情况下应该用循环查询用户输入,直至输入有效值。

// 练习6.3.1
# include <iostream>
# include <string> // 记住包含这个
using namespace std;

string tens_names[] = { "", "", "贰拾", "叁拾", "肆拾", "伍拾", "陆拾", "柒拾", "捌拾", "玖拾" };
string units_names[] = { "", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" };

int main()
{
	int n = 0;
	cout << "输入20到99的数:";
	cin >> n;

	while (n < 20 || n > 99)
	{
		cout << "这个数字超出了范围。请重新输入:";
		cin >> n;
	}
	int tens_digits = n / 10;
	int units_digits = n % 10;
	cout << "你输入的数是:";
	cout << tens_names[tens_digits] << " ";
	cout << units_names[units_digits] << " " << endl;

	return 0;
}

在这里插入图片描述

练习 6.3.2

接着几个练习逐渐扩充可接受值的范围。输入1到9,结果应该正确,只是前面有多余的空格。解决该问题。(中文版无此错误,英文版的源代码才有)

// 练习6.3.2 ,可接受1到9的值
# include <iostream>
# include <string> // 记住包含这个
using namespace std;

string tens_names[] = { "", "", "贰拾", "叁拾", "肆拾", "伍拾", "陆拾", "柒拾", "捌拾", "玖拾" };
string units_names[] = { "", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" };

int main()
{
	int n = 0;
	cout << "输入1到9或20到99的数:";
	cin >> n;

	while ((n < 20 || n > 99) && (n < 1 || n > 9))
	{
		cout << "这个数字超出了范围。请重新输入:";
		cin >> n;
	}
	int tens_digits = n / 10;
	int units_digits = n % 10;
	cout << "你输入的数是:";

	if (n > 9)
	{
		cout << tens_names[tens_digits] << "";
	}
	cout << units_names[units_digits] << " " << endl;

	return 0;
}

![在这里插入图片描述](https://img-blog.csdnimg.cn/20200608120840435.png

练习 6.3.3

支持输入10到19.中文版在tens_names 数组对应位置添加“壹拾”即可。英文版需要添加另一个数组和额外的条件测试。

中文版:

// 练习6.3.3 支持输入10到19
# include <iostream>
# include <string> // 记住包含这个
using namespace std;

string tens_names[] = { "", "壹拾", "贰拾", "叁拾", "肆拾", "伍拾", "陆拾", "柒拾", "捌拾", "玖拾" };
string units_names[] = { "", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" };

int main()
{
	int n = 0;
	cout << "输入1到99的数:";
	cin >> n;

	while (n < 1 || n > 99) 
	{
		cout << "这个数字超出了范围。请重新输入:";
		cin >> n;
	}
	int tens_digits = n / 10;
	int units_digits = n % 10;
	cout << "你输入的数是:";

	if (n > 9)
	{
		cout << tens_names[tens_digits] << "";
	}
	cout << units_names[units_digits] << " " << endl;

	return 0;
}

在这里插入图片描述
英文版:

// Exercise 6.3.3
// This version of the program supports the full range 1 to 99,
// by adding teen names and adding appropriate conditions.

#include <iostream>
#include <string>  // REMEMBER TO INCLUDE THIS!

using namespace std;

string tens_names[ ] = {"", "", "twenty", "thirty", "forty",
   "fifty", "sixty", "seventy", "eighty", "ninety" };

string units_names[ ] = {"", "one", "two", "three", "four",
   "five", "six", "seven", "eight", "nine" };

string teen_names[ ] = {"ten", "eleven", "twelve", "thirteen",
   "fourteen", "fifteen", "sixteen", "seventeen", "eighteen",
   "nineteen" };

int main()
{
    int  n = 0;

    cout << "Enter a number from 1 to 99: ";
    cin >> n;
    
    while (n < 1 || n > 99) {
        cout << "Number out of range. Re-enter: ";
        cin >> n;
    }

    int tens_digits = n / 10;
    int units_digits = n % 10;
    cout << "The number you entered was ";
    if (n > 9 && n < 20) {
        cout << teen_names[units_digits] << endl;
    } else {
        if (n > 19) {
            cout << tens_names[tens_digits] << " ";
        }
        cout << units_names[units_digits] << endl;
    }
    return 0;
}

练习 6.3.4

最后添加百位数支持以1到999的数。如果有兴趣,甚至可以修改程序处理最大999999的数。

// 练习6.3.4
# include <iostream>
# include <string> // 记住包含这个
using namespace std;

string teen_names[] = { "", "壹佰", "贰佰", "叁佰", "肆佰", "伍佰", "陆佰", "柒佰", "捌佰", "玖佰" };
string tens_names[] = { "", "壹拾", "贰拾", "叁拾", "肆拾", "伍拾", "陆拾", "柒拾", "捌拾", "玖拾" };
string units_names[] = { "", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" };

int main()
{
	int n = 0;
	cout << "输入1到999的数:";
	cin >> n;

	while (n < 1 || n > 999)
	{
		cout << "这个数字超出了范围。请重新输入:";
		cin >> n;
	}
	int teen_digits = n / 100;
	int tens_digits = (n % 100) / 10;
	int units_digits =(n % 100) % 10;
	cout << "你输入的数是:";

	if (n > 9)
	{
		if (n > 99)
		{
			cout << teen_names[teen_digits] << "";
		}
		cout << tens_names[tens_digits] << "";
	}
	cout << units_names[units_digits] << " " << endl;

	return 0;
}

在这里插入图片描述

例6.4:简单的发牌程序

本章最后展示一个简单应用程序,并在第15章进行回顾。怎样模拟一个简单发牌程序?
为简化问题,我有两个假设。

  1. 只关系牌点,不关心花色。
  2. 暂不关心重新洗牌的问题。

示例输出如下所示:
A 5 3 K K
或者:
Q 7 10 6 7
通过发正好5张牌来测试deal-a-card函数(忽略花色)。虽然都随机,但牌的行为有别于骰子。每次掷骰子都是独立事件。骰子无记忆,但一副牌有。例如,假定还剩半副牌的时候4张A都发完了,那么再发一张A的概率为零。我们用另一个数组模拟这种“发牌记忆”。具体就是创建一个整数数组来包含牌的位置:0,1,2,3,4.。。。51.然后对数组进行随机化(洗牌),并一张一张地发。由于需要使用随机数,所以程序开头要有下面几行:

# include <cstdlib>
# include <ctime>

还需要一个数组来包含牌的名称,所以还必须包含.

// dealer.cpp
// 简单的发牌程序
# include <iostream>
# include <string>	// 使用string类需要
# include <cstdlib>	// 生成随机数需要
# include <ctime>
using namespace std;

int deck[52];
string card_names[] = { "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K" };
void swap_cards(int i, int j);
int rand0_to_N(int n);

int main()
{
	srand(time(NULL));		//设置随机数种子
	// 初始化牌的位置:0,1,2,3,4。。。51
	for (int i = 0; i < 52; ++i)
	{
		deck[i] = i;
	}

	// 洗牌
	for (int i = 51; i > 0; --i)
	{
		int j = rand0_to_N(i);
		swap_cards(i, j);
	}

	// 发5张牌
	for (int i = 0; i < 5; ++i)
	{
		int j = deck[i] % 13;
		cout << card_names[j] << " ";
	}
	cout << endl;
	return 0;
}

// 交换两张牌
void swap_cards(int i, int j)
{
	int temp = deck[i];
	deck[i] = deck[j];
	deck[j] = temp;
}

// 随机设定一个位置
int rand0_to_N(int n)
{
	return rand() % (n + 1);
}

中心数据结构是deck[ ],一个52个整数的数组
数组没有显示初始化。作为全局变量,会被编译器自动初始化为零。当然最好不要依赖这一行为,因为加入deck [ ]是局部变量,它的值会被初始化为垃圾(可能包含任何东西)。
对deck数组的真正初始化在main函数中完成。用一个简单循环将值设为0,1,2,3等等。将每个元素设为它的索引值即可。

52张牌,从最下面拿一张,随机和一副牌中的任何一张牌(可能是最下面那张牌自己)交换。如替换的是最下面的那张,则换牌操作是一个no-op(无操作)。这没有问题。结果是最下面的牌可能是52张牌的任何一张。

然后,拿出那张牌,放到一边,处理剩余51张牌。重复上述操作,结果是从51张牌中选一张。拿出来,放到之前拿出来的牌上方。处理剩余50张牌。一直处理到剩余最后两张牌后,整副牌已完全打乱,每张牌都可能在任意位置。

拿到一副被完全打乱的牌后,从顶部一次发一张,将数字转换为对应的牌。用取余操作符%实现,获取0到51的一个数,产生0到12的一个数(13个不同值之一)。
int j = deck [i] % 13;
结果是j等于0到12的一个数,该数作为索引在card_name数组中查找对应的元素。这样就将0到12的一个数转换成对应的字符串,比如“A”, "K"等。

练习

练习 6.4.1

修改程序打印牌点完整英文名称:Ace, deuce, trey, four, five, six, seven, eight, nine, ten, Jack, Queen, King。

// 练习6.4.1
# include <iostream>
# include <string>	// 使用string类需要
# include <cstdlib>	// 生成随机数需要
# include <ctime>
using namespace std;

int deck[52];
string card_names[] = { "Ace", "deuce", "trey", "four", "five", "six", "seven", "eight", "nine", "ten", "Jack", "Queen", "King" };
void swap_cards(int i, int j);
int rand0_to_N(int n);

int main()
{
	srand(time(NULL));		//设置随机数种子
	// 初始化牌的位置:0,1,2,3,4。。。51
	for (int i = 0; i < 52; ++i)
	{
		deck[i] = i;
	}

	// 洗牌
	for (int i = 51; i > 0; --i)
	{
		int j = rand0_to_N(i);
		swap_cards(i, j);
	}

	// 发5张牌
	for (int i = 0; i < 5; ++i)
	{
		int j = deck[i] % 13;
		cout << card_names[j] << " ";
	}
	cout << endl;
	return 0;
}

// 交换两张牌
void swap_cards(int i, int j)
{
	int temp = deck[i];
	deck[i] = deck[j];
	deck[j] = temp;
}

// 随机设定一个位置
int rand0_to_N(int n)
{
	return rand() % (n + 1);
}

在这里插入图片描述

练习 6.4.2

同时打印花色和牌点,从而显示一张牌的完整名称,比如“黑桃”A(ace of spaeds)。共4张花色:梅花♣(clubs),方块♦(diamonds),红桃♥(hearts)和黑桃♠(spaeds)。数字0到51可以和花色关联。假定前13个是梅花,接着13个是方块,以此类推。提示:0到51的数除以13可获得0到3的数。换言之,可组合取余(%)和整数除法(\)使一个数成为牌点和花色的唯一组合。

// 练习6.4.2
# include <iostream>
# include <string>	// 使用string类需要
# include <cstdlib>	// 生成随机数需要
# include <ctime>
using namespace std;

int deck[52];
string card_names[] = { "A", "2", "3", "4", "5", "6",  "7", "8", "9", "10", "J", "Q", "K" };
string suit_names[] = { "梅花♣", "方块♦", "红桃♥", "黑桃♠" };
void swap_cards(int i, int j);
int rand0_to_N(int n);

int main()
{
	srand(time(NULL));		//设置随机数种子
	// 初始化牌的位置:0,1,2,3,4。。。51
	for (int i = 0; i < 52; ++i)
	{
		deck[i] = i;
	}

	// 洗牌
	for (int i = 51; i > 0; --i)
	{
		int j = rand0_to_N(i);
		swap_cards(i, j);
	}

	// 发5张牌
	for (int i = 0; i < 5; ++i)
	{
		int j = deck[i] % 13;
		int k = deck[i] / 13;	// 分出花色
		cout << suit_names[k] << " ";
		cout << card_names[j] << endl;
	}
	cout << endl;
	return 0;
}

// 交换两张牌
void swap_cards(int i, int j)
{
	int temp = deck[i];
	deck[i] = deck[j];
	deck[j] = temp;
}

// 随机设定一个位置
int rand0_to_N(int n)
{
	return rand() % (n + 1);
}

在这里插入图片描述

练习 6.4.3

修改程序从总共6副牌的一个牌盒(shoe)里发牌。6副牌(每副52张)一起洗。只使用0到51的编号,从而保留上个练习的花色分配功能。提示:用取余操作符将一个更大的数字集合转换成0到51.从牌盒里发牌会影响拿牌概率吗?发出4张A的概率是变大还是变小了?

// 练习6.4.3
# include <iostream>
# include <string>	// 使用string类需要
# include <cstdlib>	// 生成随机数需要
# include <ctime>
using namespace std;

int deck[312];
string card_names[] = { "A", "2", "3", "4", "5", "6",  "7", "8", "9", "10", "J", "Q", "K" };
string suit_names[] = { "梅花", "方块", "红桃", "黑桃" };
void swap_cards(int i, int j);
int rand0_to_N(int n);

int main()
{
	srand(time(NULL));		//设置随机数种子
	// 初始化牌的位置:0,1,2,3,4。。。51
	for (int i = 0; i < 312; ++i)
	{
		deck[i] = i;
	}

	// 洗牌
	for (int i = 311; i > 0; --i)
	{
		int j = rand0_to_N(i);
		swap_cards(i, j);
	}

	// 发5张牌
	for (int i = 0; i < 5; ++i)
	{
		int j = deck[i] % 13;
		int k = (deck[i] % 52) / 13;	// 分出花色
		cout << suit_names[k] << " ";
		cout << card_names[j] << endl;
	}
	cout << endl;
	return 0;
}

// 交换两张牌
void swap_cards(int i, int j)
{
	int temp = deck[i];
	deck[i] = deck[j];
	deck[j] = temp;
}

// 随机设定一个位置
int rand0_to_N(int n)
{
	return rand() % (n + 1);
}

在这里插入图片描述

6.5 二维数组:进入矩阵

大多数计算机语言除了一维数组,还支持多维数组。C++也不例外。C++二维数组具有以下形式:

类型 数组名[大小1][大小2];

元素数量等于大小1*大小2.每一维的索引编号都是0到9.所以,每一个元素是matrix[0][0],最后一个是matrix[9][9]。
用程序处理这种数组需要使用一个包含两个循环变量的嵌套循环。例如,以下代码将数组的所有成员初始化为0:

for (int i = 0; i < 10; i++)
{
	for (int j = 0; j < 10; j++)
	{
		matrix[i][j] = 0;
	}
}

工作流程如下:

  1. i设为0,完成内层循环的全部迭代,j从0递增到9.
  2. 完成外层循环的第一次迭代后,i递增到下一个值,即1.然后,再次完成内层循环的全部迭代,j从0递增到9.
  3. 重复上述过程,直到i递增到超过终值9.

结果是i和j的值将是(0,0)(0,1)(0,2).。。(0,9).之后,内层循环结束,i值递增,再次开始内层循环,即(1,0)(1,1)(1,2).。。。总共执行100次操作,外层循环每一次迭代(总共迭代10次),内层循环都迭代10次。

在C++数组中,位于右侧的索引将以最快的速度改变。换言之,元素matrix[5][0] 和 matrix[5][1]在内存中是紧挨在一起的。

小结

  • 使用方括号记法声明C++数组:
    类型 数组名[元素数量];
  • 大小为n的数组,索引范围从0到n-1。
  • 可用循环高效处理任意大小的数组。例如对于包含 size_of_array 个元素的数组,以下循环将每个元素都设为0:
for (int i = 0; i < size_of_array; ++i)
	my_array[i] = 0;
  • 用大括号之间的值来初始化数组,这称为“初始化列表”:
    double scores[5] = {6.8, 9.0, 9.0, 8.3, 7.1};
  • 用string类声明字符串变量(第8章将进一步解释该类型和传统C字符串)。例如:
# include <string>
using namespace std;
...
string name = "Joe Bloe";
  • 然后像声明其他任何类型的数组那样声明字符串数组。例如:
string band[ ] = {"John", "Paul", "George", "Ringo"};
  • 然后像索引其他任何类型的数组那样索引字符串数组:
cout << "The leader of the group was" << band[0];
  • C++不会在运行时帮你检查数组边界(除非是Visual Studio这样的托管环境)。所以在写数组访问代码时,必须小心不要覆盖别人的内存区域。
  • 像下面这样声明二维数组:
类型 数组名[大小1][大小2];
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值