一、结构体的参数传递
c/c++的结构体传参可以有三种方式:
1、传递结构体变量,值传递
2、传递结构体指针,地址传递
3、传递结构体成员,可是值传递也可以是地址传递
根据代码示例:
1、传递结构体变量
1 #include<iostream>
2 #define COMMANDLENGTH 100
3 using std::cout;
4 using std::endl;
5 //注意 c和c++中struct和typedef struct有区别。
6 struct Message{
7 int oneField;
8 short twoField;
9 char command[COMMANDLENGTH];
10 }message;
11 //c++中结构体可在类中创建也可在类外创建,使用时区别就是在类中需要用类的对象去调用。
12
13 class NewMessage{
14 private:
15 //一般用来声明变量
16 public:
17 Message Deliver(Message stu);//我让函数的返回值也是结构体,传递结构体变量
18 };
19
20 void main()
21 {
22 NewMessage newMessage;//创建类对象
23 Message tempMessage,recvMessage;24
25 tempMessage.oneField = 5;
26 tempMessage.twoField = 10;
27 recvMessage = newMessage.Deliver(tempMessage);//recvMessage用来接收函数的返回值
28 cout << recvMessage.oneField << endl << recvMessage.twoField << endl;
29 system("pause");
30 }
31
32 inline Message NewMessage::Deliver(Message stu)
33 {
34 Message temp;
35 temp = stu;
36 temp.oneField = 10;
37 temp.twoField = 20;
38 return temp;
39 }
补充:函数的执行结果会将结构体的变量值成功修改!
2.传递结构体指针,地址传递
1 #include<iostream>
2 #define COMMANDLENGTH 100
3 using std::cout;
4 using std::endl;
5 //注意 c和c++中struct和typedef struct有区别。
6 struct Message{
7 int oneField;
8 short twoField;
9 char command[COMMANDLENGTH];
10 }message;
11 //c++中结构体可在类中创建也可在类外创建,使用时区别就是在类中需要用类的对象去调用。
12
13 class NewMessage{
14 private:
15 //一般用来声明变量
16 public:
17 Message Deliver(Message *stu);//我让函数的返回值也是结构体,地址传递
18 };
19
20 void main()
21 {
22 NewMessage newMessage;//创建类对象
23 Message *tempMessage = &message;//定义Message的指针变量指向message,也就是结构体的地址
24 Message recvMessage;
25
26 tempMessage->oneField = 5;
27 tempMessage->twoField = 10;
28 recvMessage = newMessage.Deliver(tempMessage);//传递的是结构体的指针地址
29 cout << recvMessage.oneField << endl << recvMessage.twoField << endl;
30 system("pause");
31 }
32
33 inline Message NewMessage::Deliver(Message *stu)
34 {
35 Message temp;
36 temp = *stu;
37 temp.oneField = 10;
38 temp.twoField = 20;
39 return temp;
40 }
补充:函数的执行结果会将结构体的变量值成功修改!
注意:把一个完整的结构体变量作为参数传递,要将全部成员值一个一个传递,费时间又费空间,开销大。如果结构体类型中的成员很多,或有一些成员是数组,则程序运行效率会大大降低。在这种情况下,用指针做函数参数比较好,能提高运行效率。
3.传递结构体成员,值传递或地址传递
这种情况就是结构体内部的成员变量作为参数传递,可以使值传递,可以是地址传递。
1 #include<iostream>
2 #define COMMANDLENGTH 100
3 using std::cout;
4 using std::endl;
5 //注意 c和c++中struct和typedef struct有区别。
6 struct Message{
7 int oneField;
8 short twoField;
9 char command[COMMANDLENGTH];
10 }message;
11 //c++中结构体可在类中创建也可在类外创建,使用时区别就是在类中需要用类的对象去调用。
12
13 class NewMessage{
14 private:
15 //一般用来声明变量
16 public:
17 int Deliver(int onefield, int twofield);
18 };
19
20 void main()
21 {
22 NewMessage newMessage;//创建类对象
23 Message *valueMessage = &message;//定义Message的指针变量指向message,也就是结构体的地址
24 Message addrMessage;
25 int sum;
26
27 //值传递
28 /*
29 addrMessage.oneField = 5;
30 addrMessage.twoField = 10;
31 sum = newMessage.Deliver(addrMessage.oneField, addrMessage.twoField);
32 */
33
34 //指针地址传递,引用
35 valueMessage->oneField = 5;
36 valueMessage->twoField = 10;
37 sum = newMessage.Deliver(valueMessage->oneField, valueMessage->twoField);
38 cout << sum<<endl;
39 system("pause");
40 }
41
42 inline int NewMessage::Deliver(int onefield, int twofield)
43 {
44 int a = onefield;
45 int b = twofield;
46 return a + b;
47 }
二、文件的包含
文件包含是 C 预处理程序的另一个重要功能,文件包含命令行的一般形式为:
#include "文件名"
//或者
#include <文件名>
文件包含命令的功能是把指定的文件插入该命令行位置取代该命令行,从而把指定的文件和当前的源程序文件连成一个源文件。
在程序设计中,文件包含是很有用的。一个大的程序可以分为多个模块,由多个程序员分别编程,有些公用的符号常量或宏定义等可单独组成一个文件,在其他文件的开头用包含命令包含该文件即可使用。
这样,可避免在每个文件开头都去书写那些公用量,从而节省时间,并减少出错。
这里对 C 语言的文件包含命令进行以下几点说明:
(1)包含命令中的文件名可以用双引号引起来,也可以用尖括号引起来。但是这两种形式是有区别的:使用尖括号表示在包含文件目录中去查找(包含目录是由系统的环境变 量进行设置的,一般为系统头文件的默认存放目录,比如 Linux 系统在/usr/include 目录下),而不在源文件的存放目录中查找; 使用双引号则表示首先在当前的源文件目录中查找, 若未找到才到包含目录中去查找。
用户编程时可根据自己文件所在的目录来选择某一种命令形式。
(2)一个 include 命令只能指定一个被包含文件,若有多个文件要包含,则需用多个 include 命令。
(3)文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件。
三、大小端和字节序
计算机数据存储有两种字节优先顺序:高位字节优先(称为大端模式)和低位字节优先(称为小端模式)。
大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。
小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。
例子:对于内存中存放的数0x12345678来说
如果是采用大端模式存放的,则其真实的数是:0x12345678
如果是采用小端模式存放的,则其真实的数是:0x78563412
如果称某个系统所采用的字节序为主机字节序,则它可能是小端模式的,也可能是大端模式的。
而端口号和IP地址都是以网络字节序存储的,不是主机字节序,网络字节序都是大端模式。
要把主机字节序和网络字节序相互对应起来,需要对这两个字节存储优先顺序进行相互转化。
这里用到四个函数:htons(),ntohs(),htonl()和ntohl().
这四个地址分别实现网络字节序和主机字节序的转化,这里的h代表host,n代表network,s代表short,l代表long。
通常16位的IP端口号用s代表,而IP地址用l来代表
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
htonl 表示 host to network long ,用于将主机 unsigned int 型数据转换成网络字节顺序;
htons 表示 host to network short ,用于将主机 unsigned short 型数据转换成网络字节顺序;
ntohl、ntohs 的功能分别与 htonl、htons 相反。
如图,i为int类型占4个字节,但只有1个字节的值为1,另外3个字节值为0;取出低地址上的值,当其为1时则为小端模式,为0时为大端模式。
四、位域
有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。例如开关只有通电和断电两种状态,用 0 和 1 表示足以,也就是用一个二进位。正是基于这种考虑,C语言又提供了一种叫做位域的数据结构。
在结构体定义时,我们可以指定某个成员变量所占用的二进制位数(Bit),这就是位域。请看下面的例子:
struct bs{
unsigned m;
unsigned n: 4;
unsigned char ch: 6;
};
后面的数字用来限定成员变量占用的位数。成员 m 没有限制,根据数据类型即可推算出它占用 4 个字节(Byte)的内存。成员 n、ch 被:
后面的数字限制,不能再根据数据类型计算长度,它们分别占用 4、6 位(Bit)的内存。
要注意的是,C语言标准规定,位域的宽度不能超过它所依附的数据类型的长度。通俗地讲,成员变量都是有类型的,这个类型限制了成员变量的最大长度,:
后面的数字不能超过这个长度。
位域成员可以没有名称,只给出数据类型和位宽,如下所示:
struct bs{
int m: 12;
int : 20; //该位域成员不能使用
int n: 4;
};
无名位域一般用来作填充或者调整成员位置。因为没有名称,无名位域不能使用。
上面的例子中,如果没有位宽为 20 的无名成员,m、n 将会挨着存储,sizeof(struct bs) 的结果为 4;有了这 20 位作为填充,m、n 将分开存储,sizeof(struct bs) 的结果为 8。
五、函数指针
函数存放在内存的代码区域内,它们同样有地址.如果我们有一个 int test(int a) 的函数,那么,它的地址就是函数的名字,这一点如同数组一样,数组的名字就是数组的起始地址。
函数指针的定义方式
data_types (*func_pointer)( data_types arg1, data_types arg2, ...,data_types argn);
例如:
int (*fp)(int a); // 这里就定义了一个指向函数(这个函数参数仅仅为一个 int 类型,函数返回值是 int 类型)的指针 fp。
实例
int test(int a)
{
return a;
}
int main(int argc, const char * argv[])
{
int (*fp)(int a);
fp = test;
cout<<fp(2)<<endl;
return 0;
}
注意:函数指针所指向的函数一定要保持函数的返回值类型,函数参数个数,类型一致。
类成员函数指针与普通函数指针不是一码事。前者要用 .* 与 ->* 运算符来使用,而后者可以用 * 运算符(称为"解引用"dereference,或称"间址"indirection)。
普通函数指针实际上保存的是函数体的开始地址,因此也称"代码指针",以区别于 C/C++ 最常用的数据指针。
而类成员函数指针就不仅仅是类成员函数的内存起始地址,还需要能解决因为 C++ 的多重继承、虚继承而带来的类实例地址的调整问题,所以类成员函数指针在调用的时候一定要传入类实例对象。