C 语言回顾 - 高级特性

接着上一篇C 语言回顾 - 基本特性, 本文继续介绍 C 语言中的一些高级特性。

指针

指针可以说是 C 语言中最让人迷惑也是最迷人的特性了。

指针变量

现代计算机将内存分为字节(byte),每个字节存储8位(bit)信息。每个字节都有其唯一的地址,程序中的每个变量占有一个或多个字节的内存,把第一个字节的地址称为变量地址。

指针变量被用来存储地址信息,例如:用指针变量 p 存储变量 i 的地址信息,我们就说”p 指向 i”。更通俗点,指针就是地址,而指针变量就是存储地址的变量。其声明格式如下:

int *p;

取址运算符

使用取址运算符 & 获取变量在内存中的地址:

int i;
int *p = &i;

上述语句把 p 指向 i。

间接寻址运算符

间接寻址运算符 * 用于访问存储在对象中的内容:

int i = 10;
int *p = &i;        // p 指向 i 
printf("%d\n", *p); // 获取 i 的值,输出 10

指针赋值

C 语言允许对两个相同类型的指针进行赋值操作:

int i = 10, j = 20;
int *p = &i;
int *q = &j;

p = q;
printf("%d %d %d\n", *p, *q, j);    // p 和 q 都指向 j,输出 20 20 20

*p = 30;                            
printf("%d %d %d\n", *p, *q, j);    // 因为 p 和 q 都指向 j,修改 p 指向的变量的值,也就是修改 j 的值, 输出 30 30 30

*q = 40;                            
printf("%d %d %d\n", *p, *q, j);    // 因为 p 和 q 都指向 j,修改 q 指向的变量的值,也就是修改 j 的值, 输出 40 40 40

指针做为参数

直接上代码

void swap(int *p, int *q);

int main() {
    int i = 10, j = 20;
    swap(&i, &j);
    printf("%d %d \n", i, j);
}

void swap(int *p, int *q) {
    int temp;
    temp = *p;
    *p = *q;
    *q = temp;
}

C 语言默认为用值进行参数传递,使用指针可以修改传递给函数的形参的值,在某些情况下非常方便。

可以使用 const 来表示函数不会改变指针参数所指向的对象。const 应该放置在形式参数声明中,后面紧跟着形式参数的类型声明:

void f(const int *p) {
    *p = 0; // 编译时将报错
}

指针作为返回值

也比较容易理解:

int *max(int *a, int *b);

int main() {
    int i = 22, j = 33;
    int *k = max(&i, &j);
    printf("%d \n", *k);
}

int *max(int *a, int *b) {
    return *a > *b ? a : b;
}

指针的算术运算

  1. 指针加上整数: 指针 p 加上整数 j 产生指向特定元素的指针,这个特定元素是 p 原先指向的元素的后 j 个位置。也就是说,如果指针 p 指向数组元素 a[i], 那么 p + j 指向 a[i+j];
  2. 指针减去整数: 如果 p 指向数组元素 a[i], 那么 p-j 就指向数组元素 a[p-j];
  3. 两个指针相减:两个指针相减,结果为指针之间的距离。因此,如果 p 指向 a[i] 且 q 指向 a[j], 那么 p-q 就等于 i-j;
  4. 指针比较: 当两个指针指向同一个数组时,可以用关系运算符进行指针比较;

数组名作为指针

可以用数组的名字作为指向数组第一个元素的指针:

int a[10];
*a = 7;         // 修改数组第一个元素 a[0] 的值为 7
*(a+1) = 12;    // 修改数组第二个元素 a[1] 的值为 12

数组名作为指针常用于循环中:

int sum, *p;

int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for (p = &a[0]; p < &a[10]; p++) {
    sum += *p;
}
printf("%d \n", sum);

可被替换为:

int sum, *p;

int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for (p = a; p < a + 10; p++) {
    sum += *p;
}
printf("%d \n", sum);

此外,数组在传递给函数时,总是被视为指针,也会引起一些重要的结果:

  1. 因为没有对数组本身的复制,所以作为实参的数组是可以被改变的;为了不改变,可以增加 const 修饰符 ;
  2. 因为数组没有复制,所以传递数组所需的时间与数组大小无关,传递大数组也不会有不利影响;
  3. 如果需要,可以把数组形式参数声明为指针;