一个例子

本章使用一个例子作为开头, 先放上例子

/*
 * 这个程序从标准输入中读取输入行并在标准输出中打印这些输入行
 * 每个输入行的后面一行是改行内容的一部分.
 *
 * 输入的第一行是一串标号, 串的最后以一个负数结尾
 * 这些列标号成对出现, 说明需要打印的输入行的列的范围
 * 列出, 0 3 10 12 -1 表示第 0 列到第 3列, 第 10 列到第 12 列的内容将被打印
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_COLS 20         /* 所能处理的最大列号 */
#define MAX_INPUT 1000      /* 每个输入行的最大长度 */

int read_column_numbers(int columns[], int max);
void rearrange(char *output, char const *input,
        int n_columns, int const columns[]);

int main(void)
{
    int     n_columns;          /* 进行处理的列标号 */
    int     columns[MAX_COLS];  /* 需要处理的列数 */
    char    input[MAX_INPUT];   /* 容纳输入行的数组 */
    char    output[MAX_INPUT];  /* 容纳输出行的数组 */

    /*
     * 获取该串列标号
     */
    n_columns = read_column_numbers(columns, MAX_COLS);

    /*
     * 读取,处理和打印剩余的输入行
     * 源程序使用 gets, 但是 man page 上写着:
     *  Never use this funtcion
     * 网上查了下说 gets 不安全, 推荐使用 fgets
     */
    while (fgets( input, MAX_INPUT, stdin ) != NULL){
        printf("Original input: %s\n", input);
        rearrange(output, input, n_columns, columns);
        printf("Rearranged line: %s\n", output);
    }
    return EXIT_SUCCESS;
}

/*
 * 读取列标号, 如果超出规定范围则不予理会
 */
int read_column_numbers(int columns[], int max)
{
    int num = 0;
    int ch;

    /*
     * 取得列标号, 如果所读取的数小于 0 则停止
     */
    while (num < max && scanf("%d", &columns[num]) ==1
            && columns[num] >= 0)
        num += 1;
    /*
     * 确认已经读取了标号为偶数个, 因为他们是以对的形式出现
     */
    if (num % 2 != 0){
        puts("Last column number is not paired.");
        exit(EXIT_FAILURE);
    }
    /*
     * 丢弃改行包含的最后一个数字的那部分内容
     */
    while ( (ch = getchar()) != EOF && ch != '\n')
        ;
    return num;
}

/*
 * 处理输入行, 将指定列的字符连在一起, 输出以 NUL 结尾.
 */
void rearrange(char *output, char const *input, int n_columns,
        int const columns[])
{
    int col;        /* columns 数组的下标 */
    int output_col; /* 输出列的计数器 */
    int len;        /* 输入行的长度 */

    len = strlen(input);
    output_col = 0;

    /*
     * 处理每对列标号
     */
    for (col = 0; col < n_columns; col += 2){
        int nchars = columns[col + 1] - columns[col] + 1;

        /*
         * 如果输入行结束或输出行数组已满, 就结束任务
         */
        if (columns[col] >= len || output_col == MAX_INPUT - 1)
            break;

        /*
         * 如果输出行数据空间不够, 只复制可以容纳的数据
         */
        if (output_col + nchars > MAX_INPUT - 1)
            nchars = MAX_INPUT - output_col - 1;
        /*
         * 复制相关数据
         */
        strncpy(output + output_col, input + columns[col], nchars);
        output_col += nchars;
    }
    output[output_col] = '\0';
}

空白和注释

C 语言中/* 表示注释的开始 */ 表示注释的结尾, 不支持嵌套注释. 如果想大段的注释代码, 不推荐 /* */ 可以使用宏代替

#if 0
   statements...
#endif

预处理指令

#include#define 开始的行称之为预处理指令(preprocessor directives) , 因为它们由预处理器(preprocessor)解释的.

预处理器读入源代码, 根据预处理执行对其进行修改, 然后把修改过的源码递交给 编译器.

在遇到#include <stdio.h>的时候, 预处理器用名叫 stdio.h 的库函数头文件内容 替换此语句.

stdio.h 头文件使我们可以访问标准I/O库(Standard I/O Library)中的函数.

stdlib.h 定义了 EXIT_SUCCESSEXIT_FAILURE 符号.

string.h 头文件提供了函数操作字符串.

如果你有一些生命需要用于几个不同的源文件, 可以在一个单独的文件编写这些生命, 然后用 #include 指令把这个文件包含进来.

#define 定义的名字会被预处理器替换成名字对应值的常量, 所以这些名字不能复制

int read_column_numbers(int columns[], int max);
void rearrange(char *output, char const *input,
              int n_columns, int const columns[]);

这些生命被称为 函数原型(function prototype). 它们告诉编译器这些以后将在源文 件中定义的函数的特征. 以便这些函数被调用时, 编译器能对它们进行准确性检查.

每个原型以一个类型名开头, 表示函数返回值的类型. 跟在类型返回名后面的是函数的 名字, 再后面是函数期望接受的参数.

main 函数

每个 C 程序都必须有一个 main 函数, 因为它是程序执行的起点. 关键字 int 表示函数返回一个整型值, 关键字 void 表示函数不接受任何参数.

    int     n_columns;          /* 进行处理的列标号 */
    int     columns[MAX_COLS];  /* 需要处理的列数 */
    char    input[MAX_INPUT];   /* 容纳输入行的数组 */
    char    output[MAX_INPUT];  /* 容纳输出行的数组 */
上面几行声明了 4 个变量: 一个整型标量, 一个整型数组以及两个字符数组.

C 语言中, 数组参数是以引用(reference)形式进行传递的, 也就是传址调用, 而标量和常量则是按值(value)传递的. 在函数中对标量参数的任何修改都会在函数返回时丢失

注释如果不正确那还不如没有!

C 语言字符串就是一串以 '\0' 字节结尾的字符. '\0' 是作为字符串的终止符, 不是字符串的一部分.

*字符串常量(string literal)*就是源程序中被双引号括起来的一串字符.

main 函数的返回值是向操作系统返回. EXIT_SUCCESS 表示程序成功执行.

read_column_numbers 函数

注意, 这个声明和早先出现的程序中的该函数原型的参数个数和类型以及函数的 返回值完全匹配. 否则编译器会报错.

C 语言中数组参数可以不计长度. 它允许单个函数操纵任意长度的一维数组. 如果需要数组长度, 应使用单独的参数传递.

puts 函数是 gets 函数的输出版本, 他把指定的字符串写到标准输出并在末尾添上一个 换行符.

getchar 函数从标准输入读取一个字符并返回它的值. 如果输入中不再存在任何字符, 函数会返回常量 EOF(stdio.h中).

rearrange 函数

当数组名作为实参时, 传递给函数实际上是指向数组起始位置的指针, 也就是数组在内存 中的地址.函数可以按照操纵指针的方式操纵实参, 也可以使用数组名一样用下标来引用 数组元素.

如果不想数组不想被函数修改, 可以将该实参声明为 const. 它声明该函数的作者的 意图是这个参数不能被修改, 编译器会取验证是否未被该意图.

如果不存在显示的 return 语句, 那么函数将返回 void (不返回任何值)

补充

在字符串内进行搜索的函数是 strchr, 这个函数在字符串参数内搜索字符参数第一次 出现的位置, 如果搜索成功就返回指向这个位置的指针, 如果搜索失败就返回一个 NULL 执行.

strstr 函数的功能类似, 但它的第 2 个参数是个字符串. 他搜索第二个字符串在第一个 字符串中第一次出现的位置.