指针(Pointers)

指针是一个值,用来指定某个值的地址(即在内存中的位置)。指针是存储内存位置的变量。

你需要了解关于指针的四个基本概念:

  1. 如何声明指针(使用地址操作符 &):int *pointer = &variable;
  2. 如何为指针赋值pointer = NULL;
  3. 如何引用指针指向的值(通过解引用操作符 *,称为解引用):value = *pointer;
  4. 指针与数组的关系(C 中绝大多数数组是简单的列表,称为“1维数组”,我们将在后面的章节简要讨论多维数组和指针的关系)。

指针可以引用任何数据类型,甚至是函数。我们还会讨论指针与文本字符串的关系以及函数指针的更高级概念。

声明指针

考虑以下代码片段,它声明了两个指针:

struct MyStruct {
    int   m_aNumber;
    float num2;
};

int main() {
    int *pJ2;
    struct MyStruct *pAnItem;
}

第 1-4 行定义了一个结构体。第 8 行声明了一个指向 int 类型的指针变量 pJ2,第 9 行声明了一个指向 MyStruct 结构体的指针变量 pAnItem。因此,要声明一个变量作为指向某种类型的指针,而不是包含某种类型的变量,必须在变量名前加上星号(*)。

在下面的例子中,第 1 行声明了 var1 为指向 long 类型的指针,var2long 类型变量,而不是指向 long 的指针。第 2 行声明了 p3 为指向指针的指针(即指向 int 类型的指针的指针)。

long *var1, var2;
int **p3;

指针类型通常作为函数调用的参数。以下示例展示了如何声明一个使用指针作为参数的函数。由于 C 语言是按值传递函数参数,为了允许函数修改调用例程中的值,必须传递指向该值的指针。即使结构体中的内容在函数中没有被修改,指向结构体的指针也常作为函数参数传递。这样做是为了避免将结构体的完整内容复制到栈上。稍后会详细讨论指针作为函数参数的用法。

int MyFunction(struct MyStruct *pStruct);

为指针赋值

到目前为止,我们讨论了如何声明指针。接下来是如何为指针赋值。要将变量的地址赋给指针,使用 & 操作符(取地址操作符)。

int myInt;
int *pPointer;
struct MyStruct dvorak;
struct MyStruct *pKeyboard;

pPointer = &myInt;
pKeyboard = &dvorak;

在这里,pPointer 现在将引用 myIntpKeyboard 将引用 dvorak

指针还可以被赋值来引用动态分配的内存。malloc()calloc() 函数通常用于执行此操作。

#include <stdlib.h>
/* ... */
struct MyStruct *pKeyboard;
/* ... */
pKeyboard = malloc(sizeof *pKeyboard);

malloc 函数返回指向动态分配内存的指针(如果失败则返回 NULL)。这块内存的大小会根据 MyStruct 结构体的大小来适当分配。

指针赋值示例

以下是一个示例,展示了如何将一个指针赋值给另一个指针,以及如何将一个指针赋值为函数的返回值:

static struct MyStruct val1, val2, val3, val4;

struct MyStruct *ASillyFunction(int b) {
    struct MyStruct *myReturn;
  
    if (b == 1) myReturn = &val1;
    else if (b == 2) myReturn = &val2;
    else if (b == 3) myReturn = &val3;
    else myReturn = &val4;
  
    return myReturn;
}

struct MyStruct *strPointer;
int *c, *d;
int j;

c = &j;                           /* 使用 & 操作符为指针赋值 */
d = c;                            /* 将一个指针赋值给另一个指针 */
strPointer = ASillyFunction(3);    /* 从函数返回的指针 */

当从函数返回指针时,不要返回指向函数局部变量的指针,或指向函数参数的指针。因为指向局部变量的指针在函数退出时会变得无效。在上面的函数中,返回的指针指向的是静态变量。返回指向动态分配内存的指针也是有效的。

指针解引用

指针 p 指向变量 a。要访问指针所指向的值,使用解引用操作符 *。另外,操作符 -> 与指针和结构体一起使用。以下是一个简短的示例:

int c, d;
int *pj;
struct MyStruct astruct;
struct MyStruct *bb;

c = 10;
pj = &c;             /* pj 指向 c */
d = *pj;             /* d 被赋值为 pj 所指向的值,即 10 */
pj = &d;             /* 现在 pj 指向 d */
*pj = 12;            /* d 现在是 12 */
 
bb = &astruct;
(*bb).m_aNumber = 3;  /* 将 3 赋值给 astruct 的 m_aNumber 成员 */
bb->num2 = 44.3;      /* 将 44.3 赋值给 astruct 的 num2 成员   */
*pj = bb->m_aNumber;  /* 等同于 d = astruct.m_aNumber;          */

表达式 bb->m_aNumber 完全等同于 (*bb).m_aNumber。它们都访问由 bb 指向的结构体中的 m_aNumber 元素。接下来,我们将讨论另一种解引用指针的方法。

解引用无效指针时的错误

当解引用一个指向无效内存位置的指针时,通常会发生错误,导致程序终止。错误通常会报告为“段错误”(segmentation fault)。常见的原因是未初始化指针就尝试解引用它。

C 语言的特点之一是它给了你足够的自由,但也很容易出错。指针解引用就是一个典型的例子。你可以自由地编写代码访问你没有明确请求的内存区域。而且,系统内存分配的细节可能会让这些内存看起来是可用的。因此,尽管 99 次执行没有错误,但第 100 次执行时,系统可能会因为你“盗用”了内存而导致程序失败。所以,务必确保你的指针偏移量在分配的内存范围内!

void 指针

void *somePointer; 声明了一个指向某个未指定类型的指针。你可以为 void 指针赋值,但在解引用之前,必须将它强制转换为指向某个指定类型的指针。对于 void * 指针,指针运算也是无效的。

指针与数组

到目前为止,我们已经小心地避免在指针的上下文中讨论数组。指针与数组的相互作用可能会令人困惑,但有两个基本的陈述可以帮助我们理解:

  1. 声明为数组的变量充当该类型的指针。当它单独使用时,它指向数组的第一个元素。
  2. 指针可以像数组名一样被索引

第一个情况通常出现在数组作为函数参数传递时。函数声明该参数为指针,但实际传入的参数可能是数组的名称。第二种情况通常发生在访问动态分配的内存时。

让我们通过几个例子来进一步理解。以下代码中,调用 calloc() 实际上分配了一个 struct MyStruct 类型的数组。

struct MyStruct {
    int someNumber;
    float otherNumber;
};

float returnSameIfAnyEquals(struct MyStruct *workingArray, int size, int bb) { 
    /* 遍历数组并检查 any someNumber 是否等于 bb。如果是,返回 otherNumber 的值。
     * 如果没有值等于 bb,则返回 0.0f。 */
    for (int i = 0; i < size; i++) {
        if (workingArray[i].someNumber == bb) {
            return workingArray[i].otherNumber;
        }
    }
    return 0.0f;
}

// 声明变量
float someResult;
int someSize;
struct MyStruct myArray[4];
struct MyStruct *secondArray; // 注意,这是一个指针

const int ArraySize = sizeof(myArray) / sizeof(*myArray);

// 初始化 myArray
someResult = returnSameIfAnyEquals(myArray, ArraySize, 4);

secondArray = calloc(someSize, sizeof(struct MyStruct));
for (int i = 0; i < someSize; i++) {
    /* 用一些数据填充 secondArray */
    secondArray[i].someNumber = i * 2;
    secondArray[i].otherNumber = 0.304f * i * i;
}

指针和数组名称几乎可以互换使用;但是也有一些例外。你不能将新指针值赋给数组名称。数组名称始终指向数组的第一个元素。在 returnSameIfAnyEquals 函数中,workingArray 可以被重新赋值,因为它仅是指向 workingArray 第一个元素的指针。函数也可以返回指向数组元素的指针,这是有效的,前提是数组是作为参数传递的。函数永远不应返回指向局部变量的指针,尽管编译器可能不会报错。

声明函数参数时

声明一个没有大小的数组变量,相当于声明一个指针。通常这样做是为了强调指针变量将像数组一样使用。

/* 两个等效的函数原型 */

int LittleFunction(int *paramN);
int LittleFunction(int paramN[]);

指针运算

现在我们准备讨论指针运算。你可以对指针加减整数值。如果 myArray 被声明为某种类型的数组,那么表达式 *(myArray + j),其中 j 是一个整数,等价于 myArray[j]。例如,在上面的代码中,我们使用了表达式 secondArray[i].otherNumber,我们也可以将其写为 (*(secondArray + i)).otherNumber 或更简洁地写为 (secondArray + i)->otherNumber

需要注意的是,在对指针和整数进行加减运算时,指针的值不会直接根据整数调整,而是根据指针所指向类型的大小(以字节为单位)进行调整。例如,pointer + x 可以看作 pointer + (x * sizeof(*type))

你还可以将一个指针从另一个指针中减去,前提是它们指向同一个数组的元素(或者数组结束位置后的一个位置)。如果你有一个指向数组元素的指针,那么当你从该指针中减去数组名时,结果就是该元素的索引。以下是一个示例:

struct MyStruct someArray[20];
struct MyStruct *p2;
int i;

/* 数组初始化 ... */

for (p2 = someArray; p2 < someArray + 20; ++p2) {
    if (p2->num2 > testValue) 
        break;
}
i = p2 - someArray;

多维数组与指针的相互作用

你可能会想知道指针和多维数组如何相互作用。让我们详细看看这个问题。假设 A 被声明为一个二维浮点数组(float A[D1][D2];),并且 pf 被声明为指向浮点数的指针。如果 pf 被初始化为指向 A[0][0],那么 *(pf + 1) 就等价于 A[0][1],而 *(pf + D2) 就等价于 A[1][0]。数组元素是按行主序(row-major order)存储的。

float A[6][8];
float *pf;
pf = &A[0][0]; 
*(pf + 1) = 1.3;   /* 将 1.3 赋给 A[0][1] */
*(pf + 8) = 2.3;   /* 将 2.3 赋给 A[1][0] */

非规则二维数组

让我们来看一个稍微不同的问题。我们希望拥有一个二维数组,但不需要所有行的长度相同。我们通过声明一个指针数组来实现。下面的第二行声明 A 为一个指针数组,每个指针指向一个浮点数。以下是相应的代码:

float linearA[30];
float *A[6];

A[0] = linearA;              /*  5 - 0 = 5 个元素 */
A[1] = linearA + 5;          /* 11 - 5 = 6 个元素 */
A[2] = linearA + 11;         /* 15 - 11 = 4 个元素 */
A[3] = linearA + 15;         /* 21 - 15 = 6 个元素 */
A[4] = linearA + 21;         /* 25 - 21 = 4 个元素 */
A[5] = linearA + 25;         /* 30 - 25 = 5 个元素 */

*A[3][2] = 3.66;          /* 将 3.66 赋给 linearA[17] */
*A[3][-3] = 1.44;         /* 引用 linearA[12];
                            负索引有时很有用,但尽量避免使用。 */

通过使用指针数组,我们能够实现一个不规则长度的二维数组。

数组索引的一个有趣之处

我们在此还注意到数组索引的一个有趣现象。假设 myArray 是一个数组,i 是一个整数值。表达式 myArray[i] 等价于 i[myArray]。前者等价于 *(myArray + i),后者等价于 *(i + myArray)。这两者是相同的,因为加法是交换律的。

指针与前增量或后递减运算

指针可以与前增量(++)或后递减(--)一起使用,这种操作有时会在循环中出现,如以下示例所示。增量和递减运算应用于指针,而不是指针所指向的对象。换句话说,*pArray++ 等价于 *(pArray++)

long  myArray[20];
long  *pArray;
int   i;

/* 给 myArray 的每个元素赋值 */
pArray = myArray;
for (i = 0; i < 10; ++i) {
    *pArray++ = 5 + 3*i + 12*i*i;
    *pArray++ = 6 + 2*i + 7*i*i;
}

函数参数中的指针

通常我们需要用指针作为参数调用函数。在许多情况下,变量本身就是当前函数的一个参数,并且可能是指向某种结构体类型的指针。在这种情况下,不需要使用 & 符号来获取指针值,因为该变量本身已经是一个指针。下面的示例中,变量 pStruct 是一个指针,作为参数传递给 FunctTwo 函数,并作为参数传递给 FunctOne 函数。

FunctOne 函数的第二个参数是 int 类型。由于在 FunctTwo 函数中,mValue 是指向 int 的指针,因此必须首先使用 * 运算符对指针进行解引用,因此函数调用中的第二个参数是 *mValueFunctOne 函数的第三个参数是一个指向 long 类型的指针。由于 pAA 本身是指向 long 的指针,因此在作为第三个参数传递给函数时,不需要 & 符号。

int FunctOne(struct someStruct *pValue, int iValue, long *lValue)
{
    /* 做一些事情... */
    return 0;
}

int FunctTwo(struct someStruct *pStruct, int *mValue)
{
    int j;
    long  AnArray[25];
    long *pAA;
     
    pAA = &AnArray[13];
    j = FunctOne(pStruct, *mValue, pAA); /* pStruct 已经持有指针指向的地址,无需获取任何东西的地址。*/
    return j;
}

指针与文本字符串

在 C 语言中,文本字符串历史上是作为字符数组实现的,字符串的最后一个字节为零,即空字符 '\0'。大多数 C 语言实现都带有标准库函数用于操作字符串。许多常用的函数期望字符串是以 null 结尾的字符数组。要使用这些函数,需要包含标准 C 头文件 "string.h"

一个静态声明并初始化的字符串可能看起来像以下示例:

static const char *myFormat = "Total Amount Due: %d";

变量 myFormat 可以视为一个包含 21 个字符的数组。最后的隐式 null 字符('\0')被添加到字符串的末尾,作为数组的第 21 个元素。你也可以如下初始化数组中的单个字符:

static const char myFlower[] = { 'P', 'e', 't', 'u', 'n', 'i', 'a', '\0' };

一个初始化的字符串数组通常如下声明:

static const char *myColors[] = {
    "Red", "Orange", "Yellow", "Green", "Blue", "Violet" };

对于一个特别长的字符串初始化,可以将字符串跨多行写,如下所示:

static char *longString = "Hello. My name is Rudolph and I work as a reindeer "
    "around Christmas time up at the North Pole. My boss is a really swell guy."
    " He likes to give everybody gifts.";

与字符串相关的库函数将在后续章节中讨论。

指向函数的指针

C 语言还允许你创建指向函数的指针。指向函数的指针语法可能会比较复杂。以下是一个示例:

static int Z = 0;

int *pointer_to_Z(int x) {
    /* 返回整数指针的函数,而不是指向函数的指针 */
    return &Z;
}

int get_Z(int x) {
    return Z;
}
  
int (*function_pointer_to_Z)(int); // 指向一个接受 int 作为参数并返回 int 的函数的指针
function_pointer_to_Z = &get_Z;

printf("pointer_to_Z output: %d\n", *pointer_to_Z(3));
printf("function_pointer_to_Z output: %d", (*function_pointer_to_Z)(3));

声明 typedef 类型的函数指针通常能使代码更加清晰。以下是一个使用函数指针和 void * 指针实现回调的示例。DoSomethingNice 函数调用一个由调用者提供的函数 TalkJive,并传入调用者的数据。需要注意的是,DoSomethingNice 实际上并不知道 dataPointer 指向的是什么。

typedef  int (*MyFunctionType)(int, void *);      /* 函数指针的 typedef */

#define THE_BIGGEST 100

int DoSomethingNice(int aVariable, MyFunctionType aFunction, void *dataPointer)
{
    int rv = 0;
    if (aVariable < THE_BIGGEST) {
       /* 通过函数指针调用函数(旧方式) */
       rv = (*aFunction)(aVariable, dataPointer );
     } else {
         /* 通过函数指针调用函数(新方式) */
       rv = aFunction(aVariable, dataPointer );
    };
    return rv;
}

typedef struct {
    int    colorSpec;
    char   *phrase;
} DataINeed;

int TalkJive(int myNumber, void *someStuff)
{
    /* 将 void * 转换为该函数所需的指针类型 */
    DataINeed *myData = someStuff;
    /* talk jive. */
    return 5;
}

static DataINeed sillyStuff = { BLUE, "Whatcha talkin 'bout Willis?" };

DoSomethingNice(41, &TalkJive, &sillyStuff);

在某些 C 版本中,调用 DoSomethingNice 时可能不需要在 TalkJive 参数前加 & 符号。有些实现可能要求显式地将参数强制转换为 MyFunctionType 类型,尽管函数签名完全符合 typedef 的定义。

函数指针的应用

函数指针在 C 语言中非常有用,尤其是在实现多态性时。首先,声明一个结构体,结构体的元素是指向各个操作的函数指针,这些操作可以通过多态性来指定。其次,声明一个包含指向上述结构体的指针的基类结构体。通过扩展第二个结构体来定义类,并为类指定特定的数据,使用第一种结构体类型的静态变量,包含与类相关联的函数地址。这种多态性机制在标准库中调用文件 I/O 函数时得到了应用。

类似的机制也可以用于在 C 语言中实现状态机。声明一个结构体,结构体中包含指向函数的指针,用于处理可能发生的状态内事件,并为状态的进入和退出调用函数。该结构体的实例对应一个状态。每个状态都初始化为指向适合该状态的函数的指针。状态机的当前状态实际上是指向这些状态之一的指针。通过改变当前状态指针的值,可以有效地改变当前状态。当某个事件发生时,通过当前状态中的函数指针调用适当的函数。

C 语言中函数指针的实际使用

函数指针主要用于减少 switch 语句的复杂性。以下是使用 switch 语句的示例:

#include <stdio.h>

int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);
int div(int a, int b);

int main() {
    int i, result;
    int a = 10;
    int b = 5;
    printf("Enter the value between 0 and 3: ");
    scanf("%d", &i);
    switch(i) {
        case 0:  result = add(a, b); break;
        case 1:  result = sub(a, b); break;
        case 2:  result = mul(a, b); break;
        case 3:  result = div(a, b); break;
    }
}

int add(int i, int j) {
    return (i + j);
}

int sub(int i, int j) {
    return (i - j);
}

int mul(int i, int j) {
    return (i * j);
}

int div(int i, int j) {
    return (i / j);
}

不使用 switch 语句:

#include <stdio.h>

int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);
int div(int a, int b);

int (*oper[4])(int a, int b) = {add, sub, mul, div};

int main() {
    int i, result;
    int a = 10;
    int b = 5;
    printf("Enter the value between 0 and 3: ");
    scanf("%d", &i);
    result = oper[i](a, b);
}

int add(int i, int j) {
    return (i + j);
}

int sub(int i, int j) {
    return (i - j);
}

int mul(int i, int j) {
    return (i * j);
}

int div(int i, int j) {
    return (i / j);
}

函数指针用于创建结构体成员函数

typedef struct {
    int (*open)(void);
    void (*close)(void);
    int (*reg)(void);
} device;

int my_device_open(void) {
    /* ... */
}

void my_device_close(void) {
    /* ... */
}

void register_device(void) {
    /* ... */
}

device create(void) {
    device my_device;
    my_device.open = my_device_open;
    my_device.close = my_device_close;
    my_device.reg = register_device;
    my_device.reg();
    return my_device;
}

用于实现 this 指针(以下代码必须放在库中)

static struct device_data {
    /* ... 这里放置结构体的数据 ... */
};

static struct device_data obj;

typedef struct {
    int (*open)(void);
    void (*close)(void);
    int (*reg)(void);
} device;

static struct device_data create_device_data(void) {
    struct device_data my_device_data;
    /* ... 这里放置构造函数 ... */
    return my_device_data;
}

/* 这里省略了 my_device_open、my_device_close 和 register_device 函数 */

device create_device(void) {
    device my_device;
    my_device.open = my_device_open;
    my_device.close = my_device_close;
    my_device.reg = register_device;
    my_device.reg();
    return my_device;
}

指针构造示例

以下是一些可能有助于创建指针的构造示例:

int i;          // 整型变量 'i'
int *p;         // 整型指针 'p'
int a[];        // 整型数组 'a'
int f();        // 返回类型为整数的函数 'f'
int **pp;       // 指向整型指针的指针 'pp'
int (*pa)[];    // 指向整型数组的指针 'pa'
int (*pf)();    // 指向返回类型为整型的函数的指针 'pf'
int *ap[];      // 整型指针数组 'ap'
int *fp();      // 返回整型指针的函数 'fp'
int ***ppp;     // 指向指向整型指针的指针的指针 'ppp'
int (**ppa)[];  // 指向整型数组的指针的指针 'ppa'
int (**ppf)();  // 指向返回类型为整型的函数的指针的指针 'ppf'
int *(*pap)[];  // 指向整型指针数组的指针 'pap'
int *(*pfp)();  // 返回指向整型指针的函数的指针 'pfp'
int **app[];    // 指向整型指针的数组 'app'
int (*apa[])[]; // 指向整型数组的指针的数组 'apa'
int (*apf[])(); // 指向返回类型为整型的函数的指针的数组 'apf'
int ***fpp();   // 返回指向整型指针的指针的函数 'fpp'
int (*fpa())[]; // 返回指向整型数组的指针的函数 'fpa'
int (*fpf())(); // 返回指向返回类型为整型的函数的指针的函数 'fpf'

sizeof 运算符

sizeof 运算符通常用于获取同一函数中先前声明的静态数组的大小。

查找数组的末尾(例如来自 Wikipedia 的缓冲区溢出示例):

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
    char buffer[10];
    if (argc < 2) {
        fprintf(stderr, "USAGE: %s string\n", argv[0]);
        return 1;
    }
    strncpy(buffer, argv[1], sizeof(buffer));
    buffer[sizeof(buffer) - 1] = '\0';
    return 0;
}

要遍历数组中的每个元素,可以使用:

#define NUM_ELEM(x) (sizeof(x) / sizeof(*(x)))

for (i = 0; i < NUM_ELEM(array); i++) {
    /* 对 array[i] 做些事情 */
}

需要注意的是,sizeof 运算符只适用于函数中先前定义的内容。编译器会将其替换为一个固定的常数。在这个例子中,buffer 被声明为一个包含 10 个 char 的数组,编译器会在编译时将 sizeof(buffer) 替换为 10(等同于我们直接在代码中硬编码 10)。关于 buffer 长度的信息实际上并没有存储在内存中(除非我们单独跟踪它),也不能从数组/指针本身在运行时获得。

解决方案:数组长度问题

许多函数需要知道它们收到的数组的大小——一个在其他函数中定义的数组。例如:

/* broken.c - 演示一个缺陷 */

#include <stdio.h>
#include <string.h>
#define NUM_ELEM(x) (sizeof(x) / sizeof(*(x)))

int sum(int input_array[]) {
    int sum_so_far = 0;
    int i;
    for (i = 0; i < NUM_ELEM(input_array); i++) { // 这不会工作 - input_array 没有在此函数中定义。
        sum_so_far += input_array[i];
    }
    return sum_so_far;
}

int main(int argc, char *argv[]) {
    int left_array[] = {1, 2, 3};
    int right_array[] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
    int the_sum = sum(left_array);
    printf("the sum of left_array is: %d", the_sum);
    the_sum = sum(right_array);
    printf("the sum of right_array is: %d", the_sum);
    return 0;
}

不幸的是,在 C 和 C++ 中,无法从运行时传递的数组中获取其长度,因为(如上所述)数组的大小不会被存储。编译器总是将 sizeof 替换为常量。因此,sum() 函数需要处理超过一个常量长度的数组。

有一些常见的方法来解决这个问题:

  1. 编写函数时,要求每个数组参数都有一个“长度”参数(类型为 size_t)。通常我们在调用此函数时使用 sizeof
  2. 使用约定,例如使用空字符结尾的字符串来标记数组的结束。
  3. 替代传递原始数组,传递一个包含数组长度(如 .length)和数组(或指向第一个元素的指针)的结构体;这类似于 C++ 中的字符串或向量类。

解决方法示例:

/* fixed.c - 演示一种解决方法 */

#include <stdio.h>
#include <string.h>
#define NUM_ELEM(x) (sizeof(x) / sizeof(*(x)))

int sum(int input_array[], size_t length) {
    int sum_so_far = 0;
    int i;
    for (i = 0; i < length; i++) {
        sum_so_far += input_array[i];
    }
    return sum_so_far;
}

int main(int argc, char *argv[]) {
    int left_array[] = {1, 2, 3, 4};
    int right_array[] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
    int the_sum = sum(left_array, NUM_ELEM(left_array)); // 在这里工作,因为 left_array 在此函数中定义
    printf("the sum of left_array is: %d", the_sum);
    the_sum = sum(right_array, NUM_ELEM(right_array)); // 在这里工作,因为 right_array 在此函数中定义
    printf("the sum of right_array is: %d", the_sum);
    return 0;
}

值得一提的是,sizeof 运算符有两种变体:sizeof(type)(例如:sizeof(int) 或 sizeof(struct some_structure))
和 sizeof expression(例如:sizeof some_variable.some_field 或 sizeof 1)。
Last modified: Sunday, 12 January 2025, 1:27 PM