数据类型与变量&论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> 才能用:
#include <stdbool.h>
_Bool isAlive = true;
if (isAlive)
printf("still running\n");
往前倒,C89/C90 那会儿甚至没有 _Bool 这个类型,全靠用整数 0 表示假、非零表示真。C99 才引入 _Bool,<stdbool.h> 里通过宏把 bool、true、false 定义成 _Bool、1、0。一直到 C23,bool/true/false 才终于变成语言自带的关键字,不用再依赖那个头文件。
这么看,一个"布尔类型"背后其实是好几十年的标准演变,Python 里习以为常的东西,在 C 里是慢慢补出来的。
float 和 double:都是小数,但精度不同
浮点型分两种,float 是单精度,double 是双精度,后者的精度更高。C 语言里默认把 3.14 这样的小数字面量当作 double 类型,如果想显式声明为 float,得写成 3.14f:具体怎么理解的,嗯如果你高中学过物理的力学实验,大概离就是有效位数的意思
float:约 6~7 位有效数字
double:约 15~16 位有效数字
float a = 3.14f; // 单精度
double b = 3.14; // 双精度,默认情况
整型的"大小分级":类型修饰符
int 之外还有 short、long、long long,它们的区别是能表示的数值范围不同。我当时记的比喻是:小鞋盒装不下大象——数据范围超出某个类型能表示的极限,就得换更"大"的类型来装。
这套机制叫类型修饰符:short、long 是和内存长度相关的修饰符,专门用来修饰整型(long double 是例外,C99 里引入,用来修饰浮点型)。
如果硬要用小类型去装超出范围的大数字,会发生"溢出",数值会直接绕回去变成一个莫名其妙的值,而不是报错提醒你:
int max = 2147483647; // int 能装的最大值
max = max + 1; // 溢出,结果变成 -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 语言标准只规定了一个相对关系:
// 整型家族
1 == sizeof(char) < sizeof(short) < sizeof(int) <= sizeof(long) <= sizeof(long long)
// 浮点型家族
sizeof(float) < sizeof(double) <= sizeof(long double)
想知道某个类型在当前平台到底占几个字节,可以用 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_from=333.337.search-card.all.click
1他是类型,使用上可以把他和float int 这些数据类型并列
int age = 18;
float score = 95.5f;
size_t length = 100;
2
2怎么理解“本质是一个无符号整型的别名”可以从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 int,signed 这个关键字写不写都行。但 char 例外——char 到底是 signed char 还是 unsigned char,是由编译器的具体实现决定的,不像 int 那样有统一的默认值,所以如果代码依赖符号性,最好显式写清楚 signed char 或 unsigned char,不能像 int 那样省略。
取舍标准很直接:只可能是正数的数据(比如年龄、长度)用 unsigned;有正有负的数据(比如温度、财务盈亏)用 signed。
unsigned int age = 18; // 年龄不可能是负数
int temperature = -5; // 温度可以是负的
char 在 C 里是"整型",这点和 Python 不一样
这是我笔记里印象最深的一条。Python 里没有单独的字符类型,一个字符本质上就是长度为 1 的字符串。但 C 语言的 char 属于整型家族——每个字符本质上对应一个数值(ASCII 码),只是通常按字符的样子来解读和显示:
char grade = 'A';
printf("%c\n", grade); // 按字符打印:A
printf("%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 个字符。
变量和常量:能不能改,是唯一的区别
变量和常量都是用来存数据的"容器",核心区别就一条:变量的值可以改,常量一旦确定就不能改。
const int daysInWeek = 7;
daysInWeek = 8; // 编译报错: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——两个整型相除,只要有一方不是浮点型,就会做整数除法,直接把小数部分砍掉,不会四舍五入:
int a = 7;
int b = 2;
printf("%d\n", a / b); // 3,不是 3.5
printf("%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 语言没有原生字符串类型。这些"为什么"比"是什么"更值得记下来。
下一步是把这些类型放到实际的变量声明和运算里,看看它们在代码里到底怎么配合着用。
更多推荐



所有评论(0)