嵌入式c——学习笔记4
运用结构体指针输出结构体中变量
#include<stdio.h>
struct Data{
int year;
int mounth;
int day;
}data;
int main(){
Data data={2020,11,7};
struct Data *ptr;
ptr=&data;
printf("%d",ptr->year);
printf("-%d",data.mounth);
printf("-%d",ptr->day);
return 0;
}
一、结构体变量和结构体指针变量作为函数参数传递问题
#include <stdio.h>
#include <string.h>
struct Student {
int age;
char sex;
char name[100];
};
void InputStudent(struct Student * pstu) { //pstu只占四个字节
(*pstu).age = 10;
strcpy_s(pstu->name, "张三");
pstu->sex = 'F';
}
void OutStudent(struct Student ss) {
printf("%d %c %s", ss.age, ss.sex, ss.name);
}
int main(void) {
struct Student st;
InputStudent(&st);
printf("%d %c %s\n",st.age,st.sex,st.name);
OutStudent(st);
while (true){}
}
void OutStudent(struct Student ss) {
printf("%d %c %s", ss.age, ss.sex, ss.name);
}
此处传递的是一个变量,此变量占的字节空间大,我们可以利用指针,指针只占四个字节空间,而且只存变量st的第一个字节地址,然而指针指向的是整个变量。因为指针前面的类型是struct Student代表的是整个变量。修改为指针后速度变快,占的内存空间也减小:
#include <stdio.h>
#include <string.h>
struct Student {
int age;
char sex;
char name[100];
};
void InputStudent(struct Student * pstu) { //pstu只占四个字节
(*pstu).age = 10;
strcpy_s(pstu->name, "张三");
pstu->sex = 'F';
}
void OutStudent(struct Student * stu) {
printf("%d %c %s", stu->age, stu->sex, stu->name);
}
int main(void) {
struct Student st;
InputStudent(&st);
printf("%d %c %s\n",st.age,st.sex,st.name);
OutStudent(&st);
while (true){}
}
二、文件的包含问题
#include操作是:若后面带的是<>,则文件在安装路径中找;
若后面带的是“”,则文件在源目录中找。
三、大小端和字节序
大端字节序:把一个数的低位字节序的内容存储到高地址处,高位字节序的内容存储到低地址处
小端字节序:把一个数的低位字节序的内容存储到低地址处,高位字节序的内容存储到高地址处。
大小字节序的判断:
方法一:
#include <stdio.h>
int main (int argc, char **argv)
{
unsigned int a = 0x12345678;
unsigned char *c = (unsigned char *)&a;
if(*c == 0x78)
printf("Little endian\n");
else if(*c == 0x12)
printf("Big endian\n");
else
printf("Not know");
return 0;
}
方法二:
#include <stdio.h>
union u_is_lsb
{
unsigned int a;
unsigned char b;
}is_lsb;
int main (int argc, char **argv)
{
is_lsb.a = 0x12345678;
if(is_lsb.b == 0x78)
printf("Little endian!\n");
else if(is_lsb.b == 0x12)
printf("Big endian!\n");
else
printf("Not know!");
return 0;
}
四、位域
在结构体定义时,我们可以指定某个成员变量所占用的二进制位数(Bit),这就是位域。
例如:
struct bs{
unsigned m;
unsigned n: 4;
unsigned char ch: 6;
};
:
后面的数字用来限定成员变量占用的位数。成员 m 没有限制,根据数据类型即可推算出它占用 4 个字节(Byte)的内存。成员 n、ch 被:后面的数字限制,不能再根据数据类型计算长度,它们分别占用 4、6 位(Bit)的内存。
n、ch 的取值范围非常有限,数据稍微大些就会发生溢出
例如:
#include <stdio.h>
int main(){
struct bs{
unsigned m;
unsigned n: 4;
unsigned char ch: 6;
} a = { 0xad, 0xE, '$'};
//第一次输出
printf("%#x, %#x, %c\n", a.m, a.n, a.ch);
//更改值后再次输出
a.m = 0xb8901c;
a.n = 0x2d;
a.ch = 'z';
printf("%#x, %#x, %c\n", a.m, a.n, a.ch);
system("pause");
return 0;
}
对于 n 和 ch,第一次输出的数据是完整的,第二次输出的数据是残缺的。
C语言标准规定,位域的宽度不能超过它所依附的数据类型的长度。通俗地讲,成员变量都是有类型的,这个类型限制了成员变量的最大长度,:后面的数字不能超过这个长度。
C语言标准还规定,只有有限的几种数据类型可以用于位域。在 ANSI C 中,这几种数据类型是 int、signed int 和 unsigned int(int 默认就是 signed int);到了 C99,_Bool 也被支持了。
位域的存储
位域的存储规则:
1、当相邻成员的类型相同时,如果它们的位宽之和小于类型的 sizeof 大小,那么后面的成员紧邻前一个成员存储,直到不能容纳为止;如果它们的位宽之和大于类型的 sizeof 大小,那么后面的成员将从新的存储单元开始,其偏移量为类型大小的整数倍。
例如位域bs:
#include <stdio.h>
int main(){
struct bs{
unsigned m: 6;
unsigned n: 12;
unsigned p: 4;
};
printf("%d\n", sizeof(struct bs));
return 0;
}
m、n、p 的类型都是 unsigned int,sizeof 的结果为 4 个字节(Byte),也即 32 个位(Bit)。m、n、p 的位宽之和为6+12+4 = 22
,小于 32,所以它们会挨着存储,中间没有缝隙。
sizeof(struct bs)
的大小之所以为 4,而不是 3,是因为要将内存对齐到 4 个字节,以便提高存取效率。
如果将成员 m 的位宽改为 22,那么输出结果将会是 8,因为22+12 = 34
,大于 32,n 会从新的位置开始存储,相对 m 的偏移量是 sizeof(unsigned int)
,也即 4 个字节。
如果再将成员 p 的位宽也改为 22,那么输出结果将会是 12,三个成员都不会挨着存储。
2、当相邻成员的类型不同时,不同的编译器有不同的实现方案,GCC 会压缩存储,而 VC/VS 不会。
例如位域bs:
#include <stdio.h>
int main(){
struct bs{
unsigned m: 12;
unsigned char ch: 4;
unsigned p: 4;
};
printf("%d\n", sizeof(struct bs));
return 0;
}
在 GCC 下的运行结果为 4,三个成员挨着存储;在 VC/VS 下的运行结果为 12,三个成员按照各自的类型存储(与不指定位宽时的存储方式相同)。
m 、ch、p 的长度分别是 4、1、4 个字节,共计占用 9 个字节内存,为什么在 VC/VS 下的输出结果却是 12 呢?期待您的回复。
3、如果成员之间穿插着非位域成员,那么不会进行压缩。
例如位域bs:
struct bs{
unsigned m: 12;
unsigned ch;
unsigned p: 4;
};
在各个编译器下 sizeof 的结果都是 12。
总结:位域成员往往不占用完整的字节,有时候也不处于字节的开头位置,因此使用&获取位域成员的地址是没有意义的,C语言也禁止这样做。地址是字节(Byte)的编号,而不是位(Bit)的编号。
五、函数指针
1、类型 (*指针名)(参数1,参数2...); 如:int(*p)(int ,float);
例如:
#include<stdio.h>
int func1(int a, float b)
{
return a + b;
}
int func2(int c, float d)
{
return c + d;
}
int (*p)(int,float);
int main()
{
int k;
p = func1;
k = p(10, 30.0);
printf("k = %d\n", k);
return 0;
}
2、函数指针当参数,实现回调函数的调用方式
#include<stdio.h>
int func1(int a, float b)
{
return a + b;
}
int func2(int c, float d)
{
return c + d;
}
int (*p)(int,float);
int test(int (*p)(int, float))
{
int k = 0;
k = p(10,30.0);
printf("k = %d\n",k);
return 0;
}
int main()
{
test(func1);
return 0;
}
函数指针的运用:函数指针可以使代码更清晰
int i; // 定义了一个int类型的变量i;
typedef INT int; // 表示用户自己定义了一个整型数据类型INT,实际上就等同于int 所以:INT ii;
// 表示定义了一个int类型的变量ii;
void (*pFn)(void) // 定义了一个函数指针,该函数指针指向类似于void Foo(void)函数的函数入口地址
typedef void (*Fun)(void)
// 表示用户自己定义了一个函数指针数据类型
Fun pf; // 表示定义了一个函数指针pf,改函数指针指向类似于void *pf(void)的函数
char *a="This is ";// 这个在常量区分配一个空间,然后a指向此空间
char a[] = "This is";
// 这个在常量区分配一个空间,然后又在栈上分配一个空间,将常量区的内容复制过来,所以可以修改
//比如你有三个函数:
void hello(void) { printf("你好!"); }
void bye(void) { printf("再见!"); }
void ok(void) { printf("好的!"); }
/* //定义一个函数指针类型/// */
typdef void (*funcptr)(void);
// 这样就构造了一个通用的函数;你用的时候可以这样:
void speak(int id)
{
funcptr words[3] = {&hello, &bye, &ok};
funcptr fun = words[id];
(*fun)();
}
这时候在工作直接调用这个speak()函数接口就可以实现相似的功能。
speak(0)就会显示“你好!”
speak(1)就会显示“再见!”
speak(2)就会显示“好的!”
用于处理参数和返回值的形式都一样,但是功能不确定的一组函数,可以使用函数指针
比如算术运算符,加、减、乘、除,都可以用typedef int (*calc)(int,int)代表,等等