C语言编程综合指南
1. 程序示例与Makefile
首先来看一个简单的C语言程序示例:
int main(int argc, char *argv[])
{
char *prog_name = argv[0]; /* Name of the program */
if (argc == 1) {
printf("Usage is %s [options] <file-list>\n", prog_name);
exit (8);
}
for (/* argc set */; argc > 1; --argc) {
do_file(argv[1]);
++argv;
}
return (0);
}
此程序的主要功能是处理命令行参数。若未提供参数,它会输出使用说明并退出;若有参数,则依次对这些参数执行
do_file
函数。
接下来是不同编译器的Makefile示例:
-
UNIX标准cc编译器
# File: stat/makefile.unx
CC=cc
CFLAGS=-g
OBJS= stat.o ch_type.o token.o in_file.o
all: stat.out stat test.out
test.out: test.c stat
stat test.c >test.out
stat.out: stat
stat ../calc3/calc3.c >stat.out
stat: $(OBJS)
$(CC) $(CFLAGS) -o stat $(OBJS)
stat.o: stat.c token.h
$(CC) $(CFLAGS) -c stat.c
ch_type.o: ch_type.c ch_type.h
$(CC) $(CFLAGS) -c ch_type.c
token.o: token.c token.h ch_type.h in_file.h
$(CC) $(CFLAGS) -c token.c
in_file.o: in_file.c in_file.h
$(CC) $(CFLAGS) -c in_file.c
clean:
rm -f stat stat.o ch_type.o token.o in_file.o
- Free Software Foundations g++编译器
# File: stat/makefile.gcc
CC=gcc
CFLAGS=-g -Wall -D__USE_FIXED_PROTOTYPES__
OBJS= stat.o ch_type.o token.o in_file.o
all: stat.out stat test.out
test.out: test.c stat
stat test.c >test.out
stat.out: stat
stat ../calc3/calc3.c >stat.out
stat: $(OBJS)
$(CC) $(CFLAGS) -o stat $(OBJS)
stat.o: stat.c token.h
$(CC) $(CFLAGS) -c stat.c
ch_type.o: ch_type.c ch_type.h
$(CC) $(CFLAGS) -c ch_type.c
token.o: token.c token.h ch_type.h in_file.h
$(CC) $(CFLAGS) -c token.c
in_file.o: in_file.c in_file.h
$(CC) $(CFLAGS) -c in_file.c
clean:
rm -f stat stat.o ch_type.o token.o in_file.o
- Borland的Borland - C++编译器(Turbo C++)
# File: stat/makefile.tcc
CC=tcc
CFLAGS=-N -v -w -ml
OBJS= stat.obj ch_type.obj token.obj in_file.obj
all: stat.out stat.exe test.out
test.out: test.c stat.exe
stat test.c >test.out
stat.out: stat.exe
stat ..\calc3\calc3.c >stat.out
stat.exe: $(OBJS)
$(CC) $(CFLAGS) -estat $(OBJS)
stat.obj: stat.c token.h
$(CC) $(CFLAGS) -c stat.c
in_file.obj: in_file.c in_file.h
$(CC) $(CFLAGS) -c in_file.c
ch_type.obj: ch_type.c ch_type.h
$(CC) $(CFLAGS) -c ch_type.c
token.obj: token.c token.h ch_type.h
$(CC) $(CFLAGS) -c token.c
clean:
erase stat.exe
erase stat.obj
erase ch_type.obj
erase in_file.obj
erase token.obj
- Borland的Borland C++编译器
# File: stat/makefile.bcc
CC=bcc
CFLAGS=-N -v -w -ml
OBJS= stat.obj ch_type.obj token.obj in_file.obj
all: stat.out stat.exe test.out
test.out: test.c stat.exe
stat test.c >test.out
stat.out: stat.exe
stat ..\calc3\calc3.c >stat.out
stat.exe: $(OBJS)
$(CC) $(CFLAGS) -estat $(OBJS)
stat.obj: stat.c token.h
$(CC) $(CFLAGS) -c stat.c
in_file.obj: in_file.c in_file.h
$(CC) $(CFLAGS) -c in_file.c
ch_type.obj: ch_type.c ch_type.h
$(CC) $(CFLAGS) -c ch_type.c
token.obj: token.c token.h ch_type.h
$(CC) $(CFLAGS) -c token.c
clean:
erase stat.exe
erase stat.obj
erase ch_type.obj
erase in_file.obj
erase token.obj
- Microsoft Visual C++编译器
# File: stat/makefile.msc
CC=cl
CFLAGS=/AL /Zi /W1
OBJS= stat.obj ch_type.obj token.obj in_file.obj
all: stat.out stat.exe test.out
test.out: test.c stat.exe
stat test.c >test.out
stat.out: stat.exe
stat ..\calc3\calc3.c >stat.out
stat.exe: $(OBJS)
$(CC) $(CCFLAGS) $(OBJS)
stat.obj: stat.c token.h
$(CC) $(CCFLAGS) -c stat.c
ch_type.obj: ch_type.c ch_type.h
$(CC) $(CCFLAGS) -c ch_type.c
token.obj: token.c token.h ch_type.h
$(CC) $(CCFLAGS) -c token.c
in_file.obj: in_file.c
$(CC) $(CCFLAGS) -c in_file.c
clean:
erase stat.exe
erase stat.obj
erase ch_type.obj
erase token.obj
erase in_file.obj
这些Makefile文件定义了如何编译和链接程序。
all
目标指定了要生成的最终文件,每个目标都有相应的依赖项和命令来生成目标文件。
clean
目标则用于清理生成的文件。
2. 编程练习
以下是一些编程练习题目,可帮助提升编程能力:
-
练习22 - 1
:编写一个程序,检查文本文件中是否存在重复的单词,例如 “in the the file”。
-
操作步骤
:
1. 打开文本文件。
2. 逐行读取文件内容。
3. 将每行内容拆分为单词。
4. 检查相邻单词是否相同。
-
练习22 - 2
:编写一个程序,从文件中移除四个字母的单词,并将其替换为更合适的等价词。
-
操作步骤
:
1. 打开文件。
2. 逐行读取文件内容。
3. 将每行内容拆分为单词。
4. 检查每个单词的长度是否为4。
5. 若为4个字母的单词,将其替换为合适的等价词。
-
练习22 - 3
:编写一个邮件列表程序,该程序可以读取、写入、排序和打印邮件标签。
-
操作步骤
:
1. 设计数据结构来存储邮件标签信息。
2. 实现读取邮件标签的功能,可从文件或用户输入读取。
3. 实现写入邮件标签的功能,将数据保存到文件。
4. 实现排序功能,可按姓名、地址等排序。
5. 实现打印功能,输出邮件标签。
-
练习22 - 4
:更新本章介绍的统计程序,添加交叉引用功能。
-
操作步骤
:
1. 确定交叉引用的需求,例如统计单词出现的位置。
2. 在统计程序的基础上,添加数据结构来存储单词和其出现的位置。
3. 在遍历文件内容时,记录单词的出现位置。
4. 实现交叉引用的输出功能。
-
练习22 - 5
:编写一个程序,将文本文件中的长行拆分为两个较短的行。拆分点应尽可能在句子末尾,若句子过长,则在单词末尾拆分。
-
操作步骤
:
1. 打开文件。
2. 逐行读取文件内容。
3. 检查每行的长度是否超过阈值。
4. 若超过阈值,寻找句子末尾或单词末尾进行拆分。
5. 将拆分后的行写入新文件。
3. 编程格言与规范
在C语言编程中,遵循一些编程格言和规范能让代码更易读、易维护且减少错误:
-
通用准则
-
多写注释
:在程序中添加大量注释,这有助于其他程序员理解代码,也能让自己在后续查看代码时快速回忆起代码功能。
-
遵循KISS原则
:保持代码简单易懂,清晰简单的代码比复杂精妙的代码更好。
-
避免副作用
:将
++
和
--
操作放在单独的行中,使用前缀版本(
++x
,
--x
)而非后缀版本(
x++
,
x--
),虽然在C语言中这区别不大,但在C++中会很有用。
-
避免在条件语句和其他语句中进行赋值操作
:要清楚
=
和
==
的区别,使用
=
代替
==
是常见且难以发现的错误。
-
不要静默地“什么都不做”
:例如,避免像下面这样编程:
for (index = 0; data[index] < key; ++index);
应改为:
for (index = 0; data[index] < key; ++index)
continue;
-
设计准则
- 遵循“最少惊讶原则” :程序的行为应尽量符合用户的预期,让用户感到最少的惊讶。
- 简化用户界面 :使用户界面尽可能简单和一致,为用户提供尽可能多的帮助。
- 明确错误信息 :所有错误信息都应明确包含 “error” 字样,并尝试给用户一些纠正问题的提示。
-
声明准则
- 每行声明一个变量并注释 :这样能让代码更清晰,便于理解每个变量的用途。
- 合理命名变量 :变量名应足够长以便于理解,但也不要过长导致难以输入,通常两到三个单词就足够。
-
避免使用默认声明
:如果函数返回整数,应将其声明为
int类型,所有函数参数都应声明并注释。 -
正确声明
main函数 :应使用int main(void)或int main(int argc, char *argv[])进行声明,避免使用void main()或void main(int ac, char *av[])。
-
switch语句准则-
添加
default情况 :即使default情况什么都不做,也应添加到switch语句中。
-
添加
switch (expression) {
/* ... */
default:
/* do nothing */
}
- **每个 `case` 结束时使用 `break` 或注释**:每个 `case` 应使用 `break` 结束,或者添加 `/* Fall through */` 注释。
-
预处理器准则
-
使用括号
:在预处理器
#define指令定义的每个常量表达式周围加上括号。
-
使用括号
:在预处理器
#define BOX_SIZE (3*10) /* size of the box in pixels */
- **使用 `const` 声明**:尽可能使用 `const` 声明代替 `#define`。
- **参数化宏加括号**:在参数化宏的每个参数周围加上括号。
#define SQUARE(x) ((x) * (x))
- **用花括号包围宏**:包含完整语句的宏应使用花括号 `{}` 包围。
#define DIE(msg) {printf(msg);exit(8);}
- **注释条件编译语句**:使用 `#ifdef/#endif` 进行条件编译时,将 `#define` 和 `#undef` 语句放在程序顶部并进行注释。
-
风格准则
-
控制代码块长度
:用
{}包围的单个代码块不应超过几页,若代码块太大,应拆分为几个更小、更简单的过程。 - 拆分长过程 :当代码开始超出右边界时,应将过程拆分为几个更小、更简单的过程。
-
控制代码块长度
:用
-
编译准则
- 创建Makefile :始终创建Makefile,以便他人知道如何编译你的程序。
- 消除警告 :打开所有警告标志,然后使程序无警告。
4. ASCII表与变量范围
-
ASCII表
ASCII字符集现在几乎被普遍使用,以下是其八进制、十六进制和十进制表示:
| Dec | Oct | Hex | Char |
| — | — | — | — |
| 0 | 000 | 00 | NUL |
| 1 | 001 | 01 | SOH |
| 2 | 002 | 02 | STX |
| … | … | … | … |
| 127 | 177 | 7F | DEL | -
变量范围
不同系统和数据类型的变量范围有所不同:-
32位UNIX机器
| Name | Bits | Low value | High value | Accuracy |
| — | — | — | — | — |
| int | 32 | -2147483648 | 2147483647 | |
| short int | 16 | -32768 | 32767 | |
| long int | 32 | -2147483648 | 2147483647 | |
| unsigned int | 32 | 0 | 4294967295 | |
| unsigned short int | 16 | 0 | 65535 | |
| unsigned long int | 32 | 0 | 4294967295 | |
| char | 8 | System Dependent | | |
| unsigned char | 8 | 0 | 255 | |
| float | 32 | -3.4E+38 | 3.4E+38 | 6 digits |
| double | 64 | -1.7E+308 | 1.7E+308 | 15 digits |
| long double | 64 | -1.7E+308 | 1.7E+308 | 15 digits | -
Turbo C++、Borland C++和大多数其他16位系统
| Name | Bits | Low value | High value | Accuracy |
| — | — | — | — | — |
| int | 16 | -32768 | 32767 | |
| short int | 16 | -32768 | 32767 | |
| long int | 32 | -2147483648 | 2147483647 | |
| unsigned int | 16 | 0 | 65535 | |
| unsigned short int | 16 | 0 | 65535 | |
| unsigned long int | 32 | 0 | 4294967295 | |
| char | 8 | -128 | 127 | |
| unsigned char | 8 | 0 | 255 | |
| float | 32 | -3.4E+38 | 3.4E+38 | 6 digits |
| double | 64 | -1.7E+308 | 1.7E+308 | 15 digits |
| long double | 80 | -3.4E+4932 | 3.4E+4932 | 17 digits |
-
32位UNIX机器
5. 自动类型转换与运算符优先级
-
自动类型转换
在将参数传递给函数时,C语言会执行以下自动转换以避免一些问题:
| Type | Converted to |
| — | — |
| char | int |
| short int | int |
| int | int |
| long int | long int |
| float | double |
| double | double |
| long double | long double |
| array | pointer | -
运算符优先级
-
标准规则
运算符的优先级决定了表达式的计算顺序,以下是C语言运算符的优先级规则:
| Precedence | Operator |
| — | — |
| 1 | ( ) [ ] -> . |
| 2 | ! ~ ++ – (type) - (unary) * (dereference) & (address of) sizeof |
| 3 | * (multiply) / % |
| 4 | + - |
| 5 | << >> |
| 6 | < <= > >= |
| 7 | == != |
| 8 | & (bitwise and) |
| 9 | ^ |
| 10 | | |
| 11 | && |
| 12 | || |
| 13 | ?: |
| 14 | = += -= etc. |
| 15 | , | -
实用子集
在实际编程中,可记住以下优先级规则,其他部分用括号明确计算顺序:
| Precedence | Operator |
| — | — |
| 1 | * (multiply) / % |
| 2 | + - |
-
标准规则
6. 正弦函数计算程序
下面是一个使用幂级数计算正弦函数的程序:
#include <stdlib.h>
#include <math.h>
#include <stdio.h>
static char *float_2_ascii(float number)
{
static char result[10]; /*place to put the number */
sprintf(result,"%8.3E", number);
return (result);
}
float fix_float(float number)
{
float result; /* result of the conversion */
char ascii[10]; /* ascii version of number */
sprintf(ascii,"%8.4e", number);
sscanf(ascii, "%e", &result);
return (result);
}
float factorial(float number)
{
if (number <= 1.0)
return (number);
else
return (number *factorial(number - 1.0));
}
int main(int argc, char *argv[])
{
float total; /* total of series so far */
float new_total;/* newer version of total */
float term_top;/* top part of term */
float term_bottom;/* bottom of current term */
float term; /* current term */
float exp; /* exponent of current term */
float sign; /* +1 or -1 (changes on each term) */
float value; /* value of the argument to sin */
int index; /* index for counting terms */
if (argc != 2) {
fprintf(stderr,"Usage is:\n");
fprintf(stderr," sine <value>\n");
exit (8);
}
value = fix_float(atof(&argv[1][0]));
total = 0.0;
exp = 1.0;
sign = 1.0;
for (index = 0; /* take care of below */ ; ++index)
{
term_top = fix_float(pow(value, exp));
term_bottom = fix_float(factorial(exp));
term = fix_float(term_top / term_bottom);
printf("x**%d %s\n", (int)exp, float_2_ascii(term_top));
printf("%d! %s\n", (int)exp, float_2_ascii(term_bottom));
printf("x**%d/%d! %s\n", (int)exp, (int)exp, float_2_ascii(term));
printf("\n");
new_total = fix_float(total + sign * term);
if (new_total == total)
break;
total = new_total;
sign = -sign;
exp = exp + 2.0;
printf(" total %s\n", float_2_ascii(total));
printf("\n");
}
printf("%d term computed\n", index+1);
printf("sin(%s)=\n", float_2_ascii(value));
printf(" %s\n", float_2_ascii(total));
printf("Actual sin(%G)=%G\n", atof(&argv[1][0]), sin(atof(&argv[1][0])));
return (0);
}
该程序使用幂级数计算正弦函数,采用了非常有限的浮点格式来展示使用浮点数时可能出现的问题。程序通过命令行参数传入一个弧度值,计算幂级数的每一项并显示结果,直到最后一项对最终结果的贡献非常小为止。同时,还会显示库函数
sin
的计算结果以供比较。
7. 术语表
以下是一些C语言编程中常见的术语解释:
-
符号相关
-
!
:逻辑非运算符。
-
!=
:不等于关系运算符。
-
%
:取模运算符。
-
&
:按位与运算符,也用于取变量地址。
-
&&
:逻辑与运算符。
-
*
:乘法运算符,也用于指针解引用。
-
+
:加法运算符。
-
++
:自增运算符。
-
-
:减法运算符。
-
--
:自减运算符。
-
->
:用于从类或结构体指针获取成员。
-
/
:除法运算符。
-
<
:小于关系运算符。
-
<<
:左移运算符。
-
<=
:小于等于关系运算符。
-
==
:等于关系运算符。
-
>
:大于关系运算符。
-
>=
:大于等于关系运算符。
-
>>
:右移运算符。
-
? :
:条件运算符。
-
^
:按位异或运算符。
-
\
:字符串中用于表示特殊字符。
-
\b
:退格字符。
-
\f
:换页字符。
-
\n
:换行字符。
-
|
:按位或运算符。
-
||
:逻辑或运算符。
-
~
:按位取反运算符。
-
'\0'
:字符串结束字符。
-
#define
:预处理器指令,用于定义替换文本。
-
#endif
:与
#ifdef
配合使用的预处理器结束指令。
-
#ifdef
:检查宏是否定义的预处理器指令。
-
#ifndef
:检查宏是否未定义的预处理器指令。
-
#include
:预处理器指令,用于插入文件。
-
#undef
:取消
#define
定义的预处理器指令。
-
概念相关
-
Accuracy
:实数表示中固有的误差的定量测量。
-
Address
:内存中存储位置的值。
-
ANSI - C
:符合美国国家标准协会委员会X3J规范的任何C语言版本。
-
Array
:按一个或多个维度索引的数据元素集合,在C语言中数组存储在连续内存中。
-
ASCII
:美国信息交换标准代码,用于表示字符。
-
Assignment statement
:将值存储到变量中的操作。
-
Bit
:二进制数字,0或1。
-
Bit field
:一组连续的位作为一个单元,C语言的一种特性,允许访问单个位。
-
Block
:用花括号括起来的代码段。
-
Boolean
:能返回真或假结果的操作或值。
-
C
:1974年由Dennis Ritchie在贝尔实验室开发的通用计算机编程语言,属于中高级语言。
-
C++
:基于C语言由Bjarne Stroustrup在1980年发明的语言,最初称为 “C with classes”,现已发展成为独立的语言。
-
Compilation
:将源代码翻译成机器代码的过程。
-
Compiler
:执行编译的系统程序。
-
Conditional compilation
:根据条件指令中测试的条件的真假选择性编译程序部分的能力。
-
Debugging
:查找和消除程序中错误的过程。
-
Declaration
:指定程序中要使用的变量的类型和名称。
-
Function
:返回值的过程。
-
Global variable
:在整个程序中都可见的变量。
-
Heap
:
malloc
用于为新结构和数组分配空间的内存部分,可使用
free
函数将空间返回给该池。
-
Library
:通常包含用于链接到程序中的目标代码的文件集合,也称为存档。
-
Pointer
:保存内存中位置地址的数据类型。
-
Preprocessor
:执行预处理以扩展宏代码模板以生成C代码的程序。
-
Program
:使计算机执行一系列操作的指令组。
-
Recursion
:函数直接或间接调用自身的情况。
-
Syntax
:控制语句构造的规则。
-
Variable
:引用值的名称,在程序执行过程中,该名称所代表的数据可以取不同的值。
C语言编程综合指南
8. 编程实践中的注意事项
在实际的C语言编程过程中,除了遵循上述的编程格言和规范外,还有一些具体的注意事项需要牢记,以避免常见的错误和提高代码的质量。
-
避免赋值与比较混淆
:在条件判断中,要特别注意区分赋值运算符
=和比较运算符==。例如,下面的代码是错误的:
if (x = 5) {
// 这里本意可能是比较x是否等于5,但使用了赋值运算符
}
正确的写法应该是:
if (x == 5) {
// 正确的比较操作
}
- 指针操作的安全性 :指针是C语言强大但危险的特性。在使用指针时,要确保指针已经被正确初始化,避免访问空指针或野指针。例如:
int *ptr;
// 未初始化的指针
// *ptr = 10; // 错误,可能导致程序崩溃
// 正确的做法是先分配内存或指向有效的地址
ptr = (int *)malloc(sizeof(int));
if (ptr != NULL) {
*ptr = 10;
free(ptr); // 使用完后释放内存
}
-
内存管理 :在使用动态内存分配函数(如
malloc、calloc、realloc)时,要记得在不需要内存时使用free函数释放内存,避免内存泄漏。同时,要注意内存分配失败的情况,检查返回值是否为NULL。 -
数组越界问题 :访问数组元素时,要确保索引在合法范围内,避免数组越界。例如:
int arr[5];
// 正确的访问
for (int i = 0; i < 5; i++) {
arr[i] = i;
}
// 错误的访问,可能导致未定义行为
// arr[5] = 10;
9. 编程技巧与优化
为了提高程序的性能和可读性,还可以采用一些编程技巧和优化方法。
- 使用宏定义常量 :使用宏定义常量可以提高代码的可读性和可维护性。例如:
#define MAX_SIZE 100
int arr[MAX_SIZE];
- 函数封装 :将一些常用的功能封装成函数,避免代码重复,提高代码的复用性。例如,将读取文件的操作封装成一个函数:
#include <stdio.h>
void read_file(const char *filename) {
FILE *file = fopen(filename, "r");
if (file != NULL) {
// 读取文件内容
fclose(file);
}
}
- 算法优化 :选择合适的算法可以显著提高程序的性能。例如,在排序问题中,对于小规模数据可以使用简单的冒泡排序,而对于大规模数据则可以使用快速排序等高效算法。
10. 编程工具与调试
在C语言编程中,合适的编程工具和调试技巧可以帮助我们更高效地开发和调试程序。
-
编译器选择 :不同的编译器有不同的特点和优势,可以根据项目需求选择合适的编译器。例如,
gcc是一个广泛使用的开源编译器,支持多种平台;MSVC则是微软的编译器,与Windows平台兼容性好。 -
调试工具 :使用调试工具可以帮助我们快速定位和解决程序中的问题。常见的调试工具有
gdb(用于gcc编译的程序)和Visual Studio的调试器等。调试的基本步骤如下:- 在代码中设置断点。
- 启动调试器,运行程序。
- 当程序执行到断点时,暂停执行,可以查看变量的值、调用栈等信息。
- 逐步执行代码,观察程序的执行流程和变量的变化,找出问题所在。
11. 流程图示例
下面是一个简单的程序流程图,展示了一个判断输入数字是否为偶数的程序流程:
graph TD;
A[开始] --> B[输入一个数字];
B --> C{数字是否能被2整除};
C -- 是 --> D[输出“是偶数”];
C -- 否 --> E[输出“是奇数”];
D --> F[结束];
E --> F[结束];
12. 总结
C语言是一门功能强大且广泛应用的编程语言,但也具有一定的复杂性。通过掌握基本的语法知识、遵循编程规范、进行大量的编程实践和使用合适的工具,我们可以编写出高质量、高效的C语言程序。在编程过程中,要不断总结经验,遇到问题时善于利用各种资源(如文档、论坛等)来解决问题。希望本文介绍的内容能对大家的C语言编程学习和实践有所帮助。
附:常见问题解答
-
问题1:为什么我的程序在运行时崩溃了?
可能的原因包括:内存访问错误(如空指针引用、数组越界)、栈溢出、未初始化变量等。可以使用调试工具(如gdb)来定位问题。 -
问题2:如何提高程序的性能?
可以从算法优化、内存管理、减少函数调用开销等方面入手。选择合适的算法,避免不必要的内存分配和释放,将频繁调用的代码进行优化。 -
问题3:C语言和C++有什么区别?
C++是在C语言的基础上发展而来的,增加了面向对象编程的特性(如类、继承、多态)和一些新的库。C语言更注重底层操作和性能,而C++更适合开发大型、复杂的软件项目。
通过不断学习和实践,相信大家能够更好地掌握C语言编程,解决各种实际问题。
超级会员免费看
291

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



