c++ 的特点: 抽象、封装、继承、多态。针对这四个特性,C++引入一系列的特征。
初学者一般说成员函数,后面熟悉了要纠正为方法(method)
explicit
的作用是用来声明类构造函数是显示调用的,而非隐式调用,所以只用于修饰单参构造函数。因为无参构造函数和多参构造函数本身就是显示调用的。
以我目前的理解,显式构造就是使用函数()
,当然,这个函数是类的构造函数,而且还是单参的,
#include <iostream>
#include <string>
#include <iostream>
class Explicit
{
private:
public:
Explicit(int size){ // 可以使用explicit关键词进行修饰
// 格式: 如 explicit Explicit(int size)
std::cout << "the size is " << size << std::endl;
}
Explicit(const char *str){ // 也可以使用explicit 关键字修饰
std::string _str = str;
std::cout << "the str is " << _str << std::endl;
}
Explicit(const Explicit& ins){
std::cout <<"the Explicit is ins" << std::endl;
}
Explicit(int a, int b){
std::cout << "the a,b is " << a << b << std::endl;
}
};
int main()
{
Explicit test0(15);
Explicit test1 = 10;// hidden call or implicit conversions
Explicit test2 = ("hello,yubo");
Explicit test3 = "hello,yubo";// implicit conversions
Explicit test4 = (1, 10);
Explicit test5 = test1;
}
如果上面类中的两个构造函数使用explicit
修饰,像主函数中用=
赋值就不会被允许,报错如下:
explicit.cpp: In function ‘int main()’:
explicit.cpp:33:19: error: conversion from ‘int’ to non-scalar type ‘Explicit’ requested
Explicit test1 = 10;// hidden call or implicit conversions
^~
explicit.cpp:35:32: error: conversion from ‘const char [11]’ to non-scalar type ‘Explicit’ requested
Explicit test2 = ("hello,yubo");
^
explicit.cpp:36:19: error: conversion from ‘const char [11]’ to non-scalar type ‘Explicit’ requested
Explicit test3 = "hello,yubo";// implicit conversions
^~~~~~~~~~~~
explicit.cpp:38:21: error: conversion from ‘int’ to non-scalar type ‘Explicit’ requested
Explicit test4 = (1, 10);
~~^~~~~
这是c++的一个class特性,来看一个基本的代码:
class BaseClass{
public:
void disp(){
cout << "Function of Parent Class\n";
}
};
class DerivedClass: public BaseClass {
public:
void disp(){
cout << "Function of Child class\n";
}
};
int main(){
DerivedClass obj = DerivedClass();
obj.disp();
return 0;
}
请注意在实例化时,我们使用的那个类就是调用哪个类,在这个例子中,打印的是,Function of child
.如果
,使用BaseClass obj则会打印父类的成员函数。
这里既然提到函数重载(Function overriding),就多说一下吧。函数重载说简单些基本就是让类中的方法在不同的 情况下可以自己去处理一些问题。比如,如果我们的方法所处理的事务处理的参数可能不一致怎么办?这个时候我们 就函数重载了。以下面为例:
class Sum{
public:
int add(int a, int b){
return a + b;
}
int add(int a, int b, int c){
return a + b + c;
}
};
我们可以使用下面的语句进行调用:
cout << obj.add(1,2) << obj.add(1,2,3) << endl;
对操作符
进行操作也是类似的。
按照书上的理解,this
指针保存了目前 object 的地址,通过这个地址,你可以访问很多普通手段无法访问的数据。
class Demo {
private:
int num;
char ch;
public:
void setMyValue(int num, char ch){
this->num = num;// this is number variable
this->ch = ch; //so, we have to use this
}
void displayMyValues(){
cout << num << endl;
cout << ch << endl;
}
};
int main(){
Demo obj;
obj.setMyValue(100, 'A');
obj.displayMyValues();
return 0;
}
这个例子就很好的解释了this指针在访问成员变量的方法。
这是我第一次看到类似的说法,而且还是很有意思的。这个方法也就是在创建实例之后,我们可以 连续调用其中的成员函数.那么,为什么会使用这个方案,现在还不得而知。
class Demo {
private:
int num;
char ch;
public:
Demo &setNum(int num){ // Here, function prototype
this->num = num; // class name and "&"
return *this;
}
Demo &setCh(char ch){
this->num++;
this->ch = ch;
return *this;
}
void disPlay(){
cout << num << endl;
cout << ch << endl;
}
};
int main()
{
Demo obj;
//obj.setNum(100).setCh('A');
obj.setCh('B').setNum(99);
obj.disPlay();
return 0;
}
在时候这个方案的时候,我们可以清晰的看到,在class中是如何声明的,尤其注意的是,Demo &f()
.还有一点就是调用函数的顺序没有特别的要求,谁也可以在前面,谁也可以在后面。
2020’s new year is very hard for China.It will always be keep in my mind and contribute my best effort to suort work.
在读书期间,由于自己的研究项目是xdp,该项目必须使用llvm产生bpf架构的代码,所以,也算初步接触了llvm.来到新 的单位,llvm的使用的机会大大增加,所以,必须在最短的时间内能够切入进llvm的开发。
llvm是c++编写的,所以还得夯实自己的c++基础,不管怎样,需要什么就去学习什么。
cmu.edu 这篇ppt介绍了llvm的最基本的语法、规则,需要总结。
ibm工程师的一个实践课程ibm
根据这篇article
int sum(int a, int b) {
return a+b;
}
上面代码代码保存为sum.c
clang sum.c -emit-llvm -c -o sum.bc
clang sum.c -emit-llvm -S -c -o sum.ll
llvm-as
汇编代码产生二进制(bitcode)
llvm-as sum.ll -o sum.bc
llvm-dis
llvm-dis sum.bc -o sum.ll
下面以汇编代码进行简单的分析:
; ModuleID = 'sum.c'
source_filename = "sum.c"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
llvm所处理的IR最高层的类为Module
。上面我们可以知道的另外的信息是该计算机的架构为小端(e),
如果为大端的话,则为大写字母E,
; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @sum(i32, i32) #0 {
%3 = alloca i32, align 4
%4 = alloca i32, align 4
store i32 %0, i32* %3, align 4
store i32 %1, i32* %4, align 4
%5 = load i32, i32* %3, align 4
%6 = load i32, i32* %4, align 4
%7 = add nsw i32 %5, %6
ret i32 %7
}
define dso_local i32 @sum(i32, i32) #0 {
这条语句返回一个类型为i32的值,两个i32的参数,局部变量
需要 %
符号作为前缀,全局变量使用@
作为前缀。
下面几个也很重要。
任意大小的整数以iN的形式(i32, i64, i128)。
浮点类型(32-bit单精度float和64-bit双精度double)
数组以 x >
的形式。
Android是一个特别大的软件项目,我们只有在git的管理基础上再进行一次封装。
在Ubuntu上安装repo一般有两种方案,一种是源码,不过这种方案我没有成功;第二种方案是直接通过命令apt install repo
即可。
link
link
在我的工作场景下是使用这么一个命令的。
repo init -u ssh://[email protected]:29418/platform/manifest -b zhimo-mr1-dev --repo-url=ssh://[email protected]:29418/tools/git-repo --reference=/home/local_mirror
这里有很多重点,需要简单地记录下。其实,一个简单的repo是这样的:
repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest
请不要在意URL,这玩意一通百通。如果需要某个特定的 Android 版本列表:
repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest -b android-4.0.1_r1
因为我们的开发分支是xx,所以不太一样,这一点需要注意。 初始化成功的界面如下:
repo has been initialized in /home/user/src
在上面的第一条命令中,需要关注的就是-b
和--reference
选项,后者是指向了一个本地仓库,这样就能大大减轻服务器的压力,这个参数的作用还可以体现在下载内核源代码时。
和直接从Gerrit服务器下载相比优点如下:
代码下载时间大大缩短,基本上就是直接checkout了。
节省虚拟机磁盘空间,因为git相关的二进制文件都链接到镜像中了
降低Gerrit负载
可以用 -m 参数来选择获取 repository 中的某一个特定的 manifest 文件,如果不具体指定,那么表示为默认的 namifest 文件 (default.xml)
repo init -u git://android.git.kernel.org/platform/manifest.git -m dalvik-plus.xml
假设,我的分支是别人已经更改过的,那么,我想使用原生的安卓源代码,怎么办?加个参数:
-m aosp_base/android-10.0.0_r20.xml
这里有一个问题,比如我clone aosp代码时指定的是 -b r20
,但是,当在开发途中,突然告知要
切换到r25版本(这里的r25只是一个分支名字,实际中可以叫任何一个名字)。那么,我就删库从头下
吗?事实上是不必的,在aosp的root目录下,重新指定分支即可.
repo init -u ssh://[email protected]:29418/platform/manifest -b zhimo-mr1-dev --reference=/home/local_mirror
这里是不会覆盖的,而且,在我的测试的时候,在一个分支上重新下载分支,同步时还会默认使用之前的标志,比如,自动更新:
repo sync -cdj4 --no-tags
当所有的项目切换到新的分支上,这个sync
就已经完成了.但是,如果你在本地的代码上有一些改动,则会提示:
Fetching projects: 100% (747/747), done.
art/: discarding 1 commits
bionic/: discarding 1 commits
build/blueprint/: discarding 1 commits
build/make/: discarding 1 commits
build/soong/: discarding 3 commits
你肯定不希望自己辛辛苦苦修改的代码完全丢弃了啊,那么如何更新呢?
那么,如何完成代码的同步更新?
一个标准的命令是:
repo sync
我们用的命令是:
repo sync -cdj4 --no-tags
这样就可以不必下载过多的tag
江湖救急,将git push
这个命令放在这里以备不时之需。
git push origin HEAD:refs/for/git-repo-branch
在aosp中,进入到每个repo中,前期的操作与正常的git操作是一模一样的,只是在提交 commit的时候,使用上面的命令,后面的repo branch要和相匹配的git repo branch相对应。
使用命令:
du -h --max-depth=1
看一下这个工程的大小:
user@host037-ubuntu-1804:~/src$ du -h --max-depth=1
677M ./.repo
76M ./art
44M ./bionic
15M ./bootable
15M ./build
1.4G ./cts
26M ./dalvik
405M ./developers
132M ./development
1.8G ./device
7.7G ./external
1.9G ./frameworks
185M ./hardware
1.2M ./kernel
84M ./libcore
372K ./libnativehelper
773M ./packages
752K ./pdk
4.5M ./platform_testing
32G ./prebuilts
27M ./sdk
500M ./system
398M ./test
102M ./toolchain
1.7G ./tools
50G .
一些版本控制的信息在.repo
目录下。
需要重点关注下,.repo/
目录下manifest.xml
文档,它记录了整个项目的构成什么的。
这个命令提供了一个全局的git库的概述,主要包括各个分支在某个仓库的情况,如下:
vimer@host:~/src/aosp_art/art$ repo info
Manifest branch: zhimo-aosp
Manifest merge branch: refs/heads/zhimo-aosp-d_art
Manifest groups: all,-notdefault
----------------------------
Project: platform/art
Mount path: /home/vimer/src/aosp_art/art
Current revision: 9e22b01ffc829a3cdf9e66e07d6afd30eb1e3a7e
Local Branches: 5 [vimer_dev, run_test_yuzhen, run_test_0817, assembler_dev, fmv-s_dev]
以art为例,与art有关的代码是: 1.art目录 2. libcore目录,包含 jdk相关源代码 3.libnativehelper目录,包含JNI相关代码
frameworks/base/cmds/am、frameworks/base/core、frameworks/base/include
目录,包含Zygote相关的源代码。这个函数还是挺特殊的,特意记录下。先看代码:
int main()
{
char str[80] = "This is my website";
const char s[2] = " ";
char* token;
token = strtok(str, s);
while(token != NULL){
printf("%s\n", token);
token = strtok(NULL, s);
}
return 0;
}
这里有一个违反直觉的使用方法,在while循环中,居然在使用strtok时传递给NULL参数,
这不对啊,那样的话,指针指向什么地方了?原来在第一次分割后
根据分隔符已经产生了”this”,也就是token指向了”this”,但是,str
的指针存在一个静态
变量中,后面使用strtok函数传递给的参数NULL,只是一个符号。
以上,就是使用strtok
的基本规则。
从接触linux至今,至少见到了Makefile编译系统,但是,在不同的平台上,这个Makefile 还需要进一步改写,这严重制约了项目的代码效率(特指维护方面)。CMake的提出就是解决这个问题的。
首先编制一个简单的c程序,命名为add.cc.
#include <stdio.h>
#include <stdlib.h>
int add_summary(int a,int b)
{
return (a + b);
}
int main()
{
int x,y;
printf("Please input two numbers and be carefully overflow\n");
scanf("%d %d", &x, &y);
printf("The sum of two nums is %d\n", add_summary(x, y));
}
在相同目录下,然后编写一个CMakeLists文件。
swin:~/test/cmake$ cat CMakeLists.txt
# CMake不区分大小写
# CMake最低的版本号
cmake_minimum_required (VERSION 3.0)
# 项目信息,与包含工程的目录一直
project (cmake)
# 指定生成文件及源文件
add_executable (cmake add.cc)
$: ~/test/cmake$ ls
add.cc CMakeLists.txt
然后在本地目录cmake .
cmake .
-- The C compiler identification is GNU 7.4.0
-- The CXX compiler identification is GNU 7.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/yubo/test/cmake
会生成:
yubo@win:~/test/cmake$ ls
add.cc CMakeCache.txt CMakeFiles cmake_install.cmake CMakeLists.txt Makefile
借着Make:
win:~/test/cmake$ make
Scanning dependencies of target cmake
[ 50%] Building CXX object CMakeFiles/cmake.dir/add.cc.o
[100%] Linking CXX executable cmake
[100%] Built target cmake
直接生成了可执行文件cmake:
swin:~/test/cmake$ ls
add.cc CMakeCache.txt cmake_install.cmake Makefile
cmake CMakeFiles CMakeLists.txt
看到Makefile文件,你就可以接着使用make命令的其他选项了.
只需修改CMakeLists.txt即可。
# 第一个参数是可执行文件的名字,然后依次将工程所需的源文件添加
# 进去
add_executable(cmake, add.cc, sub.cc)
从工作量上来说,如果源文件很多的话,这个任务还是很艰巨的。更省事的方法是使用aux_source_directory
命令,该命令会将指定目录下的所有源文件,以特定变量名保存结果。
# 查找当前目录下的所有源文件
# 并将结果保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 然后指定生成目标
add_executable(cmake ${DIR_SRCS})
假设一个项目由多个目录构成,这也是在项目中非常常见的。上面我们将加法的 程序分离出去,编译成一个静态库。
假设只需要某个库的头文件,也可以通过Cmake控制。
vimer@host:~/git/AviSynthPlus$ mkdir avisynth
vimer@host:~/git/AviSynthPlus$ cd avisynth/
vimer@host:~/git/AviSynthPlus/avisynth$ ls
vimer@host:~/git/AviSynthPlus/avisynth$ cmake ../ -DHEADERS_ONLY:bool=on
-- Install Only Headers: ON
-- Configuring done
-- Generating done
-- Build files have been written to: /home/vimer/git/AviSynthPlus/avisynth
vimer@host:~/git/AviSynthPlus/avisynth$ make install
通过-DHEADERS_ONLY
可以控制这个行为。
首先说明下的是,«深入理解计算机系统»可以算是一本计算机科学领域中 的经典教材。其实在本科后半段开始,一直就关注这本书,奈何自己的自制力没有 让自己坚持下来。时至今日,到了2020年的第一周,这个新年新气象如何表示?只有 看书,将过去一段时间浪费的时间补回来,我想,就这样坚持下去一年或者两年看看会 发生什么事情。
另一个问题是这本书的内容太广泛,在进行总结的时候我竟然找不到一个合适的归类, 你说放到system?这个category本身就有一些问题。新建一个csapp?可以,但是那样导致的 一个问题就是以后其他的书也要新开一个,这样就会导致category的条目过长。
好吧,经过这个疑虑之后,我先放到book下面,但是命名方式为 书名 + 章节。后期如有 参考,尽量按照这个方式查找。总体而言,这是有关这本书这章节的一个总结。
编译过程序的人都知道,我们的机器码在最底层就是一串数字,但是这个数字是怎么来?这就 需要一个转化的过程。而且,这个过程也需要遵循一定的逻辑规则(或者硬件规则)。
影响这个数字表示的一个关键因素就是字长(word),其取决于你的计算机系统。比如,一个int型 在32位系统上占用4个字节(4 * 8bit = 32bits). 一个指针也为32bits,则指向的地址空间为2^32-1 bits,也就是4GB.
gcc -std=c99 prog.c
指定c的编译标准。
对于一串二进制来说,从右往左数(right-left),每4位组成一个十六进制,使用0x表示。比如,01111010(b)对应的 二进制为 0x7A。如果左边不足4位,则补0凑齐。011(b) == 0011(b) ==> 0x3.
说明,b
代表二进制。
为了方便记忆,最好需要记住三个Hex:
A
== 1010
C
== 1100
F
== 1111
记忆特点,这三个Hex对应的二进制中1的个数为偶数,其相邻的Hex分别加1或者减1.
如果为了可移植性,可以使用sizeof(int)
.注意short int
为2bytes,char为1byte
.
比如说,一个int型变量x的地址为0x100,在c语言中,这个说法标准的做法是:
int x = 10;
int pointer = &x;
printf("the address of x is %d(decimal) or %x(hex)", x, pointer);
printf("the address of x is %d(decimal) or 0x%x(hex)\n", x, &x);
在我的机器上显示:the address of x is 10(decimal) or 6fd1f520(hex).那么, 一个int型占用的字节为4,那么,x占用的hex连续起来就是6fd1f521、6fd1f522、6fd1f523, 为了解释方便,我们暂时使用0x100、0x101、0x102和0x103表示。假设在0x100的位置上表示的 int型变量代表的值为0x01234567,若:
addiress:0x100---0x101---0x102---0x103
value(hex):01 23 45 67
表示方式,则为Big endian.这里补充一点,对于数值0x01234567,最左边的01为最高有效位(Most Significant Bit, 简称MSB),最右边的76为最低有效位(Least Significant Bit,简称LSB),这符合我们人类在书写他们的 方式,尽管不符合人类的阅读习惯.
小端(Little endian)则相反,也就是LSB处于地址的起始地方:
address: 0x100----0x101----0x102----0x103
value(Hex) 67 45 23 01
这一点对我而言经常容易混,那应该怎么记呢?或者可以去搜搜这个名称的由来。
以下代码来自于csapp.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
typedef unsigned char *byte_pointer;
void show_bytes(byte_pointer start, int len){
int i;
for(i = 0; i < len; i++){
printf(" %.2x", start[i]);// 以16进制最少打印两位
}
printf("\n");
}
void show_int(int x){
printf("show int: ");
show_bytes((byte_pointer) &x, sizeof(int));
}
void show_float(float x){
printf("show float: ");
show_bytes((byte_pointer) &x, sizeof(float));
}
void show_pointer(void *x){
printf("show pointer: ");
show_bytes((byte_pointer) &x, sizeof(void *));
}
void test_show_bytes(int val)
{
int ival = val;
float fval = (float) ival;
int *pval = &ival;
show_int(ival);
show_float(fval);
show_pointer(pval);
}
int main(){
int x = 12345;
test_show_bytes(x);
return 0;
}
在函数show_bytes
中,我们看到,在将一个指针&x强制转换为unsigned char \*
,按照书上的解释,
这个转换的作用是将指针指向的内容转换为一串字节序而不需要理会这个指针原来的内容。这是一个hint:
比如在操作系统中需要经常知道某个对象的地址,可以使用这个方案去做测试。
输出的结果为:
show int: 39 30 00 00 // int 12345 的二进制为: 0b 0011 0000 0011 1001
show float: 00 e4 40 46
show pointer: 38 ec 0a fc fe 7f 00 00
这里解释下, 尽管根据上面的链接中我们知道%x是HEx的格式符,但是根据OS,”%x”控制符期待的数据类型为unsigned int
,
所以,在上面的代码中,我们尽可能地强制类型转换为(unsigned char*), 我目前还不能确认的是,是不是这个int导致
输出2个hex(尽管有%.2x的控制).
十进制数字12345对应的hex为0x3039.说明,我这个测试机器为Little endian.另外,上面的int和float之间的数字 并不是无意义的一串数字,比如,按照我们正常的书写顺序:
0x 00 00 30 39 => 00000000 00000000 00110000 00111001
0x 46 40 e4 00 => 01000110 01000000 11100100 00000000
-> ... <-
你会看到这里有几位是匹配的。
这里需要注意的是,字符串是没有大小端之分的。比如,
const char *s = "abcde";
show_bytes((byte_pointer) s, strlen(s));
的结果为
print the string sequence
61 62 63 64 65
因为,对于字符串而言,只有第一个字符的地址具有作用,后面的就是具体内容了。
对于普通数字字符而言,注意是字符,’0’,’1’,’2’,’x’…对应的是0x30,0x31,0x32,0x3x,就是说, 他们之间有个线性的转换,可以直接加0x3确定。字符A的Hex为0x41,十进制为65,”Z“加24. 字符”a”的hex为0x61,十进制为97。
在这个Boolean ring中,有一个重要的符合需要去掌握’^’.
0 ^ 0 = 1 ^ 1 = 0;
(a ^ b) ^ b = a; //出现两个一样的,消除掉
(a ^ b) ^ a = b;
如果从LSB开始到MSB,数字位上为1的话,说明了这个位置的数值。两个二进制串据此可以进行 并、交等集合的运算。
这个方法是不借助第三方,只需要这两个值本身即可。
void inplace_swap(int *x, int *y)
{
int *y = *x ^ *y;
int *x = *x ^ *y; // 等价于 *x = (*x ^ (*x ^ *y)) = *y,so *x = *y,即*y传递给了*x
int *y = *x ^ *y; // 等价于 *y = (*y ^ (*x ^ *y)) = *x
}
如果~0xFF
这样不指定bit的位数,那么低 8 bits为0,剩余为1,相反,0xFFFFFF00只会针对32 bits
的数字有效.
原数 x = 0x87654321,若要使最低2位数字不变,其余变为0,则相应的表达式为x & 0XFF</>
若要求得0X787ABC21(这个hex与x是一种什么关系呢?对了,就是各个位上与原数相加为F除了最后两位,有待求证是不是反码:))
那么,这个表达式为x ^ 0XFFFFFF00
.
例如, z = x & ~m
表达的含义是m中有1的位置z置为0.
还有一个公式,这个也需要更多方面的总结。x ^ y = (x & ~y) | (y & ~x)
这里想说的是,逻辑运算符的结果只有0x00和0x01两种结果。
!0x41 = 0x00
!0x00 = 0x01
!!0x41 = 0x01
0x45 && 0x69 = 0x01
0x45 || 0x69 = 0x01
使用一段由bit操作和逻辑运算的语句,实现下面c语句的作用:
if (x == y)
true
Results: !(x ^ y)
这也是c语言中一个重要的特性,左移只有一种模式 ‘«‘,然而右移分为逻辑-logical和算术arithmetic 移动。算术右移就是考虑MSB(最高有效位的符号)插入到因移动而产生的空位。c语言还有一个默认的规则: 对于unsigned 数据而言,一定是logical shift,而对于signed的数据而言,则要区分下。
移位操作符的优先级是很低的,比如,1 « 2 + 3 « 4,实际的效果是1 « (2 + 3) « 4的,而不是咱们想象的 那个优先级。
实际上,B2U(w)(x)的含义是 Binary to Unsigned,w代表的是位数。
UMax
w = 2w - 1 ,即范围是在[0…0]到[1..1]范围内。
比如, UMax
4 = 2^4 - 1 = 15
这里的T
就是 Two’s complement的意思(Binary to T),其大小要加上最高位的值,”1”表示负的,”0”表示正的。
其公式可以为 B2Tw = -Xw-12w-1 + sum(from i = 0 to i = w-2) x乘以2^(i)
这是其数学表达式为这样表示,如果用我们更方便的计算则是,
-
号,则其值就为大小了。TMin(4) = [1000], TMax(4) = [0111]
,则T(4)的值从-8到7.
其实上述第二点加重了记忆负担,我觉得书上的介绍就挺好的。最高位就是带负号的权重,除了最高位其他都是正的, 这样理解起来更好理解