欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C-结构

程序员文章站 2022-05-16 10:04:12
c提供了两种类型的聚合数据类型,数组和结构.数组是相同元素的集合,它的每个元素是通过下标引用或指针间接访问来选择的.结构也是一些值的集合,这些值称为它的成员但是一个结构类型的各个成员可能具有不同的类...

c提供了两种类型的聚合数据类型,数组和结构.数组是相同元素的集合,它的每个元素是通过下标引用或指针间接访问来选择的.结构也是一些值的集合,这些值称为它的成员但是一个结构类型的各个成员可能具有不同的类型;结构变量属于标量类型,所以我们可以像对待其他标量类型一样执行相同类型的操作.

一、声明结构类型:
声明结构的形式有多种方式,比如:
1、单纯地声明一个结构:

struct date
{
    int month;
    int day;
    int year;
};
定义变量的方式为:struct date d1,d2; d1 和 d2 都是date ,里面有month, day 和 year ;
在date的前面一定要加上struct关键字,不能单纯地拿date来做类型的名字,要在date前面加上struct, 然后后面加上变量的名字.

2、声明无名结构:

struct
{
    int month;
    int day;
    int year;
}d1,d2;

struct
{
    int month;
    int day;
    int year;
}*c;

这种方式没有了名字,但是在大括号的后面跟了两个变量d1 和 d2,d1 和 d2都是一种无名结构体,里面依然有month , day 和 year,这种方式应用的场合是在程序员不打算在将来的某个场合再次使用这个结构体,而是在当前这个特定的场合只使用这么一次,以后就不再使用,所以这种用法不太常见.

注:这两个声明被编译器当作两种截然不同的类型,即使它们的成员列表完全相同.因此,变量d1 和 c的类型是不同的,所以一下用法是非法的
    c = &d1;

3、声明一个结构并且定义变量:

struct date
{
    int month;
    int day;
    int year;
}d1, d2;

对于第一和第三种形式,都声明了结构体date,但是第二种形式没有声明date,只是定义了两个变量d1 和 d2.

声明结构时可以使用的另外一种良好的技巧是用typedef创建一个新的类型,在下文会有所提及.

二、结构的初始化

放在函数内部的变量我们叫做本地变量,本地变量是没有默认的初始值的,如果没有给定初始值的话,那么本地变量的值将无法预测,结构体也不例外,所以我们需要给一个结构变量赋初始值.

在对数组赋初值的时候,我们可以用一对大括号“{ }”,来对数组赋初值,同样我们也可以用一对大括号“ { }”来对结构进行赋初值.

(以上面声明的结构体为例)

1、对整个结构赋初值:struct date today = {08, 21, 2016};
2、对结构的部分成员赋初值:struct date today = {.month = 8,  .year = 2016 };

和数组的赋值是一样的,如果只是对结构体的部分成员进行赋初值,那么,那些没有被赋初值的成员的值将会被初始化为0;

三、结构成员

结构和数组有点像,数组里头有很多的单元,结构里头有很多的成员,不一样的是数组的单元必须是相同的数据类型,而结构的成员可以是不同类型
数组用“ [ ] ” 运算符和下标访问其成员 a[ 0 ]  = 1; 
结构用“ . ” 运算符和名字来访问其成员

today.day  、 today.year
p1.x 、p1.y

其中today 和 p1都是结构变量

四、 结构运算

1、要访问整个结构,可以直接用结构变量的名字
2、 对于整个结构,可以做赋值、取地址, 也可以传递给函数参数

(1)、today = (struct date){ 8, 21, 2016}; //相当于today.month = 8; today.day = 21; today.year = 2016;
(2)、today1 = today2; //相当于today1.month = today2.month; today1.day = today2.day;  today1.year = today2.year;

五、结构指针
和数组不同,结构变量的名字并不是结构变量的地址,必须使用&运算符,如:

struct date *pdate = &today;

六、结构作为函数参数

int numberofdays(struct date d)

1、整个结构可以作为参数的值传入函数
2、这时候是在函数内新建一个结构变量,并复制调用者的结构的值
3、也可以返回一个结构

七、输入一个结构

传入结构和传入数组是不一样的,把一个结构传入函数,然后在函数中操作,但是操作完的值却没有返回回去,问题就在于传入函数的是外面那个结构的克隆体,而不是指针

没有直接的方式可以一次scanf一个结构,所以有以下两种方式:

1、在这个输入函数中,完全可以创建一个临时的结构变量,然后把这个结构返回给调用者.

struct date getstruct(void)
{
    struct date today;
    scanf("%d", &today.month);
    scanf("%d", &today.day);
    scanf("%d", &today.year);
    return today;
}
void output(struct date p)
{
    printf("%d, %d\n", p.month, p.day, p.year);
}
int main(int argc, char const argv[ ])
{
    struct date y = {0, 0, 0};
    y = getstruct();
    output(y);
    return 0;
}

2、结构指针作为参数
结构指针的操作方式:

struct date today;
struct date *p = &today;
(*p).month = 12;
p->month = 12;//推荐

“->”表示指针所指的结构变量中的成员
:”->”的左边跟的一定是一个指针

struct date* getstruct(structdate *p)
{
    scanf("%d", &p->month);
    scanf("%d", &p->day);
    scanf("%d", &p->year);
    printf("%d, %d, %d\n", p->month, p->day , p->year);
    return p;
}
void pintf(const struct date *p)
{
    printf("%d, %d, %d\n", p->month, p->day, p->year);
}

把结构体指针作为返回值返回的好处是能够将该返回的指针作为函数参数传入另外一个函数中,可以进行巧妙的操作,操作如下:

int main(int argc, char const argv[ ])
{
    struct point y = {0, 0, 0};
    print(getstruct(&y));
    return 0;
}

当然,我们也可以把返回值给去掉

void  getstruct(structdate *p)
{
    scanf("%d", &p->month);
    scanf("%d", &p->day);
    scanf("%d", &p->year);
    printf("%d, %d, %d\n", p->month, p->day , p->year);
}
int main(int argc, char const argv[ ])
{
    struct date y = {0, 0, 0};
    output(*getstruct(&y)); //等价于getstruct(&y); output(y); 
    return 0;
}  

八、结构数组

struct date days[5];  //长度为5的结构体数组
struct date days[ ] = { {4, 5, 2005}, { 2, 4, 2005} };
int main(void)
{
    struct date testtimes[5] = { {11, 59,2011}, {12, 0 , 2012}, { 1, 29,2013}, {23, 59, 2014}, {19 , 12,2015}};
    int i;
    for(i = 0; i< 5; ++i)
    {
        printf("time is %.2i : %.2i : %.2i\n",
        testtimes[i].hour, testtimes[i].minuters, testtimes[i].seconds);//结构体数组的引用方式 
    }
    return 0;
} 

九、结构中的结构(嵌套的结构)

struct point 
{
    int x;
    int y;
};

struct rectangle
{
    struct point pt1;
    struct point pt2;
};

如果有变量 struct rectangle r;
就可以有:

r.pt1.x、 r.pt1.y,
r.pt2.x、r.pt2.y

如果有变量定义:

struct rectangle r, *rp;
rp = &r;

下面的四种形式是等价的:

r.pt1.x  <=>  rp->pt1.x  <=>  (r.pt1).x  <=>  (rp->pt1).x
但是没有rp->pt1->x (因为pt1不是指针)

十、结构中的结构的数组

struct point
{
    int x;
    int y;
};

struct rectangle 
{
    struct point p1;
    struct point p2;
};

void printrect(struct rectangle r)
{
    printf("<%d, %d> to <%d, %d>\n", r.p1.x, r.p1.y , r.p2.x, r.p2.y);
}

int main(int argc, char const *argv[ ])
{
    int i;
    struct rectangle rects[ ] = {
    {{1, 2}, { 3, 4}},  //这两行是属于两个结构,两个结构中又有两个结构
    {{5, 6}, { 7, 8}}
    };
    for(i=0; i<2; i++)
        printrect(rects[i]);

    return 0;
}   

十一、typedef与结构

typedef用来声明新的类型名字,新的名字是某种类型的别名
用typedef来声明新的名字常常会忘记原来的类型和新的类型名字顺序应该怎样写:
typedef long int64_t; 
中间的那个是旧的那个类型名,最后面的那个单词才是新的类型名字.
在声明结构的时候可以用typedef对该结构类型起一个新的别名,这样以后再定义结构体变量的时候就不用再带上struct关键字:

typedef struct date
{
    int month;
    int day;
    int year;
}date;

可以直接用date来定义变量:
date d = { 9, 1, 2005};

typedef struct
{
    int month;
    int day;
    int year;
}date;

如果没有这个typedef,那么我们现在在声明一个无名结构,date将会是它的一个变量名,但是现在有了这个typedef关键字之后
date就不是这个结构的变量,而是变成了这个结构的一个新的别名,至于原来的struct是什么名字,我们已经不关心了,因为我们有
一种更好的方式去表达它了.

十二、结构的自引用

在一个结构内部包含一个类型为该结构本身的成员是否合法:

1、
    struct date
    {
        int a;
        struct date b;
        int c;
    };

这种自引用时非法的,因为成员b是另一个完整的结构,其内容还将包含它自己的成员b. 这第二个成员又是另一个完整的结构,它还将包含自己的成员b,这样重复永无止境.

2、
    struct date
    {
        int a;
        struct date *b;
        int c;
    };

这个声明是合法的,区别就在于b现在是个指针而不是结构.编译器在结构的长度确定之前就已经知道指针的长度,所以这种类型的自引用时合法的.

3、
    typedef struct
    {
        int a;
        struct date*b;
        int c;
    }date;

这个声明的目的是为这个结构起一个新的别名date,但是,这是错误的,因为类型名直到声明的末尾才定义,所以在结构声明的内部它尚未定义.
解决的方法如下:

typedef struct date
{
    int a;
    struct date*b;
    int c;
}date;

:和其他的数据类型一样,如果结构在函数的内部被声明的话,那么该结构体就只能在这个函数中使用,不能被外部其他的函数使用,所以,声明结构体通常在函数的外部声明,这样就能够被多个函数使用。