IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C 语言 —— 结构体 -> 正文阅读

[C++知识库]C 语言 —— 结构体

结构简介

什么是结构为什么要用结构
??编写代码时,最重要的步骤之一是选择表示数据的方法。在许多情况下,基本的变量类型和数组还步够,为此 C 提供了结构变量来表示复杂的数据。例如,对于一本书,可能需要书名、作者、价格等属性。

使用结构需要掌握的三个技巧:

  • 为结构建立一个格式或样式;
  • 声明一个合适该样式的变量;
  • 访问结构变量的各个部分。

建立结构声明

结构声明描述了一个结构的组织布局。

struct book {
	char title[100];
	char author[80];
	float value;
};

如上所示的声明描述了一个由两个字符数组和一个 float 类型变量组成的结构。注意,该声明仅仅只是描述了该结构由什么组成,并未创建实际的数据对象
??接下来分析一下结构声明的细节。
??struct 是声明结构的关键字,该关键字表明跟在其后面的是一个结构。
??book 是一个可选的标记,该标记表示结构的名称(该例中是 book)。关于标记,会在定义结构变量处讲解。
??{ }; 花括号括起来的是结构成员列表,每个成员都是可以是任意一种 C 的数据类型,甚至可以是其他结构。注意,**右括号后面一定要有 ****;** !!

结构声明可以放在所有函数的外部,也可以放在一个函数定义的内部。如果把结构声明置于一个函数的内部,它的标记(如本例中的 book),只能限于该函数的内部使用。如果把结构声明置于函数的外部,那么该声明之后的所有函数都可以使用它的标记(如本例中的 book)。

结构变量的定义和初始化

定义结构变量

结构有两层含义,一层是上面讲过的结构布局,另一层则是这里要讲的定义结构变量。以上面声明的结构 book 为例,定义一个变量 library 的代码如下所示:

struct book library;

这行代码表明,用 book 结构来为 library 变量分配空间,包括了一个还有 100 个元素的 char 数组,一个包含 80 个元素的 char 数组,和一个 float 类型的变量。

??在结构变量 library 的声明中,struct book 的作用相当于 int 或者 float 。和基本类型变量的声明一样,可以定义多个结构变量,甚至是结构指针。

struct book doyle, panshin, *ptbook;

从本质上看,book 结构声明创建了一个名为 struct book 的新类型。就计算机而言,struct book library; 是以下声明的简化:

struct book {
	char title[100];
	char author[80];
	float value;
} library;

所以,建立结构声明的过程和定义结构变量的过程可以组合成一个步骤。组合后的结构声明和结构变量的定义可以不使用结构标记。

struct {
	char title[100];
	char author[80];
	float value;
} library;

关于可选的结构标记是否可以省略的问题。 如果是只需要定义有限个变量,可以将结构声明和变量定义合并到一起,这样结构模板只会被使用一次,此时可以省略结构标记。然而,如果打算多次使用结构模板,就必须使用带标记的形式,或者使用 typedef (这里不做介绍)。

初始化结构变量

和定义基本数据类型变量一样,声明结构变量的同时可以对其进行初始化。
初始化结构变量使用花括号中括起来的初始化列表进行初始化,各初始化项用逗号分隔,也可以用结构体变量来初始化。
PS:使用结构体变量来初始化的变量不能作为全局变量。

struct book library = {
	"The Pious Pirate and the Devious Damsel",
	"Renee Vivotte",
	1.95
};
struct book l1 = library; // 不能作为全局变量。

结构成员的使用

对于结构变量来说,使用结构成员运算符 —— 点(.)访问结构中的成员。例如使用 library.value 访问 library 中的 value 部分。
??对于指向结构变量的指针来说,使用 -> 运算符访问结构中的成员,或者(*指针变量).成员

struct book *ptbook = &library;
ptbook->value;
(*ptbook).value;

结构体的赋值

同一个结构体的变量之间可以直接赋值。

struct book b1 = {
    "C Primer",
    "Stephen Prata",
    68.3
};
struct book b2 = b1;

但如果结构体的成员涉及到动态内存分配的话会有一些问题。

数组成员的赋值

其实在对结构体变量赋值时,会发现有一个很奇妙的事情:无法将一个数组直接赋值给另一个数组,但是如果结构体的成员中包含数组,在将一个结构体变量赋值给另一个结构体变量的时候,则会完成数组的赋值。

浅拷贝

// 浅拷贝演示
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct book {
    char *title;
    char *author;
    float value;
};

void show_book(const struct book* ptr_book) {
    printf("This book\'s name is %s, the author is %s, the value is %lf.\n",
           ptr_book->title, ptr_book->author, ptr_book->value);
    printf("The struct value\'s address is %p.\n", ptr_book);
    printf("The title\'s address is %p.\n", ptr_book->title);
    printf("The author\'s address is %p.\n", ptr_book->author);
    printf("-------------------------------------------------------------\n");
}

void free_book(struct book* ptr_b) {
    if (ptr_b == NULL)
        return;
    if(ptr_b->title != NULL) {
        free(ptr_b->title);
        ptr_b->title = NULL;
    }
    if (ptr_b->author != NULL) {
        free(ptr_b->author);
        ptr_b->title = NULL;
    }
}
int main(int argc, char** args) {
    // 初始化 b1 变量
    struct book b1 = {.value = 68.3};
    b1.title = (char*) malloc(9 * sizeof (char));
    strcpy(b1.title, "C Primer");
    b1.author = (char*) malloc(13 * sizeof (char));
    strcpy(b1.author, "Stephen Prata");
    // 展示 b1 的成员
    show_book(&b1);
    // 声明 b2, 并将 b1 赋值给 b2
    struct book b2 = b1;
    // 以下代码和直接赋值作用一样
//    memcpy(&b2, &b1, sizeof (struct book));
    // 展示 b2 的成员
    show_book(&b2);

    // 修改 b2 的 author 成员指向的内容
    strcpy(b2.author, "You Ka");
    // 展示 b2 的成员
    show_book(&b2);
    // 展示 b1 的成员
    show_book((&b1));

    // 释放 b1 动态分配的内存
    free_book(&b1);
    // 释放 b2 动态分配的内存 —— 如果取消注释,会造成程序崩溃
//    free_book(&b2); 
    return 0;
}
// 输出结果:
This book's name is C Primer, the author is Stephen Prata, the value is 68.3.
The struct value's address is 000000000061FE00.
The title's address is 0000000000A11420.
The author's address is 0000000000A11440.
-------------------------------------------------------------
This book's name is C Primer, the author is Stephen Prata, the value is 68.3.
The struct value's address is 000000000061FDE0.
The title's address is 0000000000A11420.
The author's address is 0000000000A11440.
-------------------------------------------------------------
This book's name is C Primer, the author is You Ka, the value is 68.3.
The struct value's address is 000000000061FDE0.
The title's address is 0000000000A11420.
The author's address is 0000000000A11440.
-------------------------------------------------------------
This book's name is C Primer, the author is You Ka, the value is 68.3.
The struct value's address is 000000000061FE00.
The title's address is 0000000000A11420.
The author's address is 0000000000A11440.
-------------------------------------------------------------

可以看到,b2 = b1 其实是将 b1 成员的值复制给 b2,b2 的 title 成员和 author 成员的值和 b1 一模一样,即只复制指针本身,而不复制指针指向的模板。因此结构体赋值,采用的类似于 memcpy() 这种形式。
?

在结构体成员中有指针,并且会用到动态内存分配的情况下,就会产生一些问题。例如,在本例的结构体中就会存在一下问题:

  1. 同一地址多次使用 free 释放,导致程序崩溃。结构体变量 b1 和 b2 的 title 和 author 指针都指向一个 malloc 分配的地址,malloc 分配的地址是需要 free 释放的。如果使用 free 释放 b1 的 title 和 author ,此时 b2 的 title 和 author 就会成为野指针,后续使用 b2 时就会出现一些不可预见的问题。例如,free b1 之后,再用 free 释放 b2 就会造成程序崩溃。
  2. 修改某一个变量指针成员指向的内容,会导致其他变量指向的内容一起改变。结构体变量 b1 和 b2 的 title 和 author 指针都指向同一个地址,修改任意一个变量指向的内容,另一个变量的内容也会被修改。
  3. malloc 申请的内存没有被 free 释放,造成内存泄漏。如果在将 b1 赋值给 b2 之前,b2 的 title 和 author 指针已经指向一个 malloc 申请的内存,并且没有其他变量的成员指针指向该内存。如果直接将 b1 赋值给 b2 的话,会造成之前 b2 指向的内存没有被回收。

深拷贝

解决办法:不直接使用赋值运算符对结构体进行赋值,编写代码使两个结构体中指针地址不同,但是指向的内容一致。

// 深拷贝演示
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct book {
    char *title;
    char *author;
    float value;
};

void show_book(const struct book* ptr_book) {
    printf("This book\'s name is %s, the author is %s, the value is %lf.\n",
           ptr_book->title, ptr_book->author, ptr_book->value);
    printf("The struct value\'s address is %p.\n", ptr_book);
    printf("The title\'s address is %p.\n", ptr_book->title);
    printf("The author\'s address is %p.\n", ptr_book->author);
    printf("-------------------------------------------------------------\n");
}

void free_book(struct book* ptr_b) {
    if (ptr_b == NULL)
        return;
    if(ptr_b->title != NULL) {
        free(ptr_b->title);
        ptr_b->title = NULL;
    }
    if (ptr_b->author != NULL) {
        free(ptr_b->author);
        ptr_b->title = NULL;
    }
    printf("Free Memory Success!\n");
}

// 执行深拷贝的函数
void copy_book(struct book * dest, const struct book * src) {
    // 如果dest的指针成员正指向一个动态分配的内存,首先将其释放掉,防止内存泄漏
    if(dest != NULL)
        free_book(dest);

    // 动态分配能够存储src对应成员的内存
    dest->title = malloc(strlen(src->title) + 1);
    dest->author = malloc(strlen(src->author) + 1);

    // 赋值
    strcpy(dest->title, src->title);
    strcpy(dest->author, src->author);
    dest->value = src->value;
}

int main(int argc, char** args) {
    // 初始化 b1 变量
    struct book b1 = {.value = 68.3};
    b1.title = (char*) malloc(9 * sizeof (char));
    strcpy(b1.title, "C Primer");
    b1.author = (char*) malloc(13 * sizeof (char));
    strcpy(b1.author, "Stephen Prata");
    // 展示 b1 的成员
    show_book(&b1);
    
    // ------------------------------------------------
    // 声明 b2, 并将 b1 赋值给 b2
    struct book b2;
    // 调用深拷贝的函数
    copy_book(&b2, &b1);
    // ------------------------------------------------
    
    // 展示 b2 的成员
    show_book(&b2);

    // 修改 b2 的 author 成员指向的内容
    strcpy(b2.author, "You Ka");
    // 展示 b2 的成员
    show_book(&b2);
    // 展示 b1 的成员
    show_book((&b1));

    // 释放 b1 动态分配的内存
    free_book(&b1);
    // 释放 b2 动态分配的内存,此时不会崩溃
    free_book(&b2);
    return 0;
}
D:\MyNote\C++\source\cmake-build-debug\C_Struct2.exe
This book's name is C Primer, the author is Stephen Prata, the value is 68.3.
The struct value's address is 000000000061FE00.
The title's address is 0000000000BA1420.
The author's address is 0000000000BA1440.
-------------------------------------------------------------
Free Memory Success!
This book's name is C Primer, the author is Stephen Prata, the value is 68.3.
The struct value's address is 000000000061FDE0.
The title's address is 0000000000BA1460.
The author's address is 0000000000BA1480.
-------------------------------------------------------------
This book's name is C Primer, the author is You Ka, the value is 68.3.
The struct value's address is 000000000061FDE0.
The title's address is 0000000000BA1460.
The author's address is 0000000000BA1480.
-------------------------------------------------------------
This book's name is C Primer, the author is Stephen Prata, the value is 68.3.
The struct value's address is 000000000061FE00.
The title's address is 0000000000BA1420.
The author's address is 0000000000BA1440.
-------------------------------------------------------------
Free Memory Success!
Free Memory Success!

结构体的大小(笔试考点)

之前学过的基本数据类型,如 char、int、double,它们的大小都是固定的,在 64 位系统中,char 占 1 字节,int 占 4 字节,double 占 8 字节。而结构体的大小并不固定,因为结构体的成员类型和数目是程序员自定义的。和基本数据类型一样,我们可以用 sizeof 这个运算符来获取结构体占据的内存大小。

struct Cookie
{
    int a;
    short b;
    short c;    
} cookie;
int main()
{
    printf("%d\n", sizeof cookie);
    return 0;
}

上面代码输出的结果是 8,正好是 1 个 int 类型,2 个 short 类型的大小之和。
那么结构体的大小是各成员大小之和吗?我们把 Cookie 稍微变化一下。

struct Cookie
{
    int a;
    short b;
    char c;    
} cookie;

将 Cookie 的第3个成员变为 char,运行程序发现输出的结果依旧是 8,而不是 4+3+1 = 7。由此可知结构体的大小并不是简单的各成员大小之和。
在计算结构体大小的时候,需要满足以下规则。

对齐原则

  1. 结构体成员的地址需要根据对齐数进行字节对齐。对齐数=编译器默认的对齐数与该成员类型的大小中的较小值。其中编辑器默认的对齐数由编辑器决定,Linux 环境下是 4,这个值也可以由程序员指定。
  2. 结构体总大小为其最大成员大小的整数倍。

示例1:编译器默认对齐数

注:以下程序是在默认对齐数为 4 的编译器下运行。如果是在像 VS 这种默认对齐数为 8 的编译器下运行结果会有所不同。

struct Cookie1
{
    int a;
    short b;
    char c[10];    
} cookie1;
struct Cookie2
{
    short b;
    int a;
    char c[10];    
} cookie2;

首先看 Cookie1 的大小。Cookie1 的成员中 int 占4字节,short 占2字节,char 占1字节,因此最大成员是占4字节的 int(对于数组,考虑数组元素的大小而不是数组的大小),因此最后计算的 Cookie1 的大小一定是4的倍数。对于第一个成员 a,占4字节。对于第二个成员 b,大小是2字节,小于编译器默认的4字节,因此 b 的对齐数为 2,而 b 前面的成员已经占据的内存大小为 4,是2字节的倍数,因此 b 不需要字节补齐,此时 Cookie1 的内存大小是 4+2=6 字节。对于第三个成员 c,对齐数是 1,前面成员以及占据的内存是 6,是对齐数1的倍数,因此不需要字节补齐。此时 Cookie1 的内存大小是 4+2+10=16,正好是其最大成员 a 大小的倍数,因此 Cookie1 的内存大小是 16。
再来看 Cookie2 的大小。Cookie2 的成员中依旧是 int 为最大成员,因此最后计算的 Cookie2 的大小也一定是 4 的倍数。对于第一个成员 b,占2字节。对于第二个成员 a,对齐数是 4,而 a 前面已占据的内存是 2,不是 4 的倍数,因此需要对前面的内存进行补齐,空出 2 字节空间,此时 Cookie2 的大小为 2(存储 b) + 2(空出) + 4(存储 a) = 8 字节。对于成员 c,不需要字节补齐。此时 Cookie2 的大小为 2+2+4+10 = 18,不是 4 的倍数,因此最后还需要空出 2 字节进行字节补齐,使得 Cookie2 的大小为 4 的倍数,故 Cookie2 的大小为 2+2(空出)+4+10+2(空出)=20。

struct Cookie3
{
    int a;
    double b;    
    short c;
} cookie3;

Cookie3 最大成员为 double 类型,因此 Cookie 的大小一定是 8 的倍数。第一个成员 a,占 4 字节内存。第二个成员 b,对齐数为 min(编译器默认对齐数(4), double 大小(8)),即 b 的对齐数为 4,而 b 前面已占据的内存大小为 4,不需要补齐字节。第三个成员 c,对齐数为 2,之前的成员占据的内存为 4 + 8 = 12,是 2 的倍数,不需要补齐。此时 Cookie3 的大小为 4+8+2 = 14,不是 8 的倍数,需要补齐2字节使其成为 8 的倍数,故 Cookie3 的大小为 16 字节。

示例2:程序员指定对齐数

可以通过 #param pack(n) 来指定 n 为编译器默认的对齐数。
PS:n 的值可以是 1、2、4、8。

#pragma pack(4)
struct Cookie3
{
    int a;
    double c;    
    short b;
} cookie3;
// Cookie3 的大小为 16
#pragma pack(8)
struct Cookie4
{
    int a;
    double c;    
    short b;
} cookie4;
// Cookie4 的大小为 24

为什么要字节对齐?

  1. 为了 CPU 访问数据的高效率。如果变量的地址不对齐,那么 CPU 读取结构体就需要对结构体成员进行重复的访问,然后组合得到数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。
  2. 在有的硬件平台中,计算机在内存读取数据时,只能在规定的地址处读数据,而不是内存中任意地址都是可以读取。因此字节对齐尤为重要。

为什么要结构体大小?

通过示例1,我们可以看到存储相同的成员的结构体,在结构体声明中成员的顺序不同,结构体的大小是不同的。了解了结构体大小的计算,我们可以在声明结构体的时候声明一个占内存最小的结构体,这样可以减少内存开销。
?

指向结构的指针

为什么使用指向结构的指针?

  • 指向结构的指针通常比结构本身容易操控。
  • 一些早期的 C 视线中,结构不能作为参数传递给函数,但是可以传递指向结构的指针。
  • 传递指针通常更有效率。
  • 一些用于表示数据的结构中包含指向其他结构的指针。

向函数传递结构

和基本类型数据相同,可以向函数传递结构变量和指向结构的指针。

结构和结构指针作为函数参数的优缺点:

结构作为参数指针作为参数
优点1. 函数处理的是原始数据的副本,保护了原始数组;
2. 代码风格更为清晰。1. 无论是以前还是现在的实现都可以使用这种方法;
2. 执行起来很快,只需要传递一个地址。
缺点1. 较老版本的实现可能无法处理这样的代码;
2. 传递结构浪费时间和存储空间。无法保护原始数据,但 ANSI C 新增的 const 限定符解决了这个问题。

结构和结构指针作为函数参数的选择:
??通常,我们为了追求效率会使用结构指针作为函数参数,如需防止原始数据被意外修改,可以使用 const 限定符。而按值传递结构是处理小型结构最常用的方法。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-03 15:50:34  更:2022-03-03 15:55:11 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:28:52-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码