【小课堂】四点五、一些支线任务

小课堂的基础篇即将过半,下半场的难度则将更上一个台阶。而在主线学习的过程中,C/C++语言还有许多细枝末节的技术知识,因此借此“中场休息”时间,本课介绍一些之前中没有涉及的“支线任务”,有些内容有些深奥,跳过不影响后续主线的学习,可视情况阅读。

注释

(其实在之前的教程中就已经出现过)
注释是写在代码中给自己或其他程序员看的文字。注释方法有两种:

1
2
3
4
5
6
//这是单行的注释

/*
如果需要跨行写注释
可以像这样框起来
*/

注释文字在程序中不起任何作用,编译器将忽略所有注释。但注释对一段良好的代码是必需的。随着代码逐渐复杂,将很难一眼看懂其功能,边编写边写注释可以大大提高维护代码时的工作效率。

注释的另一大功能是在测试代码时对语句进行临时的保留/删除,如:

1
2
3
4
for(int i=1;i<=100;i++){
a = (b + 2) * i;
//a = (b + 2) / i;
}

使用注释可以快速使不想执行的代码暂时“封印”,如以后需要,只需将注释符删去,代码就恢复了功能。


sizeof运算符

虽然长得不太像,sizeof的确是C/C++运算符之一,用于计算数值/表达式/类型的空间大小(字节数),如:

1
2
cout<<sizeof(1)<<endl;
cout<<sizeof(double)<<endl;

输出:
4
8
表示整型数1占4个字节,一个double型数据占8个字节。
如果需要估算开一个数组的开销,就可以这样计算:

1
2
double a[10000][5000]
cout<<sizeof(double)*10000*5000/1024/1024;

输出:
381
所以,使用一个10000*5000的double型数组的开销约为381MiB

位运算符

在介绍位运算之前首先要简单引入现代计算机中的数字编码知识。
我们知道,数字在计算机中是以二进制存储的(不仅是历史原因,和二分、倍增、二叉等算法数据结构也有微妙的联系)。二进制的概念不再详细介绍,有需要可自行搜索。
位运算就是C/C++对于二进制数的操作,由于与计算机底层架构一致,效率一般比常规的运算符快上不少。
位运算符有:

1
2
3
4
5
6
&    //按位与
| //按位或
~ //按位非
^ //按位异或
<< //左移
>> //右移

我们拿int a = 42;(二进制101010)int b = 23;(二进制1 0111)为例:

1
2
3
4
5
6
7
int a = 42, b = 23;
cout<< (a & b) <<endl;
cout<< (a | b) <<endl;
cout<< (~a) <<endl;
cout<< (a ^ b) <<endl;
cout<< (a << 2) <<endl;
cout<< (a >> 2) <<endl;

输出:
2
63
-43
61
168
10

按位与:将两个二进制数每一位进行与运算

1
2
3
4
   0010 1010
& 0001 0111
--------------
0000 0010 (=十进制2

按位或:同理,将两个二进制数每一位进行或运算

1
2
3
4
   0010 1010
| 0001 0111
--------------
0011 1111 (=十进制63

按位非:将一个二进制数每一位进行非运算

1
2
3
 ~ 0010 1010
--------------
1101 0101 (=一个补码为11010101的数,即十进制-43

此处有疑问可跳过,或自行了解详细的数字编码介绍(原码、反码、补码)

按位异或:将两个二进制数每一位进行异或运算
异或(xor)是和与或非类似的逻辑运算,真值表如下:

1
2
3
4
5
a    b    a xor b
1 1 0
0 0 0
1 0 1
0 1 1

即两个布尔量相同时为false,不同时为true
所以,按位异或也是如此:

1
2
3
4
   0010 1010
^ 0001 0111
--------------
0011 1101 (=十进制61

左移/右移:左边是一个二进制,右边是要移动的位数

1
2
3
4
5
6
7
8
9
10
    0010 1010
<< 2 (每一位都往左移动了两位,最后两位用0填充)
--------------
1010 1000 (=十进制168

0010 1010
>> 2 (每一位都往右移动了两位,最后两位舍去了)
--------------
0000 1010 (=十进制10

位运算在数学中较少出现,但在程序编写中有许多意想不到的功能,用得好则可以大大提高运行效率。这里简单介绍几种用法,更多用途可以自行探索。

我们发现,左、右移操作就是在二进制数末尾添0/去除位数的操作。类比十进制数添n个0 == $*10^n$ ,对照上例,a<<n即 $a*2^n$ 、a>>n即 $a/2^n$(向下取整)

一些其他的数据类型

除了之前介绍过的数据类型外,C/C++语言还提供更多的内置类型,这里也只是简单介绍:

类型修饰符

C/C++提供类型修饰符对基础的类型进行修饰,以适应不同的应用。
修饰符有:

1
signed unsigned long short

它们可置于char、int、double类型前,改变数据类型,如:

1
2
3
4
5
short int a;//-32768 到 32767
unsigned int b;//0 到 4294967295
unsigned short int c;//0 到 65535
long double d;//16 个字节
unsigned char e;//0 到 255

另外,C/C++语言还提供指针、自定义类型,都是C/C++类型系统的一部分:
C中有:指针、枚举、联合体、结构体
C++中有:指针、引用、枚举、联合体、结构体、、还有模板提供泛型甚至元编程的支持。

typedef

C/C++还提供typedef保留字为数据类型起别名,以增加代码的可读性,如:

1
2
3
typedef int month;
month a,b;
a=8;

此时变量a的类型仍是int,但可以用month进行定义。

switch语句

与if类似,switch语句是C/C++提供的另一种分支结构语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
switch(变量){
case 常量1:
语句1;
break;//可选
case 常量2:
语句2;
break;//可选
...
case 常量n:
语句n;
break;//可选
default:
语句n+1;
}

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
char choice;
int score;
cin>>choice;
switch(choice){
case 'A':
score = 100;
break;
case 'B':
score = 80;
break;
case 'C':
score = 70;
break;
default:
score = 0;
}
cout<<score;

即:选A得100分、选B得80分、选C的70分,其他选项不得分。

其中的变量须是整型、字符型或枚举型,满足条件即会进入对应的case语句,执行其中的语句。须注意:break语句为可选,如果删除则执行完case中的语句后将不会退出switch语句,而是执行下面的case语句,可自行验证。

switch语句的功能与对应的if-else语句等价,虽然switch语句语法略复杂,但在效率上switch语句会略优于if-else语句,也有更好的可读性和可维护性。因此,可以视使用场合灵活运用两种分支语句。