记录一下c语言的学习过程,笔记如有错误,欢迎指出!!!
文章目录
- 10.结构体共用体和联合体:
- 1.结构体(数组)的创建
- 2.结构体的赋值
- 3.结构体的嵌套
- 4.结构体的偏移量
- 5.结构体的内存对齐
10.结构体共用体和联合体:
1.结构体(数组)的创建
struct Person
{
char name[64];
int age;
};
typedef struct Person myPerson;
typedef struct Person
{
char name[64];
int age;
}myPerson;
void test01()
{
struct Person p = { "Tom", 18 };
myPerson p2 = { "Jerry", 20 };
}
struct Person2
{
char name[64];
int age;
}myPerson2 = { "aaa", 20 };
//匿名结构体, //只能定义一次该类型
struct
{
char name[64];
int age;
}myPerson3 = { "bbb", 30 };
结构体的创建
//创建在栈上
struct Person p = { "aaa", 10 };
printf("姓名:%s 年龄: %d\n", p.name, p.age);
//创建在堆区
struct Person * p2 = malloc(sizeof(struct Person));
strcpy(p2->name, "bbb"); //只有定义时,才可以采用字符串赋值,其他情况下,需要用strcpy, sprintf等函数,修改字符串的值。
p2->age = 20;
结构体数组的创建
//在栈上分配内存
struct Person persons[] =
{
{ "aaa", 10 },
{ "bbb", 20 },
{ "ccc", 30 },
{ "ddd", 40 },
};
int len = sizeof(persons) / sizeof(struct Person);
//printArray(persons, len);
//在堆区分配内存
struct Person * pArray = malloc(sizeof(struct Person) * 4); // sizeof(pArray) = 4
for (int i = 0; i < 4;i++)
{
sprintf(pArray[i].name, "name_%d", i + 1);
pArray[i].age = 18 + i;
}
printArray(pArray, 4);
if (pArray != NULL)
{
free(pArray);
pArray = NULL;
}
2.结构体的赋值
struct Person
{
char name[64];
int age;
};
struct Person p1 = { "Tom", 18 };
struct Person p2 = { "Jerry", 20 };
//浅拷贝,只拷贝,对应内存中存放的值
p1 = p2;
printf("p1的姓名:%s 年龄 :%d\n", p1.name, p1.age);
printf("p2的姓名:%s 年龄 :%d\n", p2.name, p2.age);
struct Person2
{
char * name;
int age;
};
struct Person2 p1;
p1.name = malloc(sizeof (char)* 64);
strcpy(p1.name, "Tom");
p1.age = 18;
struct Person2 p2;
p2.name = malloc(sizeof (char)* 128);
strcpy(p2.name, "Jerry");
p2.age = 28;
//p1 = p2; //系统提供的赋值操作是简单的浅拷贝 ,我们需要做手动赋值,提供深拷贝, name中存放的是地址,若进行浅拷贝,则p1, p2指向相同的地址
// free(p1); free(p2) 会出错,因为name中的地址释放了两次,因此需要手动赋值
手动赋值
//先释放原来堆区的内容
if (p1.name != NULL)
{
free(p1.name);
p1.name = NULL;
}
//在堆区创建内存
p1.name = malloc(strlen(p2.name) + 1);
strcpy(p1.name, p2.name);
p1.age = p2.age;
printf("p1的姓名:%s 年龄 :%d\n", p1.name, p1.age);
printf("p2的姓名:%s 年龄 :%d\n", p2.name, p2.age);
if (p1.name != NULL)
{
free(p1.name);
p1.name = NULL;
}
if (p2.name != NULL)
{
free(p2.name);
p2.name = NULL;
}
3.结构体的嵌套
一级指针
struct Person
{
char * name;
int age;
};
struct Person ** allocateSpace() //返回struct **person类型, 在堆区创建,函数结束后不会使用,可以使用同级指针接收开辟的空间
{
struct Person ** temp = malloc(sizeof(struct Person *) * 3);
for (int i = 0; i < 3;i++)
{
//创建结构体内存
temp[i] = malloc(sizeof(struct Person));
//将结构体姓名 创建在堆区
temp[i]->name = malloc(sizeof(char)* 64);
//给姓名赋值
sprintf(temp[i]->name, "name_%d", i + 1);
temp[i]->age = 18 + i;
}
return temp;
}
void printPerson(struct Person ** pArray, int len)
{
for (int i = 0; i < len;i++)
{
printf("姓名: %s 年龄: %d\n", pArray[i]->name, pArray[i]->age);
}
}
void freeSpace(struct Person ** pArray , int len) //由内向外逐步释放,由于接受的是同级指针,最外层指针需要在函数外面置NULL
{
if ( pArray == NULL)
{
return;
}
if (len <= 0)
{
return;
}
for (int i = 0; i < 3;i++)
{
if (pArray[i]->name != NULL)
{
printf("%s被释放了\n", pArray[i]->name);
free(pArray[i]->name);
pArray[i]->name = NULL;
}
free(pArray[i]);
pArray[i] = NULL;
}
free(pArray);
pArray = NULL;
}
void test01()
{
struct Person ** pArray = NULL;
pArray = allocateSpace();
//打印数组
printPerson(pArray, 3);
//释放内存
freeSpace(pArray,3);
pArray = NULL;
}
结构体二级指针嵌套
struct Teacher
{
char * name;
char ** students;
};
void allocateSpace(struct Teacher*** teachers) //形参为三级指针
{
if (teachers == NULL)
{
return;
}
//开辟内存
struct Teacher ** ts = malloc(sizeof(struct Teacher *) * 3);
//给每个老师分配内存
for (int i = 0; i < 3;i++)
{
ts[i] = malloc(sizeof(struct Teacher));
//给老师的姓名分配内存
ts[i]->name = malloc(sizeof(char)* 64);
//给老师起名称
sprintf(ts[i]->name, "Teacher_%d", i + 1);
//给学生的数组分配内存
ts[i]->students = malloc(sizeof(char *)* 4);
//给学生的姓名开辟内存 以及赋值
for (int j = 0; j < 4;j++)
{
ts[i]->students[j] = malloc(sizeof(char)* 64);
sprintf(ts[i]->students[j], "%s_Student_%d", ts[i]->name, j + 1);
}
}
*teachers = ts;
}
void printTeachers(struct Teacher** pArray)
{
if (pArray == NULL)
{
return;
}
for (int i = 0; i < 3;i++)
{
printf("%s\n", pArray[i]->name);
for (int j = 0; j < 4;j++)
{
printf(" %s\n", pArray[i]->students[j]);
}
}
}
void freeSpace(struct Teacher ** pArray)
{
if (pArray == NULL)
{
return;
}
for (int i = 0; i < 3;i++)
{
//先释放老师姓名
if (pArray[i]->name != NULL)
{
free(pArray[i]->name);
pArray[i]->name = NULL;
}
//释放学生姓名
for (int j = 0; j < 4;j++)
{
if (pArray[i]->students[j] != NULL)
{
free(pArray[i]->students[j]);
pArray[i]->students[j] = NULL;
}
}
//释放学生的数组
if (pArray[i]->students != NULL)
{
free(pArray[i]->students);
pArray[i]->students = NULL;
}
//释放老师
if (pArray[i] != NULL)
{
free(pArray[i]);
pArray[i] = NULL;
}
}
//释放老师数组
if (pArray != NULL)
{
free(pArray);
pArray = NULL;
}
}
void test01()
{
struct Teacher ** pArray = NULL;
//开辟内存
allocateSpace(&pArray);
//打印数组
printTeachers(pArray);
//释放数组
freeSpace(pArray);
pArray = NULL;
}
4.结构体的偏移量
struct Teacher
{
char a; //0 ~ 3
int b; //4 ~ 7
};
struct Teacher t1;
struct Teacher *p = &t1;
//两种求偏移量的方式
printf("b的属性偏移量为:%d\n", (int)&(p->b) - (int)p);
printf("b的属性偏移量为:%d\n", offsetof(struct Teacher, b)); // offsetof 在stddef.h中
//通过偏移量访问结构体元素
struct Teacher t1 = { 'a', 10 };
printf("t1.b = %d\n", *(int *)((char *)&t1 + offsetof(struct Teacher, b)));
printf("t1.b = %d\n", *(int *)((int *)&t1 + 1 ));
//结构体嵌套求偏移量
struct Teacher2
{
char a;
int b;
struct Teacher c;
};
int offset1 = offsetof(struct Teacher2, c);
int offset2 = offsetof(struct Teacher, b);
printf("%d\n", *(int*)((char*)&t1 + offset1 + offset2));
printf("%d\n", (( struct Teacher * )((char*)&t1 +offset1))->b );
共用体
union test
{
char ch;
short sh;
int var;
};
联合体,内部所有成员变量地址一致。等同于整个联合体的地址。
联合体的大小,是内部成员变量中,最大的那个成员变量的大小。(对齐)
修改其中任意一个成员变量的值,其他成员变量会随之修改。
枚 举:
enum color { 枚举常量 };
enum color { red, green, blue, black, pink, yellow };
枚举常量: 是整型常量。不能是浮点数。可以是负值。 默认初值从 0 开始,后续常量较前一个常量 +1.
可以给任意一个常量赋任意初值。后续常量较前一个常量 +1
5.结构体的内存对齐
Cpu按块读取内存,一块是2^n,假设一块是4个字节
读取一个int型的变量,若变量存放在4的整数倍内存处,只需要读取一次,若存放在奇地址处,则需要读取两个块,即读取两次
因此内存对齐可以提高访问效率,以空间换时间。
结构体中存在对齐操作, char类型占四个字节
结构体内存对齐规则:
1.结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对⻬到某个数字(对⻬数)的整数倍(0,1,2…)的地址处。
对⻬数=编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。
- VS 中默认的值为 8 (查看当前对齐(模)数 : #pragma pack(show) 修改对齐(模)数 : #pragma pack(1))
- Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
3.结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
为了解决上面问题,函数的调用方和被调用放对于函数如何调用必须有一个明确约定,这样的约定成为调用惯例
cdecl,C/C++默认调用惯例, C++也可使用stdcall(标准调用惯例)
栈
C语言中,栈顶是低地址,栈底是高地址,高位数据放高地址,低位数据放低地址 –小端对齐
main函数中定义普通变量如 int a = 9; int b = 10 时,int类型占的字节不是4,因为还要存放上下地址,两个int的地址不一定连续