ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C语言数据类型深度解析:从内存存储到底层逻辑

C语言数据类型深度解析:从内存存储到底层逻辑 1. C语言概述为什么今天的开发者仍然绕不开这门四十多年历史的老伙计想学C语言或者刚刚开始学C语言的朋友十有八九都听过一句话“C语言是编程语言的基石。”这句话听了很多遍但真正理解的人并不多。2024年的今天编程语言排行榜上Java、Python、JavaScript轮流坐庄C语言却一直稳定在榜单前几名靠的不是情怀而是实打实的底层地位。我第一次接触C语言是大学一年级那时候完全搞不懂为什么指针要用星号、为什么数组下标从0开始、为什么写个“Hello World”要那么多行代码。直到工作以后做了嵌入式开发和底层系统优化回头再看C语言才明白当年学的每一个知识点都不是孤立的——C语言教给我们的不是某种语法而是计算机运行程序的底层逻辑。这篇内容定在《C语言-02》这个位置意味着读者大概率已经装好了开发环境甚至可能已经敲出了人生的第一行C代码。这篇文章围绕两个核心词展开C语言概述和数据类型。概述部分解决“C语言到底是怎么回事”的问题数据类型部分解决“程序里各种数据是怎么在内存中存放和表示”的问题。这两个问题搞清楚了后面学运算符、流程控制、函数、指针都会顺手很多。先说清楚这篇文章适合谁来读零基础刚入门、被学校教材绕晕了的学习者或者学过Python/Java但想往底层走、想学单片机和嵌入式方向的开发者。如果你已经写了多年C语言基础的字节数和取值范围早就滚瓜烂熟了那这篇文章你可以重点看第三部分的存储逻辑和第五部分类型转换的坑这两块即使是老手也容易忽略细节。C语言的诞生背景我简单讲一下不做太多背书但这段历史能帮你理解C语言为什么是今天这个样子。1972年贝尔实验室的丹尼斯·里奇为了重写Unix系统在B语言的基础上设计了C语言。那个年代内存资源极其宝贵编译器能做的事情很有限所以C语言被刻意设计成一种“接近硬件”的语言没有垃圾回收、没有对象、没有异常处理所有内存管理交给程序员自己。这种设计在现在看来不够“友好”但它换来的是极致的执行效率和直接操作硬件的能力。今天的操作系统内核Linux、Windows、嵌入式系统、数据库引擎、编译器、甚至是Java虚拟机和Python解释器底层都是用C语言写的。学C语言学的不只是一门编程语言而是在学习“程序在计算机里到底是怎么跑的”。这句话是我认为C语言最大的价值也是后文所有内容的总纲。接下来我先把C语言程序的基本结构拆开讲清楚这是数据类型知识落地的载体。2. 第一个C语言程序的完整解剖从main函数到printf的输出链路绝大多数C语言教材的第一课都是“Hello World”很多自学的人抄完代码跑起来也就不再管了。这其实浪费了一个绝佳的理解机会——一个最简单的C程序其实包含了C语言运行链路的全部核心组件。我用标注的方式把每一行都拆开你对照自己机器上跑通的代码来看保证会有新的认识。#include stdio.h int main(void) { printf(Hello World!\n); return 0; }这是最经典的版本我们逐行来看。第一行#include stdio.h这一行叫预处理指令。stdio.h是C语言标准库中的头文件里面声明了标准输入输出函数的原型printf就在里面。#include的作用可以理解为在编译之前把stdio.h这个文件的内容原封不动地复制到当前文件里。所以如果你删掉这行代码直接用printf编译器会报一个“隐式声明”的警告——现代编译器干脆直接报错。这里有个初学者最容易困惑的点#include后面到底加不加.h答案是标准库头文件要加你自己写的头文件也要加编译器是靠这个后缀来识别头文件的。第二行int main(void)是主函数声明。main是C语言程序的入口点程序从main函数的第一条语句开始执行。int表示这个函数的返回值类型是整型void表示这个函数不接受任何参数。早期教材里可能写void main()这在很多编译器下也能通过但严格来说是不符合C标准的写法正确写法就是用int main(void)或int main(int argc, char *argv[])后者用于接收命令行参数后面讲到字符指针数组时再展开。关于 main 函数的返回值return 0;是告诉操作系统“程序正常结束”。非零值一般表示异常结束比如return 1;可能表示某个错误导致程序提前退出。这个约定在写脚本程序、写自动化测试时非常有用因为你可以用条件判断程序退出的状态。第三行到第六行是main函数的函数体被一对花括号{}包裹。C语言用花括号来表示块结构这一对花括号标示了main函数的开始和结束。进入函数体后printf(Hello World!\n);执行了实际输出操作。printf是一个库函数它的作用是把格式化的文本输出到标准输出默认是屏幕。双引号里的内容叫字符串字面量\n是转义序列表示换行符——注意这是在C语言里第一次接触到“类型”的地方双引号包裹的内容是字符串类型\n虽然看起来是两个字符但在编译后只代表一个ASCII值为10的字符。关于字符串和字符的区别第三章数据类型会专门讲。花括号后的return 0;表示main函数的执行结果是0程序结束。我再补充一个很多初学者不会注意、但非常关键的细节C程序从源码到可执行文件的完整过程。你写完的.c文件其实只是文本计算机并不能直接执行。它的转换过程分四步预处理处理#include、#define等预处理指令把头文件内容插入进来。编译把预处理后的代码翻译成汇编语言。汇编把汇编代码翻译成机器指令生成目标文件.o或.obj。链接把目标文件与需要的库文件比如stdio.h对应的libc库链接在一起生成可执行文件。在Linux下用gcc hello.c -o hello其实一条命令就完成了这四步流程。理解这四个步骤对后面理解为什么C语言程序一般比其他高级语言更快有很大帮助C语言的编译产物是直接针对特定CPU的机器指令而Python或Java在运行时需要额外的解释器或虚拟机来解释执行。这也是C语言适合写系统级程序的原因之一——它在编译阶段就把大部分工作做完了。拆完程序结构接下来进入这篇文章的正题数据类型。数据类型是C语言的基石也是程序内存布局的基础。3. 基本数据类型全景char、int、float、double在内存中的真实存储逻辑C语言的数据类型体系可以分成两大类基本类型和派生类型。基本类型包括字符型char、整型int、浮点型float、双精度浮点型double、空类型void。派生类型则是在基本类型之上通过指针、数组、结构体、联合体等方式组合出来的。这篇文章只聚焦基本类型派生类型里的指针和数组在后面的文章里专门展开因为那需要单独篇幅讲才说得透。3.1 字符型一个“整数”伪装成字符的典型案例char类型初学者最大的误区是以为它是“专门用来存字符的”。准确地说char存的是整数只是这个整数被约定为ASCII码值。字符A在内存中存的其实是数字65字符0存的是数字48换行符\n是10。在标准C语言中char占用1个字节8位比特可以表示的整数值范围取决于实现是有符号还是无符号——标准没有强制规定通常由编译器决定。在大多数现代编译器GCC、Clang上char是有符号的范围是 -128 到 127也就是 -2^7 到 2^7-1。那么问题来了字符a存的是97这个97在内存里是怎么放的二进制。97的二进制是01100001这8个比特就对应一个字节。你看字符和整数的存储本质上没有任何区别区别只在于你用%c还是%d去解释它。写代码的时候被这个坑过的同学应该不少char ch A; printf(%c\n, ch); // 输出 A printf(%d\n, ch); // 输出 65同样一个变量ch用%c格式符时编译器把它解释成一个字符用%d解释它时就把它当作一个整数。这也从侧面说明了一个重要事实在C语言里数据和数据类型是绑定的但绑定关系是由程序员通过语法来维护的编译器只是忠实地按照你写的格式符来解读内存中的二进制数据。这一点和各语言的“强类型”机制不同C语言实际上是一种“静态但弱类型”的语言——类型检查发生在编译期但运行期基本不检查类型是否匹配。3.2 整型家族short、int、long、long long 的范围与选择整型integer是C语言里最庞大、也最重要的类型家族。先说标准规定short短整型至少2字节int整型至少2字节大多数现代系统上是4字节long长整型至少4字节在64位Linux上通常是8字节long long更长的整型至少8字节这里的“至少”是一种保守表述——C语言标准只保证最小范围。以我们最常用的 x86/x86-64 平台上的 GCC 编译器为例实际情况下类型字节数范围有符号short2-32,768 ~ 32,767int4-2,147,483,648 ~ 2,147,483,647long864位系统/ 432位系统-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807long long8与64位long范围相同我一直建议初学者记住一个公式有符号n位整数的范围是 -2^(n-1) 到 2^(n-1)-1。16位int能存到3276732位int最大是2147483647如果你尝试存一个超出范围的值比如int x 2147483648;会发生什么它并不会报错而是会发生整数溢出得到一个负数——这是初学阶段最隐蔽的坑之一我后面会单独用一节讲。为什么要分这么多种整型主要原因是内存。嵌入式系统或单片机场景下内存非常紧张如果你只需要存0到100的数用int占4字节就浪费了用unsigned char占1字节就够了。这也是为什么我在阅读招聘要求时经常看到嵌入式岗位要求“熟悉C语言的数据类型和内存布局”——因为一个变量的类型选择直接决定了程序的内存占用和运行效率。3.3 浮点数为什么0.1在计算机里永远不精确float单精度浮点型4字节和double双精度浮点型8字节是C语言里用于表示小数的类型。几乎所有初学者对浮点数的理解都是错的会用“带小数的数”来概括这不够。由于篇幅关系这里我直接给出精确实用的结论计算机使用二进制来存储小数但很多十进制小数不能用二进制精确表示比如0.1在二进制中是无限循环小数所以存储时会做舍入。这就是为什么你算0.1 0.2会得到0.30000000000000004而不是漂亮的0.3。float的有效数字大约是6到7位十进制位double大约是15到16位。当你需要精度较高的计算时直接用double别用float。浮点数的存储遵循IEEE 754标准分三部分符号位、指数位、尾数位。float的分布是1位符号8位指数23位尾数double是1位符号11位指数52位尾数。用代码直观感受一下浮点数的精度问题#include stdio.h int main(void) { float f 0.1f; double d 0.1; printf(float: %.10f\n, f); printf(double: %.10f\n, d); return 0; }输出结果是惊人的float: 0.1000000015 double: 0.1000000000表面看double更精确但实际上double在内部也存在舍入只是有效位数更多显示到10位时看不出来而已。再在底层0.1和0.2永远无法精确表达成二进制。所以在C语言中判断两个浮点数是否相等永远不要直接写if (f 0.1)而是应该计算两个数的差的绝对值是否小于一个很小的阈值比如1e-6。void类型只在两个场景出现一是函数没有返回值void func()二是指向任意类型数据的指针void *p。前者在嵌入式开发中非常常见后者是泛型编程的基础后面学指针时再深入。4. 从“存什么”到“怎么存”变量、常量和类型修饰符的实战视角理解了基本数据类型接下来要解决的问题是这些类型怎么在程序中变成实际可用的变量和常量。这章偏实战很多细节是教材上容易一带而过但在写代码时一定会遇到的。4.1 变量声明内存分配的真正触发点变量声明的基本语法是类型名 变量名。例如int age; double salary; char grade;这行代码在运行时做了什么它告诉编译器请在内存中划分出一个sizeof(int)大小的空间一般是4字节以后age这个名字就指代这块内存。这在本质上就是内存分配的过程。分配内存之后这块空间里的值是未知的可能是上次程序留下的残留数据所以你在读这个变量之前必须先给它赋值——这个原则叫**“使用前初始化”**是C语言编程中最重要的安全习惯。初学者经常困惑“为什么别人的代码里变量不初始化也没事”原因很简单那个程序恰好运行在一块干净的或可预测的内存区域比如刚启动时内存恰好是0但这完全是运气不是你代码的正确性。我在实际调试中遇到过的未初始化变量问题至少占初学者bug的三成表现形式还特别诡异——同一个程序在A机器上跑结果是正确的在B机器上结果是错的。原因往往就是未初始化变量读到了不同的残留值。所以记住一条铁律变量声明时顺手初始化成本极低收益极高。变量命名方面C语言的规则是只能由字母、数字、下划线组成不能以数字开头不能是关键字int、void、main等。我建议初学者从一开始就养成命名好习惯变量名要有意义用student_count而不是sc用temperature而不是tmp。C语言没有强制的命名规范但社区通行做法是变量用小写加下划线宏定义全大写。养成好习惯以后进入企业环境会很省事。4.2 常量字面量、const与#define的选择常量在C语言里有三种主要形态第一种是字面量常量就是你直接写在代码里的数值或字符。42是int型字面量3.14是double型字面量a是char型字面量hello是字符串字面量。字面量不能赋新值这是字面量的属性决定的你不可能写42 3;。第二种是const修饰的变量const int MAX_SIZE 100;const告诉编译器这个变量一旦初始化就不能再修改。如果代码中试图给MAX_SIZE重新赋值编译器会直接报错。需要注意const变量本质上仍然是变量它有内存地址可以被指针指向只是不能通过它修改这在后面学指针时会涉及。第三种是#define宏定义#define MAX_SIZE 100#define是预处理指令它在编译前的预处理阶段进行文本替换把代码中所有MAX_SIZE原样替换成100。#define定义的是“字符层面的替换”它没有类型不占内存纯粹是源文件层面的修改。初学阶段到底用const还是#define我的建议是能用const用const。原因有三第一const有类型检查编译器能帮你捕捉类型不匹配的错误第二#define在替换多个表达式时容易出错比如#define SQUARE(x) x*x对SQUARE(ab)求值时结果是ab*ab而不是(ab)*(ab)第三const在调试器中有符号信息可以直接查看变量值而#define在调试器里只能看到预处理器替换后的数值。只有在需要定义数组大小C89标准不支持用const定义数组大小C99之后才支持变长数组、或者需要条件编译时才必须用#define。4.3 signed、unsigned 与 sizeof两个容易被忽略的细节signed和unsigned用于修饰整型分别表示有符号和无符号。unsigned int把原本用于表示符号的1个比特也用来表示数值所以正数范围翻倍int最大是2147483647unsigned int最大是4294967295。有个经典的问题unsigned int减到0之后继续减1会变成什么答案是4294967295。因为无符号整数不允许出现负数结果会以模运算的方式绕回最大值。这就是所谓的“无符号整数回绕”。写代码时如果比较一个有符号数和一个无符号数编译器会做隐式转换可能得到让人意外的结果。这个场景在刷题时经常遇到比如for (unsigned int i n; i 0; i--)会直接死循环因为i 0对无符号整数来说永远成立。初学阶段我建议循环变量用有符号整数除非有明确理由比如位运算才用无符号。sizeof是一个运算符用于获取变量或类型在内存中占据的字节数。用法是sizeof(int)或sizeof(age)。注意sizeof不是函数括号只是为了让表达式看得更清楚。很多初学者会混淆sizeof是编译期求值还是运行期求值——答案是编译期求值除变长数组外所以它在编译时就已经确定结果不会带来运行时开销。用下面的代码分别打印各类型的字节数#include stdio.h int main(void) { printf(char: %d\n, sizeof(char)); printf(short: %d\n, sizeof(short)); printf(int: %d\n, sizeof(int)); printf(long: %d\n, sizeof(long)); printf(long long: %d\n, sizeof(long long)); printf(float: %d\n, sizeof(float)); printf(double: %d\n, sizeof(double)); return 0; }在64位Linux上这个程序的输出是1、2、4、8、8、4、8。注意这里sizeof返回的类型是size_t它是一个无符号整数类型要用%luLinux下是%zu打印用%d在一些平台上会出现警告这也是不少初学者一开始会碰到的坑。5. 数据类型转换隐式转换与强制转换的规则和陷阱有了类型和数据程序在运行时的变量会发生赋值、参与运算。当不同类型的数据“接触”时C语言会按规则进行类型转换。这是个完全避不开的话题搜索热词里“数据类型强制转换”“c语言格式化输出时类型转换”都指向这里说明这是大家学习中普遍卡住的知识点。5.1 隐式类型转换你不知道但已经在用的规则C语言在表达式中遇到不同类型的数据时会按以下规则顺序做隐式转换如果表达式中存在long double其他操作数转为long double。否则如果存在double其他操作数转为double。否则如果存在float其他操作数转为float。否则进行整型提升char、short等小类型会提升为int或者unsigned int再进行类型比较和转换。化简记忆就是一句口诀“向上看齐”——小的往大的转精度低的往精度高的转。所以写int a 1; double b 2.5; double c a b;时表达式a b会把a先隐式转换为double再进行运算结果是3.5而不是3。隐式转换最典型的坑在于赋值时的截断int a 3.99;这段代码不会报错原因就是赋值时3.99被隐式转换为int型3。小数部分被直接截断不是四舍五入。这种代码不会报错但结果跟预期不同调试起来特别费劲。所以我建议凡是可能出现精度丢失的地方都要显式写转换强制转换让意图明确也方便后续维护的人理解。5.2 强制类型转换用完之后要记得它只是“临时面具”强制类型转换的语法是在值前面加括号写出目标类型int a 5, b 2; double result (double)a / b;如果不加转换a / b是整数除法结果是2加上(double)a后编译器先把a转为double再进行除法得到2.5。初学者在写分数计算时最容易犯的错误就是不理解整数除法的规则——两个整数相除结果仍然是整数小数部分被舍去。这是强制转换最经典的使用场景。一个需要特别强调的限制强制转换后的结果只是一个“临时值”它不会改变变量本身的类型和存储。执行完(double)aa自身仍然是int类型内存里的4字节也没变。可以把这个转换理解为“戴了一副临时面具”只在当前的表达式中生效。很多初学者会以为“我(double)a之后a就变成double了”这是错误的理解。另一个限制是强制转换不能把不相关类型随便转。比如把void*强制转换成一个具体类型的指针是合法的这是动态内存分配的标准做法但把一个float强制转换成一个函数指针就不行——它们是完全不同的数据形态。如果你确实需要做这类操作说明算法设计有问题建议优先调整逻辑而不是强转。5.3 转换中的溢出与精度丢失两个必须记住的边界案例第一个案例有符号转无符号的“魔法变化”。int a -1; unsigned int b (unsigned int)a; printf(%u\n, b); // 输出 4294967295-1 转成无符号为什么变成 4294967295因为 -1 在内存中的二进制表示是 32个1补码表示法转成无符号后按整数解读就是 2^32-1 4294967295。这就是二进制存储与类型解读之间的关系同一种二进制形态用有符号和无符号去解读得到的结果完全不同。第二个案例浮点转整型的截断。double pi 3.14159; int int_pi (int)pi; // int_pi 3浮点转整型不是四舍五入而是直接向零截断。3.999转成 int 是 3-3.999转成 int 是 -3而不是 -4。如果你需要四舍五入需要先调用round()函数再转。这两个案例在刷OJ在线判题系统题目和实际开发中都极其常见。特别是求平均值、计算百分比时整数除法隐式转换导致的精度问题是程序员debug到怀疑人生的高频原因。6. 初学C语言最容易踩的五个坑来自排错一线的经验记录这章的内容不是从教材上抄来的是我在实际带新人和自己写代码过程中整理出来的高频踩坑点。每个坑都配了“症状描述、原因分析、正确做法”三件套排查思路完整给你以后遇到同类问题可以直接照着查。6.1 坑一printf格式符与数据类型不匹配症状程序能编译但输出结果完全不是预期值甚至出现大得离谱的数或乱码。典型案例int num 42; printf(%f\n, num); // 错误用 %f 打印 int原因printf并不知道你传进去的变量是什么类型它完全依赖格式符来解释内存中的二进制数据。%f期望一个double8字节但你传了一个int4字节编译器会把其他地方的无关数据也当作double的一部分来解释结果自然荒诞。正确做法确保格式符和实际类型匹配这是printf使用的第一原则。我建议写完一段printf代码后养成随手检查一遍的习惯。如果是GCC编译器打开-Wall警告选项会让大部分格式符不匹配的问题在编译期就暴露出来gcc -Wall -o program program.c关键结论格式符是“阅读理解规则”不匹配就是拿错误的规则去解读错误的数据结果一定是垃圾。6.2 坑二整数除法导致意外截断症状计算5 / 2得到2而不是2.5。原因C语言中两个整数相除执行的是整数除法结果直接舍去小数部分而不是保留小数。典型场景计算平均分int total 95; int count 10; float avg total / count;结果是9.0而不是9.5。因为右侧total / count先按整数除法计算得到9再被赋给浮点变量时转成9.0——截断发生在赋值之前的整数除法阶段。正确做法先把其中一个操作数转成浮点类型float avg (float)total / count;或者直接使用浮点除法float avg total * 1.0f / count;。关键点在于只要除号两边有一个是浮点数整个除法就会按浮点运算执行。6.3 坑三整数溢出与类型范围选择失误症状累加计算到某个值后突然变成负数或结果与数学计算结果相差很大。典型案例int year_seconds 365 * 24 * 60 * 60;这个值等于31536000在int范围内2^31-1≈210万但如果再加一个计算后缀呢int distance 300000000 * 60;——300000000已经是很大的数乘以60后等于18000000000远超int最大存储范围发生溢出后结果是 -2565423360完全不可理喻。原因C语言的整数运算是“按类型位数截断”的。当结果超出存储范围时高位比特被丢弃剩余比特被重新解读为一个几乎不可能正确的数值。正确做法在计算前评估结果可能达到的数量级选择足够大的类型。比如long long distance 300000000LL * 60;注意我数值后面加了LL——这是long long字面量的后缀告诉编译器这个数是8字节整型。否则即使左边变量是long long右边的300000000 * 60仍然按int运算后再转换溢出还是会发生。6.4 坑四未初始化的局部变量症状一个变量打印出来是个无法预测的随机值或者用条件判断if (x 0)时结果时对时不对。典型案例int count; count count 1;原因int count;声明了一个变量但没有给它初始值。C语言不会自动把局部变量清零它的初始值是在栈上残留的“随机垃圾数据”。如果拿这个垃圾值参与运算行为完全不可预测。正确做法声明时直接初始化哪怕先初始化为0int count 0;这也回答了热词“c语言变量用%d输入一个字符后的值”关联的一个问题在C里变量的值完全取决于你如何初始化和赋值编译器不会替你做任何“合理的默认动作”。我用-Wall编译选项时GCC会对“可能使用未初始化变量”的代码发出警告但它在某些复杂流程下比如条件分支可能检测不出来所以最终还是要靠自己的初始化习惯来兜底。6.5 坑五scanf忘了加地址符症状程序运行到scanf时崩溃或输入后变量值完全不变。典型案例int age; scanf(%d, age); // 错误少了个 正确写法是int age; scanf(%d, age);原因scanf的功能是“把键盘输入的数据存到某个内存地址中”。它需要知道你指定变量的地址在哪里才能往那块内存里写入数据。如果你只写age它拿到的是变量本身的值一个随机垃圾数把这个值当作一个地址去写入轻则写入到未知区域导致变量没被正确赋值重则直接触发段错误崩溃。在这点上printf和scanf是对应但相反的关系printf只需要读取变量的值所以传变量名scanf需要写入变量的值所以必须传地址。凡是“需要让函数修改某个变量的值”的场景都要传指针取地址这是C语言函数参数传递的核心逻辑后面讲指针时会反复用到。7. 关于数据类型选择的一些实用建议与我的个人体会这篇文章写到这里基础知识链已经完整了。最后分享一些我在多年写C语言过程中的实际经验关于如何选类型、如何设计程序中的数据表示这些不在教材里但实战中非常有用。第一个体会类型选择是编程的第一步也是数据流设计最重要的一步。在动手写逻辑之前先想清楚这个程序涉及哪些数据每个数据用什么类型表示范围可能到多大需不需要小数。比如写一个温度传感器采集程序温度值可能是-40.0到80.0之间的小数那就用float如果只是个简单的计数器0到1000用int足够如果是时间戳很可能要用long long因为time_t在某些平台上就是64位。类型选错了后面所有逻辑都可能出错而且排错成本极高。第二个体会用“内存视角”去理解C语言很多问题都会豁然开朗。你写的每一个变量都对应内存中的一块空间每一次类型转换都是对二进制数据的不同解读或改写每一行printf都是按你的格式串去“翻译”二进制数据。当你从内存视角理解C语言后指针、数组、结构体、动态内存分配这些高难课题就不那么可怕了因为它们本质上是内存的地址、连续内存空间、分块组织内存、堆上申请内存这些操作。这也是C语言最迷人的地方——它不给你提供抽象层逼着你理解硬件真正发生了什么。第三个建议建议你从第一天就写下自己的小工具库。比如打印各种类型变量值、演示溢出行为、测试类型转换的小程序都保留下来。积累起来你会发现这些原来为了验证概念而写的小程序在后面的学习里反复能用上。调试方面从一开始就练习使用gcc -Wall -g编译学会看警告信息学会用gdb或IDE内置调试器打断点观察变量变化这些习惯比多学几个语法点重要得多。用-Wall能让编译器帮你提前拦截很多坑用-g能保留调试信息让断点调试成为可能。第四个体会C语言学习一定要动手验证而不是“听懂了”就过去。数据类型这块概念抽象但验证成本极低——写几行小程序打印几个变量的字节数和值跑一跑就能有非常直观的认知。我见过太多同学在“类型转换”一节对规则倒背如流真到写代码时还是掉进整数除法的坑里。原因就是没有亲手跑过亲手打印一次5 / 2的结果亲手试一次0.1 0.2的值那种“原来如此”的认识比任何背诵都牢固。数据类型只是C语言大厦的地基之一。地基打牢了后面的运算符、分支循环、函数、数组、指针才有坚实的地基来承重。下一篇写运算符和表达式时你会看到这篇文章里埋下的许多伏笔——比如char本质上是整数那它能不能参与加减运算比如整型提升规则是怎么影响表达式结果的这些问题等下一篇再把它们串起来。先把这篇文章里的代码动手跑一遍尤其是“打印各类字节数”和“类型转换”的小例子都亲自验证一次你会对C语言有一个更踏实的起步。
返回列表