深入理解工程编程中的文件操作与高级编程技巧
1. 文件操作的重要性
在工程编程中,文件操作是必不可少的一部分。它允许程序访问和处理大量的数据,这些数据通常存储在硬盘、光盘驱动器或磁带驱动器等大容量存储设备中。文件可以被视为一个巨大的数据数组,程序可以从这个数据源读取,写入数据,并在程序中创建新的实例或将现有实例消除。文件操作有两种主要模式:低级操作和高级操作。低级操作处理数据字节,而高级操作使用数据流来存储和检索变量值。
1.1 低级文件操作
低级文件操作直接处理数据字节。文件是操作系统(OS)资源,必须向操作系统请求访问它们。操作系统为文件分配文件名,文件名中允许的字符和长度取决于系统。通常,文件名有前缀和后缀,前缀描述文件的内容,后缀指示文件类型。
打开文件
要访问文件中的数据,首先必须打开文件。在C语言中,使用
open
函数来完成这个操作。函数格式如下:
fd = open("myfile.dat", mode);
其中:
-
fd是操作系统返回的整型文件描述符。 -
"myfile.dat"是文件名或包含文件名的字符串变量。 -
mode是一个整数,它决定了将对文件进行何种访问,如下所示:
| 模式 | 描述 |
|---|---|
| 0 | 读取访问 |
| 1 | 写入访问 |
| 2 | 读/写权限 |
如果文件不存在或用户没有访问权限,操作系统返回的文件描述符将是负数。因此,在尝试读取或写入文件之前,测试文件描述符是否为非负数是一个好主意。
读取文件
读取函数的格式为:
n_read = read(fd, buffer, nbytes);
其中:
-
fd是由open函数返回的文件描述符。 -
buffer是一个数组,数据读取将被转移到其中。 -
nbytes是要读取的字节数。 -
n_read是被读取的字节数。
写入文件
写入函数与读取类似,其格式为:
n_written = write(fd, buffer, nbytes);
其中:
-
fd是文件描述符。 -
buffer是一个待写入的数据数组。 -
nbytes是要写入的字节数。 -
n_written是实际写入的字节数。
文件必须已打开以供读取或读写,以进行读取操作;同样地,必须打开以写入或读写,以进行写入操作。如果返回变量
n_read
或
n_written
返回负值,则表示发生了错误。当
n_read
的值为零时,这表示没有更多数据可读。
1.2 创建和关闭文件
创建文件
如果你想创建一个新文件,可以使用
create
函数。这个函数的格式与
open
类似:
fd = create("myfile.dat", prot);
其中:
-
fd是一个由操作系统返回的整型文件描述符。 -
"myfile.dat"是文件名。 -
prot是一个整数,用来指定一个保护模式,通常用八进制常量表示。
模式示例如下:
| 模式 | 描述 |
|---|---|
| 0600 | 读/写权限 |
| 0400 | 只读权限 |
| 0200 | 只写权限 |
关闭文件
文件操作完成后,必须关闭文件以释放文件描述符。使用
close
函数来完成:
close(fd);
1.3 文件复制示例
下面是一个文件复制程序的示例,它从用户那里获取一个现有文件的文件名,并将其复制到用户指定的文件名:
#include <stdio.h>
void main() {
int in_file, out_file, buf_in;
char filename[20], buf[256];
/* 获取输入文件名并打开 */
printf("复制来源文件名?");
scanf("%s", filename);
in_file = open(filename, 0);
if (in_file < 0) {
printf("文件打开错误...退出!\n");
exit(0);
}
/* 获取输出文件名并创建 */
printf("复制目标文件名?");
scanf("%s", filename);
out_file = create(filename, 0600);
if (out_file < 0) {
printf("文件创建错误...退出!\n");
exit(0);
}
/* 读取最多256个字符,当buf_in=0时停止 */
while ((buf_in = read(in_file, buf, 256)) > 0)
write(out_file, buf, buf_in);
close(in_file);
close(out_file);
}
1.4 高级文件操作
高级文件操作与低级操作的区别在于,数据传输是通过缓冲和解码进行的。高级文件I/O函数使用了所谓的流。流只是数据传输的路径,以便它可以从未存储区域移动到另一个区域。流通过文件指针而不是文件描述符来识别。
声明文件指针
要使用流访问文件,必须声明一个指向流的指针:
FILE *filep;
FILE
的定义可以在
stdio.h
头文件中找到。我们使用
fopen
函数打开一个流:
filep = fopen("myfile.dat", mode);
其中:
-
filep是操作系统返回的文件指针。 -
"myfile.dat"是文件名。 -
mode是一个字符串,用来指定访问模式。
最常用的模式如下:
| 模式 | 描述 |
|---|---|
| “r” | 只读权限 |
| “w” | 只写权限(如果文件不存在则创建,如果文件已存在则覆盖) |
| “a” | 追加数据到文件 |
使用
fprintf
和
fscanf
fprintf
和
fscanf
用于格式化输入和输出。这些函数与它们的标准I/O对应物
printf
和
scanf
相同,唯一的区别是每个函数的第一个参数是流。例如,以下程序用于将
x
的平方根表从
x=0
写到
1.0
,以
.1
的增量递增到文件
xroots.tab
:
#include <stdio.h>
#include <math.h>
void main() {
FILE *fx;
float x = 0;
fx = fopen("xroots.tab", "w");
if (fx == NULL) {
printf("文件打开错误...退出。\n");
exit(0);
}
while (x <= 1.0) {
fprintf(fx, "%f\t\t%f\n", x, sqrt(x));
x += 0.1;
}
fclose(fx);
}
1.5 文件读取示例
以下程序展示了如何使用
fscanf
读取程序生成的“平方根表文件”中的数据,并将其立即使用
printf
发送到控制台:
#include <stdio.h>
void main() {
FILE *fx;
float x, rootx;
fx = fopen("xroots.tab", "r");
if (fx == NULL) {
printf("文件打开错误...退出。\n");
exit(0);
}
while (fscanf(fx, "%f\t\t%f\n", &x, &rootx) > 0)
printf("%f\t\t%f\n", x, rootx);
fclose(fx);
}
2. 控制台绘图
控制台绘图涉及将数据以图形方式显示,而不是以表格方式显示。这在许多工程应用中非常有用,因为工程师每天必须处理大量的数据。通过控制台绘图,可以更直观地展示数据,便于分析和设计。
2.1 控制台绘图原理
为了在控制台上绘制正弦波,我们需要理解标准ASCII显示的布局。标准ASCII显示通常是80个字符宽,24行高,这将是我们的“虚拟坐标纸”的布局。我们可以通过连续输出换行符(
\n
)来产生一个垂直轴,并通过在每行中按比例放置与函数值成比例的空格来放置绘制的值。
2.2 正弦波绘制示例
选择一个绘图宽度为40个空格。我们知道正弦和余弦的值在-1到1之间变化;因此,当函数值为-1时,我们不希望有任何空格。当函数值为零时,我们希望空格为20,当函数值为1时,我们希望空格为40。声明一个整型变量
P
,它将是需要移动的空格数,以便下面的C语言语句能够产生一个与
x
的正弦函数所需的空格成比例的
P
值:
P = (20 * sin(x)) + 20;
完整的正弦波绘制程序
以下是一个完整的正弦波绘制程序:
#include <stdio.h>
#include <math.h>
void main(void) {
float w, theta, Tstart, Tend, dt, T;
int fen, P, spc;
printf("»");
scanf("%f %f %f %f %f", &w, &theta, &Tstart, &Tend, &dt);
printf("%f %f %f %f %f\n\n", w, theta, Tstart, Tend, dt);
for (T = Tstart; T <= Tend; T += dt) {
printf("%5.2f ", T);
P = (int)(20 * sin(w * T + theta));
for (spc = 0; spc < 40; spc++) {
if (spc == P) {
printf("*");
continue;
}
printf(" ");
}
printf("\n");
}
}
2.3 添加坐标轴
为了使图表更易于阅读,我们可以添加坐标轴。以下是一个带有坐标轴的正弦波绘制程序:
#include <stdio.h>
#include <math.h>
void main(void) {
float w, theta, Tstart, Tend, dt, T;
int fen, P, spc;
printf("»");
scanf("%f %f %f %f %f", &w, &theta, &Tstart, &Tend, &dt);
printf("%f %f %f %f %f\n\n", w, theta, Tstart, Tend, dt);
/* 打印 y 轴 */
printf(" +-----------------------------+\n");
for (T = Tstart; T <= Tend; T += dt) {
printf("%5.2f ", T);
P = (int)(20 * sin(w * T + theta));
for (spc = 0; spc < 40; spc++) {
if (spc == P) {
printf("*");
continue;
}
printf(" ");
}
printf("\n");
}
/* 打印 x 轴 */
printf(" +-----------------------------+\n");
}
2.4 控制台绘图的优化
为了使程序更高效,可以减少不必要的循环和条件判断。例如,可以使用
memset
函数来初始化一行的空格,然后在适当的位置放置
*
号。这不仅提高了程序的效率,还使代码更简洁。
优化后的正弦波绘制程序
#include <stdio.h>
#include <math.h>
#include <string.h>
void main(void) {
float w, theta, Tstart, Tend, dt, T;
char line[41];
printf("»");
scanf("%f %f %f %f %f", &w, &theta, &Tstart, &Tend, &dt);
printf("%f %f %f %f %f\n\n", w, theta, Tstart, Tend, dt);
/* 打印 y 轴 */
printf(" +-----------------------------+\n");
for (T = Tstart; T <= Tend; T += dt) {
memset(line, ' ', sizeof(line));
P = (int)(20 * sin(w * T + theta));
if (P >= 0 && P < 40) {
line[P] = '*';
}
printf("%5.2f %s\n", T, line);
}
/* 打印 x 轴 */
printf(" +-----------------------------+\n");
}
2.5 流程图说明
为了更好地理解控制台绘图的流程,可以使用流程图进行说明。以下是一个控制台绘图的流程图:
graph TD;
A[开始] --> B[输入参数 w, theta, Tstart, Tend, dt];
B --> C[初始化 y 轴];
C --> D[循环 T 从 Tstart 到 Tend];
D --> E[计算 P = (int)(20 * sin(w * T + theta))];
E --> F[初始化一行空格];
F --> G[如果 P >= 0 且 P < 40,则在 P 位置放置 *];
G --> H[输出当前行];
H --> I[结束循环];
I --> J[打印 x 轴];
J --> K[结束];
通过这种方式,可以清晰地展示程序的逻辑和流程,帮助读者更好地理解和实现控制台绘图。
3. 潮汐计算程序
潮汐计算程序在许多海洋工程应用中都有使用,被称为建模程序,因为它模拟了物理现象的行为。建模与仿真相关,在这种情况下,计算机用于模拟一个过程或设备。计算机建模和仿真的技术是工程实践的主要方面,因为它们允许我们在制造物理原型之前在实验室中设计和分析系统。
3.1 潮汐时间计算
潮汐时间计算函数
TideTime
用于计算在某个高度的时间。它不接受任何参数,并向用户查询所需的潮汐高度。然后,它调用
getParams
函数以从用户那里获取必要的年鉴数据。这个函数会输入用户当天的高低潮时间及高度。这些数据可以在年鉴中找到,但更复杂的潮汐计算程序会整合这类信息的数据库,因此用户无需输入这些信息。
float TideTime(void) {
float hi, h2, tl, t2, hx;
printf("\nEnter the tidal height you want the time for:");
scanf("%f", &hx);
getParams(&hi, &h2, &tl, &t2);
return (t2 + (tl - t2) * acos(1.0 - 2.0 * ((hx - h2) / (hi - h2))) / 180.0);
}
3.2 潮汐高度计算
潮汐高度计算函数
TideHeight
用于计算在某个时间的高度。它不接受任何参数,但作为
printf
在主函数中的一个参数被调用。
TideHeight
要求用户以两个整数的形式输入所需的时间,即小时和分钟。然后它调用
getParams
函数获取年鉴数据。之后,
TideHeight
返回计算出的潮高浮点值。
float TideHeight(void) {
int hour, minute;
float R, high1, high2, low1, low2, timex;
printf("\n请输入您想知道潮汐的时间(小时):");
scanf("%d", &hour);
printf("请输入您想知道潮汐的时间(分钟):");
scanf("%d", &minute);
getParams(&high1, &high2, &low1, &low2);
timex = TimeFloat(hour, minute);
return (((high1 - high2) / 2.0) - ((R / 2.0) * cos(180.0 * ((timex - low2) / (low1 - low2))));
}
3.3 潮汐程序的用户界面
为了使潮汐程序更友好,可以通过让程序通过名称询问图表参数来轻松实现。另一个选择是允许用户选择其他函数进行绘图。例如,用户可以选择绘制正弦或余弦图表。以下是改进后的用户界面示例:
#include <stdio.h>
#include <math.h>
void main(void) {
unsigned char more(void);
void TimeOut(float TimeIn);
float TideTime(void);
float TideHeight(void);
while (1) {
printf("\n潮汐时间,高度或退出 (t/h/q)?");
switch (getch()) {
case 't':
case 'T':
TimeOut(TideTime());
if (!more()) exit(0);
break;
case 'h':
case 'H':
printf("\n潮高将会是: %f 英尺。\n", TideHeight());
if (!more()) exit(0);
break;
case 'q':
case 'Q':
exit(0);
default:
printf("\n请输入 't', 'h' 或 'q'。");
}
}
}
unsigned char more(void) {
for (;;) {
printf("再次计算 (y/n)?");
switch (getch()) {
case 'y':
case 'Y':
return (1);
case 'n':
case 'N':
return (0);
}
}
}
3.4 潮汐程序的测试
为了测试程序,可以使用报纸上的潮汐数据。以下数据于1997年5月29日在Orlando Sentinel上发布:
- 佛罗里达州代托纳海滩单位是英尺
- 低潮:1997年5月29日周四晚上8:53 PM EDT 0.08英尺
- 高潮:1997年5月30日周五凌晨3:06 AM EDT 4.10英尺
运行程序并输入这些数据,可以得到潮汐时间或高度的计算结果。例如,输入潮汐高度为2英尺,程序将输出潮汐将在何时达到这个高度。同样,输入时间为20:43,程序将输出此时的潮汐高度。
3.5 潮汐程序的进一步优化
为了使程序更高效,可以减少对函数的调用,或者可能完全消除某些函数。例如,可以通过询问用户是否想要输入新的潮汐数据,并在重用旧数据时跳过对
getParams
的调用。另一个改进是让程序输出潮汐高度与时间的图表。这可以通过使用控制台绘图技术来实现。
添加图表绘制功能
以下是一个添加了图表绘制功能的潮汐程序示例:
#include <stdio.h>
#include <math.h>
#include <string.h>
void main(void) {
unsigned char more(void);
void TimeOut(float TimeIn);
float TideTime(void);
float TideHeight(void);
while (1) {
printf("\n潮汐时间,高度或退出 (t/h/q)?");
switch (getch()) {
case 't':
case 'T':
TimeOut(TideTime());
if (!more()) exit(0);
break;
case 'h':
case 'H':
printf("\n潮高将会是: %f 英尺。\n", TideHeight());
if (!more()) exit(0);
break;
case 'q':
case 'Q':
exit(0);
default:
printf("\n请输入 't', 'h' 或 'q'。");
}
/* 绘制潮汐高度与时间的图表 */
drawTideChart();
}
}
unsigned char more(void) {
for (;;) {
printf("再次计算 (y/n)?");
switch (getch()) {
case 'y':
case 'Y':
return (1);
case 'n':
case 'N':
return (0);
}
}
}
void drawTideChart() {
float w, theta, Tstart, Tend, dt, T;
char line[41];
printf("»");
scanf("%f %f %f %f %f", &w, &theta, &Tstart, &Tend, &dt);
printf("%f %f %f %f %f\n\n", w, theta, Tstart, Tend, dt);
/* 打印 y 轴 */
printf(" +-----------------------------+\n");
for (T = Tstart; T <= Tend; T += dt) {
memset(line, ' ', sizeof(line));
int P = (int)(20 * sin(w * T + theta));
if (P >= 0 && P < 40) {
line[P] = '*';
}
printf("%5.2f %s\n", T, line);
}
/* 打印 x 轴 */
printf(" +-----------------------------+\n");
}
通过这种方式,用户不仅可以计算潮汐时间和高度,还可以直观地看到潮汐变化的趋势,帮助更好地理解和分析潮汐数据。
4. 工程编程中的数据类型与运算符
在工程编程中,正确理解和使用数据类型和运算符是至关重要的。不同的数据类型和运算符会影响程序的性能和准确性。以下是几种常见的数据类型和运算符及其使用方法。
4.1 数据类型
C语言中有几种基本的数据类型,包括整型、浮点型、双精度型、字符型和无类型。每种数据类型都有其特定的用途和限制。例如,整型用于表示整数,浮点型用于表示实数,字符型用于表示单个字符。
| 类型 | 描述 | 示例 |
|---|---|---|
| int | 整数 | … -2, -1, 0, 1, 2 … |
| float | 浮点数 | 3.1416, -0.0003 |
| double | 双精度浮点数 | 更大的值 |
| char | 单字节字符 | A, b, &, * |
4.2 运算符
C语言中有多种运算符,包括算术运算符、逻辑运算符、关系运算符和一元运算符。算术运算符用于数值运算,逻辑运算符用于布尔运算,关系运算符用于比较,一元运算符用于单个变量或常量。
| 运算符 | 描述 |
|---|---|
| + | 加法 |
| - | 减法 |
| * | 乘法 |
| / | 除法 |
| % | 模运算符 |
| == | 等价 |
| != | 不等于 |
| < | 小于 |
| > | 大于 |
| <= | 小于或等于 |
| >= | 大于或等于 |
4.3 数据类型转换
在C语言中,数据类型转换可以通过强制类型转换操作符
()
来实现。例如,以下操作将
int
类型的
i
转换为
float
类型:
(float)i;
4.4 运算符优先级
运算符优先级决定了编译器评估操作的顺序。了解这些规则可以帮助避免表达式评估中的错误。例如,乘法优先于加法,并且是从左到右结合的。以下是C语言运算符优先级的表格:
| 运算符 | 结合性 |
|---|---|
| () | 左到右 |
| * / % | 左到右 |
| + - | 左到右 |
| == != < > <= >= | 左到右 |
| && |
5. 工程编程中的函数与子程序
在工程编程中,函数和子程序是非常重要的工具。它们不仅可以简化代码结构,还可以提高代码的可读性和可维护性。以下是函数和子程序的基本定义和使用方法。
5.1 函数定义
函数是一个独立的程序单元,用于执行特定的任务。所有C程序必须有一个
main
函数,因为它被定义为程序的入口点或开始的地方。C语言中的函数定义语法如下:
<类型> <标签>(<参数列表>) {
<语句>
}
例如,以下是一个计算矩形坐标到极坐标转换的函数:
void rec_to_polar(float x, float y, float *r, float *theta) {
*r = sqrt(x * x + y * y);
*theta = atan(y / x);
}
5.2 函数调用
函数调用是通过函数名和参数列表来实现的。例如,以下是一个调用
rec_to_polar
函数的示例:
float r, theta;
rec_to_polar(1.0, 1.0, &r, &theta);
printf("(1,1)->(%f, %f)\n", r, theta);
5.3 子程序定义
子程序是小型程序,可以像函数一样被调用,但没有返回值。子程序定义的语法如下:
子程序<label>(<参数列表>) {
<语句>
}
例如,以下是一个Fortran子程序,用于将华氏温度转换为摄氏温度:
subroutine FtoC(inTemp, outTemp)
real :: inTemp, outTemp
outTemp = (5.0 / 9.0) * (inTemp - 32.0)
end subroutine FtoC
5.4 函数与子程序的区别
C语言和Fortran在函数和子程序方面有一些重要的区别。C语言通过值传递数据,而Fortran通过引用(地址)传递数据。因此,在Fortran中,变量的地址被传递给函数,因此在函数中对变量所做的任何赋值都会反映在调用程序中声明的变量上。
C语言函数调用示例
float a_real, a_imag, b_real, b_imag;
a_real = 1.0; a_imag = 1.0;
b_real = 2.95; b_imag = -1.0;
c_mult(a_real, a_imag, b_real, b_imag, &c_real, &c_imag);
printf("%f %+fi\n", c_real, c_imag);
Fortran子程序调用示例
real :: a_real, a_imag, b_real, b_imag, c_real, c_imag
a_real = 1.0; a_imag = 1.0;
b_real = 2.95; b_imag = -1.0;
call c_mult(a_real, a_imag, b_real, b_imag, c_real, c_imag)
print *, c_real, c_imag
通过这种方式,可以更灵活地处理不同类型的数据,并在程序中实现复杂的计算和操作。
6. 工程编程中的结构体与指针
结构体和指针是C语言中非常强大的工具,尤其是在处理复杂数据结构时。结构体用于表示不同类型变量的组合,而指针用于直接访问内存地址。
6.1 结构体定义
结构体是一种用于表示不同类型变量组合的机制。在C语言中,工程师最常用的结构体是用于坐标系统和复数的结构体。以复数为例,可以定义一个C结构体,允许我们声明一个具有两个部分或成员的单一复数变量。
struct complex {
float real, imag;
};
struct complex a, b;
a.real = 1.0;
a.imag = 1.0;
b.real = 2.95;
b.imag = -1.0;
6.2 指针定义
指针是一个其值为地址的变量。C语言中的指针是类型敏感的,因为类型决定了变量在字节数方面的大小。要声明一个指针变量,我们使用一元指针操作符
*
在声明中的变量名前面。
int *pk;
pk = &k;
6.3 结构体与指针结合
可以定义指向结构体的指针。为此,我们在结构体变量声明中使用指针操作符
*
。使用复杂的结构体,我们可以定义一个指向复杂结构体的指针:
struct complex *c_ptr;
现在变量
c_ptr
是一个指向
complex
结构体的指针。在编写操作结构体的函数时,结构体指针非常有用。
乘以两个复数的函数
void c_mult(struct complex a, struct complex b, struct complex *c) {
c->real = (a.real * b.real) - (a.imag * b.imag);
c->imag = (a.real * b.imag) + (a.imag * b.real);
}
通过这种方式,可以更高效地处理复数运算,并在程序中实现复杂的数学操作。
6.4 指针与数组结合
指针也可以与数组结合使用,以更灵活地访问和操作数组元素。例如,以下代码展示了如何使用指针访问数组元素:
float x[5] = {1.0, 2.0, 3.0, 4.0, 5.0};
float *px = x;
for (int i = 0; i < 5; i++) {
printf("x[%d] = %f\n", i, *(px + i));
}
通过这种方式,可以更灵活地处理数组,并在程序中实现复杂的计算和操作。
7. 工程编程中的数组与字符串
数组和字符串是C语言中非常常见的数据结构。它们用于存储和处理多个相同类型的元素。以下是数组和字符串的基本定义和使用方法。
7.1 数组定义
C语言中的数组有几种不同的定义方式。最简单的方式是通过声明一个变量并使用方括号表示法给它定义维度。
float x[5][5], y[3], z[3][3][3];
char s[10];
int I[2][2];
数组的索引从零开始,一直到数组大小减一为止。例如,以下是一个5x5的浮点数矩阵
x
的索引:
graph TD;
A[x[0][0]] --> B[x[0][1]];
B --> C[x[1][0]];
C --> D[x[1][1]];
D --> E[x[2][0]];
E --> F[x[2][1]];
F --> G[x[3][0]];
G --> H[x[3][1]];
H --> I[x[4][0]];
I --> J[x[4][1]];
7.2 字符串定义
字符数组在C语言中被称为字符串,并且在使用时需要特别考虑。以下是一个字符数组声明的示例:
char s[10];
声明为10个字符元素分配了空间;然而,第10个元素保留给空字符
\0
。字符串常量由引号定义,例如:
char *str = "Hello World!";
初始化字符数组可以使用以下方式:
char str[] = "Hello World!";
字符串是字符数组;因此,像数值数组一样,我们不能用它们形成算术、逻辑或关系表达式。然而,我们可以用字符串的各个元素形成表达式。我们也可以使用变量来索引字符串。
字符串操作示例
以下是一组用于操作字符串的函数:
#include <stdio.h>
#include <string.h>
void main(void) {
char str1[50], str2[50];
printf("输入第一个字符串:");
scanf("%s", str1);
printf("输入第二个字符串:");
scanf("%s", str2);
strcat(str1, str2);
printf("连接后的字符串:%s\n", str1);
strcpy(str1, str2);
printf("复制后的字符串:%s\n", str1);
int len = strlen(str1);
printf("字符串长度:%d\n", len);
int cmp = strcmp(str1, str2);
if (cmp == 0) {
printf("两个字符串相等\n");
} else {
printf("两个字符串不相等\n");
}
}
通过这种方式,可以更灵活地处理字符串,并在程序中实现复杂的字符串操作。
8. 工程编程中的控制结构
控制结构是编程中的核心部分,用于控制程序的执行流程。C语言提供了多种控制结构,包括
if
语句、
for
循环、
do-while
循环和
switch
语句。以下是这些控制结构的基本定义和使用方法。
8.1
if
语句
if
语句用于根据条件执行特定的代码块。它有多种形式,包括
if
、
if-else
和
if-else if
。以下是
if
语句的基本语法:
if (<表达式>) {
<语句>
}
例如,以下代码展示了如何使用
if
语句:
if (a == 0) {
printf("a 等价于 0\n");
}
8.2
for
循环
for
循环用于在特定范围内执行语句。它有三个部分:初始化表达式、测试表达式和增量表达式。以下是
for
循环的基本语法:
for (初始化; 测试; 增量) {
<语句>
}
例如,以下代码展示了如何使用
for
循环计算平方根表:
#include <stdio.h>
#include <math.h>
void main() {
FILE *fx;
float x = 0;
fx = fopen("xroots.tab", "w");
if (fx == NULL) {
printf("文件打开错误...退出。\n");
exit(0);
}
while (x <= 1.0) {
fprintf(fx, "%f\t\t%f\n", x, sqrt(x));
x += 0.1;
}
fclose(fx);
}
8.3
do-while
循环
do-while
循环用于先执行语句,然后评估条件表达式以确定是否继续循环。以下是
do-while
循环的基本语法:
do {
<语句>
} while (<表达式>);
例如,以下代码展示了如何使用
do-while
循环:
do {
printf("请输入命令: ");
switch (getchar()) {
case 'f':
forward();
break;
case 's':
stop();
break;
case 'l':
left();
break;
case 'r':
right();
break;
case 'b':
backward();
break;
case 'e':
stop();
exit();
break;
default:
printf("请输入 f, s, l, r, b 或 e!\n");
}
} while (1);
8.4
switch
语句
switch
语句用于根据多个条件执行特定的代码块。它有多个
case
语句和一个
default
语句。以下是
switch
语句的基本语法:
switch (<表达式>) {
case <常量表达式1>:
<语句>
break;
case <常量表达式2>:
<语句>
break;
...
default:
<语句>
}
例如,以下代码展示了如何使用
switch
语句:
switch (getchar()) {
case 'f':
forward();
break;
case 's':
stop();
break;
case 'l':
left();
break;
case 'r':
right();
break;
case 'b':
backward();
break;
case 'e':
stop();
exit();
break;
default:
printf("请输入 f, s, l, r, b 或 e!\n");
}
通过这种方式,可以更灵活地处理用户输入,并在程序中实现复杂的控制逻辑。
9. 工程编程中的错误处理
在工程编程中,错误处理是确保程序可靠性和稳定性的关键。常见的错误包括编译时错误、运行时错误和逻辑错误。以下是这些错误的基本定义和处理方法。
9.1 编译时错误
编译时错误是指编译器在编译过程中检测到的错误。这些错误通常包括语法错误、类型不匹配和宏错误。以下是编译时错误的一些示例:
- 括号不匹配
- 变量未声明
- 保留字误用
编译时错误处理
为了处理编译时错误,程序员应仔细检查代码的语法和逻辑。
9.2 运行时错误
运行时错误是指程序在执行过程中发生的错误。这些错误通常包括除以零、数组越界和非法内存访问。以下是运行时错误的一些示例:
- 除以零
- 数组越界
- 非法内存访问
运行时错误处理
为了处理运行时错误,程序员应加入适当的错误检查和异常处理机制。例如,在除法运算中加入检查以避免除以零:
if (denominator != 0) {
result = numerator / denominator;
} else {
printf("Error: Division by zero.\n");
exit(1);
}
9.3 逻辑错误
逻辑错误是指程序逻辑不正确,导致程序输出不符合预期。这些错误通常不易检测,因为程序仍然可以正常编译和运行。以下是逻辑错误的一些示例:
- 错误的条件判断
- 错误的数学公式
- 错误的循环终止条件
逻辑错误处理
为了处理逻辑错误,程序员应仔细检查算法和代码逻辑,确保程序按照预期的逻辑执行。例如,检查循环终止条件是否正确:
for (i = 0; i < n; i++) {
// 循环体
}
确保
n
的值是正确的,并且循环不会无限执行。
10. 工程编程中的调试技巧
调试是确保程序正确性和可靠性的重要步骤。以下是一些常用的调试技巧:
10.1 使用打印语句
在程序中加入打印语句,可以实时查看变量的值和程序的执行路径。例如:
printf("Variable x has value: %f\n", x);
10.2 使用调试器
调试器是一种强大的工具,允许逐行执行程序并检查变量的值。例如,在使用GNU调试器(GDB)时:
-
编译程序时加入调试信息:
gcc -g program.c -o program -
启动调试器:
gdb program -
设置断点:
break main -
运行程序:
run -
逐行执行:
next -
检查变量值:
print variable_name
10.3 使用日志记录
日志记录可以帮助记录程序的执行路径和变量的值。例如,使用
fprintf
将调试信息写入日志文件:
FILE *logFile = fopen("debug.log", "a");
if (logFile != NULL) {
fprintf(logFile, "Function called with parameter: %f\n", param);
fclose(logFile);
}
11. 工程编程中的函数优化
函数优化是提高程序性能和效率的关键。以下是一些优化函数的技巧:
11.1 减少函数调用
通过减少不必要的函数调用,可以提高程序的执行速度。例如,将多个
printf
语句合并为一个:
// 不优化
printf("Value of x: %f\n", x);
printf("Value of y: %f\n", y);
// 优化
printf("Values of x and y: %f, %f\n", x, y);
11.2 使用内联函数
内联函数可以减少函数调用的开销。使用
inline
关键字可以将函数体直接插入调用点:
inline float square(float x) {
return x * x;
}
11.3 避免重复计算
避免在循环中进行重复计算,可以显著提高程序的效率。例如,将常量计算移到循环外部:
// 不优化
for (i = 0; i < n; i++) {
result[i] = sin(x) * cos(y);
}
// 优化
float sin_x = sin(x);
float cos_y = cos(y);
for (i = 0; i < n; i++) {
result[i] = sin_x * cos_y;
}
12. 工程编程中的文件操作优化
文件操作的优化可以显著提高程序的性能。以下是一些优化文件操作的技巧:
12.1 使用缓冲区
通过使用缓冲区,可以减少文件读写的次数,提高效率。例如,使用
fread
和
fwrite
批量读写数据:
char buffer[BUFSIZ];
size_t bytes_read = fread(buffer, 1, BUFSIZ, filep);
fwrite(buffer, 1, bytes_read, outfile);
12.2 使用二进制模式
使用二进制模式可以避免文件操作中的格式转换,提高效率。例如,使用
rb
和
wb
模式打开文件:
FILE *infile = fopen("input.bin", "rb");
FILE *outfile = fopen("output.bin", "wb");
12.3 减少文件打开和关闭次数
尽量减少文件的打开和关闭次数,可以提高程序的效率。例如,将文件打开和关闭操作移到循环外部:
FILE *filep = fopen("data.txt", "r");
if (filep == NULL) {
printf("文件打开错误...退出。\n");
exit(0);
}
while (fscanf(filep, "%f\t\t%f\n", &x, &rootx) > 0) {
printf("%f\t\t%f\n", x, rootx);
}
fclose(filep);
12.4 使用内存映射文件
内存映射文件可以将文件内容直接映射到内存中,提高读写速度。例如,使用
mmap
函数:
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
int fd = open("data.txt", O_RDONLY);
off_t length = lseek(fd, 0, SEEK_END);
char *mapped_data = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0);
for (int i = 0; i < length; i++) {
printf("%c", mapped_data[i]);
}
munmap(mapped_data, length);
close(fd);
13. 工程编程中的数据结构优化
数据结构的优化可以显著提高程序的性能和效率。以下是一些优化数据结构的技巧:
13.1 使用合适的数据结构
选择合适的数据结构可以提高程序的效率。例如,使用哈希表代替线性搜索:
#include <stdlib.h>
struct hash_entry {
int key;
float value;
struct hash_entry *next;
};
struct hash_table {
struct hash_entry **entries;
int size;
};
struct hash_table *create_hash_table(int size) {
struct hash_table *table = malloc(sizeof(struct hash_table));
table->entries = calloc(size, sizeof(struct hash_entry *));
table->size = size;
return table;
}
void insert_hash_table(struct hash_table *table, int key, float value) {
int index = key % table->size;
struct hash_entry *entry = malloc(sizeof(struct hash_entry));
entry->key = key;
entry->value = value;
entry->next = table->entries[index];
table->entries[index] = entry;
}
float lookup_hash_table(struct hash_table *table, int key) {
int index = key % table->size;
struct hash_entry *entry = table->entries[index];
while (entry != NULL) {
if (entry->key == key) {
return entry->value;
}
entry = entry->next;
}
return -1;
}
13.2 使用位操作
位操作可以显著提高程序的效率。例如,使用位移操作代替乘法和除法:
// 不优化
result = x * 8;
// 优化
result = x << 3;
13.3 使用联合体
联合体可以节省内存空间。例如,使用联合体代替多个结构体:
union data {
int integer;
float floating_point;
char character;
};
union data d;
d.integer = 10;
printf("Integer value: %d\n", d.integer);
d.floating_point = 3.14;
printf("Floating point value: %f\n", d.floating_point);
d.character = 'A';
printf("Character value: %c\n", d.character);
14. 工程编程中的算法优化
算法优化是提高程序性能的关键。以下是一些优化算法的技巧:
14.1 使用更高效的算法
选择更高效的算法可以显著提高程序的性能。例如,使用快速排序代替冒泡排序:
void quicksort(int arr[], int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quicksort(arr, low, pi - 1);
quicksort(arr, pi + 1, high);
}
}
int partition(int arr[], int low, int high) {
int pivot = arr[high];
int i = (low - 1);
for (int j = low; j <= high - 1; j++) {
if (arr[j] < pivot) {
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
return (i + 1);
}
void swap(int *a, int *b) {
int t = *a;
*a = *b;
*b = t;
}
14.2 使用动态规划
动态规划可以显著减少重复计算,提高算法效率。例如,使用动态规划计算斐波那契数列:
int fibonacci(int n) {
if (n <= 1) {
return n;
}
int fib[n + 1];
fib[0] = 0;
fib[1] = 1;
for (int i = 2; i <= n; i++) {
fib[i] = fib[i - 1] + fib[i - 2];
}
return fib[n];
}
14.3 使用缓存
缓存可以显著减少重复计算,提高算法效率。例如,使用缓存计算平方根:
#include <math.h>
#include <stdlib.h>
float cached_sqrt(float x, float cache[]) {
int index = (int)(x * 100);
if (cache[index] == 0) {
cache[index] = sqrt(x);
}
return cache[index];
}
int main() {
float cache[10000] = {0};
float x = 2.5;
printf("Square root of %f is %f\n", x, cached_sqrt(x, cache));
return 0;
}
15. 工程编程中的性能分析
性能分析是评估程序性能的重要步骤。以下是一些常用的性能分析工具和技术:
15.1 使用计时器
使用计时器可以测量程序的执行时间。例如,使用
clock
函数:
#include <time.h>
clock_t start, end;
double cpu_time_used;
start = clock();
// 执行代码
end = clock();
cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;
printf("CPU time used: %f seconds\n", cpu_time_used);
15.2 使用性能分析工具
性能分析工具可以帮助找出程序中的瓶颈。例如,使用GNU gprof工具:
-
编译程序时加入性能分析选项:
gcc -pg program.c -o program -
运行程序:
./program -
生成性能报告:
gprof program > profile.txt
15.3 使用内存分析工具
内存分析工具可以帮助找出内存泄漏和其他内存问题。例如,使用Valgrind工具:
-
运行程序:
valgrind --leak-check=yes ./program - 查看内存分析报告
16. 工程编程中的代码优化
代码优化是提高程序性能和效率的重要手段。以下是一些优化代码的技巧:
16.1 使用内联函数
内联函数可以减少函数调用的开销。使用
inline
关键字可以将函数体直接插入调用点:
inline float square(float x) {
return x * x;
}
16.2 使用宏定义
宏定义可以减少代码冗余并提高代码可读性。例如,使用宏定义计算平方:
#define SQUARE(x) ((x) * (x))
int main() {
float x = 5.0;
printf("Square of %f is %f\n", x, SQUARE(x));
return 0;
}
16.3 使用指针操作
指针操作可以提高代码效率。例如,使用指针访问数组元素:
float x[5] = {1.0, 2.0, 3.0, 4.0, 5.0};
float *px = x;
for (int i = 0; i < 5; i++) {
printf("x[%d] = %f\n", i, *(px + i));
}
16.4 使用位操作
位操作可以显著提高代码效率。例如,使用位移操作代替乘法和除法:
// 不优化
result = x * 8;
// 优化
result = x << 3;
16.5 使用局部变量
使用局部变量可以减少全局变量的使用,提高代码效率。例如,将全局变量改为局部变量:
void compute_sum(float *array, int n) {
float sum = 0.0;
for (int i = 0; i < n; i++) {
sum += array[i];
}
printf("Sum: %f\n", sum);
}
17. 工程编程中的安全编程
安全编程是确保程序可靠性和安全性的重要步骤。以下是一些安全编程的技巧:
17.1 使用边界检查
使用边界检查可以避免数组越界和其他内存访问错误。例如,使用
if
语句检查数组边界:
if (index >= 0 && index < array_size) {
array[index] = value;
} else {
printf("Error: Index out of bounds.\n");
}
17.2 使用输入验证
使用输入验证可以避免非法输入导致的错误。例如,使用
if
语句检查输入范围:
if (input >= min_value && input <= max_value) {
// 处理输入
} else {
printf("Error: Input out of range.\n");
}
17.3 使用安全函数
使用安全函数可以避免常见的编程错误。例如,使用
fgets
代替
gets
:
char buffer[100];
fgets(buffer, sizeof(buffer), stdin);
17.4 使用内存管理
良好的内存管理可以避免内存泄漏和其他内存问题。例如,使用
malloc
和
free
管理动态内存:
int *array = malloc(array_size * sizeof(int));
if (array == NULL) {
printf("Error: Memory allocation failed.\n");
exit(1);
}
// 使用数组
free(array);
17.5 使用异常处理
使用异常处理可以捕获和处理程序中的错误。例如,使用
try-catch
结构(在支持的语言中):
try {
// 可能抛出异常的代码
} catch (ExceptionType e) {
// 处理异常
}
18. 工程编程中的代码可读性
代码可读性是确保代码易于维护和理解的重要因素。以下是一些提高代码可读性的技巧:
18.1 使用注释
使用注释可以解释代码的功能和逻辑。例如,使用
/* */
或
//
注释代码:
/* This function calculates the sum of an array */
void compute_sum(float *array, int n) {
float sum = 0.0;
for (int i = 0; i < n; i++) {
sum += array[i];
}
printf("Sum: %f\n", sum);
}
18.2 使用有意义的变量名
使用有意义的变量名可以提高代码的可读性。例如,使用
total_sum
代替
ts
:
float total_sum = 0.0;
for (int i = 0; i < n; i++) {
total_sum += array[i];
}
18.3 使用一致的代码格式
使用一致的代码格式可以提高代码的可读性。例如,使用统一的缩进和空格:
void compute_sum(float *array, int n) {
float total_sum = 0.0;
for (int i = 0; i < n; i++) {
total_sum += array[i];
}
printf("Sum: %f\n", total_sum);
}
18.4 使用函数分解
将复杂的功能分解为多个函数可以提高代码的可读性和可维护性。例如,将计算和输出分开:
float compute_sum(float *array, int n) {
float total_sum = 0.0;
for (int i = 0; i < n; i++) {
total_sum += array[i];
}
return total_sum;
}
void print_sum(float sum) {
printf("Sum: %f\n", sum);
}
int main() {
float array[] = {1.0, 2.0, 3.0, 4.0, 5.0};
int n = sizeof(array) / sizeof(array[0]);
float sum = compute_sum(array, n);
print_sum(sum);
return 0;
}
18.5 使用宏定义
使用宏定义可以提高代码的可读性和可维护性。例如,使用宏定义计算平方:
#define SQUARE(x) ((x) * (x))
int main() {
float x = 5.0;
printf("Square of %f is %f\n", x, SQUARE(x));
return 0;
}
19. 工程编程中的内存管理
内存管理是确保程序可靠性和效率的重要步骤。以下是一些内存管理的技巧:
19.1 使用动态内存分配
动态内存分配可以灵活地管理内存。例如,使用
malloc
和
free
管理动态内存:
int *array = malloc(array_size * sizeof(int));
if (array == NULL) {
printf("Error: Memory allocation failed.\n");
exit(1);
}
// 使用数组
free(array);
19.2 使用静态变量
静态变量可以保留其值,直到程序结束。例如,使用静态变量保留计数器:
static int counter = 0;
void increment_counter() {
counter++;
}
int main() {
increment_counter();
increment_counter();
printf("Counter: %d\n", counter);
return 0;
}
19.3 使用自动变量
自动变量在函数执行期间具有值。例如,使用自动变量管理局部变量:
void compute_sum(float *array, int n) {
float total_sum = 0.0;
for (int i = 0; i < n; i++) {
total_sum += array[i];
}
printf("Sum: %f\n", total_sum);
}
19.4 使用内存映射文件
内存映射文件可以将文件内容直接映射到内存中,提高读写速度。例如,使用
mmap
函数:
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
int fd = open("data.txt", O_RDONLY);
off_t length = lseek(fd, 0, SEEK_END);
char *mapped_data = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0);
for (int i = 0; i < length; i++) {
printf("%c", mapped_data[i]);
}
munmap(mapped_data, length);
close(fd);
19.5 使用内存池
内存池可以预先分配内存,提高内存分配的效率。例如,使用内存池管理内存分配:
#define POOL_SIZE 1000
char memory_pool[POOL_SIZE];
int pool_index = 0;
void *allocate_memory(int size) {
if (pool_index + size > POOL_SIZE) {
printf("Error: Out of memory.\n");
exit(1);
}
void *ptr = &memory_pool[pool_index];
pool_index += size;
return ptr;
}
void free_memory(int size) {
pool_index -= size;
}
int main() {
int *array = allocate_memory(array_size * sizeof(int));
// 使用数组
free_memory(array_size * sizeof(int));
return 0;
}
20. 工程编程中的最佳实践
工程编程中的最佳实践可以确保程序的可靠性和效率。以下是一些常见的最佳实践:
20.1 使用函数原型
使用函数原型可以确保函数调用的正确性。例如,定义函数原型:
extern float TideTime(void);
extern float TideHeight(void);
void main(void) {
while (1) {
printf("\n潮汐时间,高度或退出 (t/h/q)?");
switch (getch()) {
case 't':
case 'T':
TimeOut(TideTime());
if (!more()) exit(0);
break;
case 'h':
case 'H':
printf("\n潮高将会是: %f 英尺。\n", TideHeight());
if (!more()) exit(0);
break;
case 'q':
case 'Q':
exit(0);
default:
printf("\n请输入 't', 'h' 或 'q'。");
}
}
}
20.2 使用宏定义
使用宏定义可以提高代码的可读性和可维护性。例如,定义宏以简化常量的使用:
#define PI 3.1415927
#define MAX_ITERATIONS 1000
float compute_area(float radius) {
return PI * radius * radius;
}
int main() {
float radius = 5.0;
printf("Circle area: %f\n", compute_area(radius));
return 0;
}
20.3 使用结构体
使用结构体可以提高代码的组织性和可读性。例如,定义一个结构体表示复数:
struct complex {
float real, imag;
};
struct complex a, b;
void c_mult(struct complex a, struct complex b, struct complex *c) {
c->real = (a.real * b.real) - (a.imag * b.imag);
c->imag = (a.real * b.imag) + (a.imag * b.real);
}
int main() {
a.real = 1.0;
a.imag = 1.0;
b.real = 2.95;
b.imag = -1.0;
struct complex c;
c_mult(a, b, &c);
printf("%f %+fi\n", c.real, c.imag);
return 0;
}
20.4 使用指针
使用指针可以提高代码的灵活性和效率。例如,使用指针访问数组元素:
float x[5] = {1.0, 2.0, 3.0, 4.0, 5.0};
float *px = x;
for (int i = 0; i < 5; i++) {
printf("x[%d] = %f\n", i, *(px + i));
}
20.5 使用调试工具
使用调试工具可以帮助找出程序中的错误。例如,使用GDB调试程序:
-
编译程序时加入调试信息:
gcc -g program.c -o program -
启动调试器:
gdb program -
设置断点:
break main -
运行程序:
run -
逐行执行:
next -
检查变量值:
print variable_name
通过这些最佳实践,可以确保程序的可靠性和效率,提高代码的可读性和可维护性。
21. 工程编程中的应用实例
为了更好地理解工程编程中的概念和技术,以下是一些应用实例:
21.1 温度转换表生成器
温度转换表生成器是一个经典的工程编程应用。以下是一个使用
for
循环生成华氏到摄氏转换表的程序:
#include <stdio.h>
void main() {
float F, C;
for (F = 32.0; F <= 212.0; F += 10.0) {
C = (5.0 / 9.0) * (F - 32.0);
printf("%3.0f\t%3.0f\n", F, C);
}
}
21.2 矩阵乘法
矩阵乘法是一个常见的工程编程应用。以下是一个使用嵌套
for
循环计算矩阵乘法的程序:
#include <stdio.h>
void matrix_multiply(float A[3][3], float B[3][3], float C[3][3]) {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
C[i][j] = 0;
for (int k = 0; k < 3; k++) {
C[i][j] += A[i][k] * B[k][j];
}
}
}
}
int main() {
float A[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
float B[3][3] = {{9, 8, 7}, {6, 5, 4}, {3, 2, 1}};
float C[3][3];
matrix_multiply(A, B, C);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%f ", C[i][j]);
}
printf("\n");
}
return 0;
}
21.3 潮汐计算程序
潮汐计算程序在海洋工程中有广泛应用。以下是一个完整的潮汐计算程序:
#include <stdio.h>
#include <math.h>
void getParams(float *high1, float *high2, float *low1, float *low2) {
printf("输入高潮的高度:");
scanf("%f", high1);
printf("输入高潮发生的时间(小时):");
scanf("%d", &hour);
printf("输入高潮发生的分钟数:");
scanf("%d", &minute);
*low1 = TimeFloat(hour, minute);
printf("输入低潮的高度:");
scanf("%f", high2);
printf("输入低潮发生的时间(小时):");
scanf("%d", &hour);
printf("输入低潮发生的分钟数:");
scanf("%d", &minute);
*low2 = TimeFloat(hour, minute);
}
float TimeFloat(int hours, int minutes) {
return (float)hours + minutes / 60.0;
}
float TideTime(void) {
float hi, h2, tl, t2, hx;
printf("\nEnter the tidal height you want the time for:");
scanf("%f", &hx);
getParams(&hi, &h2, &tl, &t2);
return (t2 + (tl - t2) * acos(1.0 - 2.0 * ((hx - h2) / (hi - h2))) / 180.0);
}
float TideHeight(void) {
int hour, minute;
float R, high1, high2, low1, low2, timex;
printf("\n请输入您想知道潮汐的时间(小时):");
scanf("%d", &hour);
printf("请输入您想知道潮汐的时间(分钟):");
scanf("%d", &minute);
getParams(&high1, &high2, &low1, &low2);
timex = TimeFloat(hour, minute);
return (((high1 - high2) / 2.0) - ((R / 2.0) * cos(180.0 * ((timex - low2) / (low1 - low2))));
}
void main(void) {
unsigned char more(void);
void TimeOut(float TimeIn);
float TideTime(void);
float TideHeight(void);
while (1) {
printf("\n潮汐时间,高度或退出 (t/h/q)?");
switch (getch()) {
case 't':
case 'T':
TimeOut(TideTime());
if (!more()) exit(0);
break;
case 'h':
case 'H':
printf("\n潮高将会是: %f 英尺。\n", TideHeight());
if (!more()) exit(0);
break;
case 'q':
case 'Q':
exit(0);
default:
printf("\n请输入 't', 'h' 或 'q'。");
}
}
}
unsigned char more(void) {
for (;;) {
printf("再次计算 (y/n)?");
switch (getch()) {
case 'y':
case 'Y':
return (1);
case 'n':
case 'N':
return (0);
}
}
}
void TimeOut(float TimeIn) {
int hour, minute;
hour = (int)TimeIn;
minute = (TimeIn - hour) * 60;
printf("潮汐的时间将会是 %d:%02d\n", hour, minute);
}
21.4 控制台绘图程序
控制台绘图程序用于在控制台上绘制函数图像。以下是一个完整的控制台绘图程序:
#include <stdio.h>
#include <math.h>
#include <string.h>
void main(void) {
float w, theta, Tstart, Tend, dt, T;
char line[41];
printf("»");
scanf("%f %f %f %f %f", &w, &theta, &Tstart, &Tend, &dt);
printf("%f %f %f %f %f\n\n", w, theta, Tstart, Tend, dt);
/* 打印 y 轴 */
printf(" +-----------------------------+\n");
for (T = Tstart; T <= Tend; T += dt) {
memset(line, ' ', sizeof(line));
int P = (int)(20 * sin(w * T + theta));
if (P >= 0 && P < 40) {
line[P] = '*';
}
printf("%5.2f %s\n", T, line);
}
/* 打印 x 轴 */
printf(" +-----------------------------+\n");
}
21.5 案例研究:复杂潮汐计算
复杂潮汐计算程序可以整合数据库,减少用户输入。以下是一个整合数据库的潮汐计算程序示例:
#include <stdio.h>
#include <math.h>
#include <sqlite3.h>
void getParamsFromDatabase(sqlite3 *db, float *high1, float *high2, float *low1, float *low2) {
sqlite3_stmt *stmt;
const char *sql = "SELECT high1, high2, low1, low2 FROM tide_data WHERE date = ?";
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, "2023-10-01", -1, SQLITE_STATIC);
while (sqlite3_step(stmt) == SQLITE_ROW) {
*high1 = sqlite3_column_double(stmt, 0);
*high2 = sqlite3_column_double(stmt, 1);
*low1 = sqlite3_column_double(stmt, 2);
*low2 = sqlite3_column_double(stmt, 3);
}
sqlite3_finalize(stmt);
}
float TideTime(void) {
float hi, h2, tl, t2, hx;
sqlite3 *db;
sqlite3_open("tide_data.db", &db);
getParamsFromDatabase(db, &hi, &h2, &tl, &t2);
sqlite3_close(db);
printf("\nEnter the tidal height you want the time for:");
scanf("%f", &hx);
return (t2 + (tl - t2) * acos(1.0 - 2.0 * ((hx - h2) / (hi - h2))) / 180.0);
}
float TideHeight(void) {
int hour, minute;
float R, high1, high2, low1, low2, timex;
sqlite3 *db;
sqlite3_open("tide_data.db", &db);
getParamsFromDatabase(db, &high1, &high2, &low1, &low2);
sqlite3_close(db);
printf("\n请输入您想知道潮汐的时间(小时):");
scanf("%d", &hour);
printf("请输入您想知道潮汐的时间(分钟):");
scanf("%d", &minute);
timex = TimeFloat(hour, minute);
return (((high1 - high2) / 2.0) - ((R / 2.0) * cos(180.0 * ((timex - low2) / (low1 - low2))));
}
void main(void) {
unsigned char more(void);
void TimeOut(float TimeIn);
float TideTime(void);
float TideHeight(void);
while (1) {
printf("\n潮汐时间,高度或退出 (t/h/q)?");
switch (getch()) {
case 't':
case 'T':
TimeOut(TideTime());
if (!more()) exit(0);
break;
case 'h':
case 'H':
printf("\n潮高将会是: %f 英尺。\n", TideHeight());
if (!more()) exit(0);
break;
case 'q':
case 'Q':
exit(0);
default:
printf("\n请输入 't', 'h' 或 'q'。");
}
/* 绘制潮汐高度与时间的图表 */
drawTideChart();
}
}
unsigned char more(void) {
for (;;) {
printf("再次计算 (y/n)?");
switch (getch()) {
case 'y':
case 'Y':
return (1);
case 'n':
case 'N':
return (0);
}
}
}
void drawTideChart() {
float w, theta, Tstart, Tend, dt, T;
char line[41];
printf("»");
scanf("%f %f %f %f %f", &w, &theta, &Tstart, &Tend, &dt);
printf("%f %f %f %f %f\n\n", w, theta, Tstart, Tend, dt);
/* 打印 y 轴 */
printf(" +-----------------------------+\n");
for (T = Tstart; T <= Tend; T += dt) {
memset(line, ' ', sizeof(line));
int P = (int)(20 * sin(w * T + theta));
if (P >= 0 && P < 40) {
line[P] = '*';
}
printf("%5.2f %s\n", T, line);
}
/* 打印 x 轴 */
printf(" +-----------------------------+\n");
}
通过整合数据库,用户无需输入潮汐数据,程序可以自动从数据库中获取。这不仅提高了用户体验,还减少了输入错误的可能性。
22. 工程编程中的综合应用
综合应用可以展示多种编程技术和概念的结合。以下是一个结合了文件操作、控制台绘图和数据库访问的综合应用示例:
22.1 天气监测数据处理
天气监测数据处理程序可以从文件中读取数据,并动态显示在控制台上。以下是一个完整的天气监测数据处理程序:
```c
include
include
include
define MAX_FILES 5
void process_weather_data(const char
filenames[MAX_FILES]) {
FILE
files[MAX_FILES];
float temp, baro_p, wind_spd, wind_dir, rh;
for (int i = 0; i < MAX_FILES; i++) {
files[i] = fopen(filenames[i], "r");
if (files[i] == NULL) {
printf("文件打开错误...退出。\n");
exit(0);
超级会员免费看

被折叠的 条评论
为什么被折叠?



