diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..d0d1a8e --- /dev/null +++ b/404.html @@ -0,0 +1,686 @@ + + + +
+ + + + + + + + + + + + + + +Abstract
+GNU Make是一个用于自动化编译的工具,它可以根据文件的依赖关系自动执行编译任务.本文将介绍GNU Make的基本使用方法.
+为啥要学Make?
+属于是为了这盘醋包了顿饺子,由于数据结构基础这门课的大作业或多或少需要用到多文件编译,我干脆就希望使用Make进行自动化编译,而且在系统课上接触了Makefile的编写,所以就有了这篇文章.
+gcc的编译过程可以简要分为四个阶段:预处理、编译、汇编、链接.
+gcc编译工具链是以gcc编译器为核心的一整套工具,主要包含以下三部分内容:
+预处理阶段的主要任务是处理源文件以#
开头的预处理指令,比如#include
、#define
等.这里主要是将#include
的一些头文件与宏定义进行展开,生成一个.i
文件.
预处理过程输入的是C的源文件,输出的是一个中间/预加载文件,这个文件还是C代码.此阶段使用gcc参数-E
,同时参数-o
指定了最后输出文件的名字,下面的例子就将main.c
文件经过预处理生成main.i
文件:
编译过程使用gcc编译器将预处理后的.i
文件通过编译转换为汇编语言,生成一个.s
文件.这是gcc编译器完成的工作,在这部分过程之中,gcc编译器会检查各个源文件的语法,即使我们调用了一个没有定义的函数,也不会报错,
编译过程输入的是一个中间/预加载文件,输出的是一个汇编文件,当然,直接以C文件作为输入进行编译也是可以的.此阶段使用gcc参数-S
,具体例子如下:
汇编阶段的主要任务是将汇编语言文件经过汇编,生成目标文件.o
文件,每一个源文件都对应一个目标文件.即把汇编语言的代码转换成机器码,这是as汇编器完成的工作.
汇编过程输入的是汇编文件,输出.o
后缀的目标文件,gcc的参数-c
表示只编译源文件但不链接,当然,我们也可以直接输入C源文件,就直接包含了前面两个过程.
Linux下生成的.o
目标文件、.so
动态库文件以及下一小节链接阶段生成最终的可执行文件都是elf格式的, 可以使用 readelf 工具来查看它们的内容.
从 readelf 的工具输出的信息,可以了解到目标文件包含ELF头、程序头、节等内容,对于.o
目标文件或.so
库文件,编译器在链接阶段利用这些信息把多个文件组织起来,对于可执行文件,系统在运行时根据这些信息加载程序运行.
最后将每个源文件对应的.o
文件链接起来,就生成了一个可执行程序文件,这是这是链接器ld完成的工作.
例如一个工程里包含了A和B两个代码文件,在链接阶段,链接过程需要把A和B之间的函数调用关系理顺,也就是说要告诉A在哪里能够调用到fun
函数,建立映射关系,所以称之为链接.若链接过程中找不到fun
函数的具体定义,则会链接报错.
链接分为两种:
+--static
,它在编译阶段就会把所有用到的库打包到自己的可执行程序中.所以静态链接的优点是具有较好的兼容性,不依赖外部环境,但是生成的程序比较大.Makefile的规则包括两个部分:一个是依赖关系/prerequisites,另一个是生成目标的方法/command.在Makefile中,规则的顺序是很重要的,Makeflie中有且仅有一个最终目标,其他目标都是这个目标连带出来的,一般来说,定义在第一条规则的第一个目标就是最终目标,make完成的就是这个目标.
+但是我们经常会遇见make a.o
这样的命令,make后可以跟着一个或多个target,a.o
作为make的参数,指定了执行的内容,优先级比Makefile里边的定义要高.换句话说,倘若make后边有目标,这个目标就是最终目标,make后边没目标,默认执行Makefile的第一个目标.
规则的语法是这样的:
+ +或者这样的:
+ +targets是文件名,可以使用通配符,基本来说,我们的目标基本上是一个文件,可以是一个目标文件,可以是一个可执行文件,还可以是一个标签,是多个文件也是有可能的.
+prerequisites是生成该target所依赖的文件或者target.
+recipe是命令行,可以是任意的shell命令,如果其不与target:prerequisites
在一行,那么,必须以 Tab
键开头,如果和prerequisites
在一行,那么可以用分号做为分隔.如果命令太长,我们可以使用反斜杠\
来作为换行符.
规则告诉make两件事:一个是文件的依赖关系,target依赖于prerequisites中的文件;另一个时就会如何生成目标文件,生成规则定义在recipe中,makefile最核心的内容是:
+Makefile中的变量就像是C语言的宏一样,代表着一个文本字符串,在执行的时候会自动展开在所使用的地方,不过,我们可以在Makefile中改变其值.变量可以使用在目标,规则,依赖目标或者其他部分之中.
+在声明变量的时候,我们需要给予其初值,使用的时候需要在变量名前面加上$
符号,最好还用小括号括起来()
,变量的名字可以包含字符,数字,下划线,甚至还可以数字开头,但是不能含有:
,#
,=
或者空字符.
我们可以使用其他变量来构造变量的值,比如foo = $(bar)
,这里的bar
不一定非要是已经定义好的值,我们可以使用后面定义的值,这就很好了嘛,我们可以把变量的真实值退到后面去定义,但是无法避免递归定义,虽然make有能力检测这样的定义:-)
为了避免这个问题,我们使用:=
操作符,对于VAR := value
,右边的value
会在定义的时候就被展开.
Warning
+待更新!
+Abstract
+这份笔记将专注于bash(也就是Bourne Again SHell),暂不涉及其他种类的shell。
+下面代码均以本人 Ubuntu 22.04.3 系统中的bash为例。
+我们所熟悉的图像用户界面/Graphical User Interface/GUI在某些情况下反而限制了我们对计算机的使用,shell为我们提供了一种充分利用计算机的方式,它允许我们执行程序、输入并且获取某种半结构化的输出。
+打开终端,我们就可以使用shell,下面就是我们一般看起来的样子。
+ +其中test
是用户名;testMachine
是主机名;~
是当前工作目录,特别地,~
代表home
;$
是提示符,表示现在的身份不是root用户。在提示符后面,我们可以输入命令,命令最终会被shell解析并且执行。我们现在执行一些基本的命令:
在这个例子中:
+我们不仅可以直接输入类似date
的命令,还可以向shell传递参数,比如echo hello
。
如果我们想让shell输出hello world
,方法之一是使用单引号或者双引号包裹起来,
我们让shell执行了echo
命令
Warning
+未完工!等我学完CS61A后再接着写吧QAQ。
+这个笔记建立的初衷是帮助笔者深刻记忆C语言的语法和特性,也作为加深对C语言的理解的工具(似乎终极目的就是提升程算分数编程能力)使用。
+C语言因为其比较贴近底层,语法精简而高效,扩展性和可移植性强而闻名,因而作为计算机专业学生的第一门语言存在。另外,笔者在学习完C后,学习Python的过程极其愉悦()
+字面量即一个值:
+整型:123
表示十进制的123;0123
表示八进制的123,亦即十进制的83;0x123
是十六进制的123,亦即十进制的291。
字符型:
+一个字符类型相当于一个字节的整型,所以字符类型可以通过整型来表示:char c = 65
。
\
有转义的效果,比如'\n'
表示一个控制字符,而反斜杠只有通过\\
才能表示出来。'\101'
表示A
,而'08'
由于8超过了八进制的范围,这就是两个字符放在了一个单引号里边,是错误的用法,如果写成字符串,"\08"
就表示两个字符:一个空字符和一个8
。\x
后边接在0-9
、A-F
内的字符,可以通过十六进制表示一个字符,不过没有长度限制,遇到范围外的字符就结束,比如\x000041
也是一个字符。字节分配:char
1byte,short
2byte,int
4byte,long
4byte,long long
8byte, float
4byte,double
8byte,pointer
4/8byte。
sizeof
、对齐。?:
。,
。坑:% & <<
不能用在double\float
上。
printf()
的转换说明修饰符¶const
¶重要:声明一个指针只会分配一个给指针变量的空间(这部分空间用来存储它指向的位置的地址值),而不会分配指向的空间。使一个指针可用可以将其它变量取地址赋值给它,这样它指向的位置就是有效的。或者通过 malloc
来新分配一块堆上的内存,malloc
的返回值就是这块内存的首地址,也是你可用的。
坑:二维数组不能退化为二级指针;数组名不能被重新赋值。
+坑:数组是数组,指针是指针(这里指类型)。
+坑:指针相减的意义是计算两个指针相差几个“单位”的距离,而不是将其值简单的相减。比如:
+c
+ int a[] = {1, 2, 3, 4, 5};
+ int *p = a, *q = &a[2];
+ printf("%lu", q-p); // Output: 2
c
+ double a[]={1, 2, 3, 4, 5};
+ printf("%d", (int)&a[3] - (int)&a[0]); // Output: 24
神坑:变长数组不能通过int a[n] = {0};
的方式初始化
字符串其实是以空字符\0
结尾的char
类型数组,因此,我们可以像处理一般数组的方式处理字符串,比如:
char words[81] = "I am a string in an array.";//定义字符串words
+words[8] = 'p';//将字符串的第9个字符改为'p'
+const char* MSG = "This cannot be changed";//定义只读字符串MSG
+
如果要打印MSG[22]
,则输出的是空字符,空字符不是空格,不会在输出窗口占用位置,只是标志字符串数组的结束。
我们一般用三种方法定义字符串:字符串常量、char
类型数组、指向char
类型的指针。被双引号括起来的内容被视为指向该字符串存储位置的指针,这类似于将数组名作为指向该数组的指针。比如以下程序:
我们对字符串用%c%p
进行转换的时候,转换过去的其实是字符串第一个元素的地址和其对应的字符
数组形式的字符串(如char arr1[] = "III"
)在计算机的内存中分配一个内含4个元素的数组,每个元素作为一个字符,且最后一个元素为空字符。先将字符串常量存储在静态存储区中,程序开始运行之后为数组分配内存,初始化数组将静态存储区的字符串拷贝到数组中,编译器将数组名arr1
作为该数组首元素地址的别名,而且作为地址常量,不能被改变。
一般来说,指针形式的定义一般于字符串字面量一起使用,被双引号括起来的内容是字符串字面量,而且被视为字符串的地址。指针形式(如char *pt1 = "III"
)让编译器在静态存储区中分配4个元素的空间,开始运行程序时,编译器为指针变量(*pt1
)留出一个存储位置,该变量最初指向该字符串的首字母,但是它的值可以被改变,即可以使用递增运算符。
由于指针形式字符串的存储形式,一般建议将指针初始化为字符串自变量时使用const
限定符。
编译器可以使用内存中的一个副本来表示所有完全相同的字符串字面量,所以下面程序打印出来的都是"Jey"
由于数组名是一个指针变量,所以不能用str1 = str2
来简单地拷贝数组,这样只会让两个指针指向相同的内存区域。
我们可以定义字符串数组,也就是通过数组下标来访问不多个不同的字符串,有两种方式:使用存储字符串指针的数组或者多维数组:
+const char* strarr1[3] = {
+ "Hello",
+ "Pardon",
+ "Excuse me";
+};
+char strarr2[3][10] = {
+ "Hello",
+ "Pardon",
+ "Excuse me";
+};
+
这两种方式最后实现的效果是几乎一样的,都代表着五个字符串,只使用一个下标时只代表一个字符串。比如strarr1[0]
和strarr2[0]
都代表着字符串 "Hello"
一般来说对数组的操作都是依赖于指针进行的。
+最简单的分配空间的方式就是在 stack 上建立数组变量,而且还只能如此建立
+ +再就是利用C库函数malloc()
分配内存,比如char *name = (char *) malloc (sizeof(char)*8)
这样就可以按照数组形式的字符串来使用字符串了
gets()
函数¶C11标准中,废弃了不安全的gets()
函数,但是大多数编译器为了兼容性,仍然保留gets()
函数。
gets()
函数读取一整行输入,直到遇到换行符,然后丢弃换行符,存储其余字符在传递进来的字符串指针指向的地址上,并在字符的末尾添加一个空字符,使其成为字符串。比如:
puts()
函数经常和gets()
函数一起使用,这个函数用于显示字符串,并且在字符串的末尾添加换行符。
使用gets()
函数时,gets()
函数只知道数组的开始处,而不会检查数组的长度和字符串的长度是否相融洽。如果输入的字符串过长,超出了数组的存储范围,就会造成缓冲区溢出(buffer overflow),读取的数据将一直向后存储,覆盖掉后边内存上的内容,如果这些多余的字符只是占用了未被使用的内存,就不会立刻出现问题,而如果擦写掉了程序中的其余内存,这样就会让程序异常终止,或者出现其他情况。
出现fragmentation fault
的错误的时候,一般是程序试图访问某些未被分配的内存。
gets()
的替代品¶fgets()
和fputs()
函数fgets()
函数接受三个参数:字符串存储的位置、读入字符的最大数量和要输入的文件。
fgets()
函数接受的第二个参数时读入数组的最大数量,如果该参数的值是n
那么fgets()
将读入n-1
个字符,并且在最后加上一个空字符,或者读到第一个换行符号为止。
fgets()
函数的第三个参数指明要读入的文件,如果是从键盘读入数据,那么以stdin
作为参数,或者输入文件指针。
当输入行不溢出的时候,fgets()
函数将换行符放在结尾,这与fputs()
函数的特性相仿:这个函数在打印字符串的时候不会在最后加上换行符。可是如果使用puts()
函数一起使用,那么可能就会发现出现了两个换行。
fputs()
函数接受两个参数:第一个指出要写入的字符串的位置,第二个指出目标写入的位置,如果输出到屏幕上,那么输入stdout
作为参数。
fgets()
返回指向char
的指针,如果一切顺利,函数返回的地址和传入的第一个参数相同,如果传到文件的结尾,将返回一个特殊的指针:空指针(null pointer),这个指针不会指向有效的数据,所以可以用来标识特殊情况。在代码钟可以用数字0
来代替,但是C利用宏NULL
来代替。下面是一个很有意思的例子:
char words[10];
+while(fgets(words,10,stdin) != NULL && words[0]!='\n')
+{
+ fputs(words,stdout);
+}
+
+//Input :By the way,it returns a NULL pointer.
+//Output:By the way,it returns a NULL pointer.
+
这个程序的实际操作过程是:首先fgets()
函数读入9个字符,在后边加入\\0
之后交给fputs()
函数输出,但是此时不输出换行符,接着进入下一轮迭代,fgets()
函数继续读入字符、交给fputs()
函数输出……
gets_s()
函数
s_gets()
函数
我们可以利用fgets()
函数自行创建一个读取整行输入,并且利用空字符取代换行符、或者读取一部分字符,丢弃溢出的字符(其余部分的字符)的函数:
char *s_gets(char *st, int n){
+ char *ret_val;
+ int i = 0;
+ ret_val = fgets(st, n, stdin);
+ if(ret_val){
+ while(st[i] != '\n' && st[i] != '\0')
+ i++;
+ if(st[i] == '\n')
+ st[i] = '\0';
+ else
+ while(getchar()!= '\n')
+ continue;
+ }
+ return ret_val;
+}
+
利用字符串函数,我们可以对函数进行修改,让它更加简洁。
+char *s_gets(char *st, int n)
+{
+ char *ret_val;
+ char *find;
+
+ ret_val = fgets(st, n, stdin);
+ if(ret_val)
+ {
+ find = strchr(st, '\n');
+ if(find)
+ *find = '\0';
+ else
+ while(getchar() != '\n')
+ continue;
+ }
+ return ret_val;
+}
+
如果fgets()
函数返回NULL
,则证明读到文件结尾或者读取错误,s_gets()
函数跳过了这个过程。
我们丢弃多余的字符的原因是:这些多余的字符都存储于缓冲区之中,如果我们下一个要读取的数据是double
类型的,那么就可能造成程序崩溃(因为输入了char
类型甚至char*
类型的数据),丢弃剩余行的数据可以令读取语句和键盘输入同步。
这个函数并不完美,因为它在遇到不合适的输入的时候毫无反应,并且丢弃多余的字符的时候,不会告诉程序也不会告诉用户。但是至少会比gets()
函数安全的多;-)。
scanf()
函数scanf()
函数和%s
转换说明可以读取字符串,但是scanf()
函数在读取到空白字符(包括空格、换行符和空字符)的时候会终止对字符串的读取。scanf()
函数还有另外一种确定输入结束的方法,也就是指定字符宽度,比如%5d
,那么scanf()
将在读取完五个字符或者读取到第一个空白字符后停止。
char words[]={'H','e','y','!'};
由于这个字符数组(不是字符串!)结尾并未有空字符,所以words
不是字符串,如果我们使用这样的代码:put(words)
,puts()
函数由于未识别到空字符,就会一直向下读取、输出后续内存中的内容,这或许是 garbage value ,直到读到内存中的空字符(内存中还是有不少空字符的)。
puts()
函数很容易使用,只需要传入需要输出的字符串的地址就可以了,它在输出的时候会在后边加上一个换行符。但是puts()
函数的返回值众说纷纭:某些编译器返回的是输出的字符的个数,某些编译器输出的是输出的最后一个字符,有的干脆就返回一个非零的整数。
fputs()
函数需要接受两个参数,一个是字符串的地址,另一个是写入的地址:这一般是文件指针,如果需要输出到屏幕上,传入stdout
则可。这个函数的特点在于不会输出换行符。
printf()
函数需要转换说明,它的形式更复杂些,需要输入更多的代码,计算机执行的时间会更长,但是优点在于可以更容易地输出更复杂、更多的字符串。
这里讲的字符串函数是指定义在头文件string.h
内的函数。
strlen()
函数strlen()
函数的实现其实很简单,我们写一个while
循环就好了(),strlen()
函数接受字符串地址,返回一个unsigned int
的值来表示字符串的长度。
重要的是我们可以利用strlen()
函数来得到字符串的一些性质参数,进而更容易实现对字符串的操作,比如我们可以利用下面自行设计的函数来实现字符串的截断:
char *vit(char str[],unsigned int point)
+{
+ unsigned int length = strlen(str);
+ if(point > length - 1)
+ {
+ return str;
+ }
+ else
+ {
+ str[point]='\\0';
+ return str;
+ }
+ }
+ //或者:if(strlen(str)>point)
+ // {
+ // str[point] = '\\0';
+ // }
+
strcat()
函数strcat()
函数接受两个字符串作为参数,用于将两个字符串拼接在一起,更确切地说是将第二个字符串的拷贝附加在第一个字符串的末尾,并且将拼接后的字符串作为第一个字符串,第二个字符串不变。strcat()
函数返回第一个参数。
strcat()
函数和gets()
函数一样,如果使用不当,也会导致缓冲区溢出。但是gets()
函数被废弃的原因在于无法控制用户向程序里边输入什么,但是程序员是可以控制程序干什么的。因此,在经历输入的检查之后,我们认为至少程序是比较安全的,而使用strcat()
函数不当导致缓冲区溢出的情况,被认为是程序员粗心导致的,而C语言相信程序员,程序员也有责任确保strcat()
函数的使用安全。
strncat()
函数为了避免strcat()
函数的不安全的可能,我们类似fputs()
函数那样,添加第二个参数,确定最大添加字符数,这就是strncat()
函数的逻辑。
strncat()
函数接受三个参数,两个字符串指针和最大添加字符量,在加到最大字符量或者遇到空字符的时候停止。
配合strlen()
函数,strncat()
函数可以很好用。
strcmp()
函数和strncmp()
函数首先,我们比较两个字符串的时候,比较的是字符串的内容,而不是字符串的地址,所以我们不能做判断指针是否相等的操作,而利用循环挨个判断还蛮复杂,这就是strcmp()
函数诞生的逻辑。
strcmp()
函数接受两个字符串指针参数,如果字符串内容完全相等(包括大小写),strcmp()
函数就会返回0,否则返回非零值。
在字符串内容不一样的时候,如果第一个字符串的字符在ASCII码在第二个字符串的之前,strcmp()
返回负数,反之返回正数;在某些编译器中,会作更加复杂的操作,也就是返回两字符的ASCII码的差。
strcmp()
函数会一直比较字符是否相同,直到出现不同或者字符串结束,这样的比较方式显得就非常笨重,而strncmp()
函数提供了一种更为灵活的选择:strncmp()
函数接受的第三个整数参数指定了比较到第几个字符(这里从1开始计数 ;-) )比如strncmp(str1,"strings",7)
就指定只查找strings
这七个字符。
strcpy()
函数和strncpy()
函数strcpy()
函数
sprint()
函数
memcpy()
函数
Others.
+strchr()
函数
strrchr()
函数
strstr()
函数
atoi()
函数
Character Classification
+isalpha()
函数
和isalpha()
函数属于一类的函数还有
tolower()
和toupper()
函数
cppreference 上对这两个函数归类为 Character Manipulation 解释是:converts a character to lowercase/uppercase.
+
+static
关键词让变量具有内部链接,同时具有静态存储期。extern
关键词让变量具有外部链接,同时具有静态存储期。作用域描述程序中可以访问标识符的区域,包括:块作用域,函数作用域,函数原型作用域和文件作用域。
+块是用一对花括号括起来的代码区域,包含for
循环、while
循环、do while
循环和if
语句所控制的代码,就算这些代码没有被花括号括起来,这也算是一个块。定义在块中的变量具有块作用域(block scope),它的可见范围只是在块内,或者说从定义处到包含该定义的块的末尾。此外,函数的形式参数虽然在花括号表示的块之前,但还是具有块作用域。只有在块内的语句才能访问具有块定义域的变量。
函数作用域仅仅用于goto
语句的标签,当这个标签首次出现在函数的内层时,作用域也延伸到整个函数。函数作用域有效防止了标签混乱的情况发生,当然更好的处理方式或许是干脆不用goto
语句()
函数原型作用域的作用范围时从形式参数定义处到函数原型声明结束。这表明编译器更多的关心形式参数的类型而不是形参名,而只有在变长数组中,形参名才更有用。
+如果在函数的外边定义了一个变量,比如以下程序:
+这里的变量glb_val
就具有文件作用域,更确切地说,具有外部链接的文件作用域,我们也叫它为全局变量。
Tip:这里的glb_val
它的作用域是从定义处到文件结束。
某些我们认为的多个文件可能在编译器里边以单个文件的形式出现,比如C预处理器就将头文件里边的内容替换#include
指令。所以,编译器将源代码文件和所有的头文件都看作是一个包含着信息的单独文件,这个文件被称为是翻译单元(translation unit)。
如果程序由多个源代码文件组成,那么这个程序也由多个翻译单元组成,每个翻译单元对应着一个源代码文件和它的头文件。
+目前我们的程序还不进行多文件处理。
+C 文件有着三种链接属性:外部链接、内部链接和无连接。具有块作用域、函数作用域和函数原型作用域的变量都是无连接变量。具有文件作用域的变量可以是外部链接也可以是内部链接。具有内部链接的变量只能在一个翻译单元使用,而具有外部链接的变量能在多文件程序中使用。
+使用extern
关键词,或者直接在函数外边定义的变量都是具有外部链接的变量,而使用static
关键词的变量是具有内部链接的变量。
存储期(storage duration)描述了通过这些标识符访问的对象的生存期,某些变量存储期一过,它所占的内存就会被释放,相应的,存储的内容也会丢失。C对象有着四种存储期:静态存储期、自动存储期、线程存储期和动态分配存储期。
+extern
和static
表明了对象的链接属性与存储期(静态存储期)。The static
/extern
specifier specifies both static storage duration (unless combined with _Thread_local) and internal/external linkage.声明在函数头、块内的变量属于自动存储类别的变量,具有自动存储期,块作用域且无连接。我们可以在C中使用关键词auto
来表明这个变量的存储类型是自动变量。
我们使用关键词register
来表示该变量的存储类型为寄存器变量。寄存器变量存储在CPU的寄存器之中,寄存器是计算机最快的可用内存,因此访问并且处理这些变量的速度会更快,但是无法获取寄存器变量的地址(因为它没有内存位置)。寄存器变量在绝大多数方面都和自动变量一样,也就是具有块作用域、无链接和自动存储期。
声明变量为register
类型更像是一种请求而不是命令,因为编译器必须根据寄存器或者最快可用内存数量来衡量请求;并且由于寄存器的大小有限(通常是一个字,亦即4或8字节),可以声明为寄存器变量的数据类型有限,比如寄存器可能就没有足够大的空间来存储double
类型的值。计算机很可能会忽略我们的请求,变量则被声明成一般的自动变量(也就是存储在内存之中),即使这样,仍然不能对该变量使用取地址运算符。
我们可以创建具有块作用域、无连接的静态变量,只需要在块中(这样就提供块作用域和无连接了)用存储类别说明符static
(提供静态存储期)说明这个变量就可以了。
编译器在程序的生命周期内保证静态变量的存在,静态变量只会在程序中被初始化一次,不会在离开和进入作用域时被销毁或者重置。这是因为静态变量和外部变量在程序被载入内存的时候已经执行完毕,所以在逐个步骤调试的时候会发现含有 static
声明的变量不太像时程序中的变量 ;-)
外部链接的静态变量具有文件作用域、外部链接和静态存储期,该类别有时被称为外部存储类别,属于该类别的变量称为外部变量。如果未初始化外部变量,则其被默认初始化为0;只能用常量表达式初始化文件作用域变量(除了变长数组以外,sizeof()
表达式可以看作常量表达式)。
全局变量在main()
函数执行之前完成初始化。
我们在文件之间共享全局变量的时候需要特别小心,可以使用以下两个策略:其一,遵循外部变量的常用规则,亦即在一个文件之中使用定义式声明,在另一个文件之中使用引用式说明(使用extern
关键字);其二,将需要共享的全局变量放在一个头文件之中,在其他文件中包含这个头文件就可以了,然而,这种处理方式需要我们在头文件中使用static
关键词,如果我们不使用static
关键词或者使用extern
关键词,那么我们就在每一个文件之中都包含了一个定义式声明,C标准是不允许这样子的。然而头文件实际上是给每一个文件提供了一个单独的数据副本,数据是重复的,浪费了很多的内存。
int exint = 1; //declaration 1
+extern int falseint; //declaration 2
+int main(void){
+ /*内部不表*/
+ extern int exint; //declaration 3
+}
+
考虑上面的例子:对于外部变量来说,第一次声明declaration 1
被称为定义式声明(defining definition),为变量预留了存储空间;第二次声明declaration 3
被称为引用式声明(referencing definition),关键词extern
表明此次声明不是定义,指示编译器到别处查询定义,这表明declaration 2
是不正确的,这时编译器假定falseint
定义在程序别处,不会引起分配空间。因此我们不要用extern
关键字创建外部定义,只使用它引用外部定义。
使用关键字static
可以声明内部链接的静态变量,只需要在函数外使用static
声明就可以,并且在函数内使用时使用extern
进行引用式声明即可,但是extern
并不改变链接属性。
函数也有存储类别,可以是外部函数(默认)、静态函数或者内联函数。
+extern
关键词定义的函数是外部函数,是为了表明当前文件中使用的函数被定义在别处,除非使用static
关键词,一般函数声明都默认为extern
。static
关键词定义的函数是静态函数,静态函数只能用于其定义所在的文件。可以在其他文件中定义与之同名的函数,这样子就避免了名称冲突的问题。inline
我们在前面所探讨的存储类别都有一个共同之处,在确定好存储类别之后,就只能根据确定好的内存存储规则,自动指定存储期和作用域。但是我们也可以利用库函数灵活分配和管理内存,只不过必须好好利用指针。
+我们下面讨论malloc()
、free()
、calloc()
和realloc()
函数。
void* malloc(size_t size)
函数¶malloc()
函数接受一个参数:所需要的内存字节数,之后它会找到合适的内存块,匿名分配size
个byte
大小的内存,返回动态分配内存块的首字节地址。如果无法分配内存,malloc()
函数就会返回一个空指针。最早,由于char
类型只占用一个字节,所以malloc()
函数返回一个char *
类型的指针,后来malloc()
返回void *
类型的通用指针,指向什么都可以,完全不需要考虑类型匹配的问题,但是为了增加代码的可读性,应该坚持强制类型转换。
我们可以利用malloc()
函数提供第三种声明数组的方式:将调用malloc()
函数的返回值赋给指针,利用指针访问数组的元素,这样创建的其实是一个动态数组。比如:
我们完全可以使用正常声明数组一样的方式访问这个数组ptd
,比如ptd[18]
。
malloc()
函数也可以声明多维数组,但是语法会复杂一些:
int numrow = 6,numcolumn = 5;
+int **array2 = (int **)malloc(sizeof(int*)*numrow);
+for(int i = 0; i<m; i++)
+{
+ array2[i] = (int *)malloc(sizeof(int)*numcolumn);
+}
+//或者
+int (* array)[numcolumn] = (int (* array)[numcolumn])malloc(sizeof(int)*numcolumn*numrow);
+
先看第一种定义方式:在第二行创建了一个二级指针,也就是存储着指针的数组array2
,在接下来的循环中,逐个为二维数组的每一行分配空间,同时将数组指针存储在array2[i]
中。在读取元素array2[1][2]
的时候,我们先读取出array2[1]
,发现是个指针(其实是数组),然后读取这个数组的第三个元素(编号是2),这样就读出来了元素array2[1][2]
。
再看第二种定义方式:简而言之,等号左侧定义了一个指针变量array
,指向的是int[numcolumn]
类型的指针,说白了array
也是一个二级指针。如果还要整花活,我们发现*(*(array+1)+2)
和array[1][2]
其实是一样的。换句话说,array
指向一个内含6个整型的数组,因此array[i]
表示一个由numcolumn
个整数构成的元素,array[i][j]
表明一个整数。
逻辑上看,二维数组是指针的数组(亦即二级数组);但是从物理上来看,二维数组是一块连续的内存,对于二维数组array3[4][5]
:我们完全可以按照5进制来理解这块内存的排布,五进制数 ij 表示的数所对应的内存上边的内容就是array[i][j]
存储的内容。
void* calloc(size_t num,size_t size)
函数¶calloc()
函数分配num
个size
大小的连续内存空间,并且将每一个字节都初始化为0,所以calloc()
调用的结果是分配了num*size
个字节长度的内存空间。calloc()
函数的返回值和malloc()
函数的一样。
void* realloc(void* ptr,size_t new_size)
函数¶realloc()
函数重新分配指针ptr
指向位置内存块的大小。函数返回新分配内存块的起始位置的地址,并且原指针ptr
在调用后不再可用。
realloc()
函数被调用时,只会做下面两种行为之中的一种:
ptr
指向的内存区域扩大或者缩小,并且尽可能保留剩余原有区域的内容(The contents of the area remain unchanged up to the lesser of the new and old sizes.),如果内存区域扩大,新的内存内容为未定义的(the contens of the new part of the array are undefined.)。void free(void *ptr)
函数¶free()
函数接受先前被malloc(),calloc(),realloc()
动态分配过的内存地址,之后将这些内存释放(deallocate),如果free()
接受一个空指针,那么它什么都不会做。free()
函数不返回任何值。如果free()
函数接受的参数不是先前被malloc(),calloc(),realloc()
分配过的内存地址,它的行为并未被定义。(The behavior is undefined if the value of ptr
does not equal a value returned earlier by malloc(),calloc(),realloc()
)我们也不能释放同一内存两次(The behavior is undefined if the memory area referred to by ptr
has already been deallocated, that is, free()
, free_sized()
, free_aligned_sized()
(since C23), or realloc()
has already been called with ptr
as the argument and no calls tomalloc(), calloc(), realloc()
or aligned_alloc()
(since C11) resulted in a pointer equal to ptr
afterwards.)。
最重要的是:动态分配的内存必须被释放,否则会发生内存泄漏(memory leak)。
+值得注意的是,C99标准为限定符增加了一个新的属性:幂等性。也就是说可以在同一个声明之中使用多个相同的限定符,多余的限定符将被忽略。
+const
限定符¶被const
关键词声明的对象将成为只读变量,其值不能通过赋值、递增或递减等方式修改,但是至少初始化变量是没问题的,这样我们就只可以使用但不能修改对象的值了。
如果对指针使用const
限定符,如果const
限定符在*
的前面,也就是const int *num
或者int const *num
,其实限定了指针指向的值为const
,num
指向了一个int
类型的const
值。如果const
限定符在*
的后面,也就是int * const num
,则我们创建的指针本身的值不能改变,但是它指向的值可以改变。
更加常见的用法是声明为函数形参的指针。比如void display(const int array[], int num)
,另外一个更熟悉的例子是字符串函数void strcat(char * restrict string1,const char * restrict string2)
。 这使得传进去的数组的值没有被修改,这其实表明了const
限定符实际提供了一种保护数据的方法。
我们同样可以对全局变量使用const
限定符保护数据,因为extern
限定符使得程序的任何一个部分都能使用并且改变这个变量,所以会平白无故产生许多危险,而const
限定符让变量变成只读变量,这样就可以另程序更加安全。
volatile
限定符¶restrict
限定符¶文件其实是硬盘上的一段已经被命名的存储区域,C将文件看成一系列连续的字节,每一段字节都可以被单独读取。
+C提供两种文件模式:文本模式和二进制模式。
+C程序会自动打开三个文件:标准输入、标准输出和标准错误输出。通常时候下,标准输入是普通的输入设备,一般是键盘;标准输出和标准错误输出都是系统的普通输出设备,一般是显示屏。函数getchar()
、函数printf()
和函数puts()
都使用的是标准输出。标准错误输出提供了一个逻辑上不同的地方来显示错误输出,如果我们将输出发送给文件,那么发送到标准错误输出的内容仍然会被发送到屏幕上。
[[noreturn]] void exit(int exit_code)
函数exit()
函数关闭所有打开的文件并且结束程序,正如函数声明处所说
File *fopen(const char *restrict filename, const char *restrict mode)
函数fopen()
函数打开一个文件,其文件名由传入函数的第一个参数标识,返回文件指针。其需要的第二个参数是一个字符串,指定了待打开文件的模式。
我们常见的打开文件模式有下面这些:
+"r"
以只读模式打开文件;"w"
以写模式打开文件,并且将现有文件的长度截为 0,如果文件不存在,则创建一个新文件;"a"
以写模式打开文件,在现有文件结尾添加内容,若文件不存在,则创建一个新文件;"r+"
以更新模式打开文件,亦即可以读写文件。如果打开文件失败,且不创建新文件,返回一个空指针
+值得注意的是,文件指针并不指向任何实际文件,只是指向一个包含文件信息的数据对象(换句话说是一个结构)其中包含了操作文件所用函数所需要的缓冲区信息。
+int fclose(FILE *stream)
函数fclose()
函数关闭由stream
给出的文件流,无论关闭是否成功,stream
均与这个文件无关。The behavior is undefined if the value of the pointer stream
is used after fclose
returns.
如果关闭成功,fclose()
函数返回0
,反之返回EOF
。
int fprintf(FILE *restrict stream, const char *restrict format, ...)
函数fprintf()
函数和printf()
函数基本相同,只不过输出流从默认的stdout
变成了需要自行给出的stream
,亦即函数接受的第一个参数表示需要输出的位置。
int fscanf(FILE *restrict stream,const char *restrict format, ...)
函数这个函数和scanf()
函数大差不差,只不过接受的第一个参数需要是待读取文件的文件指针。
char *strerror(int errnum)
函数返回一个指针,指向错误代码errnum
代表的文字描述。errnum
一般需要从变量errno
中取得。
long ftell(FILE *stream)
函数ftell()
函数返回一个long
类型的值,为stream
的位置标识符的当前值,亦即。如果出现错误,ftell()
函数将会返回-1
,全局变量errno
被设置为一个正值,我们可以使用errno
变量来查看错误代码。比如:
if(fseek(fp, 0L, SEEK_END) == -1)
+{
+ fprintf(stderr,"ERROR:%s\n",strerror(errno));
+ fclose(fp);
+ return 1;
+}
+
int fseek(FILE *stream, long offset, int origin)
函数fseek()
函数将文件看做是数组,将位置标识符stream
移动到目标位置。函数的第三个参数是模式,这个参数确定起始点。stdio.h
头文件内有三个表示模式的文件常量:SEEK_SET
表示文件开始处;SEEK_CUR
表示当前位置;SEEK_END
表示文件末尾。第二个参数是相对于origin
的偏移量,以字节为单位,可以为正值(前移)、负值(后移)或者0(保持不动)。
如果一切正常,fseek()
返回0
,若出现错误(比如试图移动的距离超出文件范围了),返回值为-1
。
ftell()
函数在文本模式和在二进制模式的工作方式不同,ANSI C规定,ftell()
函数的返回值可以当做fseek()
函数的第二个参数。对于MS-DOS,ftell()
返回的值将\r\n
当做一个字节计数。
函数
+函数
+函数
+size_t fread(void *restrict buffer, size_t size, size_t count, FILE * resrict stream)
函数
fread()
函数接受的参数和fwrite()
相同。在fread()
函数之中,buffer
是待读取文件数据在内存之中的地址,stream
指定要读取的文件,该函数可以用于读取文件之中的数据,size
代表着待读取数据每个元素的大小,count
代表待读取项的项数。函数返回成功读取项的项数,一般是count
,如果出现错误或者读到EOF
,返回的值就会比count
小。
值得一提的是:The file position indicator for the stream is advanced by the number of characters read.
+size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream)
函数fwrite()
函数将缓冲数组buffer
里面的count
个元素写入到流stream
之中。函数将需要写入的数据重新编译为unsigned char
类型的数组,通过重复调用fptc()
函数将其写入stream
之中。和fread()
函数相同,The file position indicator for the stream is advanced by the number of characters read.
+ 在实际使用fread()
函数和fwrite()
函数读写文件之中的数据时,文件位置指示符不断前进,这使得我们不会重复读取数据,亦即可以实现这样的操作,一个循环就能实现文件从头读到尾的操作:
while(fread(&buffer, sizeof(int16_t), 1, input))
+{
+ buffer *= factor;
+ fwrite(&buffer, sizeof(int16_t), 1, output);
+}
+
在C中,我们处理的是流,流是一系列连续的字节,不同属性和不同种类的输入由属性更加统一的流来表示。流告诉我们,我们可以利用和处理文件相同的方式来处理键盘输入。打开文件的过程就是把流与文件相关联,读写都通过流来完成。
+在标准头文件stdio.h
之中,定义了三个文本流stdin stdout stderr
,
结构的声明描述了一个结构的组织布局:
+ +结构体struct book
描述了由两个整数类型组成的一个结构体,后续程序中可以利用模板struct book
来声明具有相同数据组织结构的结构变量。
一般使用的结构初始化方式有三种,除了对于每一个结构成员进行值初始化之外,可以直接进行列表初始化和利用初始化器初始化:
+ +指向结构的指针具有指针的优点。指向结构的指针比结构本身更加容易操控、在函数中执行更有效率,并且有可能数据表示数据的结构中包含指向其他结构的指针。结构和数组不同,结构名代表的是这个数据集合,而不是结构变量的地址。
+结构允许嵌套,也允许使用匿名结构,还允许定义结构数组,但是结构中的的匿名结构就很恶心。
+在两个结构类型相同的情况下,我们允许将一个结构赋值给另外一个结构,这是将每个成员的值都赋给另外一个结构的相应成员。
+结构不仅可以作为参数传递,也可以作为返回值返回。与传递结构参数相比,传递结构指针执行起来很快,并且在不同版本的C上都可以执行;缺点是不能保护数据(const
限定符就可以解决了)。将结构作为参数传递的优点是,函数处理的是原始数据的副本,可以保护数据,但是需要拷贝副本,浪费了时间和内存。
我们可以在结构之中使用以下两种方式存储字符串:
+#define LEN 20
+struct names{
+ char first[LEN];
+ char last[LEN];
+}
+
+struct names1{
+ char *first1;
+ char *last1;
+}
+
如果仅仅是声明数组并且随即初始化,那么两种声明方式是没有区别的。但是如果使用类似于scanf("%s",&names1.first1);
的方式,第二种声明方式就会出现危险。因为程序并未给first1
分配存储空间,它对应的是存储在静态存储区的字符串字面量,换言之,结构体names1
里存储的只是两个字符串的指针,如果仍要实行该操作,因为scanf()
函数将字符串放在names1.first1
对应的地址上,但是由于这是没有经过初始化的变量,地址可以是任何值,因此程序会将字符串放在任何地方。简而言之,结构变量中的指针应该只是用来程序中管理那些已经分配或者分配在别的地方的字符串。
如果使用malloc()
函数来分配内存并且用指针来存储地址,这样处理字符串就会比较合理,当然需要记得使用free()
释放相关内存。
复合字面量可以用于数组或者结构,如果活只需要一个临时结构值,符合字面量很好用,比如我们可以使用复合字面量创建一个结构赋给另外一个结构或者作为函数的参数。语法是将类型名放在圆括号之中,后面紧跟花括号括起来的初始化列表。比如(某些代码进行了适当的精简):
+struct book{
+ char title[10];
+ char auther[10];
+ double value;
+}
+struct book POMA = (struct book){
+ "thistitle",
+ "thisman",
+ 11.11
+};
+int cal(struct book tmp);
+cal((struct book){"thattitle","thatman",22.2});
+
利用伸缩型数组成员这一特性声明的结构,起最后一个数组成员具有一些特性:其一,该数组不会立刻存在;其二,使用这个伸缩型数组成员可以编写合适的代码,就好像这个数组确实存在并且具有所需数目的元素一样。但是对这个数组有这以下要求:首先,这个数组成员必须是结构的最后一个成员;其次,结构中至少拥有一个成员;最后,伸缩数组的声明类似于普通数组,只不过其中括号是空的。另外,声明一个具有伸缩型数组成员的结构时,我们不能使用这个数组干任何事,必须先给它分配内存空间后才能以指针形式使用它。比如:
+struct flex{
+ size_t count;
+ double average;
+ double array[];
+}
+struct flex *ptr;
+ptr = malloc(sizeof(struct flex) + n*sizeof(double));
+ptr->count = n;
+
使用伸缩型数组成员的结构具有一下要求:第一,我们不能用结构进行赋值或者拷贝,比如*ptr1 = *ptr2
,不然这样只能拷贝非伸缩型数组成员外的所有成员,如果非要拷贝,应该使用mencpy()
函数;第二,不要按值方式将这种结构传递给函数,应该传递指针;第三,不应该将使用伸缩型数组成员的结构作为数组成员或者另外一个结构的成员。
联合(union)是一种数据类型,可以在同一个内存空间之中存储不同的数据类型(但是不是同时)。其典型用法是,设计一种表以存储既无规律,实现也不知道顺序的混合类型。使用联合类型的数组,每个联合都大小相等,每个联合可以存储各种数据类型。
+ +以上声明的联合可以存储一个int
类型、一个double
类型或者一个char
类型的值。联合只能存储一个值,其占用的内存空间是占用内存最大类型所占用的内存。使用联合的方法如下:
union hold fix;
+fix.digit = 1; //把1存储在fix,占用两个字节
+fix.bigfl = 1.1; //把先前存储的内容抹去,把1.1存储在fix,占用八个字节
+
可以使用枚举类型(enumerated type)声明符号名称表示整型常量。使用关键字enum
可以声明枚举类型并且指定其可以具有的值(事实上,enum
常量就是int
类型,所有可以使用int
类型的地方都可以使用枚举类型)。enum
类型的用法如下:
enum spectrum {red, orange, yellow, green = 100, blue, violet};
+enum spectrum color = red;
+printf("%d, %d, %d, %d", red, orange, blue, violet); //输出为0, 1, 101, 102
+
在使用完枚举声明后,red
等就成了具有名称的常量。第二个语句声明了枚举变量color
,并且拿red
的值初始化color
。这种不连续的枚举直接按照整数进行处理,无法按照想象中直接遍历枚举内的元素。
typedef
¶利用typedef
可以为某一类型自定义名称。我们之前已经利用define
进行了自定义名称,但是define
只是单纯的文本替换,并且相比于typedef
要更为死板很多。比如:
#define STRING char*
+typedef char* String;
+STRING str1, str2; //其实是char *str1, str2;只创建了一个字符串和一个字符
+String strr1, strr2; //其实是char *strr1, *strr2;创建了两个字符串
+
这就明白为什么define
只不过是单纯的文本替换了。同时,利用typedef
为结构变量命名的时候,可以省略掉这个结构的标签,比如:
其实这是为一个匿名结构命名。typedef
更好的一点是可以为更加复杂的类型命名:比如typedef char (* FRPTC()) [5];
将FRPTC
声明为一个函数类型,这个函数返回一个指针,指针指向内含五个char
元素的数组。
计算机基于二进制,通过关闭和打开状态的组合来表示信息。C语言利用字节表示存储系统字符集所需的大小,通常一字节(byte)包含八位(bit),计算机界用八位组(octet)特指八位字节。可以从左到右给这八位编码为7 ~0,编号为7的位被称为高阶位,编号为0的被称为低阶位。
+如何利用二进制表示有符号整数取决于硬件,最通用的做法是利用补码。二进制补码利用一字节的第一位(高阶位)表示数值的正负,如果高阶位是0,则此时表示非负数,其值与正常情况相同;如果高阶位为1,则此时表示负数,但是这时负值的量是九位组合100000000
(256的位组合)减去这个负数的位组合。比如某负数为10011010
,其表示-122
;11111111
则表示-1
。这样,我们就可以用一字节来表示从-128~127
的所有数字。
浮点数分两部分存储:二进制小数和二进制指数。计算机中存储浮点数的时候,要留出若干位存储二进制小数,剩下的位存储指数。二进制小数用有限多个\(1/2\)的幂的和近似表示数字(事实上,二进制小数只能精确表示有限多个\(1/2\)的幂的和)。一般而言,数字的实际值是由二进制小数乘以\(2\)的指定次幂组成。
+计算机界常用八进制和十六进制计数系统,这些计数系统比十进制更加接近计算机的二进制系统。
+0
来特别表示一个数是八进制。0x
来特别表示一个数是十六进制。十六进制是表示字节的非常好的方式。按位取反(反码):~
按位与:&
按位或:|
按位异或:^
<<
>>
没看懂,先不写。
+assert
库¶stdio
库¶int sprintf(char *buffer, const char *format,...)
函数
向字符串buffer
里写入,相当于多了一个转换格式/读写的工具函数。
int fprintf(FILE *stream, const char *format,...)
函数
sscanf()
函数
fscanf()
函数
向输出流stream
中写入。
stdlib
库¶void srand(unsigned int seed)
srand()
函数为伪随机数生成器rand()
播种,正常的用法是:srand((unsigned int) time(NULL))
这段代码利用当前时间为伪随机数生成器rand(0)
提供种子,这样子就可以得到了近似于真随机的随机数。
int rand(void)
伪随机数生成器rand()
生成一个介于0
到RAND_MAX
的随机数。如果没有srand()
的播种,rand()
函数就会默认生成种子为1的随机数。每次调用rand()
函数,我们得到的都是上次生成的随机数的下一个数
值得注意的是,在调用函数rand()
之前的时候,伪随机数生成器只应该被播种一次。
++Generally speaking, the pseudo-random number generator should only be seeded once, before any calls to
+rand()
, and the start of the program. It should not be repeatedly seeded, or reseeded every time you wish to generate a new batch of pseudo-random numbers.
更重要的是,当rand()
接受相同的种子的时候,他会生成相同的随机数数列。
time
库¶time_t
这是一个适合储存日历时间的长整型(long int)
变量,表示着从POSIX time (1970年1月1日00:00)开始的总秒数。time_t time(time_t *seconds)
time()
函数将当前日历时间作为一个time_t
类型的变量返回,并且将这个变量存储在输入的指针seconds
中(前提是这个指针不为空指针)。
由于time_t
类型其实是一个long int
转换成int
(或者unsigned int
)的时候还是需要强制转换说明的2
Abstract
+这是我学习Java语言的笔记,动机很简单,我想听的CS61B与Algorithms课程都是基于Java的,所以我需要学习Java。
+参考书籍:
+++Java is a high-level, class-based, object-oriented programming language.
+
Java的类库源文件在JDK中以压缩文件lib/src.zip
的形式发布,其包括了所有公共类库的源代码,解压缩这个文件就可以得到源代码。
使用命令行工具,我们可以编译和运行Java程序。编译Java程序使用javac
命令,javac
程序是一个Java编译器,将我们的代码编译成字节码文件,也就是类文件(扩展名为.class
);再使用java
命令启动Java虚拟机,执行编译器编译到类文件的字节码。
编译器需要文件名,需要提供扩展名.java
,而虚拟机需要类名,不需要提供扩展名。
调用方法的通用语法是object.method(parameters)
,其中object
是一个对象,method
是对象的一个方法,parameters
是方法的参数。
对于这段最简单的代码:
+public class FirstSample{
+ public static void main(String[] args){
+ System.out.println("We will not use 'Hello, World!'");
+ }
+}
+
关键词public
被称为访问修饰符/Access modifier,决定了控制程序其他部分对这部分代码的访问级别。class
表示Java程序中的全部内容都包含在类中,类是Java应用的构建模块。一个源文件只能有一个公共类,但是可以有任意数量的非公共类,源文件的文件名必须和公共类的类名相同,并且用.java
作为扩展名。
在执行已经编译的程序的时候,虚拟机总是从指定类的main
方法的代码开始执行,所以类的源代码中必须包含一个main
方法,且main
方法必须声明为public
,当然直接声明全套public static
也是极好的。方法其实就是函数的另外一种说法,我们也可以自行定义方法并且添加到类中。
Java是一种纯粹的面对对象的语言,面对对象的程序是由对象组成的,每个对象包括对用户公开的特定功能与隐藏的实现。在面对对象程序设计中,数据是第一位的,之后我们才考虑操作数据的大小。
+类/Class指定了如何构造对象,通过一个类构造/Construct对象的过程称为创建这个类的一个实例/Instance。封装/Encapsulation是面对对象程序设计的一个重要概念,是指将数据与行为组合在一个包中,并对对象的使用者隐藏了具体的实现细节。对象中的数据称为实例字段/Instance field,操作数据的过程称为方法/Method,作为一个类的实例,一个对象有一组特定的实例字段值,这些值的集合就是这个对象的当前状态/State。只要在对象上调用一个方法,它的状态就有可能发生改变。
+封装的关键在于,不能让其他类的方法直接访问这个类的实例字段。我们还可以通过扩展其他的类来构建新类,这个新类具有被扩展的类的所有属性与方法,这种通过扩展一个类来得到另外一个类的过程叫做继承/Inheritance。
+使用面对对象编程之前,必须清楚对象的三个主要特性:
+对象的标识是两两不同的,每个对象都有一个唯一的标识。
+类之间的最常见的关系有:依赖/uses-a,聚合/has-a与继承/is-a。
+依赖/Dependence是最一般且最明显的关系,比如Order
类使用了Account
类,因为Order
类需要访问Account
类来获取信息。应该尽可能减少相互依赖的类,或者说减少类之间的耦合/Coupling。因为耦合度越低,越不容易在修改一个类的时候影响其他类。
聚合/Aggregation表明了一个类包含另外一个类的对象。
+继承/Inheritance表示了一个更特殊的类与一个更一般的类之间的关系,在特殊化的类里边定义了更多的特殊方法与额外功能。
+在Java中,没有类就不能做任何事情,但是并不是所有类都表现出面对对象的典型特征,比如Math
类,它只包括了一些方法,甚至没有实例字段。下面我们将以Date
类与LocalDate
类为例说明类的使用。
在Java中,我们需要使用构造器/构造函数/Constructor来构造新的实例,构造器是一种特殊的方法,用来构造并且初始化对象,并且构造器总是与类同名。我们看几个例子:
+new Date();
:使用new
操作符,我们就可以构造一个Date
对象,并且将这个对象初始化为当前的日期与时间。String s = new Date().toString();
我们可以对这个对象应用一个方法,将这个日期转换成一个字符串。System.out.println(new Date());
我们也可以将这个对象传递给一个方法。Date rightNow = new Date();
我们定义了一个对象那个变量rightNow
,其可以引用Date
类型的变量,并且将新构造的对象存储在对象变量rightNow
中。对于对象变量而言,他们并不包含一个对象,只是引用一个对象,我们可以显式地将对象变量初始化为null
,这就表明这个变量目前没有引用任何对象,对一个赋值为null
的变量,我们不允许应用任何方法。
尽管我们使用了引用这个词,但是Java中的对象变量更像是C++中的对象指针,并且Java的语法甚至和C++的是一样的。所有的Java对象都存储在堆之中,当一个对象包含另外一个对象变量的时候,其实只是包含了另外一个堆对象的指针。
+上面提到的Date
类的实例有一个状态,就是一个特定的时间点,时间是距离另外一个固定的时间点的毫秒数,这个时间点就是所谓的纪元/Epoch,在Java中,纪元是UTC时间1970年1月1日00:00:00。
LocalDate.now();
LocalDate newYearsEve = LocalDate.of(1999, 12, 31);
int year = newYearsEve.getYear();
/int month = newYearsEve.getMonthValue();
/int day = newYearsEve.getDayOfMonth();
LocalDate aThousandDaysLater = newYearsEve.plusDays(1000);
:访问器方法/Accessor method,与更改器方法/Mutator method。我们不仅仅需要学会使用常用的类与配套的方法,还需要学会编写类。我们经常写的一种类叫做主力类/Workhorse class,这种类没有main
方法,但是有自己的实例字段和实力方法,是构建一个完整程序的众多部分之一。
最简单的类形式为:
+ +实例字段可以是基本类型,也可以是对象。我们一般需要将实例字段声明为private
,这确保只有这个类的方法可以访问这种字段,这就是封装的一部分。而方法可以声明为private
或者public
,public
方法可以被任何类的任何方法调用。private
方法只可以被本类的其他方法调用。
我们先看一个自定义类的例子:
+public class Employee{
+ private String name;
+ private double salary;
+ private LocalDate hireDay;
+
+ public Employee(String n, double s, int year, int month, int day){
+ name = n;
+ salary = s;
+ hireDay = LocalDate.of(year, month, day);
+ } // constructor
+
+ public String getName(){
+ return name;
+ } // more methods
+}
+
从构造器开始看,构造器与类同名。构造Employee
类的对象时,构造器会运行,将实例字段初始化为所希望的初始状态。构造器没有返回值,可以有参数,也可以没有参数。
在声明对象变量的时候,我们可以用var
关键字声明,Java会根据变量的初始值推导出其类型。比如,对上面的类,我们只需要声明var harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);
就可以了。
在使用构造器初始化一个对象的时候,有可能将某个实例字段初始化为null
,但又有可能对这个字段应用某个方法,一个“宽容”的解决方法就是将null
参数转换成一个适当的非null
值,Objects
类给予了一种便利方法:Objects.requireNonNullElse(T, defalt obj)
,如果输入的对象T
是null
,那么就将返回默认值default obj
。同样地,其实还提供了一种“严格”的方法Objects.requireNonNull(T, string message)
,如果输入的对象T
是null
,那么就将抛出一个NullPointerException
异常并且显示出问题的描述。
方法会操作对象并且访问其实例字段,方法一般会有两个参数,第一个参数是隐式参数/Implicit parameter,出现在方法名之前;第二个参数是显式参数/Explicit parameter,出现在方法名之后的括号里面。在方法中,我们可以使用this
关键字来引用隐式参数。
封装是极其有必要的,我们来看一些比较特殊的方法,这些方法只访问并且返回实例字段的值,因此被称为字段访问器/Field accessor。相比于将实例字段声明为public
,编写单独的访问器会更加安全:外界的代码不能修改这些实例字段,并且如果这些实例字段出了问题,我们直接调试字段访问器就可以了,如果是public
的话,我们就需要在所有的代码中寻找问题。
有时,想要获取或者改变一个实例字段的值,我们需要提供下面三项内容:
+这样的处理非常好,首先可以改变内部实现而不改变该类方法之外的任何其他代码;其次,更改器方法可以完成错误检查,这就非常好了!
+另外,不要编写返回可变对象引用的访问器方法,这样我们好好的封装就毁了!如果硬要返回一个可变对象的引用的话,首先应该对它进行克隆/Clone,克隆是指存放在另外一个新位置上的对象副本,使用类重的clone
方法可以完成这个操作。
访问权限是一件非常重要的事情。方法可以访问所属类的对象的任何数据,当然包括私有数据,但是不能访问其他对象的私有数据。
+尽管大部分方法都是公共的,但是某些情况下,私有方法可能会更加有用,比如我们希望将一个计算方法分解成若干个独立的辅助方法,但是这些方法不应该作为公共接口,这是因为其与当前实现关系非常紧密,或者需要一个特殊的协议或者调用次序,这些方法就应该实现为私有方法。实现私有方法很简单,只需要将关键字public
改为private
就好了。
如果一个方法是私有的,并且类的作者确信这个方法不会在别处使用,这个方法就可以被简单地剔除,但是如果方法是公共的,就不能简单地删除一个方法了,因为还有可能会有其余的代码依赖于这个方法。
+我们可以将一些变量、类与方法设置为final
。当我们定义一个类时使用了final
修饰,这个类就不能被继承,这个类的成员变量可以根据需要设为final
,但是类中的所有成员方法都会被设为final
。如果定义了一个方法为final
,这个方法就被“锁定”了,无法被子类的 方法重写,对方法定义为final
一般常见于认为这个方法已经足够完整,不需要改变了。修饰变量是final
用的最多的地方,final
变量必须显式指定初始值,并且一旦被赋值,就不能给被重新赋值;如果final
的是一个基本变量,这个变量就不能改动了,如果是一个引用变量,那么对其初始化之后就不能再指向其他变量。
比如private final StringBuilder evaluations = new StringBuilder();
,这里的evaluations
就不能指向别的对象了,但是这个对象可以修改,比如evaluations.append(LocalDate.now() + ":Yes!")
。final
修饰符对于类型为基本类型或者不可变类的字段尤其有用,并且final
修饰符一般与static
修饰符一起使用。
我们发现,先前的很多方法都标记了static
修饰符,下面
数据类型就是一组数据与对其能进行的操作的集合。
+Java是典型的C类语言,声明变量的方法与C极为相似,但是在Java中,变量名的标识符的组成得到了扩充:字母、数字、货币符号与“标点链接符”组成变量名,首字母不能为数字。特别地,字母、数字与货币符号的范围更大,字母可以是一种语言表示中的任何Unicode字符,数字可以是0
到9
与表示一位数字的任何Unicode字符。
Java最基本的类型有下面几种,六种数字类型,一种字符类型,一种布尔型:
+整型
+双精度实数类型,与其对应的算数运算符(double
)。
我们使用double
类型来表示双精度实数,使用64位,值域非常之大。
字符型char
:char
使用UTF-16方案进行编码,以前原本用于表示单个字符,但是现在情况变化了,有的Unicode字符需要两个char
值。char
类型的字面量要用单引号括起来,也可以表示为十六进制的值,比如\u0041
就是A
。
在Unicode编码之前,已经有许多编码标准了,我们最熟悉的就是美国的ASCII编码。标准不统一会出现下面两个问题:对一个特定的代码值,在不同的机制中对应不同的字母;大字符集的语言的编码长度会有不同,有的是单字节编码,有的就使用双字节或者多字节编码。
+Java的字符型使用的16位编码在当时设计时的确是很好的改进,但是现在的Unicode字符已经超过65536个,这就尴尬住了。所以一个实用的建议就是不要在程序之中使用char
类型,除非要处理UTF-16代码单元,否则就使用String
类型。
还是简单介绍一下Unicode编码吧:码点/Code point是指与一个编码表中某个字符对应的代码值,Unicode中的码点使用十六进制书写,并且在前面加上一个U+
,U+0041
就是A的码点。Unicode中的码点可以分为17个代码平面/Code plane。第一个代码平面被称为基本多语言平面/Basic multilingual plane,其包含了从U+0000
到U+FFFF
的“经典”Unicode编码,其余的16个代码平面从U+10000
到U+10FFFF
,包含了各种辅助字符/Supplementary character,这些平面包含了一些不常用的字符,比如一些古代文字、表意文字等等。
UTF-16使用不同长度的代码表示所有Unicode码点,在基本多语言平面之中,每个字符使用16位表示,被称为代码单元/Code unit,辅助字符使用一对连续的代码单元表示,这种编码对使用基本多语言平面中未采用的2048个值范围(称为替代区域,U+D800
到U+DBFF
用于第一个代码单元,U+DC00
到U+DFFF
用于第二个代码单元)。而Java中的char
类型描述就采用了UTF-16编码的一个代码单元。
变量名只能包含字母、数字和下划线 “_”,变量名的第一个字符只能是字母或者下划线,变量名对大小写敏感,且不能将关键字 (亦即保留字) 作为变量名。
+Python中的变量不需要声明数据类型,每个变量在使用之前必须赋值,变量在赋值之后才会被创建。我们在创建变量的时候不需要考虑变量的类型——这和C不同——变量就是变量,没有类型,而我们所说的“类型”只是变量所指的内存中对象的类型,因为不同的对象 (比如字符串和整数) 需要以不同的方式存储。并且我们可以为多个变量同时赋值,也可以为多个对象指定多个变量。比如下面的例子:
+counter = 100 # 整型变量
+miles = 1000.0 # 浮点型变量
+name = "runoob" # 字符串
+
+a = b = c = 1 # 为多个变量同时赋值
+a, b, c = 1, 2, "Hi" # 为多个对象指定多个变量
+
Python内置的type()
函数可以用来查询变量所指的对象类型,并且可以使用isinstance()
来判断:
a, b, c, d =20, 5.5, True, 4+3j
+print(type(a), type(b), type(c), type(d))
+>>> <class 'int'> <class 'float'> <class 'bool'> <class 'complex'>
+print(isinstance(a, int))
+>>> True
+
type()
和isinstance()
的区别在于:
type()
不会认为子类是一种父类类型;isinstance()
会认为子类是一种父类类型。字符串就是一系列字符,由引号 (可以是单引号或者双引号) 确定,其列表有两种索引方式:从左到右默认从0开始;或者从右向左从-1开始。对于字符串的某些处理和C类似,比如我们可以实现以下操作:
+str = "Hello"
+print(str[0]) #Output:H
+print(str[2:4]) #Output:ll
+print(str * 2) #Output:HelloHello
+print(str + " " + "world") #Output:Hello world
+print(str[0:4:2]) #Output:Hl
+
我们可以发现,字符串的某些处理其实就是对列表的处理。但是值得注意的是,Python没有单独的字符类型,一个字符就是长度为1的字符串,并且Python的字符串不能被改变。
+在Python中,我们可以使用方法进行对数据的操作,某些方法会改变数据内容 (比如reverse()
),某些则不会 (比如下面这些)。对于字符串,利用lower()
、upper()
、title()
、strip()
、lstrip()
和rstrip()
,我们可以去除字符串两侧的空格,并且调整字符的大小写。比如:
name = "hEllo "
+print(name.title()) #Output:Hello
+print(name.upper()) #Output:HELLO
+print(name.lower(),end="") #Output:hello (后边没有换行)
+print(name.rstrip(),end="@")#Output:hEllo@
+
在Python中,空白泛指所有非打印字符,比如空格、列表和换行符。print()
在打印结束之后会自动换行,但是如果在print()
函数之后参加end=""
参数,我们就可以实现不换行效果,事实上Python里end
参数的默认值为"\n"
,表明在打印的字符串的末尾自动添加换行符,来设定特定符号,比如上面代码的最后一行将字符串的末尾自动添加了一个@
。
Python支持int
、float
、bool
、complex
四种类型。整数类型只有一种int
,表示为长整型。
求模运算符%
、四则运算符+ - * /
与取余运算符%
均和C语言中的相同,只是默认情况下的除法/
的结果是一个浮点数,而使用运算符//
就可以得到一个整数。此外,运算符**
表示乘方。
以下函数均将接受的参数进行转化并返回。
+str()
函数
int()
函数将字符串表示的整形数值转化成整型数字。
float()
函数将字符串表示的浮点数转化成浮点数。
列表由一系列按照特定顺序排列的元素组成,我们用方括号表示列表,并且用逗号分隔其中的元素。列表中的元素可以不相同,甚至可以包含列表 (也就是嵌套)。列表的索引和字符串的索引相同,从0
开始;或者从尾部开始,最后一个元素的索引为-1
,往前一位为-2
,以此类推。
需要注意的是,sorted()
函数和sort()
方法 (和排序相关的) 均不能用在字符串和数字混合的列表 (元组和字典) 排序之中。
字典是一系列键-值对。每一个键都与值相对应,我们可以用键访问对应的值。值可以是任意数据类型,但是键必须是不可变的数据类型,比如数字或字符串。我们允许创建空字典,可以随时添加、修改或者删除字典内的数据。
+dictionary1 = dict()
+dictionary1['first'] = "1st" #添加键-值对
+dictionary1['first'] = "111" #修改值
+print(dictionary['first']) #访问值
+del dictionary1['first'] #删除值的同时删除键-值对
+dictionary1.clear() #清空词典
+del dictionary1 #删除词典
+
可以发现添加键-值对的语法和修改值的相同。
+如果要遍历字典内的键-值对,我们需要先声明两个变量,使用items()
方法,它返回一个键-值对列表,在遍历每一个键值对的过程之中,会将键和值依次存储到两个变量之中。同理keys()
方法和values()
方法分别返回一个存储着键和值的列表,值得注意的是这些列表的元素顺序和其原本的存储顺序不相同,因为Python只关心键与值的对应关系,而不关心存储顺序。
dic1 ={'name': "Hello", 'year': 18, 'position': 'hang'}
+for key,val in dic1.items():
+ print(key,val)
+for key in dic1.keys:
+ print(key.title())
+for val in dic1.values()
+ print(val.upper())
+
Python支持三个布尔运算符:and
、or
和not
,它们的内容和C中的&&
、||
和!
相同,优先级也相同:not
有最高的优先级 ,and
其次,or
的优先级最低。同时,布尔运算符还支持短路运算(short circiuting),即如果and
和or
的第一个操作数(也就是operand)一定可以决定表达式的真值,就不会检查后面的表达式,所以True or 1 / 0
和False and 1 / 0
甚至都没问题。
需要注意的是,and
和or
都不将返回值限定为True
或者False
,相反,它们返回最后一个求值的参数(the last evaluated argument),比如字符串s
需要在它为空的时候被替换成一个默认值,就可以使用s = s or "Default"
来处理。但是not
总需要创建一个新的值,所以不管接收到的参数是不是一个布尔值,都返回布尔值。
Python将0
、None
、''
(空字符串)和[]
(空列表)都规定为False
,其余值规定为True
。这表明了布尔值的范围其实比纯粹的True
和False
更加丰富。
语句值由解释器运行的,执行一项操作的语句(A statement is executed by the interpreter to perform an action)。
+复合语句由一个或者多个子句(clause)组成,每个子句由一个句头(header)和一个句体(suite)组成,组成复合语句的子句头都处于相同的缩进层级。 每个子句头以一个作为唯一标识的关键字开始并以一个冒号结束。 子句体是由一个子句控制的一组语句。 子句体可以是在子句头的冒号之后与其同处一行的一条或者由分号分隔的多条简单语句,或者也可以是在其之后缩进的一行或多行语句。 只有后一种形式的子句体才能包含嵌套的复合语句。简而言之,复合语句大概长这样:
+<header>:
+ <statements> # 两个句头之间夹着的是Suite
+ ... # 以上是一个Clause
+<separating header>:
+ <statements>
+ ...
+...
+
def
语句、if
语句和while
语句都是标准的复合语句。if
语句¶for
循环¶while
循环¶利用continue
和break
来跳过此次循环或者跳出循环,逻辑和C一样。
while
循环还可以这样用:根据对列表判断是否非空来控制循环。
unconfirmed_users = ["1","2","3"]
+while unconfirmed_users
+ print(unconfirmed_users.pop())
+print(unconfirmed_users)
+
这个循环只会在列表unconfirmed_users
非空的时候运行,当它变空的时候,就不会继续运行了。
assert
语句¶assert
语句的基本语法如下:assert_stmt ::= "assert" <expression> ["," <expression>]
。当assert
后面的<expression>
的布尔值是False
时,程序会中断运行,并且抛出AssertionError: <expression>
,这里的<expression>
对应的是方括号里的<expression>
。一个例子如下:
>>> assert 2 > 3, 'Math is broken'
+# Traceback (most recent call last):
+# File "<stdin>", line 1, in <module>
+# AssertionError: Math is broken
+
return
语句¶A return statement completes the evaluation of call
+函数就是一个带名字的代码块,用于完成具体的工作。关键字def
表示开始定义一个函数,向Python指出函数名,并且提供形参。函数定义下所有缩进行构成函数体,被三个引号引起来的部分是称作文档字符串的注释,描述函数是做什么的,Python用其生成有关程序中函数的文档。
在定义中括号里出现的变量是形式参数 (Formal Parameter),是函数完成工作需要的信息,在调用函数时传入的是实际参数 (Actual Argument),是调用函数的时候传递给函数的信息,值存储在相应的形式参数之中。
+def calc(number,times=2): # Function signature
+ """完成乘方的操作,默认为平方"""
+ ret = 1 # Function body
+ for cnt in range(0,times):
+ ret *= number
+ return ret
+
我们认为赋值操作是一种简单的抽象(abstraction)方式,它将变量的名字与其值联系到了一起;而函数定义是一种更强大的抽象方式,它允许将名字与表达式联系到了一起。函数由函数签名与函数体组成。
+函数签名(function signature)<name>(<formal parameters>)
表明了函数的名字与接受的形式参数的数量;
函数体(function body)<expression>
决定了使用函数时的计算过程。
def
语句的执行过程如下:
<name>(<formal parameters>)
;<name>
to that function in current frame.Procedure for calling/applying user-defined functions(version 1):
+函数的定义域(domain):
+函数的值域(range):
+函数可以分为纯函数(pure function)和非纯函数(non-pure function)两类,纯函数指的是没有副作用(side-effect)的函数,反之,非纯函数有副作用,print()
函数就是典型的非纯函数,它将内容显示在终端上。如果一个函数体内没有return
语句,函数会自动返回None
,print()
就是这样返回的。A side effect isn't a value:it is anything that happens as a consequence of calling a function.
有意思的是,在终端中,print()
会显示没有引号的文本,但是return
会保留引号。下面是一个例子:
def what_prints():
+ print('Hello World!')
+ return 'Exiting this function.'
+
+>>> what_prints()
+Hello World!
+'Exiting this function.'
+
None
¶None
在Python里面表示一种特殊的值:Nothing。
None
其实有着其相应的数据类型:NoneType
,所以在进行None + 4
的操作的时候,就会出现类型错误:TypeError: unsupported operand type(s) for +: 'NoneType' and 'int',这表明None
和一般的数据类型不能相加。
input()
函数接受一个参数,即需要向用户显示的提示 (prompt) ,暂停程序运行并将提示输出到屏幕上,等待读取用户输入,在按下回车键之后继续运行,并且将用户的输入作为input()
函数的返回值。
+ | + | + |
---|---|---|
max(a,b) | +输出a和b的最大值 | ++ |
+ | + | + |
+ | + | + |
+ | + | + |
+ | + | + |
+ | + | + |
+ | + | + |
Lambda expressions (sometimes called lambda forms) are expressions that evaluate to fuctions by specifying two things: the parameters and a return expression. Lambda expressions are used to create anonymous functions. The expression lambda parameters : expression
yields a function object, and this unnamed object behaves as a functions object defined with:
While both lambda
expressions and def
statements create function objects, there are some notable differences. lambda
expressions work like other expressions; much like a mathematical expression just evaluates to a number and does not alter the current environment, a lambda
expression evaluates to a function without changing the current environment: It does not create or modify any variables.
Definition: A function is called recursive if the body of that function calls itself, either directly or indirectly.
+Implication: Executing the body of a recursive function may require applying that function again.
+The anatomy of a recursive function:
+operator
模块¶opertaor
模块包含了一套和Python内置的运算符对应的高效率函数,包含的种类有:对象的比较运算、逻辑运算、数学运算和序列运算。
--- | +--- | +--- | +
---|---|---|
+ | + | + |
operator.add() | +add(1,2) | +3 | +
operator.mul() | +mul(2,3) | +6 | +
+ | + | + |
+ | + | + |
+ | + | + |
+ | + | + |
+ | + | + |
+ | + | + |
Docstring:
+An expression describes a computation and evaluates to a value.
+Evaluation procedure for call expressions:
+All expressions can use function call notation. (demo)
+Call expression:
+Environment diagrams visualize the interpreter's progress.
+a function's signature has all the information needed to create a local frame.
+Pure functions: only return values.
+Non-pure functions: have side effects.
+print()
is a non-pure function because it displays its output depending on the argument passed in , and returning None
.
print()
statements¶Abstract
+这是我对《深入理解计算机系统》一书的读书笔记。
+参考:
+《深入理解计算机系统》
+《Comptuer System: A Programmer's Perspective》
+计算机系统的硬件组成包括:总线/Bus,I/O设备/IO devices,主存/Main memory,处理器/Processor。
+总线是贯穿整个系统的一组电子管道,携带信息字节并负责在各个设备之间传输。总线被设计成传送定长的字节块,被称为字/Word,现代的计算机的字长大多为4个字节/32位或者8个字节/64位。
+I/O设备
+主存是一个临时存储设备,在处理器执行程序的时候,用来存放程序和程序处理的数据。主存是由一组动态随机存取存储器/Dynamic random access memory/DRAM组成的,在逻辑上看是一个线性的字节数组,每个字节都有唯一的地址,且地址从零开始。
+中央处理器单元/Central processing unit简称为处理器,处理器负责解释(或者执行)主存中的指令。处理器的核心是大小为一个字的寄存器/Register,称为程序计数器/Program counter/PC,
+当我们对系统的某个部分加速的时候,其对整个系统性能的影响取决于该部分的重要性和加速程度。特别地,假设系统执行某个应用程序需要的时间为\(T_{old}\),某部分所需执行时间与该时间所用的比例为\(\alpha\),而这部分性能提升的比例为\(k\),改进之后,总的执行时间为
+那么,我们可以的到加速比\(S\)为
+我们同时也考虑极限情况,也就是 \(k\to\infty\) 的情况,此时加速比\(S\)为
+这就是加速比的上界了。
+总之,Amdahl's Law告诉我们,为了显著提升系统性能,必须提升全系统中相当大的部分的性能
+并发(concurrency)和并行(parallelism)是两个不同的概念。并发是一个通用的概念,指一个同时具有多个活动的系统。并行是指使用并发来使一个系统运行得更快。并行可以在计算机系统的多个抽象层次上运用,我们按照系统层次结构中从高到底的顺序重点强调三个层次:
+十九世纪中期,布尔通过将逻辑值TRUE
和FALSE
编码为二进制值1
与0
,能够设计为一种代数,以研究逻辑推理的基本原则,这种代数叫做布尔代数/Boolean algebra。
布尔代数有四种基本运算~
、&
、|
、^
,分别对应于逻辑运算NOT、AND、OR与EXCLUSIVE-OR,我们可以列出简单的真值表如下:
接下来,我们将上述四个布尔运算推广到位向量/Bit vectors的运算,所谓位向量就是固定长度\(w\),由0
与1
组成的串。所谓的推广也非常简单,就是将上述四个布尔运算应用到位向量的每一位上,得到的结果也是一个位向量。换句话说,就是我们在C语言中学的按位运算。
无符号数的编码就是经典的二进制编码,假设一个无符号整数数据有\(w\)位,我们可以将位向量写作\(\vec{x}\),也就是\([x_{w-1},x_{w-2},\cdots,x_0]\)来表示向量的每一位。我们用一个函数\(B2U_w\)(是Binary to Unsigned的缩写)来表示二进制向无符号整数的转换:
+我们很容易可以得知:
+最常见的有符号整数编码是补码/Two's-complement编码。在补码编码中,一个\(w\)位的有符号整数\(\vec{x}\)的值可以表示为:
+最高有效位也称为符号位,其权重为\(-2^{w-1}\),其余位的权重和无符号整数编码一样。同样,我们可以得知:
+类似的,我们可以定义四进制与十六进制的编码。
+补码编码有十分有趣的特性:
+++在C库中的
+limits.h
中定义了一些常用的整数的最大值与最小值,用来限制编译器运行的不同整型数据的取值范围,例如INT_MAX
、INT_MIN
、UINT_MAX
等。在C库中的
+stdint.h
中定义了一些固定大小的整数类型,例如int8_t
、uint8_t
、int16_t
、uint16_t
等,这些类型很好地提升了程序的可移植性。
有符号数还有下面两种其他的表示方法:
+这两种编码方式都有统一的缺点:对于数字0
,有两种完全不同的表示方法,并且这两种编码不能很好地支持算数运算,因而,我们现在开始使用更加方便的补码编码。
FPGA/Field Programmable Gate Array/现场可编程门阵列:FPGA器属于专用集成电路/ASIC的一种半定制电路,是可以编程的逻辑列阵,可以按照设计人员的需求配置指定的电路结构,让客户不必依赖于芯片制造商设计和制造的专用集成电路就可以实现所需要的功能,同时实现非常高效的逻辑运算。其基本结构包括可编程输入输出单元,可配置逻辑块,数字时钟管理模块,嵌入式块RAM,布线资源,内嵌专用硬核,底层内嵌功能单元。
+Verilog HDL是一种硬件描述语言,用于从算法级、门级到开关级的多种抽象设计层次的数字系统建模。Verilog HDL提供了编程语言接口,通过这个接口可以在模拟、验证期间从设计外部访问设计,包括模拟的具体控制和运行。
+ +wire的电器特性:
+assign
输入;assign
输出.按位运算符:
+&
:按位与;|
:按位或;^
:按位异或;~
:按位取反;~^
:按位同或;我们首先以一种特别的角度看与门:与的运算的作用之一就是屏蔽,当某个输入的值为零时,与的输出就是零,不管另一个输入是什么。这就使得我想要的数据都未被屏蔽,不想要的都被屏蔽为0。比如对于运算\(A\land S\),\(S\)可以看作一个选择子,当\(S=T\)的时候,输出就是\(A\),不论\(A\)的真值为多少,输出的值就是\(A\)的值;当\(S=F\)的时候,输出就是\(F\),这时候\(A\)就被屏蔽了。
+二路选择器的逻辑就是“屏蔽”,对于下面的二路选择器,最重要的结构就是上下两个与门和中间一个非门,选择信号\(S\)分成两份,通过非门变成两个不同的信号,分别接向两个与门,如果\(S\)的信号为\(1\)/\(T\),那么就将下面的门屏蔽,输出上边的门信号;反之亦然。 +
+这里边利用了Verilog内置的一些门,比如AND
和OR
门。这种描述方式的优点就是可以很好的与真实的电路相对应,但是缺点就是不够简洁,写起来很坐牢。
+
这种描述方法充分利用了与&
、或|
、非~
以及异或^
等运算符代替了AND
、OR
、NOT
等门的描述,使得描述更加简洁。忍不住了,直接写数组。
+
~
>&
>|
,所以这里的写法是正确的。
+我们还应该知道:
+if-else 必须在always块中使用,并且输出必须是reg类型。但是在always@(*)中,内部的reg被综合成wire类型
+多路选择器可以根据选择子从多个单bit输入中选择单bit输出,但是如果我们需要从多个多bit输入中选择多bit输出,那么就需要使用复合多路选择器。复合多路选择器在硬件实现上其实是由多个单路选择器级联而成的。
+七段数码管的显示译码的对应关系如下,使用复合多路选择器,就不难得到下面源码。解释源码的方法很简单,把它的接口a
到g
分开,当卡诺图写就好了。
+
+module SegDecoder (
+ input wire [3:0] data,
+ input wire point,
+ input wire LE,
+
+ output wire a,
+ output wire b,
+ output wire c,
+ output wire d,
+ output wire e,
+ output wire f,
+ output wire g,
+ output wire p
+);
+
+ assign a = LE | ( data[0] & data[1] & ~data[2] & data[3] |
+ data[0] & ~data[1] & data[2] & data[3] |
+ ~data[0] & ~data[1] & data[2] & ~data[3] |
+ data[0] & ~data[1] & ~data[2] & ~data[3] );
+ assign b = LE | ( data[0] & data[1] & data[3] |
+ ~data[0] & data[2] & data[3] |
+ ~data[0] & data[1] & data[2] |
+ data[0] & ~data[1] & data[2] & ~data[3] );
+ assign c = LE | ( data[1] & data[2] & data[3] |
+ ~data[0] & data[1] & ~data[2] & ~data[3] |
+ ~data[0] & data[2] & data[3] );
+ assign d = LE | (~data[0] & data[1] & ~data[2] & data[3] |
+ data[0] & data[1] & data[2] |
+ ~data[0] & ~data[1] & data[2] & ~data[3] |
+ data[0] & ~data[1] & ~data[2] & ~data[3] );
+ assign e = LE | ( data[0] & ~data[1] & ~data[2] |
+ ~data[1] & data[2] & ~data[3] |
+ data[0] & ~data[3] );
+ assign f = LE | ( data[0] & data[1] & ~data[3] |
+ data[1] & ~data[2] & ~data[3] |
+ data[0] & ~data[2] & ~data[3] |
+ data[0] & ~data[1] & data[2] & data[3] );
+ assign g = LE | (~data[0] & ~data[1] & data[2] & data[3] |
+ data[0] & data[1] & data[2] & ~data[3] |
+ ~data[1] & ~data[2] & ~data[3] );
+ assign p = ~point;
+
+endmodule //SegDecoder
+
Abstract
+这是我的HTML笔记。
+为啥要学HTML?
+都学计算机了,总得学点前端吧。况且对于一个强迫症而言,我需要合适的工具来改进排版。
+Info
+HTML(HyperText Markup Language)是用来描述网页的一种标记语言。标记语言使用一套标记标签来描述内容。
+下面是一个完整的HTML页面:
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title> 第一个HTML页面 </title>
+ </head>
+
+ <body>
+ <h1> 第一个标题 </h1>
+ <p> 第一个段落 </p>
+ </body>
+</html>
+
其中:
+- <!DOCTYPE HTML>
声明了文档类型,为HTML5文档。
+- <html>
元素是HTML页面的根元素,定义了整个HTML文档。
+- <head>
元素包含了文档的元(meta)数据,如标题、链接到外部样式表等。
+- <meta charset="utf-8">
这就是一个元数据,定义了网页的编码格式为UTF-8。
+- <title>
元素描述了文档的标题。
+- <body>
元素包含了可见的页面内容,定义了HTML文档的主体。
+- <h1>
元素定义了一个大标题,这是最高级的标题,<h6>
定义了最低级的标题。
+- <p>
元素定义了一个段落。
我们能看出来,HTML标签是由尖括号包围的关键词,比如<html>
,并且大多数标签都是成对出现的,比如<html>
和</html>
,其中前者是开始标签/开放标签,后者是结束标签/闭合标签。
HTML元素和HTML标签表示的是一样的意思,但是严格来说,HTML元素包括了开始标签和结束标签,元素的内容就是标签之间塞进去的部分。
+下面就是一个可视化的HTML页面结构:
+标题:标题是通过<h1>
到<h6>
标签来定义的,<h1>
是最大的标题,<h6>
是最小的标题。
段落:段落是通过<p>
标签来定义的。
链接:链接是通过<a>
标签来定义的,在href属性中指定连接的URL。
图像:图像是通过<img>
标签来定义的,通过src属性指定图像的URL。图像的名称和尺寸是通过属性的方式指定的。
HTML元素以开始标签开始,以结束标签终止,结束标签里边会塞一个斜杠/
,中间塞的就是元素内容。值得注意的是:
没有内容的HTML元素称为空元素,空元素在开始标签内关闭。比如定义换行的标签<br>
就是没有关闭标签的空元素。尽管空元素给我们一种错觉,似乎空元素就不用关闭了,但是在开始标签内添加一个斜杠才是空元素的正确关闭方式,比如<br />
,虽然现在的标准与当前的浏览器对于<br>
都是有效的,但是XHTML、XML和未来版本的HTML都要求所有元素必须关闭。
HTML标签对于大小写不敏感,<P>
和<p>
是一样的,甚至很多网站都使用大写的HYML标签。但是标准推荐小写,并且在XHTML和未来版本的HTML中,都强制使用小写。
对于HTML属性,有下面信息:
+name="value"
;我们拿链接元素来举例:
+ +属性值应该始终被包括在引号之内,双引号是最常用的,但是单引号也可以,如果属性值本身就有了双引号,那么必须在外边使用单引号。
+ + + + + + + + + + + + + +YAML是一种数据序列化语言,更多用于编写配置文件
+大小写敏感
+使用缩进表示层级关系
+缩进只能使用空格,不能使用tab
缩进的空格数目不重要,但是同一个层级上的元素左侧必须对齐
+#
表示注释,但是只支持单行注释
YAML支持下面几种数据类型:
+对象:是键值对的集合,也叫映射(Mapping)
+数组:一组按照次序排列的值,有成为序列(Sequence)或者列表(List)
+纯量:单个的、不可再分的值,也叫标量(Scalar)
+对象键值对使用冒号结构表示:key: value
,冒号的后面要加一个空格。对象支持多级嵌套,也支持流式风格的语法(亦即使用花括号包裹,拿逗号分割),比如:
当遇到复杂对象时,我们允许使用问号?
来声明,这样就可以使用多个词汇(其实是数组)来组成键,亦即对象的属性是一个数组,对应的值也是一个数组:
一组以区块格式(Block Format)(亦即短横线+空格)开头的数据构成一个数组:
+ +YAML也支持*内联格式(Inline Format)的数组(拿方括号包裹,逗号加空格分割):
+ +同样,嵌套格式的数组也是完全支持的,只需要使用缩进表示层级关系就好了。
+字符串一般不需要使用引号包裹,但是如果在字符串之中使用了反斜杠开头的转义字符就必须用引号包裹了:
+strings:
+ - this is a string
+ - 'this is s string'
+ - "this is a string"
+ - this is "a 'string'"
+
布尔值:True
、true
、TRUE
、Yes
、yes
、YES
都为真;False
、false
、FALSE
、No
、no
、NO
皆为假。
整数:除了可以正常写十进制,YAML还支持二进制(前缀0b
)和十六进制(前缀0x
)表示:
浮点数:字面量(3,14
)和科学计数法(6.8523015e+5
)都可以。
空:null
、Null
、~
都是空,不指定默认也是空。
时间戳:使用ISO 8601格式的时间数据(日期和时间之间使用T
链接,使用+
表示时区):
为了保持内容的简洁,避免过多的重复的定义,YAML使用&
表示建立锚点,*
表示引用锚点、<<
表示合并到当前数据:
defaults: &defaults
+ adapter: postgres
+ host: localhost
+
+development:
+ database: myapp_development
+ <<: *defaults
+
+test:
+ database: myapp_test
+ <<: *defaults
+
上面的代码相当于:
+defaults:
+ adapter: postgres
+ host: localhost
+
+development:
+ database: myapp_development
+ adapter: postgres
+ host: localhost
+
+test:
+ database: myapp_test
+ adapter: postgres
+ host: localhost
+
使用竖线|
来表示该语法,这时每行的缩进和尾行空白都会去掉,而额外的缩进则被保留:
+
lines: |
+ 我是第一行
+ 我是第二行
+ 我是吴彦祖
+ 我是第四行
+ 我是第五行
+
+# JSON
+# "lines": "我是第一行\n我是第二行\n 我是吴彦祖\n 我是第四行\n我是第五行"
+
使用右尖括号>
表示该语法,这时只有空白行才会被识别为换行,原来的换行符都会被转换为一个空格:
Abstract
+有关计算机的都在这了!
+这里是我在浙江大学图灵班期间的学习笔记。
+Warning
+这里有部分为了整齐而放弃了一部分考究的内容分类,请不要在意,谢谢!
+System: CSAPP <IN-PROGRESS>
+A proposition is a declarative sentence that is either true or false, but not both. We use letters to denote propositional variables, or sentential variables, i.e. variables that represent propositions. The truth value of a proposition is true, denoted by T, if it is a true proposition, and similiarly, the truth value of a proposition is false, denoted by F, if it is a false proposition.Propositions that cannot be expressed in terms of simpler propositions are called atomic propositions.
+We can form new propostions from existing ones using logical connectives. Here are six useful logical connectives: Negation/NOT (\(\neg\)), Conjunction/AND (\(\land\)), Disjunction/OR (\(\lor\)), Exclusive Or/XOR (\(\oplus\)), Conditional/IF-THEN (\(\to\)), and Biconditional/IFF AND ONLY IF (\(\leftrightarrow\)).
+More on IMPLICATION:
+From \(p\to q\), we can form the converse \(q\to p\), the inverse \(\neg p\to \neg q\), and the contrapositive \(\neg q\to \neg p\). The converse and the inverse are not logically equivalent to the original conditional, but the contrapositive is.
+Construction of a truth table:
+The truth value of \(p\leftrightarrow q\) is the same as the truth value of \((p\to q)\land (q\to p)\), that is to say, \(p\leftrightarrow q\) is true if and only if \(p\) and \(q\) have the same truth value.
+Precedence of Logical Operators: From highest to lowest, the precedence of logical operators is \(\neg\), \(\land\), \(\lor\), \(\to\), and \(\leftrightarrow\).
+Basic terminology and its concepts:
+Two compound propositions \(p\) amd \(q\) are logically equivalent if \(q\leftrightarrow q\) is a tautology. We denote this by \(p\equiv q\) or \(p\Leftrightarrow q\).
+Compound propositions that have the same truth values for all possible cases, in other words, the columns in a truth table giving their truth values agree, are called equivalent.
+(Important) Conditional-disjunction equivalence states that for any propositions \(p\) and \(q\), we have
+Absorption laws states that for any propositions \(p\) and \(q\), we have
+De Morgan's Laws states that for any propositions \(p\) and \(q\), we have
+Distribution laws states that for any propositions \(p\), \(q\), and \(r\), we have
+Identity Laws states that for any propositions \(p\), we have
+Domination Laws states that for any propositions \(p\), we have
+Idempotent Laws states that for any propositions \(p\), we have
+Moreover, Commutative Laws and Associative Laws are also valid for logical connectives: for any propositions \(p\), \(q\), and \(r\), we have
+Involving Conditional and Biconditional statements, we have
+ + + +The Dual of compound proposition that contains only the logic operators \(\land\), \(\lor\), and \(\neg\) the proposition obtained by replacing each \(\land\) by \(\lor\), each \(\lor\) by \(\land\), each \(T\) by \(F\), and each \(F\) by \(T\). The dual of \(S\) is denoted by \(S^*\). For example, the dual of \(p\lor (q\land \neg r)\) is \(\neg p\land (q\lor \neg r)\).
+We already know that only two logical operators \(\{\neg,\land\}\) or \(\{\neg,\lor\}\) are enough to express all logical propositions. Thus, a collection of logical operators is called functionally complete if every possible logical proposition is logically equivalent to a compound proposition involving only these operators.
+The Sheffer Stroke/与非 is a functionally complete set of logical operators. It is denoted by \(p|q\), and \(p|q\) is false when both \(p\) and \(q\) are true, and true otherwise. The Peirce Arrow/或非 is also a functionally complete set of logical operators. It is denoted by \(p\downarrow q\), and \(p\downarrow q\) is true when both \(p\) and \(q\) are false, and false otherwise.
+A compound proposition is satisfiable if there is an assignment of truth values to its variables that makes it true. When no such assignments exits, the compound proposition is unsatisfiable.
+A compound proposition is unsatisfiable if and only if it is a contradiction or its negation is a tautology.
+A list of propositions is consistent if it is possible to assign truth values to the proposition variables so that each proposition is true.
+Propositional formula/命题公式 is a compound proposition that is built up from atomic propositions using logical connectives with the following criteria:
+Formulas can be transformed into standard forms so that they become more convenient for symbolic manipulations and make identification and comparison of two formulas easier. There are two types of normal forms in propositional calculus:
+the Disjunctive Normal Form/DNF/析取范式: A formula is said to be in DNF if it written as a disjuction, in which all terms are conjunctions of literals.
+ For example, \((p\land q)\lor (\neg p\land r)\), \(p\lor (q\land r)\), \(\neg p\lor T\) are in DNF, and the disjunction \(\neg(p\land q)\lor r\) is not.
the Conjunctive Normal Form/CNF/合取范式: A formula is said to be in CNF if it written as a conjunction, in which all terms are disjunctions of literals.
+We can introduce the concept of Clauses/子句 to simplify the concept of DNF and CNF. A disjunction/conjunction with literials as disjuncts/conjuncts are called a Disjunctive/Conjunctive clause/析取子句/合取子句. Disjunctive/conjunctive clauses are simply called clauses. Moreover, conjunctive clause is also called Basic product and disjunctive clause is also called Basic addition.
+Thus, a CNF is a conjunction of disjunctive clauses, and a DNF is a disjunction of conjunctive clauses.
+A Midterm/极小项 is a conjunction of literials in which each variable is represented exactly once.
+(IMPORTANT) There are \(2n\) different minterm for \(n\) propositional variables. For example there \(4\) different minterm for \(p\), \(q\), they are \(p\land q\), \(p\land\neg q\), \(\neg p \land q\), \(\neg p\land\neg q\). For the sake of simplification, we use \(m_j\) denote the minterms. Where \(j\) is a integer, its binary representation corresponds the evaluation of variables that make \(m_j\) be equal to T.
+If a proposition form is denoted by: \(f=m_j\lor m_k\lor\cdots\lor m_l\), then we simply denote
+Properties of minterms:
+If a function, as \(f\), is given by truth table, we know exactly for which assignments it is true. Consequently, we can select the minterms that make the function true and form the disjunction of these minterms.
+If a Boolean function is expressed as a disjunction of minterms, it is said to be in full disjunctive form.
+All above is the concept of DNF and the concept and use of minterms. Now we turn to CNF.
+A compound proposition is in CNF if it is a conjunction of disjunctive clauses. Every proposition can be put in an equivalent CNF. CNF is useful in the study of resolution theorem proving used in AI.
+A compound proposition can be put in conjunctive normal form through repeated application of the logical equivalences covered earlier.
+A Maxterm/极大项 is a disjunction of literials in which each variable is represented exactly once. If a Boolean function is expressed as a conjunction of maxterms, it is said to be in full conjunctive form.
+We can get the full conjunctive form of a Boolean function from its full disjunction form: Let \(f=\sum f(j,k,\cdots,l)\), \(g=\sum m(\{0,1,2,\cdots,2^{n-1}\}-\{j,k,\cdots,l\})\), then \(f\lor g = T\), \(f\land g = F\).
+The \(M_i\) is a maxterm defined by \(M_i=\neg m_i\).
+In this section, we will introduce Predicate logic/谓词逻辑. A predicate refers to a property that the subject of the statement can have. We can denote the statement "\(x\) is greater than \(3\)" by \(P(x)\), where \(P\) denotes the predicate "is greater than \(3\)" and \(x\) is the variable. The statement \(P(x)\) is also said to be the value of the propositional function \(P\) at \(x\).
+Propositional functions become propositions when their variables are each replaced by a value from the domain.
+We need quantifiers/量词 to express the meaning of English words including all and some. Two most important quantifiers are:
+Domain/domain or discourse/universe of discourse: the range of the possible values of the variable.
+Given the domain as \(\{x_1, x_2, \cdots, x_n\}\), the proposition \(\forall xP(x)\) is equivalent to \(P(x_1)\land P(x_2)\land\cdots\land P(x_n)\), and the proposition \(\exists xP(x)\) is equivalent to \(P(x_1)\lor P(x_2)\lor\cdots\lor P(x_n)\).
+Uniqueness Quantifier: \(\exists !\) means "There exists a unique", that is \(P(x)\) is true for one and only one \(x\) in the domain. The uniqueness quantifier can be expressed without the symbol \(!\) : \(\exists x(P(x)\land \forall y(P(x)\to y=x))\).
+Precedence of Quantifiers: The quantifiers \(\forall\) and \(\exists\) have higher precedence than the logical connectives \(\neg\), \(\land\), \(\lor\), \(\to\), and \(\leftrightarrow\). For example, \(\forall xP(x)\land Q(x)\) means \((\forall xP(x))\land Q(x)\).
+Bound Variable: A variable is bound if it is known or quantified. A variable is free if it is neither quantified or specified with a value.
+All the variables in a propositional function must be quantified or set equal to a particular value to turn it into a proposition.
+Scope of a quantifier: the part of a logical expression to which the quantifier is applied
+Logical Equivalence with Logical Quantifiers:
+ +De Morgan's Laws for Quantifiers:
+Nested Quantifiers: Two quantifiers are nested if one is within the scope of the other. For example, \(\forall x\exists yP(x,y)\) means "For every \(x\), there exists a \(y\) such that \(P(x,y)\) is true".
+Order of Quantifiers: Only both quantifiers are universal or both are existential, the order of quantifiers can be changed. +
+Distributions for Quantifiers over Logical Connectives: Here I list two examples: \(\forall x(P(x)\land Q(x))\equiv \forall xP(x)\land \forall xQ(x)\) is True, whereas \(\forall x(P(x)\to Q(x))\equiv \forall xP(x)\to \forall xQ(x)\) is False.
+Valid Argumemts:An argument in propositional logic is a sequence of propositions. All but the final proposition are called premises/前提. The last statement is the conclusion/结论. The argument is valid/有效 if the premises imply the conclusion. An argument form is an argument that is valid no matter what propositions are substituted into its propositional variables.
+If the premises are \(p_1, p_2,\dots, p_n\) and the conclusion is \(q\) then \((p_1\land p2 \land \cdots\land p_n )\to q\) is a tautology.
+Inference Rules are all argument simple argument forms that will be used to construct more complex argument forms.
+Modus Ponens/假言推理: If \(p\to q\) and \(p\) are true, then \(q\) is true. Corresponding Tautology: \((p\land (p\to q))\to q\).
+Modus Tollens/取拒式: If \(p\to q\) and \(\neg q\) are true, then \(\neg p\) is true. Corresponding Tautology: \(((p\to q)\land \neg q)\to \neg p\).
+Hypothetical Syllogism/假言三段论: If \(p\to q\) and \(q\to r\) are true, then \(p\to r\) is true. Corresponding Tautology: \(((p\to q)\land (q\to r))\to (p\to r)\).
+Disjunctive Syllogism/析取三段论: If \(p\lor q\) and \(\neg p\) are true, then \(q\) is true. Corresponding Tautology: \(((p\lor q)\land \neg p)\to q\).
+Addition/附加律: If \(p\) is true, then \(p\lor q\) is true. Corresponding Tautology: \(p\to (p\lor q)\).
+Simplification/简化律: If \(p\land q\) is true, then \(p\) is true. Corresponding Tautology: \((p\land q)\to p\).
+Conjunction/合取律: If \(p\) and \(q\) are true, then \(p\land q\) is true. Corresponding Tautology: \((p\land q)\to (p\land q)\).
+Resolution/消解律: If \(p\lor q\) and \(\neg p\lor r\) are true, then \(q\lor r\) is true. Corresponding Tautology: \(((p\lor q)\land (\neg p\lor r))\to (q\lor r)\).
+Universal Instantiation/全称实例: If \(\forall xP(x)\) is true, then \(P(c)\) is true for any \(c\) in the domain. Corresponding Tautology: \(\forall xP(x)\to P(c)\).
+Universial Generalization/全称引入: If \(P(c)\) is true for any \(c\) in the domain, then \(\forall xP(x)\) is true. Corresponding Tautology: \(P(c)\to \forall xP(x)\).
+Existential Instantiation/存在实例: If \(\exists xP(x)\) is true, then \(P(c)\) is true for some \(c\) in the domain. Corresponding Tautology: \(\exists xP(x)\to P(c)\).
+Existential Generalization/存在引入: If \(P(c)\) is true for some \(c\) in the domain, then \(\exists xP(x)\) is true. Corresponding Tautology: \(P(c)\to \exists xP(x)\).
+Universial Modus Ponens/全称假言推理: If \(\forall x(p(x)\to q(x))\) and \(p(a)\) are true, then \(q(a)\) is true. Corresponding Tautology: \((\forall x(p(x)\to q(x))\land p(a))\to q(a)\).
+A proof is a valid argument that establishes the truth of a statement. In math, CS, and other disciplines, informal proofs which are generally shorter, are generally used.
+A theorem/定理 is a statement that can be shown to be true using: definitions, other theorems, axioms (statements which are given as true), rules of inference.
+A lemma/引理 is a 'helping theorem' or a result which is needed to prove a theorem.
+A corollary/推论 is a result which follows directly from a theorem.
+Less important theorems are sometimes called propositions/命题.
+A conjecture/猜想 is a statement that is being proposed to be true. Once a proof of a conjecture is found, it becomes a theorem. It may turn out to be false.
+Direct Proof: Assume that \(p\) is true. Use rules of inference, axioms, and logical equivalences to show that \(q\) must also be true.
+Proof by Contraposition/反证法: Assume \(\neg q\) and show \(\neg p\) is true also. This is sometimes called an indirect proof method. If we give a direct proof of \(\neg q\to\neg p\) then we have a proof of \(p\to q\).
+Proof by Contradiction/归谬证明法/Reductio ad absurdum: To prove \(p\), assume \(\neg p\) and derive a contradiction such as \(r\land \neg r\). (an indirect form of proof). Since we have shown that \(\neg p\to F\) is true , it follows that the contrapositive \(T\to p\) also holds.
+Proof by cases: To prove \((p_1\lor p_2\lor \cdots\lor p_n)\to q\), using the tautology \((p_1\to q)\land (p_2\to q)\land\cdots\land (p_n\to q)\leftrightarrow (p_1\lor p_2\lor \cdots\lor p_n)\to q\), we need to prove \(p_1\to q\), \(p_2\to q\), \(\cdots\), and \(p_n\to q\).
+Existence Proofs/存在性证明,Without Loss of Generality/不失一般性,Nonconstructive Proofs/非构造性证明,Proof by Counterexample/反例证明,Uniqueness Proofs/唯一性证明,Backward Proof/逆向证明.
+ + + + + + + + + + + + + +A set is an unordered collection of distinct objects, called elements or members of the set. A set is said to contain its elements. We write \(a\in A\) to denote that \(a\) is an element of the set \(A\). The notation \(a\notin A\) denotes that \(a\) is not an element of the set \(A\).
+Roster method: A set can be described by listing its elements between braces. For example, the set of vowels in the English alphabet can be written as \(V=\{a,e,i,o,u\}\). Listing an element more than once does not change the set. The set \(\{a,e,i,o,u\}\) is the same as the set \(\{a,e,i,o,u,u\}\).
+Set-builder notation: A set can be described by specifying a property that its members must satisfy.
+Universal Set: The set \(U\) containing all the objects currently under consideration.
+Empty Set: The set containing no elements, denoted by \(\emptyset\) or \(\{\}\).
+ + + + + + + + + + + + + +Abstract
+这是我在2023-2024学年春夏学期修读《离散数学理论基础》的课程笔记。
+离散数学的内容繁杂,包含逻辑、集合论、图论等内容。对于计算机专业的学生来说,这部分包含的内容更加宽泛,可以说是“在数理基础课上讲不到的都在这了”。
+参考书籍:
+Abstract
+有关数学的都在这了,目前正在学习的有:
+Abstract
+这里是我的分析学笔记,我打算在分析学稍微深耕一点点,所以这里的内容可能会比较多。
+我会奋力学,奋力更的!
+Warning
+什么?还没写?
+先学再说!
+Abstract
+真的在写了!哦,只是抄抄书啊……
+The Brundtland Commission laid out the most famous definition of sustainable development as development that "meets the needs of the present without compromising the ability of future generations to meet their needs."
+ + + + + + + + + + + + + +Info
+Taking Sides,亦即《立场》丛书
+Info
+未完工!我会加紧进度的!
+Warning
+本文仅作为本人对《The Western Heritage》一书的笔记,除记录知识以外再无其它意义
+The western heritage emerges from an evolved and evolving story of human actions and interactions, peaceful and violent, that arose in the eastern Mediterranean, then spread across the western Mediterranean into northern Europe, and eventually to the American continents, and in their broadest impact, to the peoples of Africa and Asia as well.
+The Western Heritage as a distinct portion of world history descends from the ancient Greeks, who saw their own political life based on open discussion of law and policy. The Greeks invented the concept of citizenship, defining it as engagement in some form of self-government. The Greeks also established their conviction that reason can shape and analyze physical nature, politics, and morality.
+Rome spread its authority through military conquest across the Mediterranean world, embracing Greek literature and philosophy. Romans' conquest and imposition of law created the Western world as a vast empire stretching from Egypt and Syria in the east to Britain in the West. Although the Roman Republic, governed by Senate and popular political institutions (元老院与公众政治机构), gave way to the autocratic rule of Roman Empire, the idea of a free republic law and constitutional (宪法的) arrangements limiting political authority survived centuries of arbitrary (武断专制的、随心所欲的) rule by emperors.
+Emperor Constantine reorganized the Roman Empire in two fundamental ways: First, he moved the capital from Rome to Constantinople. Thereafter (其后) large portions of the Western empire became subject to the rulers of Germanic tribes. In the confusion of these times, most of the texts embodying ancient philosophy, literature, and history became lost in the West, and for centuries Western Europeans became intellectually severed from that ancient heritage. Second, Constantine's recognition of Christianity as the official religion of the empire.
+康斯坦丁将基督教作为帝国的官方宗教,由于基督教是单神论宗教,康斯坦丁对基督教的接纳导致了异端多神论宗教的消亡。此后,西方世界或多或少的都有与基督教相连,或者与承认罗马主教为首的基督教会相连。
+随着皇权逐渐崩溃,主教变成了西欧许多区域的事实上的统治者,但是基督教会从未在未与世俗的统治者协商或者冲突的情况下加以统治,并且宗教法也没有取代世俗法,况且世俗的统治者也无法在忽略教会的影响下加以统治。Hence, from the fourth century C.E. to the present day, rival claims to political and moral authority between ecclesiastical and political officials have characterized the west.
+In the seventh century, the rise of Islam, a new monotheistic religion, which spread rapidly through conquests across North Africa and eventually into Spain, confronted a new challenge to the Western World. Christians attempted to reclaim the Holy Land (圣地,亦即巴勒斯坦) from Muslim control in church-inspired military crusades (十字军东征) that still resonate negatively in the Islamic world.
+However, while intellectual life languished in the West, most of the texts of ancient Greek and Latin learning survived and were studied in the Muslim world. By the fourteenth century, European thinkers redefined themselves and their intellectual ambitions by recovering the literature and science from the ancient world, reuniting Europe with its Graeco-Roman past.
+From the twelfth through the eighteenth centuries, a new European political system arose based on centralized monarchies (中央集权的君主制) characterized by large armies, navies and bureaucracies loyal to the monarch (忠诚于皇帝的官僚体制), and by the capacity to raise revenues (提升税收). Most of these monarchies recognized both the political role of local or national assemblies drawn from the propertied elites (有产阶级精英) and the binding power of constitutional law on themselves. ** (宪法对于他们自己的约束力) The monarchies, their military, and their expanding commercial economies became the basis for the extension of European and Western influence around the globe.**
+In the late fifteenth and early sixteenth centuries, two transforming events occurred. The first was the European discovery and the conquest of American continents, thus opening the Americas to Western institutions, religion, and economic exploitation. The labor shortage of Americas led to the forced migration of millions of Africans as slaves to the America. By the mid-seventeenth century the West consequently embraced the entire transatlantic world and its multiracial societies.
+Second, shortly after the American encounter, a religious schism erupted within Latin Christianity. (基督教分裂) Reformers rejecting both many medieval Christian doctrines as unbiblical and the primacy of the Pope in Rome established Protestant churches across much of northern Europe. (宗教改革者不仅反对许多中世纪的基督教义,认为它们是不符合圣经的,还反对罗马天主教教皇的至高无上的地位,并且在北欧的大部分土地上建立了新教教堂) As a consequence, for almost two centuries religious warfare between Protestants and Roman Catholics overwhelmed the continent as monarchies chose to defend one side or the other. The religious turmoil meant that Europeans who conquered and settled the Americans carried with them particularly energized religious convictions, with **Roman Catholics dominating Latin America and English Protestants most of North America. **
+By the late eighteenth century, the idea if the West denoted a culture increasingly dominated by two new forces. First, science arising from a new understanding of nature achieved during the sixteenth and seventeenth centuries persuaded growing numbers of the educated elite that human beings can rationally master nature for ever-expanding productive purposes improving the health and well-being of humankind. From this era to the present, the West has been associated with advances in technology, medicine, and scientific research. Second, during the eighteenth century, a drive for economy improvement that vastly increased agricultural production and then industrial manufacturing transformed economic life, especially in Western Europe and later the United States. Both of these economic development went hand in hand with urbanization and the movement of industrial economy into cities where the new urban populations experienced major urban dislocation (社会失序).
+During the last quarter of eighteenth century, political revolution erupted across the transatlantic world. The British colonies of North America revolted, and then revolution occurred in France and spread across much of Europe. The Wars of Independence liberated Latin America from its European conquerors. Those revolutions created bold new modes of political life, rooting the legitimacy of the state in some form of popular government and generally written constitutions. Thereafter, despite the presence of authoritarian governments on the European continent, the idea of West, now including the new republics of the United States and Latin America, became associated with liberal democratic governments. (这些革命创造了新的政治生活模式,将国家的合法性和某种形式的人民政府和成文宪法联系到了一起。自此之后,除了欧洲大陆某些独裁政府,西方的概念,变得与自由民主的政府联系到了一起。)
+During the nineteenth century, most major European states came to identify themselves in terms of nationality - language, history, and ethnicity - rather than loyalty to a monarch. Nationalism eventually inflamed popular opinion and unloosed unprecedented political ambition by European governments.
+These ambitions led to imperialism and the creation of new overseas European empires in the late nineteenth century. For people living in European-administered Asian and African colonies, the idea and reality of the West embodied foreign domination and often disadvantageous involvement in a world economy. Even after colonial peoples around the globe challenged European imperial authority and gained independence, these former colonial peoples often suspected the West of seeking to control them. Hence, anticolonialism like colonialism before it redefined definitions of the West far from its borders.
+Late nineteenth-century nationalism and imperialism also unleashed with World War I in 1914 unprecedented military hostilities among European nations that spread around the globe, followed a quarter century later by an even greater world war. As one result of World War, revolution occurred in Russia with the establishment of the communist Soviet Union. During the interwar years, a Fascist Party seized power in Italy and a Nazi Party took control of Germany. In response to these new authoritarian regimes, West European powers and the United States identified themselves with liberal democratic constitutionalism, individual freedom, commercial capitalism, science and learning freely pursued, and religious liberty, all of which they defined as the Western Heritage. (将他们自己认为是自由民主立宪政府、拥有个人自由、商业资本主义、拥有追求知识和科学的自由、宗教信仰自由,他们将这些定义为 Western Heritage。)
+During the Cold War , conceived of as an East-West, democratic versus communist struggle that concluded with the collapse of the Soviet Union in 1991, the Western Powers led by the United States continued to embrace those values in conscious opposition to the Soviet government, which since 1945 had also dominated much of Eastern Europe.
+Since 1991 the West has again become redefined in the minds of many people as a world political and economic order dominated by the United States. Europe clearly remains the West, but political leadership has moved to Northern America. That American domination and recent American foreign policy have led throughout the West and elsewhere to much criticism of United States.
+Such self-criticism itself embodies one of the most important and persistent parts of the Western Heritage. From Hebrew prophets and Socrates to the critics of European imperialism, American foreign policy, social inequality, and environmental devastation, voices in the West have again and again been raised to criticize often in the most strident manner the policies of Western governments and the thought, values, social conditions, and the inequalities of Western societies.
+Consequently, we study the Western Heritage not because the subject always or even primarily presents an admirable picture, but because the study of the Western Heritage like the study of all history calls us to an integrity of research, observation, and analysis that clarifies our minds and challenges our moral sensibilities. The challenge of history is the challenge of thinking, and it is to that challenge that this book invites its readers.
+Culture may be defined as the ways of living built up by a group and passed on from a generation to another. It includes behavior such as courtship and child-rearing practices; material things such as tools, clothing, and shelter; and ideas, institutions, and beliefs. Language, apparently a uniquely human trait, lies behind our ability to create ideas and institutions and to transmit culture from one generation to another. Our flexible and dexterous hands enable us to hold and make tools and so to create the material artifacts of culture. Because culture is learned and not inherited, it permits rapid adaption to changing conditions, making possible the spread of humanity to almost all the lands of the globe.
+During the Paleolithic,
+ + + + + + + + + + + + + +Abstract
+在这里的都是我的读书笔记!包括但不限于哲学、历史学、心理学与社会学。
+当然挖的坑越多就越难填(x
+加油哦!
+《数学分析》 梅加强 著
+《数学分析讲义》 陈天权 著
+《Real And Complex Analysis》 Walter Rudin 著
+《哲学导论》 王德峰 著
+《西方现代思想讲义》 刘擎 著
+《The Western Heritage》
+《枫丹白露宫 千年法国史》 让·弗朗索瓦·埃贝尔、蒂埃里·萨尔芒 著
+Abstract
+挖坑时间到!这真的只是我对我的学习期望而已,想学不代表一定会学(x
+当然,我是一定会奋力学的!
+{"use strict";/*!
+ * escape-html
+ * Copyright(c) 2012-2013 TJ Holowaychuk
+ * Copyright(c) 2015 Andreas Lubbe
+ * Copyright(c) 2015 Tiancheng "Timothy" Gu
+ * MIT Licensed
+ */var Va=/["'&<>]/;qn.exports=za;function za(e){var t=""+e,r=Va.exec(t);if(!r)return t;var o,n="",i=0,s=0;for(i=r.index;i