C语言提高(下)
结构体数组排序
在上个程序基础上,在结构体中增加age变量,然后根据(Teacher的)年龄进行排序
定义结构体:
typedef struct Teacher
{
int age;
char **stu; //二维内存
}Teacher;
先给age赋值,然后比较age的大小。
注意是怎么交换的。
void sortArray(Teacher* q, int n)
{
int i, j;
Teacher tmp;
for (i = 0; i < n - 1; i++)
{
for (j = i + 1; j < n; j++)
{
if (q[i].age < q[j].age)
{
//降序
tmp = q[i];
q[i] = q[j];
q[j] = tmp;
}
}
}
}
主函数调用
sortArray(q,3);
结构体深拷贝和浅拷贝
浅拷贝
定义两个相同类型的结构体变量,给其中一个结构体赋值,该结构体中有指针类型的变量,因此需要在堆区开辟空间,赋值完成之后,直接将该结构体赋值给另一个结构体。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct Teacher
{
char* name;
int age;
}Teacher;
int main(int argc,char *argv[])
{
Teacher t1;
t1.name = (char*)malloc(30);
strcpy(t1.name, "Lili");
t1.age = 22;
Teacher t2;
t2 = t1;
printf("[t2]-name:%s、age = %d.\n", t2.name,t2.age);
printf("\n");
system("pause");
return 0;
}
画图分析:
从图中,可以看到赋值之后,两个指针指向了同一堆内存,这就是浅拷贝。
程序主体功能完成之后,需要释放堆内存空间,那我们是应该释放一次还是释放两次呢,如果程序量很大,是不是每一次的浅拷贝都需要释放。
对于C语言来说,只需要释放开辟空间的那一次就可以了。
if (t1.name!=NULL)
{
free(t1.name);
t1.name = NULL;
}
如果释放多次,是会报错的。
原因是:同一块内存只能释放一次。
深拷贝
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct Teacher
{
char* name;
int age;
}Teacher;
int main(int argc,char *argv[])
{
Teacher t1;
t1.name = (char*)malloc(30);
strcpy(t1.name, "Lili");
t1.age = 22;
Teacher t2;
t2 = t1;
//深拷贝:人为动态分配空间,再重新拷贝一次。
t2.name = (char*)malloc(30);
strcpy(t2.name, t1.name);
printf("[t2]-name:%s、age = %d.\n", t2.name,t2.age);
if (t1.name!=NULL)
{
free(t1.name);
t1.name = NULL;
}
//现在释放就没事了。
if (t2.name != NULL)
{
free(t2.name);
t2.name = NULL;
}
printf("\n");
system("pause");
return 0;
}
画图分析:
总结:浅拷贝和深拷贝对c语言影响不大。但是对c++会有影响。浅拷贝只需要释放开辟的那一次,而深拷贝则是开辟一次,释放一次。
结构体的偏移量
结构体类型定义下来,内部的成员变量的内存布局(各成员在内存中的偏移)已经确定。
验证
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//结构体类型定义下来,内部的成员变量的内存布局(各成员在内存中的偏移)已经确定
typedef struct Teacher
{
char name[64];//64
int age;//4
int id;//4
}Teacher;
int main(int argc,char *argv[])
{
Teacher t1;
Teacher* p = NULL;
p = &t1;
//测试下age的偏移量
//&(p->age),箭头的优先级高,加不加不影响。
int n1=(int)(&p->age)- (int)p;//相对于结构体首地址
printf("n1 = %d .", n1);
int n2 = (int)&((Teacher* )0)->age;//绝对0地址的偏移
printf("n2 = %d .", n2);
printf("\n");
system("pause");
return 0;
}
输出结果:
结构体的字节对齐
环境默认对齐单位
Windows 默认8字节对齐
Linux 32位 默认4字节对齐,64位默认8字节对齐
普通变量(含数组)类型
原则1:数据成员的对齐规则(以最大的类型字节为单位)。
结构体(struct)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存放在offset为该数据成员大小的整数倍的地方(比如int在32位机为4字节,则要从4的整数倍地址开始存储)
原则2:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。
例子1
先简单的举个例子:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(int argc,char *argv[])
{
//结构体类型定义下来,内部的成员变量的内存布局(各成员在内存中的偏移)已经确定
struct
{
int a; //int占4个字节
short b;//short占2个字节
}A;
//对齐单位为 4 个字节
//偏移量:
//a: 4 * 0 = 0
//b : 2 * 2 = 4
printf("sizeof(A) = %d .", sizeof(A));
printf("\n");
system("pause");
return 0;
}
分析:
输出结果:
例子2 结构体成员变量一样,但成员顺序不一样。
1、
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(int argc,char *argv[])
{
//结构体类型定义下来,内部的成员变量的内存布局(各成员在内存中的偏移)已经确定
struct
{
int a;
char b;
short c;
}A;
//对齐单位 4 个字节
//a: 4 * 0 = 0
//b: 1 * 4 = 4
//c: 2 * 3 = 6
printf("sizeof(A) = %d .", sizeof(A));
printf("\n");
system("pause");
return 0;
}
分析:
输出结果:
2、调整下结构体成员顺序
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(int argc,char *argv[])
{
//结构体类型定义下来,内部的成员变量的内存布局(各成员在内存中的偏移)已经确定
struct
{
char b;
int a;
short c;
}A;
//对齐单位 4 个字节
//b: 1 * 0 = 0
//a: 4 * 1 = 4
//c: 2 * 4 = 8
printf("sizeof(A) = %d .", sizeof(A));
printf("\n");
system("pause");
return 0;
}
分析:
输出结果:
例子3 结构体中带有数组成员。
分析起来一样。数组如char a[7],仍然按char所占字节来算。
比如当前结构体中最大字节数是int类型即四个字节,我的结构体中char a[7],1行不够我存的,我就再存一行。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(int argc,char *argv[])
{
//结构体类型定义下来,内部的成员变量的内存布局(各成员在内存中的偏移)已经确定
struct
{
int a;
char b[7];
short c;
}A;
//对齐单位 4 个字节
//a: 4 * 0 = 0
//b: 1 * 4 = 4
//c: 2 * 6 = 12
printf("sizeof(A) = %d .", sizeof(A));
printf("\n");
system("pause");
return 0;
}
输出结果:
结构体中嵌套结构体
保留上两个原则的基础上。
原则3:比较嵌套的结构体中最大数据类型与本结构体中所占字节最大的数据类型,取大的那个进行对齐。
原则4:如果一个结构体B中嵌套另一个结构体A,比较出两个结构体中所占字节最大的数据类型之后,然后按照该字节进行对齐。当结构体B中的其他成员按照原则1对齐之后,则结构体A应从最大对齐字节的整数倍的地方开始存储(之前普通成员没存满的也不存了)。对于结构体中的普通成员也遵循原则1进行存储。最后,要遵循原则3,结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。
例子4 结构体B中嵌套结构体A
结构体A是结构体B中最后一个成员变量
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(int argc,char *argv[])
{
struct A
{
int a;
double b;
float c;
};
struct
{
char e[2];
int f;
double g;
short h;
struct A;
}B;
//普通成员偏移量
//e: 1 * 0 = 0
//f: 4 * 1 = 4
//g: 8 * 1 = 8
//h: 2 * 8 = 16
//记录结构体起点坐标
// 8 * 3 = 24
//结构体成员偏移量
//a: 24 + 4 * 0 = 24
//b: 24 + 8 * 1 = 32
//c: 24 + 4 * 4 = 40
printf("sizeof(A) = %d .", sizeof(B));
printf("\n");
system("pause");
return 0;
}
输出结果:
结构体A不是结构体B中最后一个成员变量
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(int argc,char *argv[])
{
struct A
{
int a;
double b;
float c;
};
struct
{
char e[2];
int f;
double g;
short h;
struct A;
char d;
}B;
//普通成员偏移量
//e: 1 * 0 = 0
//f: 4 * 1 = 4
//g: 8 * 1 = 8
//h: 2 * 8 = 16
//记录结构体A起点坐标
// 8 * 3 = 24
//结构体成员偏移量
//a: 24 + 4 * 0 = 24
//b: 24 + 8 * 1 = 32
//c: 24 + 4 * 4 = 40
//记录结构体A结束后,普通变量的起点坐标
// 8 * 6 = 48
//d: 48 + 1 * 0 = 48
printf("sizeof(A) = %d .", sizeof(B));
printf("\n");
system("pause");
return 0;
}
输出结果:
指定结构体对齐单位
使用 #pragma pack(n) 来指定对齐单位
使用 #pragma pack(n) 来指定对齐单位,n取1、2 、4、8 、16
但不能超过结构体中最长那个成员。比如int类型是4字节,但我指定结构体对齐以2为单位。1行存不满再用一行来存。
当我指定结构体对齐以2为单位,对于int类型是4字节,1行存不满再用一行来存。
当指定对齐单位为1时,计算sizeof最好算,只需要将各变量的数据类型所占字节数直接相加即可。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(int argc,char *argv[])
{
#pragma pack(1)
struct
{
int d;
char a;
short c;
char b;
}B;
printf("sizeof(B) = %d .", sizeof(B));
printf("\n");
system("pause");
return 0;
}
输出结果:
使用 #pragma pack(n) 来指定对齐单位
参考链接: https://blog.csdn.net/u014717398/article/details/55511197.
GNU C的一大特色(却不被初学者所知)就是_ _ attribute _ 机制。 _ attribute _ _可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。
_ _ attribute _ _语法格式为:
_ _ attribute _ _((attribute-list))
其位置约束为:放于声明的尾部“;”之前。
packed是类型属性(Type Attribute)的一个参数,使用packed可以减小对象占用的空间。需要注意的是,attribute属性的效力与你的连接器也有关,如果你的连接器最大只支持16字节对齐,那么你此时定义32字节对齐也是无济于事的。
使用 packed 属性对 struct 或者 union 类型进行定义,设定其类型的每一个变量的内存约束。当用在enum类型定义时,暗示了应该使用最小完整的类型(it indicates that the smallest integral type should be used)。
下面的例子中,my-packed-struct类型的变量数组中的值会紧凑在一起,但内部的成员变量s不会被“pack”,如果希望内部的成员变量也被packed的话,my-unpacked-struct也需要使用packed进行相应的约束。
struct my_unpacked_struct
{
char c;
int i;
};
struct my_packed_struct
{
char c;
int i;
struct my_unpacked_struct s;
}__attribute__ ((__packed__));
内存对齐,往往是由编译器来做的,如果你使用的是gcc,可以在定义变量时,添加__attribute__,来决定是否使用内存对齐,或是内存对齐到几个字节,以上面的结构体为例:
1、指定 4 字节对齐,同样可指定对齐到8字节。
struct student
{
char name[7];
uint32_t id;
char subject[5];
} __attribute__ ((aligned(4)));
2、告诉编译器不对齐,结构体的长度,就是各个变量长度的和
struct student
{
char name[7];
uint32_t id;
char subject[5];
} __attribute__ ((packed));
结构体-位域
没用到过,用到时再仔细看。
链接: https://blog.csdn.net/xiaohangwj/article/details/51596115?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162764085916780255249990%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=162764085916780255249990&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_v2~rank_v29-1-51596115.pc_v2_rank_blog_default&utm_term=%E7%BB%93%E6%9E%84%E4%BD%93&spm=1018.2226.3001.4450.
文件
文件基本概念
要引入头文件 #include<stdio.h>
文件的分类
文件缓冲区
ANSI C标准的库函数才有文件缓冲区,系统调用是没有的,比如write、read等。
对于文件缓冲区,可以理解为操作系统给我们定义的一个数组,目的是提高效率。
举个例子,假如一个老师让A同学跑个10里地去送一样很轻的东西,A同学刚送完,该老师又让他送,如此反复,给A同学累的够呛,于是,A同学学聪明了,老师让他送东西,他不急着去送,而是等待给的差不多了,一次性去送。这样就提升了效率。
输出文件缓冲区
Linux 和 Windows 的输出文件缓冲区,可以通过刷新、关闭文件和退出可执行文件来将缓冲区中的内容输出到文件。有一种特殊情况也会,就是输出文件缓冲区满了。
下面通过关闭文件和退出可执行文件来验证文件缓冲区
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(int argc,char *argv[])
{
char buf[] = "this is a test.\n";
//定义一个文件指针
FILE* fp = fopen("./demo.text","w+");
fputs(buf,fp);
printf("\n");
system("pause");
return 0;
}
注意:上述程序没有关闭文件的过程,因为我没写close(fp);
可执行程序且暂时不关闭。
查看本地中的demo.txt文件。
说明没写进去。
关闭可执行程序或者在程序中写close(fp);再试试,
说明写进去了。
下面通过刷新函数 fflush 来验证文件缓冲区
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(int argc,char *argv[])
{
char buf[] = "this is a test.\n";
//定义一个文件指针
FILE* fp = fopen("./hello.text","w+");
fputs(buf,fp);
fflush(fp);
printf("\n");
system("pause");
return 0;
}
运行程序:
说明写进去了。
输入输出流
C语言中的文件指针
文件句柄
句柄实际上就是一个结构体变量。
文件api
标准的文件读写
需要注意:
1、
如果后缀加了“w”,如果文件存在,就会先把文件内容清空。所以,有时候想要读写文件时,使用了“rw”参数,但最后造成的结果是文件内容被清空。因此以后如果想要读写文件,可以使用“r+”,这样文件内容就不会被清空了。
2、
使用“rb”是用二进制文件,而使用“rt”是打开文本文件,“t”可省略。
3、
在Windows平台上,
打开文本文件后,
写文件时,会自动把 “\n” 转变为 “\r\n” ,
读文件时,会自动把 "\r\n"转变为 "\n"
而在Linux平台上则不会。写什么样就是什么样,读什么样也是什么样。因此,同一个文本文件,在windows打开结果和在Linux上打开结果可能不一样。
对于二进制文件,打开后,不管Linux平台还是windows平台,都是写什么样就是什么样,读什么样也是什么样。