信息的表示和处理——信息存储

写在前面

本文是对 《csapp》 第二章 “信息的表示和处理” 做的笔记,读者有兴趣可以去读原文,以下内容全来自对原文的总结。

信息存储

计算机大多数采用 8 bit 的块,作为最小单元寻址。每个块都有唯一的数字标识,也就是地址。

我们可以将整个虚拟内存看作是一个巨大的 char 类型数组。

进制

人类所擅长使用的是 十进制,所谓十进制,就是 逢十进一。按照相同的定义方式,我们可以定义出 X 进制

在这里插入图片描述

转十进制

  • 十进制: 321 = 3 × 10 2 + 2 × 10 1 + 1 × 10 0 {321=3 \times \mathop{{10}}\nolimits^{{2}}+2 \times \mathop{{10}}\nolimits^{{1}}+1 \times \mathop{{10}}\nolimits^{{0}}} 321=3×102+2×101+1×100
  • 八进制: ( 501 ) 8 = 5 × 8 2 + 0 × 8 1 + 1 × 8 0 = 321 { \left( \mathop{{\left. 501 \right) }}\nolimits_{{8}}=5 \times \mathop{{8}}\nolimits^{{2}}+0 \times \mathop{{8}}\nolimits^{{1}}+1 \times \mathop{{8}}\nolimits^{{0}}=321\right. } (501)8=5×82+0×81+1×80=321
  • 十六进制: ( 141 ) 16 = 1 × 16 2 + 4 × 16 1 + 1 × 16 0 = 321 \mathop{{ \left( 141 \right) }}\nolimits_{{16}}=1 \times \mathop{{16}}\nolimits^{{2}}+4 \times \mathop{{16}}\nolimits^{{1}}+1 \times \mathop{{16}}\nolimits^{{0}}=321 (141)16=1×162+4×161+1×160=321
  • 二进制: ( 1   0100   0001 ) 2 = 1 × 2 8 + 1 × 2 6 + 1 × 2 0 = 321 \mathop{{\left. { \left( 1\text{ }0100\text{ }0001\right. } \right) }}\nolimits_{{2}}=1 \times \mathop{{2}}\nolimits^{{8}}+1 \times \mathop{{2}}\nolimits^{{6}}+1 \times \mathop{{2}}\nolimits^{{0}}=321 (101000001)2=1×28+1×26+1×20=321

再次强调,计算机只存储 二进制,以上风格迥异的数字,在计算机底层都是一串相同的二进制序列。

在 c/c++ 中,没有前缀的则是十进制,0 开头的则是八进制,0x 开头的则是十六进制,无二进制字面量表示。

int main() {
    int a = 321;    // 十进制
    int b = 0501;   // 八进制
    int c = 0x141;  // 十六进制
    printf("%d %d %d \n", a, b, c);
}

二进制与十六进制、八进制转换

二进制 -> 八进制:3 个二进制位顶 1 个八进制位;八进制 -> 二进制:1 个八进制位顶 3个二进制位。

2 进制:101 000 001
8 进制:5   0   1 

恰好三位划分,可一一映射。

二进制 -> 十六进制:4 个二进制位顶 1 个十六进制位;十六进制 -> 二进制:1 个十六进制顶 4 个二进制位

2  进制:0001 0100 0001
16 进制:1    4    1

恰好四位划分,可一一映射。

大小端序

前面我们说到,我们将整个虚拟内存看出一个 char 数组,一个 char 类型是一个字节的。而几乎所有数据类型都超过了一个字节,这时我们要如何将一个多个字节在内存中排列呢?在计算机中有两种排列方式。

  • 大端:最高有效字节在前
  • 小端:最低有效字节在前

0x1234567 为例:

在这里插入图片描述

在此前我们强调过,计算机内部是一串二进制序列,本身是没有意义的。如果想要将其产生某种意义,那就需要对其做出解释。双方的解释不同,那就毫无意义、没法交流了(鸡同鸭讲)。

大部分的计算机内部都采用小端序,而网络传输统一规定使用大端序。

联合体判断大小端序

#define BIG   0
#define SMALL 1

int is_little_endian() {
    union {
        int a;
        char b;
    } u;
    u.a = 1;
    return u.b == 1 ? SMALL : BIG;
}

利用联合体特性,联合体共用一块内存

小端存储时。u.a = 1最高位直接置为 1,自然不等于占用最低字节的 u.b

大端存储时。u.a = 1最低位直接置为 1,所以会等于占用最低字节的 u.b

表示字符串

c 中的字符串是以 '\0' 结尾的字符串数组,通过结尾标识就能断定字符串在哪儿结束。

NULL'\0' 都是 0

如下面计算字符串长度函数。

int str_len(char str[]) {
    int len = 0;
    while (str[len]) len++;
    return len;
}

但是这种字符串表示是非常不安全的,如果数组末尾的标志符丢失,将无法断定字符串的结束位置。

所以有些字符串的实现,会将其封装成一个结构体,用第一个字段标志长度。或用数组的第一个元素指明字符串的长度。

结构体也是按序排列的,如果你第一个字段就丢了,那就相当于没有过这个串。

位运算

与:&,同一出一

或:|,有一出一

非:~,按位取反

异或:无进位加法

在这里插入图片描述

位运算有很多骚操作,再此不做赘述,详情请看我以前写的文章:传送门

用作集合

二进制序列中,将 x 位上置为 1,就将其看成在这个集合中包含 x 这个元素。所以一个 char 可以视为最多包含 0~7 的集合。

a = [0110 1001] ---> A = {0, 3, 5, 6}
b = [0101 0101] ---> B = {0, 2, 4, 6}
交:a & b = [0100 0001] = {0, 6}
并:a | b = [0111 1111] = {0, 1, 2, 3, 4, 5, 6}
补:~a    = [1001 0110] = {2, 3, 5, 7}

用位运算对一串二进制序列位上的操作,充当对一个集合的增删改查操作,这在计算机中很常见。这类还有一个很高大上的名字,掩码

移位运算

算术右移:>> ,往右移动,低位直接丢弃,高位由符号位补充。

逻辑右移:Java 中是 >>> ,c\c++ 中没有。往右移动,低位直接丢弃,高位由 0 补充。

左移:往左移动,高位直接丢弃,低位由 0 补充。也分算术和逻辑,但效果是一样的。

对于算术左移 k 位或右移 k 位,最直观的就是对一个数乘 2 k \mathop{{2}}\nolimits^{{k}} 2k 或除 2 k \mathop{{2}}\nolimits^{{k}} 2k。对除来说又有略微的不同,再此不做缀诉,详情请看我以前写的文章:传送门

如果对一个数的移位 k 超过数本身的长度 w,那么就会做 k mod w 移位。

优先级问题:不建议写很复杂的移位运算,如果要写请打上括号,很多人都搞不清优先级。

  1. + - 的优先级都比移位要高
  2. 移位运算是从左到右结合的

如:1 << 2 + 3 << 4,很多人都弄不清怎么计算,正确的结合方式是 (1 << (2 + 3)) << 4 ,所以结果是 512

别说上面这种复杂的移位运算,就连 l + r >> 1 的结合方式,具有多年工作经验的程序员来说都分不清。

所以再次建议,不要作复杂的位运算,如果非要做!那么请封装好一个函数或宏。

逻辑运算

&&||!,切不可与位运算中的与&|~ 混淆。前者是对两个数的布尔运算,后者是对两个数的位运算。

&& 运算与 || 运算具有短路属性。所谓短路,即前面的布尔运算能确定整个布尔运算的结果,则后半段就不会执行。

比如:

  • a || ba 如果为真,就不需要再做后面的 b 部分。
  • a && ba 如果为假,就不需要再做后面的 b 部分。

再比如,下面的算法,当 i = n 时,后面的 str[i] != c 就会被短路,所以不存在越界的风险。

// 在一个字符串中搜寻某个字符的索引并返回。
int search(const char str[], int n, char c) {
    int i = 0;
    while(i < n && str[i] != c) i++;
    return i;
}

再比如,求 1 + 2 + ... + n 的和,不能使用乘除法、forwhileifelseswitchcase 等关键字及条件判断语句 (A?B:C) 实现。

int getSum(int n) {
    int res = n;
    (n > 0) && (res += getSum(n - 1)); // 利用短路运算终止递归
    return res;
}

参考资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值