C语言探索之旅:创建你自己的变量类型

内容简介

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的首字母,表示维度)世界的坐标系的大致印象:

当我们在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的代码块可以简写为:

typedefstruct 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(程序员联盟)的结构体,在定义里放入你想创建的变量。然后创建此结构体的一个数组,用循环的方式给变量赋值,再用循环的方式打印出其中变量的信息。

结构体的初始化

之前的课程里,我们建议对于基本变量,数组和指针,最好在创建的时候对其初始化。结构体也不例外。初始化有一个很大的好处,就是避免此变量里存放“任意数据”。事实上,一个变量在创建时,如果没有初始化,它会取当时在内存那个位置所存的值,所以这个值的随机性是很大的。

我们来回忆一下,不同变量的初始化应该怎么做:

  1. 基础变量(int,double,char等):初始化为0

  2. 指针:初始化为NULL。事实上,NULL位于stdlib.h标准库头文件中,是用#define预处理命令定义的一个常量。它的值通常是0。虽然是0,但是有多种定义形式,例如:

    #define NULL 0

    #define NULL 0L

    #define NULL ((void *) 0)

    但是我们只要每次用NULL就好了,为了清楚表明这是指针变量,而不是一般变量。

  3. 数组:将每一个成员变量初始化为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<stdio.h>

typedefstructCoordinate// Coordinate是英语“坐标”的意思

{

intx;//横坐标值

inty;//纵坐标值

} Coordinate;

voidinitializeStruct(Coordinate*point);//函数原型

intmain(intargc,char*argv[]){

CoordinatemyPoint;

initializeCoordinate(&myPoint);

return0;

}

//用于初始化结构体变量

voidinitializeCoordinate(Coordinate*point){

//放置结构体初始化的代码

}

上面的initializeCoordinate函数体内,我们将放置初始化结构体的成员变量的代码。

我们按顺序来看一下这段代码:

  1. 首先,我们定义了一个结构体,叫做Coordinate,里面包含两个变量,x和y。

  2. 我们在main函数中创建了Coordinate结构体的变量,名字叫myPoint。

  3. 我们将myPoint的地址传递给initializeCoordinate这个函数。

接下来,我们就在initializeCoordinate函数中添加初始化x和y变量的代码吧:

voidinitializeCoordinate(Coordinate*point){

*point.x = 0;

*point.y = 0;

}

point前面的*号是不可以少的噢,因为,传进函数的参数是一个结构体指针,我们要取到此结构体,就需要用到“解引用”符号:*号。

但是,认真的读者看出上面这个函数中的错误了吗?

我们的初衷是想要:先用*号解引用point这个结构体指针,取到结构体,然后再用.号取到其中的变量x和y。但是如果按上面的写法,其实效果相当于如下:

*(point.x) = 0;

*(point.y) = 0;

因为.号的优先级是高于*号的。有兴趣可以看一下运算符的优先级,不过之前的课我们也说过了,记不清怎么办呢?加括号就解决啦。

上面的代码编译是通不过的,因为结构体指针point并没有成员叫x和y,而且,对于结构体指针我们也不能用.号来取到什么值。

因此,我们需要修改一下。改为如下就可以了:

voidinitializeCoordinate(Coordinate*point){

(*point).x = 0;

(*point).y = 0;

}

这样就对了。用括号去掉了运算符优先级的影响。

但是,之前也说过:程序员是懂得偷懒的一群人。

如果每次要取结构体的成员变量都要这么麻烦,要用*号,还要加括号,再用.号。

想想都要让Denis Ritchie老爷子醉了。

他是决不允许这种事发生的,因此,他就定义了一个新的符号: ->

用法如下:

point->x = 0;

就相当于:

(*point).x = 0;

是不是简便了很多?

记住:这个符号,只能用在指针上面。

因此,我们的函数可以改写为:

voidinitializeCoordinate(Coordinate*point){

point->x=0;

point->y=0;

}

我们在main函数里也可以这样写:

int main(int argc, char *argv[])

{

Coordinate myPoint;

Coordinate *myPointPointer = &myPoint;

myPoint.x = 10; // 用结构体的方式,修改myPoint中的x值

myPointPointer->y = 15; // 用结构体指针的方式,修改myPoint中的y值

return 0;

}

结构体是C语言中一个非常好用,很重要的概念,希望大家好好掌握,当然还有不少知识细节就要大家自己去看C语言的经典教材了,例如《C程序设计语言》(不是谭浩强那本《C语言程序设计》),《C和指针》,《C专家编程》,《C语言深度解剖》,《C陷阱和缺陷》,等等。

union

union是术语“联合”的意思,是C语言的关键字,也有的书上翻译为“共用体”。

我们可以来写一个union的例子。

union ProgrammerLeague

{

char character;

int memberNumber;

double rate;

};

乍看之下,简直和struct没什么区别么。但是真的没有区别吗?

假如我们用sizeof关键字来测试此union的大小(大小指的是其在内存中所占的字节(byte)数,一个字节相当于8个bit(二进制位)):

#include<stdio.h>

typedefunionProgrammerLeague

{

charcharacter; //大小是1

intmemberNumber;//大小是4

doublerate; //大小是8

} ProgrammerLeague;

intmain(intargc,char*argv[]){

ProgrammerLeagueprogrammerLeague;

printf(\”此联合的大小是%lu\\n\”,sizeof(programmerLeague));

return0;

}

运行程序,输出:

此Union的大小是8

假如我们对结构体也做一次测试,对比一下:

#include<stdio.h>

typedefstructProgrammerLeague

{

charcharacter; //大小是1

intmemberNumber;//大小是4

doublerate; //大小是8

} ProgrammerLeague;

intmain(intargc,char*argv[]){

ProgrammerLeagueprogrammerLeague;

printf(\”此结构体的大小是%lu\\n\”,sizeof(programmerLeague));

return0;

}

运行程序,输出:

此Structure的大小是 16

为什么一个是8,而另一个是16呢?

这就涉及到union(共用体)和struct(结构体)的区别了。

struct的大小是其中所有变量大小的总和,但是你会说:“不对啊, 1+4+8 = 13,为什么sizeof(programmerLeague)的值为16呢?”

好问题!这个有点复杂,涉及到内存对齐的问题,我们以后再说。

或者有兴趣的读者可以去参考《C语言深度解剖》(网上可以搜到PDF)的解释。一般初学C语言不需要太深究这个问题。

在嵌入式编程等内存有限的环境下,需要考虑内存对齐,以节省空间。

union的大小等于其中最大(sizeof()最大)的那个变量的大小。所以我们就知道了,其实union的储存是这样的:

其中的每个变量在内存中的起始地址是一样的,所以union同一时刻只能存放其中一个变量,所以union的大小等于其中最大的那个变量,以保证可以容纳随便哪个成员。union适合用在很多相同类型的变量集,但是某一时刻只需用到其中一个的情况,比较节省空间。

enum

看完了struct(结构体)和union(联合),我们最后来学习很常用的一个自定义变量类型:enum。

enum是英语enumeration(枚举)的缩写,也是一个C语言关键字。

枚举是一个比较特别的自定义变量类型。当初学C语言时,一开始还真有点不理解。但用好了,却非常实用。

我们之前学了:结构体里面包含了多个可以是不同类型的成员变量(一说“成员”就有点面向对象的感觉 :P。咬字要清晰,不是“淳元”皇后那个郑淳元,是成员。小编你这样顽皮可不行啊…)。

但是enum(枚举)里面是一系列可选择的值。也就是说每次只能取其中一个值,听着和union有点类似啊。但是和union还是有区别的。

我们来举一个例子就知道区别了:

typedefenumShapeShape;

enumShape // shape是英语“身材、体型”的意思

{

THIN, // thin是英语“瘦”的意思

MEDIUM, // medium是英语“中等”的意思

FAT // fat是英语“胖”的意思

};

所以,我们定义了一个名叫 Shape的enum变量。其中有三个值,分别是THIN, MEDIUM和FAT(身材有瘦,中等和胖之分)。不一定要大写,只是习惯。

那我们怎么来创建enum变量呢?如下:

Shape shape = MEDIUM;

shape这个变量,我们在程序里,也可以再将其修改为 THIN或者FAT。

将数值赋给enum的成员

大家看到enum和union以及struct的区别了吗?是的,enum的定义里,每个成员没有变量类型(int,char,double之类)!

很奇怪吧。想起来为什么enum的成员习惯用大写了吗?

对,就是因为enum的每个成员都不是变量,而是常量。但是enum的机制和常量定义以及#define还是有些区别:

像上面的代码:

typedef enumShape

{

THIN,

MEDIUM,

FAT

} Shape;

编译器会自动为其中的每一个成员绑定一个常量值,我们写程序测试一下:

#include<stdio.h>

typedefenumShapeShape;

enumShape

{

THIN,

MEDIUM,

FAT

};

intmain(intargc,char*argv[]){

Shapeshape =THIN;

printf(\”THIN = %d\\n\”, shape);

shape =MEDIUM;

printf(\”MEDIUM = %d\\n\”, shape);

shape =FAT;

printf(\”FAT = %d\\n\”, shape);

return0;

}

运行程序,输出:

THIN = 0

MEDIUM = 1

FAT = 2

看到了吗?编译器自动给这三个成员赋了 0,1和2。如果没有指定enum成员的值,那么它们的值是从0开始,依次加1。

我们也可以自己来定义enum成员的值,不一定要每次让编译器给我们自动分配。

我们可以这样写:

typedef enumShape

{

THIN = 40,

MEDIUM = 60,

FAT = 90

} Shape;

这样,我们就自己给每个成员定义了值。

我们也可以让编译器为我们自动分配几个值,再自己定义几个值,例如:

typedef enumShape

{

THIN,

MEDIUM,

FAT = 90

} Shape;

上面,我们没有为THIN和MEDIUM赋值,那么编译器会给他们赋值为0和1,而FAT,因为我们已经指定了其值为90,所以FAT就等于90。

enum和#define的区别

是不是觉得enum和用#define来定义的常量是有些类似呢?

其实,还是有些不同的:

  1. #define 宏常量(或 预处理常量)是在预处理阶段进行简单替换,枚举常量则是在编译的时候确定其值。

  2. 一般在编译器里,可以调试枚举常量,但是不能调试宏常量。

  3. 枚举可以一次定义大量相关的常量,而#define 宏一次只能定义一个。

总结

  1. 结构体(struct)是一种自定义的变量类型,完全由我们自由发挥,自己定制。与int,double等基础变量类型有所区别。结构体的使用可以使我们的C语言程序更加灵活,可以做更多事。

  2. 结构体里包含成员变量,通常是基础变量类型的变量,如int,double等变量,但也可以有指针变量,数组,甚至其他的结构体变量。

  3. 为了访问到结构体的成员变量,我们可以用普通的结构体方式访问:结构体变量名称.成员变量名

  4. 我们也可以用特别简便的结构体指针的方式来方位我们的结构体的成员变量:结构体指针变量名->成员变量名

  5. union(“共用体”,或“联合”)和struct的最大不同就是:union的大小是其中容量最大的那个成员变量的大小,而结构体的大小是每一个成员变量的总和(还要考虑内存对齐),union一次只能取其中一个变量。

  6. enum(枚举)一次只能取其中的一个成员的值,这一点和union有些类似,但是enum的成员都是常量,而不是变量。而且enum的成员如果没有指定数值,编译器会按照递增顺序为每一个变量赋值,从0开始。

第二部分第七课预告:

今天的课就到这里,一起加油咯。

下一次我们学习:文件读写

您若觉得本文不错,请点击“分享”

*新朋友请关注「程序员联盟」微信搜公众号 ProgrammerLeague

小编微信号: frogoscar

小编邮箱: enmingx@gmail.com

程序员联盟微信群和QQ群:先加我微信

C语言程序设计(谭浩强第五版) 第5章 循环结构程序设计 习题解析答案

你也可以上程序咖(https://meta.chengxuka.com),打开大学幕题板块,不但有答案,讲解,还可以在线答题。

题目1:请画出例 5.6 中给出的3个程序段的流程图。

解∶下面分别是教材第5章例5.6给出的程序,据此画出流程图。

(1)程序1:

运行结果:

其对应的流程图见图5. 1。

(2)程序2:

运行结果:

遇到第3行第1列时,执行 break,结束内循环,进行第 4 次外循环。

其对应的流程图见图 5.2 。

(3)程序 3:

运行结果:

遇到第3行第1列时,执行continue,只是提前结束本次内循环,不输出原来的第3行第1列的数3,而进行下一次内循环,接着在该位置上输出原来的第 3行第 2列的数6。

请仔细区分 break 语句和 continue 语句。

其对应的流程图见图 5.3。

题目2:请补充例 5.7 程序,分别统计当\” fabs(t)>=1e-6\”和\”fabs(t)>=1e-8\” 时执行循环体的次数。

解:

例5.7 程序是用

π4≈1−13+15−17+…

公式求 π 的近似值,直到发现某一项的绝对值小于 10-6 为止。根据本题要求,分别统计当 fabs(t)>=1e-6 和 fabs(t)>=1e-8 时,执行循环体的次数。

(1)采用fabs(t)>=le-6作为循环终止条件的程序补充修改如下∶

运行结果:

执行50万次循环。

(2) 采用fabs(t)>= 1e-8作为循环终止条件的程序,只需把上面程序的第8行如下修改即可:

运行结果:

执行5000万次循环。

题目3:输入两个正整数 m 和 n,求其最大公约数和最小公倍数。

解:

答案代码:

运行结果:

题目4:输入一行字符,分别统计出其中英文字母、空格、数字和其他字符的个数。

解:

答案代码:

运行结果:

题目5:求 个Sn=a+aa+aaa+⋯+aa…a⏞n个a 之值,其中a是一个数字,n表示a的位数,n由键盘输入。例如:2+22+222+2222+22222 (此时 n=5)

解:

答案代码:

运行结果:

题目6:求 ∑n=120n!​ (即求1!+2!+3!+4!+…+20!)。

解:

答案代码:

运行结果:

请注意:s 不应定义为 int 型或 long 型,因为在用 Turbo C 或 Turbo C++ 等编译系统时,int 型数据在内存占 2个字节,整数的范围为-32768~32767,long 数据在内存占 4 个字节,整数的范围为 -21亿~21亿。用Visual C++ 6.0 时,int 型和 long 型数据在内存都占4 个字节,数据的范围为-21亿~21 亿。无法容纳求得的结果。今将 s 定义为 double 型,以得到更多的精度。在输出时,用 22.15e 格式,使数据宽度为 22,数字部分中小数位数为15位。

题目7:求 ∑k=1100k+∑k=150k2+∑k=1101k​ 。

解:

答案代码:

运行结果∶

题目8:输出所有的\”水仙花数\”,所谓\”水仙花数\”是指—个 3位数,其各位数字立方和等于该数本身。例如,153是水仙花数,因为 153=13+53+33​ 。

解:

答案代码:

运行结果:

题目9:一个数如果恰好等于它的因子之和,这个数就称为\”完数\”。例如,6的因子为1,2,3,而 6=1+2+3 ,因此 6 是\”完数\”。编程序找出 1000 之内的所有完数,并按下面格式输出其因子:

6 its factors are 1,2,3

解:方法一。

答案代码:

运行结果:

方法二。

答案代码:

运行结果:

题目10:有一个分数序列

21,32,53,85,138,2113…

求出这个数列的前20项之和。

解∶

答案代码:

运行结果∶

题目11:一个球从100m高度自由落下,每次落地后反弹回原高度的一半,再落下,再反弹。求它在第 10 次落地时共经过多少米,第 10次反弹多高。

解∶

答案代码;

运行结果∶

题目12:猴子吃桃问题。猴子第1天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了一个。第 2天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。到第10天早上想再吃时,就只剩一个桃子了。求第1天共摘多少个桃子。

解:

答案代码:

运行结果∶

题目13:用迭代法求 x=a​ 。求平方根的迭代公式为

xn+1=12(xn)+axn

要求前后两次求出的 x​ 的差的绝对值小于 10−5​ 。

解:

用迭代法求平方根的算法如下∶

(1)设定一个 x​ 的初值 x0​ ;

(2)用以上公式求出 x​ 的下一个值 x1​ ;

(3)再将 x1​ 代入以上公式右侧的 xn​ ,求出 x​ 的下一个值 x2​ ;

(4)如此继续下去,直到前后两次求出的 x​ 值( x​ 和 xn+1​ )满足以下关系:

|xn+1−xn|<10−5

为了便于程序处理,今只用 x0​ 和 x1​ ,先令 x​ 的初值 x0=a/2​ (也可以是另外的值),求出 x1​ ;如果此时 |x1−x0|≥10−5​ 就使 x1⇒x0​ ,然后用这个新的 x0​ 求出下一个 x1​ ;如此反复,直到 |x1−x0|<10−5​ 为止。

答案代码:

运行结果∶

题目14:用牛顿迭代法求下面方程在1.5附近的根:

2×3−4×2+3x−6=0

解:

牛顿迭代法又称牛顿切线法,它采用以下的方法求根:先任意设定一个与真实的根接近的值 x0​ 。作为第 1 次近似根,由 x0​ 求出 f(x0)​ ,过 (x0,f(x0))​ 点做 f(x)​ 的切线,交 x​ 轴于 x1​ ,把 x1​ 作为第 2 次近似根,再由 x1​ 求出 f(x1)​ ,过 (x1,f(x1))​ 点做 f(x)​ 的切线,交 x​ 轴于 x2​ ,再求出 f(x2)​ ,再作切线……如此继续下去,直到足够接近真正的根 x∗​ 为止,见图5.4。

从图5.4可以看出:

f′(x0)=f(x0)x1−x0

因此

x1=x0−f(x0)f′(x0)

这就是牛顿迭代公式。可以利用它由 x0​ 求出 x1​ ,然后由 x1​ 求出 x2​ ……

在本题中:

f(x)=2×3−4×2+3x−6

可以写成以下形式:

f(x)=((2x−4)x+3)x−6

同样,f′(x)​ 可写成:

f′(x)=6×2−8x+3=(6x−8)x+3

用这种方法表示的表达式在运算时可节省时间。例如,求 f(x)​ 只需要进行 3 次乘法和 3 次加法,而原来的表达式要经过多次指数运算、对数运算和乘法、加法运算,花费时间较多。

但是由于计算机的运算速度越来越快,这点时间开销是微不足道的。这是以前计算机的运算速度较慢时所提出的问题。由于过去编写的程序往往采用了这种形式,所以在此也顺便介绍一下,以便在阅读别人所写的程序时知其所以然。

答案代码:

运行结果∶

为了便于循环处理,程序中只设了变量 x0 和 x1,x0 代表前一次的近似根,x1代表后一次的近似根。在求出一个x1 后,把它的值赋给x0,然后用它求下一个x1。由于第1次执行循环体时,需要对 x0 赋值,故在开始时应先对 x1 赋一个初值(今为1.5,也可以是接近真实根的其他值)。

题目15:用二分法求下面方程在(-10,10)的根:

2×3−4×2+3x−6=0

解:

二分法的思路为∶先指定一个区间 [x1,x2]​ ,如果函数 f(x)​ 在此区间是单调变化,可以根据 f(x1)​ 和 f(x2)​ 是否同符号来确定方程 f(x)=0​ 在 [x1,x2]​ 区间是否有一个实根。若 f(x1)​ 和 f(x2)​ 不同符号,则 f(x)=0​ 在 [x1,x2]​ 区间必有一个(且只有一个)实根; 如果 f(x1)​ 和 f(x2)​ 同符号,说明在[x1,x2]​ 区间无实根,要重新改变 x1​ 和 x2​ 的值。当确定 [x1,x2]​ 有一个实根后,采取二分法将 [x1,x2]​ 区间一分为二,再判断在哪一个小区间中有实根。如此不断进行下去,直到小区间足够小为止,见图5.5。

算法如下:

(1)输入 x1​ 和 x2​ 的值。

(2)求出 f(x1)​ 和 f(x2)​ 。

(3)如果 f(x1)​ 和 f(x2)​ 同符号,说明在 [x1,x2]​ 区间无实根,返回(1),重新输入 x1​ 和 x2​ 的值; 若 f(x1)​ 和 f(x2)​ 不同符号,则在 [x1,x2]​ 区间必有一个实根,执行(4)。

(4)求 x1​ 和 x2​ 间的中点:x0=x1+x22​ 。

(5)求出 f(x0)​ 。

(6)判断 f(x0)​ 和 f(x1)​ 是否同符号。

①如同符号,则应在 [x0,x2]​ 中去找根,此时 x1​ 已 不起作用,用 x0​ 代替 x1​,用 f(x0)​ 代替 f(x1)​ 。

②如用 f(x0)​ 与 f(x1)​ 不同符号,说明应在 [x1,x0]​ 中去找根,此时 x2​ 已不起作用,用 x0​ 代替 x2​ ,用 f(x0)​ 代替 f(x2)​ 。

(7)判断 f(x0)​ 的绝对值是否小于某一个指定的值(例如 10−5​ )。若不小于 10−5​ ,就返回(4),重复执行(4)、(5)、(6);若小于 10−5​ ,则执行(8)。

(8)输出 x0​ 的值,它就是所求出的近似根。

N-S图见图5.6。

答案代码:

运行结果:

题目16:输出以下图案:

解:

答案代码:

运行结果:

题目17:两个乒乓球队进行比赛,各出3人。甲队为A,B,C3人,乙队为X,Y,Z3人。已抽签决定比赛名单。有人向队员打听比赛的名单,A说他不和 X 比,C说他不和 X,Z比,请编程序找出3对赛手的名单。

解:

先分析题目。按题意,画出图5.7的示意图。

图5.7中带 ×​ 符号的虚线表示不允许的组合。从图中可以看到∶①X既不与 A比赛,又不与C比赛,必然与B比赛。②C既不与X比赛,又不与Z比赛,必然与Y比赛。③剩下的只能是A与Z比赛,见图5.8。

以上是经过逻辑推理得到的结论。用计算机程序处理此问题时,不可能立即就得出结论,而必须对每一种成对的组合一一检验,看它们是否符合条件。开始时,并不知道A,B,C与X,Y,Z中哪一个比赛,可以假设∶A与i比赛,B与j比赛,C与k 比赛,即∶

A—i,

B—j,

C—k

i,j,k分别是X,Y,Z之一,且i,j,k 互不相等(一个队员不能与对方的两人比赛),见图5.9。

外循环使 i 由 \’X\’ 变到 \’Z\’ ,中循环使 j 由 \’X\’ 变到 \’Z\’(但 i 不应与 j 相等)。然后对每一组 i、j 的值,找符合条件的k 值。k 同样也可能是 \’X\’、\’Y\’、\’Z\’ 之一,但 k 也不应与 i 或 j 相等。在 i≠j≠k 的条件下,再把 i≠\’X\’ 和 k≠\’X\’ 以及k≠\’Z\’ 的 i,j,k的值输出即可。

答案代码:

运行结果∶

说明:

(1)整个执行部分只有一个语句,所以只在语句的最后有一个分号。请读者弄清楚循环和选择结构的嵌套关系。 (2)分析最下面一个if语句中的条件;i≠\’X\’,k≠\’X\’,k≠\’Z\’,因为已事先假定 A—i,B—j,C—k,由于题目规定 A不与X对抗,因此i不能等于\’X\’,同理,C不与X,Z对抗,因此k 不应等于\’X\’和\’Z\’。

(3)题目给的是 A,B,C,X,Y,Z,而程序中用了加撇号的字符常量\’X\’,\’Y\’,\’Z\’,这是为什么?这是为了在运行时能直接输出字符A,B,C,X,Y,Z,以表示 3组对抗的情况。

一起学《C程序设计》第一课——C语言概述和学习前的准备、意识

第一节课我们先了解C语言的一些基本常识和概念性;作为一个纯小白,首次接触编程需要做好一些心理准备,消除一些思想上的误区,避免一些弯路;然后准备好硬件设备(电脑)和软件(代码编辑器、编译器等),顺便体验敲一段代码和编译运行的快感。

注意,请认真学习完《C程序设计(第五版)》第一章后再阅读本文会有更大的收获。

计算机是由各种硬件组成,而让这些硬件工作靠的是程序。程序是一个总称,它由N多条的指令构成,每一条单独的指令去和硬件交互直至执行完所有的指令——执行完整个程序,最终输出结果。明白程序的基本概念后,是不是觉得写一个程序很简单?就是把一堆指令凑在一起,so easy?

但是计算机毕竟不是人,它只能识别它自己的语言——机器语言,就是二进制代码,由0和1组成,可长可短,随意组合。如果把我们日常的行为操作指令都转换成二进制,那可太费事了,所以经过人们的不断探索和迭代,更加高级的“指令”诞生了。所谓高级,指的是和我们人的行为习惯保持一致,不用把指令转换成二进制,就像现实世界人与人交流一样去编写“指令”——高级编程语言的代码,在机器执行这些高级代码之前通过编译器把它们编译成机器识别的二进制即可,这样大大提高了编程的效率和程序的可移植性。

C语言是高级语言吗

C语言是高级语言。我们通常讲的高级语言是相对于汇编语言来说,不直接和底层硬件打交道;但是C语言自身又可以去做底层硬件的开发,比如驱动程序、单片机系统开发,这又让C语言显得不那么高级。

时至今日,随着编程语言的不断发展,和人交互更加自然流畅的语言我们成为高级,但是他们大多数底层的实现还是靠C语言的,学好了C,你也可以开发出更高级的语言。

C语言有什么用处

C语言是偏后端的语言,不像前端那样学习很快就能看到网页、APP界面那样有成就感,看起来好像离我们挺远。

那C语言究竟能做什么呢?往大了说,可以开发操作系统,可以开发出其他高级语言,做硬件驱动开发;往小了说,可以做个GUI软件(有图形界面的软件),帮我们算一道数学题。

其实,作为现代编程语言的基础,学习C语言能更好的让我们了解计算机系统,也能更好地去学习更高级的编程语言。以C语言作为学习编程的入门语言,你肯定不会吃亏,会让你今后在编程上的学习受益终身。

C语言要背代码吗

任何编程语言的学习都不要背代码。学编程不是靠背,一些语法规则,控制结构在你熟练之后会得心应手,通篇背代码除了应付一下考试之外别无用处。甚至你也不用刻意去死记硬背一些常用的库和原生的函数等,编程是为了解决问题,不是纯粹为了编程而编程。

工作中,在做项目写代码的时候,没有人能打包票什么函数都能记得住,什么类库都能得心应手的使用;往往大部分时间花在技术调研,Google搜索和浏览一些技术社区上。既然不背代码那背什么呢?答案是记住编程思路,转换为己用

C语言学习难吗

对于初学者来说,学习一个全新的技能尤其是虚拟世界触碰不到的都会有相当的困难。但是只要坚定信念,踏实学习,不要以应付考试为目的,带着兴趣去学,按照书本的学习路线进行下去不会有太大的障碍。过程中会牵扯一些计算机特有的概念和知识点,要多去了解和探索,知其然更要知其所以然,对未知领域有渴求和探索欲,“难”不在话下。

硬件

普通台式电脑或者笔记本都可以。Windows 系统或者 Mac 系统均可。

软件

去windows官网下载Visual Studio:https://visualstudio.microsoft.com/zh-hans/,它自带编译环境,不用我们手动再去配置,这个对初学者来说很友好,通常软件环境的搭建就会把一小部分人劝退了。

安装选择 Community 个人免费版即可,过程中勾选“使用C++的桌面开发”,更加详细的安装教程请自行网络搜索。

安装Visual Studio

勾选“使用C++的桌面开发”

PS:注意更改一下安装目录,软件体积较大,默认安装在系统盘。

Hello World

按照国际惯例我们写一个简单的输出“Hello World”程序。新建一个空白项目,然后在源文件里添加“新建项”,选择“C++文件(.cpp)”,起名helloWorld改后缀为.c,编辑helloWorld.c文件。

添加新建项

新建helloWorld.c文件

编写代码

运行输出

怎样,有趣好玩不?如果此刻激发了你对C语言、编程的兴趣,那就动手实操一下吧。

往期文章

本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com

点赞 0
收藏 0

文章为作者独立观点不代本网立场,未经允许不得转载。