前言
在Android音视频开发中,网上知识点过于零碎,自学起来难度非常大,不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》,结合我自己的工作学习经历,我准备写一个音视频系列blog。C/C++是音视频必备编程语言,我准备用几篇文章来快速回顾C语言。本文是音视频系列blog的其中一个, 对应的要学习的内容是:快速回顾C语言的结构、联合和枚举,指针的高级应用,声明,文件操作。
音视频系列blog
音视频系列blog: 点击此处跳转查看
目录
1 结构、联合和枚举
1.1 结构变量
C语言中的结构(Structure)是一种用户自定义的数据类型,它允许你将不同类型的数据项组合在一起,形成一个逻辑上相关的数据单元。结构可以包含多个成员(也称为字段),每个成员可以是不同的数据类型。结构在处理复杂数据时非常有用,可以使代码更清晰、模块化和可读性强。
以下是创建和使用结构变量的示例:
#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
关键字来为结构类型定义别名,以提高代码的可读性:
#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
关键字来定义结构类型。结构的定义格式如下:
struct 结构类型名 {
数据类型 成员名1;
数据类型 成员名2;
// ... 其他成员
};
以下是一个示例,定义了一个名为Person
的结构类型:
struct Person {
char name[50];
int age;
float height;
};
然后,可以通过该结构类型创建结构变量:
struct Person person1;
为了提高可读性,通常会使用typedef
关键字来为结构类型定义别名,以便在声明结构变量时更方便地使用别名。例如:
typedef struct {
char name[50];
int age;
float height;
} Person;
现在,可以直接使用Person
作为结构类型名:
Person person1;
通过结构类型,可以创建包含不同类型数据的复合数据类型,从而更有效地表示和操作复杂的数据。例如,可以用结构表示学生信息、图书记录、员工数据等。使用结构,可以将相关的数据项组织在一起,使代码更加模块化和易于维护。
1.3 嵌套的数组和结构
C语言允许在数组和结构中进行嵌套,即在数组或结构中嵌套其他数组或结构。这种嵌套可以用于表示更复杂的数据结构,如二维数组、多维数组、结构数组等。下面是关于嵌套数组和嵌套结构的一些示例:
嵌套数组示例:
- 二维数组: 二维数组是数组的数组,可以用于表示矩阵等数据结构。
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
- 数组中嵌套结构: 可以在数组中嵌套结构,用于表示一组具有相似属性的数据。
struct Point {
int x;
int y;
};
struct Point points[5] = {
{1, 2},
{3, 4},
{5, 6},
{7, 8},
{9, 10}
};
嵌套结构示例:
- 结构中嵌套结构: 可以在结构中嵌套其他结构,用于表示更复杂的数据。
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}
};
- 结构数组中嵌套结构: 可以在结构数组中嵌套其他结构,用于表示多个具有复杂属性的实体。
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)是一种特殊的数据类型,它允许在同一内存位置存储不同的数据类型,但同一时间只能存储其中的一个成员。联合的大小等于其最大成员的大小。与结构不同,联合的各个成员共享同一块内存空间。
以下是联合的定义和用法示例:
#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.5 枚举
C语言中,枚举(Enumeration)是一种用户自定义的数据类型,用于创建一组具有命名值的常量。枚举常常用于表示一组相关的符号常量,例如表示月份、星期几等。枚举的每个值称为枚举常量(Enumerator)。
以下是枚举的定义和用法示例:
#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
,然后通过条件判断来判断今天是否为星期三。
需要注意以下几点:
- 枚举类型是一个自定义的数据类型,它可以包含一组常量值。
- 枚举常量的默认值从0开始递增,但你也可以显式地为枚举常量赋值。
- 枚举常量的作用域在整个枚举类型内部。
- 枚举变量可以在声明时指定类型名,也可以直接使用枚举常量。
枚举类型在C语言中非常有用,可以使代码更加可读性强,同时提供了一种更有意义的方式来表示一组相关的常量值。
2 指针的高级应用
2.1 动态存储分配
C语言中的动态存储分配是一种高级的指针应用,它允许在程序运行时动态地分配和释放内存空间,以便灵活地管理数据结构和资源。这对于处理未知数量的数据或避免静态内存分配的限制非常有用。在动态存储分配中,主要使用了以下几个函数:malloc()
、calloc()
、realloc()
和 free()
。
-
malloc()
: 用于分配一块指定大小的内存空间。它接受一个参数,表示要分配的字节数,返回分配的内存的首地址。如果分配失败,返回NULL
。int *ptr; ptr = (int *)malloc(5 * sizeof(int)); // 分配包含5个整数的内存空间 if (ptr == NULL) { printf("Memory allocation failed.\n"); return 1; }
-
calloc()
: 用于分配一块指定数量和大小的连续内存空间,初始化为零。它接受两个参数,表示元素的数量和每个元素的大小,返回分配的内存的首地址。int *ptr; ptr = (int *)calloc(5, sizeof(int)); // 分配包含5个整数的内存空间并初始化为零 if (ptr == NULL) { printf("Memory allocation failed.\n"); return 1; }
-
realloc()
: 用于重新分配已分配内存的大小。它接受两个参数,第一个参数是之前分配的内存的指针,第二个参数是新的大小。它可能会在原地重新分配或者将数据移到新的内存位置。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; }
-
free()
: 用于释放之前分配的内存空间,以便将其返回给系统。释放内存后,指针变量仍然保存着已释放的内存地址,但访问它将产生未定义的行为。free(ptr); // 释放之前分配的内存空间
使用动态存储分配,你可以根据需要在程序运行时分配和释放内存,提高内存的使用效率和灵活性。但要小心内存泄漏和悬挂指针等问题,确保在适当的时候释放已分配的内存。
2.2 动态分配字符串
在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()
函数释放动态分配的内存。
需要注意以下几点:
- 在使用
malloc()
分配字符串内存时,需要为字符串长度留出足够的空间,通常是字符串长度加上一个空字符的大小(\0
,表示字符串的结尾)。 - 动态分配的字符串内存可以使用指针来访问和操作。
- 一定要在使用完字符串后使用
free()
函数释放动态分配的内存,以避免内存泄漏。 - 使用动态分配字符串内存时,要确保不会访问未初始化或未分配的内存区域,以避免未定义的行为。
动态分配字符串内存允许你根据实际输入和操作的需要,适应不同长度的字符串,提高了灵活性和内存的有效使用。
2.3 动态分配数组
在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()
函数释放动态分配的内存。
需要注意以下几点:
- 动态分配数组内存时,要根据数组元素的类型和数量来分配足够的内存空间。
- 动态数组的元素可以使用指针进行访问和操作。
- 动态分配的数组内存应在使用完毕后使用
free()
函数释放,以避免内存泄漏。 - 动态数组可以根据实际需要分配不同大小的内存,提高了内存的利用率和灵活性。
动态分配数组内存允许你根据实际的输入和操作,灵活地创建不同大小的数组,适应不同场景下的需求。
2.4 释放存储空间
在C语言中,手动释放已分配的内存空间是十分重要的,以避免内存泄漏(未释放的内存)和资源浪费。当你使用动态内存分配函数(如malloc()
、calloc()
、realloc()
)分配内存后,务必在不再需要使用这块内存时通过调用free()
函数来释放它。
以下是释放存储空间的示例:
#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()
函数释放了已分配的内存。
需要注意以下几点:
free()
函数只能用于释放通过动态内存分配函数(如malloc()
、calloc()
、realloc()
)分配的内存。使用free()
来释放静态或自动分配的内存,或释放同一块内存多次,都会导致未定义的行为。- 释放内存后,不要再尝试使用指向已释放内存的指针,这可能会导致悬挂指针(Dangling Pointer)问题。
- 内存泄漏是指在程序运行时未释放不再使用的内存,这会导致内存消耗不断增加。务必确保在不再需要使用内存时释放它,以保持程序的健壮性和性能。
有效地释放存储空间是编程中的一个重要方面,帮助你有效管理系统资源,避免内存泄漏问题,以及提高程序的稳定性和性能。
2.5 受限指针
C语言中的受限指针(Restricted Pointers)是C99标准引入的一种指针类型,用于表示指针的使用范围和行为约束。受限指针可以帮助编译器优化代码,并增强代码的可读性和可维护性。它们通常在指针的生命周期中具有特定的限制和用法。
使用受限指针可以告诉编译器,某个指针变量仅在某些限定的情况下才会被修改,这有助于编译器进行更多的优化。受限指针通常用于在编译期间检查指针的使用情况,从而提高代码的安全性和可靠性。
以下是受限指针的一个简单示例:
#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
指向的内存位置。这有助于提高代码的性能。
需要注意以下几点:
- 受限指针只能用于指针函数的形参,用于约束指针的使用范围。
restrict
关键字只在C99标准及以后版本中有效。- 在使用受限指针时,务必确保不会在函数中修改其他指针所指向的内存,以充分发挥优化效果。
虽然受限指针可以提高代码性能和优化,但在使用时需要注意其约束和限制,以避免产生不正确的结果。
2.6 灵活数组成员
C语言中的灵活数组成员(Flexible Array Members)是C99标准引入的特性,允许在结构中定义一个大小可变的数组作为最后一个成员。这使得你可以在结构中创建具有不定大小的数组,从而在处理某些数据结构时更加灵活。
以下是灵活数组成员的一个简单示例:
#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()
函数分配了足够的内存以容纳整个结构和数组元素。通过这种方式,我们可以在不同场景下创建具有不同大小的灵活数组。
需要注意以下几点:
- 灵活数组成员只能作为结构的最后一个成员,且结构定义中只能有一个灵活数组成员。
- 灵活数组成员的大小是在运行时根据需要进行动态分配的。
- 在使用灵活数组成员时,需要分配足够的内存以容纳整个结构和数组元素,以及结构中的其他成员。
- 释放灵活数组成员时,需要使用
free()
函数释放整个结构的内存。
灵活数组成员允许你在结构中创建大小可变的数组,适应不同大小的数据,提高了数据结构的灵活性和可用性。
3 声明
3.1 声明的语法
在C语言中,声明用于告诉编译器有关变量、函数或类型的信息,从而在代码中引入这些实体而不需要实际定义它们。声明通常用于头文件中,以便在多个源文件中共享信息。以下是C语言中一些常见声明的语法示例:
- 变量声明: 用于声明变量的名称和类型,但不分配内存。
// 声明整数变量
int count;
// 声明字符变量
char letter;
// 声明指针变量
float *ptr;
- 函数声明: 用于声明函数的原型,包括函数名称、参数列表和返回类型。
// 声明函数原型
int add(int a, int b);
// 声明不带参数的函数原型
void greet(void);
- 数组声明: 用于声明数组的名称和类型。
// 声明整数数组
int numbers[10];
// 声明字符数组(字符串)
char message[50];
- 类型声明: 用于为用户自定义类型(结构、枚举等)创建声明。
// 声明结构类型
struct Point {
int x;
int y;
};
// 声明枚举类型
enum Color { Red, Green, Blue };
- 外部变量声明: 用于在一个源文件中引用另一个源文件中定义的外部变量。
// 在当前文件中声明外部变量
extern int globalVar;
需要注意以下几点:
- 声明不分配内存或存储空间,它只是为编译器提供信息以便正确解析代码。
- 声明可以在任何地方进行,但通常最好将它们放在头文件中,以便在多个源文件中共享。
- 变量和函数的定义包含实际分配内存或实现代码的部分,而声明只是提供了名称和类型信息。
- 在函数原型中,可以省略参数名称,只保留参数类型,例如
int add(int, int);
。 - 在不同的情况下,声明的语法可能会有所不同,根据上下文合理使用。
在大型程序中,正确使用声明可以帮助减少编译错误,提高代码的可读性和可维护性。
3.2 存储类型
C语言中的存储类型(Storage Class)用于描述变量、函数和数据对象的生命周期、作用域和可见性等特性。C语言提供了多种存储类型,以适应不同的编程需求。以下是C语言中常见的存储类型:
- 自动存储类(Automatic Storage Class): 默认的存储类,用于描述局部变量。它在函数内部声明的局部变量的默认存储类。当程序控制流离开变量的作用域时,自动变量会被销毁。
void function() {
int a; // 自动存储类
}
- 寄存器存储类(Register Storage Class): 请求编译器将变量存储在寄存器中,以便提高访问速度。编译器可以选择将部分变量存储在寄存器中。但是,由于寄存器数量有限,不一定所有请求都会被满足。
register int x; // 寄存器存储类
- 静态存储类(Static Storage Class): 用于描述具有持久性的局部变量,它在程序的整个生命周期内保持其值。静态变量在首次执行它们的声明语句时初始化,并在整个程序执行过程中保持值。
static int count = 0; // 静态存储类
- 外部存储类(External Storage Class): 用于描述全局变量和外部函数。外部变量在多个文件之间共享,它们可以在一个文件中定义,在其他文件中声明,以便在整个程序中使用。
extern int globalVar; // 外部存储类
- 线程存储类(Thread Storage Class): 用于多线程程序,指示变量在每个线程中具有单独的存储。
_Thread_local int data; // 线程存储类
- 空存储类(No Storage Class): 用于描述类型,但不指定存储类。通常在类型声明中使用。
typedef int Age; // 空存储类,用于类型别名
不同存储类的使用取决于程序的需求。理解这些存储类的特性和用法将有助于正确地管理变量的生命周期、作用域和可见性,以及优化代码的性能和可读性。
3.3 类型限定符
C语言中的类型限定符用于修改变量的存储类别和访问权限,以及指定变量的性质和使用方式。C语言提供了以下几种常见的类型限定符:
- const:
const
用于声明一个只读变量,表示变量的值不能被修改。
const int x = 10; // 声明一个只读整数变量
- volatile:
volatile
用于声明一个变量是易变的,表示变量的值可能会在未经预期的情况下被修改,例如在中断服务程序中。
volatile int sensorValue; // 声明一个易变的整数变量
- restrict:
restrict
用于声明指针,表示指针是唯一引用对象的指针,编译器可以对这种指针进行更好的优化。
void updateArray(int *restrict dest, const int *restrict src, size_t n);
- _Atomic:
_Atomic
用于声明一个原子类型的变量,支持多线程并发访问时的原子操作。
_Atomic int counter; // 声明一个原子整数变量
- _Thread_local:
_Thread_local
用于声明一个线程本地变量,使变量在每个线程中具有单独的副本。
_Thread_local int threadLocalData; // 声明一个线程本地整数变量
这些类型限定符可以根据程序的需求来使用,以改变变量的特性、可见性和存储行为。例如,const
可用于确保变量的值不会被意外修改,volatile
可用于处理不受控制的变量访问,restrict
可以帮助编译器进行更好的优化等。理解并正确使用这些类型限定符可以提高代码的安全性、可靠性和性能。
3.4 声明符
C语言中的声明符(Declarator)用于描述变量或函数的名称以及与之相关的一些属性。声明符是用于创建变量或函数的声明的一部分,它告诉编译器有关实体的类型、存储类和其他信息。以下是一些常见的声明符形式:
-
直接声明符(Direct Declarator): 用于声明变量、数组和函数,直接指定名称和类型。
- 变量声明:
type variableName;
int x;
- 数组声明:
type arrayName[size];
int numbers[10];
- 函数声明:
returnType functionName(parameters);
int add(int a, int b);
- 变量声明:
-
指针声明符(Pointer Declarator): 用于声明指向其他类型的指针。
int *ptr; // 声明一个指向整数的指针
-
数组声明符(Array Declarator): 用于声明数组,包括数组的维度。
int matrix[3][3]; // 声明一个3x3的整数数组
-
函数声明符(Function Declarator): 用于声明函数,包括参数列表和返回类型。
int (*funcPtr)(int, int); // 声明一个函数指针
-
函数指针声明符(Function Pointer Declarator): 用于声明指向函数的指针。
int (*operation)(int, int); // 声明一个指向函数的函数指针
-
类型限定符声明符(Type Qualifier Declarator): 用于声明带有类型限定符的变量。
const int *readOnlyPtr; // 声明一个指向只读整数的指针
-
类型限定符函数声明符(Type Qualifier Function Declarator): 用于声明带有类型限定符的函数。
int sum(const int array[], int size); // 声明一个带有只读参数的函数
声明符的形式因所声明实体的类型和属性而异。在C语言中,声明符是用于创建变量和函数声明的重要组成部分,帮助编译器正确解析和生成代码。
3.5 初始化式
C语言中的初始化式(Initializer)用于在声明变量时为其赋初值。初始化式可以用于各种类型的变量,包括基本数据类型、数组、结构和联合等。以下是一些常见的初始化式形式:
-
基本数据类型的初始化:
- 初始化整数变量:
int x = 10;
- 初始化浮点数变量:
float pi = 3.14159;
- 初始化字符变量:
char letter = 'A';
-
数组的初始化:
- 初始化整数数组:
int numbers[5] = {1, 2, 3, 4, 5};
- 部分初始化数组(剩余元素会被自动初始化为0):
int partialInit[10] = {1, 2, 3};
-
结构的初始化:
- 初始化结构变量:
struct Point { int x; int y; }; struct Point p = {10, 20};
-
联合的初始化:
- 初始化联合变量:
union Data { int num; float pi; }; union Data d = {3.14159};
-
指针的初始化:
- 初始化指向整数的指针:
int x = 10; int *ptr = &x;
- 初始化指向字符串的指针:
char *message = "Hello, world!";
-
复合初始化: 可以通过嵌套使用大括号进行复合初始化,用于初始化数组和结构中的嵌套元素。
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)是一种编译器的优化手段,用于在编译时将函数的调用代码替换为函数体的实际代码,从而减少函数调用的开销。内联函数通常用于需要频繁调用的短小函数,以提高程序的性能。内联函数的使用方式和语法如下:
- 内联函数声明: 在函数声明前加上关键字
inline
。
inline int add(int a, int b) {
return a + b;
}
- 内联函数调用: 编译器会尝试将内联函数的调用替换为函数体。
int result = add(5, 3); // 可能被替换为 int result = 5 + 3;
需要注意以下几点:
- 内联函数通常适用于简单的、执行时间短的函数,例如数学运算、比较操作等。
- 内联函数的调用可能会增加代码的大小,因为函数体会被复制到每个调用点。
- 编译器会根据函数的复杂性和调用频率来决定是否真正内联函数。
- 使用内联函数的前提是:函数的定义必须在调用点之前,以便编译器知道函数的实际代码。
- 内联函数不一定比普通函数更快,它的效果取决于具体情况。在一些情况下,编译器会自动进行优化,而不需要显式使用内联函数。
需要注意的是,内联函数只是对编译器的建议,编译器可以选择是否真正内联函数。使用内联函数时,应当在性能和代码大小之间进行权衡,适用于需要频繁调用的短小函数,以提高程序的性能。
4 输入输出
4.1 流
4.1.1 文件指针
在C语言中,文件指针(File Pointer)是一种用于访问文件的数据结构。文件指针可以用于在文件中定位并操作数据,包括读取和写入。使用文件指针,你可以打开文件、定位文件中的特定位置,以及进行文件的读写操作。
C语言标准库提供了一组函数来处理文件指针的操作,其中最常用的函数包括:
-
FILE
数据类型: C语言中使用FILE
数据类型来表示文件指针。它是一个不透明的结构体类型,用于管理文件的信息。 -
fopen
函数: 用于打开文件,并返回一个指向该文件的指针。它接受两个参数,文件名和打开模式。FILE *fptr = fopen("myfile.txt", "r"); // 以只读模式打开文件
-
fclose
函数: 用于关闭已打开的文件。关闭文件后,对文件的读写操作将不再有效。fclose(fptr); // 关闭文件指针
-
读取操作: C语言提供了多个函数用于从文件中读取数据。
fgetc
: 从文件中读取一个字符。fgets
: 从文件中读取一行字符串。fread
: 从文件中读取指定数量的字节。
-
写入操作: C语言提供了多个函数用于向文件中写入数据。
fputc
: 将一个字符写入文件。fputs
: 将一个字符串写入文件。fwrite
: 将指定数量的字节写入文件。
-
定位函数: 用于在文件中定位文件指针的位置。
fseek
: 移动文件指针到指定的位置。ftell
: 返回文件指针的当前位置。
-
错误处理: C语言提供了
feof
和ferror
函数用于检测文件读写的错误。
文件指针的使用可以帮助你读取和写入文件中的数据,进行文件的操作和管理。在操作文件时,务必确保正确地打开和关闭文件,以避免资源泄漏和数据丢失。
4.1.2 标准流和重定向
C语言标准流(Standard Streams)是在C语言中用于输入和输出的三个默认流,它们分别是标准输入流(stdin)、标准输出流(stdout)和标准错误流(stderr)。这些流与键盘、屏幕和错误信息相关联,允许程序与用户交互并进行输入输出操作。标准流通常通过文件指针进行访问。
重定向是一种将标准流与其他文件或设备连接的机制。通过重定向,你可以将标准输入流、标准输出流或标准错误流重定向到文件中,从而实现文件的输入输出,而不仅限于键盘和屏幕。以下是有关C语言标准流和重定向的一些重要信息:
- 标准输入流(stdin): 默认情况下关联于键盘输入,可以通过重定向将其关联到文件。
- 标准输出流(stdout): 默认情况下关联于屏幕输出,可以通过重定向将其关联到文件。
- 标准错误流(stderr): 默认情况下关联于屏幕输出,用于显示错误信息,可以通过重定向将其关联到文件。
- 重定向操作符: 在命令行中使用特定的符号来进行重定向操作。
>
: 用于将标准输出重定向到文件中。>>
: 用于将标准输出追加到文件中。<
: 用于将文件内容作为标准输入。2>
: 用于将标准错误重定向到文件中。2>>
: 用于将标准错误追加到文件中。
示例:
./myprogram > output.txt # 将程序的标准输出重定向到output.txt文件
./myprogram < input.txt # 将input.txt文件内容作为程序的标准输入
./myprogram 2> error.txt # 将程序的标准错误重定向到error.txt文件
- 使用
freopen
函数进行重定向: 在C语言程序中,可以使用freopen
函数来实现重定向操作。
#include <stdio.h>
int main() {
freopen("output.txt", "w", stdout); // 将标准输出重定向到output.txt文件
printf("This is redirected output.\n");
return 0;
}
通过重定向,你可以将程序的输入输出与文件关联,实现更灵活的数据处理和文件操作。
4.1.3 文本文件与二进制文件
C语言中的文件可以分为两种主要类型:文本文件和二进制文件。这两种文件类型的区别在于它们存储和表示数据的方式。
-
文本文件: 文本文件是以文本形式存储的文件,其中的数据以可读的字符表示,通常使用ASCII编码。文本文件包含普通文本字符,例如字母、数字、标点符号等。文本文件可以被普通文本编辑器打开和编辑。
示例:
textfile.txt
Hello, this is a text file. Line 2 of the text file.
-
二进制文件: 二进制文件是以二进制形式存储的文件,其中的数据以字节表示,可以包含任何类型的数据,例如图像、音频、视频等。二进制文件的内容不是人类可读的,需要特定的程序来解析和处理。二进制文件通常更紧凑,适用于存储和传输非文本数据。
示例:
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语言中,你可以使用标准库函数来读写文本文件和二进制文件。
-
读写文本文件:
FILE *file = fopen("textfile.txt", "r"); // 打开文本文件进行读取 // 使用 fscanf 读取文本文件内容 fclose(file);
-
读写二进制文件:
FILE *file = fopen("binaryfile.bin", "rb"); // 打开二进制文件进行读取 // 使用 fread 读取二进制文件内容 fclose(file);
-
写入文本文件:
FILE *file = fopen("textfile.txt", "w"); // 打开文本文件进行写入 // 使用 fprintf 写入文本内容 fclose(file);
-
写入二进制文件:
FILE *file = fopen("binaryfile.bin", "wb"); // 打开二进制文件进行写入 // 使用 fwrite 写入二进制内容 fclose(file);
可以根据需求选择使用文本文件还是二进制文件来存储数据,具体的选择取决于所处理数据的性质以及需要。
4.2 文件操作
4.2.1 打开文件
在C语言中,你可以使用标准库函数来打开文件,以便进行读取和写入操作。主要用到的函数是 fopen
函数。下面是关于如何在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)。
在打开文件后,你可以使用其他标准库函数(如 fscanf
、fgets
、fread
、fprintf
、fputs
等)来进行读写操作。完成文件操作后,务必使用 fclose
函数关闭文件,以确保资源正确释放。
需要注意的是,文件操作可能会受到操作系统权限的限制,确保你有足够的权限来读写目标文件。
4.2.2 模式
在C语言中,文件操作通过使用标准库函数来实现。这些函数位于 <stdio.h>
头文件中,提供了多种文件操作模式。以下是常用的文件操作模式及其说明:
- “r” (Read): 以只读模式打开文件。如果文件不存在,打开操作将失败;如果文件存在,数据可以被读取。
FILE *file = fopen("filename.txt", "r");
- “w” (Write): 以写入模式打开文件。如果文件不存在,将创建一个新文件;如果文件存在,文件中的内容将被清空。
FILE *file = fopen("filename.txt", "w");
- “a” (Append): 以追加模式打开文件。如果文件不存在,将创建一个新文件;如果文件存在,数据将被追加到文件末尾。
FILE *file = fopen("filename.txt", "a");
- “rb” (Read Binary): 以二进制只读模式打开文件。与 “r” 类似,但以二进制模式处理文件。
FILE *file = fopen("filename.bin", "rb");
- “wb” (Write Binary): 以二进制写入模式打开文件。与 “w” 类似,但以二进制模式处理文件。
FILE *file = fopen("filename.bin", "wb");
- “ab” (Append Binary): 以二进制追加模式打开文件。与 “a” 类似,但以二进制模式处理文件。
FILE *file = fopen("filename.bin", "ab");
- “r+” (Read and Write): 以读写模式打开文件。文件必须存在,可以读取和写入数据。
FILE *file = fopen("filename.txt", "r+");
- “w+” (Write and Read): 以读写模式打开文件。如果文件不存在,将创建一个新文件;如果文件存在,文件中的内容将被清空,可以读取和写入数据。
FILE *file = fopen("filename.txt", "w+");
- “a+” (Append and Read): 以读写追加模式打开文件。如果文件不存在,将创建一个新文件;如果文件存在,数据将被追加到文件末尾,可以读取和写入数据。
FILE *file = fopen("filename.txt", "a+");
这些模式提供了不同的文件操作选项,您可以根据需要选择合适的模式来操作文件。注意,在操作文件后,应该使用 fclose()
函数来关闭文件,以释放资源。
4.2.3 关闭文件
在C语言中,要关闭已打开的文件,您需要使用 fclose()
函数。这个函数接受一个文件指针作为参数,然后将该文件指针所表示的文件关闭,释放与该文件关联的资源。
以下是 fclose()
函数的使用方法:
#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;
}
请注意以下几点:
- 在打开文件后,进行了文件读写等操作后,应该使用
fclose()
来关闭文件,以确保资源得到释放。 - 在关闭文件之后,不应再尝试使用已关闭的文件指针进行读写操作,否则可能会导致未定义的行为。
- 检查
fopen()
是否成功打开文件。如果文件打开失败(例如,文件不存在或没有读取权限),文件指针将为NULL
,在尝试关闭文件之前应该进行错误检查。 - 虽然在程序终止时操作系统会关闭已打开的文件,但是最好的实践是在您不再需要文件时显式地使用
fclose()
函数关闭文件。
如果您在程序执行期间打开了多个文件,每个文件都需要使用 fclose()
分别关闭。
4.2.4 为打开的流附加文件
在C语言中,您可以使用不同的文件打开模式来附加内容到已经存在的文件中。为了在已打开的流(文件指针)中附加内容,您可以使用以下打开模式:
- “a” (Append): 使用这个模式打开文件,会将数据追加到文件的末尾。如果文件不存在,将创建一个新文件。
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);
- “ab” (Append Binary): 以二进制追加模式打开文件。与 “a” 类似,但以二进制模式处理文件。
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);
- “a+” (Append and Read): 以读写追加模式打开文件。如果文件不存在,将创建一个新文件。可以在文件末尾追加数据,并在需要时进行读取。
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()
函数,其中第一个参数是程序的名称,后续参数是由空格分隔的命令行参数。
以下是如何从命令行获取文件名的示例代码:
#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]
是第一个命令行参数,即文件名。
运行这个程序时,您可以在命令行中传递文件名作为参数,例如:
./program_name filename.txt
这将输出:
File name: filename.txt
然后,您可以使用 filename
变量来进行文件操作,如打开、读取或写入文件。请注意,在实际使用中,应该添加适当的错误检查和文件操作代码。
4.2.6 临时文件
C语言中可以使用标准库函数来创建临时文件,这在需要在程序运行时临时存储数据时非常有用。临时文件通常在程序结束后会被自动删除,因此适合用于存储临时性的数据或中间结果。
您可以使用 tmpfile()
函数来创建一个临时文件,并返回一个指向该临时文件的文件指针。下面是一个示例:
#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标准库提供了三种文件缓冲类型:
- 全缓冲 (Fully Buffered): 当缓冲区被填满或调用
fflush()
函数时,数据被写入磁盘。这是默认的文件缓冲类型,适用于大部分文件。
setvbuf(file, NULL, _IOFBF, BUFSIZ);
- 行缓冲 (Line Buffered): 当缓冲区被填满、遇到换行符
\n
或调用fflush()
函数时,数据被写入磁盘。适用于需要逐行写入的文件,如终端设备。
setvbuf(file, NULL, _IOLBF, BUFSIZ);
- 无缓冲 (Unbuffered): 每次写入都会立即写入磁盘,适用于需要实时写入的文件,如标准错误流。
setvbuf(file, NULL, _IONBF, BUFSIZ);
其中,setvbuf()
函数用于设置文件缓冲类型,具体的参数解释如下:
- 第一个参数是文件指针。
- 第二个参数通常为
NULL
,表示使用默认缓冲大小。 - 第三个参数可以是
_IOFBF
(全缓冲)、_IOLBF
(行缓冲)或_IONBF
(无缓冲)。 - 第四个参数是缓冲区的大小,可以根据需要进行设置。
以下是一个示例,演示如何使用不同的文件缓冲类型:
#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语言还提供了许多其他文件操作函数,用于进行文件的读取、写入、定位以及错误处理等操作。以下是一些常见的文件操作函数:
- 读取文件内容:
fgetc(FILE *stream)
: 从文件中读取一个字符。fgets(char *str, int n, FILE *stream)
: 从文件中读取一行文本。fread(void *ptr, size_t size, size_t count, FILE *stream)
: 从文件中读取指定数量的字节数据。
- 写入文件内容:
fputc(int c, FILE *stream)
: 将一个字符写入文件。fputs(const char *str, FILE *stream)
: 将字符串写入文件。fwrite(const void *ptr, size_t size, size_t count, FILE *stream)
: 将指定数量的字节数据写入文件。
- 文件定位:
fseek(FILE *stream, long int offset, int whence)
: 移动文件指针到指定位置。ftell(FILE *stream)
: 获取当前文件指针的位置。
- 刷新文件缓冲:
fflush(FILE *stream)
: 刷新文件缓冲,将缓冲区数据写入文件。
- 检查文件结尾:
feof(FILE *stream)
: 检查文件是否已经到达结尾。
- 清除文件错误状态:
clearerr(FILE *stream)
: 清除文件的错误状态。
- 文件错误处理:
perror(const char *str)
: 打印错误信息。errno
: 表示最近一次错误代码的全局变量。
- 重命名和删除文件:
rename(const char *oldname, const char *newname)
: 重命名文件。remove(const char *filename)
: 删除文件。
- 检查文件是否存在:
- 使用系统特定的函数(例如,
access()
在Unix/Linux中)来检查文件是否存在。
- 使用系统特定的函数(例如,
这些函数提供了广泛的功能,可用于进行文件的读写、定位、错误处理等操作。请根据您的具体需求选择适当的函数进行文件操作。在使用这些函数时,始终记得进行错误检查,以确保文件操作能够正常执行。
4.3 字符的输入/输出
在C语言中,可以使用标准库函数来进行字符的输入和输出操作。以下是常用的字符输入和输出函数:
- 字符输入:
getchar()
: 从标准输入(键盘)获取一个字符。getch()
(非标准函数): 从终端获取一个字符,不需要按下回车键。getchar_unlocked()
(非标准函数): 与getchar()
类似,但不进行线程锁定,适用于单线程环境。
#include <stdio.h>
int main() {
char ch;
printf("Enter a character: ");
ch = getchar();
printf("You entered: %c\n", ch);
return 0;
}
- 字符输出:
putchar(int c)
: 输出一个字符到标准输出(屏幕)。putch()
(非标准函数): 输出一个字符到终端,不需要换行。
#include <stdio.h>
int main() {
char ch = 'A';
putchar(ch);
putchar('\n'); // 换行
return 0;
}
这些函数可以用于处理单个字符的输入和输出操作。在实际应用中,您可以将这些函数与循环结构结合使用,从而实现更复杂的字符处理逻辑。注意,字符输入函数通常会在用户按下回车键后才会返回,而不需要特殊操作的字符输出函数可以直接将字符打印到终端。
4.4 行的输入/输出
在C语言中,行的输入和输出涉及到字符串的处理,通常使用标准库函数来进行。以下是一些常用的行输入和输出函数:
- 行输入:
fgets(char *str, int n, FILE *stream)
: 从文件中读取一行文本(包括换行符),最多读取n-1
个字符。它会将读取的内容存储到字符串str
中,并在末尾添加一个 null 终止字符。
#include <stdio.h>
int main() {
char buffer[100];
printf("Enter a string: ");
fgets(buffer, sizeof(buffer), stdin);
printf("You entered: %s", buffer);
return 0;
}
- 行输出:
puts(const char *str)
: 输出一个字符串到标准输出(屏幕),并自动添加换行符。
#include <stdio.h>
int main() {
char str[] = "Hello, world!";
puts(str);
return 0;
}
这些函数允许您处理包含换行符的文本行。fgets()
函数会读取整行文本,包括换行符,而 puts()
函数会将字符串输出并自动添加换行符。
请注意,fgets()
会在达到指定的最大字符数或读取到换行符时停止读取,所以需要适当处理换行符。另外,puts()
输出字符串时会自动添加换行符,因此无需手动添加。
4.5 块的输入/输出
在C语言中,块的输入和输出通常涉及到二进制数据的读写,而不是像行输入输出那样处理文本数据。您可以使用标准库函数来进行块的输入和输出操作。
以下是常用的二进制块输入和输出函数:
- 块输入:
fread(void *ptr, size_t size, size_t count, FILE *stream)
: 从文件中读取指定数量的字节数据,并存储到内存中的缓冲区ptr
中。size
是每个数据项的大小,count
是要读取的数据项数量。
#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;
}
- 块输出:
fwrite(const void *ptr, size_t size, size_t count, FILE *stream)
: 将内存中的数据块写入文件。ptr
是包含数据的指针,size
是每个数据项的大小,count
是要写入的数据项数量。
#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;
}
这些函数允许您以二进制方式读取和写入数据块。通过适当设置 size
和 count
,您可以处理不同类型和大小的数据块。
4.6 文件定位
在C语言中,您可以使用文件定位函数来在文件中移动文件指针的位置,以便进行读写操作。文件定位函数允许您以字节为单位进行精确定位。
以下是常用的文件定位函数:
fseek()
函数:用于设置文件指针的位置。
int fseek(FILE *stream, long int offset, int whence);
stream
: 文件指针。offset
: 相对于whence
的偏移量。正数表示向后移动,负数表示向前移动。whence
: 定位基准,可以是以下常量之一:SEEK_SET
: 从文件开头开始计算偏移。SEEK_CUR
: 从当前位置开始计算偏移。SEEK_END
: 从文件末尾开始计算偏移。
ftell()
函数:获取当前文件指针的位置。
long int ftell(FILE *stream);
下面是一个使用 fseek()
和 ftell()
的示例:
#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语言中,您可以使用标准库函数来进行字符串的输入和输出操作。以下是常用的字符串输入和输出函数:
- 字符串输入:
scanf()
函数:用于从标准输入(键盘)读取格式化的输入,可以读取字符串,但可能会受到空格等字符的影响。
#include <stdio.h>
int main() {
char str[100];
printf("Enter a string: ");
scanf("%s", str);
printf("You entered: %s\n", str);
return 0;
}
gets()
函数:用于从标准输入(键盘)读取一行字符串,包括空格,但不进行越界检查。
#include <stdio.h>
int main() {
char str[100];
printf("Enter a string: ");
gets(str);
printf("You entered: %s\n", str);
return 0;
}
fgets()
函数:用于从文件中读取一行字符串,包括空格,也可以用于从标准输入读取。
#include <stdio.h>
int main() {
char str[100];
printf("Enter a string: ");
fgets(str, sizeof(str), stdin);
printf("You entered: %s", str);
return 0;
}
- 字符串输出:
printf()
函数:用于将格式化的字符串输出到标准输出(屏幕)。
#include <stdio.h>
int main() {
char str[] = "Hello, world!";
printf("%s\n", str);
return 0;
}
puts()
函数:用于将字符串输出到标准输出(屏幕),自动添加换行符。
#include <stdio.h>
int main() {
char str[] = "Hello, world!";
puts(str);
return 0;
}
这些函数允许您在程序中输入和输出字符串。gets()
函数存在缓冲区溢出的安全问题,不推荐使用。使用 fgets()
函数可以更安全地读取一行字符串。在使用这些函数时,请确保适当处理换行符和空格等特殊字符。