数组和指针

  • 关键字 static

  • 运算符 & * (一元)

  • 创建和初始化数组

  • 指针、指针和数组

  • 处理数组的函数

  • 二维数组

数组

C99 标准中的新特性:指定初始化器(designated initializer),可以初始化指定的元素。

int array[50] = {
    0,
    1,
    [20] = 20,
    21,
    [49] = 49,
};
int arr[5] = {1, 2, 3, 4, 5};

未初始化的都为 0.

C 语言的数组要注意边界,数组越界也是一个数组使用的常见问题。

二维数组。

指针和数组

int a = 10, b = 20;
p = &a;  // p指向a
p = &b;  // p现在指向b

指针提供了一种用符号使用地址的方法。计算机硬件指令依赖地址,指针把程序员的想法以更接近机器的方式传递给机器

  • 指针的值是它指向的地址。许多计算机按字节编址,一个 int 类型的地址通常是第一个字节的地址

  • 指针前面使用 * 可以得到指向对象的值

  • 指针加 1,指针的值递增指向类型的大小

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

上例说明数组和指针关系密切。

  • 指针和数组的区别

    • 指针存放地址变量,可以修改

    • 数组是存储多个元素的集合。数组名本身可以用作指向数组首元素的指针,但数组本质上是一个固定大小的内存块。

函数、数组和指针

C 语言函数处理数组是本质上传入的是指针,所以通常要有一个额外的参数指明数组长度

int sum(int *arr, int n)
{
  int total = 0;
  for (int i=0; i<n; i++)
    total += arr[i];
  return total;
}

注意第一个参数是指针形参。还有另一种操作方法

int sum(int * start, int *end)
{
    int total = 0;
    while(start < end)
        total += *(start++);
        return total; 
}

指针变量的基本操作

  • 复制

  • 解引用 ,即 *ptr 计算出指向地址的值

  • 取地址,指针存放的地址

  • 指针与整数相加,如 ptr + 2 ,先用整数和指向类型大小相乘,然后与初始地址相加

  • 指针递增,移动到下一元素,本质和加整数一样

  • 指针求差 ptr1 - ptr2 ,计算同一数组两元素的距离

数组中数据的保护

通常处理数组时,传递地址的问题:会对数据产生修改。有时候我们并不会修改数组数据,只读。更好的做法:

int sum(const int * arr, int n)
{
    int total = 0;

    for (int i=0; i<n; i++)
      total += arr[i];

    return total; 
}

const 告诉编译器,函数不可以修改此指针指向的内容。

关于 const,如果定义一个常量

#define PI 3.14159

const double pi = 3.14159;

还有一个要注意的

const int nums[5] = {1, 2, 3, 4, 5};

int * ptr = nums;   //不合法

关于 const 的讨论

int num = 1;
const int * ptr1 = &num; //常量指针,地址指向的内容不可修改
int * const ptr2 = &num; //指针常量,指针内容不可修改
const int * const ptr3 = &num; //常量指针常量,指针不可修改,指向的内容不可修改

指针和多维数组

int matrix[4][2];

matrix 是该数组首元素的地址。matrix 的首元素 matrix[0] 是一个包含两个 int 的数组。

  • matrix == &matrix[0], &matrix[0] == &matrix[0][0]

  • matrix[0] 是一个地址,地址指向的类型为一个 int

  • matrix 也是一个地址,地址指向的类型为 2*int

  • matrix[0] + 1,和 matrix + 1 指向的地址不一样,参考上一条与指针整数相加

  • 指针的解引用

    • *(matrix[0]) 的值为 matrix[0][0]

    • *matrix 的值为 &matrix[0][0]

    • **matrix 和 *&matrix[0][0] 等价

  • matrix 是地址的地址,是指针的指针,需要解引用两次才得到值。双重间接(double indirection)

一个等价的表示 *(*(matrix+2) + 1)matrix[2][1]

如果想声明一个指针,指向一个二维数组,比如指向 matrix,int *ptr 是不行的,ptr 指向的是一个整数类型,和 matrix[0] 的类型匹配。matrix 是一个含有两个 int 类型的一维数组。因此指针要为指向一个内含两个int的数组

int (*ptr)[2];

int:这是基本数据类型,表示整数。(*pz):这是一个指针的声明,指针 pz 会被解引用。[2]:这是一个数组的大小,表示数组中有 2 个元素。

综合起来,int (*pz)[2] 就是一个指向包含 2 个整数的数组的指针。这种指针通常用于传递数组到函数中,而不仅仅是单个整数或指向整数的指针。

指针之间的赋值比较严格。不像变量之间赋值,int 可以直接赋值为 double

函数和多维数组

向函数传入一个多维数组这个需求是很常见的。

int nums[3][4];

void data_proc1(int (*ptr)[4], int n);
void data_proc2(int ptr[][4], int n);

含义为 ptr 是个指针,指向包含4个int元素的对象。

特别要注意的是

void date_proc(int ptr[][], int n);

这个语法是不对的,。编译器会把数组表示法转换成指针表示法,如 ptr[1] 转换成 *(ptr + 1),对 ptr + 1 求值,需要知道 ptr 指向的对象的大小(指针和整数的运算),因此上面的声明直接就是有问题的。

void date_proc(int ptr[3][4], int n);

这个声明是没问题的,也是最好理解的,但事实上,有没有3 无所谓,甚至 3 是被编译器忽略的。

加深理解

typedef int arr4_t[4];
typedef arr4_t arr3x4_t[3];

void date_proc(arr3x4_t ptr, int n);

进一步地,声明一个指向 N 维数组的指针时,只省略最左边的值

int data_proc1(int arr[][5][4][3][2], int n);
int data_proc2(int (*arr)[5][4][3][2], int n);

原理同上,第一对方括号指明是个指针,后面的描述指针指向数据的类型。

变长数组 VLA

对于数组处理函数的声明

int data_proc1(int arr[], int n);

对于二维数组处理函数的声明

int nums[3][4] = {
  {1, 1, 1, 1},
  {2, 2, 2, 2},
  {3, 3, 3, 3}
};

#define COLS  4
int data_proc1(int arr[][COLS], int n);

只把行数作为了函数形参,列数内置在函数体内了。

如何向使用一个函数处理任意行列的数组,一个做法是把数组作为一维数组传递,同时传入行列值,函数内计算每行的开始。

字面量

5 是 int 类型的字面量,'a' 是 char 类型的字面量,"hello world" 是字符串字面量。数组的字面量

(int [2]){1, 2};

字面量需要在创建的时候就使用。

int *ptr = (int [3]){1, 2, 3};

sum((int []){1, 2, 3}, 3);

对于二维数组字面量也是类似的

int (*ptr)[4];

ptr = (int [][4]){{1,1,1,1},{2,2,2,2}};

最后更新于