《The C Programming Language, 2nd Edition》K&R Chapter 5 - Pointers and Arrays 笔记

指针

指针是足够存放一个地址的一组存储单元,通常是4或8字节。

&和*

地址运算符&只能应用于内存中的对象,不能用于表达式、常量或register变量。&z[0]的值为z[0]的地址,即z的地址。
*是间接寻址或间接引用运算符。int *ip;表示*ip是int类型。
void*对象不能*(间接寻址)。

指针初始化

char *allocp = allocbuf;等价于char *allocp; allocp = allocbuf;
C语言保证0永远不是有效的地址。程序中常使用符号常量NULL代替0,NULL定义在<stddef.h>中。

指针与简单数组

数组名与指针的区别是,指针是变量,而数组名不是。比如,当a为数组时,a=pa和a++都是非法的。
将数组名作为实参时,传递的是第一个元素的地址。
形参char s[]char *s等价。

char amessage[] ="now is the time";
char *pmessage ="now is the time";

amessage是足以容纳初始化字符串和'\0'的一维数组,数组中的字符可以修改,数组的地址不可修改。
pmessage是一个指针,指向一个字符串常量,它可以修改以指向其他的地址,但试图修改字符串的内容是未定义的。

指针运算

ANSI C使用void*替代char*作为通用指针类型。
除void*指针以外,不同类型指针之间的赋值是非法的。void*指针与任意类型指针之间的赋值是合法的。

p++的含义是p自增并指向下一个元素,p+=i的含义是对p进行加i运算,使指针p指向当前元素之后第i个元素。
在计算p+n时,n将根据p指向的对象的长度按比例缩放,而p指向的对象的长度则取决于p的声明。例如,如果int占4个字节,int类型的计算中,对应的n将4的倍数计算。
a[i]等价于*(a+i)。&a[i]与a+i也等价。

(*p)++;中()是必需的,因为一元运算符遵循从右向左结合。
(*++argv)[0]等价于**++argv,其中()是必需的,因为[]的结合优先级比*和++高。

指针的减法的意义是求两个操作数之间的元素个数。
<stddef.h>中定义的类型ptrdiff_t足以容纳两个指针之间的带符号差值。

指针支持关系运算,但指向不同数组元素的指针之间的算术或关系运算是未定义的。例外,标准规定,指针在算术运算中可指向数组的最后一个元素的下一个元素的地址。

指针与复杂数组

多维数组的支持以下初始化表达式列表:

static char daytab[2][13] = {
    {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
    {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};

多维数组作函数的形参时,第一维可以省略,如f(int daytab[][13]) {}
f(int daytab[][13]) {}等价于f(int (*daytab)[13]) {},因为[]优先级比*高。
f(int *daytab[13]) {}中daytab为元素为int*,长度为13的数组。

int a[10][20];
int *b[10];

a分配了200个int内存。
b只分配了10个指针内存。如果b的元素都为长度为20的int数组,总计要消耗10个指针+200个int的内存。与a相比b的优势是,它的元素数组的长度可以不同。

main函数

main接收两个实参

  1. 第一个实参是整数,表示第二个实参的元素个数,通常命名为argc。
  2. 第二个实参是字符串数组,从环境接收,通常命名为argv。

从控制台执行程序,argv就是执行程序的那条命令,以空白符分隔,生成的字符串数组。
点击程序图标执行程序,argv只有一个元素,为程序的完整名称,包含路径和后辍。
拖动其他文件或文件夹到程序图标上执行程序,argv第一个元素为程序的完整名称,第二个元素为被拖动的文件或文件夹的完整名称,都包含路径和后辍(文件夹无后辍)。

因此,argv中第一个元素必定是程序名,因此argc至少为1。
ANSI C规定argv[argc]为空指针。

其他

size_t为sizeof运算符返回的无符号整型,在<stddef.h>中定义。

<string.h>库函数strstr(s, t)返回一个指针,指向字符串t在s中第一次出现的位置,如果t中不存在s,返回NULL(空指针)。

例程

交换变量的值

void swap(int *px, int *py) /* interchange *px and *py */
{
    int temp;

    temp = *px;
    *px = *py;
    *py = temp;
}

读入一个整数

#include <ctype.h>

int getch(void);
void ungetch(int);

/* getint: get next integer from input into *pn */
int getint(int *pn)
{
    int c, sign;

    while (isspace(c = getch())) /* skip white space */
        ;
    if (!isdigit(c) && c != EOF && c != '+' && c != '-') {
        ungetch(c); /* it is not a number */
        return 0;
    }
    sign = (c == '-') ? -1 : 1;
    if (c == '+' || c == '-')
        c = getch();
    for (*pn = 0; isdigit(c); c = getch())
        *pn = 10 * *pn + (c - '0');
    *pn *= sign;
    if (c != EOF)
        ungetch(c);
    return c;
}

不同版本的strlen函数

/* strlen: return length of string s */
int strlen(char *s)
{
    int n;

    for (n = 0; *s != '\0', s++)
        n++;
    return n;
}
/* strlen: return length of string s */
int strlen(char *s)
{
    char *p = s;

    while (*p != '\0')
        p++;
    return p - s;
}

不完善的内存分配程序(后分配的必须先释放)

#define ALLOCSIZE 10000 /* size of available space */

static char allocbuf[ALLOCSIZE]; /* storage for alloc */
static char *allocp = allocbuf; /* next free position */

char *alloc(int n) /* return pointer to n characters */
{
    if (allocbuf + ALLOCSIZE - allocp >= n) { /* it fits */
        allocp += n;
        return allocp - n; /* old p */
    } else /* not enough room */
        return 0;
}

void afree(char *p) /* free storage pointed to by p */
{
    if (p >= allocbuf && p < allocbuf + ALLOCSIZE)
        allocp = p;
}

不同版本的strcpy函数

/* strcpy: copy t to s; array subscript version */
void strcpy(char *s, char *t)
{
    int i;

    i = 0;
    while ((s[i] = t[i]) != '\0')
        i++;
}
/* strcpy: copy t to s; pointer version 3 */
void strcpy(char *s, char *t)
{
    while (*s++ = *t++)
        ;
}

不同版本的strcmp函数

/* strcmp: return <0 if s<t, 0 if s==t, >0 if s>t */
int strcmp(char *s, char *t)
{
    int i;
    for (i = 0; s[i] == t[i]; i++)
        if (s[i] == '\0')
            return 0;
    return s[i] - t[i];
}
/* strcmp: return <0 if s<t, 0 if s==t, >0 if s>t */
int strcmp(char *s, char *t)
{
    for ( ; *s == *t; s++, t++)
        if (*s == '\0')
            return 0;
    return *s - *t;
}

依据行对文本排序

#include <stdio.h>
#include <string.h>

#define MAXLINES 5000 /* max #lines to be sorted */

char *lineptr[MAXLINES]; /* pointers to text lines */

int readlines(char *lineptr[], int nlines);
void writelines(char *lineptr[], int nlines);

void qsort(char *lineptr[], int left, int right);

/* sort input lines */
main()
{
    int nlines; /* number of input lines read */

    if ((nlines = readlines(lineptr, MAXLINES)) >= 0) {
        qsort(lineptr, 0, nlines-1);
        writelines(lineptr, nlines);
        return 0;
    } else {
        printf("error: input too big to sort\n");
        return 1;
    }
}
#define MAXLEN 1000 /* max length of any input line */
int getline(char *, int);
char *alloc(int);

/* readlines: read input lines */
int readlines(char *lineptr[], int maxlines)
{
    int len, nlines;
    char *p, line[MAXLEN];
    nlines = 0;
    while ((len = getline(line, MAXLEN)) > 0)
        if (nlines >= maxlines || (p = alloc(len)) == NULL)
            return -1;
        else {
            line[len-1] = '\0'; /* delete newline */
            strcpy(p, line);
            lineptr[nlines++] = p;
        }
    return nlines;
}

/* writelines: write output lines */
void writelines(char *lineptr[], int nlines)
{
    int i;
    for (i = 0; i < nlines; i++)
        printf("%s\n", lineptr[i]);
}

/* qsort: sort v[left]...v[right] into increasing order */
void qsort(char *v[], int left, int right)
{
    int i, last;
    void swap(char *v[], int i, int j);

    if (left >= right) /* do nothing if array contains */
        return; /* fewer than two elements */
    swap(v, left, (left + right)/2);
    last = left;
    for (i = left+1; i <= right; i++)
        if (strcmp(v[i], v[left]) < 0)
            swap(v, ++last, i);
    swap(v, left, last);
    qsort(v, left, last-1);
    qsort(v, last+1, right);
}

/* swap: interchange v[i] and v[j] */
void swap(char *v[], int i, int j)
{
    char *temp;

    temp = v[i];
    v[i] = v[j];
    v[j] = temp;
}

日期表示形式转换

static char daytab[2][13] = {
    {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
    {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};

/* day_of_year: set day of year from month & day */
int day_of_year(int year, int month, int day)
{
    int i, leap;

    leap = year%4 == 0 && year%100 != 0 || year%400 == 0;
    for (i = 1; i < month; i++)
        day += daytab[leap][i];
    return day;
}

/* month_day: set month, day from day of year */
void month_day(int year, int yearday, int *pmonth, int *pday)
{
    int i, leap;

    leap = year%4 == 0 && year%100 != 0 || year%400 == 0;
    for (i = 1; yearday > daytab[leap][i]; i++)
        yearday -= daytab[leap][i];
    *pmonth = i;
    *pday = yearday;
}

求月份名称

/* month_name: return name of n-th month */
char *month_name(int n)
{
    static char *name[] = {
        "Illegal month",
        "January", "February", "March",
        "April", "May", "June",
        "July", "August", "September",
        "October", "November", "December"
    };
    
    return (n < 1 || n > 12) ? name[0] : name[n];
}

不同版本的echo程序

#include <stdio.h>

/* echo command-line arguments; 1st version */
main(int argc, char *argv[])
{
    int i;

    for (i = 1; i < argc; i++)
        printf("%s%s", argv[i], (i < argc-1) ? " " : "");
    printf("\n");
    return 0;
}
#include <stdio.h>

/* echo command-line arguments; 2nd version */
main(int argc, char *argv[])
{
    while (--argc > 0)
        printf("%s%s", *++argv, (argc > 1) ? " " : "");
    printf("\n");
    return 0;
}

打印与第一个参数匹配的行

支持-x表示除匹配的行之外,-n显示行号,支持-xn的写法

#include <stdio.h>
#include <string.h>
#define MAXLINE 1000

int getline(char *line, int max);

/* find: print lines that match pattern from 1st arg */
main(int argc, char *argv[])
{
    char line[MAXLINE];
    long lineno = 0;
    int c, except = 0, number = 0, found = 0;

    while (--argc > 0 && (*++argv)[0] == '-')
        while (c = *++argv[0])
            switch (c) {
            case 'x':
                except = 1;
                break;
            case 'n':
                number = 1;
                break;
            default:
                printf("find: illegal option %c\n", c);
                argc = 0;
                found = -1;
                break;
            }
    if (argc != 1)
        printf("Usage: find -x -n pattern\n");
    else
        while (getline(line, MAXLINE) > 0) {
            lineno++;
            if ((strstr(line, *argv) != NULL) != except) {
                if (number)
                    printf("%ld:", lineno);
                printf("%s", line);
                found++;
            }
        }
    return found;
}

依据行对文本排序,-n表示按数值大小排序

main.c

#include <stdio.h>
#include <string.h>

#define MAXLINES 5000 /* max #lines to be sorted */
char *lineptr[MAXLINES]; /* pointers to text lines */

int readlines(char *lineptr[], int nlines);
void writelines(char *lineptr[], int nlines);

void qsort(void *lineptr[], int left, int right,
    int (*comp)(void *, void *));
int numcmp(char *, char *);

/* sort input lines */
main(int argc, char *argv[])
{
    int nlines; /* number of input lines read */
    int numeric = 0; /* 1 if numeric sort */

    if (argc > 1 && strcmp(argv[1], "-n") == 0)
        numeric = 1;
    if ((nlines = readlines(lineptr, MAXLINES)) >= 0) {
        qsort((void**) lineptr, 0, nlines-1,
            (int (*)(void*,void*))(numeric ? numcmp : strcmp));
        writelines(lineptr, nlines);
        return 0;
    } else {
        printf("input too big to sort\n");
        return 1;
    }
}

/* qsort: sort v[left]...v[right] into increasing order */
void qsort(void *v[], int left, int right,
    int (*comp)(void *, void *))
{
    int i, last;
    void swap(void *v[], int, int);

    if (left >= right) /* do nothing if array contains */
        return; /* fewer than two elements */
    swap(v, left, (left + right)/2);
    last = left;
    for (i = left+1; i <= right; i++)
        if ((*comp)(v[i], v[left]) < 0)
            swap(v, ++last, i);
    swap(v, left, last);
    qsort(v, left, last-1, comp);
    qsort(v, last+1, right, comp);
}

void swap(void *v[], int i, int j)
{
    void *temp;

    temp = v[i];
    v[i] = v[j];
    v[j] = temp;
}

numcmp.c

#include <stdlib.h>

/* numcmp: compare s1 and s2 numerically */
int numcmp(char *s1, char *s2)
{
    double v1, v2;

    v1 = atof(s1);
    v2 = atof(s2);
    if (v1 < v2)
        return -1;
    else if (v1 > v2)
        return 1;
    else
        return 0;
}

将声明转换为文字描述

(只支持简单数据类型,不支持限定符和函数)

转换示例:

char **argv
    argv: pointer to pointer to char
int (*daytab)[13]
    daytab: pointer to array[13] of int
int *daytab[13]
    daytab: array[13] of pointer to int
void *comp()
    comp: function returning pointer to void
void (*comp)()
    comp: pointer to function returning void
char (*(*x())[])()
    x: function returning pointer to array[] of pointer to function returning char
char (*(*x[3])())[5]
    x: array[3] of pointer to function returning pointer to array[5] of char

声明语法:(除去最左边的数据类型)

dcl: 
    前面带0或1或多个*的 direct-dcl
direct-dcl: 
    name
    (dcl)
    direct-dcl()
    direct-dcl[可选的长度]

声明分析逻辑:

  1. 找到name,一个声明中name是唯一的,name是direct-dcl。
  2. direct-dcl向右边结合[]和(),结果依旧是direct-dcl。
  3. direct-dcl向左边结合任意个*(含0个),结果是dcl。
  4. dcl向左边结合一个(,向右边结合一个),结果是direct-dcl,跳回步骤二。如果左右不存在(),否则声明分析完毕。

举例,(*pfa[])()的声明分析逻辑如下图:

例程是一个递归声明分析程序,从左到右,从外向内递归分析。

  1. 核心函数gettoken(),跳过空格,读入1或多个字符,返回tokentype,并为外部变量tokentype、token赋值。tokentype有五种可能的值:
    1. 枚举值PARENS,并将"()"写入到token。
    2. '('。
    3. 枚举值BRACKETS,并将'['、'['和']'之间的数字、']'组成的字符串写入到token。
    4. 枚举值NAME,并将name字符串写入到token。
    5. 其他字符或EOF。其他字符中有效的只有'*'、')'、'\n'。
  2. dcl(),读入所有的'*'字符,并多调用一次gettoken(),再调用dirdcl(),之后依据读入'*'字符的个数,将相同个数的" pointer to"输出到out。
  3. dirdcl(),如果tokentype为 '(',调用dcl(),如果tokentype为NAME,则将token写入到name。在这之后调用一次gettoken()。如果tokentype为PAREN,则将" function returning"输出到out,并再次调用gettoken()。如果tokentype为BRACKETS,则将" array" + token + " of"输出到out,并再次调用gettoken()。

举例,例程分析以下声明的逻辑:

char (*(*x[3])())[5]
    x: array[3] of pointer to function returning pointer to array[5] of char
  1. 调用gettoken(),将"char"写入到datatype。
  2. 调用dcl()。
  3. dcl()中:调用gettoken()返回'(',调用dirdcl()。
  4. dirdcl()中:因为tokentype为'(',调用dcl(),之后再调用gettoken()返回BRACKETS,将" array[5] of "输出到out。
  5. dcl()中:因为调用gettoken()返回'*',再次调用gettoken()返回'(',调用dirdcl(),之后将" pointer to"输出到out。
  6. dirdcl()中:因为tokentype为'(',调用dcl(),之后再调用gettoken()返回PAREN,将" function returning"输出到out。
  7. dcl()中:因为调用gettoken()返回'*',再次调用gettoken()返回NAME,调用dirdcl(),之后将" pointer to"输出到out。
  8. dirdcl()中:因为tokentype为NAME,将token(即"x")写入到name,并后跟一个冒号。之后再调用gettoken()返回BRACKETS,将" array[3] of "输出到out。
  9. 递归完毕,最后以name、冒号、out、datatype的顺序打印。

main.c:

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define MAXTOKEN 100

enum { NAME, PARENS, BRACKETS };

void dcl(void);
void dirdcl(void);
int gettoken(void);
int tokentype; /* type of last token */
char token[MAXTOKEN]; /* last token string */
char name[MAXTOKEN]; /* identifier name */
char datatype[MAXTOKEN]; /* data type = char, int, etc. */
char out[1000];

main() /* convert declaration to words */
{
    while (gettoken() != EOF) { /* 1st token on line */
        strcpy(datatype, token); /* is the datatype */
        out[0] = '\0';
        dcl(); /* parse rest of line */
        if (tokentype != '\n')
            printf("syntax error\n");
        printf("%s: %s %s\n", name, out, datatype);
    }
    return 0;
}

int gettoken(void) /* return next token */
{
    int c, getch(void);
    void ungetch(int);
    char *p = token;

    while ((c = getch()) == ' ' || c == '\t')
        ;
    if (c == '(') {
        if ((c = getch()) == ')') {
            strcpy(token, "()");
            return tokentype = PARENS;
        } else {
            ungetch(c);
            return tokentype = '(';
        }
    } else if (c == '[') {
        for (*p++ = c; (*p++ = getch()) != ']'; )
            ;
        *p = '\0';
        return tokentype = BRACKETS;
    } else if (isalpha(c)) {
        for (*p++ = c; isalnum(c = getch()); )
            *p++ = c;
        *p = '\0';
        ungetch(c);
        return tokentype = NAME;
    } else
        return tokentype = c;
}

/* dcl: parse a declarator */
void dcl(void)
{
    int ns;

    for (ns = 0; gettoken() == '*'; ) /* count *'s */
        ns++;
    dirdcl();
    while (ns-- > 0)
        strcat(out, " pointer to");
}

/* dirdcl: parse a direct declarator */
void dirdcl(void)
{
    int type;

    if (tokentype == '(') { /* ( dcl ) */
        dcl();
        if (tokentype != ')')
            printf("error: missing )\n");
    } else if (tokentype == NAME) /* variable name */
        strcpy(name, token);
    else
        printf("error: expected name or (dcl)\n");
    while ((type=gettoken()) == PARENS || type == BRACKETS)
        if (type == PARENS)
            strcat(out, " function returning");
        else {
            strcat(out, " array");
            strcat(out, token);
            strcat(out, " of");
        }
}

将描述转换为声明

示例:x is a function
returning a pointer to an array of pointers to functions returning char 表示为x () * [] * () char,将它转换为char (*(*x())[])()

/* undcl: convert word descriptions to declarations */
main()
{
    int type;
    char temp[MAXTOKEN];

    while (gettoken() != EOF) {
        strcpy(out, token);
        while ((type = gettoken()) != '\n')
            if (type == PARENS || type == BRACKETS)
                strcat(out, token);
            else if (type == '*') {
                sprintf(temp, "(*%s)", out);
                strcpy(out, temp);
            } else if (type == NAME) {
                sprintf(temp, "%s %s", token, out);
                strcpy(out, temp);
            } else
                printf("invalid input at %s\n", token);
        printf("%s\n", out);
    }
    return 0;
}