《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接收两个实参
- 第一个实参是整数,表示第二个实参的元素个数,通常命名为argc。
- 第二个实参是字符串数组,从环境接收,通常命名为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[可选的长度]
声明分析逻辑:
- 找到name,一个声明中name是唯一的,name是direct-dcl。
- direct-dcl向右边结合[]和(),结果依旧是direct-dcl。
- direct-dcl向左边结合任意个*(含0个),结果是dcl。
- dcl向左边结合一个(,向右边结合一个),结果是direct-dcl,跳回步骤二。如果左右不存在(),否则声明分析完毕。
举例,(*pfa[])()的声明分析逻辑如下图:
例程是一个递归声明分析程序,从左到右,从外向内递归分析。
- 核心函数gettoken(),跳过空格,读入1或多个字符,返回tokentype,并为外部变量tokentype、token赋值。tokentype有五种可能的值:
- 枚举值PARENS,并将"()"写入到token。
- '('。
- 枚举值BRACKETS,并将'['、'['和']'之间的数字、']'组成的字符串写入到token。
- 枚举值NAME,并将name字符串写入到token。
- 其他字符或EOF。其他字符中有效的只有'*'、')'、'\n'。
- dcl(),读入所有的'*'字符,并多调用一次gettoken(),再调用dirdcl(),之后依据读入'*'字符的个数,将相同个数的" pointer to"输出到out。
- 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
- 调用gettoken(),将"char"写入到datatype。
- 调用dcl()。
- dcl()中:调用gettoken()返回'(',调用dirdcl()。
- dirdcl()中:因为tokentype为'(',调用dcl(),之后再调用gettoken()返回BRACKETS,将" array[5] of "输出到out。
- dcl()中:因为调用gettoken()返回'*',再次调用gettoken()返回'(',调用dirdcl(),之后将" pointer to"输出到out。
- dirdcl()中:因为tokentype为'(',调用dcl(),之后再调用gettoken()返回PAREN,将" function returning"输出到out。
- dcl()中:因为调用gettoken()返回'*',再次调用gettoken()返回NAME,调用dirdcl(),之后将" pointer to"输出到out。
- dirdcl()中:因为tokentype为NAME,将token(即"x")写入到name,并后跟一个冒号。之后再调用gettoken()返回BRACKETS,将" array[3] of "输出到out。
- 递归完毕,最后以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;
}