标签:
0 内容简介 1、课程大纲 2、第二部分第六课: 创建你自己的变量类型 3、第二部分第七课预告: 文件读写 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案。还会带大家用C语言编写三个游戏。 C语言编程基础知识 什么是编程? 工欲善其事,必先利其器 你的第一个程序 变量的世界 运算那点事 条件表达式 循环语句 实战:第一个C语言小游戏 函数 练习题 习作:完善第一个C语言小游戏 C语言高级技术 模块化编程 进击的指针,C语言王牌 数组 字符串 预处理 创建你自己的变量类型 文件读写 动态分配 实战:“悬挂小人”游戏 安全的文本输入 练习题 习作:用自己的语言解释指针 用基于C语言的SDL库开发2D游戏 安装SDL 创建窗口和画布 显示图像 事件处理 实战:“超级玛丽推箱子”游戏 掌握时间的使用 用SDL_ttf编辑文字 用FMOD控制声音 实战:可视化的声音谱线 练习题 数据结构 链表 堆,栈和队列 哈希表 练习题 第二部分第六课:创建你自己的变量类型 众所周知,C语言是面向过程的编程语言,与Java,C#等面向对象的编程语言有所不同。 在面向对象的编程语言中,有类的概念。C语言是没有类这种“类型”的,但是C语言就不能模拟“面向对象”编程了吗? 不,只要你设计得好,C语言也可以模拟面向对象编程。这一课我们学到的关于struct(结构体)的知识就可以使你有能力用C语言实现面向对象。 前面我们学习了指针,数组,字符串和预处理,掌握这些知识你的C语言水平已经还不错啦,但是我们岂能就此止步。C语言还可以让我们做一些更厉害的事情:创建你自己的变量类型。 我们可以将其称为“自定义的变量类型”,我们来看三种:struct,union和enum。 因为当你需要编写的程序比较复杂的时候,你会发现创建自定义的变量类型是很重要的。 幸好,这学起来其实也不是特别难。但是大家需要专心学习这一课,因为从下一课开始,我们会一直用到struct了。 定义一个struct 什么是struct呢,首先它是英语structure(结构)的简写。所以struct的专业术语是“结构体”。 定义:struct就是一系列变量的集合,但是这些变量可以是不同类型的。 这个定义是不是唤起了大家对我们的老朋友:数组的怀念啊。数组里面的每个成员都必须是同一个类型的,相比之下struct更灵活。 一般来说,我们习惯把struct定义在.h头文件中,也就是和预处理命令以及函数原型那群家伙在一起。 下面就给出一个struct的例子: struct 你的struct的名字 { char variable1; short variable2; int otherVariable; double numberDecimal; }; 可以看到:struct的定义以关键字struct开始,后面接你自定义的struct的名称(比如 Dog,Cat,Person等)。 一般来说,在我的代码里,我的struct的命名也是遵照变量的命名规则,唯有一点不一样,就是struct的名称我会将首字母大写,例如:SchoolName。 但是我的普通变量一般都是首字母小写,例如:studentNumber。这样做只是个人习惯,便于在代码里区分普通的变量和自定义的变量,之后会学到的enum和union,我也是习惯将其名称的首字母大写。 在struct的名字之后,我们需要写上一对大括号,在这对大括号里面放你的struct要包含的各种类型的变量。通常说来,struct的大括号内至少得定义两个变量吧,如果只有一个变量,那定义这个结构体也没什么意义。 注意:特别不要忘了,在大括号后面还要加上一个分号; 因为毕竟这个struct是一个变量,变量的定义最后都要加分号的。 如你所见,创建一个自定义的变量也不复杂么。其实结构体就是各种基本类型变量的集合,一个大杂烩。当然以后的课程中我们还会看到:结构体的嵌套定义(结构体里包含另一个结构体)。 结构体的例子 假设我们需要自定义一个结构体,它储存屏幕上的一个点的坐标。在之后第三部分“用C语言编写游戏”里面,会用到类似的结构。 下面就给出2D(D是英语dimension的首字母,表示维度)世界的坐标系的大致印象: 0 当我们在2D世界中做研究时,我们有两个轴:横坐标轴(从左到右,一般也称为x轴)和纵坐标轴(从下到上,一般也称为y轴)。只要数学还没有还给小学化学老师,应该都知道x和y轴的。 现在,你自己可以写出一个名叫Coordinate(英语“坐标”的意思)的struct的定义了吗?我看好你! 可以自己先写,然后对一下我们给出的参考: struct Coordinate { int x; // 横坐标 int y; // 纵坐标 }; 我们的Coordinate这个struct包含了两个变量:x和y,都是int类型,分别表示横坐标值和纵坐标值。 当然了,如果我们愿意,也可以创建一个表示3D(三维)空间的点的struct,只需要在刚才的Coordinate这个结构体的基础上加上z轴。 结构体里面的数组 结构体里面也可以存放数组。 可以构造一个名叫Person(英语“人”的意思)的结构体,如下所示: struct Person { char firstName[100]; // 名 char lastName[100]; // 姓 char address[1000]; // 地址 int age; // 年龄 int boy; // 布尔值 : 1 = boy(男孩), 0 = girl(女孩) }; 可以看到,这个结构体变量包含五个基本的变量。 前三个分别表示 名,姓和地址,是字符数组。第四个是年龄,第五个是一个“布尔值”(当然,C语言本身没有定义布尔类型(true或false),但是可以用数值来“表示”布尔值的真或假),boy这个int变量的值如果为1,那就是表示男孩,如果为0,那就是女孩。 这个结构体可以用于构建一个通讯录程序。当然,你大可以在这个结构体里再添加其他变量,使其更完善。在一个结构体里没有变量的数目限制。 结构体的使用 现在,我们的结构体已经定义在.h头文件里了,那么我们就可以在include(包含)此头文件的文件中使用这些结构体了。 以下展示如何创建一个类型为Coordinate(此结构体我们上面定义了,表示二维空间的坐标)的变量: #include "main.h" // 包含定义结构体的头文件 int main(int argc, char *argv[]) { struct Coordinate point; // 创建一个Coordinate类型的变量,名字是point(英语“点”的意思) return 0; } 如上,我们创建了一个Coordinate类型的变量,名字是point。这个变量自动拥有两个子变量:x和y,都是int类型,分别表示此二维坐标的横坐标值和纵坐标值。 你也许要问:“创建结构体变量开头的那个关键字struct是必须的吗?” 是的,必须的。“爱我,非你莫属...”不好意思又跑题了。。。 struct关键字使电脑能够区分基础变量类型(例如int)和自定义变量类型(例如Coordinate)。 然而,每次加struct关键字也有点麻烦,所以聪(懒)明(惰)伶(成)俐(性)的C语言开发者们设计了typedef关键字。当然了,人类的大多数发明都是为了“懒惰”的缘故,能提高效率谁不想啊。就是这个feel,倍爽儿。 typedef关键字 重新回到刚才定义Coordinate这个结构体的.h头文件中。我们来加一条由typedef开头的命令,目的是为Coordinate结构体创建一个别名。 什么是别名呢? 就是比如有一个人,真实姓名叫王小明,别名就可以是小明,明明,等,但都代表那个人。 有点类似C++的引用的机制。 所以对别名的操作就是对原先对象的操作。 我们就在Coordinate结构体的定义之前加这句命令吧,一般习惯加在后面的,但是加在前面也可以: typedef struct Coordinate Coordinate; struct Coordinate { int x; int y; }; 可以看到,我们新加了一行命令: typedef struct Coordinate Coordinate; 为了更好地理解这句命令的作用,我们把它拆为三部分来看: 1. typedef:说明我们将要创建一个别名 2. struct Coordinate:这是我们要为其创建别名的结构体 3. Coordinate:这就是别名 所以,上面这句命令的含义就是“从今以后,Coordinate就相当于struct Coordinate”。 这样做以后,我们就可以不用每次在创建一个新的Coordinate结构体的变量时都加上struct关键字了。 所以,我们的.c文件中就可以改写为: int main(int argc, char *argv[]) { Coordonnees point; // 因为有了typedef,电脑就清楚地知道此处的Coordinate其实就是“struct Coordinate” return 0; } 当然,不一定要都叫Coordinate,别名也可以叫:Coor。例如: typedef struct Coordinate Coor; struct Coordinate { int x; int y; }; Coor coor; // 创建一个变量 建议大家在平时定义了struct类型后,也加一句typdedef命令,这样在代码里就不用每次新建一个此类型的变量时都要在开头写struct关键字了。很多程序员都会这么做。因为一个好的程序员是懂得如何偷懒的程序员,这和一个懒惰的程序员是有区别的。我们要使我们的代码“write less,do more”(用尽量少的代码做更多的事)。 当然,上面的代码块可以简写为: typedef struct 名字 { // struct的内容 } 别名; 所以上面Coordinate的代码块可以简写为: typedef struct Coordinate { int x; int y; } Coordinate; 注意:之后我们的示例代码,有时会出现例如 Person player1; Coordinate point1; 这样的形式,那就是假定我们之前已经用了typedef了:typedef struct Person Person; 就可以省略开头的struct关键字,不需要再写成: struct Person player1; 修改struct的成员变量 既然我们的point变量(是Coordinate类型的,希望大家还没晕)已经创建好了,那我们就可以修改它的成员的值了。 我们如何访问point的两个成员x和y呢?如下所示: int main(int argc, char *argv[]) { Coordinate point; point.x = 10; point.y = 20; return 0; } 这样,我们就顺利地修改了point的两个成员的值,使其x坐标为10,y坐标为20。因此我们的点就位于坐标系的(10,20)处了。 所以,为了能访问到结构体的某个成员,我们可以这样做: 结构体实例名称.成员名 中间的点(.)表示“从属”关系。 如果有面向对象编程基础的朋友,就会觉得:这与“类和对象”也太像了吧,是的,其实我们可以用struct来“模拟”类。 如果我们用上面我们创建的Person这个结构体来举例子的话: int main(int argc, char *argv[]) { Person user; // user是英语“用户”的意思 printf("您姓什么 ? "); scanf("%s", user.lastName); printf("您名叫什么 ? "); scanf("%s", user.firstName); printf("原来你的名字是 %s%s,失敬失敬\n", user.lastName, user.firstName); return 0; } 运行输出: 您姓什么?王 您名叫什么?小明 原来您的名字是 王小明,失敬失敬。 我们把user.lastName传给scanf,使得用户输入的值直接修改user的lastName成员;我们对user.firstName也是如此。当然我们也可以再添加对address,age,boy的赋值。但是小编懒,就不继续啦。做程序员就是要会偷懒。 当然了,你也许会说:“我不知道结构体的使用,我用两个单独的字符串变量lastName和firstName不是也可以做到和上述程序相同的事么?” 是的,但是用结构体的好处就是我们可以创建此结构体的变量,将很多相关联的数据封装在一起,成为一个整体,而不是零散地定义。比如定义了Person这个结构体之后,凡是用Person来创建的变量,里面都自动包含了 lastName,firstName,address,age和boy这五个变量,非常方便。 比如我们可以这样创建: Person player1, player2; //之前肯定有用typedef,( typedef struct Person Person;) 在player1和player2中都包含lastName,firstName,address,age和boy这五个变量。 我们也可以更“偷懒”一些:创建结构体数组。例如: Person player[2]; 这样,我们就可以很方便的访问player[1]当中的变量了,例如: player[1].lastName = "xiaoming"; 用结构体数组的好处是可以方便地使用循环,等等。 自测小练习: 创建一个名叫ProgrammerLeague(程序员联盟)的结构体,在定义里放入你想创建的变量。然后创建此结构体的一个数组,用循环的方式给变量赋值,再用循环的方式打印出其中变量的信息。 结构体的初始化 之前的课程里,我们建议对于基本变量,数组和指针,最好在创建的时候对其初始化。结构体也不例外。初始化有一个很大的好处,就是避免此变量里存放“任意数据”。事实上,一个变量在创建时,如果没有初始化,它会取当时在内存那个位置所存的值,所以这个值的随机性是很大的。 我们来回忆一下,不同变量的初始化应该怎么做: 基础变量(int,double,char等):初始化为0 指针:初始化为NULL。事实上,NULL位于stdlib.h标准库头文件中,是用#define预处理命令定义的一个常量。它的值通常是0。虽然是0,但是有多种定义形式,例如: #define NULL 0 #define NULL 0L #define NULL ((void *) 0) 但是我们只要每次用NULL就好了,为了清楚表明这是指针变量,而不是一般变量。 数组:将每一个成员变量初始化为0 对于我们的朋友:结构体,我们怎么初始化呢? 其实结构体的初始化也很简单,与数组的初始化很类似。我们可以像下面这样定义: Coordinate point = {0, 0}; 这样,我们就依照顺序将point.x和point.y都初始化为0了。 对于像Person这样的结构体,里面的变量类型有 char型数组和int,那么我们可以将char型数组初始化为""(双引号中间为空)。我们可以像这样初始化一个字符串,在之前“字符串”那一课忘记提了。不过,我想现在提还不算晚吧。 所以我们就可以这样来初始化我们的Person结构体变量: Person player = {"", "", "", 0, 0}; 然而,我们也可以这样来初始化一个结构体变量。创建一个函数,比如叫initializeStruct,可以为每一个传递给它的结构体做初始化,这样就方便很多,特别是当结构体中的变量很多时。之前指针那一章我们也已经学了,如果我们对函数传递普通变量,那么因为C语言的函数参数传递方式是值传递,所以它会对传给它的函数参数做一份拷贝,所以函数里面修改的其实是那一份拷贝,真正的实参并没有被改变。为了让实参实实在在被修改,我们需要用到指针,也就是传递此变量的地址。对于结构体,也需要这样。因此,接下来我们就来学习如何使用结构体指针。开始要有点难咯。准备好了吗? 结构体指针 结构体指针的创建其实和普通的指针变量创建没什么区别。例如: Coordinate *point = NULL; 上面的代码就创建了一个叫做point的Coordinate结构体指针变量(Coordinate是我们上面定义的表示坐标的一个结构体)。 我们再来提醒一次: 一般推荐写成: Coordinate *point = NULL; // 星号挨着指针变量名字 而不推荐写成: Coordinate* point = NULL; // 星号挨着结构体名 在指针的创建中,我们推荐第一种写法,因为如果用第二种写法,如果你在一行上创建好几个指针变量时,会容易忘记在第二个之后的变量前加*号。例如,容易写成这样: Coordinate* point1 = NULL, point2 = NULL; 但这样编译会出错,因为point2其实是Coordinate结构体变量,而不是Coordinate结构体指针变量! 所以我们建议这样写: Coordinate *point1 = NULL, *point2 = NULL; 在以前的课程中,对于基础类型的指针变量,我们也是这样建议: int *number1 = NULL, *number2 = NULL; 特别是int型的指针,还很不容易察觉到错误,如果写成: int* number1 = NULL, number2 = NULL; 编译器是不会报错的。因为NULL的值就是0,可以赋给number2这个int型变量(注意:上面的number2不是int指针)。 好吧,回顾总是很好的,(伤心总是难免的...)。 结构体作为函数参数 这里,我们主要来学习如何将一个结构体指针(为什么是传结构体指针而不是传结构体,可以看之前的解释)传给一个函数(作为参数),使得函数内部可以真正修改此结构体。 我们来看一个实例: #include标签:
原文地址:http://www.cnblogs.com/frogoscar/p/4396845.html