在 C 语言中,并没有一个叫做 table 的内置数据类型,所谓的“表格”通常是通过以下几种方式来实现的,每种方式都有其特定的应用场景。

(图片来源网络,侵删)
使用二维数组
这是最直接、最基础的方式来表示一个固定大小的表格,非常适合表示矩阵、棋盘、小型数据集等。
概念
二维数组本质上是一个“数组的数组”,你可以把它想象成一个有行和列的网格。
语法
// 数据类型 数组名[行数][列数]; int table[3][4]; // 定义一个 3 行 4 列的整型表格
示例:创建并初始化一个学生成绩表格
假设我们有 3 个学生,每个学生有 4 门课程的成绩。
#include <stdio.h>
int main() {
// 定义并初始化一个 3行4列的整型表格
// 行代表学生,列代表课程
int grades[3][4] = {
{90, 85, 88, 92}, // 学生0的成绩
{78, 82, 79, 85}, // 学生1的成绩
{95, 91, 93, 97} // 学生2的成绩
};
// 访问和修改表格中的数据
// 访问学生0的第2门课程成绩 (索引从0开始)
printf("学生0的第2门课程成绩是: %d\n", grades[0][1]);
// 修改学生1的第3门课程成绩
grades[1][2] = 80;
printf("修改后,学生1的第3门课程成绩是: %d\n", grades[1][2]);
// 遍历整个表格并打印
printf("\n--- 所有学生成绩 ---\n");
for (int i = 0; i < 3; i++) { // 遍历行 (学生)
printf("学生%d的成绩: ", i);
for (int j = 0; j < 4; j++) { // 遍历列 (课程)
printf("%d ", grades[i][j]);
}
printf("\n");
}
return 0;
}
优点:

(图片来源网络,侵删)
- 简单直观,易于理解。
- 内存是连续的,访问速度快。
缺点:
- 大小固定:一旦定义,行数和列数就不能改变。
- 内存效率可能不高:如果表格很大但很多单元格是空的,会造成内存浪费。
使用结构体数组
当你想表示的表格每一列的数据类型不同时(一个学生信息表,有姓名、年龄、成绩等),二维数组就无能为力了,这时,结构体数组是最佳选择。
概念
首先定义一个结构体来描述“一行”数据的结构,然后创建一个该结构体的数组来表示整个表格。
示例:创建一个学生信息表格
#include <stdio.h>
#include <string.h>
// 1. 定义一个结构体来表示表格的一行(一个学生的信息)
struct Student {
int id; // 学号
char name[50]; // 姓名
float score; // 成绩
};
int main() {
// 2. 定义一个结构体数组来表示整个表格
struct Student class_list[3] = {
{101, "张三", 88.5},
{102, "李四", 92.0},
{103, "王五", 76.5}
};
// 3. 访问和修改表格数据
// 访问第二个学生的姓名
printf("第二个学生的姓名是: %s\n", class_list[1].name);
// 修改第三个学生的成绩
class_list[2].score = 80.0;
printf("修改后,第三个学生的成绩是: %.1f\n", class_list[2].score);
// 4. 遍历整个表格并打印
printf("\n--- 全班学生信息 ---\n");
for (int i = 0; i < 3; i++) {
printf("学号: %d, 姓名: %s, 成绩: %.1f\n",
class_list[i].id,
class_list[i].name,
class_list[i].score);
}
return 0;
}
优点:

(图片来源网络,侵删)
- 可以处理不同数据类型的列。
- 逻辑清晰,代码可读性高。
缺点:
- 大小同样固定。
- 内存不连续,访问速度可能略慢于二维数组。
使用动态内存分配(模拟动态表格)
如果你需要一个大小在运行时才能确定的表格,可以使用动态内存分配。
概念
使用 malloc 或 calloc 在堆上为表格分配内存,对于二维表格,通常是先分配一个“行指针”数组,然后再为每一行分配“列”的内存。
示例:动态创建一个 M x N 的整数表格
#include <stdio.h>
#include <stdlib.h> // 用于 malloc 和 free
int main() {
int rows, cols;
printf("请输入表格的行数: ");
scanf("%d", &rows);
printf("请输入表格的列数: ");
scanf("%d", &cols);
// 1. 分配行指针数组
// 这是一个指向整型指针的指针,即 int*[rows]
int **table = (int **)malloc(rows * sizeof(int *));
if (table == NULL) {
printf("内存分配失败!\n");
return 1;
}
// 2. 为每一行分配列内存
for (int i = 0; i < rows; i++) {
table[i] = (int *)malloc(cols * sizeof(int));
if (table[i] == NULL) {
printf("内存分配失败!\n");
// 如果分配失败,需要释放之前已经分配的内存
for (int j = 0; j < i; j++) {
free(table[j]);
}
free(table);
return 1;
}
}
// 3. 使用表格(填充数据)
int count = 1;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
table[i][j] = count++;
}
}
// 4. 打印表格
printf("\n--- 动态生成的表格 ---\n");
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%4d ", table[i][j]);
}
printf("\n");
}
// 5. 释放内存(非常重要!)
// 先释放每一行的内存
for (int i = 0; i < rows; i++) {
free(table[i]);
}
// 再释放行指针数组
free(table);
return 0;
}
优点:
- 大小动态:可以在程序运行时决定表格的大小。
- 灵活性高。
缺点:
- 代码复杂,需要手动管理内存。
- 容易忘记
free,导致内存泄漏。 - 内存不连续,可能存在性能开销。
使用更高级的数据结构(链表)
如果表格的行数需要频繁地增加或删除,使用动态数组可能会因为 realloc 导致性能问题或数据复制,这时,可以使用链表来模拟表格。
概念
每一行数据用一个节点(通常是结构体)来存储,节点中包含指向下一行节点的指针,这样添加或删除一行就非常高效。
示例:使用链表实现学生信息表
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义链表节点,即表格的一行
struct StudentNode {
int id;
char name[50];
float score;
struct StudentNode *next; // 指向下一行的指针
};
// 函数:添加新行到表格(链表)
struct StudentNode* addStudent(struct StudentNode *head, int id, const char *name, float score) {
struct StudentNode *newNode = (struct StudentNode*)malloc(sizeof(struct StudentNode));
if (newNode == NULL) {
printf("内存分配失败!\n");
return head;
}
newNode->id = id;
strcpy(newNode->name, name);
newNode->score = score;
newNode->next = NULL;
if (head == NULL) {
head = newNode;
} else {
struct StudentNode *current = head;
while (current->next != NULL) {
current = current->next;
}
current->next = newNode;
}
return head;
}
// 函数:打印整个表格(链表)
void printTable(struct StudentNode *head) {
struct StudentNode *current = head;
printf("\n--- 学生信息表格 (链表实现) ---\n");
while (current != NULL) {
printf("学号: %d, 姓名: %s, 成绩: %.1f\n", current->id, current->name, current->score);
current = current->next;
}
}
// 函数:释放整个表格(链表)的内存
void freeTable(struct StudentNode *head) {
struct StudentNode *current = head;
while (current != NULL) {
struct StudentNode *temp = current;
current = current->next;
free(temp);
}
}
int main() {
struct StudentNode *class_list = NULL; // 初始化一个空表格
// 动态添加行
class_list = addStudent(class_list, 101, "赵六", 88.5);
class_list = addStudent(class_list, 102, "钱七", 92.0);
class_list = addStudent(class_list, 103, "孙八", 76.5);
printTable(class_list);
freeTable(class_list); // 释放所有内存
return 0;
}
优点:
- 插入和删除操作非常高效(时间复杂度 O(1) 或 O(n),取决于插入位置)。
- 大小完全动态,没有上限。
缺点:
- 代码最复杂。
- 内存不连续,访问特定行的速度较慢(需要遍历)。
- 每个节点都需要额外的空间存储指针,内存开销较大。
总结与选择
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 二维数组 | 固定大小的同类型数据表格(如矩阵、图像) | 简单、快速、内存连续 | 大小固定,类型单一 |
| 结构体数组 | 固定大小的异类型数据表格(如学生信息) | 逻辑清晰,可读性好 | 大小固定,内存不连续 |
| 动态内存分配 | 运行时才知道大小的表格 | 大小灵活 | 代码复杂,易内存泄漏 |
| 链表 | 需要频繁增删行的表格 | 增删高效,大小完全动态 | 访问慢,代码复杂,内存开销大 |
对于初学者来说,从二维数组和结构体数组开始是最好的选择,当你需要处理更复杂、更动态的数据时,再逐步学习动态内存分配和链表。
