结构体
1. 定义:
用户自定义的数据类型,在结构体中可以包含若干个不同数据类型的成员变量(也可以相同),使这些数据项组合起来反映某一个信息
2. 格式:
struct 结构体名 (用户自定义的数据类型)
{
数据类型 成员变量1;
数据类型 成员变量2;
数据类型 成员变量2;
};
3. 结构体变量:
3.1. 概念:
通过结构体类型定义的变量
3.2. 格式:
struct 结构体名 变量名;
3.2.1. 先定义结构体,在定义结构体变量
struct 结构体名
{
成员变量;
};
struct 结构体名 变量名;
#include <stdio.h>
struct student
{
char name[32];
int id;
int age;
};
int main(int argc, char const *argv[])
{
struct student stu;
return 0;
}
3.2.2. 定义结构体的同时,定义结构体变量
struct 结构体名
{
成员变量;
} 变量名;
struct student
{
char name[32];
int id;
int age;
} stu;
3.2.3. 缺省结构体名定义结构体变量
struct
{
成员变量;
} 变量名;
4. 赋值:
4.1. 定义变量的同时直接用大括号赋值
#include <stdio.h>
struct student
{
char name[32];
int id;
int age;
};
int main(int argc, char const *argv[])
{
struct student stu = {"zhangsan", 153461, 18};
return 0;
}
4.2. 定义变量时未初始化,然后对变量单独赋值
#include <stdio.h>
#include <string.h>
struct student
{
char name[32];
int id;
int age;
};
int main(int argc, char const *argv[])
{
struct student stu;
stu.age = 18;
stu.id = 3517635;
strcpy(stu.name, "zhangsan");
return 0;
}
4.3. 点等法赋值
#include <stdio.h>
#include <string.h>
struct student
{
char name[32];
int id;
int age;
};
int main(int argc, char const *argv[])
{
struct student stu = {
.id = 361867,
.age = 18,
.name = "zhangsan",
};
return 0;
}
5. 访问
通过 . 访问:结构体变量名.成员变量名
scanf("%s %d %d", stu.name, &stu.age, &stu.id);
printf("%s %d %d\n", stu.name, stu.age, stu.id);
6. 重定义 typedef
typedef int int_num;
int a; == int_num a;
typedef int * int_p;
int *p = &a; == int_p p = &a;
6.1. 定义结构体的同时重定义
typedef struct student
{
int id;
int age;
} STU; // STU 是结构体类型重定义的名字
struct student stu; == STU stu;
6.2. 先定义结构体,然后重定义
struct student
{
int id;
int age;
};
typedef struct student STU;
STU stu;
例子:
typedef struct student
{
int id;
int age;
} STU;
STU stu = {1, 18};
STU *p = &stu; // struct student * p = &stu
int a;
typedef int STU
STU *p = &a;
typedef struct student
{
int id;
int age;
} STU, *STUP;
typedef struct student STU
typedef struct student * STUP
STU stu = {1, 21};
STUP p = &stu;
STU *q = &stu; // STUP p = &stu;
练习:创建一个名为student的结构体,包含姓名,学号,班级,分数,(数据类型自己定义),从终端输入学生的信息并打印。
练习:用指针实现strcpy、strcat
strcpy:
#include <stdio.h>
char *mystrcpy(char *s1, char *s2)
{
char *p = s1;
while (*s2 != '\0')
{
*s1 = *s2;
s1++;
s2++;
}
*s1 = '\0';
return p;
}
int main(int argc, char const *argv[])
{
char str[32] = {};
char arr[32] = "hello";
mystrcpy(str, arr);
printf("%s\n", str); // hello
return 0;
}
结构体数组
1. 概念
结构体类型相同的变量组成的数组
2. 格式:
2.1. 定义结构体的同时定义结构体数组
struct student
{
int id;
int age;
} stu[3];
2.2. 先定义结构体,然后定义结构体数组
struct student
{
int id;
int age;
};
struct student stu[3];
3. 初始化
3.1 定义结构体数组同时赋值
struct student
{
int id;
int age;
} stu[3] = {
{3415341, 21}, // 这是第一个人的信息
{35134, 18}, // 这是第二个人的信息
{763163, 23} // 这是第三个人的信息
};
3.2. 先定义结构体数组,在对结构体数组的每一个元素分别赋值
struct student
{
int id;
int age;
} stu[3];
stu[0].id = 1;
stu[0].age = 18;
stu[1].id = 2;
4. 结构体数组的大小
sizeof(结构体数组名);
元素个数*结构体类型大小;
5. 结构体数组输入输出(for循环)
#include <stdio.h>
struct student
{
int id;
int age;
float score;
char *name;
} stu[3];
int main(int argc, char const *argv[])
{
for (int i = 0; i < 3; i++)
{
scanf("%d %d %f", &stu[i].id, &stu[i].age, &stu[i].score);
}
for (int i = 0; i < 3; i++)
{
printf("%d %d %.2f\n", stu[i].id, stu[i].age, stu[i].score);
}
return 0;
}
练习: 创建一个名为student的结构体数组,包含学号,姓名,分数,(数据类型自己定义),从终端输入学生的信息并打印分数及格的学生信息(输入3人即可)。
结构体指针
1. 概念:
指向结构体的指针
2. 格式:
struct 结构体名 *结构体指针名
#include <stdio.h>
struct student
{
int id;
int age;
} stu1, stu2;
struct work
{
int id;
int age;
} w1, w2;
int main(int argc, char const *argv[])
{
struct student *p1 = &stu1;
struct student *p2 = &w1; // 错误,结构体类型不匹配
return 0;
}
3. 赋值
格式:结构体指针变量名 -> 成员变量名
-> 指向的
#include <stdio.h>
struct student
{
int id;
int age;
} stu1, stu2;
int main(int argc, char const *argv[])
{
struct student *p1 = &stu1;
p1 -> id = 1;
p1 -> age = 18;
printf("%d %d\n", p1 -> id, p1 -> age);
return 0;
}
(*p1).成员变量
练习:创建一个结构体数组,数组名为student,成员包含学号,姓名,成绩(数据类型自己设定)从终端输入学生信息,封装函数实现按成绩从低到高打印学生信息。
4. 结构体指针大小
本质是指针,4字节
总结
1. 不能把结构体类型变量作为整体引用,只能对结构体类型变量中的成员变量分别引用
2. 如果成员变量本身属于另一种结构体类型,用若干个成员运算符一级级找到你想要的成员变量
3. 可以把成员变量当成普通变量运算
4. 在数组中,数组之间不能彼此赋值,结构体变量可以相互赋值
inta[3]={};
intb[3]={1, 2, 3};
a=b;
结构体大小
sizeof(struct 结构体名);//结构体类型大小
struct stu
{
char a;
short w;
char y;
int b;
char c;
};
sizeof(struct stu); // 12
结构体大小遵循字节对齐原则
1. 字节对齐
在实际使用中,访问特定数据类型变量时需要在特定的内存起始地址进行访问,这就需要各种数据类型按照一定的规则在空间上进行排列,而不是顺序地一个接一个地存放,这就是字节对齐
2. 字节对齐原则
a. 在32位系统下默认的value值为4字节,判断结构体类型中最大成员变量的字节大小,和默认的value值进行比较,按小的数进行对齐
b. 结构体成员进行对齐时遵循地址偏移量是成员类型的整数倍,double 类型数据存放在4字节的整数倍
c. 结构体成员按顺序进行存储,如果不满足以上条件时,需要填充空字节
3. 为什么要进行字节对齐?
a. 平台原因:不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。(提高程序的移植性)
b. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
c. 内存原因:在设计结构体时,通过对齐规则尽可能优化结构体的空间大小至最小空间
例子:
struct S
{
double a;
char b;
int c;
}
第一步:找出成员变量的大小,让其与编译器的默认的对齐数进行比较,取较小的值作为该成员变量的对齐数
注:我们现在使用的编译器默认对齐数为4
VS编译器默认对齐数为8
第二步:根据每个成员对应的对齐数画出他们在内存中的相对位置
第三步:通过最大对齐数决定最终该结构体的大小
通过图片我们可以知道。绿色部分+红色部分紫色部分+红色与紫色之间的白色部分(浪费掉的)总共占用了16字节的内存空间
补充:
指针数组:
命令行参数:
int main(int argc, char const *argv[])
{
return 0;
}
argv:就是一个指针数组,里面存方法的是命令行传递的字符串
argc:表示argv指针数组里面存放数据的个数,即命令行传递字符串的个数
共用体
不同类型的成员变量共用一块地址空间
1. 格式:
union共用体名
{
成员列表;
};
2. 定义共用体变量
union共用体名变量名;
union val
{
int a;
char ch;
}
union valv;
v.a=10;
//他俩不能同时出现,是以最后一次赋值为准
v.ch='a';
3. 特性:
1) 共用体成员共用同一块地址空间
2) 赋值顺序以最后一次为准
3) 共用体大小为成员中类型最大的数据的大小
4. 使用共用体测试大小端
#include <stdio.h>
union val
{
int num;
char ch;
};
int main(int argc, char const *argv[])
{
union val v;
v.num = 0x12345678;
if(v.ch == 0x78)
{
printf("小端\n");
}
else
{
printf("大断\n");
}
return 0;
}
枚举
维基百科的理解:枚举类型用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型。 定义:是指将变量的值一一列出来,变量的值只限于列举出来的值的范围内。
我的理解:枚举类型就是将一些比较固定的值一一列举出来,比如一年有十二个月,一个礼拜有七天,这是毫无疑问的,就可以将这些月份天数用常量来代替。枚举类型和宏定义是差不多的,只有细微区别,宏运行是在预处理阶段完成的,枚举类型是在与编译阶段完成的。
1. 定义:
用户自定义数据类型,用于声明一组常数
2. 格式:
enum枚举名
{
value1,
value2,
value3,
.....
};
#include <stdio.h>
enum week
{
MON,
TUE,
WED
};
int main(int argc, char const *argv[])
{
printf("%d %d %d\n", MON, TUE, WED); // 0 1 2
int n;
scanf("%d", &n);
switch (n)
{
case MON:
printf("周一\n");
break;
case TUE:
printf("周二\n");
break;
case WED:
printf("周三\n");
break;
}
return 0;
}
未赋初值,第一个常数会默认为0,依次加一(如果第一个值被设为1,则默认从1开始递增)。
存储类型
auto static extern register
1. auto
修饰变量,一般省略时会认为是auto类型
2. static:修饰变量和函数
修饰变量:
1. 变量存在全局区(静态区)
如果静态局部变量有初值,存放在.data区,没有初值存放在.bss区域
2. 生命周期为整个程序
3. 限制作用域:
a. 修饰局部变量,和普通的局部变量的作用域没有区别,但是生命周期被延长为整个程序
b. 修饰全局变量,限制在本文件中使用
4. 只初始化一次,初值赋值0;
tatic 关键字在 C++ 中用于修饰局部变量、全局变量以及函数时,具有不同的特点和作用。
修饰局部变量
当 static 修饰局部变量时,其主要特点如下:
- 延长生命周期:静态局部变量在程序执行期间一直存在,其生命周期从首次初始化开始,一直持续到程序结束。这意味着,即使在函数返回后,静态局部变量的值也会被保留下来。
- 初始化:静态局部变量在首次进入其声明所在的作用域时被初始化,且只被初始化一次。
- 作用域限制:静态局部变量只在其声明所在的作用域内可见,其他函数无法直接访问它,但可以通过返回值或引用间接访问。
- 内存存储:静态局部变量在内存中存储于静态数据区,而不是在栈上分配空间。
修饰全局变量
当 static 修饰全局变量时,其主要特点在于限制变量的可见性:
- 文件内可见:被 static 修饰的全局变量只对定义在同一文件中的函数可见。其他文件即使包含了该变量的声明,也无法直接访问它。
- 内存存储:未初始化的静态全局变量存放在 BSS 段,已经初始化的则放在 DATA 段。
修饰函数
当 static 修饰函数时,其主要特点如下:
- 文件内调用:被 static 修饰的函数只能在同一文件中被调用。这有助于隐藏函数的实现细节,防止其他文件误用或修改。
- 访问方式:由于静态函数具有文件作用域,因此无法通过其他文件的函数指针来调用它。但在同一文件内,可以通过函数名直接调用。
总结
static 关键字通过控制变量的生命周期、可见性以及函数的可见性,帮助程序员更好地组织和管理代码。它有助于隐藏实现细节,提高代码的安全性和可维护性。同时,通过限制变量的作用域和函数的调用范围,可以减少潜在的错误和冲突。
#include <stdio.h>
void fun()
{
static int a = 0;
a++;
printf("%d\n", a);
}
int main(int argc, char const *argv[])
{
fun(); // 1
fun(); // 2
return 0;
}
3. extern:外部引用
通过extern可以引用其他文件的全局变量或函数
4. register:寄存器类型
由于寄存器数量比较少,申请不到空间时和auto一样
练习:
创建一个结构体数组,数组名为book,结构体成员包含编号,书名,售价(数据类型自己设定)。写一个函数,包含两个形参,分别接收结构体数组的首地址和一个指定的售价,函数的功能为打印结构体数组中售价大于指定售价的书的信息。