C

C 知识量:16 - 74 - 317

14.4 传递结构信息><

传递结构信息的方法- 14.4.1 -

函数的参数用于将外部信息传入函数中,结构的信息也可以作为参数传给函数,主要分为3种情况:

  • 传递结构本身。

  • 传递指向结构的指针。

  • 传递结构的成员。

传递结构成员- 14.4.2 -

如果结构成员的数据类型是基本数据类型,例如int、float等,就可以简单的将结构成员作为参数传递给函数。函数并不关心传递的是否是结构成员,只要其数据类型与参数类型一致就可以。例如:

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

struct people {
    char name[20];
    int age;
};
int add10(int);

int main(void) {
    int newAge;
    struct people Jeff = {
        "Jeff",
        35
    };
    newAge = add10(Jeff.age);
    printf("After count,the age is %d.\n", newAge);
    system("pause");
    return 0;
}

int add10(int n) {
    int sum = n + 10;
    return sum;
}

运行结果为:

After count,the age is 45.

以上代码中,通过Jeff.age将结构成员age的信息传递给了函数add10(),成员age与函数参数的数据类型都是int。

传递结构的地址- 14.4.3 -

可以把结构的地址作为参数传递给函数。例如上节示例可以修改为:

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

struct people {
    char name[20];
    int age;
};
int add10(const struct people *);

int main(void) {

    int newAge;
    struct people Jeff = {
        "Jeff",
        35
    };
    newAge = add10(&Jeff);
    printf("After count,the age is %d.\n", newAge);
    system("pause");
    return 0;
}

int add10(const struct people * p) {
    int sum = p->age + 10;
    return sum;
}

运行结果为:

After count,the age is 45.

与上一节传递结构成员相比,这次传递的是一个指向结构的指针。因为不需要改变结构的内容,所以在函数参数中使用了const的关键字。通过&运算符获取了结构的地址,又通过->运算符获取了结构成员age的值。

传递结构- 14.4.4 -

还有一种传递结构信息的方式就是传递结构本身。例如将以上示例代码修改为:

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

struct people {
    char name[20];
    int age;
};
int add10(struct people);

int main(void) {

    int newAge;
    struct people Jeff = {
        "Jeff",
        35
    };
    newAge = add10(Jeff);
    printf("After count,the age is %d.\n", newAge);
    system("pause");
    return 0;
}

int add10(struct people p) {
    int sum = p.age + 10;
    return sum;
}

运行结果为:

After count,the age is 45.

以上代码中,在调用add10()时,编译器会根据people模板创建了一个名为p的自动结构变量。然后,结构p的各成员被初始化为Jeff结构变量相应成员的值的副本,因此,程序是使用原来结构的副本来进行计算的,这也意味着即使在add10()中改变了结构成员的值,原结构Jeff中的值也不会改变。

其他结构特性- 14.4.5 -

C允许把一个结构赋值给另一个结构,也可以把一个结构初始化为相同类型的另一个结构,例如:

struct people {
    char name[20];
    int age;
};
struct people Jeff = {
    "Jeff",
    35
};
struct people Bob;
Bob = Jeff;                 // 把一个结构赋值给另一个结构
struct people Jame = Jeff;  // 把一个结构初始化为另一个结构

此外,函数不仅能把结构本身作为参数传递,还能把结构作为返回值返回。把结构作为函数参数可以把结构的信息传送给函数;把结构作为返回值的函数能把结构的信息从被调函数传回主调函数。结构指针也允许这种双向通信。

结构和结构指针的选择- 14.4.6 -

结构与结构指针在使用时面临选择问题,它们都有优缺点。

对于结构指针来说,其优点是:无论是以前还是现在的C实现都能使用这种方法,而且执行起来很快,只需要传递一个地址。缺点是:无法保护数据。被调函数中的某些操作可能会意外影响原来结构中的数据。不过,ANSI C新增的const限定符解决了这个问题。

对于结构来说,其优点是:函数处理的是原始数据的副本,这保护了原始数据。另外,代码风格也更清楚。缺点是:老版本的C实现可能不支持直接传递结构,而且传递结构浪费时间和存储空间。尤其是把大型结构传递给函数,而它只使用结构中的一两个成员时特别浪费。这种情况下传递指针或只传递函数所需的成员更合理。

综上所述,为了追求效率应当使用结构指针作为函数参数,如需防止原始数据被意外修改,应当使用const限定符。而传递结构本身是处理小型结构最常用的方法。

字符数组和字符指针- 14.4.7 -

在结构中通常使用字符数组来存储字符串,但理论上也可以使用指向char的指针来代替字符数组。例如:

struct people {
    char name[20];
    int age;
};

struct person {
    char * name;
    int age;
};

people中使用的是字符数组;person中使用的是指向char类型的指针。看上去它们都没问题,但是使用指针需要格外小心。

在people类型的结构中,需要分配的存储空间是可确定和计算的;但是在person中,由于字符串储存在编译器储存常量的地方,结构本身只储存1个地址,而不用为字符串分配任何存储空间。因此,应当特别关注一下指针的使用。例如:

struct person Jame;
puts("Enter a name for Jame:");
scanf("%s", Jame.name); // 这里有一个潜在的危险

scanf()把字符串放到Jame.name表示的地址上。由于这是未经初始化的变量,地址可以是任何值,因此程序可以把名放在任何地方。

结构、指针和malloc()- 14.4.8 -

在结构中使用指针处理字符串时可以使用malloc()来动态分配内存,这样做的优点是可以请求malloc()为字符串分配合适的存储空间,而不会造内存空间浪费。以下是一个示例:

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

struct person {
    char * name;
    int age;
};

int main(void) {
    struct person Jame;
    char temp[] = "Jame";
    Jame.name = (char *) malloc(strlen(temp) + 1);
    strcpy(Jame.name, temp); //把名拷贝到动态分配的内存中
    printf("Jame' name is:%s.\n", Jame.name);
    free(Jame.name);
    system("pause");
    return 0;
}

运行结果为:

Jame' name is:Jame.

复合字面量和结构- 14.4.9 -

复合字面量特性可用于结构,其语法是把类型名放在圆括号中,后面紧跟一个用花括号括起来的初始化列表。例如:

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

struct people {
    char name[20];
    int age;
};

int main(void) {
    struct people somebody;
    int n = 0;
    puts("Enter a number for n:");
    scanf("%d", &n);
    if (n > 0) {
        somebody = (struct people){"Jeff", 35}; //使用复合字面量
    } else {
        somebody = (struct people){"Lee", 30};  //使用复合字面量
    }
    printf("n is %d and somebody is %s.\n", n, somebody.name);
    system("pause");
    return 0;
}

运行结果为:

Enter a number for n:
-5
n is -5 and somebody is Lee.

除了以上用法,还可以把复合字面量作为函数的参数。如果函数接受一个结构,可以把复合字面量作为实际参数传递,例如:

add10((struct people){"Jeff", 35});

如果函数接受一个地址,可以传递复合字面量的地址,例如:

add10(&(struct people){"Lee", 30});

伸缩型数组成员- 14.4.10 -

伸缩型数组成员是C99新增的一个特性,在结构中声明一个伸缩型数组成员有如下规则:

  • 伸缩型数组成员必须是结构的最后一个成员。

  • 结构中必须至少有一个成员。

  • 伸缩数组的声明类似于普通数组,只是它的方括号中是空的。

当声明一个具有伸缩型数组成员的结构时,实际上是先声明一个指向该结构类型的指针,然后用malloc()来分配足够的空间,以储存该类型结构的常规内容和伸缩型数组成员所需的额外空间。以下是一个示例:

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

struct box {
    size_t count;
    int number[];
};
void showBox(const struct box * b);

int main(void) {
    struct box * b1, *b2;
    int n = 5;
    int i;
    int tot = 0;
    /****** 为结构和数组分配存储空间******/
    /******结构b1******/
    b1 = malloc(sizeof (struct box) +n * sizeof (int));
    b1->count = n;
    for (i = 0; i < n; i++) {
        b1->number[i] = 100 - i;
        tot += b1->number[i];
    }
    showBox(b1);
    /******设置n及重置tot******/
    n = 8;
    tot = 0;
    /******结构b2******/
    b2 = malloc(sizeof (struct box) +n * sizeof (int));
    b2->count = n;
    for (i = 0; i < n; i++) {
        b2->number[i] = 100 - i * 2;
        tot += b2->number[i];
    }
    showBox(b2);
    /******释放内存******/
    free(b1);
    free(b2);
    system("pause");
    return 0;
}

void showBox(const struct box * b) {
    int i;
    printf("Number : ");
    for (i = 0; i < b->count; i++)
        printf("%d ", b->number[i]);
    printf("\n");
}

运行结果为:

Number : 100 99 98 97 96
Number : 100 98 96 94 92 90 88 86

需要注意的是,带伸缩型数组成员的结构有一些特殊的处理要求:

  1. 不能用结构进行赋值或拷贝。这样做只能拷贝除伸缩型数组成员以外的其他成员。

  2. 不要以按值方式把这种结构传递给结构。按值传递一个参数与赋值类似。要把结构的地址传递给函数。

  3. 不要使用带伸缩型数组成员的结构作为数组成员或另一个结构的成员。

匿名结构- 14.4.11 -

匿名结构是一个没有名称的结构成员,通常用于结构的嵌套定义中,例如通过嵌套定义teacher结构类型:

struct names {
    char first[20];
    char last[20];
};

struct teacher {
    struct names name;
    int age;
};

创建和访问该结构可以使用以下方法:

struct teacher zhao = {{"Ting", "Zhao"} ,30};
puts(zhao.name.first);

如果使用匿名结构,可以修改为:

struct teacher {
    struct {
        char first[20];
        char last[20];
    };
    int age;
};

瞬间简化了不少,而且在访问嵌套结构成员时也简化了步骤,例如只需把first或last看作是teacher的普通成员即可:

struct teacher zhao = {{"Ting", "Zhao"} ,30};
puts(zhao.first);