在ANSI C中,结构体指针变量可以作为参数传递给函数,同样,函数也可以返回一个结构体指针变量。而且,较新的ANSI C允许将结构体变量作为函数的参数和返回值。将其作为函数的参数和返回值与其他变量的规则相似,本小节不再叙述。需要特别注意的是作为参数或返回值的是结构体变量还是结构体指针变量。
1. 结构体变量和结构体指针变量作为函数返回值
各种数据类型都可以作为函数返回值的类型。例如,基本数据类型int,float,数组类型,当然也可以是结构体类型。可作为函数返回值的结构体类型可以是一个结构体变量或结构体指针变量。下面给出这两种方式的函数原型:
struct struct_fuction(void);
struct * struct_fuction(void);
貌似这两个函数原型的区别就是一个多一个指针运算符*,另外一个则没有。从形式上看的确如此,而其实质上的区别就在于一个返回的是指针对象,一个返回的是结构体对象。
例如,有两个变量,一个为结构体变量,一个为结构体指针变量,二者都需要初始化一些必要的信息以便使用,所以此处提供了两个初始化函数,一个初始化结构体变量并返回结构体变量,一个初始化结构体指针变量并返回结构体指针变量。
假如两个变量可以说话,结构体变量会对初始化函数说:“兄弟,帮我把衣服印上一点信息吧。”初始化函数会说,好的。于是初始化函数会拿上喷枪将信息喷上结构体变量。然后结构体指针变量会对另外一个初始化函数说:“也给我的衣服印上点东西吧。”初始化函数会说好的。然后会在别的衣服印上信息后,告诉结构体指针变量说:“嗨,兄弟,你要的东西已经弄好了,你可以自己去某某地方穿上。”
这样,两个变量的实际对象——衣服上都有了一些信息,只是印的方式不同而已,一个是直接印上去,一个则是套上一层印有信息的外壳。如图9.10所示:
图9.10 结构体指针和结构体变量作返回值
下面的结构体初始化程序清单说明函数返回结构体和返回结构体指针的具体区别:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//声明结构体类型
struct comm
{
unsigned char name[10];
unsigned char *address;
};
//定义结构体变量
struct comm person_1;
struct comm *person_2;
//函数声明
static struct comm person_1_init(const void *name, const void *address);
static struct comm * person_2_init(const void *name, const void *address );
static void struct_str_print(const unsigned char *struct_str, size_t length);
static size_t str_len(const void *str);
static void str_cpy(void *dest, const void *src, size_t len);
int main(void)
{
//初始化结构体
person_1 = person_1_init("Bright", "NewYeak");
person_2 = person_2_init("Minos", "Hongkong");
//打印初始化内容
printf("person_1.name : %s/tperson_1.address : %s/n",
person_1.name, person_1.address);
printf("person_2->name : %s/tperson_2->address : %s/n",
person_2->name, person_2->address);
//打印函数返回值的字符串形式
printf("person_1 : <");
struct_str_print((unsigned char *)&person_1, sizeof(struct comm));
printf(">/n");
printf("person_2 : <");
struct_str_print((unsigned char *)person_2, sizeof(struct comm *));
printf(">/n");
//回收堆空间
free(person_1.address);
free(person_2->address);
free(person_2);
// 避免野指针
person_1.address = NULL;
person_2->address = NULL;
person_2 = NULL;
getchar();
return 0;
}
/*
结构体变量初始化函数
用形参来传递初始化数据
返回值为结构体或者结构体指针
*/
static struct comm person_1_init(const void *name, const void *address)
{
assert((name != NULL) && (address != NULL));
assert((str_len(name) + 1 <= 10) && (str_len(address) + 1 <= 100));
person_1.address = (unsigned char *)malloc(sizeof(unsigned char) * 100);
str_cpy(person_1.name, name, str_len(name));
str_cpy(person_1.address, address, str_len(address));
return person_1;
}
static struct comm * person_2_init(const void *name, const void *address )
{
assert((name != NULL) && (address != NULL));
assert((str_len(name) + 1 <= 10) && (str_len(address) + 1 <= 100));
person_2 = (struct comm *)malloc(sizeof(struct comm));
person_2->address = (unsigned char *)malloc(sizeof(unsigned char) * 100);
str_cpy(person_2->name, name, str_len(name));
str_cpy(person_2->address, address, str_len(address));
return person_2;
}
/*
打印固定大小的字符串十六进制
形参为字符串指针,字符串长度
无返回值
*/
static void struct_str_print(const unsigned char *struct_str, size_t length)
{
assert(struct_str != NULL);
size_t str_idx;
for (str_idx = 0; str_idx < length; ++str_idx)
{
printf("%x", *struct_str++);
}
}
//计算字符串长度
static size_t str_len(const void *string)
{
assert(string != NULL);
size_t length = 0;
const unsigned char *str = (const unsigned char *)string;
while(*str++ != '/0')
length++;
return length;
}
/*
字符串拷贝
非完全版本,需注意数据溢出修改问题
*/
static void str_cpy(void *dest, const void *src, size_t len)
{
assert((src != NULL) && (dest != NULL));
size_t length = len + 1;
unsigned char *dest_bp = (unsigned char *)dest;
const unsigned char *src_bp = (const unsigned char *)src;
while(length-- > 0)
*dest_bp++ = *src_bp++;
}
从上面的程序可以看出,当返回值为结构体变量时,初始化函数返回该变量时,是直接将该返回值初始化后对其赋值;而当返回值为结构体指针变量时,初始化函数返回该指针变量时,是间接修改内存中的一个结构变量的值,并将该结构变量的初始地址返回。
说明:选择结构体变量还是结构体指针作为函数返回值时,应该遵循具体应用来考虑使用间接写还是直接写,但是考虑到速度上的优势,大多数情况下使用间接写,即返回结构体指针的函数。
2. 结构体变量和结构体指针变量作为函数形式参数
同样的,结构体变量和结构体指针变量都可以作为函数的形式参数,先来看两者的函数原型:
struct struct_fuction(struct struct_pm,......);
struct * struct_fuction(struct *pstruct_pm,......);
上面两个函数原型中形式参数分别使用了结构体变量和结构体指针变量,其区别和返回值类似,一个传递的是普通变量,一个传递的是一个地址。
用两个变量的对话例子说明两者的区别:
假设有一个带有结构体变量形式参数的函数A_INIT和一个带有结构体指针变量形式参数的函数B_INIT,并且还有结构体变量A和结构体指针变量B。假设有一天两个变量A和B想去做一个发型,变量A去了理发店,而变量B则给理发店下了一个订单并且留下了地址。变量A到达了理发店的时候得到了函数发型师A_INIT的接待,变量B则在家里等待函数发型师B_INIT的上门服务。
函数发型师A_INIT首先用模型头复制了A原来的发型,然后根据A需要的发型参数来对A的复制模型头进行发型制作,做好了以后将A原来的发型用复制模型头的发型覆盖掉,至于是怎么覆盖的,留给读者一点遐想空间吧;此时,函数发型师B_INIT根据变量B所留的地址的一份拷贝已经到达变量B的家里,他马上便根据变量B要求的发型参数直接在B的头上进行发型的制作,然后变量A和变量B 都有了新的发型。
上述例子只是用比较形象的描述函数对待结构体变量形参函数和结构体指针变量形参函数的区别,其具体的区别如图9.11所示:
图9.11 结构体指针和结构体变量作形参
下面的程序与前面的结构体初始化程序功能一致,只不过使用了结构体变量和结构体指针作为函数形式参数,下面给出程序清单:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//声明结构体类型
struct comm
{
unsigned char name[10];
unsigned char *address;
};
//函数声明
static struct comm person_1_init(struct comm person,
const void *name, const void *address);
static struct comm * person_2_init(struct comm *person,
const void *name, const void *address );
static void struct_str_print(const unsigned char *struct_str, size_t length);
static size_t str_len(const void *str);
static void str_cpy(void *dest, const void *src, size_t len);
int main(void)
{
//定义结构体变量
struct comm person_1;
struct comm *person_2;
//初始化结构体
person_1 = person_1_init(person_1, "Bright", "NewYeak");
person_2 = person_2_init(person_2, "Minos", "Hongkong");
//打印初始化内容
printf("person_1.name : %s/tperson_1.address : %s/n",
person_1.name, person_1.address);
printf("person_2->name : %s/tperson_2->address : %s/n",
person_2->name, person_2->address);
//打印函数返回值的字符串形式
printf("person_1 : <");
struct_str_print((unsigned char *)&person_1, sizeof(struct comm));
printf(">/n");
printf("person_2 : <");
struct_str_print((unsigned char *)person_2, sizeof(struct comm *));
printf(">/n");
//回收堆空间
free(person_1.address);
free(person_2->address);
free(person_2);
//避免野指针
person_1.address = NULL;
person_2->address = NULL;
person_2 = NULL;
getchar();
return 0;
}
/*
结构体变量初始化函数,
用形参来传递初始化数据
返回值为结构体或者结构体指针
*/
static struct comm person_1_init(struct comm person,
const void *name, const void *address)
{
assert((name != NULL) && (address != NULL));
assert((str_len(name) + 1 <= 10) && (str_len(address) + 1 <= 100));
person.address = (unsigned char *)malloc(sizeof(unsigned char) * 100);
str_cpy(person.name, name, str_len(name));
str_cpy(person.address, address, str_len(address));
return person;
}
static struct comm * person_2_init(struct comm *person,
const void *name, const void *address )
{
assert((name != NULL) && (address != NULL));
assert((str_len(name) + 1 <= 10) && (str_len(address) + 1 <= 100));
person = (struct comm *)malloc(sizeof(struct comm));
person->address = (unsigned char *)malloc(sizeof(unsigned char) * 100);
str_cpy(person->name, name, str_len(name));
str_cpy(person->address, address, str_len(address));
return person;
}
/*
打印固定大小的字符串十六进制
形参为字符串指针,字符串长度
无返回值
*/
static void struct_str_print(const unsigned char *struct_str, size_t length)
{
assert(struct_str != NULL);
size_t str_idx;
for (str_idx = 0; str_idx < length; ++str_idx)
{
printf("%x", *struct_str++);
}
}
//计算字符串长度
static size_t str_len(const void *string)
{
assert(string != NULL);
size_t length = 0;
const unsigned char *str = (const unsigned char *)string;
while(*str++ != '/0')
length++;
return length;
}
/*
字符串拷贝
非完全版本,需注意数据溢出修改问题
*/
static void str_cpy(void *dest, const void *src, size_t len)
{
assert((src != NULL) && (dest != NULL));
size_t length = len + 1;
unsigned char *dest_bp = (unsigned char *)dest;
const unsigned char *src_bp = (const unsigned char *)src;
while(length-- > 0)
*dest_bp++ = *src_bp++;
}
从上面的程序可以看出,当结构体变量做形参时,在调用该函数时首先拷贝整个结构体形成结构体副本,然后对副本进行修改,但并没有修改原来结构体,再将副本作为返回值返回;当结构体指针作为形参时,在调用该函数时,首先拷贝的是该结构体的地址,形成一个地址副本,然后使用这个地址副本对其指向的结构体变量进行修改,其实修改的还是原来地址所指向的结构体变量,然后用地址副本作为返回地址。
最新作品《C语言参悟之旅》全新上市,敬请关注!
官方网站:http://www.tqbooks.net/product/gb/product_detail.asp?catalogid=10&productid=1474
China-pub有售:http://www.china-pub.com/49980