C语言零基础入门(结构体)

这篇具有很好参考价值的文章主要介绍了C语言零基础入门(结构体)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

简介

C 语言内置的数据类型,除了最基本的几种原始类型,只有数组属于复合类型,可以同时包含多个值,但是只能包含相同类型的数据,实际使用中并不够用。

实际使用中,主要有下面两种情况,需要更灵活强大的复合类型。

  • 复杂的物体需要使用多个变量描述,这些变量都是相关的,最好有某种机制将它们联系起来。
  • 某些函数需要传入多个参数,如果一个个按照顺序传入,非常麻烦,最好能组合成一个复合结构传入。

为了解决这些问题,C 语言提供了struct关键字,允许自定义复合数据类型,将不同类型的值组合在一起。这样不仅为编程提供方便,也有利于增强代码的可读性。C 语言没有其他语言的对象(object)和类(class)的概念,struct 结构很大程度上提供了对象和类的功能。

下面是struct自定义数据类型的一个例子。

struct fraction {
  int numerator;
  int denominator;
};

上面示例定义了一个分数的数据类型struct fraction,包含两个属性numeratordenominator

注意,作为一个自定义的数据类型,它的类型名要包括struct关键字,比如上例是struct fraction,单独的fraction没有任何意义,甚至脚本还可以另外定义名为fraction的变量,虽然这样很容易造成混淆。另外,struct语句结尾的分号不能省略,否则很容易产生错误。

定义了新的数据类型以后,就可以声明该类型的变量,这与声明其他类型变量的写法是一样的。

struct fraction f1;

f1.numerator = 22;
f1.denominator = 7;

上面示例中,先声明了一个struct fraction类型的变量f1,这时编译器就会为f1分配内存,接着就可以为f1的不同属性赋值。可以看到,struct 结构的属性通过点(.)来表示,比如numerator属性要写成f1.numerator

再提醒一下,声明自定义类型的变量时,类型名前面,不要忘记加上struct关键字。也就是说,必须使用struct fraction f1声明变量,不能写成fraction f1

除了逐一对属性赋值,也可以使用大括号,一次性对 struct 结构的所有属性赋值。

struct car {
  char* name;
  float price;
  int speed;
};

struct car saturn = {"Saturn SL/2", 16000.99, 175};

上面示例中,变量saturnstruct car类型,大括号里面同时对它的三个属性赋值。如果大括号里面的值的数量,少于属性的数量,那么缺失的属性自动初始化为0

注意,大括号里面的值的顺序,必须与 struct 类型声明时属性的顺序一致。否则,必须为每个值指定属性名。

struct car saturn = {.speed=172, .name="Saturn SL/2"};

上面示例中,初始化的属性少于声明时的属性,这时剩下的那些属性都会初始化为0

声明变量以后,可以修改某个属性的值。

struct car saturn = {.speed=172, .name="Saturn SL/2"};
saturn.speed = 168;

上面示例将speed属性的值改成168

struct 的数据类型声明语句与变量的声明语句,可以合并为一个语句。

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

上面的语句同时声明了数据类型book和该类型的变量b1。如果类型标识符book只用在这一个地方,后面不再用到,这里可以将类型名省略。

struct {
  char title[500];
  char author[100];
  float value;
} b1;

上面示例中,struct声明了一个匿名数据类型,然后又声明了这个类型的变量b1

与其他变量声明语句一样,可以在声明变量的同时,对变量赋值。

struct {
  char title[500];
  char author[100];
  float value;
} b1 = {"Harry Potter", "J. K. Rowling", 10.0},
  b2 = {"Cancer Ward", "Aleksandr Solzhenitsyn", 7.85};

上面示例中,在声明变量b1b2的同时,为它们赋值。

下一章介绍的typedef命令可以为 struct 结构指定一个别名,这样使用起来更简洁。

typedef struct cell_phone {
  int cell_no;
  float minutes_of_charge;
} phone;

phone p = {5551234, 5};

上面示例中,phone就是struct cell_phone的别名。

指针变量也可以指向struct结构。

struct book {
  char title[500];
  char author[100];
  float value;
}* b1;

// 或者写成两个语句
struct book {
  char title[500];
  char author[100];
  float value;
};
struct book* b1;

上面示例中,变量b1是一个指针,指向的数据是struct book类型的实例。

struct 结构也可以作为数组成员。

struct fraction numbers[1000];

numbers[0].numerator = 22;
numbers[0].denominator = 7;

上面示例声明了一个有1000个成员的数组numbers,每个成员都是自定义类型fraction的实例。

struct 结构占用的存储空间,不是各个属性存储空间的总和,而是最大内存占用属性的存储空间的倍数,其他属性会添加空位与之对齐。这样可以提高读写效率。

struct foo {
  int a;
  char* b;
  char c;
};
printf("%d\n", sizeof(struct foo)); // 24

上面示例中,struct foo有三个属性,在64位计算机上占用的存储空间分别是:int a占4个字节,指针char* b占8个字节,char c占1个字节。它们加起来,一共是13个字节(4 + 8 + 1)。但是实际上,struct foo会占用24个字节,原因是它最大的内存占用属性是char* b的8个字节,导致其他属性的存储空间也是8个字节,这样才可以对齐,导致整个struct foo就是24个字节(8 * 3)。

多出来的存储空间,都采用空位填充,所以上面的struct foo真实的结构其实是下面这样。

struct foo {
  int a;        // 4
  char pad1[4]; // 填充4字节
  char *b;      // 8
  char c;       // 1
  char pad2[7]; // 填充7字节
};
printf("%d\n", sizeof(struct foo)); // 24

为什么浪费这么多空间进行内存对齐呢?这是为了加快读写速度,把内存占用划分成等长的区块,就可以快速在 Struct 结构体中定位到每个属性的起始地址。

由于这个特性,在有必要的情况下,定义 Struct 结构体时,可以采用存储空间递增的顺序,定义每个属性,这样就能节省一些空间。

struct foo {
  char c;
  int a;
  char* b;
};
printf("%d\n", sizeof(struct foo)); // 16

上面示例中,占用空间最小的char c排在第一位,其次是int a,占用空间最大的char* b排在最后。整个strct foo的内存占用就从24字节下降到16字节。

struct 的复制

struct 变量可以使用赋值运算符(=),复制给另一个变量,这时会生成一个全新的副本。系统会分配一块新的内存空间,大小与原来的变量相同,把每个属性都复制过去,即原样生成了一份数据。这一点跟数组的复制不一样,务必小心。

struct cat { char name[30]; short age; } a, b;

strcpy(a.name, "Hula");
a.age = 3;

b = a;
b.name[0] = 'M';

printf("%s\n", a.name); // Hula
printf("%s\n", b.name); // Mula

上面示例中,变量b是变量a的副本,两个变量的值是各自独立的,修改掉b.name不影响a.name

上面这个示例是有前提的,就是 struct 结构的属性必须定义成字符数组,才能复制数据。如果稍作修改,属性定义成字符指针,结果就不一样。

struct cat { char* name; short age; } a, b;

a.name = "Hula";
a.age = 3;

b = a;

上面示例中,name属性变成了一个字符指针,这时a赋值给b,导致b.name也是同样的字符指针,指向同一个地址,也就是说两个属性共享同一个地址。因为这时,struct 结构内部保存的是一个指针,而不是上一个例子的数组,这时复制的就不是字符串本身,而是它的指针。并且,这个时候也没法修改字符串,因为字符指针指向的字符串是不能修改的。

总结一下,赋值运算符(=)可以将 struct 结构每个属性的值,一模一样复制一份,拷贝给另一个 struct 变量。这一点跟数组完全不同,使用赋值运算符复制数组,不会复制数据,只会共享地址。

注意,这种赋值要求两个变量是同一个类型,不同类型的 struct 变量无法互相赋值。

另外,C 语言没有提供比较两个自定义数据结构是否相等的方法,无法用比较运算符(比如==!=)比较两个数据结构是否相等或不等。

struct 指针

如果将 struct 变量传入函数,函数内部得到的是一个原始值的副本。

#include <stdio.h>

struct turtle {
  char* name;
  char* species;
  int age;
};

void happy(struct turtle t) {
  t.age = t.age + 1;
}

int main() {
  struct turtle myTurtle = {"MyTurtle", "sea turtle", 99};
  happy(myTurtle);
  printf("Age is %i\n", myTurtle.age); // 输出 99
  return 0;
}

上面示例中,函数happy()传入的是一个 struct 变量myTurtle,函数内部有一个自增操作。但是,执行完happy()以后,函数外部的age属性值根本没变。原因就是函数内部得到的是 struct 变量的副本,改变副本影响不到函数外部的原始数据。

通常情况下,开发者希望传入函数的是同一份数据,函数内部修改数据以后,会反映在函数外部。而且,传入的是同一份数据,也有利于提高程序性能。这时就需要将 struct 变量的指针传入函数,通过指针来修改 struct 属性,就可以影响到函数外部。

struct 指针传入函数的写法如下。

void happy(struct turtle* t) {
}

happy(&myTurtle);

上面代码中,t是 struct 结构的指针,调用函数时传入的是指针。struct 类型跟数组不一样,类型标识符本身并不是指针,所以传入时,指针必须写成&myTurtle

函数内部也必须使用(*t).age的写法,从指针拿到 struct 结构本身。

void happy(struct turtle* t) {
  (*t).age = (*t).age + 1;
}

上面示例中,(*t).age不能写成*t.age,因为点运算符.的优先级高于**t.age这种写法会将t.age看成一个指针,然后取它对应的值,会出现无法预料的结果。

现在,重新编译执行上面的整个示例,happy()内部对 struct 结构的操作,就会反映到函数外部。

(*t).age这样的写法很麻烦。C 语言就引入了一个新的箭头运算符(->),可以从 struct 指针上直接获取属性,大大增强了代码的可读性。

void happy(struct turtle* t) {
  t->age = t->age + 1;
}

总结一下,对于 struct 变量名,使用点运算符(.)获取属性;对于 struct 变量指针,使用箭头运算符(->)获取属性。以变量myStruct为例,假设ptr是它的指针,那么下面三种写法是同一回事。

// ptr == &myStruct
myStruct.prop == (*ptr).prop == ptr->prop

struct 的嵌套

struct 结构的成员可以是另一个 struct 结构。

struct species {
  char* name;
  int kinds;
};

struct fish {
  char* name;
  int age;
  struct species breed;
};

上面示例中,fish的属性breed是另一个 struct 结构species

赋值的时候有多种写法。

// 写法一
struct fish shark = {"shark", 9, {"Selachimorpha", 500}};

// 写法二
struct species myBreed = {"Selachimorpha", 500};
struct fish shark = {"shark", 9, myBreed};

// 写法三
struct fish shark = {
  .name="shark",
  .age=9,
  .breed={"Selachimorpha", 500}
};

// 写法四
struct fish shark = {
  .name="shark",
  .age=9,
  .breed.name="Selachimorpha",
  .breed.kinds=500
};

printf("Shark's species is %s", shark.breed.name);

上面示例展示了嵌套 Struct 结构的四种赋值写法。另外,引用breed属性的内部属性,要使用两次点运算符(shark.breed.name)。

下面是另一个嵌套 struct 的例子。

struct name {
  char first[50];
  char last[50];
};

struct student {
  struct name name;
  short age;
  char sex;
} student1;

strcpy(student1.name.first, "Harry");
strcpy(student1.name.last, "Potter");

// or
struct name myname = {"Harry", "Potter"};
student1.name = myname;

上面示例中,自定义类型studentname属性是另一个自定义类型,如果要引用后者的属性,就必须使用两个.运算符,比如student1.name.first。另外,对字符数组属性赋值,要使用strcpy()函数,不能直接赋值,因为直接改掉字符数组名的地址会报错。

struct 结构内部不仅可以引用其他结构,还可以自我引用,即结构内部引用当前结构。比如,链表结构的节点就可以写成下面这样。

struct node {
  int data;
  struct node* next;
};

上面示例中,node结构的next属性,就是指向另一个node实例的指针。下面,使用这个结构自定义一个数据链表。

struct node {
  int data;
  struct node* next;
};

struct node* head;

// 生成一个三个节点的列表 (11)->(22)->(33)
head = malloc(sizeof(struct node));

head->data = 11;
head->next = malloc(sizeof(struct node));

head->next->data = 22;
head->next->next = malloc(sizeof(struct node));

head->next->next->data = 33;
head->next->next->next = NULL;

// 遍历这个列表
for (struct node *cur = head; cur != NULL; cur = cur->next) {
  printf("%d\n", cur->data);
}

上面示例是链表结构的最简单实现,通过for循环可以对其进行遍历。

位字段

struct 还可以用来定义二进制位组成的数据结构,称为“位字段”(bit field),这对于操作底层的二进制数据非常有用。

struct {
  unsigned int ab:1;
  unsigned int cd:1;
  unsigned int ef:1;
  unsigned int gh:1;
} synth;

synth.ab = 0;
synth.cd = 1;

上面示例中,每个属性后面的:1,表示指定这些属性只占用一个二进制位,所以这个数据结构一共是4个二进制位。

注意,定义二进制位时,结构内部的各个属性只能是整数类型。

实际存储的时候,C 语言会按照int类型占用的字节数,存储一个位字段结构。如果有剩余的二进制位,可以使用未命名属性,填满那些位。也可以使用宽度为0的属性,表示占满当前字节剩余的二进制位,迫使下一个属性存储在下一个字节。

struct {
  unsigned int field1 : 1;
  unsigned int        : 2;
  unsigned int field2 : 1;
  unsigned int        : 0;
  unsigned int field3 : 1;
} stuff;

上面示例中,stuff.field1stuff.field2之间,有一个宽度为两个二进制位的未命名属性。stuff.field3将存储在下一个字节。

弹性数组成员

很多时候,不能事先确定数组到底有多少个成员。如果声明数组的时候,事先给出一个很大的成员数,就会很浪费空间。C 语言提供了一个解决方法,叫做弹性数组成员(flexible array member)。

如果不能事先确定数组成员的数量时,可以定义一个 struct 结构。

struct vstring {
  int len;
  char chars[];
};

上面示例中,struct vstring结构有两个属性。len属性用来记录数组chars的长度,chars属性是一个数组,但是没有给出成员数量。

chars数组到底有多少个成员,可以在为vstring分配内存时确定。

struct vstring* str = malloc(sizeof(struct vstring) + n * sizeof(char));
str->len = n;

上面示例中,假定chars数组的成员数量是n,只有在运行时才能知道n到底是多少。然后,就为struct vstring分配它需要的内存:它本身占用的内存长度,再加上n个数组成员占用的内存长度。最后,len属性记录一下n是多少。

这样就可以让数组charsn个成员,不用事先确定,可以跟运行时的需要保持一致。

弹性数组成员有一些专门的规则。首先,弹性成员的数组,必须是 struct 结构的最后一个属性。另外,除了弹性数组成员,struct 结构必须至少还有一个其他属性。文章来源地址https://www.toymoban.com/news/detail-798272.html

到了这里,关于C语言零基础入门(结构体)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 初学编程入门基础教学视频,中文编程开发语言工具箱之豪华编辑构件,免费版中文编程软件下载

    初学编程入门基础教学视频,中文编程开发语言工具箱之豪华编辑构件,免费版中文编程软件下载  构件的其中一个属性、方法,查找内容,替换内容。 构件工具箱非常丰富,其中该构件在 文本件构件板菜单下。 编程系统化课程总目录及明细,零基础学中文编程视频教程,

    2024年02月07日
    浏览(76)
  • 中文编程开发语言工具系统化教程零基础入门篇和初级1专辑课程已经上线,可以进入轻松学编程

    中文编程开发语言工具系统化教程零基础入门篇和初级1专辑课程已经上线,可以进入轻松学编程 学习编程捷径:(不论是正在学习编程的大学生,还是IT人士或者是编程爱好者,在学习编程的过程中用正确的学习方法 可以达到事半功倍的效果。对于初学者,可以通过下面的

    2024年02月08日
    浏览(78)
  • 时间老去,Ruby不死,Ruby语言基础入门教程之Ruby3全平台开发环境搭建EP00

    如果说电子游戏是第九艺术,那么,编程技术则配得上第十艺术的雅称。艺术发展的普遍规律就是要给与人们对于艺术作品的更高层感受,而Matz的Ruby语言则正是这样一件艺术品。 无论是语法还是理念,都让Ruby开发者感受到款待,如此,Ruby代码就像活了过来,它们时而高声

    2024年01月20日
    浏览(55)
  • 【05】STM32·HAL库开发-C语言基础知识 | stdint.h介绍 | 位操作 | 宏定义的使用 | 条件编译 | extern声明 | typdef使用 | 结构体、指针、代码规范介绍。

      stdint.h 是从 C99 中引进的一个标准 C 库的文件,可以在MDK5的安装路径:D:MDK5.34ARMARMCCinclude中找到。   stdint.h 定义了很多类型别名,将有符号的char类型定义别名为int8_t等,使用此套别名有易于移植。   在MDK中需要配置才能支持使用S99标准, 默认是勾选的 。   只

    2024年02月08日
    浏览(43)
  • 数据结构入门(C语言版)二叉树概念及结构(入门)

    1.1 树的概念 树是一种非线性的数据结构,它是由n(n=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。 ☆有一个特殊的结点,称为根结点,根节点没有前驱结点 ☆除根节点外,其余结点被分成M

    2023年04月14日
    浏览(40)
  • 【C语言】入门——结构体

    目录 结构体 为什么有结构体? 1.结构体的声明  1.2结构体变量的访问和初始化  2.结构体成员的访问 结构体和数组不同,同一类型的数据的集合是数组;             结构体是多种类型的数据的集合; 为什么有结构体? 在平常生活中,我们记录一个一个学生的信息,往往

    2024年02月09日
    浏览(31)
  • 洛谷-【入门3】循环结构——C语言

    题目描述 给出n和n个整数ai,求这n个整数中最小值是什么。 输入格式 第一行输入一个正整数n,表示数字个数。 第二行输入n个非负整数,表示a1,a2…an,以空格隔开。 输出格式 输出一个非负整数,表示这n个非负整数中的最小值。 输入 #1 输出 #1 题目描述 给定n和k,将从1到

    2024年02月06日
    浏览(52)
  • Go语言入门6(struct 结构体)

    ​结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员 type + 结构体名 + struct + {成员列表} ​⭐如果结构体成员名字是以大写字母开头的,那么该成员就是导出的。这是Go语言导出规则决 定的。一个结构体可能同时包含导出和

    2023年04月12日
    浏览(40)
  • web基础入门和php语言基础入门 二

    WHERE 子句,条件限制语句: SELECT *或其他 FROM 数据表(可以是多个) WHERE 需要满足的条件 SELECT NAME FROM VULN WHERE NAME=\\\"ecoloy\\\"; SELECT * FROM VULN WHERE NAME=\\\"ecoloy\\\"; 首先,我先插入了几条数据。 然后我进行了限制查询,从 vuln 数据表中的 name 字段下筛选出来 NAME=\\\"ecoloy\\\" 数据。 第二句话是

    2024年02月13日
    浏览(53)
  • 数据结构入门(C语言版)二叉树链式结构的实现

    简单回顾一下二叉树的 概念: ★ 空树 ★非空:根节点,根节点的左子树、根节点的右子树组成的。 从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。 下面我们先看二叉树的结构体定义以及创建 首先结构体的定义是元素本身,以

    2023年04月23日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包