MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C语言结构体作为函数参数的传递方式

2021-07-103.7k 阅读

C语言结构体作为函数参数的传递方式

在C语言中,结构体是一种非常重要的数据类型,它允许我们将不同类型的数据组合在一起,形成一个新的数据类型。当我们需要在函数之间传递结构体时,有几种不同的传递方式可供选择。每种方式都有其特点和适用场景,深入理解这些传递方式对于编写高效、健壮的C语言程序至关重要。

结构体值传递

  1. 原理
    • 结构体值传递是指将结构体变量作为一个整体,把它的所有成员的值都复制一份传递给函数的形参。在函数内部对形参结构体的修改,不会影响到函数外部的实参结构体。
    • 这就如同我们在现实生活中,给别人一份文件的副本,别人对副本进行修改,不会影响到我们手中的原始文件。
  2. 代码示例
#include <stdio.h>

// 定义一个结构体
struct Point {
    int x;
    int y;
};

// 函数声明,采用结构体值传递
void printPoint(struct Point p) {
    printf("Point: (%d, %d)\n", p.x, p.y);
}

int main() {
    struct Point myPoint = {10, 20};
    printPoint(myPoint);
    return 0;
}

在上述代码中,printPoint 函数接受一个 struct Point 类型的结构体参数 p。在 main 函数中,我们创建了一个 myPoint 结构体变量,并将其传递给 printPoint 函数。printPoint 函数内部打印出结构体的成员值。由于是值传递,即使在 printPoint 函数内部对 p 进行修改,myPoint 的值也不会改变。 3. 优缺点

  • 优点
    • 这种传递方式简单直观,符合我们对一般数据类型传递的认知。对于小型结构体,其性能开销相对较小,因为复制操作相对较快。
  • 缺点
    • 当结构体比较大时,复制结构体的所有成员会带来较大的性能开销。例如,如果结构体包含大量的成员或者成员本身是大型数组等,复制操作会消耗较多的时间和内存。

结构体指针传递

  1. 原理
    • 结构体指针传递是将结构体变量的地址传递给函数的形参。函数通过这个指针来访问和修改结构体的成员。这就好比我们给别人一个指向文件的指针(地址),别人通过这个指针去访问和修改原始文件。
  2. 代码示例
#include <stdio.h>

// 定义一个结构体
struct Rectangle {
    int width;
    int height;
};

// 函数声明,采用结构体指针传递
void setRectangle(struct Rectangle *rect, int w, int h) {
    rect->width = w;
    rect->height = h;
}

void printRectangle(struct Rectangle *rect) {
    printf("Rectangle: width = %d, height = %d\n", rect->width, rect->height);
}

int main() {
    struct Rectangle myRect;
    setRectangle(&myRect, 5, 10);
    printRectangle(&myRect);
    return 0;
}

在这段代码中,setRectangle 函数和 printRectangle 函数都接受一个 struct Rectangle * 类型的指针参数。在 main 函数中,我们先定义了一个 myRect 结构体变量,然后通过 & 运算符获取其地址,并将地址传递给 setRectangle 函数,该函数通过指针修改结构体的成员值。接着,printRectangle 函数通过指针打印出结构体的成员值。 3. 优缺点

  • 优点
    • 对于大型结构体,传递指针的性能开销比值传递要小得多,因为只需要传递一个指针(通常是4字节或8字节,取决于系统架构),而不是复制整个结构体。这大大提高了函数调用的效率。
    • 通过指针传递,函数可以直接修改原始结构体,这在很多需要修改传入数据的场景中非常有用。
  • 缺点
    • 由于通过指针操作结构体,代码的可读性相对值传递可能会稍差一些,需要更小心地处理指针,避免出现空指针引用等错误。

结构体引用传递(C语言中通过指针模拟)

  1. 原理
    • 在C++ 中,有直接的引用传递方式。但在C语言中,我们可以通过指针来模拟结构体引用传递。其本质和结构体指针传递类似,都是通过传递结构体的地址来操作原始结构体,但在使用上更接近引用的概念。
  2. 代码示例
#include <stdio.h>

// 定义一个结构体
struct Circle {
    int radius;
    int centerX;
    int centerY;
};

// 函数声明,通过指针模拟引用传递
void scaleCircle(struct Circle *c, int factor) {
    c->radius *= factor;
}

void printCircle(struct Circle *c) {
    printf("Circle: radius = %d, center = (%d, %d)\n", c->radius, c->centerX, c->centerY);
}

int main() {
    struct Circle myCircle = {5, 10, 10};
    printCircle(&myCircle);
    scaleCircle(&myCircle, 2);
    printCircle(&myCircle);
    return 0;
}

在这个例子中,scaleCircle 函数和 printCircle 函数接受 struct Circle * 类型的指针参数。在 main 函数中,我们创建了 myCircle 结构体变量,并通过传递其地址来调用函数。scaleCircle 函数通过指针修改 myCircleradius 成员,printCircle 函数通过指针打印出 myCircle 的成员值。 3. 优缺点

  • 优点
    • 同结构体指针传递一样,对于大型结构体,性能开销小。并且在逻辑上,虽然是通过指针实现,但我们可以将其看作是对结构体的直接操作,在一定程度上提高了代码的逻辑性和可读性(相比于普通指针操作)。
  • 缺点
    • 仍然需要小心处理指针,避免空指针等问题。而且在C语言中毕竟不是真正的引用,在语法上没有C++ 引用那么简洁。

结构体数组作为函数参数传递

  1. 原理
    • 当我们需要传递多个结构体实例时,可以使用结构体数组作为函数参数。和普通数组传递类似,传递的实际上是结构体数组的首地址。函数内部可以通过这个首地址访问和操作整个结构体数组。
  2. 代码示例
#include <stdio.h>

// 定义一个结构体
struct Student {
    char name[50];
    int age;
    float score;
};

// 函数声明,接受结构体数组作为参数
void printStudents(struct Student students[], int size) {
    for (int i = 0; i < size; i++) {
        printf("Student %d: Name = %s, Age = %d, Score = %.2f\n", i + 1, students[i].name, students[i].age, students[i].score);
    }
}

int main() {
    struct Student class[3] = {
        {"Alice", 20, 85.5},
        {"Bob", 21, 90.0},
        {"Charlie", 20, 78.0}
    };
    printStudents(class, 3);
    return 0;
}

在上述代码中,printStudents 函数接受一个 struct Student 类型的数组 students 和数组的大小 size。在 main 函数中,我们定义了一个包含3个 struct Student 实例的数组 class,并将其传递给 printStudents 函数。printStudents 函数通过循环遍历数组,打印出每个学生的信息。 3. 优缺点

  • 优点
    • 对于批量处理结构体数据非常方便,可以一次性传递多个结构体实例。和传递单个结构体指针一样,性能开销较小,因为只传递了数组的首地址。
  • 缺点
    • 需要额外传递数组的大小信息,以确保在函数内部能够正确地遍历数组。如果在函数调用时传入错误的数组大小,可能会导致数组越界等问题。

结构体嵌套时的参数传递

  1. 原理
    • 当结构体中包含其他结构体成员时,也就是结构体嵌套的情况。在传递这种结构体时,同样可以采用上述的值传递、指针传递等方式。但需要注意的是,无论是值传递还是指针传递,对于嵌套的结构体成员,都要遵循相应的传递规则。
  2. 代码示例
#include <stdio.h>

// 定义一个内部结构体
struct Inner {
    int value1;
    int value2;
};

// 定义一个外部结构体,包含内部结构体成员
struct Outer {
    struct Inner inner;
    char label[20];
};

// 函数声明,采用结构体值传递
void printOuter(struct Outer o) {
    printf("Label: %s, Inner - value1 = %d, value2 = %d\n", o.label, o.inner.value1, o.inner.value2);
}

// 函数声明,采用结构体指针传递
void setOuter(struct Outer *o, int v1, int v2, const char *l) {
    o->inner.value1 = v1;
    o->inner.value2 = v2;
    snprintf(o->label, sizeof(o->label), "%s", l);
}

int main() {
    struct Outer myOuter;
    setOuter(&myOuter, 10, 20, "Test");
    printOuter(myOuter);
    return 0;
}

在这个例子中,struct Outer 结构体包含一个 struct Inner 结构体成员。printOuter 函数采用值传递方式打印 struct Outer 的信息,setOuter 函数采用指针传递方式设置 struct Outer 的成员值。无论是值传递还是指针传递,对于嵌套的 struct Inner 结构体成员都按照相应规则处理。 3. 优缺点

  • 优点
    • 结构体嵌套可以方便地组织复杂的数据结构,并且在传递时可以根据实际需求选择合适的传递方式,以平衡性能和代码可读性。
  • 缺点
    • 当结构体嵌套层次较深时,代码的维护和理解难度会增加。特别是在值传递时,如果嵌套的结构体较大,性能开销会累积,变得更加显著。

不同传递方式在实际项目中的应用场景

  1. 值传递的应用场景
    • 当结构体非常小,复制结构体的开销可以忽略不计时,值传递是一个简单且直观的选择。例如,一个只包含两个 int 类型成员的结构体,在一些对性能要求不是特别高,且希望代码简单易读的场景中,可以使用值传递。
    • 当函数不需要修改传入的结构体,只是对其进行读取操作时,值传递可以保证原始结构体的安全性,避免函数内部意外修改结构体。
  2. 指针传递的应用场景
    • 在处理大型结构体时,指针传递是首选方式。比如在图形处理程序中,可能会有一个包含大量顶点坐标等信息的结构体,传递这样的结构体时,使用指针传递可以显著提高性能。
    • 当函数需要修改传入的结构体时,指针传递是必须的。例如在文件系统操作中,可能有一个结构体记录文件的属性,函数需要根据某些操作修改这些属性,此时使用指针传递结构体可以直接修改原始数据。
  3. 结构体数组传递的应用场景
    • 在需要批量处理结构体数据的场景中,结构体数组传递非常有用。例如在一个学生管理系统中,可能需要对多个学生的信息进行统一的查询、修改等操作,使用结构体数组传递这些学生信息可以方便地实现这些功能。
  4. 结构体嵌套传递的应用场景
    • 在表示复杂数据关系时,结构体嵌套很常见。比如在一个游戏开发项目中,一个角色结构体可能包含位置结构体、装备结构体等嵌套结构体。在传递角色结构体时,根据实际情况选择合适的传递方式,以确保游戏的性能和数据处理的正确性。

在C语言编程中,深入理解结构体作为函数参数的不同传递方式,并根据实际需求合理选择,对于编写高效、可靠的程序至关重要。通过上述详细的讲解和丰富的代码示例,希望读者能够在实际项目中灵活运用这些知识。