Skip to content
← 返回C列表
知识专题

C 首批课程:指针、内存与文件统计完整项目 · 环境与第一个程序至结构体、头文件与标准库接口 ​

返回分篇目录

本轮仅全文静态复核,示例未执行;下文Windows/Linux运行记录属于此前批次,不证明本轮修订源码已经运行。

C适合学习对象如何存储、接口如何传地址和长度、资源如何显式释放。首批不要求先学C++或复杂算法;顺序是编译与函数→指针和数组→字符串与生存期→结构体和分离编译→文件与失败路径。原有类型和控制流、进制可用于巩固,本篇及项目构成独立可运行起点。

C 首批课程:指针、内存与文件统计完整项目 ​

本轮仅全文静态复核,示例未执行;下文Windows/Linux运行记录属于此前批次,不证明本轮修订源码已经运行。

C适合学习对象如何存储、接口如何传地址和长度、资源如何显式释放。首批不要求先学C++或复杂算法;顺序是编译与函数→指针和数组→字符串与生存期→结构体和分离编译→文件与失败路径。原有类型和控制流、进制可用于巩固,本篇及项目构成独立可运行起点。

环境与第一个程序 ​

教学采用C11,不把它说成最新标准;本批工具为Zig0.15.2所带Clang,Windows amd64 PowerShell与WSL Linux amd64 Bash分别构建运行。Zig是工具链选择,源码只依赖C标准库;已有GCC/Clang也可用,但先检查版本和目标平台。macOS不纳入本轮验收。安装Zig官方对应系统的压缩包,解压后用实际路径执行zig version;无需改系统PATH。以下zig代表该可执行文件,PowerShell路径含空格时用& '完整路径/zig.exe'。

保存为hello.c;终端进入所在目录。文件名和当前目录是路径概念,终端命令不是C代码。

c
#include <stdio.h>
static int passed(const int scores[], size_t length)
{
    int count = 0;
    for (size_t i = 0; i < length; ++i) if (scores[i] >= 60) ++count;
    return count;
}
int main(void)
{
    int scores[] = {59, 60, 100};
    printf("passed=%d\n", passed(scores, sizeof scores / sizeof scores[0]));
    return 0;
}

Windows执行zig cc -std=c11 -Wall -Wextra -Werror hello.c -o hello.exe、.\hello.exe;Linux将输出名改为hello,执行./hello。预期passed=2、退出0。-std选语言版本,警告参数帮助暴露问题,-Werror让警告作为失败;它们不能证明没有未定义行为。编译失败先读第一条诊断,程序尚未生成时不要继续运行旧的同名文件。

变量先初始化;整数、浮点、字符不是同一种表示。sizeof结果为size_t,打印用%zu;int不保证在所有实现中都是32位。整数除法先按操作数类型进行,1/2不是0.5。if、for和函数把处理过程组织起来,函数声明和实现须保持参数/返回类型一致。

指针、数组与长度契约 ​

变量保存值,&value取得该对象的地址,*pointer访问指向的对象。指针本身也是值;传入指针不自动转移所有权或保证有效。下面完整pointer.c的swap约定参数非NULL且指向仍存活、可写的int;调用者负责满足。

c
#include <assert.h>
static void swap(int *left, int *right)
{
    int temporary = *left;
    *left = *right;
    *right = temporary;
}
int main(void)
{
    int first = 1, second = 2;
    swap(&first, &second);
    assert(first == 2 && second == 1);
    int values[] = {10, 20, 30};
    int *position = values + 1;
    assert(*position == 20);
    return 0;
}

沿setup方式编译运行,预期无输出、退出0。数组表达式在很多上下文会转换为首元素指针,但数组不是指针对象;hello里的sizeof scores在main得到整个数组大小,函数数组形参已调整为指针,不能在passed里推回调用者长度。接口同时传地址与元素个数。只在同一数组及其尾后位置范围内做相应指针运算;尾后指针可作结束标记,不能解引用。指向局部变量的指针不能在其生存期结束后继续使用。

const int *p限制通过p修改所指int,不表示所有别名都不能修改该对象;int *const p限制指针重新指向。NULL表示空指针,不等于“可以读的零值对象”。野指针、越界或悬空访问可能偶尔看似工作,仍不能依赖。

字符串与显式资源 ​

C字符串是以零字节结束的字符序列;数组容量包含终止符,strlen只算终止符前的字节。字符串字面量不可修改,想改内容要使用可写数组。下面memory.c同时演示字符串和动态数组:

c
#include <assert.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
    char name[] = "C";
    name[0] = 'c';
    assert(strlen(name) == 1 && sizeof name == 2);
    size_t count = 4;
    int *values = calloc(count, sizeof *values);
    if (values == NULL) return 1;
    for (size_t i = 0; i < count; ++i) values[i] = (int)i;
    assert(values[3] == 3);
    free(values);
    return 0;
}

预期无输出、退出0;分配失败返回1。calloc获得动态存储,当前作用域结束不会自动free;每份分配只释放一次,free之后不能再读或写那片存储。把一个别名设NULL不会使其他悬空别名失效。外部输入决定数量时,另需检查数量与大小计算、业务上限;本例固定4,不是通用分配器。栈/堆是实现解释的常用词,语言生存期与所有权契约才是接口应说明的内容。

结构体、头文件与标准库接口 ​

结构体把字段组合为一个值;赋值复制字段,不会自动深拷贝指针所指内容。头文件描述共享声明,.c文件给出实现。预处理#include引入声明;编译把各源文件变成目标代码;链接把函数定义和所需库连起来。只编译main.c却调用score.c中的函数,常得到undefined reference一类链接错误。

项目使用stdio读取字节/关闭文件,stdlib做带结束位置的十进制解析,ctype检查空白。isspace等函数的字符参数需为unsigned char可表示值或EOF,不能随意传可能为负的char。strtol同时检查errno、结束位置与业务范围;只看转换结果会把1x当成1。atoi无法提供本项目所需的失败区分。

继续阅读