您的位置:首页 > 编程语言 > C语言/C++

C语言入门篇-14

2013-10-18 16:22 253 查看

点击链接加入群【C语言】:http://jq.qq.com/?_wv=1027&k=2H9sgjG



不知不觉,已经谈到文件流啦,如果你已经挺过来了, 那么我告诉你,你已经离成功很近啦。
前面我们谈到过结构体,也没有讲它是什么东西,我们今天来看看C中的两个宝贝,结构体和联合体。
什么是结构体,结构体就是可以用来定义新类型的一个关键字,我们可以用struct 来定义新的数据类型
struct Person
{
intage;
boolsex;//真为男,假为女
charname[10];
charwork[20];
};
这就是一个新类型,啊,又是类型,哈哈,我们之前讲过类型的重要性啦,这个Person 类型,就可以用来定义一些变量啦,对比一下和int变量的定义
int val ;
Person val;
好理解吧,其实就是一样的,我们在前面是非常关心其内存结构的,比如int 类型的数据在内存中是怎么存放的,那么Person又是怎么存放的呢?
我们先用 sizeof求一下它占的大小吧
#include <stdio.h>
struct Person
{
intage;
boolsex;//真为男,假为女
charname[10];
charwork[20];
};
int main()
{
printf("%d\n",sizeof Person); //注意这里没有加(),也就再次说明sizeof 并不是函数,而是一个关键字,有关它的说明可以参考MSDN
return 0;
}
MSDN中对sizeof的说明:
sizeof Operator sizeof操作符
sizeof expression sizeof 表达式 //所以,我们没有加括号也是可以滴啦
The sizeof keyword gives the amount of storage, in bytes, associated with a variable or a type (including aggregate types). This keyword returns a value of type size_t.
//sizeof关键字会给出一个变量或类型(包含集合类型(如结构体等))占用的存储空间的总字节数,返回值类型为 size_t。
The expression is either an identifier or a type-cast a type specifier enclosed in parentheses).
//sizeof后面跟的表达式既可以是一个标识符,也可以是一个类型强转表达式(如 (int) ) .所以我们加上括号也是对的
When applied to a structure type or variable, sizeof returns the actual size, which may include padding bytes inserted for alignment. When applied to a statically dimensioned array, sizeof returns the size of the entire array. The sizeof operator cannot return the size of dynamically allocated arrays or external arrays.
//当应用到结构体类型或变量的时候,sizeof返回的是实际大小,可能会包含一些为了对齐而插入的填充字节。当应用到一个静态维度的数组时,它返回的是整个数组占用的空间大小。它不能返回动态申请的数组空间的大小 或 外部数组的大小 。//详情,我们以后会谈到滴

好啦,我们会看到结果是 36个字节, int 4 bool 1 name 10 work 20 怎么加也不是 36 ,而应该是35 吧,为什么会变成36呢
原来呀,结构体有个对齐的问题,实际上这是由许多原因造成的,可能是由于CPU在访问地址总线时,如果地址为奇数,效率会低,所以我们不用去关心那些原因,也不用太关心它到底是怎么对齐的,但是我们的目的只有一个,就是让我们的结构体使用的空间尽量饱和,比如,上面的Person中 sex会占用两个字节空间,所以,我们在设计数据结构的时候应该尽量让这种填充字节占的空间小,上面的Person结构体填充字节占一个字节。哈哈,你应该可以理解吧,大家如果有兴趣,可以稍微研究一下结构体的字节对齐问题,百度一下就明白啦,不过不要把它当成什么重点,因为实际开发中,我们是遵循的最小浪费(最小为0)原则,所以,这个知识点做为扩展知识。
其它的都是一样的啦,跟普通数据是一样的
不过我们在访问结构体中的数据(换个名字叫成员)的时候是通过 . 来进行访问的,比如
#include <stdio.h>
struct Person
{
intage;
boolsex;//真为男,假为女
charname[10];
charwork[20];
};
int main()
{
Person person;
person.age = 25;
person.sex = true;
person.name = "周星驰";
person.work = "喜剧演员";
return 0;
}
这段代码大家运行会有问题的哦,哈哈,必须有问题,而这个问题也是很多新人朋友会遇到的哦,我们定义了一个person,那么在栈中就申请了36个字节的空间,给age和sex赋值都没有问题,但是给name赋值的时候就不对啦,为什么,可以想像,我们的程序执行到main函数栈桢的时候,person的地址已经是固定的啦,我们讲过数组名是什么呢?数组名是一个地址,但是它本身不能改变,而我们的代码却正是在执行这种操作,肯定是不行的啦,这是从运行的角度来看滴,如果从编译的角度来分析,
--------------------Configuration: abc - Win32 Debug--------------------
Compiling...
abc.cpp
D:\ZGY\C++\abc\abc.cpp(14) : error C2440: '=' : cannot convert from 'char [7]' to 'char [10]'
There is no context in which this conversion is possible
D:\ZGY\C++\abc\abc.cpp(15) : error C2440: '=' : cannot convert from 'char [9]' to 'char [20]'
There is no context in which this conversion is possible
Error executing cl.exe.
Creating browse info file...

abc.exe - 2 error(s), 0 warning(s)
给出的提示,赋值运算符两边的类型不相同,编译都通过不了。所以,这里我们要如何改变这种状态呢。
我们已经知道的,如果程序编译并链接成功,那么字符串是被写到.EXE文件里的,当程序运行的时候,字符串从文件中被加载到数据区,所以,我们有几种方案:

方案A:
我们可以将数据动态的拷贝到栈空间中
#include <stdio.h>
#include <string.h>
struct Person
{
intage;
boolsex;//真为男,假为女
charname[10];
charwork[20];
};
int main()
{
Person person;
person.age = 25;
person.sex = true;
strcpy(person.name ,"周星驰");
strcpy(person.work ,"喜剧演员");
return 0;
}
方案B:
我们可以将数组定义成指针,让这个指针指向数据
#include <stdio.h>
#include <string.h>
struct Person
{
intage;
boolsex;//真为男,假为女
char * name;
char *work;
};
int main()
{
Person person;
person.age = 25;
person.sex = true;
person.name = "周星驰";
person.work = "喜剧演员";
return 0;
}
方案C:
方案B中的指针指向了数据区的数据,我们也可以在堆中申请好空间,然后将数据拷贝到堆中,在程序结束的时候来销毁这块内存
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Person
{
intage;
boolsex;//真为男,假为女
char * name;
char *work;
};
int main()
{
Person person;
person.age = 25;
person.sex = true;
person.name = (char *)malloc(10);
memset(person.name,0,10);
strcpy(person.name,"周星驰");
person.work = (char *)calloc(20,1);
strcpy(person.work,"喜剧演员");

free(person.name);
free(person.work);
person.name = NULL;
person.work = NULL;
return 0;
}

好,这几个过程,用到的都是我们前面所讲过的知识,我们又多用了几个函数,strcpy,memset
strcpy功能介绍 str是string的缩写,cpy是copy的缩写
char *strcpy( char *strDestination, const char *strSource );
我们可以看到这个函数返回的是 char * 类型,接收的参数是两个 char * 类型的,其实我们就可以这样子理解啦,接收两个地址,返回一个地址,OK,下面的操作就是将源地址里的数据拷贝到目标地址中去,那么拷贝多少个呢,字符串在内存中是以 0 结尾的,如下
00422FA4 CF B2 BE E7 喜剧
00422FA8 D1 DD D4 B1 演员
00422FAC 00 00 00 00 ....
00422FB0 D6 DC D0 C7 周星
00422FB4 B3 DB 00 00 驰..
所以,我们也就明白了这个函数内部的大概实现啦
当*strSou != 0X00 的时候,执行 *(strDest ++) = *(strSou ++) ,这个东西应该可以看懂吧,然后一直循环,当然这只是一种猜测,系统并不一定就是这么实现的,但是这种实现是可以的。其实前面的代码中的两个括号是可以省略掉的,但是我们没有省略,因为什么,我们写代码并不是让代码看起来多么个性,而是要容易理解,所以,如果大家看一下外国人写的代码,会发现是那么的井井有条,所以,我们在这方面是应该向外国朋友们学习啦,这样子做的目的主要是方便我们以后对代码进行修改,现在,你想想你上个月的今天在干什么?估计你也忘了吧,其实我们是很容易遗忘的,写的代码过个几个月,再回来看,自己可能也读不懂啦。哈哈,扯的有点远啦。还有一个问题,为什么要返回一个地址呢,返回是否成功不是更好吗,是好,不过不如这个好,为什么这么讲,我们先来看一下它返回的是什么,
Return Value
Each of these functions returns the destination string. No return value is reserved to indicate an error.
这个函数返回的是 目标字符串的地址,没有为出错情况设置预定义的返回值。
返回目标地址,有什么好处,方便我们对这个新字符串进行操作呀,哈哈,比如下面的例子
int main()
{
char szBuf[100] ={0};
strcat( strcpy(szBuf,"ABCDEFG!") ,"abcdefg!"); //拷贝完返回的szBuf的地址,然后利用这个地址,进行连接字符串
return 0;
}
我们把类似于上面的这种操作叫做级联调用,如果你有c++的知识的话, 你肯定知道 cout<<"a"<<"b"<<"c"<<endl; 那么其实它也是级联调用的一种哦。你能感觉到呢?我们以后会讲到的啦。

memset功能介绍,就是将某个内存区域的值做个初始化,也叫为块赋值
void *memset( void *dest, int c, size_t count );
要为内存块赋值,首先要有一个起始地址吧,就是你要为哪块内存写数据,写的数据是什么值,内存块的大小,就这么简单。我就不再多做什么介绍啦。

现在大家应该感觉到我们前面讲的基础有多么重要了吧,其实现在讲的这些对上层应用来讲还是基础,所以基础不牢,地动山摇是真的,一定要打好基础。

结构体暂时就讨论这么多,让我们来看一下联合体,还记得我们看到的内存吗,一串连续的内存空间,你可以把它看成N种结果,比如连续的8个字节,你可以看成2个整数,4个短整数,8个字符,一个双精度浮点数,对,联合体就是这种思想,看一下
union UN
{
charc;
shorts;
inti;
floatf;
doubled;
};
如果大家测试一下这个类型的大小,会得到8,为什么是8,因为联合的思想就是用一块空间,来表示不同的数据,所以空间一定要能表示出所占空间最大的的值,也就是上面的double啦,
union UN
{
charc;
shorts;
inti;
floatf;
doubled;
};
int main()
{
UN un;
un.c=0X11;
un.s=0X2233;
un.i=0X11223344;
un.f=123.125;
return 0;
}
0X12FF78是un的首地址

执行un.c那行
0012FF78 11 CC CC CC .烫.
0012FF7C CC CC CC CC 烫烫

执行un.s那行
0012FF78 33 22 CC CC 3"烫
0012FF7C CC CC CC CC 烫烫

执行un.i那行
0012FF78 44 33 22 11 D3".
0012FF7C CC CC CC CC 烫烫

执行un.f那行
0012FF78 00 40 F6 42 .@鯞
0012FF7C CC CC CC CC 烫烫

我想大家已经可以明白了,只要你前面学的好了,这里基本上不是什么难题,关键问题是这有什么用呢?
我们举一个MSDN中的例子,来看看这东西的用处,在msdn中查询 in_addr这个结构体
struct in_addr {
union {
struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { u_short s_w1,s_w2; } S_un_w;
u_long S_addr;
} S_un;
};
这是一个结构体,里面有一个联合体,联合的大小是4B,而联合里面有三种类型定义,其实大家应该可以看的懂啦,再举一个例子,我们前面讨论过大小端问题,我们用联合来看一下怎么解决呢?
#include <stdio.h>
union UN
{
char c;
int i;
};
int main()
{
UN un;
un.i=1;
if (un.c == 1)printf("小端\n");
if (un.c == 0)printf("大端\n");
return 0;
}
哈哈,你要不理解,你就得再研究一下前面的内容,如果你在阅读本系列文章的时候有什么疑问,可以在评论中留言啦。

今天就介绍这么多,主要就是C中的结构体和联合体的意义。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C语言学习