《C编程专家》——第一章学习笔记

C编程专家——第一章

1.1 C语言的史前阶段

先有C语言还是先有UNIX?——UNIX比C语言出现的早(UNIX系统时间是从1970年1月1日起按秒计算的)

早期C、UNIX和相关的硬件系统关系如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hv25RjWI-1606129959381)(C:\Users\PY\AppData\Roaming\Typora\typora-user-images\image-20201121112425072.png)]

软件信条:
编译器设计者的金科玉律:效率(几乎)就是一切
  • 在编译器中,效率几乎就是一切。还有别的需要关心:有意义的错误信息、良好的文档、产品支持
  • 编译器的效率包括:运行效率(代码的运行速度)、编译效率(产生可以执行代码的速度)
  • 很多编译优化措施会延长编译时间,但却能缩短运行时间。有的既能缩短编译时间,又能减少运行时间,同时还能减少内存的使用量(eg:清除无用代码、忽略运行时检查等)
  • 上述的优化措施的缺点:无法发现程序中无效的运行结果。

当1970年开发平台转移到PDP-11后,无类型语言很快就显得不合时宜了。这种处理器以硬件支持几种不同长度的数据类型为特色,B语言无法表达出数据类型。效率也不高,Dennis Ritchie利用PDP-11的强大性能,创立的能够同时解决多种数据类型和效率的“New B”(后来的C)语言,它采用编译模式而不是解释模式,并引入了类型系统,每个变量在使用前必须先声明。

1.2 C语言的早期体验

Pascal中,类型系统的目的是保护程序员,防止她们在数据上进行无效的操作。C语言排斥强类型,它允许程序员需要时可以在不同类型的对象间赋值。时至今日,许多C程序员任然认为“强类型”只不过是增加了敲击键盘的无用功。

百度百科:强类型

强类型指的是程序中表达的任何对象所从属的类型都必须能在编译时刻确定。常见的强类型语言有C++、Java、Apex和Python等。强类型语言在大规模信息系统开发中具有巨大优势。

C语言的许多特性是为了方便编译器设计者而建立的(因为开始几年C语言的主要客户就是那些编译器设计者)。根据编译器设计者的思路发展形成的语言特性有:

  1. 数组下标从0而不是1开始。——因为偏移量的概念在她们心中根深蒂固。
  2. C语言的基本数据类型直接与底层硬件相对应。——某种语言要素如果底层硬件没有提供直接支持,那么编译器设计者就不会在上面浪费精力。C一开始不支持浮点型,直到硬件系统能直接支持浮点数。
  3. auto关键字是摆设。——这个关键字是创建符号表入口的编译器设计者有意义。它是“在进入程序块时自动进行内存分配”(与全局静态分配在堆上动态分配相反)
  4. 表达式中数组名可以看作指针。——把指针传递到一个函数时,不必忍受必须复制所有数组内容的低效率。(不过,数组和指针并不是在任何情况都等效的)
  5. float被自动扩展为double。——在ANSI C中不再如此。最初浮点数常量的精度都是double型,所有表达式中float变量总被自动转换成double。这与PDP-11中浮点数的硬件表示方式有关,因为在PDP-11或VAX中,从float转换到double代价很小,只要在后面增加一个每个位均为0的字即可。如果要转回来,去掉第二个字即可。在一些PDP-11的浮点数硬件表示形式中有一个运算模式位(mode bit),你可以只进行float运算,也能只进行double运算。若要两种方式切换,就必须修改这个位来改变运算模式。但是吧,在早期的UNIX里用float不多,所以把运算模式固定位double就比较方便,因此float被自动扩展为double。
  6. 不允许嵌套函数(函数内部包含另一个函数的定义)。——因为简化了编译器,稍微提高了C程序运行时组织结构。
  7. register关键字——这个关键字给编译器设计者提供线索:程序中哪些变量属于热门(常常被使用),这样就可以把它们放到寄存器中。(这个设计是失误,简化编译器,包袱给程序员)因为如果让编译器在使用各个变量时自动处理寄存器的分配工作,显然比一经声明就把这类变量在生命期内始终保留在寄存器里好。

百度百科:ANSI C、PDP-11
  • ANSI C是美国国家标准协会(ANSI)对C语言发布的标准。使用C的软件开发者被鼓励遵循ANSI C文档的要求,因为它鼓励使用跨平台的代码。

  • PDP-11为美商迪吉多电脑(Digital Equipment Corp.)於1970到1980年代,所销售的一系列16位元 迷你电脑 。PDP-11是迪吉多电脑的PDP-8系列的后续机种。

    PDP-11有着许多创新的特色,而且比起其前代机种更容易撰写程式。当32位元的后续扩充机型VAX-11推出时,PDP-11已经广受程式设计师的喜爱。这两个机型后续的市场,则多由IBM PC、苹果二号与升阳电脑的工作站电脑等个人电脑所取代。

1.3 标准I/O库和C预处理器

C语言中,功能在运行时处理,可以出现在应用程序代码中,有可以出现在运行时库函数(runtime library)。在一些其他语言中,编译器回植入一些代码,隐式调用运行时候需要的工具,但C中,绝大数多数库函数或辅助程序都需要显示调用

C原来没有定义I/O,是在1972年左右出现,由库函数提供。后来对其进行优化,成了今天的标准I/O函数库。

C预处理器的三个主要功能:

  1. 字符串替换:通常用于为常量提供一个符号名。eg: #define name stuff
  2. 头文件包含:“.h ”作为头文件拓展名。一般性的声明可以被分离到头文件中,可以被许多源文件使用。
  3. 通过代码模板的扩展:宏(marco)在连续几个调用中所接收的参数的类型可以不同(宏的实际参数只是按照原样输出)

宏定义最好只用于命名常量(如上1),宏名应该大写,便于区分函数调用。

1.4 K&RC

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FHEk9FIa-1606129775444)(C:\Users\PY\AppData\Roaming\Typora\typora-user-images\image-20201121131455523.png)]

1978年,C语言经典名著 The C Programming language 出版,作者是Brian Kernighan和Dennis Ritchie,所以这个版本的C语言被称为“K&R C”

1.5 今日之ANSI C

1983年,美国国家标准化组织(ANSI)成立了C语言工作小组,开始C语言的标准化工作。Mirosoft为IBM PC制作了一个C编译器,引入了几个关键字(far,near等)帮助指针处理80x86芯片不规则的架构。而最终ANSI 还是没有接受near和far关键字。

该用哪个版本的C语言?——ANSI C

ANSI所采纳的C语言标准是ISO C,我们日常所说的标准也应该是ISO C。

C语言标准的官方名称是:ISO/IEC 9899:1990. ISO/IEC是指国际标准化组织和国际电工组织

1.6 它很棒,但它符合标准吗?

不可移植的代码:

  1. 由编译器定义的——由编译器设计师决定采取何种行动(在不同的编译器中说采取的行为可能并不相同,但是她们都是正确的)
  2. 未确定的——在某些正确情况下的做法,标准未明确规定应该怎样做

坏代码:

  1. 未定义的——在某些不正确情况下的做法,但是标准并未规定应该怎样做。
  2. 约束条件——这是一个必须遵守的限制或要求,若不遵守,程序的行为就会变成未定义的。标准规定编译器只有在违反语法规则和约束条件的情况下才产生错误信息,这意味着所有不属于约束条件的语义规则都可以不遵循,而且由于这种行为属于未定义,所以编译器可以采取任何行动。
    • 不属于约束条件规范的例子:所有在C语言标准头文件中声明的标识符均保留,所以不能声明一个叫做malloc()的函数,因为在标准头文件里已经有一个函数以此为名,但是由于这不是约束条件,所以可以违反它,而且编译器不会给你警告。

可移植代码:(严格遵循标准的)

  • 只使用已确定的特性
  • 不突破任何由编译器实现的限制
  • 不产生任何依赖由编译器定义的或未确定的或未定义的特性的输出

事实上,在所有遵循标准的程序中,属于这一类的程序不多。下面这个程序就不是严格遵循标准的:

#include<stdio.h>
#include<limits.h>

int main() {
	(void)printf("biggest int is %d", INT_MAX);   //print返回值为输出成功的变量数量
	return 0;
}

因为其输出结果是由编译器定义的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xRC5G52r-1606129775447)(C:\Users\PY\AppData\Roaming\Typora\typora-user-images\image-20201121170246192.png)]

1.7 编译限制

每一个ANSI C编译器必须能够支持:

  • 在函数定义的形参、实参数量的上限都至少可以达到31个
  • 一条源代码行里至少可以有509个字符
  • 在表达式中至少可以支持32层嵌套的括号
  • long int的最大值不得小于2147483647(long型整数不得低于32位)

以上并不是约束条件,所以当编译器发现违反上述规定的情况时并不一定产生错误信息。

1.8 ANSI C标准的结构

K&R C和ANSI C的区别
  • 原型——把形参的类型作为函数声明的一部分。eg: void fun(int a);ASNI最重要的新特征。因为原型使得编译器很容易根据函数的定义检查函数的用法,这就允许编译器在参数的使用和声明之间检查一致性。把“原型”称作是“带有所有参数的函数名”是不够充分的,它应该被称为“函数签名”。

  • 增加一些关键字——enum、const、volatile、signed、void,同时删除entry。

    ​ 补充:1. enum

    枚举型是一个集合,第一个枚举成员的默认值为0(如果第一个成员设置了值,那以设置值为准),后续枚举成员的值在前一个+1。也可以认为的设定枚举成员的值,从而自定义某个范围内的整数。枚举型是预处理指令#define的代替。

    enum DAY{                            //DAY是一个标识符
        MON=-2,TUE,WED,THU,FRI,SAT,SUN	 //集合中的元素(枚举成员)是一些命名的整型常量,元素之                                      //间用逗号隔开。
    };                                   //类型定义以分号结束
    

    3.volatile

    修饰被不同线程访问和修改的变量;作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。该变量是说它可能会被意想不到的改变,这样,编译器就不会去假设这个变量的值了。

    (一个挺奇妙的东西,有待研究)

    4.signed

    unsigned的作用就是将数字类型无符号化, 例如 int 型的范围:-2^31 ~ 2^31 - 1,而unsigned int的范围:0 ~ 2^32。看起来unsigned 是个不错的类型,尤其是用在自增或者没有负数的情况。但是在实际使用中会出现一些意外的情况。

    signed在默认情况下声明的整型变量都是有符号的类型(char有点特别),如果需声明无符号类型的话就需要在类型前加上unsigned。无符号版本和有符号版本的区别就是无符号类型能保存2倍于有符号类型的正整数数据

1.9 阅读ANSI C标准,寻找乐趣和裨益

书中说以下程序报错:原因是参数传递过程类似赋值。要求操作数都是指向相容类型,且左边限定符要包含右边的限定符

#include <stdio.h>
foo(const char** p) {

}

int main(int argc,char** argv)
{
	foo(argv);
	return 0;
}

而在visual stdio2019中:没有报错还能运行!————编译器优化了呀~

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8ZbXgoml-1606129775450)(E:\干啥啥不行写笔记第一名\C编程专家.assets\image-20201123181430511.png)]

char *cp;
const char* ccp;
ccp = cp;
const和void学习:

const不能把变量变成常量,在一个符号前面加上const限定符只能说明其不能被赋值,只是可读的。const最多的用处在于它用来限定函数的形参,这样该函数将不会改变实参指针所指的数据。

void指针:无类型指针、空类型指针。可以指向任何类型的数据,任何类型指针可以直接赋值给void而不用强制类型转换。

void* p1;
int* p2;
p2 = p1;

反过来,void指针赋给其他指针就要强制类型转换,因为“有类型”不能包容“空类型”

编译器不知道void指针所指的对象大小,所以对其进行算术是不合法的。但是在GNU中,指定void的算法和char一致,因此可以

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kaKi4sq0-1606129775453)(E:\干啥啥不行写笔记第一名\C编程专家.assets\image-20201123182619083.png)]

算法操作的指针必须知道其指向数据类型的大小,即是要求知道内存中的目的地址。

const char*p="abcd";

一个char指针p指向的对象是常量。不允许修改值,但p是普通的指针,是可以动的。eg:p=“efg”;

char *const p="abcd";

一个char类型的常量指针p指向一个char类型的变量,指针不变,数据可以变。比如:p[3]=‘z’;就把d改成了z

const char* const p="1234"

一个char类型的常量指针p指向一个char类型的常变量。指针和数据都不变化

我的总结:const离右边的谁近,谁就是常变量不可修改。

1.10”安静的改变“究竟有多少安静

整型升级:

char,short int或int,包括它们的有符号或无符号变型,枚举都可以使用在需要int或unsigned int的表达式中,如果int可以表示完,使用int,否则使用unsigned int。

寻常算术转换

算数类型的双目运算会引发转换,以类似的方式产生结果类型。目的是产生一个普通类型,同时也是运算结果的类型。

​ 假设操作op

  • (long double) op (xxx), xxx—>long double //一个操作数类型为long double,运算后另一个操作数也会被转换为long double
  • (double)op(xxx),xxx—>double
  • (float)op(xxx),xxx—>float
  • (unsigned long int)op(xxx),xxx—>unsigned long int
  • (long int) op (unsigned int),如果long int能表示完unsigned int的所有值,unsigned int—>long int。否则两个操作数都转成unsigned long int (总结:想嘛,要节约内存啊,看范围,能用小范围肯定用小范围,用不了就扩大呗)
  • 数据类型一般朝着浮点精度更高,长度更长的方向转换
  • 整型如果转换成signed不会丢信息,就转signed,否则转unsigned。

这有个bug得注意:

在这里插入图片描述

又逻辑可以知道total为7,但是sizeof()的范围值类型是无符号数,将d的类型变成了unsigned int。而负数以补码的形式存储在计算机,-1的原码:1000 0000 0000 0001,反码:1111 1111 1111 1110,补码:1111 1111 1111 1111是一个非常大的数,因此转换成unsigned int时值为1111 1111 1111 1111,导致结果错误。

解决方案:使用强制类型转换。由下图,对total进行强制类型转换成int后,输出结果正确。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HwqiiaA7-1606129775455)(E:\干啥啥不行写笔记第一名\C编程专家.assets\image-20201123185946252.png)]

因此一个程序中最好要么都是有符号数,要么都是无符号数,以避免混合使用导致程序错误。

有理解不对,或是搞错的地方,望大佬指出呀!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值