C语言的底层逻辑剖析(结构体篇)结构体声明,结构成员的类型,结构体变量的定义和初始化,结构体成员的访问,结构体传参

结构体声明

结构体的概念

结构体的概念:结构(结构体)是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。

为什么要引入结构体呢?实际上是因为在生活中我们有许多复杂的事物,难以用一个int或者float等等一个简单的类型来描述清楚,例如一个学生,他应该有姓名,性别,身高,等等诸多因素,再比如一本书,有书名,作者,出版社,等等。所以在C语言中我们提供了自定义类型,有枚举,结构体,联合体等等,>我们今天只简单学一下结构体,其他的自定义类型会放到进阶里来学习。

结构的声明

假设我们要来描述一个学生,包括有姓名,性别,年龄,考试成绩这四个考虑因素,我们应该怎么来定义呢?

struct Stu
{
    //结构体成员列表
	char name[20];
	char sex[10];
	int age;
	float score;
};//注意这里是有分号的

这样我们就定义了一个结构体类型,我们要描述一个学生,起的名称叫做Stu。

结构成员的类型

结构成员的类型是没有什么要求的,标量,数组,指针,其他结构体也可以(又开始套娃了对吧)。这一般提到C/C++就是都不得不提到C/C++的灵活性,这种灵活性是体现在方方面面的,所以要把C学精用好还是很有难度的。

结构体变量的定义和初始化

这里就是我们的重点了,我们创建了一个类型要先去定义变量然后初始化,最后去使用这个变量对吧,这是我们最终的目的。那么结构体变量如何创建呢?

这里其实有两种方式,我们来看一段代码:

struct Stu
{
	char name;
	char sex;
	int age;
	float score;
}s4,s5;//这里两种方式其实是等价的,
struct Stu s6;//定义的都是全局的结构体变量

int main()
{
	struct Stu s1, s2, s3;//此处就是定义的局部的结构体变量了
	return 0;
}

那么接下来就是我们的初始化了,怎么进行初始化呢?

struct Stu
{
	char name[20];
	char sex[10];
	int age;
	float score;
}s4 = { "如花","female",20,60.0 };

int main()
{
	struct Stu s = { "zhangsan","male",18,95.5};
	struct Stu s4 = { "如花","female",20,60.0 };
    printf("%s %s %d %.1f\n", s.name, s.sex, s.age, s.score);
	return 0;
}

还有一点值得一提的是,结构体里面是可以放结构体的,例如下面代码示例:

struct S
{
	int a;
	char b;
};
struct Stu
{
	char name[20];
	struct S;//结构体成员可以是结构体
	int age;
};

int main()
{
    //初始化也用大括号嵌套
	struct Stu p = { "旺财",{28,'c'},18 };
	printf("%s %d %c %d\n", p.name, p.a, p.b, p.age);
	return 0;
}

看到这里,我想你也应该有点感觉了,结构体就是自己定义的一种类型,其实也不是什么高深的东西对不对。

结构体成员的访问

第一种访问方式

其实结构体成员的访问上面的代码已经示例了,就是通过’.'一个点这个操作符来访问的,它的操作数是两个,例如上面的使用:

printf("%s %s %d %.1f\n", s.name, s.sex, s.age, s.score);

printf("%s %d %c %d\n", p.name, p.a, p.b, p.age);

当我们使用 . 来访问的时候其实是有提示的,如图:

image-20221122204210746

我们只需要选择我们想要使用的成员即可,需要注意的是,要与前面的打印格式一一对应。这个是个小细节,否则报错你可能看不懂找不到错误。

当然我们还可以用函数的方式来打印:

struct Stu
{
	char name;
	int age;
};

void Print(struct Stu p1)
{
	printf("%s %d\n", p1.name, p1.age);
}

int main()
{
	struct Stu p = { "如花",18 };
	Print(p);//传值调用
	return 0;
}

第二种访问方式

除了上面的访问方式呢,还有另外一种,只能用于结构体指针,是通过->一个箭头来访问,代码示例如下:

#include<stdio.h>

struct S
{
	int a;
	char b[20];
};
struct Stu
{
	char name[20];
	struct S;
	int age;
};
void Print(struct Stu* sp)
{
    printf("%s %d %d %s\n", (*sp).b,(*sp).a,(*sp).age,(*sp.).name);//比较啰嗦的写法
	printf("%s %d %d %s\n", sp->b, sp->a, sp->age, sp->name);//使用箭头直接指向对象
}
int main()
{
	struct Stu p = { "旺财",{28,'c'},18 };
	Print(&p);
	return 0;
}

总结结构体成员两种访问格式,1.结构体变量.成员,2.结构体指针->成员。

结构体传参

其实上面我们连带着把结构体传参也已经涉及到了,但是我们这里再来总结一下:

#include<stdio.h>
struct s
{
	float score;
};

struct Stu
{
	char name[20];
	int age;
	struct s;
};

void Print1(struct Stu p1)
{
	printf("%s %d %.1f\n", p1.name, p1.age, p1.score);
}

void Print2(struct Stu* p2)
{
	printf("%s %d %.1f\n", p2->name, p2->age, p2->score);
}

int main()
{
	struct Stu p = { "李华",18,{90.5} };
	Print1(p);//传值调用
	Print2(&p);//传址调用

	return 0;

}

你觉得上面Print1和Print2哪一种传参方式好一点,传值和传址两种方式,其实这道题看似两种方法都可以,但实际上是有标准答案的,一定是第二种方式,传址调用,为什么呢?我们就要回到函数栈帧那块的内容。

函数传参的时候,参数是需要压栈的。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。

我们用通俗的解释一下就是,我们都知道,函数的形参是一份临时拷贝,当我们传的参数过于多或者大的时候,是不是要浪费很多空间啊,而如果是传一个首元素地址过去,是不是就没什么压力,地址大小一定是4或8个字节嘛。

结论:函数传参的时候,要传地址来使用。

好啦,关于结构体这块内容就到这里了,只要理解了结构体是个什么东西,会使用即可。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
首先,我们需要了解什么是非终结符和First集。 非终结符是指在语法分析中,可以被其他符号所推导出来的符号。而First集是指在语法分析中,某一个非终结符能够推导出的所有终结符的集合。 下面是使用C语言结构体和数组实现输出各非终结符的First集的示例代码: ```c #include <stdio.h> #include <string.h> #define MAX_RULES 10 #define MAX_TERMINALS 10 #define MAX_NONTERMINALS 10 // 结构体:产生式 struct Production { char lhs; // 左部非终结符 char rhs[MAX_RULES][MAX_TERMINALS + 1]; // 右部终结符/非终结符 int num_rhs; // 右部符号个数 }; // 结构体:First集 struct FirstSet { char nonterminal; // 非终结符 char terminals[MAX_TERMINALS + 1]; // First集中的终结符 int num_terminals; // First集中的终结符个数 }; int num_productions; // 产生式个数 struct Production productions[MAX_RULES]; // 所有产生式 int num_nonterminals; // 非终结符个数 char nonterminals[MAX_NONTERMINALS]; // 所有非终结符 struct FirstSet first_sets[MAX_NONTERMINALS]; // 所有非终结符的First集 // 获取一个非终结符在所有产生式中出现的位置 void get_positions(char nonterminal, int positions[], int *num_positions) { *num_positions = 0; for (int i = 0; i < num_productions; i++) { if (productions[i].lhs == nonterminal) { positions[*num_positions] = i; (*num_positions)++; } } } // 检查一个符号是否为终结符 int is_terminal(char symbol) { if (symbol >= 'a' && symbol <= 'z') { return 1; } else { return 0; } } // 检查一个符号是否为非终结符 int is_nonterminal(char symbol) { if (symbol >= 'A' && symbol <= 'Z') { return 1; } else { return 0; } } // 计算一个非终结符的First集 void compute_first_set(char nonterminal) { int positions[MAX_RULES]; int num_positions; get_positions(nonterminal, positions, &num_positions); // 初始化First集 first_sets[num_nonterminals].nonterminal = nonterminal; first_sets[num_nonterminals].num_terminals = 0; // 遍历该非终结符对应的所有产生式 for (int i = 0; i < num_positions; i++) { // 如果该产生式右部的第一个符号是终结符,将其加入First集 if (is_terminal(productions[positions[i]].rhs[0][0])) { first_sets[num_nonterminals].terminals[first_sets[num_nonterminals].num_terminals] = productions[positions[i]].rhs[0][0]; first_sets[num_nonterminals].num_terminals++; } // 如果该产生式右部的第一个符号是非终结符,计算其First集并将其加入First集 else if (is_nonterminal(productions[positions[i]].rhs[0][0])) { compute_first_set(productions[positions[i]].rhs[0][0]); for (int j = 0; j < first_sets[num_nonterminals].num_terminals; j++) { if (!strchr(first_sets[num_nonterminals].terminals, first_sets[num_nonterminals].terminals[j])) { first_sets[num_nonterminals].terminals[first_sets[num_nonterminals].num_terminals] = first_sets[num_nonterminals].terminals[j]; first_sets[num_nonterminals].num_terminals++; } } } } // 将该非终结符的First集输出到控制台 printf("First(%c) = { ", nonterminal); for (int i = 0; i < first_sets[num_nonterminals].num_terminals; i++) { printf("%c ", first_sets[num_nonterminals].terminals[i]); } printf("}\n"); num_nonterminals++; } int main() { // 初始化所有产生式 num_productions = 3; strcpy(productions[0].rhs[0], "aCd"); productions[0].num_rhs = 3; productions[0].lhs = 'S'; strcpy(productions[1].rhs[0], "b"); productions[1].num_rhs = 1; productions[1].lhs = 'C'; strcpy(productions[2].rhs[0], "e"); productions[2].num_rhs = 1; productions[2].lhs = 'D'; // 初始化所有非终结符 num_nonterminals = 0; nonterminals[0] = 'S'; nonterminals[1] = 'C'; nonterminals[2] = 'D'; // 计算所有非终结符的First集 for (int i = 0; i < num_nonterminals; i++) { compute_first_set(nonterminals[i]); } return 0; } ``` 在这个示例代码中,我们定义了两个结构体,分别是产生式和First集。 产生式结构体包含了左部非终结符和右部终结符/非终结符。右部终结符/非终结符是一个二维字符数组,每一行代表一个右部符号串,每一列代表一个符号。我们还定义了一个函数`get_positions()`,用于获取一个非终结符在所有产生式中出现的位置。 First集结构体包含了一个非终结符和该非终结符的First集中的所有终结符。我们还定义了一个函数`compute_first_set()`,用于计算一个非终结符的First集。该函数首先获取该非终结符在所有产生式中出现的位置,然后遍历该非终结符对应的所有产生式。如果该产生式右部的第一个符号是终结符,将其加入该非终结符的First集。如果该产生式右部的第一个符号是非终结符,计算其First集并将其加入该非终结符的First集。最后,将该非终结符的First集输出到控制台。 在`main()`函数中,我们初始化了所有产生式和非终结符,并计算了所有非终结符的First集。 输出结果如下: ``` First(S) = { a b e } First(C) = { b } First(D) = { e } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝不过海呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值