《C语言点滴》学习笔记(1)

这几天再看赵岩老师的《C语言点滴》,以复习巩固C语言的基础知识。现将笔记整理如下,如有错误之处,麻烦在评论区指明,万谢!

第二章 编程基础知识

2.1 编程语言

C语言的前身确实是B语言(20世纪70年代,叫new B语言)。C语言与UNIX来源于一个失败的操作系统项目,这个失败的操作系统项目提供了很多教训。随着C的普及,ANSI最终接管了C的标准制定和维护工作,我们现在用的是 ANSI C语言

由于一开始脱胎于UNIX操作系统,C语言中的很多特性更倾向于硬件。

2.2 如何学好C语言

不管你会不会,马上动手做,然后产生问题,解决问题,进步,再产生问题,再解决问题,再进步……
- 你只有自己实践一遍,有了感性认识,知识才是你的。

2.3 开发平台

Linux系统具有三个优点
- 效率 ,熟悉命令行可以快速完成一些操作。
- 国际交流
- 国外通常在Linux下开发。
- tar.gz 文件
- 程序员理念 。开源

2.4开发工具

  • Code Blocks 中的项目一定要保存在英文路径名中??,否则程序不支持调试
  • Total Commander软件,其中有一个同步文件夹功能。

2.5 编程风格

2.5.1 变量名

  • 命名方法
    • 驼峰方法 每个单词首字母大写
    • 下划线法
  • 一般临时的循环变量常用单字母 i ,j ,k
  • 一般 typedef 多用于定义结构体类型
  • 一般系统中的变量名都是以下划线开头的,所以在给变量或者函数起名时,应该你避免以下划线开头
  • 一个函数应该只完成一个功能,并且长度最好局限在一个屏幕内
    • 一个函数的内部变量最好不要超过7个

第三章 数据类型

3.1原码、反码、补码的解释

  • 原码:二进制码
    • 绝对值相同的数原码相同(符号位除外)
  • 反码:把原码(除符号位以外)的每一个位都取反
    • 其实是为求补码服务的
  • 补码:反码基础上+1

  • 无符号整型数使用原码存储的

  • 有符号整型数使用补码存储的

    • 正数的原码、反码、补码都相同
    • 负数的补码 = (它的反码+1)
  • 虽然我们也可以用二进制中最小的数去对应最小的负数,最大的也相对应,但是那样不科学。

  • 1个字节它不管怎么样还是只能表示256个数,因为有符号所以我们就把它表示成范围:-128~127。它在计算机中是怎么储存的呢?可以这样理解,用最高位表示符号位,如果是0表示正数,如果是1表示负数,剩下的7位用来储存数的绝对值的话,能表示2^7个数的绝对值,再考虑正负两种情况,2^7*2还是256个数。

  • 实际上,10000000(补码)在计算机中表示最小的负整数,就是这里的-128,而且实际上并不是从10000001到11111111依次表示-1到-127,而是刚好相反的,从10000001到11111111依次表示-127到-1。

3.3 整型数的溢出深入分析

溢出的定义

溢出的边界

  • 上溢出是由加法造成
  • 下溢出由减法造成

  • 无符号数,溢出发生在6点钟方向

  • 有符号数,溢出发生在12点钟方向

  • 避免在同一个表达式中混合使用有符号数和无符号数

3.5 int 和 char的关系

  • char就是short short 因为char占的是一个字节——8位
  • char的符号 不确定
    • 各种编译器对char符号都有自己的定义
int getchar()

明明返回一个字符,为什么用int来接收呢?
- 因为getchar();在读到 文件末尾 或者 错误 的时候,会返回 EOF,(EOF在studio.h中被定义为-1);
- 如果返回的是char值,那么当你的平台上的char恰好是无符号的话,getchar永远也不会停止

浮点数的有效位

  • limits.h 中定义了 浮点数的表示范围,与它的有效位

通常float类型的有效位只有6位或7位,后面的数字都是编译器随机猜的,所以浮点数保存的都是一个近似值。
- FLT_EPSILON 定义两个浮点数的差是否 “足够小”
- FLT_DIG 代表 float的有效位

常量与常量后缀

  • 0开头表示 八进制数
    • 010表示十进制的8
  • 0x开头表示 16进制数
    • 0x10 表示十进制的16
  • 常用常量后缀的形式来指定常量的数据类型
    • UL后缀 表示unsigned long

sizeof运算符

  • sizeof是C语言的一个关键字,不是一个函数(只是行为很像是一个函数)。因为它返回一个类型为size_t的无符号的 整型数

  • 考虑到字节对齐,C语言中struct的长度并不等于其中每个单个成员数据类型长度的和。

  • 其他数据类型的长度假设不一定就对。
  • 使用二进制读写文件时,通常用sizeof来计算变量所占内存的真实尺寸。

  • sizeof(任意指针类型) 指针只保存一个地址,所以任何类型的指针都占用相同的字节数。

  • sizeof(数组名)得到的是整个数组占用的字节数。
    • 计算数组a的长度 sizeof(a) / sizeof(a[0])

口诀

296两宏两头
- 29 表示整型数的上限大约为2x10^9
- 6 表示浮点数的有效位为6位
- 两宏 分别表示_MAX类型宏 和 FLT_EPSILON宏
- 两头 分别表示 float.h 和 limits.h 头文件

第四章 表达式和运算符

4.1 自增自减运算符

单目运算符都是右结合的

n = *p++;
等价于:
p++;
n = *p;
-------------------------

++*p++;

++*(p++);
等价于
++(*p);
p=p+1;

4.2 左值和右值

在内存中有一个确定位置的叫左值
但是数组名a不能作为左值

int a = 5;

变量a是左值,因为a在内存有一个位置,而且可以通过变量名来确定这个位置。
  • 左值可分为可修改左值和不可修改左值

4.3 布尔值

以往C中没有bool关键字。C99标准中C开始支持布尔类型_bool ,但是使用麻烦,
- 需要 include

4.4 数据类型的转换

  • 计算结果的数据类型与运算的优先级没有关系,一定是表达式中精度最高的数据类型。
  • 但是具体到数据结果的数值,与运算符的优先级和运算顺序有关系。
  • 如果需要转换,尽量使用强制类型转换。

    • 标准写法 (类型)表达式
    • (float)(x/y) 与 (float) x/y 结果可能完全不同

4.5 写表达式注意的事项

4.5.2 避免运算顺序问题

除了固定顺序的那三个半运算符
(条件、逻辑或、逻辑与),其他运算符号都是没有运算顺序的。
- 当逗号用来分开函数的实参时,运算顺序也是不固定的。

凡是涉及到改变某个值的表达式,例如自增运算符,等号等,最好能自成一行,不要混杂在其他表达式里。

eg:
    int a[] = {1,10,20};
    int* p = a;
    cout<<*p++<<"  "<<*p<<endl; 
    cout<<*p<<endl;

    结果为
    1  1
    10
    而不是自己所想的 1 10
                    10
    另外:               
    f() + g() * h();
    运算的顺序是不确定的,如果要求按照特定的顺序,例如 f先 然后g 最后 h
    解决:加括号是没用,应该向下面那样把语句分为多行
    a = f();
    b = g();
    c = h();
    a + b * c;

4.6 模运算

4.6.1 模运算的基本知识和用法

两个基本特点:
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;
}

4.6.2 模运算和哈希结构

hash函数实例

usigned hash(char *str){
    unsigned int bucket = 0;
    while(*str != '\0')
        bucket = (256 * bucket + *str++) % NBUCKETS;
    return bucket;    
}

霍纳法则编程实现
- 多项式化解为某一种形式

Pn = 0while(n!=0){
    Pn = Pn*x+a[n--];
}

4.7 位运算

位运算的基本用法可归结为3条。
1. 利用位与运算 ‘&’和0 可以 把某位置 0
2. 利用或运算 ‘|’和1 可以把某位置 1
3. 利用异或运算 ‘^’和1可以把某位置翻转。

右移位中的算术、逻辑移位 的 三条线索
1. 算术右移和逻辑右移,操作的结果都是原来的数除以2;
2. 为了满足第一条,对于 无符号数 采用的是逻辑右移
- 对有符号数采用的是 算术右移
3. 逻辑右移时,左侧移入0;
- 算术右移中,最左面的符号位 复制一份,插到符号位的右面

第5章 输入输出

流是一种特殊的数据结构,它是动态的和线性的。
- 动态 是指数据的内容和时间有关,比如在某个时刻你从一个流中读到了一个字节,下一次再读就你不就不是原来的字节了。
- 线性 是指流只在纵向上有长度,在横向上没有宽度。 即 每次流只能读入一个字节,不可能一次同时并行读两个字节。
- 分类
- 按照方向 分为I/O流
- 文本流 二进制流
- 文本流:由文本行组成,每一个文本行由0个或者多个某种字符集的字符组成,并以‘\n’字符为结束标志。
- 读入和读出时, 可能会对其内容做更改。 因为字符是有一定意义的,系统可以识别并在适当时候解释。
- eg:输出文本流中碰到‘\b’时,系统的操作是将输入流中的前一个字符删除,输出的结果就是它前面的输出的这个字符被删除了。
- 二进制流:‘生’的,未处理的 01序列,
- 不会对其内容进行修改

5.2 stdin 、stdout 、stderr

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");
}
  • freopen可以将stdin stdout重新定向。但是不推荐使用。因为重定向出去简单,定向回来很难
  • 解决: 在命令行中进行重新定向标准输入输出。

5.3 单个字符输入输出

buffer和cache的异同

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栗子:

鲁番的葡萄熟了,要用大卡车装葡萄运出去卖
果园的姑娘采摘葡萄,当然不是前手把葡萄摘下来,后手就放到卡车上,而是需要一个中间过程“箩筐”:摘葡萄→放到箩筐里→把箩筐里的葡萄倒入卡车。

  • 即,虽然最终目的是“把葡萄倒入卡车”,但中间必须要经过“箩筐”的转手,这里的箩筐就是Buffer。是“暂时存放物品的空间”。

二者异同:
  • buffer解决的是空间问题。先把东西存到一个桶里,盛满后(最后一次可能存到半桶就行),再倒进水池,避免老是把一丁点水倒进水盆。
  • cache 解决的是时间问题。
  • 为了提高速度,引入了cache这个中间层
  • 为了给信息找到一个暂存的空间,引入了cache这个中间层。
  • 为了解决2个不同维度的问题(时间、空间),恰巧取了同一种方法:加入一个中间层,先把数据写到这个中间层上,然后再写入目标。

  • 这个中间层就是内存“RAM”,既然是存储器就有2个参数:写入的速度有多块(速度),能装多少东西(容量

  • Cache利用的是RAM提供的高读写速度,Buffer利用的是RAM提供的存储容量。


定义: 空白字符:主要指本身没有显示,但是占据一定的水平和垂直距离的字符。
- 能够通过键盘输入的常见的空白字符有三个:\t 、tab、 \n

当我们在键盘上输入的时候,所有的输入都首先保存在缓冲区中,直到我们输入一个回车。如果缓冲区为空,命令行界面会暂停,等待用户输入。
- getchar() 从输入流中得到一个字符。每次读入任意一个字符,包括 回车 等空白字符
- 从默认的stdin(键盘)输入流中得到一个字符
- 对应的函数 putchar()

getch()函数

非标准函数,还是少用吧

5.4 字符 串 输入输出

  • gets()读入字符串,以回车或者EOF 为结束标志,同时把回车从缓冲区中读走。
  • puts() 字符串输出函数。
#include<stdio.h>
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<stdio.h>
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>

include

延伸 《C 陷阱和缺陷》 边界计算与不对称边界

栏杆错误(差一错误)
- 解决这个问题的两种方法,两个通用的法则
1. 首先考虑最简单情况下的特例,然后将得到的结果往外推
2. 仔细计算边界,绝不掉以轻心

数组从零开始的优势。不对称边界 <= <,出界点减去入界点
- 用第一个入界点 和 第一个出界点来表示一个数组的
- 不是引用出界点的那一个元素,而是引用它的地址 &a[N]

image

printf("6.2s",abcd); 

输出  空空空空ab

注:“空”指的空格
  • 听说这样会发生溢出攻击。
    • 因为in和pw两个数组的存储位置 是紧挨在一起的 ,故意让数组越界(C语言不检查数组越界),导致 pw存储的内容被修改。
  • 天啊撸,要输到第16位才开始影响到 pw, 而且结果输出来的in 的长度远远大于8,遇到 ‘\0’才结束。

第六章 控制结构

for循环说明

多层循环体,从内到外分析

==阅读C 陷阱和缺陷 边界计算与不对称边界==

  • 常用的控制提示时间间隔的办法就是采用模运算,例如
#include<stdio.h>
int main(){
    for(int i = 0; i < 10000; i++){
        if(i%10==0){
            printf("program has finished %f\n ",(float)i/10000);
        }
    }

}

延伸 《C 陷阱和缺陷》 边界计算与不对称边界

《C陷阱与缺陷》 p54

差一错误(英语:Off-by-one error,缩写OBOE)是在计数时由于边界条件判断失误导致结果多了一或少了一的错误,通常指计算机编程中循环多了一次或者少了一次的程序错误,属于逻辑错误的一种。

差一错误往往就来源于在数物体还是数间隔选择上出了差错

  • 解决这个问题的两种方法,两个通用的法则:
    1. 首先考虑最简单情况下的特例,然后将得到的结果往外推
    2. 仔细计算边界,绝不掉以轻心

数组从零开始的优势:不对称边界 [ ),用出界点减去入界点即可得到元素数

  • 第一个入界点第一个出界点来表示一个数组的范围。
    1. 取值范围就是上界和下界之差。
      • 38 - 16 值为 22,恰恰是不对称边界16和38之间所包括的元素数目。
    2. 如果取值范围为空,那么上界等于下界。
    3. 即使取值范围为空,上界也永远不可能小于下界。

image

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值