C语言快速回顾(三)

前言

在Android音视频开发中,网上知识点过于零碎,自学起来难度非常大,不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》,结合我自己的工作学习经历,我准备写一个音视频系列blog。C/C++是音视频必备编程语言,我准备用几篇文章来快速回顾C语言。本文是音视频系列blog的其中一个, 对应的要学习的内容是:快速回顾C语言的结构、联合和枚举,指针的高级应用,声明,文件操作。


音视频系列blog

音视频系列blog: 点击此处跳转查看


目录


1 结构、联合和枚举

1.1 结构变量

C语言中的结构(Structure)是一种用户自定义的数据类型,它允许你将不同类型的数据项组合在一起,形成一个逻辑上相关的数据单元。结构可以包含多个成员(也称为字段),每个成员可以是不同的数据类型。结构在处理复杂数据时非常有用,可以使代码更清晰、模块化和可读性强。

以下是创建和使用结构变量的示例:

c 复制代码
#include <stdio.h>

// 定义一个结构
struct Person {
    char name[50];
    int age;
    float height;
};

int main() {
    // 声明一个结构变量
    struct Person person1;

    // 初始化结构变量的成员
    strcpy(person1.name, "John");
    person1.age = 30;
    person1.height = 175.5;

    // 访问结构变量的成员
    printf("Name: %s\n", person1.name);
    printf("Age: %d\n", person1.age);
    printf("Height: %.2f\n", person1.height);

    return 0;
}

在上述示例中,我们首先使用struct关键字定义了一个名为Person的结构,该结构有三个成员:name(一个字符串数组)、age(一个整数)和height(一个浮点数)。然后在main函数中,我们声明了一个名为person1的结构变量,并使用点操作符(.)来访问和设置结构的成员。

此外,C语言还支持通过typedef关键字来为结构类型定义别名,以提高代码的可读性:

c 复制代码
#include <stdio.h>

typedef struct {
    char name[50];
    int age;
    float height;
} Person;

int main() {
    Person person1;

    strcpy(person1.name, "John");
    person1.age = 30;
    person1.height = 175.5;

    printf("Name: %s\n", person1.name);
    printf("Age: %d\n", person1.age);
    printf("Height: %.2f\n", person1.height);

    return 0;
}

结构在C语言中用于表示和操作复杂的数据,如员工记录、图书信息、学生数据等。通过使用结构,可以将相关的数据项组织在一起,使代码更加结构化和易于维护。


1.2 结构类型

C语言中的结构(Structure)是一种用户自定义的数据类型,它允许你将不同类型的数据项组合在一起,形成一个逻辑上相关的数据单元。结构可以包含多个成员(也称为字段),每个成员可以是不同的数据类型。结构在处理复杂数据时非常有用,可以使代码更清晰、模块化和可读性强。

在C语言中,可以使用struct关键字来定义结构类型。结构的定义格式如下:

c 复制代码
struct 结构类型名 {
    数据类型 成员名1;
    数据类型 成员名2;
    // ... 其他成员
};

以下是一个示例,定义了一个名为Person的结构类型:

c 复制代码
struct Person {
    char name[50];
    int age;
    float height;
};

然后,可以通过该结构类型创建结构变量:

c 复制代码
struct Person person1;

为了提高可读性,通常会使用typedef关键字来为结构类型定义别名,以便在声明结构变量时更方便地使用别名。例如:

c 复制代码
typedef struct {
    char name[50];
    int age;
    float height;
} Person;

现在,可以直接使用Person作为结构类型名:

c 复制代码
Person person1;

通过结构类型,可以创建包含不同类型数据的复合数据类型,从而更有效地表示和操作复杂的数据。例如,可以用结构表示学生信息、图书记录、员工数据等。使用结构,可以将相关的数据项组织在一起,使代码更加模块化和易于维护。


1.3 嵌套的数组和结构

C语言允许在数组和结构中进行嵌套,即在数组或结构中嵌套其他数组或结构。这种嵌套可以用于表示更复杂的数据结构,如二维数组、多维数组、结构数组等。下面是关于嵌套数组和嵌套结构的一些示例:

嵌套数组示例:

  1. 二维数组: 二维数组是数组的数组,可以用于表示矩阵等数据结构。
c 复制代码
int matrix[3][3] = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};
  1. 数组中嵌套结构: 可以在数组中嵌套结构,用于表示一组具有相似属性的数据。
c 复制代码
struct Point {
    int x;
    int y;
};

struct Point points[5] = {
    {1, 2},
    {3, 4},
    {5, 6},
    {7, 8},
    {9, 10}
};

嵌套结构示例:

  1. 结构中嵌套结构: 可以在结构中嵌套其他结构,用于表示更复杂的数据。
c 复制代码
struct Date {
    int day;
    int month;
    int year;
};

struct Person {
    char name[50];
    int age;
    struct Date birthdate;
};

struct Person person1 = {
    "Alice",
    25,
    {15, 6, 1998}
};
  1. 结构数组中嵌套结构: 可以在结构数组中嵌套其他结构,用于表示多个具有复杂属性的实体。
c 复制代码
struct Book {
    char title[100];
    char author[50];
};

struct Library {
    struct Book books[100];
    int numBooks;
};

struct Library myLibrary = {
    { {"Book1", "Author1"}, {"Book2", "Author2"} },
    2
};

通过嵌套数组和结构,你可以创建更复杂的数据结构,用于表示各种实际问题中的数据关系。这种嵌套可以让你更有效地组织和操作数据,提高代码的可读性和维护性。需要注意在访问嵌套的成员时,需要使用适当的点操作符或下标操作符。


1.4 联合

在C语言中,联合(Union)是一种特殊的数据类型,它允许在同一内存位置存储不同的数据类型,但同一时间只能存储其中的一个成员。联合的大小等于其最大成员的大小。与结构不同,联合的各个成员共享同一块内存空间。

以下是联合的定义和用法示例:

c 复制代码
#include <stdio.h>

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

int main() {
    union Data data;
    
    data.i = 10;
    printf("data.i: %d\n", data.i);
    
    data.f = 3.14;
    printf("data.f: %.2f\n", data.f);
    
    strcpy(data.str, "Hello, Union!");
    printf("data.str: %s\n", data.str);

    printf("Size of union Data: %lu bytes\n", sizeof(data));

    return 0;
}

在上面的示例中,我们定义了一个名为Data的联合,它包含三个成员:i(整数类型)、f(浮点数类型)和str(字符串数组类型)。我们可以看到,联合的各个成员共享同一块内存,因此在修改一个成员的值后,其他成员的值也会受到影响。

需要注意以下几点:

  1. 联合只能同时存储一个成员的值,存储新的成员值会覆盖原有的值。
  2. 联合的大小等于其最大成员的大小。
  3. 联合的成员共享同一块内存,因此对一个成员的修改可能会影响其他成员。
  4. 联合常用于节省内存或在特定情况下实现多种数据类型的表示。

联合虽然有其独特的用途,但需要谨慎使用,特别是在不清楚各成员访问顺序的情况下。因为联合的成员共享内存,使用不当可能导致数据混乱或错误。 ​


1.5 枚举

C语言中,枚举(Enumeration)是一种用户自定义的数据类型,用于创建一组具有命名值的常量。枚举常常用于表示一组相关的符号常量,例如表示月份、星期几等。枚举的每个值称为枚举常量(Enumerator)。

以下是枚举的定义和用法示例:

c 复制代码
#include <stdio.h>

// 定义一个枚举类型
enum Weekday {
    Monday,    // 默认值为0
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
};

int main() {
    enum Weekday today = Wednesday;
    
    if (today == Wednesday) {
        printf("Today is Wednesday.\n");
    } else {
        printf("Today is not Wednesday.\n");
    }

    return 0;
}

在上面的示例中,我们定义了一个名为Weekday的枚举类型,其中包含了一组星期几的枚举常量。默认情况下,枚举常量的值从0开始递增,但可以通过显式赋值来修改枚举常量的值。在main函数中,我们声明了一个名为today的枚举变量,并将其赋值为Wednesday,然后通过条件判断来判断今天是否为星期三。

需要注意以下几点:

  1. 枚举类型是一个自定义的数据类型,它可以包含一组常量值。
  2. 枚举常量的默认值从0开始递增,但你也可以显式地为枚举常量赋值。
  3. 枚举常量的作用域在整个枚举类型内部。
  4. 枚举变量可以在声明时指定类型名,也可以直接使用枚举常量。

枚举类型在C语言中非常有用,可以使代码更加可读性强,同时提供了一种更有意义的方式来表示一组相关的常量值。


2 指针的高级应用

2.1 动态存储分配

C语言中的动态存储分配是一种高级的指针应用,它允许在程序运行时动态地分配和释放内存空间,以便灵活地管理数据结构和资源。这对于处理未知数量的数据或避免静态内存分配的限制非常有用。在动态存储分配中,主要使用了以下几个函数:malloc()calloc()realloc()free()

  1. malloc() 用于分配一块指定大小的内存空间。它接受一个参数,表示要分配的字节数,返回分配的内存的首地址。如果分配失败,返回NULL

    c 复制代码
    int *ptr;
    ptr = (int *)malloc(5 * sizeof(int)); // 分配包含5个整数的内存空间
    if (ptr == NULL) {
        printf("Memory allocation failed.\n");
        return 1;
    }
  2. calloc() 用于分配一块指定数量和大小的连续内存空间,初始化为零。它接受两个参数,表示元素的数量和每个元素的大小,返回分配的内存的首地址。

    c 复制代码
    int *ptr;
    ptr = (int *)calloc(5, sizeof(int)); // 分配包含5个整数的内存空间并初始化为零
    if (ptr == NULL) {
        printf("Memory allocation failed.\n");
        return 1;
    }
  3. realloc() 用于重新分配已分配内存的大小。它接受两个参数,第一个参数是之前分配的内存的指针,第二个参数是新的大小。它可能会在原地重新分配或者将数据移到新的内存位置。

    c 复制代码
    int *newPtr;
    newPtr = (int *)realloc(ptr, 10 * sizeof(int)); // 将之前分配的内存重新分配为包含10个整数的内存空间
    if (newPtr == NULL) {
        printf("Memory reallocation failed.\n");
        free(ptr);
        return 1;
    } else {
        ptr = newPtr;
    }
  4. free() 用于释放之前分配的内存空间,以便将其返回给系统。释放内存后,指针变量仍然保存着已释放的内存地址,但访问它将产生未定义的行为。

    c 复制代码
    free(ptr); // 释放之前分配的内存空间

使用动态存储分配,你可以根据需要在程序运行时分配和释放内存,提高内存的使用效率和灵活性。但要小心内存泄漏和悬挂指针等问题,确保在适当的时候释放已分配的内存。


2.2 动态分配字符串

在C语言中,使用指针和动态内存分配可以有效地处理字符串,尤其是在不知道字符串长度的情况下。动态分配字符串内存允许你根据实际需要分配和释放内存,从而更灵活地处理字符串操作。

以下是动态分配字符串的示例:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    char *name;

    // 动态分配内存以容纳字符串
    name = (char *)malloc(50 * sizeof(char));
    if (name == NULL) {
        printf("Memory allocation failed.\n");
        return 1;
    }

    // 输入字符串
    printf("Enter your name: ");
    scanf("%s", name);

    // 输出字符串
    printf("Hello, %s!\n", name);

    // 释放动态分配的内存
    free(name);

    return 0;
}

在上述示例中,我们首先使用malloc()函数动态分配了一块内存以容纳字符串,然后使用scanf()函数输入字符串,并使用printf()函数输出字符串。最后,我们使用free()函数释放动态分配的内存。

需要注意以下几点:

  1. 在使用malloc()分配字符串内存时,需要为字符串长度留出足够的空间,通常是字符串长度加上一个空字符的大小(\0,表示字符串的结尾)。
  2. 动态分配的字符串内存可以使用指针来访问和操作。
  3. 一定要在使用完字符串后使用free()函数释放动态分配的内存,以避免内存泄漏。
  4. 使用动态分配字符串内存时,要确保不会访问未初始化或未分配的内存区域,以避免未定义的行为。

动态分配字符串内存允许你根据实际输入和操作的需要,适应不同长度的字符串,提高了灵活性和内存的有效使用。


2.3 动态分配数组

在C语言中,通过指针和动态内存分配,可以创建动态数组,即在程序运行时根据需要分配和释放内存来存储数组元素。这使得你可以灵活地处理不确定大小的数组,避免了静态数组的大小限制。

以下是动态分配数组的示例:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *numbers;
    int size, i;

    // 输入数组大小
    printf("Enter the size of the array: ");
    scanf("%d", &size);

    // 动态分配内存以容纳数组
    numbers = (int *)malloc(size * sizeof(int));
    if (numbers == NULL) {
        printf("Memory allocation failed.\n");
        return 1;
    }

    // 输入数组元素
    printf("Enter %d integers:\n", size);
    for (i = 0; i < size; i++) {
        scanf("%d", &numbers[i]);
    }

    // 输出数组元素
    printf("Array elements: ");
    for (i = 0; i < size; i++) {
        printf("%d ", numbers[i]);
    }
    printf("\n");

    // 释放动态分配的内存
    free(numbers);

    return 0;
}

在上述示例中,我们首先输入了数组的大小,然后使用malloc()函数动态分配内存以容纳数组元素。随后,我们输入数组元素,输出数组元素,并使用free()函数释放动态分配的内存。

需要注意以下几点:

  1. 动态分配数组内存时,要根据数组元素的类型和数量来分配足够的内存空间。
  2. 动态数组的元素可以使用指针进行访问和操作。
  3. 动态分配的数组内存应在使用完毕后使用free()函数释放,以避免内存泄漏。
  4. 动态数组可以根据实际需要分配不同大小的内存,提高了内存的利用率和灵活性。

动态分配数组内存允许你根据实际的输入和操作,灵活地创建不同大小的数组,适应不同场景下的需求。


2.4 释放存储空间

在C语言中,手动释放已分配的内存空间是十分重要的,以避免内存泄漏(未释放的内存)和资源浪费。当你使用动态内存分配函数(如malloc()calloc()realloc())分配内存后,务必在不再需要使用这块内存时通过调用free()函数来释放它。

以下是释放存储空间的示例:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *numbers;

    // 分配内存
    numbers = (int *)malloc(5 * sizeof(int));
    if (numbers == NULL) {
        printf("Memory allocation failed.\n");
        return 1;
    }

    // 使用内存
    for (int i = 0; i < 5; i++) {
        numbers[i] = i * 10;
    }

    // 释放内存
    free(numbers);

    return 0;
}

在上述示例中,我们使用malloc()函数分配了一块内存,然后使用循环为数组赋值。最后,在程序结束前,我们使用free()函数释放了已分配的内存。

需要注意以下几点:

  1. free()函数只能用于释放通过动态内存分配函数(如malloc()calloc()realloc())分配的内存。使用free()来释放静态或自动分配的内存,或释放同一块内存多次,都会导致未定义的行为。
  2. 释放内存后,不要再尝试使用指向已释放内存的指针,这可能会导致悬挂指针(Dangling Pointer)问题。
  3. 内存泄漏是指在程序运行时未释放不再使用的内存,这会导致内存消耗不断增加。务必确保在不再需要使用内存时释放它,以保持程序的健壮性和性能。

有效地释放存储空间是编程中的一个重要方面,帮助你有效管理系统资源,避免内存泄漏问题,以及提高程序的稳定性和性能。


2.5 受限指针

C语言中的受限指针(Restricted Pointers)是C99标准引入的一种指针类型,用于表示指针的使用范围和行为约束。受限指针可以帮助编译器优化代码,并增强代码的可读性和可维护性。它们通常在指针的生命周期中具有特定的限制和用法。

使用受限指针可以告诉编译器,某个指针变量仅在某些限定的情况下才会被修改,这有助于编译器进行更多的优化。受限指针通常用于在编译期间检查指针的使用情况,从而提高代码的安全性和可靠性。

以下是受限指针的一个简单示例:

c 复制代码
#include <stdio.h>

void increment(int *restrict ptr) {
    *ptr += 1;
}

int main() {
    int num = 10;

    increment(&num);
    printf("num: %d\n", num);

    return 0;
}

在上面的示例中,increment函数使用了受限指针。restrict关键字告诉编译器,ptr指针是一个受限指针,函数内部不会修改除*ptr之外的任何内存。这使得编译器可以进行更多的优化,因为它可以假设其他指针不会指向*ptr指向的内存位置。这有助于提高代码的性能。

需要注意以下几点:

  1. 受限指针只能用于指针函数的形参,用于约束指针的使用范围。
  2. restrict关键字只在C99标准及以后版本中有效。
  3. 在使用受限指针时,务必确保不会在函数中修改其他指针所指向的内存,以充分发挥优化效果。

虽然受限指针可以提高代码性能和优化,但在使用时需要注意其约束和限制,以避免产生不正确的结果。


2.6 灵活数组成员

C语言中的灵活数组成员(Flexible Array Members)是C99标准引入的特性,允许在结构中定义一个大小可变的数组作为最后一个成员。这使得你可以在结构中创建具有不定大小的数组,从而在处理某些数据结构时更加灵活。

以下是灵活数组成员的一个简单示例:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

struct DynamicArray {
    int length;
    int data[]; // 灵活数组成员,大小可变
};

int main() {
    int size = 5;
    struct DynamicArray *array = malloc(sizeof(struct DynamicArray) + size * sizeof(int));
    
    if (array == NULL) {
        printf("Memory allocation failed.\n");
        return 1;
    }

    array->length = size;
    for (int i = 0; i < size; i++) {
        array->data[i] = i * 10;
    }

    printf("Array elements: ");
    for (int i = 0; i < array->length; i++) {
        printf("%d ", array->data[i]);
    }
    printf("\n");

    free(array);

    return 0;
}

在上面的示例中,我们定义了一个包含灵活数组成员的结构DynamicArray。在结构定义中,data[]是一个没有指定大小的数组。然后,我们使用malloc()函数分配了足够的内存以容纳整个结构和数组元素。通过这种方式,我们可以在不同场景下创建具有不同大小的灵活数组。

需要注意以下几点:

  1. 灵活数组成员只能作为结构的最后一个成员,且结构定义中只能有一个灵活数组成员。
  2. 灵活数组成员的大小是在运行时根据需要进行动态分配的。
  3. 在使用灵活数组成员时,需要分配足够的内存以容纳整个结构和数组元素,以及结构中的其他成员。
  4. 释放灵活数组成员时,需要使用free()函数释放整个结构的内存。

灵活数组成员允许你在结构中创建大小可变的数组,适应不同大小的数据,提高了数据结构的灵活性和可用性。


3 声明

3.1 声明的语法

在C语言中,声明用于告诉编译器有关变量、函数或类型的信息,从而在代码中引入这些实体而不需要实际定义它们。声明通常用于头文件中,以便在多个源文件中共享信息。以下是C语言中一些常见声明的语法示例:

  1. 变量声明: 用于声明变量的名称和类型,但不分配内存。
c 复制代码
// 声明整数变量
int count;

// 声明字符变量
char letter;

// 声明指针变量
float *ptr;
  1. 函数声明: 用于声明函数的原型,包括函数名称、参数列表和返回类型。
c 复制代码
// 声明函数原型
int add(int a, int b);

// 声明不带参数的函数原型
void greet(void);
  1. 数组声明: 用于声明数组的名称和类型。
c 复制代码
// 声明整数数组
int numbers[10];

// 声明字符数组(字符串)
char message[50];
  1. 类型声明: 用于为用户自定义类型(结构、枚举等)创建声明。
c 复制代码
// 声明结构类型
struct Point {
    int x;
    int y;
};

// 声明枚举类型
enum Color { Red, Green, Blue };
  1. 外部变量声明: 用于在一个源文件中引用另一个源文件中定义的外部变量。
c 复制代码
// 在当前文件中声明外部变量
extern int globalVar;

需要注意以下几点:

  • 声明不分配内存或存储空间,它只是为编译器提供信息以便正确解析代码。
  • 声明可以在任何地方进行,但通常最好将它们放在头文件中,以便在多个源文件中共享。
  • 变量和函数的定义包含实际分配内存或实现代码的部分,而声明只是提供了名称和类型信息。
  • 在函数原型中,可以省略参数名称,只保留参数类型,例如 int add(int, int);
  • 在不同的情况下,声明的语法可能会有所不同,根据上下文合理使用。

在大型程序中,正确使用声明可以帮助减少编译错误,提高代码的可读性和可维护性。


3.2 存储类型

C语言中的存储类型(Storage Class)用于描述变量、函数和数据对象的生命周期、作用域和可见性等特性。C语言提供了多种存储类型,以适应不同的编程需求。以下是C语言中常见的存储类型:

  1. 自动存储类(Automatic Storage Class): 默认的存储类,用于描述局部变量。它在函数内部声明的局部变量的默认存储类。当程序控制流离开变量的作用域时,自动变量会被销毁。
c 复制代码
void function() {
    int a; // 自动存储类
}
  1. 寄存器存储类(Register Storage Class): 请求编译器将变量存储在寄存器中,以便提高访问速度。编译器可以选择将部分变量存储在寄存器中。但是,由于寄存器数量有限,不一定所有请求都会被满足。
c 复制代码
register int x; // 寄存器存储类
  1. 静态存储类(Static Storage Class): 用于描述具有持久性的局部变量,它在程序的整个生命周期内保持其值。静态变量在首次执行它们的声明语句时初始化,并在整个程序执行过程中保持值。
c 复制代码
static int count = 0; // 静态存储类
  1. 外部存储类(External Storage Class): 用于描述全局变量和外部函数。外部变量在多个文件之间共享,它们可以在一个文件中定义,在其他文件中声明,以便在整个程序中使用。
c 复制代码
extern int globalVar; // 外部存储类
  1. 线程存储类(Thread Storage Class): 用于多线程程序,指示变量在每个线程中具有单独的存储。
c 复制代码
_Thread_local int data; // 线程存储类
  1. 空存储类(No Storage Class): 用于描述类型,但不指定存储类。通常在类型声明中使用。
c 复制代码
typedef int Age; // 空存储类,用于类型别名

不同存储类的使用取决于程序的需求。理解这些存储类的特性和用法将有助于正确地管理变量的生命周期、作用域和可见性,以及优化代码的性能和可读性。


3.3 类型限定符

C语言中的类型限定符用于修改变量的存储类别和访问权限,以及指定变量的性质和使用方式。C语言提供了以下几种常见的类型限定符:

  1. const: const 用于声明一个只读变量,表示变量的值不能被修改。
c 复制代码
const int x = 10; // 声明一个只读整数变量
  1. volatile: volatile 用于声明一个变量是易变的,表示变量的值可能会在未经预期的情况下被修改,例如在中断服务程序中。
c 复制代码
volatile int sensorValue; // 声明一个易变的整数变量
  1. restrict: restrict 用于声明指针,表示指针是唯一引用对象的指针,编译器可以对这种指针进行更好的优化。
c 复制代码
void updateArray(int *restrict dest, const int *restrict src, size_t n);
  1. _Atomic: _Atomic 用于声明一个原子类型的变量,支持多线程并发访问时的原子操作。
c 复制代码
_Atomic int counter; // 声明一个原子整数变量
  1. _Thread_local: _Thread_local 用于声明一个线程本地变量,使变量在每个线程中具有单独的副本。
c 复制代码
_Thread_local int threadLocalData; // 声明一个线程本地整数变量

这些类型限定符可以根据程序的需求来使用,以改变变量的特性、可见性和存储行为。例如,const 可用于确保变量的值不会被意外修改,volatile 可用于处理不受控制的变量访问,restrict 可以帮助编译器进行更好的优化等。理解并正确使用这些类型限定符可以提高代码的安全性、可靠性和性能。


3.4 声明符

C语言中的声明符(Declarator)用于描述变量或函数的名称以及与之相关的一些属性。声明符是用于创建变量或函数的声明的一部分,它告诉编译器有关实体的类型、存储类和其他信息。以下是一些常见的声明符形式:

  1. 直接声明符(Direct Declarator): 用于声明变量、数组和函数,直接指定名称和类型。

    • 变量声明:type variableName;
    c 复制代码
    int x;
    • 数组声明:type arrayName[size];
    c 复制代码
    int numbers[10];
    • 函数声明:returnType functionName(parameters);
    c 复制代码
    int add(int a, int b);
  2. 指针声明符(Pointer Declarator): 用于声明指向其他类型的指针。

    c 复制代码
    int *ptr; // 声明一个指向整数的指针
  3. 数组声明符(Array Declarator): 用于声明数组,包括数组的维度。

    c 复制代码
    int matrix[3][3]; // 声明一个3x3的整数数组
  4. 函数声明符(Function Declarator): 用于声明函数,包括参数列表和返回类型。

    c 复制代码
    int (*funcPtr)(int, int); // 声明一个函数指针
  5. 函数指针声明符(Function Pointer Declarator): 用于声明指向函数的指针。

    c 复制代码
    int (*operation)(int, int); // 声明一个指向函数的函数指针
  6. 类型限定符声明符(Type Qualifier Declarator): 用于声明带有类型限定符的变量。

    c 复制代码
    const int *readOnlyPtr; // 声明一个指向只读整数的指针
  7. 类型限定符函数声明符(Type Qualifier Function Declarator): 用于声明带有类型限定符的函数。

    c 复制代码
    int sum(const int array[], int size); // 声明一个带有只读参数的函数

声明符的形式因所声明实体的类型和属性而异。在C语言中,声明符是用于创建变量和函数声明的重要组成部分,帮助编译器正确解析和生成代码。


3.5 初始化式

C语言中的初始化式(Initializer)用于在声明变量时为其赋初值。初始化式可以用于各种类型的变量,包括基本数据类型、数组、结构和联合等。以下是一些常见的初始化式形式:

  1. 基本数据类型的初始化:

    • 初始化整数变量:
    c 复制代码
    int x = 10;
    • 初始化浮点数变量:
    c 复制代码
    float pi = 3.14159;
    • 初始化字符变量:
    c 复制代码
    char letter = 'A';
  2. 数组的初始化:

    • 初始化整数数组:
    c 复制代码
    int numbers[5] = {1, 2, 3, 4, 5};
    • 部分初始化数组(剩余元素会被自动初始化为0):
    c 复制代码
    int partialInit[10] = {1, 2, 3};
  3. 结构的初始化:

    • 初始化结构变量:
    c 复制代码
    struct Point {
        int x;
        int y;
    };
    
    struct Point p = {10, 20};
  4. 联合的初始化:

    • 初始化联合变量:
    c 复制代码
    union Data {
        int num;
        float pi;
    };
    
    union Data d = {3.14159};
  5. 指针的初始化:

    • 初始化指向整数的指针:
    c 复制代码
    int x = 10;
    int *ptr = &x;
    • 初始化指向字符串的指针:
    c 复制代码
    char *message = "Hello, world!";
  6. 复合初始化: 可以通过嵌套使用大括号进行复合初始化,用于初始化数组和结构中的嵌套元素。

    c 复制代码
    struct Rectangle {
        struct Point topLeft;
        struct Point bottomRight;
    };
    
    struct Rectangle rect = {{0, 0}, {100, 200}};

需要注意以下几点:

  • 初始化式是在变量声明时提供初值的一种方式,用于初始化变量的内容。
  • 对于未显式初始化的变量,C语言会自动将其初始化为默认值(例如,整数为0,浮点数为0.0,指针为NULL,字符为'\0')。
  • 复合初始化对于数组和结构非常有用,可以在一条语句中初始化多个元素。
  • 初始化式可以使用在各种不同类型的变量声明中,包括局部变量和全局变量。

正确的初始化可以帮助你避免使用未定义的内存内容,并提供初始值以便在变量声明后立即使用。


3.6 内联函数

C语言内联函数(Inline Function)是一种编译器的优化手段,用于在编译时将函数的调用代码替换为函数体的实际代码,从而减少函数调用的开销。内联函数通常用于需要频繁调用的短小函数,以提高程序的性能。内联函数的使用方式和语法如下:

  1. 内联函数声明: 在函数声明前加上关键字 inline
c 复制代码
inline int add(int a, int b) {
    return a + b;
}
  1. 内联函数调用: 编译器会尝试将内联函数的调用替换为函数体。
c 复制代码
int result = add(5, 3); // 可能被替换为 int result = 5 + 3;

需要注意以下几点:

  • 内联函数通常适用于简单的、执行时间短的函数,例如数学运算、比较操作等。
  • 内联函数的调用可能会增加代码的大小,因为函数体会被复制到每个调用点。
  • 编译器会根据函数的复杂性和调用频率来决定是否真正内联函数。
  • 使用内联函数的前提是:函数的定义必须在调用点之前,以便编译器知道函数的实际代码。
  • 内联函数不一定比普通函数更快,它的效果取决于具体情况。在一些情况下,编译器会自动进行优化,而不需要显式使用内联函数。

需要注意的是,内联函数只是对编译器的建议,编译器可以选择是否真正内联函数。使用内联函数时,应当在性能和代码大小之间进行权衡,适用于需要频繁调用的短小函数,以提高程序的性能。


4 输入输出

4.1 流

4.1.1 文件指针

在C语言中,文件指针(File Pointer)是一种用于访问文件的数据结构。文件指针可以用于在文件中定位并操作数据,包括读取和写入。使用文件指针,你可以打开文件、定位文件中的特定位置,以及进行文件的读写操作。

C语言标准库提供了一组函数来处理文件指针的操作,其中最常用的函数包括:

  1. FILE 数据类型: C语言中使用 FILE 数据类型来表示文件指针。它是一个不透明的结构体类型,用于管理文件的信息。

  2. fopen 函数: 用于打开文件,并返回一个指向该文件的指针。它接受两个参数,文件名和打开模式。

    c 复制代码
    FILE *fptr = fopen("myfile.txt", "r"); // 以只读模式打开文件
  3. fclose 函数: 用于关闭已打开的文件。关闭文件后,对文件的读写操作将不再有效。

    c 复制代码
    fclose(fptr); // 关闭文件指针
  4. 读取操作: C语言提供了多个函数用于从文件中读取数据。

    • fgetc: 从文件中读取一个字符。
    • fgets: 从文件中读取一行字符串。
    • fread: 从文件中读取指定数量的字节。
  5. 写入操作: C语言提供了多个函数用于向文件中写入数据。

    • fputc: 将一个字符写入文件。
    • fputs: 将一个字符串写入文件。
    • fwrite: 将指定数量的字节写入文件。
  6. 定位函数: 用于在文件中定位文件指针的位置。

    • fseek: 移动文件指针到指定的位置。
    • ftell: 返回文件指针的当前位置。
  7. 错误处理: C语言提供了 feofferror 函数用于检测文件读写的错误。

文件指针的使用可以帮助你读取和写入文件中的数据,进行文件的操作和管理。在操作文件时,务必确保正确地打开和关闭文件,以避免资源泄漏和数据丢失。


4.1.2 标准流和重定向

C语言标准流(Standard Streams)是在C语言中用于输入和输出的三个默认流,它们分别是标准输入流(stdin)、标准输出流(stdout)和标准错误流(stderr)。这些流与键盘、屏幕和错误信息相关联,允许程序与用户交互并进行输入输出操作。标准流通常通过文件指针进行访问。

重定向是一种将标准流与其他文件或设备连接的机制。通过重定向,你可以将标准输入流、标准输出流或标准错误流重定向到文件中,从而实现文件的输入输出,而不仅限于键盘和屏幕。以下是有关C语言标准流和重定向的一些重要信息:

  1. 标准输入流(stdin): 默认情况下关联于键盘输入,可以通过重定向将其关联到文件。

  2. 标准输出流(stdout): 默认情况下关联于屏幕输出,可以通过重定向将其关联到文件。

  3. 标准错误流(stderr): 默认情况下关联于屏幕输出,用于显示错误信息,可以通过重定向将其关联到文件。

  4. 重定向操作符: 在命令行中使用特定的符号来进行重定向操作。

    • >: 用于将标准输出重定向到文件中。
    • >>: 用于将标准输出追加到文件中。
    • <: 用于将文件内容作为标准输入。
    • 2>: 用于将标准错误重定向到文件中。
    • 2>>: 用于将标准错误追加到文件中。

示例:

sh 复制代码
./myprogram > output.txt  # 将程序的标准输出重定向到output.txt文件
./myprogram < input.txt   # 将input.txt文件内容作为程序的标准输入
./myprogram 2> error.txt  # 将程序的标准错误重定向到error.txt文件
  1. 使用 freopen 函数进行重定向: 在C语言程序中,可以使用 freopen 函数来实现重定向操作。
c 复制代码
#include <stdio.h>

int main() {
    freopen("output.txt", "w", stdout); // 将标准输出重定向到output.txt文件

    printf("This is redirected output.\n");

    return 0;
}

通过重定向,你可以将程序的输入输出与文件关联,实现更灵活的数据处理和文件操作。


4.1.3 文本文件与二进制文件

C语言中的文件可以分为两种主要类型:文本文件和二进制文件。这两种文件类型的区别在于它们存储和表示数据的方式。

  1. 文本文件: 文本文件是以文本形式存储的文件,其中的数据以可读的字符表示,通常使用ASCII编码。文本文件包含普通文本字符,例如字母、数字、标点符号等。文本文件可以被普通文本编辑器打开和编辑。

    示例:textfile.txt

    arduino 复制代码
    Hello, this is a text file.
    Line 2 of the text file.
  2. 二进制文件: 二进制文件是以二进制形式存储的文件,其中的数据以字节表示,可以包含任何类型的数据,例如图像、音频、视频等。二进制文件的内容不是人类可读的,需要特定的程序来解析和处理。二进制文件通常更紧凑,适用于存储和传输非文本数据。

    示例:binaryfile.bin

    01010100 01101000 01101001 01110011 00100000 01101001 01110011 00100000 01100001 00100000 01100010 01101001 01101110 01100001 01110010 01111001 00100000 01100101 01111010 01100001 01101101 01110000 01101100 01100101 00101110
    

在C语言中,你可以使用标准库函数来读写文本文件和二进制文件。

  • 读写文本文件:

    c 复制代码
    FILE *file = fopen("textfile.txt", "r"); // 打开文本文件进行读取
    // 使用 fscanf 读取文本文件内容
    fclose(file);
  • 读写二进制文件:

    c 复制代码
    FILE *file = fopen("binaryfile.bin", "rb"); // 打开二进制文件进行读取
    // 使用 fread 读取二进制文件内容
    fclose(file);
  • 写入文本文件:

    c 复制代码
    FILE *file = fopen("textfile.txt", "w"); // 打开文本文件进行写入
    // 使用 fprintf 写入文本内容
    fclose(file);
  • 写入二进制文件:

    c 复制代码
    FILE *file = fopen("binaryfile.bin", "wb"); // 打开二进制文件进行写入
    // 使用 fwrite 写入二进制内容
    fclose(file);

可以根据需求选择使用文本文件还是二进制文件来存储数据,具体的选择取决于所处理数据的性质以及需要。


4.2 文件操作

4.2.1 打开文件

在C语言中,你可以使用标准库函数来打开文件,以便进行读取和写入操作。主要用到的函数是 fopen 函数。下面是关于如何在C语言中打开文件的一些重要信息:

c 复制代码
#include <stdio.h>

int main() {
    FILE *file; // 声明一个文件指针

    // 打开文件进行读取,"r" 表示只读模式
    file = fopen("myfile.txt", "r");
    if (file == NULL) {
        printf("File could not be opened.\n");
        return 1;
    }

    // 文件读取操作

    // 关闭文件
    fclose(file);

    return 0;
}

在上面的示例中,我们使用了 fopen 函数来打开一个名为 "myfile.txt" 的文件,并指定了只读模式 "r"。如果文件打开失败,fopen 函数将返回一个空指针(NULL)。在读写操作完成后,使用 fclose 函数关闭文件。

你可以使用不同的模式来打开文件,以适应不同的操作需求:

  • "r": 只读模式(Read mode),文件必须存在。
  • "w": 写入模式(Write mode),如果文件存在则清空内容,如果不存在则创建文件。
  • "a": 追加模式(Append mode),如果文件存在则在末尾追加数据,如果不存在则创建文件。
  • "rb": 二进制读取模式(Binary read mode)。
  • "wb": 二进制写入模式(Binary write mode)。
  • "ab": 二进制追加模式(Binary append mode)。

在打开文件后,你可以使用其他标准库函数(如 fscanffgetsfreadfprintffputs 等)来进行读写操作。完成文件操作后,务必使用 fclose 函数关闭文件,以确保资源正确释放。

需要注意的是,文件操作可能会受到操作系统权限的限制,确保你有足够的权限来读写目标文件。


4.2.2 模式

在C语言中,文件操作通过使用标准库函数来实现。这些函数位于 <stdio.h> 头文件中,提供了多种文件操作模式。以下是常用的文件操作模式及其说明:

  1. "r" (Read) : 以只读模式打开文件。如果文件不存在,打开操作将失败;如果文件存在,数据可以被读取。
c 复制代码
FILE *file = fopen("filename.txt", "r");
  1. "w" (Write) : 以写入模式打开文件。如果文件不存在,将创建一个新文件;如果文件存在,文件中的内容将被清空。
c 复制代码
FILE *file = fopen("filename.txt", "w");
  1. "a" (Append) : 以追加模式打开文件。如果文件不存在,将创建一个新文件;如果文件存在,数据将被追加到文件末尾。
c 复制代码
FILE *file = fopen("filename.txt", "a");
  1. "rb" (Read Binary) : 以二进制只读模式打开文件。与 "r" 类似,但以二进制模式处理文件。
c 复制代码
FILE *file = fopen("filename.bin", "rb");
  1. "wb" (Write Binary) : 以二进制写入模式打开文件。与 "w" 类似,但以二进制模式处理文件。
c 复制代码
FILE *file = fopen("filename.bin", "wb");
  1. "ab" (Append Binary) : 以二进制追加模式打开文件。与 "a" 类似,但以二进制模式处理文件。
c 复制代码
FILE *file = fopen("filename.bin", "ab");
  1. "r+" (Read and Write) : 以读写模式打开文件。文件必须存在,可以读取和写入数据。
c 复制代码
FILE *file = fopen("filename.txt", "r+");
  1. "w+" (Write and Read) : 以读写模式打开文件。如果文件不存在,将创建一个新文件;如果文件存在,文件中的内容将被清空,可以读取和写入数据。
c 复制代码
FILE *file = fopen("filename.txt", "w+");
  1. "a+" (Append and Read) : 以读写追加模式打开文件。如果文件不存在,将创建一个新文件;如果文件存在,数据将被追加到文件末尾,可以读取和写入数据。
c 复制代码
FILE *file = fopen("filename.txt", "a+");

这些模式提供了不同的文件操作选项,您可以根据需要选择合适的模式来操作文件。注意,在操作文件后,应该使用 fclose() 函数来关闭文件,以释放资源。


4.2.3 关闭文件

在C语言中,要关闭已打开的文件,您需要使用 fclose() 函数。这个函数接受一个文件指针作为参数,然后将该文件指针所表示的文件关闭,释放与该文件关联的资源。

以下是 fclose() 函数的使用方法:

c 复制代码
#include <stdio.h>

int main() {
    FILE *file = fopen("filename.txt", "r");
    
    if (file == NULL) {
        printf("Failed to open the file.\n");
        return 1;
    }
    
    // 在这里进行文件读写操作
    
    // 关闭文件
    fclose(file);
    
    return 0;
}

请注意以下几点:

  1. 在打开文件后,进行了文件读写等操作后,应该使用 fclose() 来关闭文件,以确保资源得到释放。
  2. 在关闭文件之后,不应再尝试使用已关闭的文件指针进行读写操作,否则可能会导致未定义的行为。
  3. 检查 fopen() 是否成功打开文件。如果文件打开失败(例如,文件不存在或没有读取权限),文件指针将为 NULL,在尝试关闭文件之前应该进行错误检查。
  4. 虽然在程序终止时操作系统会关闭已打开的文件,但是最好的实践是在您不再需要文件时显式地使用 fclose() 函数关闭文件。

如果您在程序执行期间打开了多个文件,每个文件都需要使用 fclose() 分别关闭。


4.2.4 为打开的流附加文件

在C语言中,您可以使用不同的文件打开模式来附加内容到已经存在的文件中。为了在已打开的流(文件指针)中附加内容,您可以使用以下打开模式:

  1. "a" (Append) : 使用这个模式打开文件,会将数据追加到文件的末尾。如果文件不存在,将创建一个新文件。
c 复制代码
FILE *file = fopen("filename.txt", "a");
if (file == NULL) {
    printf("Failed to open the file.\n");
    return 1;
}
// 写入或追加数据到文件
fprintf(file, "This is new content.\n");
// 关闭文件
fclose(file);
  1. "ab" (Append Binary) : 以二进制追加模式打开文件。与 "a" 类似,但以二进制模式处理文件。
c 复制代码
FILE *file = fopen("filename.bin", "ab");
if (file == NULL) {
    printf("Failed to open the file.\n");
    return 1;
}
// 写入或追加数据到文件
fwrite(data, sizeof(data[0]), sizeof(data) / sizeof(data[0]), file);
// 关闭文件
fclose(file);
  1. "a+" (Append and Read) : 以读写追加模式打开文件。如果文件不存在,将创建一个新文件。可以在文件末尾追加数据,并在需要时进行读取。
c 复制代码
FILE *file = fopen("filename.txt", "a+");
if (file == NULL) {
    printf("Failed to open the file.\n");
    return 1;
}
// 写入或追加数据到文件
fprintf(file, "This is new content.\n");
// 读取数据
char buffer[100];
fseek(file, 0, SEEK_SET); // 将文件指针设置回文件开头
while (fgets(buffer, sizeof(buffer), file) != NULL) {
    printf("%s", buffer);
}
// 关闭文件
fclose(file);

以上示例中,使用不同的打开模式来附加内容到文件中。请根据您的实际需求选择适当的模式。在操作文件后,不要忘记使用 fclose() 函数来关闭文件。


4.2.5 从命令行获取文件名

在C语言中,您可以使用 main() 函数的参数来从命令行获取文件名。命令行参数以字符串数组的形式传递给 main() 函数,其中第一个参数是程序的名称,后续参数是由空格分隔的命令行参数。

以下是如何从命令行获取文件名的示例代码:

c 复制代码
#include <stdio.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: %s <filename>\n", argv[0]);
        return 1;
    }
    
    char *filename = argv[1];
    
    printf("File name: %s\n", filename);
    
    // 在这里可以使用 filename 进行文件操作
    
    return 0;
}

在这个示例中,argc 是命令行参数的数量,argv 是一个指向指针的数组,每个指针指向一个命令行参数字符串。通常,argv[0] 是程序的名称,argv[1] 是第一个命令行参数,即文件名。

运行这个程序时,您可以在命令行中传递文件名作为参数,例如:

sh 复制代码
./program_name filename.txt

这将输出:

arduino 复制代码
File name: filename.txt

然后,您可以使用 filename 变量来进行文件操作,如打开、读取或写入文件。请注意,在实际使用中,应该添加适当的错误检查和文件操作代码。


4.2.6 临时文件

C语言中可以使用标准库函数来创建临时文件,这在需要在程序运行时临时存储数据时非常有用。临时文件通常在程序结束后会被自动删除,因此适合用于存储临时性的数据或中间结果。

您可以使用 tmpfile() 函数来创建一个临时文件,并返回一个指向该临时文件的文件指针。下面是一个示例:

c 复制代码
#include <stdio.h>

int main() {
    FILE *tempFile = tmpfile();

    if (tempFile == NULL) {
        printf("Failed to create temporary file.\n");
        return 1;
    }

    fprintf(tempFile, "This is temporary data.\n");

    // 这里可以进行其他操作,如读取或写入数据

    // 关闭临时文件,文件会在程序结束时自动删除
    fclose(tempFile);

    return 0;
}

在这个示例中,tmpfile() 函数创建一个临时文件,您可以像操作普通文件一样使用返回的文件指针进行读写操作。临时文件会在程序结束时自动被删除。

如果您需要在特定的目录中创建临时文件,可以使用 tmpnam() 函数或 tmpnam_r() 函数来生成一个唯一的临时文件名。然后,您可以使用生成的文件名来创建临时文件。注意,tmpnam() 可能会在多线程环境下存在安全问题,建议使用 tmpnam_r() 或其他安全的替代方法。


4.2.7 文件缓冲

在C语言中,文件缓冲是一种用于提高文件读写性能的机制。文件缓冲将文件数据存储在内存中,以减少频繁的磁盘访问操作,从而提高了文件操作的效率。C标准库提供了三种文件缓冲类型:

  1. 全缓冲 (Fully Buffered) : 当缓冲区被填满或调用 fflush() 函数时,数据被写入磁盘。这是默认的文件缓冲类型,适用于大部分文件。
c 复制代码
setvbuf(file, NULL, _IOFBF, BUFSIZ);
  1. 行缓冲 (Line Buffered) : 当缓冲区被填满、遇到换行符 \n 或调用 fflush() 函数时,数据被写入磁盘。适用于需要逐行写入的文件,如终端设备。
c 复制代码
setvbuf(file, NULL, _IOLBF, BUFSIZ);
  1. 无缓冲 (Unbuffered) : 每次写入都会立即写入磁盘,适用于需要实时写入的文件,如标准错误流。
c 复制代码
setvbuf(file, NULL, _IONBF, BUFSIZ);

其中,setvbuf() 函数用于设置文件缓冲类型,具体的参数解释如下:

  • 第一个参数是文件指针。
  • 第二个参数通常为 NULL,表示使用默认缓冲大小。
  • 第三个参数可以是 _IOFBF(全缓冲)、_IOLBF(行缓冲)或 _IONBF(无缓冲)。
  • 第四个参数是缓冲区的大小,可以根据需要进行设置。

以下是一个示例,演示如何使用不同的文件缓冲类型:

c 复制代码
#include <stdio.h>

int main() {
    FILE *file;

    // 全缓冲
    file = fopen("full_buffered.txt", "w");
    setvbuf(file, NULL, _IOFBF, BUFSIZ);
    fprintf(file, "This is a fully buffered file.\n");
    fclose(file);

    // 行缓冲
    file = fopen("line_buffered.txt", "w");
    setvbuf(file, NULL, _IOLBF, BUFSIZ);
    fprintf(file, "This is a line buffered file.\n");
    fclose(file);

    // 无缓冲
    file = fopen("unbuffered.txt", "w");
    setvbuf(file, NULL, _IONBF, BUFSIZ);
    fprintf(file, "This is an unbuffered file.\n");
    fclose(file);

    return 0;
}

4.2.8 其他文件操作

除了前面提到的文件打开、关闭和缓冲相关的操作,C语言还提供了许多其他文件操作函数,用于进行文件的读取、写入、定位以及错误处理等操作。以下是一些常见的文件操作函数:

  1. 读取文件内容

    • fgetc(FILE *stream): 从文件中读取一个字符。
    • fgets(char *str, int n, FILE *stream): 从文件中读取一行文本。
    • fread(void *ptr, size_t size, size_t count, FILE *stream): 从文件中读取指定数量的字节数据。
  2. 写入文件内容

    • fputc(int c, FILE *stream): 将一个字符写入文件。
    • fputs(const char *str, FILE *stream): 将字符串写入文件。
    • fwrite(const void *ptr, size_t size, size_t count, FILE *stream): 将指定数量的字节数据写入文件。
  3. 文件定位

    • fseek(FILE *stream, long int offset, int whence): 移动文件指针到指定位置。
    • ftell(FILE *stream): 获取当前文件指针的位置。
  4. 刷新文件缓冲

    • fflush(FILE *stream): 刷新文件缓冲,将缓冲区数据写入文件。
  5. 检查文件结尾

    • feof(FILE *stream): 检查文件是否已经到达结尾。
  6. 清除文件错误状态

    • clearerr(FILE *stream): 清除文件的错误状态。
  7. 文件错误处理

    • perror(const char *str): 打印错误信息。
    • errno: 表示最近一次错误代码的全局变量。
  8. 重命名和删除文件

    • rename(const char *oldname, const char *newname): 重命名文件。
    • remove(const char *filename): 删除文件。
  9. 检查文件是否存在

    • 使用系统特定的函数(例如,access() 在Unix/Linux中)来检查文件是否存在。

这些函数提供了广泛的功能,可用于进行文件的读写、定位、错误处理等操作。请根据您的具体需求选择适当的函数进行文件操作。在使用这些函数时,始终记得进行错误检查,以确保文件操作能够正常执行。


4.3 字符的输入/输出

在C语言中,可以使用标准库函数来进行字符的输入和输出操作。以下是常用的字符输入和输出函数:

  1. 字符输入

    • getchar(): 从标准输入(键盘)获取一个字符。
    • getch() (非标准函数): 从终端获取一个字符,不需要按下回车键。
    • getchar_unlocked() (非标准函数): 与 getchar() 类似,但不进行线程锁定,适用于单线程环境。
c 复制代码
#include <stdio.h>

int main() {
    char ch;
    
    printf("Enter a character: ");
    ch = getchar();
    
    printf("You entered: %c\n", ch);
    
    return 0;
}
  1. 字符输出

    • putchar(int c): 输出一个字符到标准输出(屏幕)。
    • putch() (非标准函数): 输出一个字符到终端,不需要换行。
c 复制代码
#include <stdio.h>

int main() {
    char ch = 'A';
    
    putchar(ch);
    putchar('\n'); // 换行
    
    return 0;
}

这些函数可以用于处理单个字符的输入和输出操作。在实际应用中,您可以将这些函数与循环结构结合使用,从而实现更复杂的字符处理逻辑。注意,字符输入函数通常会在用户按下回车键后才会返回,而不需要特殊操作的字符输出函数可以直接将字符打印到终端。


4.4 行的输入/输出

在C语言中,行的输入和输出涉及到字符串的处理,通常使用标准库函数来进行。以下是一些常用的行输入和输出函数:

  1. 行输入

    • fgets(char *str, int n, FILE *stream): 从文件中读取一行文本(包括换行符),最多读取 n-1 个字符。它会将读取的内容存储到字符串 str 中,并在末尾添加一个 null 终止字符。
c 复制代码
#include <stdio.h>

int main() {
    char buffer[100];
    
    printf("Enter a string: ");
    fgets(buffer, sizeof(buffer), stdin);
    
    printf("You entered: %s", buffer);
    
    return 0;
}
  1. 行输出

    • puts(const char *str): 输出一个字符串到标准输出(屏幕),并自动添加换行符。
c 复制代码
#include <stdio.h>

int main() {
    char str[] = "Hello, world!";
    
    puts(str);
    
    return 0;
}

这些函数允许您处理包含换行符的文本行。fgets() 函数会读取整行文本,包括换行符,而 puts() 函数会将字符串输出并自动添加换行符。

请注意,fgets() 会在达到指定的最大字符数或读取到换行符时停止读取,所以需要适当处理换行符。另外,puts() 输出字符串时会自动添加换行符,因此无需手动添加。


4.5 块的输入/输出

在C语言中,块的输入和输出通常涉及到二进制数据的读写,而不是像行输入输出那样处理文本数据。您可以使用标准库函数来进行块的输入和输出操作。

以下是常用的二进制块输入和输出函数:

  1. 块输入

    • fread(void *ptr, size_t size, size_t count, FILE *stream): 从文件中读取指定数量的字节数据,并存储到内存中的缓冲区 ptr 中。size 是每个数据项的大小,count 是要读取的数据项数量。
c 复制代码
#include <stdio.h>

int main() {
    int data[5];
    FILE *file = fopen("data.bin", "rb");

    if (file == NULL) {
        printf("Failed to open the file.\n");
        return 1;
    }

    fread(data, sizeof(data[0]), 5, file);

    for (int i = 0; i < 5; i++) {
        printf("Data[%d]: %d\n", i, data[i]);
    }

    fclose(file);

    return 0;
}
  1. 块输出

    • fwrite(const void *ptr, size_t size, size_t count, FILE *stream): 将内存中的数据块写入文件。ptr 是包含数据的指针,size 是每个数据项的大小,count 是要写入的数据项数量。
c 复制代码
#include <stdio.h>

int main() {
    int data[] = { 10, 20, 30, 40, 50 };
    FILE *file = fopen("data.bin", "wb");

    if (file == NULL) {
        printf("Failed to open the file.\n");
        return 1;
    }

    fwrite(data, sizeof(data[0]), 5, file);

    fclose(file);

    return 0;
}

这些函数允许您以二进制方式读取和写入数据块。通过适当设置 sizecount,您可以处理不同类型和大小的数据块。


4.6 文件定位

在C语言中,您可以使用文件定位函数来在文件中移动文件指针的位置,以便进行读写操作。文件定位函数允许您以字节为单位进行精确定位。

以下是常用的文件定位函数:

  1. fseek() 函数:用于设置文件指针的位置。
c 复制代码
int fseek(FILE *stream, long int offset, int whence);
  • stream: 文件指针。

  • offset: 相对于 whence 的偏移量。正数表示向后移动,负数表示向前移动。

  • whence: 定位基准,可以是以下常量之一:

    • SEEK_SET: 从文件开头开始计算偏移。
    • SEEK_CUR: 从当前位置开始计算偏移。
    • SEEK_END: 从文件末尾开始计算偏移。
  1. ftell() 函数:获取当前文件指针的位置。
c 复制代码
long int ftell(FILE *stream);

下面是一个使用 fseek()ftell() 的示例:

c 复制代码
#include <stdio.h>

int main() {
    FILE *file = fopen("data.txt", "r");

    if (file == NULL) {
        printf("Failed to open the file.\n");
        return 1;
    }

    fseek(file, 10, SEEK_SET); // 移动到文件开头的第 10 个字节

    long int position = ftell(file); // 获取当前文件指针位置
    printf("Current position: %ld\n", position);

    fclose(file);

    return 0;
}

在此示例中,我们打开了一个文件,使用 fseek() 将文件指针移动到文件开头的第 10 个字节,然后使用 ftell() 获取当前文件指针的位置。

文件定位函数允许您在文件中随意移动文件指针,以便进行读写操作。请注意,如果您在一个已经打开的文件上进行移动,务必确保进行错误检查以处理可能的错误情况。


4.7 字符串的输入/输出

在C语言中,您可以使用标准库函数来进行字符串的输入和输出操作。以下是常用的字符串输入和输出函数:

  1. 字符串输入

    • scanf() 函数:用于从标准输入(键盘)读取格式化的输入,可以读取字符串,但可能会受到空格等字符的影响。
c 复制代码
#include <stdio.h>

int main() {
    char str[100];
    
    printf("Enter a string: ");
    scanf("%s", str);
    
    printf("You entered: %s\n", str);
    
    return 0;
}
  • gets() 函数:用于从标准输入(键盘)读取一行字符串,包括空格,但不进行越界检查。
c 复制代码
#include <stdio.h>

int main() {
    char str[100];
    
    printf("Enter a string: ");
    gets(str);
    
    printf("You entered: %s\n", str);
    
    return 0;
}
  • fgets() 函数:用于从文件中读取一行字符串,包括空格,也可以用于从标准输入读取。
c 复制代码
#include <stdio.h>

int main() {
    char str[100];
    
    printf("Enter a string: ");
    fgets(str, sizeof(str), stdin);
    
    printf("You entered: %s", str);
    
    return 0;
}
  1. 字符串输出

    • printf() 函数:用于将格式化的字符串输出到标准输出(屏幕)。
c 复制代码
#include <stdio.h>

int main() {
    char str[] = "Hello, world!";
    
    printf("%s\n", str);
    
    return 0;
}
  • puts() 函数:用于将字符串输出到标准输出(屏幕),自动添加换行符。
c 复制代码
#include <stdio.h>

int main() {
    char str[] = "Hello, world!";
    
    puts(str);
    
    return 0;
}

这些函数允许您在程序中输入和输出字符串。gets() 函数存在缓冲区溢出的安全问题,不推荐使用。使用 fgets() 函数可以更安全地读取一行字符串。在使用这些函数时,请确保适当处理换行符和空格等特殊字符。

相关推荐
朱一头zcy9 分钟前
C语言复习第9章 字符串/字符/内存函数
c语言
此生只爱蛋12 分钟前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
何曾参静谧32 分钟前
「C/C++」C/C++ 指针篇 之 指针运算
c语言·开发语言·c++
lulu_gh_yu1 小时前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法
~yY…s<#>3 小时前
【刷题17】最小栈、栈的压入弹出、逆波兰表达式
c语言·数据结构·c++·算法·leetcode
EricWang13585 小时前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端
我是谁??5 小时前
C/C++使用AddressSanitizer检测内存错误
c语言·c++
希言JY6 小时前
C字符串 | 字符串处理函数 | 使用 | 原理 | 实现
c语言·开发语言
午言若6 小时前
C语言比较两个字符串是否相同
c语言
TeYiToKu7 小时前
笔记整理—linux驱动开发部分(9)framebuffer驱动框架
linux·c语言·arm开发·驱动开发·笔记·嵌入式硬件·arm