C/C++ 语法教程(12)

C/C++ 语法教程(12)

结构体和联合体

结构体在 C/C++ 中使用极其频繁,在 C 中主要用于自定义类型,而在 C++ 中更是扩展了面向对象方面的应用,可以说在各种方面与 C++ 的类 class 基本一样。

而联合体相对就没有特别的用处,其使用往往可以被结构体所替代,更多起到节省空间和变成复杂度的作用。

联合体

当多个数据需要共享内存或者多个数据每次只取其一时,可以利用联合体(union),也称为共用体。

特征(仅做了解)

  1. 联合体是一个结构。
  2. 它的所有成员相对于基地址的偏移量都为0。
  3. 此结构空间要大到足够容纳最“宽”的成员。
  4. 大小能被其包含的所有基本数据类型的大小所整除。

使用方法(演示代码)

union U1
{
    int n;
    char s[11];
    double d;
};
union U2
{
    int n;
    char s[5];
    double d;
};



U1 u1;
U2 u2;
cout<<sizeof(u1)<<'\t'<<sizeof(u2)<<endl;
cout<<"u1各数据地址:\n"<<&u1<<'\t'<<&u1.d<<'\t'<<&u1.s<<'\t'<<&u1.n<<endl;
cout<<"u1各数据地址:\n"<<&u2<<'\t'<<&u2.d<<'\t'<<&u2.s<<'\t'<<&u2.n<<endl;

输出结果

img

结果分析

对于 $U1$ 联合体,$s$ 占 $11$ 字节,$n$ 占 $4$ 字节,$d$ 占 $8$ 字节,因此其至少需 $11$ 字节的空间。然而其实际大小并不是 $11$,用运算符 sizeof 测试其大小为 $16$。

这是因为这里存在字节对齐的问题,$11$ 既不能被 $4$ 整除,也不能被 $8$ 整除。因此补充字节到 $16$,这样就符合所有成员的自身对齐了。

从这里可以看出联合体所占的空间不仅取决于最宽成员,还跟所有成员有关系,即其大小必须满足两个条件:

  1. 大小足够容纳最宽的成员;
  2. 大小能被其包含的所有基本数据类型的大小所整除。

对于 $U2$ 联合体,同理知道,用运算符 sizeof 测试其大小为 $8$。

除此之外,还可以发现,联合体中的各数据的存储地址都是相同的,这也就是说,同一个联合体中的各个数据共用一块内存单元,也就是一个联合体实际上只能拥有一个具体的变量

同样地,改变了其中元素的值,其他元素的存储值也跟着同时改变,所以联合体一般用于存储可能的有多种变量含义的值。

当然,实际上,这个联合体的使用极为不频繁,所以主要就是了解一下。

结构体

这才是本讲的重点。

当然这里不可能把面向对象的内容都讲了,所以主要讲 C 语言下的 struct

应用需求

有时候,我们可能有很多数据相互关联。

比如一个人的身高体重、视力等等各种数值。

按照之前的方法,我们可能会定义若干个不同类型的数组来存储这些数值。

然而,对于同一个人,这些数值理应被同时移动和使用,分开到多个数组中显得有些不合理。

所以有时候,我们希望有一些自定义的数据类型,他可能有若干个整型变量用以存储各种数值,还有字符串变量存储名字等等。

这个时候就体现了结构体 struct 的作用。

定义方法

一般来说,定义一种新的结构体类型使用如下形式:

struct type_name
{
    member_type1 member_name1;
    member_type2 member_name2;
    member_type3 member_name3;
    .
    .
    .
}object_names;

其中,type_name 是新结构体类型的名称,member_type1 member_name1 就是标准的变量定义,也就是命名要求相同,且在这个结构体中,变量名不能重复。

最后的 object_names 是若干个结构体变量。

举个例子:

struct Books
{
    char title[50];
    char author[50];
    char subject[100];
    int book_id;
}book[100],last_book;

而在正常的程序中,如果已经定义过了某个结构体类型,那么想要新建一个对应的结构体变量,只需:

Books book1,book2;

如此即可。

当然,在 C 语言中,需要写为:

struct Books book1,book2;

当然如果嫌弃这种每次都要写 struct 的定义方法,也可以这样写:

typedef struct
{
    int a;
    char b;
    double c; 
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;

当然因为我们主讲 C++,所以一般只要会使用最简单的也就是最开始的方法即可。

访问结构成员

现在我们有了结构体变量,我们还需要知道如何访问某个结构体变量中的成员变量。

比如我想修改上述 book1 变量中的 book_id,这时候就需要使用 . 运算符。

我们直接看使用例子:

#include <iostream>
#include <cstring>

using namespace std;

// 声明一个结构体类型 Books 
struct Books
{
    char title[50];
    char author[50];
    char subject[100];
    int book_id;
};

int main()
{
    Books Book1,Book2;
    strcpy(Book1.title,"C/C++ 语法教程");
    strcpy(Book1.title,"wzf2000");
    strcpy(Book1.subject,"语法");
    Book1.id=2333;

    strcpy(Book2.title,"数据结构与算法教程");
    strcpy(Book2.title,"wzf2000");
    strcpy(Book2.subject,"数算");
    Book1.id=3444;

    // 输出 Book1 信息
    cout << "第一本书标题 : " << Book1.title <<endl;
    cout << "第一本书作者 : " << Book1.author <<endl;
    cout << "第一本书类目 : " << Book1.subject <<endl;
    cout << "第一本书 ID : " << Book1.book_id <<endl;

    // 输出 Book2 信息
    cout << "第二本书标题 : " << Book2.title <<endl;
    cout << "第二本书作者 : " << Book2.author <<endl;
    cout << "第二本书类目 : " << Book2.subject <<endl;
    cout << "第二本书 ID : " << Book2.book_id <<endl;

    return 0;
}

输出结果会是:

第一本书标题 : C/C++ 语法教程
第一本书作者 : wzf2000
第一本书类目 : 语法
第一本书 ID : 2333
第二本书标题 : 数据结构与算法教程
第二本书作者 : wzf2000
第二本书类目 : 数算
第二本书 ID : 3444

我相信这个例子应该还是比较容易接受的,其中的 strcpy 函数可以参见前面的库函数介绍和 char 指针那块。

结构体变量的初始化

理论上,结构体变量可以通过类似于数组的方法初始化,比如:

Books Book1={"C/C++ 语法教程","wzf2000","语法",2333};

其大括号中的顺序需要对应结构体类型定义成员变量时的顺序(不然程序怎么知道谁是谁)。

但因为之后会细讲构造函数等用法,所以这里不是特别建议如此使用。(class 类就不能如此初始化)

typedef 关键字

上面的结构体变量定义中,还需要先写一个 struct 申明是结构体类型,显得有些麻烦。

所以 C++ 中可以通过 typedef 关键字,来给变量类型一个别称。

如:

typedef struct Books
{
    char title[50];
    char author[50];
    char subject[100];
    int book_id;
};

在如此定义之后,之后所有用到 Books 结构体类型定义的变量,都可以省略 struct 的前缀。

特别强调一点,从 C99 标准开始,typedef 关键字可以省略或者强制省略。

也就是说,编译器会默认你写过 typedef,在之后的程序中,就不需要使用 struct 的前缀。

因为现在比较常见的标准就是 C99 和 C++11,所以在写 C++ 时,还是先不要使用 typedef 并且定义变量时也先不要加上 struct 前缀,除非确实出现了问题。

除此之外,typedef 关键字还可以给其他非结构体变量类型别称,比如:

// 声明
typedef long long ll;
typedef int * pint32;
typedef pair<int,int> pii;

//定义变量
ll a;
pint32 b;
pii c;

这里的 pair 类型可以不用特别在意,大概在之后的 C++ 的 STL 部分会讲解。

结构体作为函数参数

既然结构体类型相当于一个新的变量类型,那么理应可以作为函数的参数。

一般来说会这样使用:

void printBook( struct Books book );
void printBook( struct Books book )
{
   cout << "书标题 : " << book.title <<endl;
   cout << "书作者 : " << book.author <<endl;
   cout << "书类目 : " << book.subject <<endl;
   cout << "书 ID : " << book.book_id <<endl;
}

当然对于 C++,一般还是省略 struct

同样,结构体类型也能作为函数的返回值类型,比如:

struct Book1 First()
{
    //do something...
}

当然这里的 struct 也同样在 C++ 中优先省略。

结构体指针

作为一个新的变量类型,结构体类型理应同样拥有指针类型。

如下例:

struct Books *struct_pointer;

struct_pointer=&Book1;

(C++ 中同样省略)

这里的使用方法跟其他指针类型无异。

但是特别地,如果想通过指针类型调用其地址上结构体类型的成员变量,就可以使用 -> 运算符。

比如:

#include <iostream>
#include <cstring>

using namespace std;
void printBook( struct Books *book );

struct Books
{
    char title[50];
    char author[50];
    char subject[100];
    int book_id;
};

int main( )
{
    Books Book1,Book2;
    strcpy(Book1.title,"C/C++ 语法教程");
    strcpy(Book1.title,"wzf2000");
    strcpy(Book1.subject,"语法");
    Book1.id=2333;

    strcpy(Book2.title,"数据结构与算法教程");
    strcpy(Book2.title,"wzf2000");
    strcpy(Book2.subject,"数算");
    Book1.id=3444;

    // 通过传 Book1 的地址来输出 Book1 信息
    printBook( &Book1 );

    // 通过传 Book2 的地址来输出 Book2 信息
    printBook( &Book2 );

    return 0;
}
// 该函数以结构指针作为参数
void printBook( struct Books *book )
{
    cout << "书标题  : " << book->title <<endl;
    cout << "书作者 : " << book->author <<endl;
    cout << "书类目 : " << book->subject <<endl;
    cout << "书 ID : " << book->book_id <<endl;
}

当然,使用这个运算符的前提是结构体指针变量已经指向了确定的结构体变量,不然,对于 NULL 指针,我们也得不到它的成员变量的值。

总结

这一节,我们系统地学习了联合体和结构体两种自定义变量类型的使用方法。

结构体变量在之后的应用和算法学习中,将会被频繁使用,所以一定要确保掌握。

习题

  1. 使用结构体变量实现录入 $n$ 个人的姓名,身高,体重,专业,家乡,爱好以及是否单身这些数据,分别使用 char 数组、doubledoublechar 数组、char 数组、char 数组、bool 这些类型存储。

    我们希望将这些人分别按照身高、体重排序,然后按顺序输出每个人的各项数据。

    输入输出格式可以自行决定。

  2. 尝试定义一个由行数 $m$,列数 $n$,元素值 $a[i][j]$ 组成的 matrix 结构体类型,并尝试写一个 matrix mul(matrix A,matrix B) 用于实现矩阵 $A$ 和矩阵 $B$ 的乘法 $C = AB$,并返回一个新的矩阵,当然我们肯定保证 A.n==B.m

    同样,输入输出格式自行决定。

 

点赞 0

No Comments

Add your comment