这几天再看赵岩老师的《C语言点滴》,以复习巩固C语言的基础知识。现将笔记整理如下,如有错误之处,麻烦在评论区指明,万谢!
C语言的前身确实是B语言(20世纪70年代,叫new B语言)。C语言与UNIX来源于一个失败的操作系统项目,这个失败的操作系统项目提供了很多教训。随着C的普及,ANSI最终接管了C的标准制定和维护工作,我们现在用的是 ANSI C语言
由于一开始脱胎于UNIX操作系统,C语言中的很多特性更倾向于硬件。
不管你会不会,马上动手做,然后产生问题,解决问题,进步,再产生问题,再解决问题,再进步……
- 你只有自己实践一遍,有了感性认识,知识才是你的。
Linux系统具有三个优点
- 效率 ,熟悉命令行可以快速完成一些操作。
- 国际交流
- 国外通常在Linux下开发。
- tar.gz 文件
- 程序员理念 。开源
补码:反码基础上+1
无符号整型数使用原码存储的
有符号整型数使用补码存储的
虽然我们也可以用二进制中最小的数去对应最小的负数,最大的也相对应,但是那样不科学。
1个字节它不管怎么样还是只能表示256个数,因为有符号所以我们就把它表示成范围:-128~127。它在计算机中是怎么储存的呢?可以这样理解,用最高位表示符号位,如果是0表示正数,如果是1表示负数,剩下的7位用来储存数的绝对值的话,能表示2^7个数的绝对值,再考虑正负两种情况,2^7*2还是256个数。
实际上,10000000(补码)在计算机中表示最小的负整数,就是这里的-128,而且实际上并不是从10000001到11111111依次表示-1到-127,而是刚好相反的,从10000001到11111111依次表示-127到-1。
下溢出由减法造成
无符号数,溢出发生在6点钟方向
有符号数,溢出发生在12点钟方向
避免在同一个表达式中混合使用有符号数和无符号数。
int getchar()
明明返回一个字符,为什么用int来接收呢?
- 因为getchar();在读到 文件末尾 或者 错误 的时候,会返回 EOF,(EOF在studio.h中被定义为-1);
- 如果返回的是char值,那么当你的平台上的char恰好是无符号的话,getchar永远也不会停止
通常float类型的有效位只有6位或7位,后面的数字都是编译器随机猜的,所以浮点数保存的都是一个近似值。
- FLT_EPSILON 定义两个浮点数的差是否 “足够小”
- FLT_DIG 代表 float的有效位
sizeof是C语言的一个关键字,不是一个函数(只是行为很像是一个函数)。因为它返回一个类型为size_t的无符号的 整型数
考虑到字节对齐,C语言中struct的长度并不等于其中每个单个成员数据类型长度的和。
使用二进制读写文件时,通常用sizeof来计算变量所占内存的真实尺寸。
sizeof(任意指针类型) 指针只保存一个地址,所以任何类型的指针都占用相同的字节数。
296两宏两头
- 29 表示整型数的上限大约为2x10^9
- 6 表示浮点数的有效位为6位
- 两宏 分别表示_MAX类型宏 和 FLT_EPSILON宏
- 两头 分别表示 float.h 和 limits.h 头文件
单目运算符都是右结合的
n = *p++;
等价于:
p++;
n = *p;
-------------------------
++*p++;
即
++*(p++);
等价于
++(*p);
p=p+1;
在内存中有一个确定位置的叫左值。
但是数组名a不能作为左值
int a = 5;
变量a是左值,因为a在内存有一个位置,而且可以通过变量名来确定这个位置。
以往C中没有bool关键字。C99标准中C开始支持布尔类型_bool ,但是使用麻烦,
- 需要 include
如果需要转换,尽量使用强制类型转换。
除了固定顺序的那三个半运算符
(条件、逻辑或、逻辑与),其他运算符号都是没有运算顺序的。
- 当逗号用来分开函数的实参时,运算顺序也是不固定的。
凡是涉及到改变某个值的表达式,例如自增运算符,等号等,最好能自成一行,不要混杂在其他表达式里。
eg:
int a[] = {1,10,20};
int* p = a;
cout<<*p++<<" "<<*p<*p<1 1
10
而不是自己所想的 1 10
10
另外:
f() + g() * h();
运算的顺序是不确定的,如果要求按照特定的顺序,例如 f先 然后g 最后 h
解决:加括号是没用,应该向下面那样把语句分为多行
a = f();
b = g();
c = h();
a + b * c;
两个基本特点:
1. 只能用在整型数上
2. 余数的符号与被除数相同
常用的用法:
1. 判断整除
- a % b == 0
2. 映射到某个范围
- a % 100 + 1 ,a将会被映射到 1 ~ 100
- 模拟随机抽取一张扑克牌 rand() % 54 +1
3. 得到后n位数
- a% 1000 得到a的后三位数
4. 分别得到每位上的数值
- a%10得到a的最后一位数,然后将这一位去掉
int a = 1234;
while(a){
printf("%d\n", a%10);
a /= 10;
}
//将a的二进制打印出来
while(a){
printf("%d\n",a%2);
a /= 2; //等价于 a = a>>1;
}
hash函数实例
usigned hash(char *str){
unsigned int bucket = 0;
while(*str != '\0')
bucket = (256 * bucket + *str++) % NBUCKETS;
return bucket;
}
霍纳法则编程实现
- 多项式化解为某一种形式
Pn = 0;
while(n!=0){
Pn = Pn*x+a[n--];
}
位运算的基本用法可归结为3条。
1. 利用位与运算 ‘&’和0 可以 把某位置 0
2. 利用或运算 ‘|’和1 可以把某位置 1
3. 利用异或运算 ‘^’和1可以把某位置翻转。
右移位中的算术、逻辑移位 的 三条线索
1. 算术右移和逻辑右移,操作的结果都是原来的数除以2;
2. 为了满足第一条,对于 无符号数 采用的是逻辑右移,
- 对有符号数采用的是 算术右移
3. 逻辑右移时,左侧移入0;
- 算术右移中,最左面的符号位 复制一份,插到符号位的右面
流是一种特殊的数据结构,它是动态的和线性的。
- 动态 是指数据的内容和时间有关,比如在某个时刻你从一个流中读到了一个字节,下一次再读就你不就不是原来的字节了。
- 线性 是指流只在纵向上有长度,在横向上没有宽度。 即 每次流只能读入一个字节,不可能一次同时并行读两个字节。
- 分类
- 按照方向 分为I/O流
- 文本流 二进制流
- 文本流:由文本行组成,每一个文本行由0个或者多个某种字符集的字符组成,并以‘\n’字符为结束标志。
- 读入和读出时, 可能会对其内容做更改。 因为字符是有一定意义的,系统可以识别并在适当时候解释。
- eg:输出文本流中碰到‘\b’时,系统的操作是将输入流中的前一个字符删除,输出的结果就是它前面的输出的这个字符被删除了。
- 二进制流:‘生’的,未处理的 01序列,
- 不会对其内容进行修改
stdout stderr 默认情况下都是指向屏幕的。但是有区别
- stdout 的内容首先保存到缓冲区
- stderr 直接输出到屏幕。
- 错误信息越早看到越好嘛。
如果希望马上看到输出的信息。有以下两种方法:
int main(){
//利用printf函数,马上调用fflush(stdout)将缓冲区内的内容输出到屏幕
printf("1 run here now\n");
fflush(stdout);
//直接传入stderr
fprintf(stderr,"2 run here now\n");
}
ref
cache栗子:
- 以PC为例。CPU速度很快,但CPU执行的指令是从内存取出的,计算的结果也要写回内存,但内存的响应速度跟不上CPU。
- CPU跟内存说:你把某某地址的指令发给我。内存听到了,但因为速度慢,迟迟不见指令返回,这段时间,CPU只能无所事事的等待了。这样一来,再快的CPU也发挥不了效率。
- 怎么办呢?在CPU和内存之间加一块“蓄水池”,也就是Cache(片上缓存),这个Cache速度比内存快,从Cache取指令不需要等待。
- 当CPU要读内存的指令的时候先读Cache再读内存,但一开始Cache是空着的,只能从内存取,这时候的确是很慢,CPU需要等待。
- 但从内存取回的不仅仅是CPU所需要的指令,还有其它的、当前不需要的指令,然后把这些指令存在Cache里备用。
- CPU再取指令的时候还是先读Cache,看看里面有没有所需指令,如果碰巧有就直接从Cache取,不用等待即可返回(命中),这就解放了CPU,提高了效率。(当然也不会是100%命中,因为Cache的容量比内存小)
所谓Cache,就是“为了弥补高速设备和低速设备之间的矛盾”而设立的一个中间层。因为在现实里经常出现高速设备要和低速设备打交道,结果被低速设备拖后腿的情况。
buffer栗子:
鲁番的葡萄熟了,要用大卡车装葡萄运出去卖
果园的姑娘采摘葡萄,当然不是前手把葡萄摘下来,后手就放到卡车上,而是需要一个中间过程“箩筐”:摘葡萄→放到箩筐里→把箩筐里的葡萄倒入卡车。
为了解决2个不同维度的问题(时间、空间),恰巧取了同一种方法:加入一个中间层,先把数据写到这个中间层上,然后再写入目标。
这个中间层就是内存“RAM”,既然是存储器就有2个参数:写入的速度有多块(速度),能装多少东西(容量)
Cache利用的是RAM提供的高读写速度,Buffer利用的是RAM提供的存储容量。
定义: 空白字符:主要指本身没有显示,但是占据一定的水平和垂直距离的字符。
- 能够通过键盘输入的常见的空白字符有三个:\t 、tab、 \n
当我们在键盘上输入的时候,所有的输入都首先保存在缓冲区中,直到我们输入一个回车。如果缓冲区为空,命令行界面会暂停,等待用户输入。
- getchar() 从输入流中得到一个字符。每次读入任意一个字符,包括 回车 等空白字符
- 从默认的stdin(键盘)输入流中得到一个字符
- 对应的函数 putchar()
非标准函数,还是少用吧
#include
int main(){
char str[30];
char c;
gets(str);
puts(str);
c = getchar();
putchar(c);
return 0;
}
很多scanf方面的额错误来源于缓冲区中有我们上次输入剩余的字符或错误的字符
- scanf与输入缓冲区
下面的第一个程序输入w 然后输入abc并回车后,屏幕出现”you type wrong choice“
- 原因:scanf读走“abc” 但是缓冲区内还有一个回车。
#include
int main(){
char str[100];
char c;
while(1){
printf("Please input your choice...\n");
c = getchar();
if(c == 'q'){
break;
}else if(c == 'w'){
scanf("%s",str);
scanf(" ");
printf("%s",str);
}else{
printf("you type wrong choice.\n");
}
}
}
````
//scanf如果读取失败,会退出,但是它不会从缓冲区内读走不匹配的数据
<div class="se-preview-section-delimiter">div>
栏杆错误(差一错误)
- 解决这个问题的两种方法,两个通用的法则
1. 首先考虑最简单情况下的特例,然后将得到的结果往外推
2. 仔细计算边界,绝不掉以轻心
数组从零开始的优势。不对称边界 <= <,出界点减去入界点
- 用第一个入界点 和 第一个出界点来表示一个数组的
- 不是引用出界点的那一个元素,而是引用它的地址 &a[N]
printf("6.2s",abcd);
输出 空空空空ab
注:“空”指的空格
for循环说明
多层循环体,从内到外分析
==阅读C 陷阱和缺陷 边界计算与不对称边界==
#include
int main(){
for(int i = 0; i < 10000; i++){
if(i%10==0){
printf("program has finished %f\n ",(float)i/10000);
}
}
}
《C陷阱与缺陷》 p54
差一错误(英语:Off-by-one error,缩写OBOE)是在计数时由于边界条件判断失误导致结果多了一或少了一的错误,通常指计算机编程中循环多了一次或者少了一次的程序错误,属于逻辑错误的一种。
差一错误往往就来源于在数物体还是数间隔的选择上出了差错。
数组从零开始的优势:不对称边界 [ ),用出界点减去入界点即可得到元素数