C语言学习笔记
结构体
构造类型
- C语言中构造类型分为数组,结构体,联合和枚举四种类型,其中,结构体是机多种数据类型于一身的一种构造类型。
声明结构体类型
- 通常在主函数外部进行声明(全局区)
struct Stu
{
char name[10];
int age;
double high;
char num[20];
};
- 注意点:第一行中的struct后面的Stu是结构体名;大括号内的是结构体成员,其定义方式和主函数中变量的定义方式完全一样;在最后一定要记得带上分号。
- 名字可以不用写,但是这样会在定义变量时受限。
声明结构体变量
- 顺带声明:在结构体声明的最后的分号前加stu1,stu2这样的变量名,不需要写结构体的名字。
- 利用结构体名来声明变量:
struct Stu stu3;
,struct Stu stu1,stu2,stu3;
这样的语句中struct Stu
就可以理解成变量声明前的int,double等。
结构体变量的初始化
- 初始化的过程与正常的变量初始化十分类似,可以在第一种声明方式后面直接
sut1 = {"Lancibe",18,185.0,"05191256"};
- 或者在任何其他的地方,在结构体变量定义之后直接声明:
struct Stu stu1 = {"Lancibe",18,185.0,"05191256"};
- 注意点:全局变量如果不进行初始化,系统默认全为0,局部变量则会被系统声明为随机的乱码
- 指针变量的申请:
struct Stu *p = &stu1;
结构体取成员运算符
- 取成员运算符
.
,->
- 访问成员分为两种:实例变量的访问和指针变量的访问。
- 对于实例变量的访问:
stu1.age
这样这一条语句就和int定义的a
作用完全一致。打印:printf("%d,%s",stu1.age,stu1.name);
- 使用前面声明的指针变量访问:
printf("%d,%s",p->age,p->name);
。注意结构体指针一定要指向具体的第一段空间。 - 使用结构体指针指向堆区空间:
struct Stu *q = (struct Stu*)malloc(sizeof(struct Stu));
- 这里的指针指向的不同数据类型要有不同的赋值方式:
q->age = 18;
(*q).high = 190.1;
(&stu1)->age = 20;
strcpy(q->name , "Lancibe");
- 第一行和第二行,第三行的方式意义相同,*q就是变量本身,也就是一个实例变量,因此要使用
.
,第三行中,这是改变了前面的栈区指针指向的值,也就是指针那一章学到的*
,&
的操作,第四行中之所以用这样的方式给字符串赋值,是因为字符串是一个常量,初始化之后不能被改变,因此这样为其赋值。 - 注意点:在使用指针时,要注意是堆区指针还是栈区指针,两种指针的操作、用法完全不一样。
结构体成员赋值
- 字符数组尽量用
strcpy(stu3.name,"hello!")
,或者就使用循环。 - C99版本新加入的对于结构体变量的赋值(复合文字结构方式):
stu1 = (struct Stu){19,"aaa",180.21};
,如果使用这种方式,其他未进行赋值的默认是0,如果只想赋值某个元素,应使用struct Stu stu1 = {.age = 24};
指针成员和函数成员
- 指针一定要有指向,即使是指向NULL,否则无法进行调用。
struct Stu
{
int *p;
};
int a[5] = {1,2,3,4,5};
struct Stu st = {a};
printf("%d,%d\n",st.p[0],st.p[3]);
- 这样的指针就是指向了一个栈区数组,结果是1,4
struct Stu
{
int *p;
};
struct Stu st = {(int *)malloc(20)};
st.p[0] = 15;
st.p[1] = 12;
printf("%d,%d\n",st.p[0],st.p[1]);
- 这样的指针就是指向了一个堆区数组,结果就是15,12。
void fun(void)
{
printf("i am fun\n");
}
struct Stu
{
void (*p)(void);
};
int main()
{
struct Stu st = {fun};
(st.p)();
}
- 这样通过一个指针,巧妙地把函数·指针·结构体结合在了一起,实现了函数作为结构体成员。
结构体嵌套
- 某个结构体中的一个成员是另一个结构体变量
struct Teach
{
char name[10];
struct Stu st;
int tea_age;
};
- 这样定义的结构体中就包含了另一个结构体
- 结构体变量声明:
struct Teach te = {"lancibe",{"迅",18},19}
这样就完成了结构体嵌套变量的初始化
中间的花括号可加可不加,但是加上了之后明显更易理解。 - 输出的时候
printf("%s,%s\n",te.st.name,te.st.age);
- 尽量不要把结构体真的写在另一个结构体里面,这样可能会报错,得不到想要的结果。他会限制我们的初始化形式。
结构体数组
- 定义的方式既参照了结构体变量,又参照了数组
struct teach
{
char name[10];
int age;
}tea[3] = {{"LAN",26},{"Lancibe",18},{"ll",12}};
- 注意如果内部的花括号不加,顺序一定不要错,比如字符串一定要对应字符串,整形一定要对应整形……
- 如果初始化了部分元素,其他的也会默认为0,这是数组的特性。
struct teach tea[3] = {{"LAN",26},{"Lancibe"},{"ll",12}};
这样Lancibe
的那一项的age就默认为0。 - 这里也能用复合文字结构来进行赋值操作
tea[0] = (struct teach){"asda",23};
,注意老版本编译器不会支持此种方式。
内存对齐
- CPU处理数据:32位中央处理器一次能处理的数据是32bit位,占4字节,特点是不多不少,不进不退。64位中央处理器一次能处理的数据是64bit位,占8字节。起始的地址全是偶数,因为从0开始,+4或者+8。假设数据挨着存储,
char c,int a;
或者跨字节,CPU要读两次,把两次合成一个数据,效率就会降低。因此实际上,CPU会进行四字节的内存对齐,将数据全存在一个四字节中。 - 对齐是数据的存储规则,这种数据CPU只需要读一次,就得到了。这种存储方式叫做内存对齐,执行效率大大提高,但是会浪费一点空间。
- 对于
char a;short s;
他们两个加起来还不到四字节,就可以直接存放在一个四字节就可以了,因为一下就能读出来。 - 空间换时间,时间换空间,二者不可兼得。
结构体大小的计算规则
- sizeof()求大小:不是简单的所有成员大小之和。要涉及到内存对齐。
- 例如下列结构体:
struct Stu
{
char c;
int i;
double d;
short s;
};
- 使用sizeof测试:
printf("%u",sizeof(struct Stu));
结果是24。 - 计算规则:以最大类型为字节对齐宽度;依次填补各个成员字节;结尾补齐。如果出现了数组,则不予考虑,考虑的只有基本数据类型。
联合
- 关键词:
union
。 - 联合的定义和结构体十分类似,它的特点是所有成员共享一块内存。
union Un
{
char c;
short s;
int i;
}un1;
- 这样就完成了一个联合的声明和定义,以及一个联合变量的声明。
- 所有成员共享一块内存,其内存模型如下:
- 可以通过
printf("%p\n%p\n%p",&un1.c,&un1.s,&un1.i);
来验证,发现他们三个的首地址是一样的。 - 联合变量的声明及初始化:类似于数组,但是只能有一个内容,超过一个编译器会报错。
union Un un2 = {.i = 34};
这样声明比较合适。这是因为内存判定的原因。所以为避免麻烦,一般将所占字节数最大的成员置于首位。 - 在声明后再进行对其数据的修改时要注意字节数是否超过了其最大可容纳的数据,例如
char
类型的变量,其可容纳的数据范围是0~257
也就是一字节,如果数据越界,会被它截取后面的一部分数据,顺序是从右至左。
联合的大小
- 仍然使用
printf("%d\n",sizeof(un1));
来测试联合的内存大小,其实不用多想,我们很容易能想到答案一定是4,也就是int所占的字节数。正常情况下都是最大的数据类型所占字节数,但是如果出现了数组,如下:
union Un
{
int i;
char c[5];
short s;
};
- 经测试,这样的字节数是8,也就是说发生了内存对齐,对齐的格式是所占字节数最大的数组类型所占的字节数(b)*N,这个N即为多少个数组所占字节数(a)在公式
a-b*N<=0
下的最小值。
枚举
- 关键词
enum
- 声明代表整数常量的名称,通俗的来说就是给整数起个名字。
- 其意义是大大提高了代码的可读性,★不是变量,而仅仅是名字。
- 例如这一条语句:
enum Color{red,black,white,blue,yellow};
相当于是int Color
这样,red->0,black->1,white->2,blue->3,yellow->4
我们通常会将里面的数据写成类似于enum_red
的形式,便于读者理解;Color在这里面没有功能,可以忽略。 - 枚举的本质是一组有名字的正整数。
- 枚举的范围可以指定,而不仅仅是从0开始(从0开始时默认情况)
enum Color{red,black,white = 12,blue,yellow};
结果分别就是0,1,12,13,14
enum Color{red = -3,black,white,blue,yellow};
结果就是-3,-2,-1,0,1
- 一定要注意枚举的大小都是4,因为其本质就是int