遵循结构体字节对齐原则
在实际使用中,访问特定数据类型变量时需要在特定的内存起始地址进行访问,这就需要各种数据类型按照一定的规则在空间上进行排列,而不是顺序地一个接一个地存放,这就是字节对齐
a.在32位系统下会有一个默认value值大小为4字节,和结构体成员中类型最大的字节大小进行比较,按照小的数进行对齐(每次开辟的空间大小)
b.结构体成员进行对齐时遵循地址偏移量是成员类型大小的整数倍,double类型数据存放在4字节的整数倍上
c.结构体成员按顺序进行存储,在满足以上条件时,需要填充空字节
d.平台原因:不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。(提高程序的移植性)
e.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
f.内存原因:在设计结构体时,通过对齐规则尽可能优化结构体的空间大小至最小空间
不同数据类型的数据可以使用共同的存储区域,这种数据构造类型称为共用体,简称共用,又称联合体。
union 共用体名
{
成员列表;
};
注意:
1. 共用体成员在内存中使用共同的存储空间。由于共用体中各成员的数据长度往往不同,所以共用体变量在存储时总是按其成员中数据长度最大的成员占用内存空间。
2. 在共用体类型变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员就失去作用
枚举是C语言中的一种构造数据类型,它可以用于声明一组常数。当一个变量有几个固定的可能取值时,可以将这个变量定义为枚举类型。比如,你可以用一个枚举类型的变量来表示季节,因为季节只有4种可能的取值:春天、夏天、秋天、冬天。
一个星期只有七天,一年只有十二个月,一个班每周有六门课程等
enum 枚举类型名
{
valueName1,
valueName2,
valueName3,
......
};
1)枚举值默认从 0 开始,往后逐个加 1(递增)
enum week{ Mon, Tues, Wed, Thurs, Fri, Sat, Sun };
2)也可以给每个名字都指定一个值:
enum week{ Mon = 1, Tues = 2, Wed = 3, Thurs = 4, Fri = 5, Sat = 6, Sun = 7 };
3)简单的方法是只给第一个名字指定值:
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun };
4)枚举是一种类型,通过它可以定义枚举变量
enum week a, b, c;
5)有了枚举变量,就可以把列表中的值赋给它
enum week a = Mon, b = Wed, c = Sat
6)枚举列表中的 Mon、Tues、Wed 这些标识符的作用范围是全局的,不能再定义与它们名字相同的变量。
7)Mon、Tues、Wed 等都是常量,不能对它们赋值,只能将它们的值赋给其他的变量。
本质是函数,返回值为指针
格式:数据类型 *函数名(形参列表)
{
函数体
return 指针/地址; //return NULL; 一般表示返回失败的情况
}
实例:
char *fun()
{
//buf是局部的,函数调用结束空间释放,自己写程序时不可以这样写
//char buf[32] = "hello";
//想要实现在函数外能拿到hello字符串,方式如下:
//方式一:
//char *buf = "hello"; //"hello"存在常量区
//方式二:
char *buf = (char *)malloc(32); //在堆区开辟空间
strcpy(buf, "hello");
return buf;
}
int main(int argc, char const *argv[])
{
char *p = fun();
printf("%s\n", p);
free(p);
p = NULL;
return 0;
}
封装一个函数,获取堆区空间的首地址。
//方式一:通过函数的返回值拿到堆区空间首地址
// char *getmemory(int n)
// {
// char *p = (char *)malloc(n); //成功:首地址,失败:NULL
// if(p == NULL)
// return NULL;
// return p;
// }
//方式二:通过函数参数拿到堆区空间首地址
void getmemory(char **p, int n)
{
*p = (char *)malloc(n);
}
int main(int argc, char const *argv[])
{
// char *m = getmemory(32);
char *m = NULL;
getmemory(&m, 32);
strcpy(m, "helloworld");
printf("%s\n", m);
return 0;
}
本质是指针,指针指向函数的指针
数据类型 (*指针名)(形参列表);
数据类型:指向的函数的返回值类型
形参列表:和指向的函数的参数一致,此处形参列表可以只保留数据类型,变量名可以省略,如:int (*p)(int, int);
int add(int a, int b)
{
return a+b;
}
int sub(int a, int b)
{
return b-a;
}
int main(int argc, char const *argv[])
{
//函数指针
int (*p)(int, int) = add; //函数名代表函数的入口地址
p = sub; //改变指针的指向,指向sub函数
printf("%d\n", add(3, 4));
printf("%d\n", p(3, 4));
return 0;
}
int sub(int a, int b)
{ return b-a;
}
int add(int a, int b)
{ return a+b;
}
//函数指针作为函数参数
int test(int a, int b,int (*p)(int, int))
{
return a+b+p(1, 3);
}
int main(int argc, char const *argv[])
{
//可以给test函数传递不同的函数,实现代码的复用
printf("%d\n", test(10, 20, add));
printf("%d\n", test(10, 20, sub));
return 0;
}
注意:后面学习中常见的使用流程:
系统给提供包含函数指针的函数接口,用户调用此函数,并自定义函数给函数指针传参
比如在线程创建时的函数pthread_create函数
本质是数组,数组中存放函数指针(指向函数的指针)
数据类型 (*数组名[元素个数])(形参列表);
如:int (*arr[3])(int, int) = {函数名};
int add(int a, int b)
{
return a+b;
}
int sub(int a, int b)
{
return a-b;
}
int mul(int a, int b)
{
return a*b;
}
int main(int argc, char const *argv[])
{
//函数指针数组
// 定义时赋初值
// int (*arr[3])(int, int) = {add, sub, mul};
// 定义时未赋初值,需要单个元素赋值
int (*arr[3])(int, int);
arr[0] = add;
arr[1] = sub;
arr[2] = mul;
for(int i = 0; i < 3; i++)
printf("%d\n", arr[i](3, 4));
return 0;
}