跳转至

C语言详细教程:从入门到进阶

C语言是一门功能强大且应用广泛的面向过程的编程语言,自问世以来,在计算机领域扮演着至关重要的角色。[1] 本教程旨在为初学者提供一份详尽的C语言学习指南,从基础语法到核心概念,循序渐进,助您掌握这门经典的编程语言。

第一章:C语言基础

在开始编写C语言程序之前,我们需要了解一些基本概念。

1.1 第一个C程序:Hello, World!

学习任何一门编程语言,通常都是从打印"Hello, World!"开始的。下面是C语言的版本:

codeC

#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}

代码解释:

  • #include : 这是一个预处理器指令,它告诉编译器在实际编译之前,包含标准输入输出库(stdio.h)的头文件。这个头文件中包含了我们后面会用到的 printf() 函数的声明。
  • int main(): 这是主函数,是C语言程序的入口点。[2] 每个C程序都必须包含一个 main() 函数。 int 表示该函数执行完毕后会返回一个整数值。
  • printf("Hello, World!\n");: printf() 是一个库函数,用于将格式化的内容输出到屏幕上。 "Hello, World!\n" 是一个字符串,\n 是一个转义字符,表示换行。
  • return 0;: 这条语句表示 main() 函数执行完毕,并向操作系统返回一个值为0的状态码,表示程序正常退出。
  • ;: 在C语言中,分号是语句结束符,每个语句都必须以分号结尾。[3]

1.2 C语言的基本语法元素

  • 标记 (Tokens): C程序由各种标记组成,这些标记可以是关键字、标识符、常量、字符串或符号。[3]

​ * ​注释: 注释是程序中用于解释代码的文本,编译器会忽略它们。C语言支持两种注释方式:

单行注释:以 // 开始。[*4]

  • 多行注释:以 /* 开始,以 */ 结束。[5]
  • 标识符: 标识符是用来标识变量、函数或其他用户自定义项目的名称。[3] 标识符的命名规则是:

只能由字母(A-Z, a-z)、数字(0-9)和下划线(_)组成。*

  • 第一个字符必须是字母或下划线。
  • 不能是C语言的关键字。
  • 关键字: 关键字是C语言中预先保留的单词,它们具有特殊的含义,不能用作标识符。[2][3] 例如 int, void, return, for, while 等。

第二章:数据类型、变量与常量

在C语言中,数据类型用于声明不同类型的变量或函数。[6]

2.1 基本数据类型

C语言提供了多种基本数据类型,主要分为整数类型、浮点数类型和字符类型。[6][7][8]

类型 描述
char 字符型,通常占用1个字节,用于存储单个字符。
int 整型,用于存储整数。其存储大小与系统位数有关,在32位和64位系统中通常为4个字节。[9]
short 短整型,通常占用2个字节。
long 长整型,通常在32位系统中为4个字节,64位系统中为8个字节。
long long 更长的整型,通常占用8个字节,用于存储更大的整数。[9]
float 单精度浮点型,用于存储带小数的数字。
double 双精度浮点型,用于存储比 float 类型精度更高的带小数的数字。
void 空类型,主要用于表示函数没有返回值或函数没有参数。[6]

注意: 各种数据类型的存储大小可能因编译器和操作系统的不同而有所差异。[7] 可以使用 sizeof 运算符来获取特定类型或变量在当前平台上的准确大小。[9]

2.2 变量

变量是用于存储数据的内存位置的名称。在使用变量之前,必须先声明它,指明其数据类型。

变量声明与初始化:

codeC

int age;          // 声明一个名为 age 的整型变量
age = 25;         // 为变量 age 赋值

char grade = 'A'; // 在声明的同时初始化一个字符型变量

2.3 常量

常量的值在程序执行过程中是不可改变的。

定义常量的方式:

  • 使用 #define 预处理器:

codeC

#define PI 3.14159
* 使用 const 关键字:

codeC

const int MAX_AGE = 100;

使用 const 关键字声明的常量必须在声明时进行初始化。[10]

第三章:控制结构

控制结构用于控制程序的执行流程。[11] C语言主要有三种基本的控制结构:顺序结构、选择结构和循环结构。[12][13][14]

3.1 顺序结构

顺序结构是程序中最简单的结构,代码按照从上到下的顺序依次执行。[15]

3.2 选择结构

选择结构允许程序根据条件来决定执行哪个代码块。

  • if 语句: 用于基于一个条件来执行代码。

codeC

if (age > 18) {
    printf("你已经成年了。\n");
}
* if...else 语句: if 条件为假时,执行 else 后的代码块。[15]

codeC

if (score >= 60) {
    printf("及格了!\n");
} else {
    printf("不及格。\n");
}
* switch 语句: 用于根据一个表达式的值来选择多个代码块中的一个来执行。[15] 当有多个分支条件时,使用 switch 语句比使用多个 if 语句更加清晰。[12]

codeC

switch (day) {
    case 1:
        printf("星期一\n");
        break;
    case 7:
        printf("星期日\n");
        break;
    default:
        printf("无效的日期\n");
}

3.3 循环结构

循环结构用于重复执行一段代码。

  • for 循环: 用于执行一段代码固定次数。[15]

codeC

for (int i = 0; i < 5; i++) {
    printf("i 的值是 %d\n", i);
}
* while 循环: 在循环开始前检查条件,只要条件为真,循环体就会一直执行。[15]

codeC

int i = 0;
while (i < 5) {
    printf("i 的值是 %d\n", i);
    i++;
}
* do...while 循环: 至少执行一次循环体,然后在每次循环结束后检查条件。[15]

codeC

int i = 0;
do {
    printf("i 的值是 %d\n", i);
    i++;
} while (i < 5);

第四章:函数

函数是一段封装好的、可以重复使用的代码块,用于执行特定的任务。[16][17] C语言程序由一个或多个函数组成。

4.1 函数的定义和声明

  • 函数定义: 包含函数的具体实现。[18]

codeC

// 定义一个名为 add 的函数,它接收两个整数参数并返回它们的和
int add(int a, int b) {
    return a + b;
}
* ​函数声明: 告诉编译器函数的名称、返回类型和参数。[4][19] 在调用函数之前,需要先进行声明。

codeC

int add(int a, int b); // 函数声明

4.2 函数的调用

当程序需要执行函数中定义的任务时,可以通过函数名来调用它。

codeC

int main() {
    int num1 = 10;
    int num2 = 20;
    int sum = add(num1, num2); // 调用 add 函数
    printf("两数之和为: %d\n", sum);
    return 0;
}

main 函数中,我们调用了 add 函数,并将返回值赋给了 sum 变量。[16]

第五章:指针

指针是C语言的灵魂,也是其最强大的特性之一。理解指针是C语言学习过程中的一个重要里程碑。指针是一个变量,其值为另一个变量的内存地址。[1][2]

5.1 什么是指针?

想象一下你家的地址,它不是你家本身,但通过这个地址可以找到你的家。在计算机内存中,每个存储单元(比如变量)都有一个唯一的地址。指针变量就是用来存放这个地址的。

通过指针,我们可以间接地访问和修改内存中的数据,这在动态内存分配、向函数传递数组和结构体等高级编程技巧中至关重要。[3]

5.2 指针的声明与使用

指针的声明格式为:type *var-name;

  • type 是指针所指向的变量的数据类型。
  • * 表明声明的是一个指针变量。
  • var-name 是指针变量的名称。

核心操作符:

  • & (取地址运算符): 获取一个变量的内存地址。
  • * (解引用运算符): 访问指针所指向地址中存储的值。

codeC

#include <stdio.h>

int main() {
    int var = 20;   // 实际变量的声明
    int *ip;        // 指针变量的声明

    ip = &var;  // 将 var 的地址存储在指针变量 ip 中

    printf("var 变量的地址: %p\n", &var);

    // 在指针变量中存储的地址
    printf("ip 变量存储的地址: %p\n", ip);

    // 使用指针访问值
    printf("*ip 变量的值: %d\n", *ip);

    // 通过指针修改变量的值
    *ip = 30;
    printf("修改后 var 的值: %d\n", var);

    return 0;
}

5.3 NULL 指针

一个好的编程习惯是在声明指针时将其初始化为 NULL,以防它指向一个未知的内存地址。NULL 是一个特殊的宏,表示一个空指针,它不指向任何有效的内存地址。

codeC

int *ptr = NULL;

在对指针进行解引用操作前,检查它是否为 NULL 是一个非常重要的安全措施。

5.4 指针与数组

在C语言中,数组名本质上是该数组第一个元素的地址。这意味着数组名可以被当作一个指向数组开头的常量指针。[2]

codeC

int numbers[5] = {10, 20, 30, 40, 50};
int *p;

p = numbers; // 将数组 numbers 的首地址赋给指针 p

// 访问数组元素
printf("第一个元素: %d\n", *p);        // 输出 10
printf("第二个元素: %d\n", *(p + 1));  // 输出 20

// 数组下标和指针访问是等价的
// numbers[2] 等价于 *(numbers + 2)
printf("第三个元素: %d\n", numbers[2]); // 输出 30

第六章:数组与字符串

6.1 多维数组

C语言支持多维数组,最常见的是二维数组,可以将其看作是表格或矩阵。

codeC

// 声明一个 3 行 4 列的二维数组
int matrix[3][4] = {
   {1, 2, 3, 4},
   {5, 6, 7, 8},
   {9, 10, 11, 12}
};

// 访问第二行第三个元素
int element = matrix[1][2]; // element 的值为 7

6.2 字符串

在C语言中,字符串实际上是一个以空字符 \0 结尾的一维字符数组。

codeC

char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
char greeting_simple[] = "Hello"; // 编译器会自动在末尾添加 '\0'

C标准库 提供了一系列用于操作字符串的函数,非常方便:

  • strlen(): 计算字符串的长度(不包括 \0)。
  • strcpy(): 复制一个字符串到另一个。
  • strcat(): 连接两个字符串。
  • strcmp(): 比较两个字符串。

codeC

#include <stdio.h>
#include <string.h>

int main() {
   char str1[12] = "Hello";
   char str2[12] = "World";
   char str3[12];
   int  len;

   // 复制 str1 到 str3
   strcpy(str3, str1);
   printf("strcpy( str3, str1) :  %s\n", str3 );

   // 连接 str1 和 str2
   strcat(str1, str2);
   printf("strcat( str1, str2):   %s\n", str1 );

   // 连接后,str1 的总长度
   len = strlen(str1);
   printf("strlen(str1) :  %d\n", len );

   return 0;
}

第七章:结构体与联合体

7.1 结构体 (struct)

结构体是一种用户自定义的数据类型,允许我们将不同类型的变量组合成一个单一的实体。[1][2] 这对于组织复杂的数据非常有用。

codeC

#include <stdio.h>
#include <string.h>

// 定义一个名为 Book 的结构体
struct Book {
   char  title[50];
   char  author[50];
   int   book_id;
};

int main() {
   struct Book book1; // 声明一个 Book 类型的变量 book1

   // 为 book1 的成员赋值
   strcpy(book1.title, "C Programming");
   strcpy(book1.author, "Dennis Ritchie");
   book1.book_id = 12345;

   // 打印 book1 的信息
   printf("书名: %s\n", book1.title);
   printf("作者: %s\n", book1.author);
   printf("ID: %d\n", book1.book_id);

   return 0;
}

当使用指向结构体的指针时,我们可以使用箭头运算符 -> 来访问结构体的成员。

codeC

struct Book *ptr_book = &book1;
printf("书名: %s\n", ptr_book->title);

7.2 联合体 (union)

联合体(Union)是一种特殊的数据类型,它允许在相同的内存位置存储不同的数据类型。[2] 联合体的大小等于其最大成员的大小。在任何时候,联合体中只能有一个成员可以被正确地访问。

codeC

union Data {
   int i;
   float f;
   char str[20];
};

union Data data;
data.i = 10;
printf("data.i: %d\n", data.i); // 正确

data.f = 220.5;
printf("data.f: %.1f\n", data.f); // 正确

// 此时 data.i 的值已经被破坏

第八章:动态内存管理

到目前为止,我们使用的都是静态内存分配(在编译时确定大小)。C语言还允许在程序运行时动态地分配内存。[2][4]

这需要使用 头文件中的函数:

  • malloc(size): 分配指定大小(以字节为单位)的内存块,并返回一个指向该内存块起始位置的 void 类型指针。分配的内存未被初始化。

​ * ​calloc(num, size): 分配 num 个大小为 size 的连续内存空间,并返回一个指向该内存块起始位置的指针。分配的内存会被初始化为0。

​ * ​realloc(ptr, size): 重新调整之前分配的内存块 ptr 的大小为 new_size

​ * ​free(ptr): 释放之前通过 malloc, calloc realloc 分配的内存空间。

重要提示: 每次调用 malloc calloc 后,都应该检查返回的指针是否为 NULL,以确认内存分配是否成功。并且,每次动态分配的内存,在使用完毕后都必须通过 free() 来释放,否则会导致内存泄漏。[4]

codeC

#include <stdio.h>
#include <stdlib.h>

int main() {
    int n = 5;
    int *arr;

    // 动态分配一个可以存储 5 个整数的数组
    arr = (int*) malloc(n * sizeof(int));

    // 检查内存分配是否成功
    if (arr == NULL) {
        printf("内存分配失败\n");
        return 1; // 异常退出
    }

    // 使用分配的内存
    for (int i = 0; i < n; i++) {
        arr[i] = i + 1;
    }

    printf("动态分配的数组元素: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    // 释放内存
    free(arr);
    arr = NULL; // 避免悬挂指针

    return 0;
}

第九章:文件输入/输出 (I/O)

C语言提供了一系列库函数来处理文件操作。这些操作基于一个名为 FILE 的结构体。[1]

基本文件操作流程:

  • 使用 fopen() 打开一个文件,并返回一个 FILE 指针。
  • 使用 fprintf(), fscanf(), fgetc(), fputc() 等函数对文件进行读写。
  • 使用 fclose() 关闭文件。

codeC

#include <stdio.h>

int main() {
    FILE *fp = NULL;

    // 以写入模式打开文件
    fp = fopen("test.txt", "w");
    if (fp == NULL) {
        printf("无法打开文件\n");
        return 1;
    }

    // 向文件写入内容
    fprintf(fp, "这是一个测试文件。\n");
    fprintf(fp, "Hello, C File I/O!\n");

    // 关闭文件
    fclose(fp);

    char buffer[100];

    // 以读取模式打开文件
    fp = fopen("test.txt", "r");
    if (fp == NULL) {
        printf("无法打开文件\n");
        return 1;
    }

    // 从文件读取内容并打印
    while(fgets(buffer, 100, fp) != NULL) {
        printf("%s", buffer);
    }

    // 关闭文件
    fclose(fp);

    return 0;
}

至此,您已经学习了C语言从基础到进阶的核心概念。掌握这些知识将为您编写更复杂、更强大的程序打下坚实的基础。编程是一门实践的艺术,不断地练习和编写代码是提升技能的最佳途径。