数据类型与变量论Python学习者学习c语言的现状
数据类型这件事比想象中琐碎这是 C 语言课的第二讲讲数据类型、变量和常量。听课的时候记了几条笔记零散、跳跃很多地方是懂了但没写全而且学习Python的时候这些东西有很多都是我从来不知道的或者完全不一样的。这篇算是把它们捡回来按自己的理解重新捋一遍。类型是用来描述数据的生活里的数据五花八门商品价格、好评比例、购物小票上的货号、单价、数量……要让程序处理这些数据就得先告诉编译器这是个什么样的东西——整数、小数还是字符。这就是类型存在的意义相似的数据有共同的特征编译器只有知道了类型才知道该怎么操作它。这个是官方的叫法吧Python跟这个也差不多只有char这个字符类型在c里面算是int整型的。C 语言的内置类型不多字符型char、整型int、浮点型float/double还有布尔型_Bool。bool 类型C 和 Python 不一样这是我笔记里第一条记下来的东西因为踩了预期上的坑。Python 里True、False直接就是关键字拿来就能用。C 语言不是——true和false是小写的而且早期版本里根本没有专门的布尔类型得靠引入头文件stdbool.h才能用#includestdbool.h_BoolisAlivetrue;if(isAlive)printf(still running\n);往前倒C89/C90 那会儿甚至没有_Bool这个类型全靠用整数 0 表示假、非零表示真。C99 才引入_Boolstdbool.h里通过宏把bool、true、false定义成_Bool、1、0。一直到 C23bool/true/false才终于变成语言自带的关键字不用再依赖那个头文件。这么看一个布尔类型背后其实是好几十年的标准演变Python 里习以为常的东西在 C 里是慢慢补出来的。float 和 double都是小数但精度不同浮点型分两种float是单精度double是双精度后者的精度更高。C 语言里默认把3.14这样的小数字面量当作double类型如果想显式声明为float得写成3.14f具体怎么理解的嗯如果你高中学过物理的力学实验大概离就是有效位数的意思float约 67 位有效数字double约 1516 位有效数字floata3.14f;// 单精度doubleb3.14;// 双精度默认情况整型的大小分级类型修饰符int之外还有short、long、long long它们的区别是能表示的数值范围不同。我当时记的比喻是小鞋盒装不下大象——数据范围超出某个类型能表示的极限就得换更大的类型来装。这套机制叫类型修饰符short、long是和内存长度相关的修饰符专门用来修饰整型long double是例外C99 里引入用来修饰浮点型。如果硬要用小类型去装超出范围的大数字会发生溢出数值会直接绕回去变成一个莫名其妙的值而不是报错提醒你intmax2147483647;// int 能装的最大值maxmax1;// 溢出结果变成 -2147483648而不是报错printf(%d\n,max);顺带解决了一个我当时没想明白的问题为什么int是 4 个字节因为常见的 CPU 是 32 位架构32 位 4 字节正好填满一个寄存器这是硬件层面的整数int就对应了这个自然长度。不过long具体是几个字节其实并不固定——这和平台采用的数据模型有关操作系统、CPU 架构、ABI 共同决定的一套位宽约定。同样是 64 位系统long在 Linux gcc 下是 8 字节在 Windows 的 MSVC 下却只有 4 字节long double在不同编译器下差异更大可能是 8、10、16 字节。所以 C 语言标准只规定了一个相对关系// 整型家族1sizeof(char)sizeof(short)sizeof(int)sizeof(long)sizeof(longlong)// 浮点型家族sizeof(float)sizeof(double)sizeof(longdouble)想知道某个类型在当前平台到底占几个字节可以用sizeof()这个操作符去算单位是字节printf(int: %zu\n,sizeof(int));这里有个细节sizeof的返回值类型是size_t——C 语言里专门用来表示内存大小或对象计数的类型本质是一个无符号整型的别名具体对应unsigned int还是unsigned long由系统决定。打印size_t类型的值要用%zu占位符而不是%d。顺带一提这个size_t刚开始学到这里我根本搞不明白是什么东西但是课上我知道在算内存大小或者字符串长度的时候会用到字符串的定义c和Python也有很多不一样比如Python是吧字符串当对象看而c就是一个一个往后找难怪说Python简单呢要想理解这个size_t是个什么东西可以从一些线索中理解参考来源https://www.bilibili.com/video/BV1PhhYzFEJ4/?spm_id_from333.337.search-card.all.click1他是类型使用上可以把他和float int 这些数据类型并列intage18;floatscore95.5f;size_tlength100;22怎么理解“本质是一个无符号整型的别名”可以从size_t这个名字本身出发尺寸一般就是没有负数的因此创造出来就是为了记录长度、数组大小、内存字节数等的用途的我反思我也想了为什么我会对这个犯难可能因为Python 没有专门对应的 size_t通常直接使用 int。但 Python 的 int 能表示负数也能自动扩展C 的 size_t 是固定范围的无符号整数。signed 和 unsigned要不要带符号介绍之前我想追问为什么会有sighed和unsigned这个东西出来经查阅才知道int通常只有32位而size_t有64位这个和c语言本身的特性密不可分c语言紧密连接硬件c语言的int大小的固定的溢出就不妙了而Python就比较智能可以自己填充但同时也更慢了int默认是带符号的也就是说int其实等价于signed intsigned这个关键字写不写都行。但char例外——char到底是signed char还是unsigned char是由编译器的具体实现决定的不像int那样有统一的默认值所以如果代码依赖符号性最好显式写清楚signed char或unsigned char不能像int那样省略。取舍标准很直接只可能是正数的数据比如年龄、长度用unsigned有正有负的数据比如温度、财务盈亏用signed。unsignedintage18;// 年龄不可能是负数inttemperature-5;// 温度可以是负的char 在 C 里是整型这点和 Python 不一样这是我笔记里印象最深的一条。Python 里没有单独的字符类型一个字符本质上就是长度为 1 的字符串。但 C 语言的char属于整型家族——每个字符本质上对应一个数值ASCII 码只是通常按字符的样子来解读和显示chargradeA;printf(%c\n,grade);// 按字符打印Aprintf(%d\n,grade);// 按数字打印65这才是它在内存里真实的样子也正因为这样字符字面量和字符串字面量在 C 里是两种完全不同的东西A单引号是一个字符占1 个字节A双引号是一个字符串占2 个字节——多出来的那 1 个字节是字符串的结束标志\0这一点很容易和 Python 的直觉搞混但恰恰是 C 语言里字符串到底是什么的根子——C 里没有原生的字符串类型字符串就是一串字符加一个结尾标记这也是后面学strlen、数组这些内容时绕不开的前提。转义序列那些打不出来的字符ASCII 码值从 0~31 的字符是不可打印的比如换行、退格没法直接用单引号括起来表示所以 C 语言用转义序列来处理字符转义序列比如\n换行、\t制表符、\\表示反斜杠本身数字转义序列比如\ddd八进制、\xdd十六进制可以表示 ASCII 字符集里的任意字符用strlen计算字符串长度时一个转义字符哪怕写了两三个字符比如\t在实际存储里也只算 1 个字符。变量和常量能不能改是唯一的区别变量和常量都是用来存数据的容器核心区别就一条变量的值可以改常量一旦确定就不能改。constintdaysInWeek7;daysInWeek8;// 编译报错const 变量不允许再赋值变量创建的语法是数据类型 变量名命名要见名知意只能用字母、数字、下划线不能数字开头区分大小写不能是关键字。变量还分全局变量和局部变量全局变量在函数外部定义整个文件都能访问生命周期从程序启动到结束存在内存的静态区局部变量在函数或代码块内部定义只在这个范围内有效退出这个范围就被销毁存在内存的栈区。如果没有显式初始化局部变量的值是不确定的垃圾值所以最好养成初始化的习惯。常量有几种形式字面常量比如123、a、3.14、用#define定义的符号常量、用const修饰的常变量本质还是变量只是编译器不允许再赋值以后还会学到枚举常量。从 Python 过来还会踩的几个坑除了 bool 和 char 这两个已经写在前面的点学这一讲的时候我发现自己脑子里还有不少 Python 的思维惯性需要一条条掰过来。整理了几条我自己遇到或者能想到的变量要先声明类型而且类型定死了不能变。Python 里x 1之后可以直接x hello变量本身不在乎装的是什么。C 语言不行int x;声明了之后x这一辈子只能装整数编译器在编译阶段就把这件事定死了。这也是为什么 C 语言需要数据类型这么大一块内容——Python 里几乎不用操心的事在 C 里是绕不开的第一步。整数除以整数结果还是整数。Python 3 里7 / 2得到3.5很自然。但 C 语言里7 / 2的结果是3——两个整型相除只要有一方不是浮点型就会做整数除法直接把小数部分砍掉不会四舍五入inta7;intb2;printf(%d\n,a/b);// 3不是 3.5printf(%f\n,a/(double)b);// 3.5要让至少一方是浮点型没有专门的字符串类型字符串是字符数组 结束符。Python 里字符串是一等公民直接能用、切片、len()。C 语言里字符串本质是一串char末尾跟着一个\0作为结束标志这也是前面A占 1 字节、A占 2 字节的根源。字符串操作要么用数组的方式手动处理要么调用strlen、strcpy这类库函数没有 Python 那种字符串自带方法的便利。print 不能什么都往里塞得对上占位符。Python 的print()什么类型都能接。C 语言的printf得手动写占位符而且类型要对得上——%d对应int%f对应浮点数%zu对应size_t写错占位符不会报错但可能打印出乱码或者错误的值这个坑很隐蔽编译器不一定会提醒。代码块靠花括号{}不是靠缩进。Python 用缩进划定代码块缩进错了直接报语法错误。C 语言用{}缩进只是给人看的编译器根本不关心——这意味着代码格式乱一点也能跑但也意味着可能因为少写一个花括号逻辑全乱了却毫无提示。每一行代码结尾要写分号。这条几乎是老生常谈但从 Python 切过来的人真的会漏写。分号在 C 语言里是语句结束的标志不是可选的格式习惯。代码运行前要先编译。Python 是解释执行写完直接跑。C 语言得先编译成可执行文件才能运行编译这一步会先把类型、语法这些检查一遍——很多 Python 里跑起来才发现的错误在 C 里编译阶段就会被拦下来这既是麻烦某种程度上也是安全网。这些坑大多指向同一件事Python 帮你把很多细节藏起来了C 语言把这些细节全部摊开在你面前逼着你自己去弄清楚数据到底是怎么被存储、怎么被解释的。这大概也是为什么很多人说 C 语言更贴近硬件——它没有太多语法糖替你兜底。记在最后这一讲信息量比想象中大——原本以为数据类型就是背几个关键字实际上每一条背后都有原因为什么int是 4 字节是因为 CPU 架构为什么long的长度不固定是因为数据模型的差异为什么字符和字符串占的字节数不一样是因为 C 语言没有原生字符串类型。这些为什么比是什么更值得记下来。下一步是把这些类型放到实际的变量声明和运算里看看它们在代码里到底怎么配合着用。