流程控制
逻辑分支
现在有一个新情况,学校期末考试,60 分及以上为及格,尝试编写一个函数,输入成绩判断是否及格。
此时遇到了一个流程分支的情况,当分数大于等于 60 的时候,我们提示及格,小于的时候,提示不及格。遇到这种情况,我们需要使用逻辑分支语句 if-else
:
function foo(score) {
if (score >= 60) {
console.log("及格");
} else {
console.log("不及格");
}
}
foo(100);
foo(10);
上诉代码会依次打印 及格
与 不及格
,通过 if-else
语句,我们改变了程序的走向,上面代码中, >=
是一个关系运算符,表示大于等于,它用来判断两个数据的大小关系。score >= 60
是一个关系表达式,如果关系成立,那么关系表达式的值是布尔值 true
,反之是布尔值 false
。
falsy 值
falsy 值代表假值,当一个判断的结果是 falsy 值时,判断不成立,它用于 if-else
语句的判断部分。falsy 值有以下几个:
值 | 描述 |
---|---|
false | 布尔值 false |
0 | 数字 0 ,包括负零 -0 |
空字符串 | 空字符串 |
null | Null 字面量 null |
undefined | Undefined 字面量 undefined |
NaN | 数字字面量 NaN |
也就是说,如果 if-else
语句判断部分的表达式结果是以上几个值,那么就执行 else
后面的代码。
// 更换 0、""、null、undefined、NaN 表达式都不会成立
if (false) {
console.log("成立");
} else {
console.log("不成立", false);
}
if-else
if-else
语句的写法如下:
if (expression)
// code 1
else
// cdoe 2
圆括号内的 expression
是一个表达式,if-else
语句的判断部分能填写任何表达式:
- 如果这个表达式的结果不是一个 falsy 值,那么执行
if
后面的代码。 - 如果表达式的结果是一个 falsy 值,那么执行
else
后的代码。
如果条件中只有一条语句,那么花括号可以被省略,如:
var a = 10;
if (a > 0) console.log("a 大于 0");
else console.log("a 小于等于 0");
if-else
语句执行结束后,会按顺序执行后续的代码,不会结束执行:
function foo(score) {
if (score >= 60) {
console.log("及格");
} else {
console.log("不及格");
}
console.log("判断结束,后续代码会执行");
}
在 if-else
语句中,else
是可以被省略的,比如:
function foo(x) {
if (x > 0) {
console.log("x 大于 0");
}
console.log("end");
}
foo(1);
foo(0);
else-if
某些情况下,判断存在多种不同的情况,会产生多种不同的分支,比如考试成绩被分为不及格、及格、良、优等四个阶段,分别对应分数 60 分以下、60 到 80、80 到 90、90 到 100。针对这种情况,我们可以使用 if-else
嵌套:
function foo(x) {
if (x < 60) {
console.log("不及格");
} else {
if (x < 80) {
console.log("及格");
} else {
if (x < 90) {
console.log("良");
} else {
console.log("优");
}
}
}
}
针对这种情况,可以使用 else-if
语句来让逻辑并行,而不是像上面一样串行:
function foo(x) {
if (x < 60) {
console.log("不及格");
} else if (x < 80) {
console.log("及格");
} else if (x < 90) {
console.log("良");
} else {
console.log("优");
}
}
foo(50); // 不及格
foo(70); // 及格
foo(80); // 良
foo(90); // 优
使用 else-if
会基于前一次判断不成立的情况,再提起一次逻辑判断。其末尾的 else
同样可以删除。
开发中应尽量把 if-else
逻辑嵌套的层数降低,以提高代码可读性。
关系运算符
关系运算符用来进行关系比较,关系运算符组成的表达式称作 关系表达式,关系表达式的值是布尔值。
常见关系运算符如下:
运算符 | 描述 |
---|---|
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
== | 等于 |
!= | 不等于 |
=== | 全等于 |
!== | 不全等于 |
当一个关系表达式成立时,值为 true
,反之为 false
:
var foo = 1;
var bar = 2;
console.log(foo > bar, foo < bar); // false true
等于和全等于的区别
等于运算符在进行比较时,较为松懈,比如:
var num = 1;
var str = "1";
console.log(num == str); // true
数字 1
和字符 '1
' 使用等于运算符被判定为相等,如果这个你觉得这么比较很合理,那么下面判断你就会觉得很离谱:
var arr = [1];
var num = "1";
var bool = true;
console.log(arr == num, arr == bool, num == bool); // true true true
使用等于运算符比较数组 [1]
、字符 '1
'、布尔 true
时,表达式的结果都是 true
。这些特殊的规律很难总结和记忆,并且容易造成程序出错,所以 开发时一般不使用等于运算符,使用全等于运算符。
全等于运算符会严格判断两个数据,必须完全相等表达式才会成立。
var num = 1;
var str = "1";
console.log(num === str); // false
数字 1
和字符 '1
' 因为类型不同,全等于关系不成立。
不等于 !=
和不全等于 !==
的区别同上述一样,这里不再赘述。
有一个很特殊的情况, NaN
字面量无法通过关系运算符比较 ,只能使用内置函数 isNaN
来判断:
var n = NaN;
n === NaN; // false
isNaN(n); // true
所以判断一个变量是不是 NaN
的时候,不要与 NaN
作比较,两个 NaN
不相等。需要使用 isNaN
函数判断。
逻辑运算符
JS 的关系运算符不支持并且或者逻辑,比如想表示一个数字大于 2 小于 5,不能使用以下表达式:
var x = 3;
if (2 < x < 5) {
// do something
}
上面的程序实际上会先运算表达式 2 < x
的结果得到一个 true
,然后用 true
再与 5
作比较,虽然程序不会报错,但是比较的结果会让人出乎预料。
要实现一个数字同时大于 2 并且小于 5,应该使用逻辑运算符。
可以使用以下代码表示与逻辑:
if (2 < x && x < 5) {
// do something
}
&&
是一个逻辑运算符,它表示多个条件必须同时成立,被称作逻辑与。常用逻辑运算符如下:
逻辑运算符 | 描述 | 补充 |
---|---|---|
&& | 与,多个条件同时成立时,命题才成立。 | 对应数学中的并且 |
\|\| | 或,其中一个条件成立时,命题就成立。 | 对应数学中的或者 |
! | 非,把命题的成立条件颠倒。 | 对应数学中的反命题 |
与或逻辑比较简单,它们就是数学思维中常说的并且或者。
比如判断一个数字是否是 2 和 3 的倍数,就是并且关系,该数字需要是 2 的倍数,并且是 3 的倍数,它的代码如下:
function foo(x) {
if (x % 2 === 0 && x % 3 === 0) {
console.log("是");
} else {
console.log("不是");
}
}
foo(9); // 不是
foo(12); // 是
【代码解释】
如果把上面的题目改成,判断一个数字是 2 或 3 的倍数,就使用或逻辑:
function foo(x) {
if (x % 2 === 0 || x % 3 === 0) {
console.log("是");
} else {
console.log("不是");
}
}
foo(9); // 是
foo(12); // 是
foo(11); // 不是
或逻辑只要一个条件成立,命题就成立。所以参数 x
只要是 2 或者 3 其中一个的倍数,表达式就成立了。
非逻辑表示否定,代表原命题的反命题。比如一个命题是:一个数大于 1,其反命题就是:一个数小于等于 1:
var x = 0;
x < 1; // true
!(x < 1); // false
非逻辑的计算结果是一个布尔值,反命题成立,结果为 true
,反之为 false
。JS 中。
// falsy 值
!0; // true
!undefined; // true
// 不是 falsy 值
!100; // false
![1, 3, 4]; // false
逻辑转换
不同的思维方向会列出不同的逻辑表达式。比如下面两个命题:
- x 不是 1 并且也不是 2。
- x 不是 1 或者 2。
上面两个命题的意义相同,但是思维方式不同,按照不同逻辑编写的代码也会不同:
// x 不是 1 并且也不是 2。
if (val !== 1 && val !== 2) {
console.log("这个数不是 1 并且不是 2");
}
// x 不是 1 或者 2。
if (!(val === 1 || val === 2)) {
console.log("这个数不是 1 或 2");
}
上面两个判断的意思是一样的,只是思维方式不一样。所以逻辑语句之间是存在转换规则的,逻辑表达式的转换规则如下:
原表达式 | 等同于 |
---|---|
A && B | !(!A \|\| !B) |
A \|\| B | !(!A && !B) |
逻辑非还能配合关系运算符转换:
原表达式 | 等同于 |
---|---|
A <= B | !(A > B) |
A < B | !(A >= B) |
A >= B | !(A < B) |
A > B | !(A <= B) |
A != B | !(A == B) |
A !== B | !(A === B) |
优先级
意外的是,与运算符比或运算符的优先级要高,所以与逻辑会被先运算。
function foo(x) {
if (x > 0 && x < 10 || x > 20 && x < 30) {
console.log("x 在区间 (0,10) 或者 (20,30) 内");
}
}
【代码解释】
如果你记不住这点问题,vocode 的 prettire 扩展会在格式化保存时自动填充圆括号提高可读性,让你一眼就知道那里先被执行,但是还是需要同学们记住,编辑器只是辅助,你不一定所有代码都使用 vscode 编写。
逻辑非的优先级极高,大部分情况下逻辑非都会先执行。比如:
!x === false;
绝对会先计算 !A
,而不是先计算 x === false
。这点从语义上就可以理解。
逻辑表达式与短路运算
与或运算符组成的表达式称作 逻辑表达式,逻辑表达式的值不是 true
或者 false
,分析以下代码:
与或逻辑成立或者不成立时,它们的值不一定是 true
或者 false
。
var foo1 = 0 && 1;
var foo2 = 1 && 2;
var foo3 = 0 || 1;
var foo4 = 1 || 2;
console.log(foo1, foo2, foo3, foo4); // 0 2 1 1
上面的代码不会输出 true
和 false
,而是进行了短路运算。
程序中不管是与运算还是或运算,都是从左到右计算。
在与运算中:只要有一个不成立,那么逻辑表达式就不成立,所以当程序计算到第一个不成立的表达式后,就会停止后续计算,并把这个不成立的表达式作为逻辑表达式的值,如果全部表达式都成立,那么就使用最后一个值作为逻辑表达式的值。
在或运算中相反:只要有一个成立,那么逻辑表达式就成立,所以当程序计算到第一个成立的表达式后,就会停止后续计算,并把这个成立的表达式作为逻辑表达式的值,如果全部表达式都成立,那么就使用最后一个值作为逻辑表达式的值。
上述这种停止计算的行为,被叫做 短路运算。比如:
var foo = 10 > 1 && true && null && 10;
var bar = 1 > 10 || undefined || "string" || 10;
console.log(foo, bar); // null "string"
【代码解释】
观察下列代码:
var foo, bar;
foo = 0 && (bar = 1);
console.log(foo, bar); // 0 undefined
foo = 10 && (bar = 100);
console.log(foo, bar); // 100 100
在 foo = 0 && (bar = 1)
中产生了短路运算, &&
符后面的语句没有执行,foo
最终的值是 0
;
而在 foo = 10 && (bar = 100)
中没有产生短路运算,bar = 100
被执行,并且表达式的值是 100
,此时 foo
和 bar
都是 100;
短路运算常常用来设置默认值或者试探访问,比如:
var foo = {
name: null,
};
function getNameLength(obj) {
var len = (obj && obj.name && obj.name.length) || 0;
console.log(len);
}
getNameLength(); // 0
getNameLength(foo); // 0
getNameLength({ name: "text" }); // 4
试探访问和设置默认值都是提高代码的健壮性,让代码不容易出错,比如上述代码中,如果不使用试探访问与默认值设置,程序就会产生错误,导致无法运行。
小练习,判断一下程序的输出结果:
var A = 1,
B = 2,
C = 3,
D = 4,
E = 5;
console.log((!A && B) || !C || (D && !E));
三目运算符
开发中常常会遇到 if-else
内只有一行简单代码的情况,这种时候,可以使用三目运算符来简化代码:
function foo(score) {
if (score >= 60) {
console.log("及格");
} else {
console.log("不及格");
}
}
function bar(score) {
var text = score > 60 ? "及格" : "不及格";
console.log(text);
}
上诉代码中,foo
函数和 bar
函数是等价的。
三目运算符由问号和冒号 ?:
组成,共有三个操作数,格式如下:
命题 ? 成立值 : 不成立值
如果命题成立,那么表达式的值是第二个操作数,否则是第三个操作数。
使用短路运算也可以模拟三目运算符:
function baz() {
var text = (score > 60 && "及格") || "不及格";
console.log(text);
}
【代码解释】
逻辑练习
编写一个函数 foo,有一个参数 x,判断 x 是否是一个正数并且是一个双数。
编写一个函数,输入三个不相等的数字参数 x,y,z,从小到大输出这三个数。
function foo(x) {
if (x > 0 && x % 2 === 0) {
console.log("是");
} else {
console.log("不是");
}
}
// 解题思维,先判断 x 和 y 的大小,然后把 z 插入 x 和 y。
function bar(x, y, z) {
if (x > y) {
if (z > x) {
console.log(z, x, y);
} else if (z > y) {
console.log(x, z, y);
} else {
console.log(x, y, z);
}
} else {
if (z > y) {
console.log(z, y, x);
} else if (z > x) {
console.log(y, z, x);
} else {
console.log(y, x, z);
}
}
}
逻辑分支 switch
switch
是另一种逻辑分支,它的写法如下:
function foo(x) {
switch (x) {
case 1:
console.log("number is 1");
console.log("x is 1");
break;
case 2:
console.log("number is 2");
console.log("x is 2");
break;
case 3:
console.log("number is 3");
console.log("x is 3");
break;
default:
console.log("default");
}
}
foo(1);
switch
语句接受一个确定的值,并且判断自己的案例列表中是否有这个值,当匹配成功后,执行案例内的代码;
switch
规则:
- 匹配成功时,会执行匹配成功的
case
后续的所有代码,不过可以使用break
关键字跳出switch
语句 break
只是跳出switch
语句,不是跳出程序。- 匹配不成功时,会执行
default
后面的代码。 case
的匹配值一般是一个字面量或者变量,也可以是复杂的表达式,但是出于程序设计角度来说,使用太复杂的表达式请使用if-else
语句。
switch
语句和 if-else
语句都是处理逻辑分支的语句,你可以随意使用。
switch
语句的一旦查找到了对应的案例,会执行案例后的所有代码,如果不使用 break
跳出,那么后续所有案例的代码都会执行,比如:
function foo(x) {
switch (x) {
case 1:
console.log("number is 1");
console.log("x is 1");
case 2:
console.log("number is 2");
console.log("x is 2");
case 3:
console.log("number is 3");
console.log("x is 3");
break;
default:
console.log("default");
}
}
foo(2); // 会执行case 2 3 中的代码,如果删除 case 3 中的 break,那么默认案例中的代码也会执行
循环分支 for
程序中还存在一种循环分支的流程控制,它被用来处理重复的事情,比如汽车制造商制造一个轮胎,我们把制造轮胎的代码封装进一个函数,不可能今天生成 100 个,我们就写 100 次的执行代码。
循环分支的实现如下:
function foo() {
for (var i = 0; i < 10; ++i) {
console.log("第", i, "次执行");
}
}
foo();
上边这行代码会执行 10 次,打印的数据会填满你的控制台。
通过 for
关键字创建一个循环语句,for
语句的循环控制部分有三个操作数,之间使用分号 ;
号分割。
- 第一个操作数常常用来初始化循环语句需要的变量
- 第二个操作数被用来判断循环是否继续执行,如果是一个
falsy
值,那么循环停止。 - 第三个操作数会在每次循环结束后执行。
循环体用一对花括号 {}
表示,如果只有一条语句,那么可以省略花括号。
JS 中所有逻辑分支语句都不会生成函数作用域,所以我们在上面代码中写的 var i = 0;
实际上把变量 i
声明到了 foo
的函数作用域中。
尝试在循环之后打印变量 i
,你会发现它等于 10,因为当变量 i
自增到 10 的时候,第二个操作数为 10 < 10
,不成立,循环停止。
和循环关系密切的数据类型是数组,尝试编写一个函数,计算一个数字数组内数据的总和:
function sum(arr) {
var re = 0;
for (var i = 0; i < arr.length; i++) {
re += arr[i];
}
return re;
}
var foo = sum([1, 2, 3, 4]);
console.log(foo);
【代码讲解】
小练习 1: 编写一个函数,传入一个数字参数 n,返回 1 - 2 + 4 - 8 + 16... n 的值。n 从 1 开始计数
小练习 2: 编写一个函数,传入一个数字参数 n,返回菲波那切数列的第 n 项。n 从 0 开始计数。
// 小练习 1
function foo(n) {
var re = 0;
var item = 1;
for (var i = 1; i <= n; i++) {
if (i % 2) {
re += item;
} else {
re -= item;
}
item *= 2;
}
return re;
}
// 小练习 2
function fib(n) {
var start = 0;
var middle = 1;
var end;
if (n <= 1) {
return n;
} else {
for (var i = 1; i < n; i++) {
end = start + middle;
start = middle;
middle = end;
}
return end;
}
}
嵌套循环
循环之间可以嵌套,用来处理更复杂的逻辑。小学课程中有一个经典的题目叫做鸡兔同笼,尝试用程序处理鸡兔同笼的问题。
我们可以是用程序暴力破解这个题,使用多重循环撞击所有的可能性,直到找出答案:
function foo(head, foot) {
if (head * 4 < foot || head * 2 > foot) {
console.log("头脚不合,有缺胳膊少腿的?");
return;
}
for (var chicken = 0; chicken < head; chicken++) {
for (var rabbit = 0; rabbit < head; rabbit++) {
if (chicken + rabbit === head && chicken * 2 + rabbit * 4 === foot) {
console.log("鸡 : " + chicken, "兔 : " + rabbit);
return;
}
}
}
}
foo(100, 280);
上述代码中,我们就使用了二次循环暴力破解此题,尽管答案是对的。
现在改变一下思路:
function foo(head, foot) {
if (head * 4 < foot || head * 2 > foot) {
console.log("头脚不合,有缺胳膊少腿的?");
return;
}
for (var chicken = 0; chicken < head; chicken++) {
var rabbit = head - chicken;
if (chicken * 2 + rabbit * 4 === foot) {
console.log("鸡 : " + chicken, "兔 : " + rabbit);
return;
}
}
}
foo(100, 280);
我们减少了一层循环体,让代码不论从速度还是可读性上,都获得了提升。
然而实际上,这个题的最优解应该是这样:
function foo(head, foot) {
if (head * 4 < foot || head * 2 > foot) {
console.log("头脚不合,有缺胳膊少腿的?");
return;
}
var rabbit = (foot - 2 * head) / 2;
var chicken = head - rabbit;
console.log("鸡 : " + chicken, "兔 : " + rabbit);
}
只要有对应的公式,逻辑上的嵌套是可以解开的,类似上面鸡兔同笼的问题一样,不过如果是循环数据嵌套,就无法解开,尝试遍历一个三维数组:
var foo = [
[
[1, 2, 3],
[4, 5, 6],
],
[
["a", "b", "c"],
["q", "w", "e"],
],
];
for (var i = 0; i < foo.length; i++) {
for (var j = 0; j < foo[i].length; j++) {
for (var k = 0; k < foo[i][j].length; k++) {
console.log(foo[i][j][k]);
}
}
}
【代码解释】
跳出循环
在程序中,会遇到程序中途跳出循环的情况。跳出循环有三个关键字:
break
:跳出一层循环体continue
:跳出本次循环,然后执行下一次循环。return
:停止函数内代码的执行,循环也会被终止
观察下列代码:
for (var i = 0; i < 10; i++) {
if (i % 2 === 0) {
continue;
}
console.log(i);
}
上述代码每次循环的时候,都会判断 i
是不是一个偶数,是偶数就使用 continue
关键字跳出本次循环,执行下次循环。
如果你不是很理解,我给你提出一个新的非专业术语,你把每次循环读作帧,那么 continue
关键字就是跳出当前帧,执行下一帧。上面的循环一共有 10 帧,当 i
为偶数时,continue
会结束本帧的执行,执行下一帧。
break
语句会跳出一层循环语句,也就是当前循环的所有帧,比如:
for (var i = 0; i < 5; i++) {
for (var j = 0; j < 3; j++) {
console.log("j = ", j);
break;
}
console.log("i = ", i);
}
break
无法连续跳出多层循环,如果你想连续跳出多层循环,你可以在循环中设置一个标记,比如:
var flag = false;
for (var i = 0; i < 5 && flag; i++) {
for (var j = 0; j < 3; j++) {
console.log("j = ", j);
if (i > 3) {
flag = true;
}
break;
}
console.log("i = ", i);
}
上面代码中,我们定义了一个标记 flag
来决定了外层循环的条件,然后在内层可以随时更改这个条件来让外层循环结束。
label 语句
警告:本小节的内容不要在开发中使用,如果你需要用到本节的内容,那么你应该思考重构你的代码。
我们可以为每个循环命名,这样使用 continue
和 break
就可以指定循环体跳出,比如上一节的代码,我们可以直接跳到最外层:
loop1: for (var i = 0; i < 5; i++) {
for (var j = 0; j < 3; j++) {
console.log("j = ", j);
break loop1;
}
console.log("i = ", i);
}
上面代码中,我们给 for i
循环体命名为 loop1
,就像变量名一样,不论在内层的任何位置,都可以通过 break
或者 continue
语句,来指定跳出循环的位置。
这种写法不被推荐,不要在开发中使用。
循环分支 for-in
for
循环还有另一种变体 for-in
循环,它被用来遍历一个引用类型内可以枚举的属性:
var foo = {
name: "foo",
bar: "bar value",
baz: function () {},
};
for (var key in foo) {
console.log(key, foo[key]);
}
for in
循环用来遍历一个对象的属性列表,遍历的值是对象的属性名称列表,同普通的 for
语句类似,for in
语句也可以使用 break
等关键字跳出循环,或者和其他循环体互相嵌套。
for in
循环没有循环控制的操作数,需要跳出循环,只能通过 break
或者 continue
等关键字。
使用 for in
循环只能遍历出可以枚举的属性列表,数组也是引用类型,你可以使用 for in
来遍历数组的下标:
var foo = ["a", "b", "c"];
for (var key in foo) {
console.log(key);
}
上面代码会遍历出数组的下标,但是不会遍历出数组的内置属性 length
,因为这个属性是不可枚举的。
循环分支 do-while
还有另一种循环分支叫做 while
,相比 for
循环,while
循环更加简洁,它只有一个操作数:
var i = 0;
while (i++ < 10) {
console.log(i);
}
while
语句仅有的操作数作为循环控制,虽然更加简洁,但是它的使用场景比 for
循环更少。
不论是 for
、for in
或者 while
语句,都是先进行循环控制的判断,再执行循环,在 while
循环中,有一种变体,可以先循环一次,再判断是否执行下次循环。它是 do-while
循环,它的编写方式如下:
var i = 0;
do {
console.log(i);
} while (i++ < 10);
do-while
语句会先执行一次循环体内的代码,然后再进行判断是否进行下次循环。
练习
注意:练习只针对于零基础的同学,如果你有其他编程语言基础,可以无视以下练习。做每道题之前不要看解答,做完后再看解答。如果你是零基础,下列题目并不一定能全部完成,不要灰心,你并没有编程经验,这是正常情况,无法完成的题目在听完讲解后,可以再重新编做一次。
题 1:编写一个函数,传入一个一维数字数组参数 a,输出 a 中的最大值。
题 2:编写一个函数,接收一个行数与列数相同的二维数组,从数组的左上角到右下角作一条直线,把数组的数据沿这条直线翻面,如:
[ [1, 2, 3], [4, 5, 6], [7, 8, 9], ];
转化为:
[ [1, 4, 7], [2, 5, 8], [3, 6, 9], ];
题 3:编写一个函数,传入一个 一维正整数数组,判断数组内是否有由一个数字组成的数,比如 1、2、3、111、222、99999、3333333 这样的数字,如果有打印第一个这样的数字,如果没有打印
'该数组内没有找到符合题意的数字'
字符串。提示:这个题你需要使用算术运算对数字进行分割。题 4:编写一个函数,传入一个正整数 n,在控制台打印出以下图形:
* *** ***** *** *
其中,第一行的中央有一个
*
号,每行递增两个*
号,第 n 行以后每行递减两个*
号,直至没有*
号。提示:你可以把图形分成几部分处理。提示:你可能会使用绝对值计算,那么你应该多编写一个绝对值函数。
题 1:
function foo(a) {
var max = a[0];
for (var i = 1; i < a.length; i++) {
if (max < a[i]) {
max = a[i];
}
}
return max;
}
题 2:
观察可知,沿着左上与右下对角线做出分割线,分割线两边的数据项在转化后改变了行号和列号进行了对调,所以只需要把不是对角线上的数据进行行号和列号的对调即可,对角线上的数据行号和列号相同:
function foo(a) {
var size = a.length;
// row 遍历到 size - 1,col 从 row + 1 开始,只遍历右上部分
for (var row = 0; row < size - 1; row++) {
for (var col = row + 1; col < size; col++) {
var item = a[row][col];
a[row][col] = a[col][row];
a[col][row] = item;
}
}
console.log(a);
}
题 3:
这个题中,我们需要对数字的位数进行拆分。
- 从末位开始拆分,通过对 10 取模,可以得到个位数字。
- 然后把数字缩小 10 倍,让十位上的数字变到个位。
- 重复上述计算,拆分一个数字,每次都需要判断相邻的两个数字是否相同,如果不同,那么说明这个数不止一种数字,停止拆分。
- 如果数字被拆分结束后,都没有发现该数由多种数字组成,那么就是满足题意的数字。
function foo(a) {
for (var i = 0; i < a.length; i++) {
var num = a[i];
var item;
do {
item = num % 10;
num = (num - item) / 10;
} while (num % 10 === item);
if (num === 0) {
console.log(a[i]);
return;
}
}
console.log("该数组内没有找到符合题意的数字");
}
题 4:
解答:设每行的行标为 i
,i
从 1 到 n,输入数据为 n
。
不难发现新,第 i
行的开头有 |n - i|
个空格作为前缀。记作:条件 A。
行与行之间,每少一个空格,就会多两个 *
号,每行 *
号数量是少去空格数乘以 2 加 1,1 是因为首行自带一个 *
号。另外,首行的空格数量是 n - 1
,配合条件 A,可得每行 *
号数量为:1 + (n - 1 - |n - i| ) * 2
.
那么程序可以这么写:
// 绝对值函数
function pos(x) {
return x >= 0 ? x : -x;
}
function foo(n) {
var rowStr = "";
for (var i = 1; i < 2 * n; i++) {
for (var j = 0; j < pos(n - i); j++) {
rowStr += " ";
}
rowStr += "*";
for (var j = 0; j < n - 1 - pos(i - n); j++) {
rowStr += "**";
}
rowStr += "\n";
}
console.log(rowStr);
}
此题解法不一,也可以分成上下部分单独处理。