数组和指针

  • 关键字:static
  • 运算符:&、*、(一元)
  • 如何创建并初始化数组
  • 指针(在已学过的基础上)、指针和数组的关系
  • 编写处理数组的函数
  • 二维数组

数组

数组由数据类型相同的一系列元素组成。需要使用数组时候,通过声明数组告诉编译器数组中内含多少元素和这些元素的类型。编译器根据这些信息正确的创建数组。

int main(void)
{
    float candy[365]; /* 内含365个float类型元素的数组 */
    char code[12];    /* 内含12个char类型元素的数组 */
    int states[50];   /* 内含50个int类型元素的数组 */
    ...
}

初始化数组

指定初始化(C99)

int arr[6] = {[5] = 212}; //把arr[5]初始化为212
对于一般的初始化,在初始化一个元素后,未初始化的元素都会设置为0.

数组边界

在使用数组时,要防止数组下标超出边界。也就是说,必须确保下标是有效的值

编译器不会检查数组下标是否使用得当。在C标准中,使用越界下标的结果是未定义的。这意味着程序看上去可以运行,但是运行结果很奇怪,或异常中止。

指定数组的大小

多维数组

初始化二维数组

其他多维数组

可以把一维数组想象成一行数据,把二维数组想象成数据表,把三维数组想象成一叠数据表。
通常,处理三维数组要使用3重嵌套循环,处理四维数组要使用4重嵌套循环。对于其他多维数组,以此类推。

指针和数组

指针提供一种以符号形式使用地址的方法。因为计算机的硬件指令非常依赖地址,指针在某种程度上把程序员想要传达的指令以更接近机器的方式表达。因此,在使用指针的程序更有效率。尤其是,指针能有效的处理数组。数组表示法其实是在变相的使用指针。

flizny == &flizny[0]; //数组名是该数组首元素的地址

flizny和&flizny[0]都表示数组首元素的内存地址(&是地址运算符)。两者都是常量,在程序的运行过程中,不会改变。但是,可以把它们赋值给指针变量,然后可以修改指针变量的值。

  • 指针的值是它所指向对象的地址。地址的表示方式依赖于计算机内部的硬件。许多计算机都是按字节编址,意思是内存中的每个字节都按顺序编号。这里,一个较大对象的地址(如double类型的变量)通常是该对象第一个字节的地址。
  • 在指针前面使用*运算符可以得到该指针所指向对象的值。
  • 指针加1,指针的值递增它所指向类型的大小(以字节为单位)

dataes + 2 == &dates[2] // 相同的地址
*(dates+2) == dates[2] // 相同的值

不要混淆*(dates+2)和dates+2。间接运算符()的优先级高于+,所以dates+2相当于(dates)+2

函数、数组和指针

指针表示法和数组表示法

处理数组的函数实际上用指针作为参数,但是在编写这样的函数时,可以选择是使用数组表示法还是指针表示法。使用数组表示法,让函数是处理数组的这一意图更加明显。
指针表示法(尤其与递增运算符一起使用时)更接近机器语言,因此一些编译器在编译时能生成效率更高的代码。

指针操作

C提供了一些基本的指针操作,下面的程序示例中演示了8中不同的操作。

#include <stdio.h>
int main(void)
{
    int urn[5] = { 100, 200, 300, 400, 500};
    int * ptr1,*ptr2,*ptr3;

    ptr1 = urn;  //把一个地址赋给指针
    ptr2 = &urn[2]; //把一个地址赋给指针
                    //解引用指针,以及获取指针的地址
    printf("pointer value, dereferenced pointer, pointer address:\n");
    printf("ptr1 = %p, *ptr1 = %d, &ptr1 = %p\n", ptr1, *ptr1, &ptr1);

    //指针加法
    ptr3 = ptr1 + 4;
    printf("\nadding an int to a pointer:\n");
    printf("ptr1 + 4 = %p, *(ptr1 + 4) = %d\n", ptr1 + 4, *(ptr1 +4));
    ptr1++;           //递增指针
    printf("\nvalues after ptr1++:\n");
    printf("ptr1 = %p, *ptr1 = %d, &ptr1 = %p\n", ptr1, *ptr1, &ptr1);
    ptr2--;           //递减指针
    printf("\nvalues after --ptr2:\n");
    printf("ptr2 = %p, *ptr2 = %d, &ptr2 = %p\n", ptr2, *ptr2, &ptr2);
    --ptr1;            //恢复为初始值
    ++ptr2;            //恢复为初始值
    printf("\nPointers reset to original values:\ n"); 
    printf("ptr1 = %p, ptr2 = %p\n", ptr1, ptr2); 
    // 一个指针减去另一个指针 
    printf("\nsubtracting one pointer from another:\ n"); 
    printf("ptr2 = %p, ptr1 = %p, ptr2 - ptr1 = %td\ n", ptr2, ptr1, ptr2 - ptr1); 
    // 一个 指针减去一个整数 
    printf("\nsubtracting an int from a pointer:\n"); 
    printf("ptr3 = %p, ptr3 - 2 = %p\ n", ptr3, ptr3 - 2); 

    return 0;
}

运行结果

pointer value, dereferenced pointer, pointer address: 
ptr1 = 0x7fff5fbff8d0, *ptr1 = 100, &ptr1 = 0x7fff5fbff8c8 

adding an int to a pointer: 
ptr1 + 4 = 0x7fff5fbff8e0, *(ptr1 + 4) = 500 
values after ptr1++: 
ptr1 = 0x7fff5fbff8d4, *ptr1 = 200, &ptr1 = 0x7fff5fbff8c8
values after --ptr2: 
ptr2 = 0x7fff5fbff8d4, *ptr2 = 200, &ptr2 = 0x7fff5fbff8c0 

Pointers reset to original values: 
ptr1 = 0x7fff5fbff8d0, ptr2 = 0x7fff5fbff8d8 

subtracting one pointer from another: 
ptr2 = 0x7fff5fbff8d8, ptr1 = 0x7fff5fbff8d0, ptr2 - ptr1 = 2 

subtracting an int from a pointer: 
ptr3 = 0x7fff5fbff8e0, ptr3 - 2 = 0x7fff5fbff8d8

  • 指针减去一个整数:可以使用-运算符从一个指针中减去一个整数。指针必须是第1个运算对象,整数是第2 个运算对象。该整数将乘以指针指向类型的大小( 以字节为单位),然后用初始地址减去乘积。所以ptr3 - 2与&urn[2]等价,因为ptr3指向的是&urn[4]。如果相减的结果超出了初始指针所指向数组的范围,计算 结果则是未定义的。除非正好超过数组末尾第一个位置,C保证该指针有效。
  • 递减指针:当然,除了递增指针还可以递减指针。在本例中,递减ptr3使其指向数组的第2个元素而不是第3个元素。前缀或后缀的递增和递减运算符都可以使用。注意,在重置ptr1和ptr2前,它们都指向相同的元素urn[1]。
  • 指针求差:可以计算两个指针的差值。通常,求差的两个指针分别指向同一个数组的不同元素,通过计算求出两元素之间的距离。差值的单位与数组类型的单位相同。例如,程序清单10.13的输出中,ptr2 - ptr1得 2,意思是这两个指针所指向的两个元素相隔两个int,而不是2字节。只要两个指针都指向相同的数组(或者 其中一个指针指向数组后面的第1个地址),C都能保证相减运算有效。如果指向两个不同数组的指针进行求差运算可能会得出一个值,或者导致运行时错误。
  • 比较:使用关系运算符可以比较两个指针的值,前提是两个指针都指向相同类型的对象。

保护数组中的数据

编写一个处理基本类型(如,int)的函数时,要选择是传递int类型的值还是传递指向int的指针。通常都是直接传递数值,只有程序需要在函数中改变该数值时,才会传递指针。对于数组别无选择,必须传递指针,因为这样做效率高。

传递地址会导致一些问题。C通常都按值传递数据,因为这样做可以保证数据的完整性。如果函数使用的是原始数据的副本,就不会意外修改原始数据。但是,处理数组的函数通常都需要使用原始数据,因此这样的函数可以修改原数组。有时,这正是我们需要的。

对形式参数使用const

const告诉编译器,该函数不能修改ar指向的数组中的内容。

指针和多维数组

指针和多维数组有什么关系

指针的兼容性

指针之间的赋值比数值类型之间的赋值要严格。例如,不用类型转换就可以把int类型的值赋给double类型的变量,但是两个类型的指针不能这样做。

函数和多维数组

变长数组(VLA)

TIPS:
变长数组中的“变”不是指可以修改已创建数组的大小,一旦创建了变长数组,它的大小则保持不变。这里的“变”指的是:在创建数组时,可以使用变量指定数组的维度。


变长数组还允许动态内存分配,这说明可以在程序运行时指定数组的大小。普通C数组都是静态内存分配,即在编译时确定数组的大小。由于数组大小是常量,所以编译器在编译时就知道了。

复合字面量

关键概念

数组用于储存相同类型的数据。C把数组看作是派生类型,因为数组是建立在其他类型的基础上。
通常编写一个函数来处理数组,这样在特定的函数中解决特定的问题,有助于实现程序的模块化。在把数组名作为实际参数时,传递给函数的不是正规数组,而是数组的地址(因此,函数对应的形式参数是指针)。为了处理数组,函数必须知道从何处开始读取数据和要处理多少个数组元素。数组地址提供了“地址”,“元素个数”可以内置在函数中或作为单独的参数传递。第2种方法更普遍,这样做可以让同一个函数处理不同大小的数组。





上一页  C Primer Plus阅读学习(六)

下一页  web高并发的理解和发现(一)