计算机&c
CPU直接与内存打交道,他会读取内存中的数据进行处理,并将结果保存到内存。
###
字符集定义了文字和二进制的对应关系,为字符分配了唯一的编号,而字符编码规定了如何将文字的编码存储到计算机中
ASCLL是“American Standard Code for Information Interchange”的缩写,是专门针对英文的字符集。包含了基本的拉丁字母、阿拉伯数字、标点符号、特殊符号以及一些具有控制功能的字符
###
源程序是人们作为字符序列创建出来的,不能直接执行,需要进行编译(翻译),将其变为可执行程序
###
编译器能够识别代码中的词汇、句子以及各种特定的格式,并将他们转换成计算机能够识别的二进制形式,这个过程称为编译(Compile)。编译也可以理解为“翻译”,类似于将中文翻译成英文、将英文翻译成象形文字,它是一个复杂的过程,大致包括词法分析、语法分析、语义分析、性能优化、生成可执行文件五个步骤,期间涉及到复杂的算法和硬件架构。
###
必须要编译(可执行文件)和链接(和系统库即系统中的一些组件组合在一起)才能变成一个可执行的文件
###
C语言开发者们编写了很多常用函数,并分门别类的放在了不同的文件,这些文件就称为头文件(header file)。每个头文件中都包含了若干个功能类似的函数,调用某个函数时,要引入对应的头文件,否则编译器找不到函数。
###
较早的C语言标准库包含了15个头文件,stdio.h 和 stdlib.h 是最常用的两个:
- stdio 是 standard input output 的缩写,stdio.h 被称为“标准输入输出文件”,包含的函数大都和输入输出有关,puts() 就是其中之一。
- stdlib 是 standard library 的缩写,stdlib.h 被称为“标准库文件”,包含的函数比较杂乱,多是一些通用工具型函数,system() 就是其中之一。
###
这样的一段代码能够独立地完成某个功能,一次编写完成后可以重复使用,被称为函数(Function)。
###
库(Library)是编程中的一个基本概念,可以简单地认为它是一些列函数的集合,在磁盘上往往是一个文件夹。C语言自带的库称为标准库(Standard Library),其他公司或个人开发的库称为第三方库(Third-Party Library)。
###
赋值是指把数据放到内存的过程。
初始化:在生成变量的时候放入数值
赋值:在已生成的变量中放入数值
###
数据类型用来说明数据的类型,确定了数据的解释方式,还指明了数据的长度
变量名不仅仅是为数据起了一个好记的名字,还告诉我们数据存储在哪里
所谓数据长度(Length),是指数据占用多少个字节
###
printf():格式化字符串的第一个参数是转换说明,转换说明必须和要显示的值的类型一致
###
字符常量用单引号,为int类型(putchar()输出);字符串常量用双引号,为string类型(printf()输出)
###
除法运算有两种:求商、求余
浮点数之间的运算,就不会舍弃处理商的小数部分
运算符%本身的特性决定了它只能用于整数之间的运算,而不能用于浮点数之间的运算
###
运算对象,即操作数的类型不同时,较小的数据类型的操作数会转换为较大的数据类型(可以保存的小数位越大)
###
判断表达式是判断表达式的类型和值
赋值表示式的判定结果和赋值后的左操作数类型、值相同
###
a == b == c是错误的写法,因为==是双目运算符,不能实现对三个变量的判断
if(xxx);[这叫空语句]printf("123");永远会输出123
表达式加上分号,就是表达式语句了;只有分号没有表达式的语句叫空语句
###
a = b = xxx;赋值运算符具有[右结合性],所以会被解释为 a = (b = xxx)
上述赋值,不能用于初始化变量时,不能同时声明两个变量
###
复合赋值运算
后置运算符,表达式的值是操作前的值。前置操作符和后置操作符作用的时间点不同(一个是展示前,一个是展示后)
###
switch语句,如果没有break语句,程序将落到下一条语句上
###
逻辑与、逻辑或都是短路求值
###
德摩根定律:对各条件取非,然后逻辑与变为逻辑或,逻辑或变为逻辑与,然后再取其否定,结果和原条件一样。
x && y 和 !(!x || !y )相等
x || y 和 !(!x && !y)相等
###
如果表达式2省略,通常认为控制表达式的值始终不为0,除非使用break语句,否则该循环为无限循环
循环控制语句和分支控制语句都要注意空语句
多重循环中,break仅跳出最内层的循环,除非指定跳出的层数
###
第一个判断是用于第一次输入的判断,while()中的判断是用于第二次及其以后的输入判断
###
为了提高处理效率而把具有相同类型的数据有序地组织起来的一种形式---数组,在内存上排列成一条直线(链表应该是有间隙)
不管几维数组,所有的构成元素被配置在连续的内存空间中
数组声明中的[ ]仅仅是一个分隔符;而访问各个元素的[ ]是运算符,[ ]中的操作数称为下标,表示该元素是首个元素之后的第几个元素,而不是数组中的第几个元素
元素类型为Type类型,元素个数为n的数组,写作Type[n]型
数组初始化:
int v[5] = {1,2,3,4,5,};(最后一个初始值的后面,也要加上逗号)
int v[] = {1,2,3,4,5}
int v[5] = {1,3} /*用{1,3,0,0,0}初始化*/(用0对{}内没有赋初始值的元素进行初始化)
C语言中不能用赋值进行初始化(int v[3]; v = {1,2,3};/*错误*/),C语言中不支持使用基本赋值运算符 = 为数组赋值,要用for语句对数组的元素逐一赋值
###
数组的好处:减少了变量的申明
###
宏的原理和文字处理机或者编辑器的替换处理一样,在将指令替换的基础上,再进行编译与执行处理
对象宏由#define a b定义,表示将该指令后的a替换为b
引入对象式宏后,可以消除程序中的幻数了
对象宏不能用于替换字符串字面量和字符常量中的部分内容,也不能用来替换变量名等标识符中的部分内容
###
只在函数内用到的变量,尽量只在函数内声明和使用。注意不能申明和形参同名的变量
实参和形参是完全不同的两个东西,不用担心实参和形参名字相同
函数调用时传递的只是参数的值
可以认为函数就是程序的一个零件
###
用函数的返回值来初始化变量,只适用拥有自动存储期的对象,不适用拥有静态存储期的对象
###
在程序块(复合语句)中声明的变量的名称,只在该程序块中通用,在其他区域都无效。也就是说,变量的名称从变量声明的位置开始,到包含该声明的程序块最后的大括号}为止这一区间内通用。这样的作用域称为块作用域
在函数外声明的变量标识符,其名称从声明的位置开始,到该程序的结尾都是通用的。这样的作用域称为文件作用域
###
静态存储期,用static关键字定义的对象(变量),自动存储期,在函数中不使用static定义的对象(变量)
静态存储期的在main函数执行之前的准备阶段被创建出来,在程序结束的时候消失
自动存储期的对象在执行到包含该对象的程序块的结尾,也就是大括号}的时候,就会消失
静态存储期的对象不显式初始化,则被初始化为0;自动存储期的对象不显式初始化,则被初始化为不确定的值
###
函数原型声明只声明了函数的返回值和形参等相关信息,并没有定义函数的实体(一般把被调用的函数放在调用函数之前)
函数原型声明的时候可以不指定形参的名称
库函数printf或者putchar等的函数原型声明都包含在<stdio.h>中
通过#include <stdio.h>就可以把<stdio.h>中的全部内容都读取到程序中
###
在给函数传递数组的时候,为了禁止在函数内修改接收到的数组,可以在形参前加上被称为const的类型修饰符
###
哨兵查找法比线性查找(或者顺序查找)的好处在于可以减少判断的次数
###
C接受数组的形参,例:int v[]。调用函数时使用的实参,只要写明数组的名称就可以了
接受多维数组的函数,可以省略相当于开头下标的n维的元素个数。但是(n-1)维之下的元素个数必须使用常量
###
十进制转化为二进制可以参考十进制转十进制,十进制除以10的意思就是右移1位
算数类型可以分为:整数类数据类型和浮点型
整数类数据类型:枚举型、字符型和整型
- 整型分为:无符号整数和有符号整数,声明变量时,可以通过加上类型说明符signed和unsigned来指定其中一种数据类型,多不指定,默认为有符号的
- 还可以根据表示的数的个数将整型分为:char、short int、int、long int,char比较特殊,存在既不带signed又不带unsigned的"单独"的char型
C语言编译器在<limits.h>头文件中以宏的形式定义了字符型以及其他整型所能表示的数值的最小值和最大值
"位"是具有大量内存空间的运行环境的数据存储单元,可保存具有两种取值的对象
C语言中将表示字符的char型的长度定义为1(字节),由对象式宏CHAR_BIT定义在<limits.h>中,因编译器而定,至少为8位,但sizeof(char)必定为1
字符型和整型能够表示的数值范围之所以因编译器而异,是因为在内存上占据的位数因编译器而异
sizeof运算符可以判断出包括char型在内的所有数据类型的长度,该运算符以字节(byte)为单位
sizeof(int):了解数据类型的长度、sizeof (表达式):了解变量或者表达式的长度
各种数据类型有符号版和无符号版的长度相同
sizeof运算符生成的值的数据类型是在<stddef.h>头文件中定义的size_t型(许多编译器用typedef声明来定义size_t型)
整数内部的位表示使用的是纯二进制计数法。但对于构成整型的位序列的解释,无符号类型和有符号类型是完全不同的
同一数据类型的变量数值大小不一样,但长度应该都是一样;有符号的绝对值是无符号的一半,因为最左边一位用来存储符号
###
u和U表示该整型常量为无符号型
l和L表示该整型常量为long型
表示浮点数时,后缀f或F表示float型,后缀l或L表示long double型
八进制常量以0开头,十六进制常量以0x开头
###
有符号整数的内部表示法有:补码、反码、符号和绝对值
###
0的原码有两个值,有"正零"和"负零"之分,机器遇到都当做0处理。[+0] = 00000000,[-0] = 10000000(将带符号位的机器数对应的真正数值称为机器数的真值)
0的反码有两个,[+0] = 00000000,[-0] = 11111111
反码:正数的反码是其本身,负数的反码是在其原码的基础上,符号位不变,其余各个位取反
###
负数补码的两种快捷算法:数值位取反后加1或者从原码的右数第一个1后开始一直往左直到符号位取反码(正数的补码也是其本身)
在补码中,只有一个0即[0000_0000]补,-0不存在。[1000_0000]补,代表的是-128,此时他并没有原码和补码表示
###
二进制(原码)的加减法:(要符号位和数值位)1:同号的两数相加,则数值相加。2:如果是异号,则要进行减法运算,首先比较绝对值的大小,然后用大数减去小数,在选择适当的符号
###
补码的定义:把某数X加上模数K,称为以K为模的X的补码。
[X]补=K+X
采用补码表示法进行加减法运算,比原码运算方便,符号位可以和数值位一起参加运算,而不论数是正还是负,计算机总是做加法
###
通过我们定义unsigned或者signed变量,计算机就可以知道保存的是有符号还是无符号的数
无符号整数能够表示的最大值是2^(n)-1
如果位数是n,反码的范围是-2^(n-1)+1~2^(n-1)-1
如果位数是n,补码的范围是-2^(n-1)~2^(n-1)-1
###
按位运算符
###
位移运算符
<<运算符,a<<b,将a左移b位,右边空出的位用0填充
>>运算符,a>>b,将a右移b位
算术位移:
>>=为复合赋值运算符,和x = x>>1;的作用一样
在许多编译器中,会执行逻辑位移或算术位移。无论采用哪种方法都会降低程序的可移植性,所以我们要记住不要对负数进行位移
###
无符号整型的运算不会发生数据溢出,当运算结果超过最大值,结果为,与该无符号整型能够表示的最大整数+1(原因是无符号整型是从0开始的)的余数
因数据溢出(溢位)使运算结果超出可表示的数值范围或违反数学定义(除以0)时会发生异常,发生异常时程序如何运行由编译器决定
###
浮点数是表示带有小数的实数,有三种数据类型float、double、long double。
计算机内部表示浮点数时,是转化为类似科学计数法表示,存的是S/E/M(符号/指数[余码表示法]/尾数)。(指数部分的位数越多,说明能够表示的数越大;尾数部分的位数越多,说明能够表示的值精度越高)
计算机不能保证内部转化为二进制的浮点数的每一位都不发生数据丢失(不能用0.5,0.25,0.75,...的和来表示的值,就不能用有限位数的二进制数来表示)
###
运算符具有优先级和结合性,例如双目运算符'-'是左结合性,简单赋值运算符'='是右结合性
&是取址运算符,*是指针运算符
用误差的数,继续操作,就会发生误差累积(循环判断基准所使用的变量最好是整数,而非浮点型,因为这样可以避免误差积累)
###
函数相比函数式宏默默无闻地进行了一些复杂处理
- 参数传递(将实参的值复制到形参)
- 函数调用和函数返回操作(程序流程的控制)
- 返回值的传递
宏的副作用:例如sqr(a++)展开后为((a++)*(a++))。每次展开,a的值都会自增2次。在不经意间表达式被执行了2次,导致程序出现预料之外的结果
宏的注意事项:
###
除了括号,表达式与";"之间不能有其他符号,否则视为空语句么
逗号运算符连接的两个表达式"a,b"在语法上可以视为一个表达式(其实不仅限于逗号运算符,只要是由运算符连接的多个表达式,例如"a+b",都可以视为一个表达式)
对于使用逗号运算符的逗号表达式"a,b",会按顺序判断表达式a和b。对右侧的表达式b进行判断所得到的类型和值,就是逗号表达式"a,b"的类型和值
###
有n个元素的情况下,只需重复进行n-1趟(趟:将已分组的里面添加一个新成员),就可以完成排序
###
类型名称不是animal型,而是enum animal型;enum animal selected是enum animal型变量selected的声明。通过这个声明,定义了变量selected的取值范围为0、1、2、3,如果给selected赋值为5,一些人性化的编译器将会发出警告信息,提示赋给selected的是未定义的值
###
递归函数调用与其说是“调用该函数本身”,理解为“调用和该函数同样的函数”更加自然。如果真的是调用函数本身,则会一直调用下去,进入死循环
###
getchar函数的功能是从标准输入流中读取下一个字符并将其返回。输入结束或读取过程中发生错误时,就会返回EOF值
EOF来源于End Of File。在<stdio.h>头文件中,eof被定义为负值。如果未将<stdio.h>头文件包含进来,那么EOF就没有定义,这时程序不能编译和运行
在没有包含<stdio.h>头文件,进行如下定义#define EOF -1是不行的。因为EOF规定为负,但不一定是-1.因此,在EOF不为-1的编译器或运行环境中手动进行定义的程序难以保证编译和运行结果的正确性
C语言中的“字符”就是该字符的字符编码(即整数值)
###
在字符串字面量的末尾会被加上一个叫做null字符的值为0的字符。用八进制转义字符表示null字符就是'0'。若用整数常量表示就是0
字符串字面量的长度,和包括末尾的null字符在内的字符数一致,可以通过sizeof()求得
对同一字符串字面量的处理方式依赖于编译器,可以把它们视为相同,共用一个字符串字面量,这样只需要5个字符的内存空间即可。而如果视为不同,并分别存储在内存空间上,则需要10个字符的内存空间
字符串字面量具有静态存储期,因此它"活在"从程序开始到结束的整个生命周期内
字符串最适合放在char数组中存储。字符串的末尾是首次出现的null字符
s表示输出字符串。即输出数组的字符,直到null字符的前一个字符为止。如果没有明确指定精度或精度大于数组长度,则数组中必须含有null字符
字符串可以用字符数组来表示,所以字符串的集合也可以用数组的数组来表示;在将字符数组传入scanf函数时不可以带&运算符
数组初始化时,如果没有初始值,则元素个数不可省略
像char str[4] = {'A', 'B', 'C', 'D'};这样声明的变量不会被当做字符串,我们把它当做4个字符的集合,也就是"普通的"数组来使用就行了
###
对象的地址是指对象在内存中的存储位置编号,对象的地址通常是用十六进制表示的。但是不同的编译器或不同的运行环境下。基数、位数等显示形式以及具体数值都会有所不同
单目运算符&通常被称为取值运算符。将&运算符写在对象名之前,就可以得到该对象的地址。如果对象的长度为2,占用212号和213号内存单元,那么该对象的地址就是它的首地址212号
一般情况下,指向Type型对象的指针,即Type*型指针,并不只是指向"oo号",更确切地说是指向"以oo号为首地址的Type型对象"
除了使用一些特殊的技巧的情况下,Type*型指针一般不会指向Type型以外的对象
单目运算符*叫做指针运算符,将指针运算符*写在指针之前,就可以显示该指针指向的对象内容(int *isako,通过该声明定义了指向int型变量的指针变量)
*运算符也称为间接访问运算符
当p指向x时,*p就是x的别名
变量是可以装东西的盒子
函数返回到调用源的返回值只能有1个,不可能返回两个以上的值
在C语言程序中,指针的一个重要作用就是作为函数的参数来使用。如果要在函数中修改变量的值,就需要传入指向该变量的指针,即告诉程序:传入的是指针哦,请对该指针指向的对象进行处理吧
scanf函数接收的是指针(具有地址的"值"),由该指针所指对象保存从标准输入(一般为键盘)读取到的值
空指针是"什么也不指向"的特殊指针,表示空指针的对象式宏,是称为空指针常量的null
空指针常量null在<stddef.h>中定义。只要在预处理命令中包含<stdio.h>、<stdlib.h>、<string.h>、<time.h>中任意一个头文件,便可使用该宏
###
数组名原则上会被解释为指向该数组起始元素的指针,也就是说,如果数组a是数组,那么表达式a的值就与a[0]的地址,即&a[0]一致
指针p指向数组中的元素e时,p+i为指向元素e后第i个元素的指针,p-i为指向元素e前第i个元素的指针(数组是连续的)
但是,"p+i指向a[i]",仅限于p指向a[0]的时候。如果指针p指向a[2],那么指针p-1就指向a[1],指针p+1指向a[3](在指针前写上指针运算符*来访问该指针指向的对象,称为解引用。如果被调用的函数的参数是指针类型,那么通过在该指针前写上指针运算符并进行解引用,就可以间接访问调用方的对象)
数组a的元素个数为n时,构成数组a的元素是a[0]到a[n-1],共"n"个。但是,指向数组元素的指针则可以使&a[0]到&a[n],共"n+1"个。出现指针情况,是因为在对遍历数组元素的结束条件(是否到达了末尾)进行判定时,如果可以利用指向末尾元素后一个元素的指针将会非常方便
指针之间做减法是OK的,也可以为指针加上整数,但是指针之间相加是不可以的
a会被解释为指向数组起始元素的指针,但不可改写其值。如果可以这样赋值,那么数组的地址就会被改变,变为别的地址。因此,赋值表达式的左操作数不可能是数组名
上述说到不可使用赋值运算符复制数组的所有元素,不过更为准确的说法可能是"不可使用赋值运算符改变指向数组起始元素的指针"
*(p+i),括号内的p+i,是p和i的加法运算。和算术类型的数值间的加法运算a+b等同与b+a一样,p+i也等于i+p,也就是说*(p+i)和*(i+p)是等价的。访问数组元素的表达式p[i]也可以写成i[p](下标运算符[ ],是具有两个操作数的双目运算符。这两个操作数的顺序是随意的)
在Type型对象x前写上取址运算符&得到表达式&x,该表达式会生成指向对象x的指针。生成的指针的类型为Type*型,值为x的地址(指针的声明,应该是用'*')
如果数组a的元素类型为Type型,那么不管元素个数是多少,表达式a的类型就是Type*型
函数间数组的传递,是以指向第一个元素的指针的形式进行的,即使像图b那样指定元素个数,该值也会被无视。意味着,在传递数组时有必要将其元素个数作为别的参数来处理
由于指针v指向数组a的起始元素a[0],因此在ary_set(),指针v的行为就像数组a其本身一样
算术类型和指针统称为标量
###
将str那样的字符串称为用数组实现的字符串,将ptr那样的字符串称为用指针实现的字符串
一般情况下,我们把指针p指向字符串字面量"string"的首个字符's',称为"指针p指向string"。(要明白:指针指向的不是字符串字面量,而是字符串字面量的首个字符)
从图可以看出,指针ptr和字符串字面量"123"双方都占据了内存空间
指针ptr占用的内存空间为sizeof(ptr),即sizeof(char*)字节,其长度因编译器而异
用指针实现的字符串比用数组实现的字符串需要更多的内存空间
通过下标运算符[],可以访问字符串中的各个字符,是两种字符串的共同点。例如,str[0]为'A',ptr[1]为2
不能给数组赋值,如果可以赋值,会改变数组的地址(即数组在内存空间上移动了)
可以为指向字符串字面量(中的字符)的指针赋上指向别的字符串字面量(中的字符)的指针。赋值后,指针指向新的字符串字面量(中的字符)
如上图,都既有"123"又有"456"(二者都占用内存空间),是因为字符串字面量具有静态存储期,并不是说不需要的话就会被从内存空间清除