关于C语言零基础学习知识,小编有话说,各位看官敬请入下面的专栏世界:打怪升级之路------C语言之路_ankleless的博客-CSDN博客

Hi!冒险者😎,欢迎闯入 C 语言的奇幻异世界🌌!
我是 Anklelss🧑💻,和你一样的闯荡者~ 这是我的冒险笔记📖,里面有踩过的坑🕳️、攒的技能🌟、遇的惊喜🌈,希望能帮你少走弯路✨。
愿我们在代码山峦⛰️各自攀登,顶峰碰拳👊,共赏风景呀!🥳
1. 结构体
1.1 结构体类型的定义
在C语言中,结构体(struct)是一种自定义的数据类型,结构是一些值的集合,这些值被称为成员变量。结构体中每个成员变量可以是不同类型的。
1.2 结构体类型的声明
声明一个结构体我们需要使用语法元素struct,他的使用不需要包含特定的头文件。
大致声明模版如下:
cpp
struct tag
{
member-list;
}variable-list;
那么我们如何用结构体去声明一个人的特征呢?
cpp
struct people
{
char name[20];//姓名
int age;//年龄
int height;//身高
char blood_type;//血型
};
1.3 结构体的初始化
有了上述的结构体people,那么我们应该如何去初始化他的数据呢?又应该怎么将他打印在屏幕上呢?
cpp
int main()
{
struct people A = { "张三",18,182,'B' };
struct people B = { "李四",22,190,'O' };
printf("%s ", A.name);
printf("%d ", A.age);
printf("%d ", A.height);
printf("%c\n", A.blood_type);
//当我们知道一个结构体的首地址时
//也可以用如下的方法进行打印
struct people* p = &B;
printf("%s ", p->name);
printf("%d ", p->age);
printf("%d ", p->height);
printf("%c\n", p->blood_type);
return 0;
}
. (点运算符)和 -> (箭头运算符)是用于访问结构体成员的两种操作符,核心作用是从结构体变量或结构体指针中获取/修改成员数据,但使用场景不同,如下是他们的语法要求:
.
(点运算符)
作用 :直接访问结构体变量成员
语法: 结构体变量 **.**成员
适用场景: 当你拿到的是实实在在的结构体变量(不是指针)时,用**.**访问成员
->
(箭头运算符)
作用:通过结构体指针访问成员
语法 :结构体指针**->**成员名
**适用场景:**当你拿到的是结构体指针(存的是结构体变量的地址)时,用->访问成员
注 :只有在变量初始化时,才能用字符串常量去赋值。
1.4 匿名结构体
在C语言中,结构体用一种"特殊声明",它是指匿名结构体 的声明方式------即声明结构体时不指定结构体名,直接定义成员和变量。(也叫不完全声明)
匿名结构体的核心是省略结构体名,语法如下:
cpp
// 形式1:声明时直接定义变量
struct {
数据类型 成员1;
数据类型 成员2;
// ... 其他成员
} 变量名1, 变量名2; // 只能在此处定义变量
// 形式2:结合 typedef 定义类型别名(常用)
typedef struct {
数据类型 成员1;
数据类型 成员2;
} 类型别名; // 后续可通过别名定义变量
通常,他可以直接声明变量(一次性使用),也可以结合typedef定义类型别名(常用方式)
注 :直接声明变量无法重复定义变量,只有结合typedef,后续才可以反复用别名定义变量,实用性更强。
typedef 和 结构体在数据结构中的链表知识板块有更多复杂真实的应用
1.4.1 结构体的自引用
在结构体中存在一种错误的自引用方式,他们的代码表现形式如下:
1. 如下的代码形式导致了无限大的结构体变量,这是不合理的
cpp
struct Node
{
int data;
struct Node next;
//类似递归,但没有趋近条件
//导致了占用无限大的内存空间
};
2. 如下的代码形式提前使用了typedef的类型别名,这是不允许的
cpp
typedef struct
{
int data;
Node* next;//提前使用了未被定义的指针类型
}Node;
如下是正确的自引用形式,这在链表中十分重要。
cpp
typedef struct Node
{
int data;
struct Node* next;//是指针,而不是结构体本身
}Node;
1.5 结构体的内存对齐
我们应该如何计算结构体的内存大小呢?简单的相交?
根据上述的疑问,结构体的内存对齐提供了正确的答案
1.5.1 对齐规则
结构体的对齐规则如下:
- 结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
(offsetof可以计算不同成员的偏移量)
- 其他成员变量的偏移量要对齐到某个数字(对齐数)的整数倍地址处
对齐数 = 编译器默认的一个对齐数与该成员变量大小的较小值
VS中默认的值为8;Linux中gcc没有默认对齐数,对齐数就是成员自身的大小
结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大 的)的整数倍
如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数 的整数倍处,结构体的整体大小就是所有最大对齐数 (含嵌套结构体中成员的对齐数)的整数倍
我们以下面的代码为例:
cpp
struct num
{
char a;
int b;
char c;
float a;
};
如下是该结构体的内存大小计算过程:

结构体的内存对齐,是一种拿空间换时间的存储方式
1.5.2 修改默认对齐数
#pragma这个预处理指令,可以改变编译器的默认对齐数,具体用例如下:
cpp
#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
int main()
{
//输出的结果是什么?
printf("%d\n", sizeof(struct S));
return 0;
}
结构体在对齐不合适的时候,我们可以更改默认对齐数
1.6 结构体传参
cpp
struct S
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{print1(s); //传结构体
print2(&s); //传地址
return 0;
}
上述代码中,print1相当于在内存空间中拷贝了一份结构体s,会增加程序运行时的时间和空间负担,print2不会有这种缺陷,并且传指针的方式也可以对结构体内部的元素进行相对的修改
结论: 结构体传参时,需要传结构体的地址
---------------------------------------------------------------------------------------------------------------------------------
2. 联合体
2.1 联合体类型的定义
在C语言中,联合体是一种特殊的数据结构,他允许在同一块内存空间中存储不同类型的数据,但同一时刻只能存储其中一种类型的数据(即所有成员共享同一段内存)
**联想记忆:**像电影中人格分裂的角色,就和联合体特征一致,共用一具身体但是人格多样,且一次只能出现一种人格
2.2 联合体类型的声明
联合体也叫做共用体 ,给一个成员赋值时,会影响其他成员的值。声明时需使用union
cpp
//联合类型的声明
union Un
{
char c;
int i;
};
2.3 联合体的特点和内存计算
联合体的成员是共用同一块内存空间的,那么一个联合体的内存大小至少是成员中最大内存的大小(因为联合体至少得有能力保存最大的那个成员)。
当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。
cpp
#include <stdio.h>
union Un1
{
char c[5];
int i;
};
union Un2
{
short c[7];
int i;
};
int main()
{
//下⾯输出的结果是什么?
printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));
return 0;
}
我们得到UN1的大小为8个字节(最大对齐数为4 );UN2的大小为16个字节(最大对齐数为4)。
以UN2为例,最大成员所需内存大小为14字节,但联合体中最大对齐数为4, 14不是4的整数倍,根据规则,需要扩展到16个字节大小。
3. 枚举
3.1 枚举类型的定义
在C语言中,枚举是一种用户定义的数据类型,用于为整数常量 指定有意义的名称,使代码更具有可读性和可维护性。枚举类型的变量只能取枚举中定义的常量值
3.2 枚举类型的声明
枚举顾名思义就是⼀⼀列举,他的声明需要用到**enum,**示例如下:
cpp
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
枚举类型的变量本质上存储的是整数,枚举常量只是这些整数的"别名",上述声明中的枚举常量遵循默认值规则,即:
cpp
enum Num {
A, // 默认值为 0
B, // 默认值为 1(前一个值 +1)
C // 默认值为 2(前一个值 +1)
};
同样的,我们也可以显式显式指定值:
cpp
enum Status {
OK = 0, // 显式指定为 0
ERROR = -1, // 显式指定为 -1
WARN, // 自动为 0(ERROR 的值 -1 +1 = 0)
INFO = 5, // 显式指定为 5
DEBUG // 自动为 6(INFO 的值 5 +1 = 6)
};
手动为枚举常量指定任意的整数(正数、负数、零均可),后续未指定值的常量会在前一个常量的基础上+1
在枚举类型中,不同的枚举常量可以被指定为相同的值,但这种方法会降低代码可读性
3.3 枚举类型的优点
在C语言中,我们知道#define也可以定义常量,他是一种文本替换,那为什么非要使用枚举呢?他到底有什么特别之处呢?
枚举的优点:
增加代码的可读性和可维护性
和#define定义的标识符比较枚举有类型检查,更加严谨
便于调试,预处理阶段会删除#define定义的符号
使用方便,一次可以定义多个常量
枚举常量是遵循作用域规则的,如果枚举声明在函数内 ,则只能在函数内使用