一.结构体的参数传递
值传递
#include <iostream>
#include <string>
using namespace std;
struct Student
{
int id;
string name;
float score[2];
};
void OutCome(Student s)
{
cout<<s.id<<','<<s.name<<','<<s.score[0]<<','<<s.score[1]<<endl;
}
int main()
{
Student stu={2013666,"Tom",{88,99}};
OutCome(stu);
return 0;
}
引用传递
#include <iostream>
#include <string>
using namespace std;
struct Student
{
int id;
string name;
float score[2];
};
//引用传递不会进行内存重新分配,因此和指针传参类似,效率很高
void OutCome(Student &s) //引用传参
{
cout<<s.id<<','<<s.name<<','<<s.score[0]<<','<<s.score[1]<<endl;
}
int main()
{
Student stu={2013666,"Tom",{88,99}};
OutCome(stu);
return 0;
}
指针传递
把结构体的指针作为实参
#include <iostream>
#include <string>
using namespace std;
struct Student
{
int id;
string name;
float score[2];
};
void OutCome(Student *s)
{
//注意指针访问结构体就不能用“.”,要用“->”
cout<<s->id<<','<<s->name<<','<<s->score[0]<<','<<s->score[1]<<endl;
}
int main()
{
Student stu={2013666,"Tom",{88,99}};
OutCome(&stu); //这种写法不是特别规范,但可以清晰表明传递的实际上是地址
//下面这样写才清晰
//Student *p=&stu;
//OutCome(p)
return 0;
}
二.文件的包含
文件包含
开发人员将相同的函数写入单独的文件中,需要使用某个函数时直接调用此文件,无需再次编写,这种文件调用的过程称文件包含
1.包含.c文件
直接将代码写在.c文件中,其他文件要调用函数则直接包含.c文件。正如下面一段代码:

总结:
优点:可以直接编译main.c文件,不用编译fun.c,因为在main.c的编译预处理阶段,会将fun.c 的代码直接拷贝过来。可以使用gcc -E main.c -o main.i 查看预处理后.i文件的代码。发现main.i中将fun.c的代码拷贝了过来。
缺点:fun.c不能被多个文件包含,因为这样就会产生变量和函数的多个拷贝,造成严重的重定义,编译通不过。
这种方式可解决较简单程序的文件包含问题,通常不推荐使用。
2.直接多文件
多文件程序中,全局变量可以被外部引用,函数也默认是全局函数,可以被外部使用。

总结:
优点:不会出现重定义问题
缺点:1、如果要用到的外部变量多,或是在多处要使用外部变量,则要在每一个调用的文件中使用extern声明,比较麻烦。
2、如果是在fun.c中有结构体定义或是类的定义,在main.c中用使用结构体定义变量,或是使用类定义对象。编译不能通过,不知该如何处理。
3、如果是定义在fun.c中的结构体变量,在main.c中要声明外部变量,并使用,会出错。故推知:结构体不能声明为外部变量,只能是包含的头文件中定义的结构体。
3.包含.h文件
为每一个模块都编写一个.c文件,一个.h文件。
.c源文件中放:函数的定义,全局变量的定义,全局结构体变量的定义。
.h头文件中放:函数的声明,全局变量的声明(extern),全局结构体的定义,全局结构体变量的声明。
调用文件(main.c)文件中:包含.h头文件即可。不用声明任何东西。
//fun.c
#include "fun.h"
int global_var = 0;
struct student wang; //结构体变量定义
int cal(int a, int b)
{
return (2*a+3*b);
}
//fun.h
#ifndef _fun_h_ //条件编译,解决文件重复包含所引起的重定义问题
#define _fun_h_
struct student
{
int age;
int num;
};
extern int global_var;
extern struct student wang;
#endif
//fun1.c
#include "fun1.h" //没有实际代码
//fun1.h
#include "fun.h" //包含fun.h
//main.c
#include "stdio.h"
#include "fun.h"
#include "fun1.h"
//extern int global_var;
//extern struct student wang;
int main(void)
{
wang.age = 10; //调用全局结构体变量
struct student li; //定义变量
global_var =1; //使用全局变量
int i = 1,j = 2;
int c = 0;
c = cal(i, j);
printf("%d\n", c);
return 0;
}
总结:
优点:解决了文件重复包含造成的重定义问题;函数,全局变量,结构体,结构体变量都能在外部调用;每一处调用变量的文件处不用重复的写声明语句,因为声明语句直接写在了.h头文件中,直接包含头文件即可。
三.大小端和字节序
大多数处理器中内存是可以以字节为单位进行寻址的,当数据类型(int, long)大于1个字节时,其所占用就的字节在内存中的顺序存在两种模式,分别是小端模式(little endian)和大端模式(big endian)。小端模式低位字节存放在低地址,大端模式高位字节存放在低地址。
字节序,就是 大于一个字节类型的数据在内存中的存放顺序。是在跨平台和网络编程中,时常要考虑的问题。
高低字节
在十进制中靠左边的是高位,靠右边的是低位,在其他进制也是如此。例如 0x12345678,从高位到低位的字节依次是0x12、0x34、0x56和0x78。
网络字节序 就是 大端字节序:4个字节的32 bit值以下面的次序传输,首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit
主机字节序 就是 小端字节序,现代PC大多采用小端字节序。
大端与小端
字节序有两种,大端和小端
大端:数据高位存放在低地址,地位放在高地址。如0x12345678在内存中存放为 12 34 56 78 (地址,从左到右为低到高)
小端:数据地位存放在低地址,高位存放在高地址。如0x12345678在内存中存放为 78 56 34 12 (地址,从左到右为低到高)
对于数据 0x12345678,假设从地址0x4000开始存放,在大端和小端模式下,存放的位置分别为:

采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节,而Big-endian模式对操作数的存放方式是从高字节到低字节。
小端存储后:0x78563412 大端存储后:0x12345678
为什么会有字节序问题
1. 内存以8bit为一个地址单位:早期的处理器地址以8bit为一个单位(8位处理器),也就是说一次可以访问8bit的数据,后来出现了16位,32位甚至64位的处理器,但为了兼容最早的8位处理器,因此沿用8bit为一个地址单位。
2. 大于8位的处理器,如32位处理器,虽然每个8bit(一个字节)存储数据的方式是一样的,但是针对整形这样的有多个字节的数据结构的数据,每个字节内存储数据方式相同,但字节间(即几个字节的顺序)存储方式不同。
3.其他历史原因.
不同的处理器结构对整形这样的大数据类型的每个字节在内存中存放顺序不一样,这样,在不同架构的处理器平台进行通讯时,就产生了字节序的问题。
不同处理器的通讯一般通过网络通信进行,于是我们统一了网络字节序(使用大端字节序)。
关键点:
字节序: 大整形数据类型(如int,short,而char就不算了)的数据中各个字节在内存的存储书序。
一般在网络通信是需要关注。
字节序转换的几个函数:

16表示16bit,short占16位。 32表示32位,unsigned int 占32位。
h表示host, n表示network。 l表示long, s表示short
四.位域
有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几 个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。
位域通过一个结构声明来建立:该结构声明为每个字段提供标签,并确定该字段的宽度。例如,下面的声明建立了个4个1位的字段:
struct
{
unsigned int autfd:1;
unsigned int bldfc:1;
unsigned int undin:1;
unsigned int itals:1;
}prnt;
根据该声明, prnt包含4个1位的字段。现在,可以通过普通的结构成员运算符(.)单独给这些字段赋值:
prnt.itals = 0:
prnt.undin = 1;
由于每个字段恰好为1位,所以只能为其赋值1或0。变量prnt被储存在int大小的内存单元中,但是在本例中只使用了其中的4位。
:后面的数字用来限定成员变量占用的位数。位域的宽度不能超过它所依附的数据类型的长度。通俗地讲,成员变量都是有类型的,这个类型限制了成员变量的最大长度,:后面的数字不能超过这个长度。
如上述结构中autfd、bldfc、undin、itals后面的数字不能超过unsigned int的位数,即在32bit环境中就是不能超过32。
其中位域列表的形式为: 类型说明符 位域名:位域长度
例如:
struct bs
{
int a:8;
int b:2;
int c:6;
};
位域变量的说明与结构变量说明的方式相同。 可采用先定义后说明,同时定义说明或者直接说明这三种方式。例如:
View Code
说明data为bs变量,共占两个字节。其中位域a占8位,位域b占2位,位域c占6位。
使用位域的主要目的是压缩存储,其大致规则为:
1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止 2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍; 3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++,GCC采取压缩方式; 4) 如果位域字段之间穿插着非位域字段,则不进行压缩; 5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。
第一个结构体中,i,j,a共占15个位,不足8个字节,按double 8字节对齐,共16字节
第二个结构体中,i,j共占12位,不足8字节,按8字节对齐,a也按8字节对齐,加上double共8+8+8=24个字节
五.函数指针
1. 定义:
char* fun1(char* p1, char* p2)// 1
char** fun2(char* p1, char* p2);// 2
char*(*fun3)(char* p1, char* p2);// 3
1,2,3分别是什么意思呢?
-
- 很简单,char*是返回类型,fun1是函数名,括号内是参数
-
- 和1相似,char** 是返回类型
-
- 就是我们要讲的函数指针,看下面的例子:
int (*) [10]p//数组指针
这里的fun3不是函数名,是指针变量,它指向一个函数。
函数指针的定义就是,一个指向函数的指针
2. 例子
我们上面定义了一个函数指针,那我们该如何使用它呢?

我们在使用指针的时候,需要通过钥匙“ * ”来取其指向的内存的值,函数指针使用也如此。通过用(*fun1)取出该地址的函数然后调用他。也可以直接使用fun1,因为函数名被编译后其实就是一个地址。
3.函数指针数组
char* (*fun[3])(char* p);//函数指针数组
它是一个数组,数组名是fun,数组内存了3个指向函数的指针。这些指针指向的一些返回值类型为指向字符的指针,参数为一个指向字符指针的函数。
4.函数指针数组的指针
我一开始看到这东西我脑子里都是???,然后查了些博客啥的就弄清楚了。我们都知道什么事数组指针,套用数组指针的理解来理解函数指针数组不就好了吗?
char* (*(*fun)[3]))(char* p);
- 1.(*fun),是一个指针。
- 2.(*fun)[3],表示这个指针指向一个包含三个元素的数组
- 3.去掉(* fun)[3],看上面的例子,char* (* )(char* p),可以知道数组中的三个元素是返回类型为char*型的,参数为指向字符串的函数。
本文详细探讨了C++中结构体的值传递、引用传递和指针传递的原理与区别,强调了引用传递的高效性。同时,介绍了文件包含的三种方式及其优缺点,包括直接包含.c文件、多文件直接调用和包含.h文件,以及在跨平台编程中如何避免重定义问题。此外,解释了字节序的概念,包括大小端模式的差异以及在网络通信中的重要性。最后,简述了位域的使用和作用,以及函数指针的定义、使用和数组形式。


被折叠的 条评论
为什么被折叠?



