嵌入式C学习第四次任务

1.结构体的参数传递:

结构体做函数参数有三种传递方式:
一是传递结构体变量,这是值传递,二是传递结构体指针,这是地址传递,三是传递结构体成员,当然这也分为值传递和地址传递。

以传引用调用方式传递结构比用传值方式传递结构效率高。以传值方式传递结构需要对整个结构做一份拷贝。

下面看一个列子,student结构体中包含该学生的各种信息,我们在change函数中对其进行部分修改,再在主函数中输出其结果

1.下面传递结构体变量

#include<stdio.h>
#include<string.h>
#define format "%d\n%s\n%f\n%f\n%f\n"
struct student
{
    int num;
    char name[20];
    float score[3];
};
void change( struct student stu );
int main()
{
    
    struct student stu;
    stu.num = 12345;
    strcpy(stu.name, "Tom");
    stu.score[0] = 67.5;
    stu.score[1] = 89;
    stu.score[2] = 78.6;
    change(stu);
    printf(format, stu.num, stu.name, stu.score[0], stu.score[1],stu.score[2]);
    printf("\n");
    return 0;
}
 
void change(struct student stu)
{
    stu.score[0] = 100;
    strcpy(stu.name, "jerry");
}

2.地址传递

#include<stdio.h>
#define format "%d\n%s\n%f\n%f\n%f\n"
struct student
{
    int num;
    char name[20];
    float score[3];
};
void change( struct student* stu );
int main()
{
    
    struct student stu;
    stu.num = 12345;
    strcpy(stu.name, "Tom");
    stu.score[0] = 67.5;
    stu.score[1] = 89;
    stu.score[2] = 78.6;
    change(&stu);
    printf(format, stu.num, stu.name, stu.score[0], stu.score[1],stu.score[2]);
    printf("\n");
    return 0;
}
 
void change(struct student* p)
{
    p->score[0] = 100;
    strcpy(p->name, "jerry");
}

可以看到,通过地址传递修改了结构体内的数据

用&stu做实参,&stu是结构体变量stu的地址。在调用函数时将该地址传送给形参p(p是指针变量)。这样p就指向stu。

在change函数中改变结构体内成员的值,在主函数中就输出了改变后的值

3.结构体成员的地址传递和值传递

这个类似于单一变量的传递,当然是地址传递才能修改。

把一个完整的结构体变量作为参数传递,要将全部成员值一个一个传递,费时间又费空间,开销大。如果结构体类型中的成员很多,或有一些成员是数组,则程序运行效率会大大降低。在这种情况下,用指针做函数参数比较好,能提高运行效率。

原文地址:https://blog.csdn.net/lin37985/article/details/38582027

2.文件的包含:

在C语言中文件包含是指一个源文件可以将另一个源文件的全部内容包含进来。该命令的作用是在预编译时,将指定源文件的内容复制到当前文件中。文件包含是C语言预处理命令三个内容之一。

一个大程序,通常分为多个模块,并由多个程序员分别编程。有了文件包含处理功能,就可以将多个模块共用的数据(如符号常量和数据结构)或函数,集中到一个单独的文件中。这样,凡是要使用其中数据或调用其中函数的程序员,只要使用文件包含处理功能,将所需文件包含进来即可,不必再重复定义它们,从而减少重复劳动和定义不一致造成的错误。

文件包含的特点:

① 编译预处理时,预处理程序将查找指定的被包含文件,并将其复制插入到#include命令出现的位置上

② 常用在文件头部的被包含文件,称为"标题文件"或"头部文件",常以"h"(head)作为后缀,简称头文件。在头文件中,除可包含宏定义外,还可包含外部变量定义、结构类型定义等。

③ 一条包含命令,只能指定一个被包含文件。如果要包含多个文件,则要用多条包含命令。例如,文件f1.h中要使用到文件f2.h和文件f3.h的内容,则可在文件f1.h中用两个文件包含命令分别包含文件f2.h和文件f3.h,即在文件f1.h中定义:

#include "f2.h"

#include "f3.h"

在使用多个#include命令时,顺序是一个值得注意的问题。上例中,如果文件f1.h包含文件f2.h,而文件2要用到文件f3.h,则在f1.h中#include定义的顺序应该是:

#include "f3.h"

#include "f2.h"

这样文件f1.c和文件f2.h都可以使用文件f3.h的内容。

④ 文件包含可以嵌套,即被包含文件中又包含另一个文件。例如,文件f2.h中要使用到文件f1.h的内容,文件f3.h要使用到文件f2.h的内容,则可在文件f2.h中用#include "f1.h"命令,在文件f3.h中用#include "f2.h"命令,即定义如下:

文件f1.h:

{

… …

}

文件f2.h:

#include "f1.h"

int max()

{

… …

}

文件f3.h:

#include "f2.h"

main

{

… …

}

#include命令一般用来把C语言提供的标准库头文件(如stdio.h、math.h)包含到程序中。程序员也可以自己定义一个头文件,写入一些常用的函数原型、宏定义、结构和联合类型定义等,然后将它包含到程序中。例如:#include "stdio.h" (标准输入/输出函数库)

#include "math.h" (数学函数库)

#include "stdlib.h" (常用函数库)

#include "string.h" (字符串处理函数库)

3.大小端和字节序:

一、概念

字节序,就是 大于一个字节类型的数据在内存中的存放顺序。是在跨平台和网络编程中,时常要考虑的问题。

二、分类

字节序经常被分为两类:

1. Big-Endian(大端):高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

2.Little-Endian(小端):低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

三、高低地址与高低字节

高低地址:

C程序映射中内存的空间布局大致如下:


最高内存地址 0xFFFFFFFF

栈区(从高内存地址,往 低内存地址发展。即栈底在高地址,栈顶在低地址)

堆区(从低内存地址 ,往 高内存地址发展)

全局区(常量和全局变量)

代码区

最低内存地址 0x00000000

四、例子

对于数据 0x12345678,假设从地址0x4000开始存放,在大端和小端模式下,存放的位置分别为:

采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节,而Big-endian模式对操作数的存放方式是从高字节到低字节。

小端存储后:0x78563412  大端存储后:0x12345678

五、代码判断当前机器是大端还是小端

void byteorder()
{
	union
	{
		short value;
		char union_bytes[sizeof(short)];
	}test;
	test.value = 0x0102;
 
	if (sizeof(short) == 2)
	{
		if (test.union_bytes[0] == 1 && test.union_bytes[1] == 2)
			cout << "big endian" << endl;
		else if (test.union_bytes[0] == 2 && test.union_bytes[1] == 1)
			cout << "little endian" << endl;
		else
			cout << "unknown" << endl;
	}
	else
	{
		cout << "sizeof(short) == " << sizeof(short) << endl;
	}
 
	return ;
}

 

4.位域:

有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几 个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。位段成员必须声明为int、unsigned int或signed int类型(short char long)。

一、位域的定义和位域变量的说明位域定义与结构定义相仿,其形式为:  

struct 位域结构名     
{ 位域列表 }; 

其中位域列表的形式为: 类型说明符 位域名:位域长度     
例如:     

struct bs     
{     
int a:8;     
int b:2;     
int c:6;     
};  

位域变量的说明与结构变量说明的方式相同。 可采用先定义后说明,同时定义说明或者直接说明这三种方式。例如:     

  View Code

说明data为bs变量,共占两个字节。其中位域a占8位,位域b占2位,位域c占6位。对于位域的定义尚有以下几点说明:     

1. 如果一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:     

struct bs     
{     
unsigned a:4     
unsigned :0 /*空域*/     
unsigned b:4 /*从下一单元开始存放*/     
unsigned c:4     
}   

这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从第二字节开始,占用4位,c占用4位。     

2.位域的长度不能大于数据类型本身的长度,比如int类型就能超过32位二进位。

3. 位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:     

struct k     
{     
int a:1     
int :2 /*该2位不能使用*/     
int b:3     
int c:2     
};  

从以上分析可以看出,位域在本质上就是一种结构类型, 不过其成员是按二进位分配的。     

二、位域的使用位域的使用和结构成员的使用相同,其一般形式为: 位域变量名.位域名 位域允许用各种格式输出。     

struct bs     
{     
    unsigned a:1;     
    unsigned b:3;     
    unsigned c:4;     
} bit,*pbit;     
bit.a=1;     
bit.b=7; //注意:位域的赋值不能超过该域所能表示的最大值,如b只有3位,能表示的最大数为7,若赋为8,就会出错   
bit.c=15;

printf("%d,%d,%d/n",bit.a,bit.b,bit.c);
pbit=&bit;
pbit->a=0;
pbit->b&=3;
pbit->c=1;
printf("%d,%d,%d/n",pbit->a,pbit->b,pbit->c);

上例程序中定义了位域结构bs,三个位域为a,b,c。说明了bs类型的变量bit和指向bs类型的指针变量pbit。这表示位域也是可以使用指针的。 

程序的9、10、11三行分别给三个位域赋值。( 应注重赋值不能超过该位域的答应范围)程序第12行以整型量格式输出三个域的内容。第13行把位域变量bit的地址送给指针变量pbit。第14行用指针 方式给位域a重新赋值,赋为0。第15行使用了复合的位运算符"&=", 该行相当于: pbit->b=pbit->b&3位域b中原有值为7,与3作按位与运算的结果为3(111&011=011,十进制值为 3)。同样,程序第16行中使用了复合位运算"=", 相当于: pbit->c=pbit->c1其结果为15。程序第17行用指针方式输出了这三个域的值。 

我们再来看看下面两个结构体定义:

struct foo2 {
char    a : 2;
char    b : 3;
char    c : 1;
};

struct foo3 {
char    a : 2;
char    b : 3;
char    c : 7;
};

我们来打印一下这两个结构体的大小,我们得到的结果是:
sizeof(struct foo2) = 1
sizeof(struct foo3) = 2
显然都不是我们期望的,如果按照正常的内存对齐规则, 这两个结构体大小均应该为3才对,那么问题出在哪了呢?首先通过这种现象我们可以肯定的是:带有'位域'的结构体并不是按照每个域对齐的,而是将一些位域 成员'捆绑'在一起做对齐的。以foo2为例,这个结构体中所有的成员都是char型的,而且三个位域占用的总空间为6 bit < 8 bit(1 byte),这时编译器会将这三个成员'捆绑'在一起做对齐,并且以最小空间作代价,这就是为什么我们得到sizeof(struct foo2) = 1这样的结果的原因了。再看看foo3这个结构体,同foo2一样,三个成员类型也都是char型,但是三个成员位域所占空间之和为9 bit > 8 bit(1 byte),这里位域是不能跨越两个成员基本类型空间的,这时编译器将a和b两个成员'捆绑'按照char做对齐,而c单独拿出来以char类型做对齐, 这样实际上在b和c之间出现了空隙,但这也是最节省空间的方法了。我们再看一种结构体定义:

struct foo4 {
char    a : 2;
char    b : 3;
int c : 1;
};

在foo4中虽然三个位域所占用空间之和为6 bit < 8 bit(1 byte),但是由于char和int的对齐系数是不同的,是不能捆绑在一起,那是不是a、b捆绑在一起按照char对齐,c单独按照int对齐呢?我们 打印一下sizeof(struct foo4)发现结果为8,也就是说编译器把a、b、c一起捆绑起来并以int做对齐了。就是说不够一个类型的size时,将按其中最大的那个类型对齐。此 处按int对齐。


C99规定int、unsigned int和bool可以作为位域类型,但编译器几乎都对此作了扩展,
允许其它类型类型的存在。
使用位域的主要目的是压缩存储,其大致规则为:

1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止
2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++,GCC采取压缩方式;
4) 如果位域字段之间穿插着非位域字段,则不进行压缩;
5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。
struct s1 
{ 
int i: 8; 
int j: 4; 
int a: 3; 
double b; 
}; 

struct s2 
{ 
int i: 8; 
int j: 4; 
double b; 
int a:3; 
}; 

printf("sizeof(s1)= %d/n", sizeof(s1)); 
printf("sizeof(s2)= %d/n", sizeof(s2)); 
result: 16, 24 

第一个结构体中,i,j,a共占15个位,不足8个字节,按double 8字节对齐,共16字节

第二个结构体中,i,j共占12位,不足8字节,按8字节对齐,a也按8字节对齐,加上double共8+8+8=24个字节

5.函数指针:

函数指针是指向函数的指针变量。 因此"函数指针"本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。如前所述,C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是大体一致的。函数指针有两个用途:调用函数和做函数的参数

函数指针的声明方法为:

返回值类型 ( * 指针变量名) ([形参列表]);

注1:"返回值类型"说明函数的返回类型,"(指针变量名 )"中的括号不能省,括号改变了运算符的优先级。若省略整体则成为一个函数说明,说明了一个返回的数据类型是指针的函数,后面的"形参列表"表示指针变量指向的函数所带的参数列表。例如:

int func(int x); /* 声明一个函数 */

int (*f) (int x); /* 声明一个函数指针 */

f=func; /* 将func函数的首地址赋给指针f */

或者使用下面的方法将函数地址赋给函数指针:

f = &func;

赋值时函数func不带括号,也不带参数,由于func代表函数的首地址,因此经过赋值以后,指针f就指向函数func(x)的代码的首地址。

注2:函数括号中的形参可有可无,视情况而定

下面的程序说明了函数指针调用函数的方法:

例一、

ptr是指向函数的指针变量,所以可把函数max()赋给ptr作为ptr的值,即把max()的入口地址赋给ptr,以后就可以用ptr来调用该函数,实际上ptr和max都指向同一个入口地址,不同就是ptr是一个指针变量,不像函数名称那样是死的,它可以指向任何函数,就看你想怎么做了。在程序中把哪个函数的地址赋给它,它就指向哪个函数。而后用指针变量调用它,因此可以先后指向不同的函数。不过注意,指向函数的指针变量没有++和--运算,用时要小心。

不过,在某些编译器中这是不能通过的。这个例子的补充如下。

应该是这样的:

1.定义函数指针类型:

typedef int (*fun_ptr)(int,int);

2.声明变量,赋值:

fun_ptr max_func=max;

也就是说,赋给函数指针的函数应该和函数指针所指的函数原型是一致的。

指针函数和函数指针的区别:

1,这两个概念都是简称,指针函数是指返回值是指针的函数,即本质是一个函数。我们知道函数都有返回类型(如果不返回值,则为无值型),只不过指针函数返回类型是某一类型的指针。

其定义格式如下所示:

返回类型标识符*函数名称(形式参数表)

{函数体}

返回类型可以是任何基本类型和复合类型。返回指针的函数的用途十分广泛。事实上,每一个函数,即使它不带有返回某种类型的指针,它本身都有一个入口地址,该地址相当于一个指针。比如函数返回一个整型值,实际上也相当于返回一个指针变量的值,不过这时的变量是函数本身而已,而整个函数相当于一个"变量"。例如下面一个返回指针函数的例子:

学生学号从0号算起,函数find()被定义为指针函数,其形参pointer是指针指向包含4个元素的一维数组指针变量。pf是一个指针变量,它指向浮点型变量。main()函数中调用find()函数,将score数组的首地址传给pointer.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值