概述

在第一节中我们就提到C语言的构造类型,分为:数组、结构体、枚举、共用体,当然前面数组的内容已经说了很多了,这一节将会重点说一下其他三种类型。

  1. 结构体
  2. 枚举
  3. 共用体

结构体

数组中存储的是一系列相同的数据类型,那么如果想让一个变量存储不同的数据类型就要使用结构体,结构体定义类似于C++、C#、Java等高级语言中类的定义,但事实上它们又有着很大的区别。结构体是一种类型,并非一个变量,只是这种类型可以由其他C语言基本类型共同组成。

//
// main.c
// ConstructedType
//
// Created by Kenshin Cui on 14-7-18.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
// #include <stdio.h> //结构体类型Date
struct Date{
int year;
int month;
int day;
}; struct Person{
char *name;
int age;
struct Date birthday;//一个结构体中使用了另一个结构体类型,结构体类型变量声明前必须加上struct关键字
float height;
}; int main(int argc, const char * argv[]) {
struct Person p={"Kenshin",28,{1986,8,8},1.72};
//定义结构体变量并初始化,不允许先定义再直接初始化,例如:struct Person p;p={"Kenshin",28,{1986,8,8},1.72};是错误的,但是可以分别赋值,例如p.name="Kenshin" printf("name=%s,age=%d,birthday=%d-%d-%d,height=%.2f\n",p.name,p.age,p.birthday.year,p.birthday.month,p.birthday.day,p.height);
//结果:name=Kenshin,age=28,birthday=1986-8-8,height=1.72,结构体的引用是通过"结构体变量.成员名称"(注意和结构体指针访问结构体成员变量区分,结构体指针使用p->a的形式访问) printf("len(Date)=%lu,len(Person)=%lu\n",sizeof(struct Date),sizeof(struct Person));
//结果:len(Date)=12,len(Person)=32 return 0;
}

对于上面的例子需要做出如下说明:

  1. 可以在定义结构体类型的同时声明结构体变量;

  2. 如果定义结构体类型的同时声明结构体变量,此时结构体名称可以省略;
  3. 定义结构体类型并不会分配内存,在定义结构体变量的时候才进行内存分配(同基本类型时类似的);
  4. 结构体类型的所占用内存大型等于所有成员占用内存大小之和(如果不考虑内存对齐的前提下);

对第4点需要进行说明,例如上面代码是在64位编译器下运行的结果(int长度4,char长度1,float类型4),Date=4+4+4=12。但是对于Person却没有那么简单了,因为按照正常方式计算Person=8+4+12+4=28,但是从上面代码中给出的结果是32,为什么呢?这里不得不引入一个概念“内存对齐”,关于内存对齐的概念在这里不做详细说明,大家需要了解的是:在Mac OS X中对齐参数默认为8(可以通过在代码中添加#pragma pack(8)改变对齐参数),如果结构体中的类型不大于8,那么结构体长度就是其成员类型之和,但是如果成员变量的长度大于这个对齐参数那么得到的结果就不一定是各个成员变量之和了。Person类型的长度之所以是32,其实主要原因是因为Date类型长度12在存储时其偏移量12不是8的倍数,考虑到内存对齐的原因需要添加4个补齐长度,这里使用表格的形式列出了具体原因:

表格具体来源请观看下面的视频(注意由于录制软件的原因前几秒不清晰但是不影响分析):

接下来看一下结构体数组、指向结构体的指针:

//
// main.c
// ConstructedType
//
// Created by Kenshin Cui on 14-7-18.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
// #include <stdio.h> struct Date{
int year;
int month;
int day;
}; struct Person{
char *name;
int age;
struct Date birthday;
float height;
}; void changeValue(struct Person person){
person.height=1.80;
} int main(int argc, const char * argv[]) {
struct Person persons[]={
{"Kenshin",28,{1986,8,8},1.72},
{"Kaoru",27,{1987,8,8},1.60},
{"Rosa",29,{1985,8,8},1.60}
};
for (int i=0; i<3; ++i) {
printf("name=%s,age=%d,birthday=%d-%d-%d,height=%.2f\n",
persons[i].name,
persons[i].age,
persons[i].birthday.year,
persons[i].birthday.month,
persons[i].birthday.day,
persons[i].height);
}
/*输出结果:
name=Kenshin,age=28,birthday=1986-8-8,height=1.72
name=Kaoru,age=27,birthday=1987-8-8,height=1.60
name=Rosa,age=29,birthday=1985-8-8,height=1.60
*/ struct Person person=persons[0];
changeValue(person);
printf("name=%s,age=%d,birthday=%d-%d-%d,height=%.2f\n",
persons[0].name,
persons[0].age,
persons[0].birthday.year,
persons[0].birthday.month,
persons[0].birthday.day,
persons[0].height);
/*输出结果:
name=Kenshin,age=28,birthday=1986-8-8,height=1.72
*/ struct Person *p=&person;
printf("name=%s,age=%d,birthday=%d-%d-%d,height=%.2f\n",
(*p).name,
(*p).age,
(*p).birthday.year,
(*p).birthday.month,
(*p).birthday.day,
(*p).height);
/*输出结果:
name=Kenshin,age=28,birthday=1986-8-8,height=1.72
*/
printf("name=%s,age=%d,birthday=%d-%d-%d,height=%.2f\n",
p->name,
p->age,
p->birthday.year,
p->birthday.month,
p->birthday.day,
p->height);
/*输出结果:
name=Kenshin,age=28,birthday=1986-8-8,height=1.72
*/ return 0;
}

结构体作为函数参数传递的是成员的值(值传递而不是引用传递),对于结构体指针而言可以通过”->”操作符进行访问。

枚举

枚举类型是比较简单的一种数据类型,事实上在C语言中枚举类型是作为整形常量进行处理的,通常称为“枚举常量”。

//
// main.c
// ConstructedType
//
// Created by Kenshin Cui on 14-7-18.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
// #include <stdio.h> enum Season{ //默认情况下spring=0,summer=1,autumn=2,winter=3
spring,
summer,
autumn,
winter
}; int main(int argc, const char * argv[]) {
enum Season season=summer; //枚举赋值,等价于season=1
printf("summer=%d\n",season); //结果:summer=1 for(season=spring;season<=winter;++season){
printf("element value=%d\n",season);
}
/*结果:
element value=0
element value=1
element value=2
element value=3
*/
return 0;
}

需要注意的是枚举成员默认值从0开始,如果给其中一个成员赋值,其它后面的成员将依次赋值,例如上面如果summer手动指定为8,则autumn=9,winter=10,而sprint还是0。

共用体

共用体又叫联合,因为它的关键字是union(貌似数据库操作经常使用这个关键字),它的使用不像枚举和结构体那么频繁,但是作为C语言中的一种数据类型我们也有必要弄清它的用法。从前面的分析我们知道结构体的总长度等于所有成员的和(当然此时还可能遇到对齐问题),但是和结构体不同的是共用体所有成员共用一块内存,顺序从低地址开始存放,一次只能使用其中一个成员,union最终大小由共用体中最大的成员决定,对某一成员赋值可能会覆盖另一个成员。

//
// main.c
// ConstructedType
//
// Created by Kenshin Cui on 14-7-20.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
// #include <stdio.h> union Type{
char a;
short int b;
int c;
}; int main(int argc, const char * argv[]) {
union Type t;
t.a='a';
t.b=10;
t.c=65796; printf("address(Type)=%x,address(t.a)=%x,address(t.b)=%x,address(t.c)=%x\n",&t,&t.a,&t.b,&t.c);
//结果:address(Type)=5fbff7b8,address(t.a)=5fbff7b8,address(t.b)=5fbff7b8,address(t.c)=5fbff7b8 printf("len(Type)=%d\n",sizeof(union Type));
//结果:len(Type)=4 printf("t.a=%d,t.b=%d,t.c=%d\n",t.a,t.b,t.c);
//结果:t.a=4,t.b=260,t.c=65796 return 0;
}

这里需要重点解释一个问题:为什么t.a、t.b、t.c输出结果分别是4、260、65796,当然t.c等于65796并不奇怪,但是t.a前面赋值为’a’不应该是97吗,而t.b不应该是10吗?其实如果弄清这个问题共用体的概念基本就清楚了。

根据前面提到的,共用体其实每次只能使用其中一个成员,对于上面的代码经过三次赋值最终使用的其实就是t.c,而通过上面的输出结果我们也确实看到c是有效的。共用体有一个特点就是它的成员存储在同一块内存区域,这块区域的大小需要根据它的成员中长度最大的成员长度而定。由于上面的代码是在64位编译器下编译的,具体长度:char=1,short int=2,int=4,所以得出结论,Type的长度为4,又根据上面输出的地址,可以得到下面的存储信息(注意数据的存储方式:高地址存储高位,低地址存储地位):

当读取c的时候,它的二进制是“00000000  00000001  00000001  00000100”,换算成十进制就是65796;而经过三次赋值后,此时b的存储就已经被c成员的低位数据覆盖,b的长度是二,所以从起始地址取两个字节得到的二进制数据此时是“00000001  00000100”(b原来的数据已经被c低2位数据覆盖,其实此时就是c的低2位数据),换算成十进制就是260;类似的a此时的数据就是c的低一位数据”00000100”,换算成十进制就是4。

iOS开发系列--C语言之构造类型的更多相关文章

  1. iOS开发系列--Swift语言

    概述 Swift是苹果2014年推出的全新的编程语言,它继承了C语言.ObjC的特性,且克服了C语言的兼容性问题.Swift发展过程中不仅保留了ObjC很多语法特性,它也借鉴了多种现代化语言的特点,在 ...

  2. iOS开发系列--C语言之基础知识

    概览 当前移动开发的趋势已经势不可挡,这个系列希望浅谈一下个人对IOS开发的一些见解,这个IOS系列计划从几个角度去说IOS开发: C语言 OC基础 IOS开发(iphone/ipad) Swift ...

  3. iOS开发系列--C语言之存储方式和作用域

    概述 基本上每种语言都要讨论这个话题,C语言也不例外,因为只有你完全了解每个变量或函数存储方式.作用范围和销毁时间才可能正确的使用这门语言.今天将着重介绍C语言中变量作用范围.存储方式.生命周期.作用 ...

  4. iOS开发系列--C语言之数组和字符串

    概览 数组在C语言中有着特殊的地位,它有很多特性,例如它的存储是连续的,数组的名称就是数组的地址等.而在C语言中是没有String类型的,那么如果要表示一个字符串,就必须使用字符数组.今天主要就介绍如 ...

  5. iOS开发系列--C语言之指针

    概览 指针是C语言的精髓,但是很多初学者往往对于指针的概念并不深刻,以至于学完之后随着时间的推移越来越模糊,感觉指针难以掌握,本文通过简单的例子试图将指针解释清楚,今天的重点有几个方面: 什么是指针 ...

  6. iOS开发系列--C语言之预处理

    概述 大家都知道一个C程序的运行包括编译和链接两个阶段,其实在编译之前预处理器首先要进行预处理操作,将处理完产生的一个新的源文件进行编译.由于预处理指令是在编译之前就进行了,因此很多时候它要比在程序运 ...

  7. iOS开发系列文章(持续更新……)

    iOS开发系列的文章,内容循序渐进,包含C语言.ObjC.iOS开发以及日后要写的游戏开发和Swift编程几部分内容.文章会持续更新,希望大家多多关注,如果文章对你有帮助请点赞支持,多谢! 为了方便大 ...

  8. iOS开发系列--Swift进阶

    概述 上一篇文章<iOS开发系列--Swift语言>中对Swift的语法特点以及它和C.ObjC等其他语言的用法区别进行了介绍.当然,这只是Swift的入门基础,但是仅仅了解这些对于使用S ...

  9. iOS开发系列--App扩展开发

    概述 从iOS 8 开始Apple引入了扩展(Extension)用于增强系统应用服务和应用之间的交互.它的出现让自定义键盘.系统分享集成等这些依靠系统服务的开发变成了可能.WWDC 2016上众多更 ...

随机推荐

  1. scrapy爬虫结果插入mysql数据库

    1.通过工具创建数据库scrapy

  2. echarts中显示效果option中必有的属性

    写一个最简单的效果让option中不可缺少的属性. var option = { xAxis:[ //x轴,数组对象,其下至少有一个对象 {.....} ], yAxis:[//y轴,数组对象,其下可 ...

  3. ThreadLocal内部机制及使用方法

    一.介绍ThreadLocal内部机制之前,先简单说明一下其特点及用途: 1.ThreadLocal是单线程内共享资源,多线程间无法共享(即线程A访问不了线程B中ThreadLocal存放的值): 2 ...

  4. 一些VS2013的使用技巧

    作者:h46incon的Blog 1. Peek View 可以在不新建TAB的情况下快速查看.编辑一个函数的代码. 用法:在光标移至某个函数下,按下alt+F12. 然后在Peek窗口里可以继续按a ...

  5. Asp.net中static变量和viewstate的使用方法(谨慎)

    在.Net平台下进行CS软件开发时,我们经常遇到以后还要用到某些变量上次修改后的值,为了简单起见,很多人都习惯用static来定义这些变量,我也是.这样非常方便,下一次调用某个函数时该变量仍然保存的是 ...

  6. 单片机与控制实验(2)——LED点阵显示屏

    一.实验目的和要求 了解LED点阵显示的基本原理和实现方法.掌握点阵汉字库的编码和从标准字库中提取汉字编码的方法. 二.实验设备 单片机测控实验系统 LED点阵显示器实验模块 Keil开发环境 STC ...

  7. WinForm开发笔记

    Button Button默认不产生DoubleClick事件 (MSDN说明:https://msdn.microsoft.com/zh-cn/library/system.windows.form ...

  8. 【BZOJ1700】[Usaco2007 Jan]Problem Solving 解题 动态规划

    [BZOJ1700][Usaco2007 Jan]Problem Solving 解题 Description 过去的日子里,农夫John的牛没有任何题目. 可是现在他们有题目,有很多的题目. 精确地 ...

  9. ng-repeat循环出来的部分调用同一个函数并且实现每个模块之间不能相互干扰

    使用场景:用ng-repeat几个部分,每个部分调用同一个函数,但是每个模块之间的功能不能相互干扰 问题:在用repeat实现.content块repeat的时候打算这样做:新建一个空的数组(nmbe ...

  10. JavaScript_js模拟键盘输入

    function fireKeyEvent(el, evtType, keyCode) { var evtObj; if (document.createEvent) { if (window.Key ...