5.1 指针与地址

地址运算符&只能应用于内存中的对象,即变量与数组元素。它不能作用于表达式、常量或register类型的变量。

运算优先级

一元运算符*和&的优先级比算术运算符的优先级高。

*ip += 1
++*ip
(*ip)++

语句(ip)++中的圆括号是必需的,否则,该表达式将对ip进行加一运算,而不是对ip指向的对象进行加一运算,类似于\和++这样的一元运算符遵循从右至左的结合顺序。

5.2 指针与函数参数

传值的方式将参数值传递给被调用函数。传引用使用&

// array_address.c
char *str = "hello";
printf("[begin] str %s\n", str);
test_array_name(str);
printf("[end] str %s\n", str);

void test_array_name(char *s) {
    printf("[in] str %s\n", ++s);
}
void test_array_name1( char &s) {
}
/* out
>> [begin] str hello
>> [in] ca[] ello
>> [end] str hello
*/

5.3 指针与数组

通过数组下标所能完成的任何操作都可以通过指针来实现。用指针编写的程序比用数组下标编写的程序执行速度快。

指针加1意味着,pa+1指向pa所指向的对象的下一个对象。

在计算数组元素a[i]的值时,实际上先将其转换为*(a+i)的形式。通过数组和下标实现的表达式可等价地通过指针和偏移量实现。

数组名和指针之间有一个不同之处。指针是一个变量,所以pa=a和pa++是合法的。但数组名不是变量,因此a=pa和a++形式的语句是非法的。由此可以推断,当把数组名传递给一个函数时,实际上传递的是该数组第一个元素的地址。函数由此地址生成了一个局部变量。

main
{
    char ca[] = "hello";
    printf("[begin] ca[] %s\n", ca);
    test_array_name(ca);
    printf("[end] ca[] %s\n", ca);
}

void test_array_name(char s[])
{
    s++;
    printf("[in] ca[] %s\n", s);
}
out:
>> [begin] ca[] hello
>> [in] ca[] ello
>> [end] ca[] hello

函数定义中,形式参数 char s[]和char *s是等价的,习惯用后一种形式,它直观地表明了该参数是一个指针。

p[-1]、p[-2]这样的表达式在语法上都是合法的,它们分别引用位于p[0]之前的两个元素。

5.4 地址算术运算

#define ALLOCSIZE 1000
static char allocbuf[ALLOCSIZE];
static char *allocp = allocbuf;

char * alloc(int n)
{
    if (allocbuf + ALLOCSIZE - allocp >= n) {
        allocp += n;
        return allocp - n;
    } else 
        return 0;
}

void afree(char *p)
{
    if (p >= allocbuf && p < allocbuf + ALLOCSIZE)
        allocp = p;
}

对指针有意义的初始化值只能是0或者是表示地址的表达式。

指针与整数之间不能相互互换,但0是唯一的例外:常量0可以赋值给指针,指针也可以和常量0进行比较。程序中经常用符号常量NULL代替常量0,这样便于更清晰地说明常量0是指针的一个特殊值。

0不是有效的数据地址,所以经常用代表0的符号常量NULL来判别指针是否有效。

如果指针p和q指向同一个数组的成员,那么它们之间就可以进行类似与==、!=、<、>=的关系比较运算。

指针的减法是有意义的:如果p和q指向相同数组中的元素,且p<q,那么q-p+1就是位于p和q指向的元素之间的元素的数目。

int strlen(char *s)
{
    char *p = s;

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

有效的指针运算:

  1. 相同类型指针之间的赋值运算
  2. 指针同整数之间的+/-运算
  3. 指向相同数组中元素的两个指针之间的减法或比较运算
  4. 将指针赋值为0或指针与0之间的比较运算

5.5 字符指针与函数

// 两个定义之间有很大差别
char amessage[] = "now is the time";
char *pmessage = "now is the time";

5.6 指针数组以及指向指针的指针

指针本身也是变量,所以它们也可以像其他变量一样存储在数组中。

一个对字符串数组排序的例子。

5.7 多维数组

作为参数传递给函数,在函数参数声明中必须指明数组的列数。

f(int daytab[2][13])
f(int daytab[][13]) 
f(int (*daytab)[13])

5.8 指针数组的初始化

5.9 指针和多维数组

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

a分配了200个int类型长度的存储空间,用20xrow+col计算得到元素a[row][col]的位置。

b仅仅分配了10个指针,并且没有对它们初始化。

指针数组的一个重要优点在于,数组的每一行长度可以不同。

5.10 命令行参数

argc为运行程序时命令行中参数的数目 argv是一个指向字符串数组的指针,其中每个字符串对应一个参数。argv[argc]为空指针。

echo hello, world
argc 3
argv ["echo", "hello,", "world", 0]

++argv 是指向"hello"的指针 (++argv)[0] 是'h', 等效为*++argv []与操作数的结合优先级比和++高 ++argv[0] 为++(argv[0])目的是遍历一个特定的参数串

5.11 指向函数的指针

声明

int (*comp)(void *, void*)

使用

int fun(int x, int *p);
comp = fun;

int y = 1;
(*comp)(3, &y);
comp(3, &y);

函数指针的值是该函数机器代码表示中第一条指令的地址

5.12 复杂声明