引言

全国计算机等级考试(NCRE)二级C语言程序设计是检验计算机应用能力的重要标准,对于非计算机专业的学生和职场人士来说,这是一张含金量较高的证书。C语言作为一门基础且强大的编程语言,其学习过程既充满挑战又富有成就感。本篇文章将从基础语法入手,逐步深入到指针等难点,结合典型题库进行精讲,帮助考生系统复习,高效备考,轻松通关。

一、C语言基础语法精讲

1.1 数据类型与变量

C语言的数据类型是程序设计的基石。主要分为基本数据类型、构造数据类型、指针类型和空类型。

基本数据类型

  • 整型(int):用于存储整数,如 int age = 25;
  • 浮点型(float, double):用于存储小数,如 float price = 19.99; double pi = 3.1415926;
  • 字符型(char):用于存储单个字符,如 char grade = 'A';

变量的定义与初始化

#include <stdio.h>

int main() {
    int a = 10;      // 定义整型变量a并初始化为10
    float b = 3.14;  // 定义浮点型变量b并初始化为3.14
    char c = 'x';    // 定义字符型变量c并初始化为'x'
    
    printf("a = %d, b = %f, c = %c\n", a, b, c);
    return 0;
}

代码解析:这段代码展示了如何定义和初始化三种基本数据类型的变量,并使用printf函数输出它们的值。注意格式化输出符:%d用于整型,%f用于浮点型,%c用于字符型。

1.2 运算符与表达式

C语言提供了丰富的运算符,包括算术运算符、关系运算符、逻辑运算符、位运算符等。

算术运算符:+、-、*、/、%(取模)

int x = 10, y = 3;
int sum = x + y;    // 13
int diff = x - y;   // 7
int product = x * y; // 30
int quotient = x / y; // 3(整数除法)
int remainder = x % y; // 1

关系运算符:>、<、==、!=、>=、<=

int a = 5, b = 8;
int result = (a == b); // 0(false)

逻辑运算符:&&(与)、||(或)、!(非)

int m = 1, n = 0;
int andResult = m && n; // 0(false)
int orResult = m || n;  // 1(true)
int notResult = !m;     // 0(false)

自增自减运算符:++、–

int i = 5;
int j = i++; // 先使用i的值(5),然后i自增(6)
int k = ++i; // i先自增(7),然后使用i的值(7)

1.3 输入输出函数

printf函数:格式化输出

int age = 20;
float height = 1.75;
printf("年龄:%d,身高:%.2f米\n", age, height);
// 输出:年龄:20,身高:1.75米

scanf函数:格式化输入

int num;
printf("请输入一个整数:");
scanf("%d", &num); // 注意:需要取地址符&
printf("你输入的数字是:%d\n", num);

注意:scanf函数在读取字符串时容易出问题,建议使用fgets替代,但在二级考试中仍需掌握scanf。

1.4 控制结构

if-else语句

int score;
printf("请输入成绩:");
scanf("%d", &score);

if (score >= 90) {
    printf("优秀\n");
} else if (score >= 80) {
    printf("良好\n");
} else if (score >= 60) {
    printf("及格\n");
} else {
    printf("不及格\n");
}

switch语句

int day;
printf("请输入星期(1-7):");
scanf("%d", &day);

switch (day) {
    case 1: printf("星期一\n"); break;
    case 2: printf("星期二\n"); break;
    case 3: printf星期三\n"); break;
    case 4: printf("星期四\n"); break;
    case 1: printf("星期五\n"); break;
    case 6: printf("星期六\n"); break;
    case 7: printf("星期日\n"); break;
    default: printf("输入无效\n");
}

for循环

// 打印1到10的平方
for (int i = 1; i <= 10; i++) {
    printf("%d的平方是%d\n", i, i*i);
}

while循环

// 计算1到100的和
int sum = 0, i = 1;
while (i <= 100) {
    sum += i;
    i++;
}
printf("1到100的和是:%d\n", sum);

break和continue

// break示例:找到第一个能被3和5整除的数
for (int i = 1; i <= 100; i++) {
    if (i % 3 == 0 && i % 5 == 0) {
        printf("找到:%d\n", i);
        break; // 退出循环
    }
}

// continue示例:打印1-20中所有奇数
for (int i = 1; i <= 20; i++) {
    if (i % 2 == 0) continue; // 跳过偶数
    printf("%d ", i);
}

二、数组与字符串详解

2.1 一维数组

定义与初始化

int scores[5] = {87, 92, 78, 85, 90}; // 定义并初始化
int temperatures[10]; // 只定义不初始化

数组元素的访问

// 修改数组元素
scores[0] = 95; // 将第一个元素改为95

// 遍历数组
for (int i = 0; i < 5; i++) {
    printf("scores[%d] = %d\n", i, scores[i]);
}

数组作为函数参数

// 计算数组元素的平均值
float average(int arr[], int size) {
    int sum = 0;
    for (int i = 0; i < size; i++) {
        sum += arr[i];
    }
    return (float)sum / size;
}

int main() {
    int nums[5] = {10, 20, 30, 40, 50};
    float avg = average(nums, 5);
    printf("平均值:%.2f\n", avg);
    return 0;
}

2.2 二维数组

定义与初始化

int matrix[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};

遍历二维数组

// 打印矩阵
for (int i = 0;  i < 3; i++) {
    for (int j = 0; j < 4; j++) {
        printf("%4d", matrix[i][j]);
    }
    printf("\n");
}

二维数组应用:矩阵相加

void matrixAdd(int a[3][4], int b[3][4], int c[3][4]) {
    for (int i = 0; i < 3; i++) {
        for (int j = 0;j < 4; j++) {
            c[i][j] = a[i][j] + b[i][j];
        }
    }
}

2.3 字符串与字符数组

C语言中字符串是以’\0’结尾的字符数组。

字符串的定义

char str1[20] = "Hello"; // 自动添加'\0'
char str2[] = "World";   // 长度自动计算
char str3[20];           // 空字符串

字符串输入输出

char name[50];
printf("请输入你的名字:");
scanf("%s", name); // 注意:scanf遇到空格会停止
printf("你好,%s!\n", name);

// 使用fgets可以读取带空格的字符串
char fullName[100];
printf("请输入全名:");
fgets(fullName, sizeof(fullName), stdin);
printf("你好,%s", fullName);

字符串处理函数(需要#include ):

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

int main() {
    char str1[20] = "Hello";
    char str2[20] = "World";
    char str3[40];
    
    // 字符串长度
    printf("str1长度:%d\n", strlen(str1)); // 5
    
    // 字符串复制
    strcpy(str3, str1);
    printf("复制后:%s\n", str3); // Hello
    
    // 字符串连接
    strcat(str3, " ");
    strcat(str3, str2);
    printf("连接后:%s\n", str3); // Hello World
    
    // 字符串比较
    int result = strcmp(str1, str2);
    if (result == 0) {
        printf("相等\n");
    } else if (result < 0) {
        printf("str1 < str2\n");
    } else {
        printf("str1 > str2\n");
    }
    
    return 0;
}

三、函数与模块化编程

3.1 函数的定义与调用

函数定义格式

返回类型 函数名(参数列表) {
    // 函数体
    return 返回值;
}

无参函数示例

void printHello() {
    printf("Hello, World!\n");
}

int main() {
    printHello(); // 函数调用
    return 0;
}

有参函数示例

int add(int a, int  b) {
    return a + b;
}

int main() {
    int x = 5, y = 3;
    int sum = add(x, y);
    printf("%d + %d = %d\n", x, y,  sum);
    return 0;
}

3.2 函数参数传递:值传递与地址传递

值传递(不改变原变量):

void swap(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
    printf("交换后:a=%d, b=%d\n", a, b);
}

int main() {
    int x = 10, y = 20;
    swap(x, y); // 函数内部交换成功,但x和y不变
    printf("调用后:x=%d, y=%d\n", x, y); // x=10, y=20
    return 0;
}

地址传递(改变原变量):

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 10, y = 20;
    swap(&x, &y); // 传递地址
    printf("调用后:x=%d, y=%d\n", x, y); // x=20, y=10
    return 0;
}

3.3 变量的作用域与存储类别

局部变量:在函数内部定义,只在函数内有效

void func() {
    int localVar = 10; // 局部变量
    printf("%d\n", localVar);
}

全局变量:在函数外部定义,所有函数都可访问

int globalVar = 100; // 全局变量

void func1() {
    globalVar++;
    printf("func1: %d\n", globalVar);
}

void func2() {
    printf("func2: %d\n", globalVar);
}

int main() {
    func1(); // 101
    func2(); // 101
    return 0;
}

静态局部变量(static):

void counter() {
    static int count = 0; // 只初始化一次
    count++;
    printf("count = %d\n", count);
}

int main() {
    counter(); // count = 1
    counter(); // count = 2
    counter(); // count = 3
    return 0;
}

3.4 递归函数

递归函数是函数调用自身,必须有一个明确的终止条件。

递归示例:计算阶乘

int factorial(int n) {
    if (n == 0 || n == 1) {
        return 1; // 递归终止条件
    }
    return n * factorial(n - 1); // 递归调用
}

int main() {
    int n;
    printf("请输入一个非负整数:");
    scanf("%d", &n);
    printf("%d! = %d\n", n, factorial(n));
    return 0;
}

递归示例:斐波那契数列

int fibonacci(int n) {
    if (n == 0) return 0;
    if (n == 1) return 1;
    return fibonacci(n-1) + fibonacci(n-2);
}

int main() {
    for (int i = 0; i < 10; i++) {
        printf("F(%d) = %d\n", i, fibonacci(i));
    }
    return 0;
}

四、指针详解(重点与难点)

指针是C语言的灵魂,也是二级考试的重点和难点。

4.1 指针基础

什么是指针:指针是一个变量,其值为另一个变量的内存地址。

指针的定义与使用

int a = 10;
int *p;      // 定义一个指向int的指针
p = &a;      // p指向a的地址

printf("a的值:%d\n", a);        // 10
printf("a的地址:%p\n", &a);     // 某个地址
printf("p的值:%p\n", p);        // 与&a相同
printf("*p的值:%d\n", *p);      // 10(解引用)

指针的运算

int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // p指向数组第一个元素

printf("*p = %d\n", *p); // 10
p++; // 指针向后移动一个元素(4字节)
printf("*p = %d\n", *p); // 20
p--; // 指针向前移动
printf("*p = %d\n", *p); // 10

// 指针相减:计算两个指针之间相差多少个元素
int *p1 = &arr[0];
int *p2 = &arr[4];
printf("相差:%ld个元素\n", p2 - p1); // 4

4.2 指针与数组

数组名的本质:数组名是常量指针,指向数组首元素。

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // 等价于 int *p = &arr[0];

// 用指针访问数组元素
for (int i = 0; i < 5; i++) {
    printf("arr[%d] = %d\n", i, *(p + i));
}

// 指针遍历数组
int *end = arr + 5; // 指向数组末尾的下一个位置
while (p < end) {
    printf("%d ", *p);
    p++;
}

二维数组与指针

int matrix[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    matrix[0] = {1, 2, 3, 4} // matrix[0]是数组名,是地址
};

// 定义指向一维数组的指针
int (*p)[4] = matrix; // p是指向包含4个int的数组的指针

// 访问元素
printf("matrix[1][2] = %d\n", *(*(p+1) + 2)); // 7

4.3 指针与函数

指针作为函数参数(实现引用传递):

void increment(int *p) {
    (*p)++; // 对指针指向的值进行操作
}

int main() {
    int num = 10;
    increment(&num);
    printf("num = %d\n", num); // 11
    return 0;
}

指针作为函数返回值

int* findMax(int arr[], int size) {
    int *max = arr;
    for (int i = 1; i < size; i++) {
        if (arr[i] > *max) {
            max = &arr[i];
        }
    }
    return max;
}

int main() {
    int nums[5] = {3, 7, 2, 9, 1};
    int *maxPtr = findMax(nums, 5);
    printf("最大值:%d\n", *maxPtr); // 9
    return 0;
}

函数指针

// 定义一个函数指针类型
typedef int (*Operation)(int, int);

int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }

int main() {
    Operation op;
    op = add;
    printf("5 + 3 = %d\n", op(5, 3)); // 8
    op = subtract;
    printf("5 - 3 = %d\n", op(5, 3)); // 2
    return 0;
}

4.4 指针与字符串

字符串与指针

char str[] = "Hello"; // 字符数组
char *ptr = "World";  // 字符指针(指向字符串常量)

printf("%s\n", str); // Hello
printf("%s\n", ptr); // World

// 修改字符数组
str[0] = 'h'; // 合法,str变为"hello"

// 修改字符指针(危险!)
// ptr[0] = 'w'; // 错误!字符串常量不可修改

字符串处理函数的指针实现

// 自定义strlen函数
int myStrlen(const char *str) {
    const char *s = str;
    while (*s != '\0') {
        s++;
    }
    return s - str;
}

// 自定义strcpy函数
void myStrcpy(char *dest, const char *src) {
    while ((*dest++ = *src++) != '\0');
}

int main() {
    char src[] = "Hello";
    char dest[10];
    myStrcpy(dest, src);
    printf("复制结果:%s\n", dest);
    printf("长度:%d\n", myStrlen(dest));
    return 0;
}

4.5 指针数组与数组指针

指针数组(数组的元素是指针):

char *names[] = {"Alice", "Bob", "Charlie", "David"};
// names是数组,每个元素是char*,指向字符串常量

for (int i = 0; i < 4; i++) {
    printf("%s\n", names[i]);
}

数组指针(指向数组的指针):

int arr[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
int (*p)[4] = arr; // p是指向包含4个int的数组的指针

// 通过指针访问二维数组
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 4; j++) {
        printf("%4d", *(*(p+i) + j));
    }
    printf("\n");
}

4.6 动态内存分配

malloc和free函数(需要#include ):

#include <stdlib.h>

int main() {
    int *arr;
    int n;
    
    printf("请输入数组大小:");
    scanf("%d", &n);
    
    // 动态分配内存
    arr = (int*)malloc(n * sizeof(int));
    if (arr == NULL) {
        printf("内存分配失败!\n");
        return 1;
    }
    
    // 使用动态数组
    for (int i = 0; i < n; i++) {
        arr[i] = i * 10;
    }
    
    // 输出
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    // 释放内存
    free(arr);
    arr = NULL; // 防止悬空指针
    
    return 0;
}

calloc和realloc函数

// calloc:分配并初始化为0
int *arr = (int*)calloc(5, sizeof(int)); // 5个int,初始化为0

// realloc:重新调整内存大小
arr = (int*)realloc(arr, 10 * sizeof(int)); // 扩大为10个int

五、结构体与共用体

5.1 结构体

定义结构体类型

struct Student {
    char name[20];
    int age;
    float score;
};

结构体变量的定义与初始化

// 方式1:先定义类型,再定义变量
struct Student stu1 = {"张三", 20, 85.5};

// 方式2:定义类型的同时定义变量
struct Person {
    char name[20];
    int age;
} p1 = {"李四", 22};

// 方式3:直接定义匿名结构体变量
struct {
    int x;
    int y;
} point = {10, 20};

结构体数组

struct Student class[3] = {
    {"张三", 20, 85.5},
    {"李四", 21, 92.0},
    {"王五", 19, 78.5}
};

// 计算平均分
float sum = 0;
for (int i = 0; i < 3; i++) {
    sum += class[i].score;
}
printf("平均分:%.2f\n", sum / 3);

结构体指针

struct Student stu = {"赵六", 20, 88.0};
struct Student *p = &stu;

// 通过指针访问成员(两种方式)
printf("姓名:%s\n", (*p).name);
printf("年龄:%d\n", p->age); // 箭头运算符
printf("分数:%.1f\n", p->score);

结构体作为函数参数

void printStudent(struct Student *s) {
    printf("姓名:%s,年龄:%d,分数:%.1f\n", 
           s->name, s->age, s->score);
}

int main() {
    struct Student stu = {"孙七", 21, 90.5};
    printStudent(&stu);
    return 0;
}

5.2 共用体

共用体定义:所有成员共享同一段内存空间。

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);
printf("data.i = %d\n", data.i); // 值已被覆盖,结果不确定

strcpy(data.str, "C语言");
printf("data.str = %s\n", data.str);

5.3 枚举类型

枚举定义

enum Weekday {
    Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
};

枚举使用

enum Weekday today = Wednesday;
printf("今天是第%d天\n", today); // 输出2(从0开始)

// 枚举在switch中的应用
switch (today) {
    case Monday: printf("星期一\n"); break;
    case Tuesday: printf("星期二\n"); break;
    // ...
}

六、文件操作

6.1 文件的打开与关闭

FILE指针

#include <stdio.h>

FILE *fp;
fp = fopen("test.txt", "r"); // 打开文件用于读取
if (fp == NULL) {
    printf("文件打开失败!\n");
    return 1;
}
fclose(fp); // 关闭文件

文件打开模式

  • “r”:只读(文件必须存在)
  • “w”:只写(创建新文件,覆盖已有)
  • “a”:追加(在文件末尾添加)
  • “r+“:读写(文件必须存在)
  • “w+“:读写(创建新文件,覆盖已有)
  • “a+“:读写(在文件末尾添加)

6.2 文件的读写

字符读写函数

// 写字符到文件
fp = fopen("output.txt", "w");
if (fp != NULL) {
    fputc('A', fp);
    fputc('B', fp);
    fputc('C', fp);
    fclose(fp);
}

// 从文件读取字符
fp = fopen("output.txt", "r");
if (fp != NULL) {
    char ch;
    while ((ch = fgetc(fp)) != EOF) {
        putchar(ch);
    }
    fclose(fp);
}

字符串读写函数

// 写字符串到文件
fp = fopen("output.txt", "w");
if (fp != NULL) {
    fputs("Hello, File!\n", fp);
    fclose(fp);
}

// 从文件读取字符串
fp = fopen("output.txt", "r");
if (fp != NULL) {
    char buffer[100];
    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        printf("%s", buffer);
    }
    fclose(fp);
}

格式化读写函数

// 写格式化数据到文件
fp = fopen("data.txt", "w");
if (fp != NULL) {
    int age = 25;
    float score = 95.5;
    fprintf(fp, "年龄:%d,分数:%.1f\n", age, score);
    fclose(fp);
}

// 从文件读取格式化数据
fp = fopen("data.txt", "r");
if (fp != NULL) {
    int age;
    float score;
    fscanf(fp, "年龄:%d,分数:%f", &age, &score);
    printf("读取:年龄=%d,分数=%.1f\n", age, score);
    fclose(fp);
}

二进制读写函数

struct Student {
    char name[20];
    int age;
    float score;
};

// 写结构体到文件
fp = fopen("student.dat", "wb");
if (fp != NULL) {
    struct Student stu = {"张三", 20, 85.5};
    fwrite(&stu, sizeof(struct Student), 1, fp);
    fclose(fp);
}

// 从文件读取结构体
fp = fopen("student.dat", "rb");
if (fp != NULL) {
    struct Student stu;
    fread(&stu, sizeof(struct Student), 1, fp);
    printf("姓名:%s,年龄:%d,分数:%.1f\n", 
           stu.name, stu.age, stu.score);
    fclose(fp);
}

七、二级考试典型题库精讲

7.1 基础语法题

题目:编写程序,判断一个数是否为素数。

分析:素数(质数)是大于1的自然数,除了1和它本身外,不能被其他自然数整除。

代码实现

#include <stdio.h>
#include <math.h>

int main() {
    int n, i;
    int flag = 1; // 1表示是素数,0表示不是
    
    printf("请输入一个正整数:");
    scanf("%d", &n);
    
    if (n <= 1) {
        flag = 0;
    } else {
        // 只需检查到sqrt(n)即可
        for (i = 2; i <= sqrt(n); i++) {
            if (n % i == 0) {
                flag = 0;
                break;
            }
        }
    }
    
    if (flag) {
        printf("%d是素数\n", n);
    } else {
        printf("%d不是素数\n", n);
    }
    
    return 0;
}

优化版本(只检查奇数):

int isPrime(int n) {
    if (n <= 1) return 0;
    if (n == 2 || n == 3) return 1;
    if (n % 2 == 0) return 0;
    
    for (int i = 3; i * i <= n; i += 2) {
        if (n % i == 0) return 0;
    }
    return 1;
}

7.2 数组与排序算法

题目:用冒泡排序法对数组进行升序排序。

分析:冒泡排序通过相邻元素比较和交换,将最大(或最小)元素”冒泡”到数组末尾。

代码实现

#include <stdio.h>

void bubbleSort(int arr[], int size) {
    for (int i = 0; i < size - 1; i++) {
        // 每次循环将当前最大值放到最后
        for (int j = 0; j < size - 1 - i; j++) {
            if (arr[j] > arr[j+1]) {
                // 交换
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}

int main() {
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    int size = sizeof(arr) / sizeof(arr[0]);
    
    printf("排序前:");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    bubbleSort(arr, size);
    
    printf("排序后:");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    return 0;
}

选择排序(考试常考):

void selectionSort(int arr[], int size) {
    for (int i = 0; i < size - 1; i++) {
        int minIndex = i;
        for (int j = i + 1; j < size; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        // 交换
        int temp = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = temp;
    }
}

7.3 字符串处理题

题目:编写程序,将字符串中的小写字母转换为大写,大写字母转换为小写,其他字符不变。

分析:利用ASCII码值,小写字母比大写字母大32。

代码实现

#include <stdio.h>
#include <ctype.h>

void convertCase(char *str) {
    for (int i = 0; str[i] != '\0'; i++) {
        if (islower(str[i])) {
            str[i] = toupper(str[i]);
        } else if (isupper(str[i])) {
            str[i] = tolower(str[i]);
        }
    }
}

int main() {
    char str[100];
    printf("请输入字符串:");
    fgets(str, sizeof(str), stdin);
    
    convertCase(str);
    printf("转换后:%s", str);
    
    return 0;
}

不使用库函数的版本

void convertCase(char *str) {
    for (int i = 0; str[i] != '\0'; i++) {
        if (str[i] >= 'a' && str[i] <= 'z') {
            str[i] = str[i] - 32; // 小写转大写
        } else if (str[i] >= 'A' && str[i] <= 'Z') {
            str[i] = str[i] + 32; // 大写转小写
        }
    }
}

7.4 指针难题

题目:编写函数,将字符串逆序存放。

分析:使用两个指针,一个指向开头,一个指向结尾,交换字符后向中间移动。

代码实现

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

void reverseString(char *str) {
    char *start = str;
    char *end = str + strlen(str) - 1;
    char temp;
    
    while (start < end) {
        temp = *start;
        *start = *end;
        *end = temp;
        start++;
        end--;
    }
}

int main() {
    char str[100];
    printf("请输入字符串:");
    fgets(str, sizeof(str), stdin);
    
    // 去除换行符
    str[strcspn(str, "\n")] = '\0';
    
    reverseString(str);
    printf("逆序后:%s\n", str);
    
    return 0;
}

题目:编写函数,删除字符串中指定位置的字符。

分析:从删除位置开始,将后面的字符依次前移。

代码实现

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

void deleteChar(char *str, int pos) {
    int len = strlen(str);
    if (pos < 0 || pos >= len) {
        printf("位置无效!\n");
        return;
    }
    
    // 从pos位置开始,后面的字符前移
    for (int i = pos; i < len - 1; i++) {
        str[i] = str[i+1];
    }
    str[len-1] = '\0'; // 缩短字符串长度
}

int main() {
    char str[100];
    int pos;
    
    printf("请输入字符串:");
    fgets(str, sizeof(str), stdin);
    str[strcspn(str, "\n")] = '\0';
    
    printf("请输入要删除的位置:");
    scanf("%d", &pos);
    
    deleteChar(str, pos);
    printf("删除后:%s\n",  str);
    
    return 0;
}

7.5 结构体应用题

题目:有5个学生,每个学生包括学号、姓名、3门课成绩。编写程序:

  1. 输入每个学生的数据
  2. 计算每个学生的平均分
  3. 按平均分从高到低排序
  4. 输出每个学生的学号、姓名、平均分

代码实现

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

struct Student {
    char id[20];
    char name[20];
    int scores[3];
    float average;
};

void inputStudents(struct Student stu[], int n) {
    for (int i = 0; i < n; i++) {
        printf("请输入第%d个学生的学号、姓名和3门课成绩:\n", i+1);
        scanf("%s %s %d %d %d", 
              stu[i].id, stu[i].name,
              &stu[i].scores[0], &stu[i].scores[1], &stu[i].scores[2]);
        
        // 计算平均分
        stu[i].average = (stu[i].scores[0] + stu[i].scores[1] + stu[i].scores[2]) / 3.0;
    }
}

void sortStudents(struct Student stu[], int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            if (stu[j].average < stu[j+1].average) {
                // 交换结构体
                struct Student temp = stu[j];
                stu[j] = stu[j+1];
                stu[j+1] = temp;
            }
        }
    }
}

void printStudents(struct Student stu[], int n) {
    printf("\n%-10s %-10s %-6s\n", "学号", "姓名", "平均分");
    printf("----------------------------\n");
    for (int i = 0; i < n; i++) {
        printf("%-10s %-10s %-6.1f\n", 
               stu[i].id, stu[i].name, stu[i].average);
    }
}

int main() {
    struct Student students[5];
    
    inputStudents(students, 5);
    sortStudents(students, 5);
    printStudents(students, 5);
    
    return 0;
}

7.6 链表基础题

题目:创建一个单向链表,存储学生信息(学号、姓名、成绩),并遍历输出。

代码实现

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

// 链表节点结构
struct Node {
    char id[20];
    char name[20];
    float score;
    struct Node *next;
};

// 创建新节点
struct Node* createNode(char *id, char *name, float score) {
    struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
    strcpy(newNode->id, id);
    strcpy(newNode->name, name);
    newNode->score = score;
    newNode->next = NULL;
    return newNode;
}

// 插入节点到链表末尾
void insertNode(struct Node **head, char *id, char *name, float score) {
    struct Node *newNode = createNode(id, name, score);
    
    if (*head == NULL) {
        *head = newNode;
    } else {
        struct Node *temp = *head;
        while (temp->next != NULL) {
            temp = temp->next;
        }
        temp->next = newNode;
    }
}

// 遍历链表
void traverseList(struct Node *head) {
    struct Node *temp = head;
    printf("\n%-10s %-10s %-6s\n", "学号", "姓名", "成绩");
    printf("----------------------------\n");
    
    while (temp != NULL) {
        printf("%-10s %-10s %-6.1f\n", temp->id, temp->name, temp->score);
        temp = temp->next;
    }
}

// 释放链表内存
void freeList(struct Node *head) {
    struct Node *temp;
    while (head != NULL) {
        temp = head;
        head = head->next;
        free(temp);
    }
}

int main() {
    struct Node *head = NULL;
    
    // 插入一些数据
    insertNode(&head, "1001", "张三", 85.5);
    insertNode(&head, "1002", "李四", 92.0);
    insertNode(&head, "1003", "王五", 78.5);
    
    // 遍历输出
    traverseList(head);
    
    // 释放内存
    freeList(head);
    
    return 0;
}

八、备考策略与技巧

8.1 考试重点与难点分析

必考知识点

  1. 基本语法:数据类型、运算符、表达式、输入输出
  2. 控制结构:if-else、switch、for、while、do-while
  3. 数组:一维数组、二维数组、字符串
  4. 函数:定义、调用、参数传递、递归
  5. 指针:指针变量、指针与数组、指针与函数、字符串指针
  6. 结构体:定义、使用、指针
  7. 文件操作:打开、关闭、读写

高频难点

  1. 指针:尤其是多级指针、函数指针、动态内存分配
  2. 字符串处理:字符串函数的使用和自定义实现
  3. 递归:理解递归过程和终止条件
  4. 内存管理:栈、堆的区别,内存泄漏

8.2 编程题答题技巧

步骤1:理解题意

  • 仔细阅读题目要求,明确输入输出
  • 注意边界条件和特殊情况

步骤2:设计算法

  • 画流程图或写伪代码
  • 确定使用哪种数据结构(数组、结构体等)

步骤3:编写代码

  • 先写框架,再填细节
  • 注意变量命名规范
  • 添加必要的注释

步骤4:调试测试

  • 用简单数据测试
  • 检查边界情况
  • 确保没有内存泄漏

常见错误避免

  1. 数组越界for (i = 0; i <= n; i++) 应为 i < n
  2. 忘记取地址scanf("%d", a) 应为 scanf("%d", &a)
  3. 字符串未结束:字符数组要留’\0’的空间
  4. 指针未初始化int *p; *p = 10; 危险!
  5. 内存泄漏:malloc后忘记free

8.3 上机考试注意事项

考试环境

  • 通常使用Turbo C 2.0或Visual C++ 6.0
  • 注意编译器版本差异
  • 熟悉调试工具的使用

时间分配

  • 基础题:30分钟
  • 编程题:60分钟
  • 检查:30分钟

代码规范

  • 缩进规范(4个空格或Tab)
  • 变量命名有意义
  • 适当添加注释
  • 每行一条语句

8.4 高效复习方法

1. 分类练习

  • 按知识点分类刷题
  • 先易后难,循序渐进

2. 错题本

  • 记录做错的题目
  • 分析错误原因
  • 定期复习

3. 模拟考试

  • 按考试时间模拟
  • 使用真题练习
  • 适应考试压力

4. 重点突破

  • 针对薄弱环节专项训练
  • 指针和结构体要多练
  • 字符串处理要熟练

5. 理解记忆

  • 不要死记硬背
  • 理解原理和过程
  • 通过实践加深理解

九、常见问题解答

Q1:指针和数组有什么区别? A:数组是连续内存空间,数组名是常量指针;指针是变量,可以改变指向。数组大小固定,指针可以指向动态内存。

Q2:为什么字符串处理要用字符数组? A:C语言没有内置字符串类型,字符串用以’\0’结尾的字符数组实现。字符指针可以指向字符串常量或动态分配的内存。

Q3:结构体和共用体有什么区别? A:结构体每个成员有独立内存空间;共用体所有成员共享同一段内存,同一时间只能使用一个成员。

Q4:如何避免内存泄漏? A:每次malloc/calloc后都要有对应的free;确保指针不丢失(如赋值前先free旧内存);程序结束时释放所有动态分配的内存。

Q5:递归效率低,为什么还要学? A:递归使代码简洁易懂,有些问题(如树遍历)用递归更自然。二级考试会考基本递归,掌握即可。

十、总结

C语言二级考试虽然有一定难度,但只要系统掌握基础语法,深入理解指针和数组,熟练运用函数和结构体,通过大量练习巩固知识,就一定能够顺利通过。

核心要点回顾

  1. 基础扎实:语法是根基,必须熟练
  2. 指针精通:理解内存模型,掌握指针运算
  3. 实践为王:多写代码,多调试
  4. 总结规律:归纳题型,掌握套路

希望本篇精讲能为您的备考之路提供有力支持。记住:编程不是看会的,是练会的!祝您考试顺利,一次通关!


附录:常用库函数速查

函数类别 函数名 功能
输入输出 printf/scanf 格式化输入输出
字符串 strlen/strcpy/strcat/strcmp 长度/复制/连接/比较
内存 malloc/calloc/free 动态内存分配释放
文件 fopen/fclose/fread/fwrite 文件操作
数学 sqrt/pow/abs 数学运算
字符 isalpha/isdigit/toupper 字符判断转换

考试必备头文件

#include <stdio.h>   // 输入输出
#include <stdlib.h>  // 系统函数、内存分配
#include <string.h>  // 字符串处理
#include <math.h>    // 数学函数
#include <ctype.h>   // 字符处理

最后提醒:二级考试是上机考试,一定要在电脑上实际操作,熟悉考试环境。纸上得来终觉浅,绝知此事要躬行!祝您成功!