自学编程,从此开始

上第2学堂,听有趣的编程课

课文: 《你好,循环!》 (点击查看完整内容:视频+评测+讨论+……)

作者:null

调整心态,学会妥协,接受张二妞等一票人都成为女神吧。

课文题图

 

第7节 你好,循环!

1. 数组

1.1 基本概念

之前课程举过一个统计语、数、英三科成绩总分的函数例子:

int sum (int x, int y, int z)
{
    return x + y + z;
}

丁小明学习之后,接到一个任务:统计全班30个同学的总分。感觉和前面的sum也差不多,除了成绩的个数多了一点而已:

int sum (int a, int b, int c, int d, int e, int f, ……)

代码写不下去,没错,量变还能引起质变呢。要统计的数据从3个变成30个,程序的确不能再这样一个个数据写下去。数组,如视频里所说,当我们需要对一大堆同类的数据进行相同或相似的处理时,可以将这些数据整整齐齐地放在一个数组里。

整数/int、浮点数/double、布尔值/bool……这些叫类型。数组则主要表现为一种数据结构;所谓的“结构”,通常指的一个事物内部多个部件的组成方式。数组内部的每一个数据称为一个“元素”,各元素及其组成结构,最像是部队中的士兵。整齐划一。原因在于:

  1. 元素的数据类型一致。
  2. 既然类型一致,所以各个元素原始大小一致。
  3. 元素之间排列紧密,没有空余。

这些整齐划一的数据,如果排成一列,就是士兵队列,称为一维数组;如果排成多排多列,就是士兵方阵;称为二维数组;如果叠起来,就是三维数组……由于只是一种“逻辑结构”,因此比可以脱离现实的束缚,可以有四维、五维……但通常也就印度士兵会叠到三维……《感受篇》我们只讲一维数组。

你有一张银行卡,我也有一张银行卡,马云先生可能也有一些。卡片的大小都一样,但卡片背后所对应的存款可能差别巨大!马先生的卡对应的纸币,堆满了银行的某个金库……视频中提到的元素类型是std::string的数组:

std::string  names[] = {"张三", "李四", "王二麻子"};

第三个元素的内容确实比前两个元素占用的内存要大,但这个数组的元素类型是 “std::string”,而类型一样,数据的原始大小也就一致。std::string 存在names数组中的每一个元素,其实只是一张“卡”。真正的“张三”、“李四”、“王二麻子”这些数据是真钞,存在银行卡上写着帐号所对应的银行的金库里。

1.2 定义、初始化数组(一维)

  1. 定义,不初始化:元素类型 数据名 [ 元素个数 ] ;
  2. 定义,并且初始化: 元素类型 数据名 [ 元素个数 ] = { 元素1, 元素2, …… } ; \

不带安装始化的方式,数组中的数据是什么,要看上下文环境才能定,有可能随机,有可能全部填充0。

带初始化的方式,也注意点。: 如视频课程所说,此时元素个数可以省略,因为编译器会帮我们从初始化数据中数一数。但是,如果初始化的元素个数比你期望的数组元素总数小时,还是要指出元素个数。详见下面的例子。

例子:

//例1:定义,但不初始化,
//数组中十个整数的值是什么,要看上下文环境才能定,有可能随机,有可能全是0
int a [ 10 ]; 

//例2:定义,并且初始化,并且初始化元素个数,就是期待的数组的元素总数(5个):
double d5 [] = {1.2, 3.3, 4.6, 0.0, 5.5};

//例3:定义,并且初始化,但初始化元素的个数(5个),小于期待的数组的元素的总数(10个):
double d10 [ 10 ] = {1.2, 3.3, 4.6, 0.0, 5.5};

1.3 使用下标访问元素

视频中没有讲,到对数组最重要,最频繁的操作,就是访问它的各个元素。对数据元素的访问方式,有个专业术语,叫“随机访问”。 这里的随机并不是你扔骰子随机得到1至6个点的“随机”。想要从根子上理解对数组元素的访问,需要用到各位深厚的的小学算术科学知识和幼儿空间想象能力。请大家深吸一口气,往下读。

  1. 先低头看地板。
  2. 通常现在的地板都有整齐的,面各一致的瓷砖拼成,由于我们讲的是一维数组,所以大家往一个直线方向上看瓷砖。假设离你最近的那块瓷砖编号为0,再远点则编号1、编号2……
  3. 现在,来17个人,不能太胖,因为保证每个人都需要并且只需要占用一块瓷砖(均匀,即:占用内存大小一致)。17个人从0号瓷砖开始往后站,中间不留空(即:连续)。
  4. 最后,问题来了:请问第17个人站(占)的瓷砖的编号是几?

所有此时开始从第一个人往后数的同学,或者伸出双脚双手开始数的同学,或者干脆跑到队伍最后面看瓷砖编号的同学……可以去校务处办理退学了。不是我爱打击,你们真真是学会不编程的。根据前面说的条件,既没有人占用一个以上的瓷砖,也没有空白不站人的瓷砖。第一个站在0号位置上,第17个人还用得着数吗?肯定是16号啊!

对数组元素的随机访问,就这个意思: 你想访问第几个元素,直接就能算出它距离第一个元素的距离(专业点也称偏移)。对应的,最典型的“非随机访问”的一种形式,就是想访问第5个元素?你得从第1个元素那边一个个找过去……大多数过关游戏都不支持随机访问,因为你想玩第4关,对不起,请从第1关开始,一关一关通关才行……

那个瓷砖的编号,就是偏移量,在编程语言中,叫数组的 “下标”。 因为是偏移量,所以从0开始。也就是说第一个元素的下标是0。

假设数组名叫a,那么访问下村为2的元素,语法:a [2]。注意,这是第3个元素。

示例:

double d5 [] = {1.2, 3.3, 4.6, 0.0, 5.5};
cout <<  d5[0] << ", " << d5[1] << "," << d5[4] << endl; //1.2, 3.3, 5.5

千万不要访问 d5[5]……因为,你这样是出了d5的家,进入隔壁老王家了。这叫“数组访问越界“。当然,也不去访问任何正经人家数组的 第-1个元素,比如 d5[-1]。程序其实允许你这么做,但结果会比较可怕。都说了,C++语言认为它的程序员都是成年人。成年人,你去去老王家也有什么不妥嘛! 反正老王也应该也经常去你家。

课堂作业-3

试试上面说的错(没什么大不小,真的不过是天底下所有程序员都会犯的错误而已)。

2. for 循环

2.1 容器遍历循环法

for 循环有两种典型用法……我们在视频中学习的是结合数组的一种用法,这种用法还比较年轻,2011年才入的标准。

for ( 元素类型 元素名  : 数组 )
{

}

比如:

double d5 [] = {1.2, 3.3, 4.6, 0.0, 5.5};

for (double const d : d5)
{
    cout << d << ", ";
}

cout << endl;

进入for 循环时,临时定义的 d,注意,在本例中是一个常量,将依次是1.2,3.3,4.6, 5.5……

如果把const去除,我们自然可以在for循环体内,修改d,比如,将它改为0.0。那么,是不是这样就可以将d5中的每个元素清零呢?试试:

double d5 [] = {1.2, 3.3, 4.6, 0.0, 5.5};

for (double /* const */ d : d5)  //const被去除,现在d是变量
{
    d = 0.0; //篡改者!
}

//  然后,你再输出d5的所有元素值……

d5中一个元素都没有被真正改到。

课堂作业-4:

试一下本例。

为什么呢?在《你好,数据!》的课程,我们有说过:数据的两大核心属性:值和址。两个数据之所以是“*两个”数据,正是因为二者的内存地址不同。循环中的 d ,它有自己有内存地址,是一个独立的数据。只不过,它的“值”属性,在某一篇循环中,复制了d5中的某一个元素的值而已……

2.2 计数循环法

再来学习for循环更强大的计数循环法:

for (1-循环因子初始化; 2-循环条件判断;  3-循环因子变化)
{

}

注意,在这一用法中,for后面的括号中,竟然有三个语句(编号仅为方便表达)。因为,彼此之间使用分号“;”在作分隔。

还记得高中跑一千五百米的体育往事吗?

假设跑道一圈是500米。我们需要跑3圈。

假设考核时,你跑得半死,考官却叫住你,很抱歉地说:“对不起,我们忘了给你计圈,也没给你掐秒表……你重来好吗?”。会不会令你直接抓狂?不会???想不到一个体育生,身体这么棒,也会有沦落到学习C++的今天……

  1. 第一部分,"循环因子初始化"语句。在整个for循环开始,只执行一次,通常用于初始化和循环有关的一些数据因素(称为循环因子),作用相当于你在1500米跑步开始的一瞬间,体育老师按下秒表的“开始”按钮。
  2. 第二部分,“循环条件判断”语句。通常用于在for循环的每一次(也包括第1次)开始前,检查条件,如果条件不成立,for 循环结束。相当于你每跑一圈之前,体育老师判断一下你是否跑足三圈了。用什么判断呢?肯定和循环因子有关。
  3. 第三部分,“循环因子变化”语句。通常用于在for循环的每一次结束后,修改一下循环因子。相当于你每跑完成一圈,体育记录一下你跑的圈数。

千言万语,不如一例,就先说跑步吧!:

for (int i=1; i < 4; ++i)
{
    cout << "你正在跑第" << i << "圈。" << endl;
}
  1. 循环因子初始化: 用i变量表示正在跑的圈数。因为要跑三圈,所以从1开始,到4(不包括4)结束。
  2. 循环条件判断: 每一圈开始之前,都检查当前圈数是不是小于4。这要是判断写错了,可能会有同学被累死。注意,尽管在人看来,第一圈就开始检查似乎很笨。但计算机只能这样做。因为在复杂的业务需求下,出现一下最终其实是“跑零圈”的循环,很常见。
  3. 循环因子变化:每完成一圈,将计数i加一。 “++”在C++中称为“前置自增一操作”。作用就是让后面的变量(通常是整数)加一。假设 i 现在的值是1,++i之后,i的值变成2。

上面代码逻辑正确,但违反一个C/C++编程的惯用法:循环计数从0开始,所以,更常见的写法是:

for (int i=0; i < 3; ++i)
{
     cout << "你正在跑第" << i + 1 << "圈。" << endl;    
}

课堂作业-5

完成以上循环代码的编写、编译、测试

再来一个例子,用于气死高斯:

int sum = 0;
for (int n=0; n < 100; ++n)
{
    sum += (i+1);
}

cout << sum <<endl;

“+=” 操作,就是将其左边的变量的值,加上其右边的值。比如:

int a = 98;

a += 2; // 完成后,a 的值是 100

课堂作业-6

完成气死高斯的例子。

3. while 循环

while 循环比 for 循环简单很多,视频中将它和if相比,也讲得比较细。这里使用while循环来气高斯一次:

int sum =0; 
int n = 1; 
while(n <= 100)
{
    sum += n;
    ++n;
}

课堂作业-7

完成上例,并for循环实现的版本作对比,重点观察循环因子初始化、循环条件判断,循环因子变化在while循环中是如何完成的。