轻量级的面向对象C语言编程框架LW_OOPC介绍

轻量级的面向对象C语言编程框架LW_OOPC介绍
轻量级的面向对象C语言编程框架LW_OOPC介绍

轻量级的面向对象C语言编程框架LW_OOPC介绍

摘要:

本文介绍一种轻量级的面向对象的C语言编程框架:LW_OOPC。LW_OOPC是Light-Weight Object-Oriented Programming in(with) C的缩写,总共一个.h文件,20个宏,约130行代码,非常的轻量级,但却很好的支持了很多面向对象的特性,比如继承、多态,可以优美的实现面向接口编程。

为什么要用面向对象?

面向过程方式开发的系统,代码复杂,耦合性强,难以维护,随着我们所要解决的问题越来越复杂,代码也变得越来越复杂,越来越难以掌控,而面向对象改变了程序员的思维方式,以更加符合客观世界的方式来认识世界,通过合理的运用抽象、封装、继承和多态,更好的组织程序,从而很好地应对这种复杂性。

为什么不直接使用C++?

C和C++之争由来已久,可能要持续到它们中的一种去世^_^。C语言以其简洁明快,功能强大的特点,深得开发人员的喜爱,尤其是在嵌入式开发领域,C语言更是占据了绝对老大的地位。在我看来,语言只是工具,作为程序员,我们要做的是:选择合适的语言,解决恰当的问题。我们要尊重事实,考虑开发环境(软硬件环境),考虑团队成员的水平,从商用工程的角度讲,选择团队成员擅长的语言进行开发,风险要小很多。

一些从Java/C#转到C的程序员们,无法从面向对象切换到面向过程,但又必须与C语言同事们在遗留的C系统上开发软件,他们有时会非常困惑:C语言是面向过程的编程语言,如何实践面向对象,甚至面向接口编程呢?此时,就非常需要在C语言中实现面向对象的手段,而LW_OOPC正是应对这一难题的解决之道。

LW_OOPC是什么?

简而言之:LW_OOPC是一套C语言的宏,总共1个.h文件(如果需要内存泄漏检测支持以及调试打印支持,那么还需要1个.c文件(lw_oopc.c,约145行)),20个宏,约130行代码。LW_OOPC是一种C语言编程框架,用于支持在C语言中进行面向对象编程。

LW_OOPC宏介绍

下面,先通过一个简单的示例来展示LW_OOPC这套宏的使用方法。我们要创建这样一些对象:动物(Animal),鱼(Fish),狗(Dog),车子(Car)。显然,鱼和狗都属于动物,都会动,车子也会动,但是车子不是动物。会动是这些对象的共同特征,但是,显然它们不属于一个家族。因此,我们首先考虑抽象出一个接口(IMoveable),以描述会动这一行为特征:

INTERFACE(IMoveable)

{

void (*move)(IMoveable* t); // Move行为

};

INTERFACE宏用于定义接口,其成员(方法)均是函数指针类型。

然后,我们分析Animal,它应该是抽象类还是接口呢?动物都会吃,都需要呼吸,如

果仅仅考虑这两个特征,显然可以把Animal定为接口。不过,这里,为了展示抽象类在LW_OOPC中如何应用。我们让Animal拥有昵称和年龄属性,并且,让动物和我们打招呼(sayHello方法),但,我们不允许用户直接创建Animal对象,所以,这里把Animal定为抽象类:

ABS_CLASS(Animal)

{

char name[128]; // 动物的昵称(假设小于128个字符)

int age; // 动物的年龄

void (*setName)(Animal* t, const char* name); // 设置动物的昵称

void (*setAge)(Animal* t, int age); // 设置动物的年龄

void (*sayHello)(Animal* t); // 动物打招呼

void(*eat)(Animal* t); // 动物都会吃(抽象方法,由子类实现)void(*breathe)(Animal* t); // 动物都会呼吸(抽象方法,由子类实现)void (*init)(Animal* t, const char* name, int age); // 初始化昵称和年龄};

ABS_CLASS宏用于定义抽象类,允许有成员属性。代码的含义参见代码注释。

紧接着,我们来定义Fish和Dog类,它们都继承动物,然后还实现了IMoveable接口:CLASS(Fish)

{

EXTENDS(Animal); // 继承Animal抽象类

IMPLEMENTS(IMoveable); // 实现IMoveable接口

void (*init)(Fish* t, const char* name, int age); // 初始化昵称和年龄

};

CLASS(Dog)

{

EXTENDS(Animal); // 继承Animal抽象类

IMPLEMENTS(IMoveable); // 实现IMoveable接口

void(*init)(Dog* t, const char* name, int age); // 初始化昵称和年龄

};

为了让Fish对象或Dog对象在创建之后,能够很方便地初始化昵称和年龄,Fish和Dog 类均提供了init方法。

下面,我们来定义Car,车子不是动物,但可以Move,因此,让Car实现IMoveable 接口即可:

CLASS(Car)

{

IMPLEMENTS(IMoveable); // 实现IMoveable接口(车子不是动物,但可以Move)

};

接口,抽象类,具体类的定义都已经完成了。下面,我们开始实现它们。接口是不需要

实现的,所以IMoveable没有对应的实现代码。Animal是抽象动物接口,是半成品,所以需要提供半成品的实现:

/* 设置动物的昵称*/

void Animal_setName(Animal* t, const char* name)

{

// 这里假定name不会超过128个字符,为简化示例代码,不做保护(产品代码中不要这样写)

strcpy(t->name, name);

}

/* 设置动物的年龄*/

void Animal_setAge(Animal* t, int age)

{

t->age = age;

}

/* 动物和我们打招呼*/

void Animal_sayHello(Animal* t)

{

printf("Hello! 我是%s,今年%d岁了!\n", t->name, t->age);

}

/* 初始化动物的昵称和年龄*/

void Animal_init(Animal* t, const char* name, int age)

{

t->setName(t, name);

t->setAge(t, age);

}

ABS_CTOR(Animal)

FUNCTION_SETTING(setName, Animal_setName);

FUNCTION_SETTING(setAge, Animal_setAge);

FUNCTION_SETTING(sayHello, Animal_sayHello);

FUNCTION_SETTING(init, Animal_init);

END_ABS_CTOR

这里出现了几个新的宏,我们逐个进行讲解。ABS_CTOR表示抽象类的定义开始,ABS_CTOR(Animal)的含义是Animal抽象类的“构造函数”开始。在C语言里边其实是没有C++中的构造函数的概念的。LW_OOPC中的CTOR系列宏(CTOR/END_CTOR,ABS_CTOR/END_ABS_CTOR)除了给对象(在C语言中是struct实例)分配内存,然后,紧接着要为结构体中的函数指针成员赋值,这一过程,也可以称为函数绑定(有点类似C++中的动态联编)。函数绑定的过程由FUNCTION_SETTING宏来完成。

对于Fish和Dog类的实现,与Animal基本上是类似的,除了将ABS_CTOR换成了CTOR,直接参见代码:

/* 鱼的吃行为 */

void Fish_eat(Animal* t)

{

printf("鱼吃水草!\n");

}

/* 鱼的呼吸行为 */

void Fish_breathe(Animal* t)

{

printf("鱼用鳃呼吸!\n");

}

/* 鱼的移动行为 */

void Fish_move(IMoveable* t)

{

printf("鱼在水里游!\n");

}

/* 初始化鱼的昵称和年龄 */

void Fish_init(Fish* t, const char* name, int age) {

Animal* animal = SUPER_PTR(t, Animal);

animal->setName(animal, name);

animal->setAge(animal, age);

}

CTOR(Fish)

SUPER_CTOR(Animal);

FUNCTION_SETTING(Animal.eat, Fish_eat);

FUNCTION_SETTING(Animal.breathe, Fish_breathe); FUNCTION_SETTING(IMoveable.move, Fish_move); FUNCTION_SETTING(init, Fish_init);

END_CTOR

上面是Fish的实现,下面看Dog的实现:

/* 狗的吃行为 */

void Dog_eat(Animal* t)

{

printf("狗吃骨头!\n");

}

/* 狗的呼吸行为 */

void Dog_breathe(Animal* t)

{

printf("狗用肺呼吸!\n");

}

/* 狗的移动行为 */

void Dog_move(IMoveable* t)

{

printf("狗在地上跑!\n");

}

/* 初始化狗的昵称和年龄 */

void Dog_init(Dog* t, const char* name, int age)

{

Animal* animal = SUPER_PTR(t, Animal);

animal->setName(animal, name);

animal->setAge(animal, age);

}

CTOR(Dog)

SUPER_CTOR(Animal);

FUNCTION_SETTING(Animal.eat, Dog_eat);

FUNCTION_SETTING(Animal.breathe, Dog_breathe);

FUNCTION_SETTING(IMoveable.move, Dog_move);

FUNCTION_SETTING(init, Dog_init);

END_CTOR

细心的朋友可能已经注意到了,这里又有一个陌生的宏:SUPER_CTOR未介绍。这个宏是提供给子类用的,用于调用其直接父类的构造函数(类似Java语言中的super()调用,在这里,其实质是要先调用父类的函数绑定过程,再调用自身的函数绑定过程),类似Java 那样,SUPER_CTOR如果要出现,需要是ABS_CTOR或者CTOR下面紧跟的第一条语句。

最后,我们把Car类也实现了:

void Car_move(IMoveable* t)

{

printf("汽车在开动!\n");

}

CTOR(Car)

FUNCTION_SETTING(IMoveable.move, Car_move);

END_CTOR

下面,我们实现main方法,以展示LW_OOPC的威力 :

#include"animal.h"

int main()

{

Fish* fish = Fish_new(); // 创建鱼对象

Dog* dog = Dog_new(); // 创建狗对象

Car* car = Car_new(); // 创建车子对象

Animal* animals[2] = { 0 }; // 初始化动物容器(这里是Animal指针数组)

IMoveable* moveObjs[3] = { 0 }; // 初始化可移动物体容器(这里是IMoveable指针数组)

int i = 0; // i和j是循环变量

int j = 0;

// 初始化鱼对象的昵称为:小鲤鱼,年龄为:1岁

fish->init(fish, "小鲤鱼", 1);

// 将fish指针转型为Animal类型指针,并赋值给animals数组的第一个成员

animals[0] = SUPER_PTR(fish, Animal);

// 初始化狗对象的昵称为:牧羊犬,年龄为:2岁

dog->init(dog, "牧羊犬", 2);

// 将dog指针转型为Animal类型指针,并赋值给animals数组的第二个成员

animals[1] = SUPER_PTR(dog, Animal);

// 将fish指针转型为IMoveable接口类型指针,并赋值给moveOjbs数组的第一个成员 moveObjs[0] = SUPER_PTR(fish, IMoveable);

// 将dog指针转型为IMoveable接口类型指针,并赋值给moveOjbs数组的第二个成员 moveObjs[1] = SUPER_PTR(dog, IMoveable);

// 将car指针转型为IMoveable接口类型指针,并赋值给moveOjbs数组的第三个成员 moveObjs[2] = SUPER_PTR(car, IMoveable);

// 循环打印动物容器内的动物信息

for(i=0; i<2; i++)

{

Animal* animal = animals[i];

animal->eat(animal);

animal->breathe(animal);

animal->sayHello(animal);

}

// 循环打印可移动物体容器内的可移动物体移动方式的信息

for(j=0; j<3; j++)

{

IMoveable* moveObj = moveObjs[j];

moveObj->move(moveObj);

}

lw_oopc_delete(fish);

lw_oopc_delete(dog);

lw_oopc_delete(car);

return 0;

}

从上边的代码中,我们惊喜地发现,在C语言中,借助LW_OOPC,我们实现了将不同的动物(Fish和Dog对象)装入Animal容器,然后可以用完全相同的方式调用Animal 的方法(比如eat和breathe方法),而实际调用的是具体的实现类(Fish和Dog)的对应方法。这正是面向对象中的多态的概念。同样,我们可以将Fish对象,Dog对象,以及Car 对象均视为可移动物体,均装入IMoveable容器,然后用完全相同的方式调用IMoveable接口的move方法。看到了吗?借助LW_OOPC,在C语言下我们竟然可以轻松地实现面向对象和面向接口编程!

下面,再举一个稍微复杂的例子,它的覆盖面是足够全面的,足以一瞥面向对象编程的3个要素:数据抽象、继承和多态。通过这个例子,我们期望展现出LW_OOPC在遭遇问题本身比较复杂的情形下,是如何从容应对的,以加深读者对LW_OOPC的认识。(备注:该问题来自《C++沉思录》第八章的例子,有兴趣的读者可以对照参阅)

问题描述:

此程序涉及的内容是用来表示算术表达式的树。例如,表达式(-5)*(3+4)对应的树为:

一个表达式树包括代表常数、一元运算符和二元运算符的节点。这样的树结构在编译器和计算器程序中都可能用到。

我们希望能通过调用合适的函数来创建这样的树,然后打印该树的完整括号化形式。例如,我们希望

#include"stdio.h"

#include"Expr.h"

int main()

{

Expr* expr1 = Expr_new();

Expr* expr2 = Expr_new();

Expr* expr3 = Expr_new();

Expr* expr = Expr_new();

expr1->initUnaryX(expr1, "-", 5);

expr2->initBinaryX(expr2, "+", 3, 4);

expr3->initBinary(expr3, "*", expr1, expr2);

expr->initBinary(expr, "*", expr3, expr3);

expr3->print(expr3);

printf("\n");

expr->print(expr);

printf("\n");

Expr_delete(expr);

Expr_delete(expr3);

Expr_delete(expr2);

Expr_delete(expr1);

return 0;

}

打印

((-5)*(3+4))

(((-5)*(3+4))*((-5)*(3+4)))

作为输出。此外,我们不想为这些表达式的表示形式操心,更不想关心有关它们内存分配和回收的事宜。

这个程序所做的事情在很多需要处理复杂输入的大型程序中是很典型的,例如编译器、编辑器、CAD/CAM系统等。此类程序中通常要花费很大的精力来处理类似树、图和类似的数据结构。这些程序的开发者永远需要面对诸如内存分配、灵活性和效率之类的问题。面向对象技术可以把这些问题局部化,从而确保今后发生的一系列变化不会要求整个程序中的其他各个部分随之做相应调整。

解决方案:

通过考查这个树结构,会发现这里有3种节点。一种表示整数表达式,包含一个整数值,无子节点。另外两个分别表示一元表达式和二元表达式,包含一个操作符,分别有一个或两个子节点。我们希望打印各种节点,但是具体方式需要视要打印节点的类型而定。这就是动态绑定的用武之地了:我们可以定义一个虚函数(print)来指明应当如何打印各种节点。动态绑定将会负责在运行时基于打印节点的实际类型调用正确的函数。

首先,我们抽象出“节点”的概念,抽象类的名字定为Expr_node,它提供了打印的抽象接口,所有的实际节点类型均从它派生:

ABS_CLASS(Expr_node)

{

void (*print)(Expr_node* t);

};

具体类的情形怎样?这些具体类型中最简单的一类是包含一个整数,没有子节点的节点:

CLASS(Int_node)

{

EXTENDS(Expr_node);

int n;

void (*init)(Int_node* t, int k);

};

其他类型又如何呢?每个类中都必须存储一个操作符(这倒简单,本文中假定操作符最长不超过2个字符,所以,可以用长度为3的字符数组来保存),但是如何存储子节点呢?在运行时之前,我们并不知道子节点的类型会是什么,所以我们不能按值存储子节点,必须存储指针。这样,一元和二元节点类如下所示:

CLASS(Unary_node)

{

EXTENDS(Expr_node);

char op[3]; //假设操作符最长不超过2个字符

Expr_node* opnd;

void (*init)(Unary_node* t, const char* a, Expr_node* b);

};

CLASS(Binary_node)

{

EXTENDS(Expr_node);

char op[3]; //假设操作符最长不超过2个字符

Expr_node* left;

Expr_node* right;

void (*init)(Binary_node* t, const char* a, Expr_node* b,

Expr_node * c);

};

这个设计方案可以用,不过有一个问题。用户要处理的不是值,而是指针,所以必须记住分配和释放对象。例如,我们需要这么创建表达式树:

Int_node* int_node1 = Int_node_new();

Int_node* int_node2 = Int_node_new();

Int_node* int_node3 = Int_node_new();

Unary_node* unary_node = Unary_node_new();

Binary_node* binary_node1 = Binary_node_new();

Binary_node* binary_node = Binary_node_new();

int_node1->init(int_node1, 5);

int_node2->init(int_node2, 3);

int_node3->init(int_node3, 4);

unary_node->init(unary_node, "-", int_node1);

binary_node1->init(binary_node1, "+", int_node2, int_node3);

binary_node->init(binary_node, "*", unary_node, binary_node1);

lw_oopc_delete(int_node1);

……// 删除创建的其他节点

也就是说,我们需要去关心每一个节点的创建和释放。我们不仅把内存管理这类烦心事推给了用户,而且对用户来说也没有什么方便的办法来处理这些事情。我们得好好想想办法了。

这里,提供一种解决内存管理问题的思路:引用计数,这里是针对指针,对指针的状况进行计数,对象创建的时候,引用计数为1,凡是指针被赋值了,该指针所指对象的引用计数就自增一,每次指针要释放,都先检查对象的引用计数,让引用计数自减一,如果引用计数为0,则释放该对象。

另外,原先的设计不够高层,用户只能直接针对节点进行操作,没有提供操作子树的概念(这也是用户代码之所以复杂的原因之一),我们发现,通过提供子树的概念,我们不但能够隐藏Expr_node继承层次,而且,对于每一个节点,我们具备了操纵左子树和右子树的能力(原来只能操作左子节点和右子节点)。而这种功能增强完全是建立在面向对象的机制之上,我们并没有引入耦合,在非常自然和轻松的情形下,我们获得了更好的软件组件之间协作的能力,这正是面向对象的魅力所在。

这里,我们把子树的概念用类Expr来表示,由于子树此时成了Expr_node具体类的成员,同样,左右子树在Expr_node中同样是以指针的方式保存,所以,对Expr也需要进行引用计数,代码直接贴上来,细节解说参见注释:

// expr.h

#ifndef EXPR_H_INCLUDED_

#define EXPR_H_INCLUDED_

#include"lw_oopc.h"

// 表达式节点

ABS_CLASS(Expr_node)

{

int use; // 引用计数

void (*print)(Expr_node* t); // 打印表达式节点

void (*finalize)(Expr_node* t); // 子类通过覆写finalize方法,实现对资源清理行为的定制

};

// 表达式(子树的概念),其中,init*方法族提供了构建子树的高层API,方便用户使用

CLASS(Expr)

{

int use; // 引用计数

Expr_node* p; // 子树的根节点

// 构建整数表达式(包含一个整数值,无子表达式)

void (*initInt)(Expr* t, int);

// 构建一元表达式(包含一个操作符,一个子表达式)

void (*initUnary)(Expr* t, const char*, Expr*);

// 构建一元表达式的重载形式(通过传入个整型值参数,构造一个子表达式为整数表达式的一元表达式)

void (*initUnaryX)(Expr* t, const char*, int);

// 构建二元表达式(包含一个操作符,二个子表达式)

void (*initBinary)(Expr* t, const char*, Expr*, Expr*);

// 构建二元表达式的重载形式(通过传入个整型值参数,构造两个子表达式均为整数表达式的二元表达式)

void (*initBinaryX)(Expr* t, const char*, int, int);

void (*print)(Expr* t); // 打印子树

};

// 整数表达式节点

CLASS(Int_node)

{

EXTENDS(Expr_node); // 继承Expr_node

int n; // 整数值

// 初始化整数表达式节点(传入整数值)

void (*init)(Int_node* t, int k);

};

// 一元表达式节点

CLASS(Unary_node)

{

EXTENDS(Expr_node); // 继承Expr_node

char op[3]; // 假设操作符最长不超过2个字符

Expr* opnd; // 子表达式

// 初始化一元表达式节点(传入一个操作符和一个子表达式)

void (*init)(Unary_node* t, const char* a, Expr* b);

};

// 二元表达式节点

CLASS(Binary_node)

{

EXTENDS(Expr_node); // 继承Expr_node

char op[3]; // 假设操作符最长不超过2个字符

Expr* left; // 左子表达式

Expr* right; // 右子表达式

// 初始化二元表达式节点(传入一个操作符和两个子表达式)

void (*init)(Binary_node* t, const char* a, Expr* b, Expr* c);

};

#endif

//expr.c

……// 包含所需头文件

ABS_CTOR(Expr_node)

cthis->use = 1; // 构造函数中,将引用计数初始化为

END_ABS_CTOR

// Expr_node的析构函数(DTOR/END_DTOR用于实现析构函数语义)

DTOR(Expr_node)

if (--cthis->use == 0) // 递减引用计数,如果计数为,释放自己

{

cthis->finalize(cthis); // 释放内存之前先清理资源(其他需要释放的对象)

return lw_oopc_true; // 返回true,表示析构成功,可以释放内存}

return lw_oopc_false; // 返回false,表示析构失败,不能释放内存

END_DTOR

// 构建整数表达式(包含一个整数值,无子表达式),n为整数值

void Expr_initInt(Expr* expr, int n)

{

Int_node* intNode = Int_node_new(lw_oopc_file_line);

intNode->init(intNode, n);

expr->p = SUPER_PTR(intNode, Expr_node);

}

……// 因篇幅所限,构建一元表达式、二元表达式以及对应的重载形式的函数实现代码省略

// 打印表达式(子树)

void Expr_print(Expr* t)

{

Expr_node* p = t->p;

p->print(p);

}

CTOR(Expr)

FUNCTION_SETTING(initInt, Expr_initInt);

FUNCTION_SETTING(initUnary, Expr_initUnary);

FUNCTION_SETTING(initUnaryX, Expr_initUnaryX);

FUNCTION_SETTING(initBinary, Expr_initBinary);

FUNCTION_SETTING(initBinaryX, Expr_initBinaryX);

FUNCTION_SETTING(print, Expr_print);

cthis->use = 1; // 构造函数中,将引用计数初始化为

END_CTOR

// Expr的析构函数(DTOR/END_DTOR用于实现析构函数语义)

DTOR(Expr)

if (--cthis->use == 0) // 递减引用计数,如果计数为,释放自己

{

Expr_node_delete(cthis->p);

return lw_oopc_true;

}

return lw_oopc_false;

END_DTOR

// 整数表达式节点的初始化

void Int_node_init(Int_node* t, int k)

{

t->n = k;

}

// 整数表达式节点的打印

void Int_node_print(Expr_node* t)

{

Int_node* cthis = SUB_PTR(t, Expr_node, Int_node);

printf("%d", cthis->n);

}

// 整数表达式节点的资源清理

void Int_node_finalize(Expr_node* t)

{

// 什么都不需要做

}

CTOR(Int_node)

SUPER_CTOR(Expr_node);

FUNCTION_SETTING(init, Int_node_init);

FUNCTION_SETTING(Expr_node.print, Int_node_print);

FUNCTION_SETTING(Expr_node.finalize, Int_node_finalize);

END_CTOR

……// 因篇幅所限,一(二)元表达式节点的初始化、打印、资源清理、构造等函数的实现代码省略

//main.c

#include"stdio.h"

#include"Expr.h"

int main()

{

Expr* expr = Expr_new();

……// 创建expr1、expr2、expr3的代码

expr1->initUnaryX(expr1, "-", 5);

expr2->initBinaryX(expr2, "+", 3, 4);

expr3->initBinary(expr3, "*", expr1, expr2);

expr->initBinary(expr, "*", expr3, expr3);

expr3->print(expr3);

printf("\n");

expr->print(expr);

printf("\n");

Expr_delete(expr);

……// 删除expr3、expr2、expr1的代码

return 0;

}

程序运行的效果如下:

怎么样?效果还不错吧,最重要的是,我们的C语言代码现在已经完全是面向对象的。

方案的可扩展性如何?

假设我们希望添加一种Ternary_node类型来表示三元操作符,如?:(也就是if-then-else 操作符),看看,难度有多大?

事实上,正是因为前面的设计是面向对象的,要增加一种节点类型易如反掌:

// 三元表达式节点

CLASS(Ternary_node)

{

EXTENDS(Expr_node);

char op[3]; // 假设操作符最长不超过2个字符

Expr* left;

Expr* middle;

Expr* right;

// 初始化三元表达式节点(传入一个操作符和三个子表达式)

void (*init)(Ternary_node* t, const char* op, Expr* left, Expr* middle, Expr* right); };

在Expr中添加创建三元表达式的方法:

// 表达式(子树的概念),其中,init*方法族提供了构建子树的高层API,方便用户使用

CLASS(Expr)

{

int use; // 引用计数

Expr_node* p; // 子树的根节点

……// 既有实现

// 构建三元表达式(包含一个操作符,三个子表达式)

void (*initTernary)(Expr* t, const char*, Expr*, Expr*, Expr*);

// 构建三元表达式的重载形式(通过传入一个整型值参数,构造三个子表达式均为整数表达式的三元表达式)

void (*initTernaryX)(Expr* t, const char*, int, int, int);

……// 既有实现

};

请读者参照Binary_node的现有实现,实现出Ternary_node,这里不再赘述。一旦实现出Ternary_node,我们就可以这样创建表达式树并打印:

……// 创建expr1、expr2、expr3、expr对象(指针)

expr1->initUnaryX(expr1, "-", 0);

expr2->initUnaryX(expr2, "-", 5);

expr3->initBinaryX(expr3, "+", 3, 4);

expr->initTernary(expr, "?:", expr1, expr2, expr3);

expr->print(expr);

printf("\n");

为了支持新的节点类型,对原有代码的更动很少(仅对Expr类有增加方法),而且只有新增操作(新增类,新增方法),但没有修改操作(指修改原有方法),面向对象的设计赋予了系统极大的弹性,让程序在应对变化时,更加从容。在这个例子中,LW_OOPC帮助我们在C语言的世界里营造出OO的天地,带领我们再一次领略了面向对象的风采。

LW_OOPC最佳实践

说得简单一点,要想使用好LW_OOPC这套宏,还得首先懂面向对象,要遵循面向对象设计的那些大原则,比如开闭原则等。在C语言中使用面向对象,根据实际使用的情况,给出如下建议:

1)继承层次不宜过深,建议最多三层(接口、抽象类、具体类,参见图1和图2)继承层次过深,在Java/C#/C++中均不推崇,在C语言中实践面向对象的时候,尤

其要遵循这一点,只有这样,代码才能简单清爽。

2)尽量避免多重继承

尽可能使用单线继承,但可实现多个接口(与Java中的单根继承类似)。

3)尽量避免具体类继承具体类

具体类继承具体类,不符合抽象的原则,要尽量避免。

4)各继承层次分别维护好自己的数据

子类尽量不要直接访问祖先类的数据,如果确实需要访问,应当通过祖先类提供的

函数,以函数调用的方式间接访问。

图 1

图 2

LW_OOPC的优点:

1)轻量级

2)广泛的适应性,能够适应各种平台,各种编译器(能支持C的地方,基本上都能支持)

3)帮助懂OO的Java/C++程序员写出面向对象的C程序。

4)使用C,也能引入OO的设计思想和方法,在团队的C/C++分歧严重时可能非常有用。

LW_OOPC的缺点:

1)无法支持重载(C语言不支持所致)

2)不完全的封装(无法区分私有、保护和公有)

LW_OOPC的INTERFACE/ABS_CLASS/CLASS三个宏展开后都是C语言的struct,其成员全是公有的,宏本身并无能力提供良好地封装层次的支持,所以,只能从编

程规范和编程风格上进行引导。

3)不支持RTTI

既然不支持RTTI,那么显然也无法支持安全的向下转型(C++中的dynamic_cast

的转型功能)

4)不支持拷贝构造以及赋值语义

5)转换成接口的表述有点麻烦,表达形式相比C++要啰嗦很多。

6)有学习成本,需要用户学习并习惯这套宏

前四条缺点,实质上并非是LW_OOPC的缺点,而是C相对C++而言的缺点,在这里,之所以也一并列上,是希望用户不要对LW_OOPC抱太高的期望,毕竟它也只是一套C语言的宏而已,C语言有的缺点,LW_OOPC并不能够解决。

总结:

尽管如此,在使用C语言编程的时候,在某些情形下,你可能想要通过面向对象来更好的组织代码。偶尔,你也想要用用某个设计模式,此时,这套宏能够帮得上忙,使用它,有助于写出相对易于理解和维护的面向对象的代码。因篇幅所限,本文中没有介绍

LW_OOPC的高级特性,譬如对内存泄漏检测的支持。示例的完整代码、LW_OOPC的最新版本以及关于这套宏的更加详细的使用指南,请读者访问https://www.360docs.net/doc/ae18261689.html,获取。最后,期望有兴趣的读者,发挥聪明才智,提出改进建议,让LW_OOPC变得越来越好!

幕后花絮:

在完善LW_OOPC宏的过程中,我也认真研究了参考资料中列出的材料。最初V1.0版本有将近25个宏,后来,收到一些同事的反馈,认为少量宏晦涩难记,而且不易理解,经过认真考虑,删除掉5个宏,形成现在的20个宏。相比其他用C实现面向对象的方案,LW_OOPC简洁优雅,每个宏的命名都经过仔细地推敲,尽可能做到简单易懂,便于记忆。但愿LW_OOPC真的能够帮助到奋斗在一线的C程序员们。

参考资料:

[1] 高焕堂。UML+OOPC嵌入式C语言开发精讲,电子工业出版社,2008 年9月。

[2] Object-oriented Programming with ANSI-C,下载地址:https://www.360docs.net/doc/ae18261689.html,/codecu ts/pdfs/ooc.pdf。

[3] C实现面向对象的方法,https://www.360docs.net/doc/ae18261689.html,/RealtimeMantra/basics/object_oriented _programming_in_c.htm

C语言程序设计第三版习题库答案

C 语言程序设计(第三版)习题库 1、设圆半径r=,圆柱高h=3,求圆周长、圆面积、圆球表面积、圆球体积、圆柱体积。用scanf 输入数据,输出计算结果,输出时要求文字说明,取小数点后两位数字。请编程序。 #include<> main(){ floatr,h,C1,Sa,Sb,Va,Vb; scanf(__”%f ”__,&r); scanf(”%d ”,__&h _);; C1=2**r; Sa=*r*r; Sb=4*Sa; Va=4**r*r*r/3; Vb=Sa*h; printf(___”Cl=%.2fSa=%.2fSb=%.2fVa=%.2fVb=%.2f ”,Cl,Sa,Sb,Va,Vb ); } 2、输入一个华氏温度,要求输出摄氏温度。公式为c=5(F-32)/9 输出要求有文字说明,取位2小数。 #include<> main(){ floatF,c; scanf("%f",&F); ____c=5*(F-32)/9______; printf("c=%.2f",c); } 3、有一函数:?? ???≥-<≤-<=10113101121x x x x x x y 写一程序,输入x 值,输出y 值。 #include<> main(){ intx,y; printf("输入x :"); scanf("%d",&x); if(x<1){/*x<1*/ y=x; printf("x=%3d,y=x=%d\n",x,y);

}elseif(____x<10_______){/*1≤x-10*/ _____y=2*x-1_______; printf("x=%3d,y=2*x-1=%d\n",x,y); }else{/*x≥10*/ y=3*x-11; printf("x=%3d,y=3*x-11=%d\n",x#include"" main() { intx,y; scanf("%d",&x); if(x<1) {y=x;} elseif(x>=1&&x<10) {y=2*x-1;} else {y=3*x-11;} printf("%d",y); }#include"" main() { intx,y; scanf("%d",&x); if(x<1) {y=x;} elseif(x>=1&&x<10) {y=2*x-1;} else {y=3*x-11;} printf("%d\n",y); }#include"" main() { intx,y; scanf("%d",&x); if(x<1) {y=x;} elseif(x>=1&&x<10) {y=2*x-1;} else {y=3*x-11;} printf("%d",y); }scanf("%d",&x);

C语言程序设计

一、单选题 1. (4分)若调用fputc函数输出字符成功,则其返回值是( )。? A. EOF ? B. 1 ? C. 0 ? D. 输出的字符 得分:0知识点:C语言程序设计作业题收起解析 答案D 解析 2. (4分)若以下程序段: struct dent { int n; int*m; }; int a=1, b=2,c=3; struct dent s[3]={{101<&a},{102<&b},{103,&c}; main() { struct dent *p;

p=s; ….. } 则以下表达中值为2的是()。 ? A. (p++)->m ? B. *(P++)->m ? C. (*p).m ? D. *(++p)->m 得分:0知识点:C语言程序设计作业题收起解析 答案D 解析 3. (4分)在一个c源程序文件中,若要定义一个只允许本源文件中所有函数使用的全局变量,则该变量需要使用的存储类别是( ). ? A. extern ? B. register ? C. auto ? D. static 得分:0知识点:C语言程序设计作业题收起解析 答案D 解析

4. (4分)若二维数组a有m列,则在a[i][j]前面的元素个数为()。? A. j*m+i ? B. i*m+j ? C. i*m+j-1 ? D. i*m+j+1 得分:0知识点:C语言程序设计作业题收起解析 答案B 解析 5. (4分)下面程序的运行结果是(). main() { struct cmplx {int x; int y; } cnum[2]={1,3,2,7}; printf(“%d ”,cnum[0].y/cnum[0].x*cnum[1].x); } ? A. 0 ? B. 1 ? C. 3

C语言程序设计试题集与答案解析

一.填空 1. 每个C程序都必须有且仅有一个________ 函数。 2. C语言程序开发到执行通常要经过6个阶段即编辑、预处理、________、链接、加载和执行。 3. 软件是程序,以及______、使用和维护所需要的所有文档。 4. 国标中规定:“计算机程序是按照具体要求产生的适合于计算机处理的_________”。 5. 程序设计语言按照书写形式,以及思维方式的不同一般分为低级语言和________两大类。 6. C语言是由________组成的。 7. C语言的函数可分为主函数main、标准库函数和_________。 8. 一个函数是由两部分组成的,即:________和函数体。 9. 编译是将C语言所编写的源程序________成机器代码,也称为建立目标代码程序的过程。 10. 程序是由某种程序设计语言编制出来,体现了编程者的控制思想和对计算机执行操作 的要求。不同的任务功能,就会需求不同的软件程序,如:控制计算机本身软硬件协调工作,并使其设备充分发挥效力,方便用户使用的系统软件程序,称为操作系统;而为办公自动化(OA)、管理信息系统(MIS)、人工智能、电子商务、网络互联等等应用而开发的软件程序,统称为_________。 11. 机器语言是以__________形式表示的机器基本指令的集合,是计算机系统唯一不需要翻译可以直接识别和执行的程序设计语言。 12. 与机器语言相比,使用汇编语言来编写程序可以用_______来表示指令的操作码和操作对 象,也可以用标号和符号来代替地址、常量和变量。

13. 在编译程序之前,凡以____开头的代码行都先由预处理程序预处理。 14. C程序的执行均是由执行_________开始。 15. 函数体即为包含在{}内的部分。它分为________和为完成功能任务由若干个C 语句 组成的执行部分。 16. C语言程序中一条简单语句是以________字符作为结束符的。 17. C语言是结构化、________的程序设计语言。 18. 由于计算机硬件不能直接识别高级语言中的语句,因此,必须经过“_______程序”,将用高级语言编写的程序翻译成计算机硬件所能识别的机器语言程序方可执行。 19. 用高级语言编写的程序需翻译成计算机硬件所能识别的机器语言程序方可执行。所以 说,用高级语言进行程序设计,其编程效率高,方便易用,但_______没有低级语言高。 20.

C语言程序设计重点必考实验题

C语言程序设计实验36题 邓作杰编 简单程序设计与基本数据处理 1 显示短句 在屏幕上显示一个短句“Programming in C is fun!” 2 求两个数的和与差 输入整数a 和b,计算并输出a、b 的和与差。 3 求平方根 输入1 个实数x,计算并输出其平方根(保留1 位小数) 4 华氏温度转换为摄氏温度 输入华氏温度f,计算并输出相应的摄氏温度c(保留2 位小数)。c = 5/9(f-32). 5 大写字母转换成小写字母 输入一个大写英文字母,输出相应的小写字母。 6编写摄氏温度、华氏温度转换程序。 从键盘输入一个摄氏温度,屏幕就显示对应的华氏温度,输出取两位小数。转换公式:F=(C+32)×9/5 。

分支结构 7 判断数的符号 输入整数x,若x 大于0,y=1;若x 等于0,y=0;否则,y=-1,最后输出y。 8 计算个人所得税 输入一个职工的月薪salary,输出应交的个人所得税tax(保留2 位小数)。 tax = rate * (salary-850) 当salary <= 850 时,rate = 0; 当850 < salary <= 1350 时,rate = 5; 当1350 < salary <= 2850 时,rate = 10; 当2850 < salary <= 5850 时,rate = 15; 当5850 < salary 时,rate = 20; 9、试编程判断输入的正整数是否既是5又是7的正倍数。若是,则输出yes;否则输出no。

循环结构 10.是求1 -100之间的所有除5余2的数之和。 11.所谓回文数是从左往右和从右往左读起来都一样的数字,如121是一个回文数,计算从1881开始到4000年为止,共有多少个年号是回文年号? 12.是求1-5000之间的能被7整除的前若干个偶数之和,当和大于600时程序退出,请将运行结果。 13.编程序求出1~100所有整数的平方和并输出结果。 14.一个数如果刚好与小于它的所有因子之和相等,则称该数为一个“完数”,如:6=1+2+3,则6就是一个完数。求出800以内的所有完数之和。 15.所谓回文数是从左至右与从右至左读起来都是一样的数字,如:121是一个回文数。编写程序,求出300—800的范围内所有回文数的和。 16.有36个学生一起买小吃,共花钱120元,其中每个大学生花4元,每个中学生花2元,每个小学生花1元,问大、中、小学生的人数分配共有多少种不同的解(去掉某类学生为0的解)? 17. 求1至100之间的奇数和 18.输出2至1000之间在的质数。 19.输入一个3位的正整数,输出其各位上的数字。 20.对15个数进行排序,按从小到大的顺序输出。 21.一个数除了能被1和它本身整除之外,不能被其它整数整除,该数称为素数。求出1000-5000之间素数的和。 22.求[600,900]之间素数的个数。

c语言程序设计第五版习题答案

习题解析与答案 第1章C语言概述 一.简答题 1.概述C语言的主要特点。 【解答】 (1)语言简洁、紧凑,使用方便、灵活。 (2)数据类型丰富,表达能力强。 (3)运算符多样。C语言中的运算符包含的范围非常广泛。 (4)具有结构化的控制语句。如if…else语句、while语句、do while语句、switch 语句、for语句。 (5)允许直接访问物理地址。C语言中含有的位和指针运算,能够直接对内存地址进行访问操作。 (6)所生成的目标代码质量高,可移植性好。 2.构成C语言程序的基本单位是什么?它由哪几部分组成? 【解答】函数是构成C语言程序的基本单位。一个完整的C程序一般由文件包含、宏定义、函数说明、变量和一个或若干个函数组成。 3.C语言程序的运行一般要经过哪几个步骤? 【解答】(1)编辑;(2)编译;(3)连接,生成EXE文件;(4)执行。 二.运行程序写结果 1.输入下面程序并运行。 main() { int a1,a2,x; a1=100; a2=50; x=a1-a2; printf(″x=%d\n″,x); } 【解答】运行结果为:x=50 2.输入下面程序并运行。 main() { int a1,a2,x; a1=10; a2=20; x=a1*a2; printf(″a1=%d,a2=%d\n″,a1,a2); printf(″x=%d\n″,x); } 【解答】运行结果为:a1=10,a2=20 x=200 3.输入下面程序并运行。

#include main() { printf("******\n"); printf(" *****\n"); printf(" ****\n"); printf(" ***\n"); printf(" **\n"); printf(" *\n"); } 【解答】运行结果为:****** ***** **** *** ** * 思考:可以修改程序,使之输出平行四边形,等腰三角形等图形。 三.编程题 1.参照本章例题,编写一个C程序,用于显示如下信息: ************************* I love C programs! ************************* 【分析与提示】 ①要有文件包含语句#include 。C语言中没有数据的输入、输出等功能,数据的输入、输出都是通过调用系统提供的库函数scanf和printf等来实现的。这些函数的说明都包括在stdio.h文件中。 ②main是主函数的名称。用{}括起来的内容是函数体,函数体由若干条语句组成,这是计算机要执行的部分,每条语句以分号“;”结束。 ③注意显示的信息有三行,所以要用到换行符“\n”。 参考代码: #include main() { printf("************************\n"); printf(" I love C programs! \n"); printf("************************\n"); }

(完整版)《C语言程序设计》基本知识点

《C语言程序设计》教学基本知识点 第一章C语言基本知识 1.C源程序的框架 尽管各个C源程序的功能千变万化,但框架是不变的,主要有:编译预处理、主函数()、函数n()等,主函数的位置不一定在最前面,可以在程序的中部或后面,主函数的名字固定为main。 2.C语言源程序的书写规则: (1)C源程序是由一个主函数和若干个其它函数组成的。 (2)函数名后必须有小括号,函数体放在大括号内。 (3)C程序必须用小写字母书写。 (4)每句的末尾加分号。 (5)可以一行多句。 (6)可以一句多行。 (7)可以在程序的任何位置加注释。 3.语句种类 语句是程序的基本成分,程序的执行就是通过一条条语句的执行而得以实现的,根据表现形式及功能的不同,C语言的基本语句可以分为五大类。 (1)流程控制语句 流程控制语句的功能是控制程序的走向,程序的流程有三种基本结构:顺序结构、分支结构和循环结构,任何复杂的程序都可以由这三种基本结构复合而成。其中后两种结构要用特定的流程控制语句实现。 (2)表达式语句 表达式语句的形式是:表达式;,即表达式后跟一分号“;”,分号是语句结束符,是一个语句必不可少的成分。表达式和表达式语句的区别在于表达式代表的是一个数值,而表达式语句则代表一种动作。最常见的表达式语句是赋值语句。 (3)函数调用语句 函数调用语句实际上也是一种表达式语句,形式为:在一次函数调用的小括号后面加上一个分号。 (4)空语句 空语句的形式就是一个分号,它不代表任何动作,常常作为一个意义转折点使用。 (5)复合语句 复合语句从形式上看是多个语句的组合,但在语法意义上它只相当于一个语句,在任何单一语句存在的地方都可以是复合语句。注意复合语句中最后一个语句末尾的分号不能少。复合语句右大括号后面没有分号。 4.运算符 用来表示数据各种操作的符号称为运算符。运算符实际上代表了一种类型数据的运算规则。不同的运算符具有不同的运算规则,其操作的数据类型必须符合该运算符的要求,运算结果的数据类型也是固定的。 根据参加操作的数据个数多少,可以将C语言的运算符分为单目运算符,双目运算符和三目运算符(三目运算符只有条件运算符一个)。 根据运算对象和运算结果的数据类型可分为算术运算符、关系运算符、逻辑运算符等。 5.表达式 表达式是由常量、变量、函数,通过运算符连接起来而形成的一个算式。一个常量,一个变量或一个函数都可以看成是一个表达式。 表达式的种类有: 算术表达式、关系表达式、逻辑表达式、赋值表达式、字位表达式、强制类型转换表达式、逗号

(完整版)C语言程序设计练习及答案

《C语言程序设计》练习及答案 得分评卷人复查人 一、单选题,每小题1分,共60分(将正确答案的序号写在题目的括号中)。 1、结构化程序设计的三种基本控制结构是(D )。 A、主程序、子程序、函数 B、输入、处理、输出 C、调用,返回,转移 D、顺序、选择、循环 2、下列关于C程序变量的叙述, ( D )是错误的。 A、变量名必须由字母或下划线开头。 B、程序中的变量必须在被使用之前定义。 C、不同的基本类型的变量之间可以混合运算。 D、变量的数据类型决定变量的"作用域"。 3、能将C语言编写的源程序转换为目标程序的软件是(C )。 A、编辑程序 B、汇编程序 C、编译程序 D、解释程序 4、以下符号中,合法的用户标识符是( D )。 A、-p B、int C、3ab D、_xt_ 5、以下选项中,与m=n++完全等价的表达式是( C )。 A、m=++n B、m+=n+1 C、m=n, n=n+1 D、n=n+1,m=n 6、若有定义:int aa[8];。则以下表达式中不能代表数组元aa[1]的地址的是(C )。 A、&aa[0]+1 B、&aa[1] C、&aa[0]++ D、aa+1 7、表达式!5&(7+3)&&(4+5)的值是(A)。 A、0 B、1 C、5 D、9 8、以下选项中非法的C语言表达式是(A )。 A、x+1=x+1 B、0<=x<100 C、i=j==0 D、(char)(65+3) 9、在TURBO C中, int类型变量所占字节数是(B )。 A、1 B、2 C、4 D、8 10、C语言中基本的数据类型包括(B)。 A、整型,实型,逻辑型 B、整型,实型,字符型

C语言程序设计重点复习(函数之前)

目录 第一部分简单程序示例: (3) 第二部分整型数据 (7) (1)整型常量(整数) (7) (2)整型变量 (8) Ⅰ 二进制数计算: (9) Ⅱ 十进制数(正常数)转换为二进制数: (9) 第三部分字符型数据 (11) (1)字符型常量:由一对单引号括起来的单个字符(可打印) (11) (2)转义字符:一个反斜杠(\)后跟限定字符(不可打印) (11) (3)字符型变量:通过保留字char说明 (12) (4) 字符串常量:用一对双引号括起来的零个或者多个字符的序列。 (12) ASCII码例子 (13) 第四部分实型数据(浮点型数据) (14) (1)实型常量 (14) (2)实型变量:双精度(double) (14) 第五部分格式说明符与数据输入与输出 (16) Ⅰ 几个简单常用的格式说明符: (16) Ⅱ 数据的输入与输出函数: (16)

Ⅲ 前格式说明符的扩展说明 (19) 第六部分几个简单的表达式讲解 (21) Ⅰ 算术表达式 (21) Ⅱ 赋值表达式 (21) Ⅲ 复合赋值表达式 (22) Ⅳ ++和— (22) Ⅴ 逗号表达式 (23) Ⅵ 条件表达式 (24) 第七部分关系运算与逻辑运算 (25) 几个简单的关系运算符 (25) 几个简单的逻辑运算符 (25) 第八部分if语句(条件语句) (26) ①格式: (26) ②格式: (27) ③if的嵌套1 (28) ④if的嵌套2 (28) 第九部分switch语句(开关语句) (29) 第十部分break与continue的作用 (31) Ⅰ break语句,用于跳出当前循环语句或开关语句 (31)

C语言程序设计第二版习题参考答案

C语言程序设计第二版 习题参考答案 Document serial number【LGGKGB-LGG98YT-LGGT8CB-LGUT-

C语言程序设计习题参考答案 习题 1 一、判断题 1.在计算机中,小数点和正负号都有专用部件来保存和表示。 2.二进制是由0和1两个数字组成的进制方式。 3.二进制数的逻辑运算是按位进行的,位与位之间没有进位和借位的关系。 4.在整数的二进制表示方法中,0的原码、反码都有两种形式。 5.有符号数有三种表示法:原码、反码和补码。 6.常用字符的ASCII码值从小到大的排列规律是:空格、阿拉伯数字、大写英文字母、小写英文字母。 解:1.F2.T 3.T 4.T 5.T 6.T 二、单选题 1.在计算机中,最适合进行数值加减运算的数值编码是。 A. 原码 B. 反码 C. 补码 D. 移码 2.已知英文小写字母m的ASCII码为十进制数109,则英文小写字母y的ASCII 码为十进制数。 A. 112 B. 120 C. 121 D. 122 3.关于ASCII码,在计算机中的表示方法准确地描述是。 A. 使用8位二进制数,最右边一位为1 B. 使用8位二进制数,最左边一位为1 C. 使用8位二进制数,最右边一位为0 D. 使用8位二进制数,最左边一位为0 4.设在机器字长4位,X=0111B,Y=1011B,则下列逻辑运算中,正确的是 ___________。 A. X∧Y=1000 B. X∨Y=1111 C. X⊕Y=0011 D. ˉY=1000 5.下列叙述中正确的是()。 A.高级语言就是机器语言 B.汇编语言程序、高级语言程序都是计算机程序,但只有机器语言程序才是计算机可以直接识别并执行的程序 C.C语言因为具有汇编语言的一些特性,所以是汇编语言的一种 D.C源程序经过编译、连接,若正确,执行后就能得到正确的运行结果6.用C语言编写的源程序经过编译后,若没有产生编译错误,则系统将()。 A.生成可执行文件B.生成目标文件 C.输出运行结果D.自动保存源文件 7.下列叙述中不正确的是()。 A.main函数在C程序中必须有且只有一个 B. C程序的执行从main函数开始,所以main函数必须放在程序最前面 C. 函数可以带参数,也可以不带参数。

C语言程序设计习题答案

C 语言程序设计习题答案 习题一 C 语言程序设计概述 一、名词解释 (1)程序P1 (2)程序设计P1 (3)机器语言P1 (4)汇编程序P2 (5)高级语言P2 (6)编译程序P3 (7)解释程序P3 (8)算法P4 (9)结构化的程序设计P9 二、简述题 1. 设计程序时应遵循哪些基本原则?P4 答:正确性、可靠性、简明性、有效性、可维护性、可移植性。 2. 算法的要素是什么?算法具有哪些特点? 答:算法的要素是:操作与控制结构;算法的特点有:有穷性、确定性、有效性、有零个或多个输入、有一个或多个输出。 3. 算法的表示形式有哪几种? 答:算法的表示形式有:自然语言、传统流程图、伪代码、结构化的流程图(N_S 流程图,盒图)。 4. 有哪三种基本结构? 答:三种基本结构是:顺序结构、选择结构和循环结构。 5. 传统流程图与N-S 流程图最大的区别是什么? 答:N-S 流程图去掉了在传统流程图中常用的流程线,使得程序的结构显得更加清晰、简单。 三、用传统流程图、N-S 图分别表示求解以下问题的算法。 1. 有3个数a ,b ,c ,要求按由大到小的顺序把它们输出。 2. 依次将10个数输入,求出其中最大的数 和最小的数并输出。 3. 求1+2+3+…+100的值。

5. 求下列分段函数的值。 6. 求100~200之间的所有素数。 7. 求一元二次方程ax 2+bx+c=0的根。分别考虑d=b 2-4ac 大于0、等于0和小于0三种情况。 四、注释下面C 程序的各个组成部分。 main() /*主函数 */ { /*程序开始 */ int a,k,m; /*定义三个用来存放整数的变量 */ a=10; /*将整数10赋值给变量a */ k=2; /*将整数2赋值给变量k */ m=1; /*将整数1赋值给变量1 */ a=(k+m)*k/(k-m); /*先求出算术表达式的值,并将其赋值给变量a */ printf("%d\n",a); /*在屏幕上打印出变量a 的值 */ } /*程序结束 */ 习题二 数据类型、运算符与表达式 一、选择题 1~10:BCDCB DDBCA 11~20: ADDAA DBADC 21~28: DABAD CDD Y= 3X (X<1) 4X-1 (X=1) 5(X-1)+6 (1

大一C语言编程重点复习题

输出众数: int main() {int a[10],b[10]={0}; int i,j,max; for(i=0;i<10;i++) scanf("%d",&a[i]); for(i=0;i<10;i++) for(j=i;j<10;j++) if(a[i]==a[j]) b[i]++; max=b[0]; for(i=0;i<10;i++) if(b[i]>max) max=b[i]; if(max==1) printf("No"); else for(i=0;i<10;i++) if(b[i]==max) printf("%d,%d\n",a[i],b[i]);

return 0; } 平方数: #include<> int main() { int i,a,b; for(i=1000;i<=9999;i++) { a=i/100; b=i%100; if(i==(a+b)*(a+b)) printf("%d\n",i);} return 0; } 排序:由小到大 #include<> int main()

int i,j,t; for(i=0;i<10;i++) scanf("%d",&a[i]); for(j=0;j<9;j++) for(i=0;i<9-j;i++) if(a[i]>a[i+1]) { t=a[i];a[i]=a[i+1]; a[i+1]=t; } for(i=0;i<9;i++) printf("%d\n",a[i]); printf("%d",a[9]); return 0; } # {

int n,i,j; for(i=0;i<10;i++) scanf("%d",&a[i]); scanf("%d",&n); if(n>a[9]) a[10]=n; else { for(i=0;i<9;i++) if(a[i]>n) { for(j=9;j>=i;j--) a[j+1]=a[j]; a[i]=n; break; } } for(i=0;i<10;i++) printf("%d ",a[i]); printf("%d",a[10]); return 0; }

C语言程序设计第三版谭浩强课后习题答案完整版

C语言程序设计第三版谭浩强 课后习题答案完整版 第一章 1.5请参照本章例题,编写一个C程序,输出以下信息:************************** V ery Good! ************************** 解: mian() {printf(“**************************”); printf(“\n”); printf(“V ery Good!\n”); printf(“\n”); printf(“**************************”); } 1.6 编写一个程序,输入a、b、c三个值,输出其中最大值。解: mian() {int a,b,c,max; printf(“请输入三个数a,b,c:\n”); scanf(“%d,%d,%d”,&a,&b,&c); max=a; if(max main() { char c1=?C?,c2=?h?,c3=?i?,c4=?n?,c5=?a?; c1+=4; c2+=4; c3+=4; c4+=4; c5+=4; printf("密码是%c%c%c%c%c\n",c1,c2,c3,c4,c5); } 运行结果: 密码是Glmre 3.9求下面算术表达式的值。 (1)x+a%3*(int)(x+y)%2/4 设x=2.5,a=7,y=4.7 (2)(float)(a+b)/2+(int)x%(int)y 设a=2,b=3,x=3.5,y=2.5 (1)2.5 (2)3.5 3.10写出程序运行的结果。 main() {int i,j,m,n; i=8; j=10; m=++i; n=j++; printf(“%d,%d,%d,%d”,i,j,m,n); } 解: 9,11,9,10 3.12 写出下面表达式运算后a的值,设原来a=12。设a和n都已定义为整型变量。 (1)a+=a (2)a-=2 (3)a*=2+3 (4)a/=a+a (5)a%=(n%=2),n的值等于5 (6)a+=a-=a*=a 解: (1) 24 (2) 10 (3) 60 (4) 0 (5) 0 (6) 0 第四章 4.4若a=3,b=4,c=5,x=1.2,y=2.4,z=-3.6,u=51274,n=128765,c1=’a’,c2=’b’。想得到以下输出格式和结果,请写出程序(包括定义变量类型和设计输出)。 a=_3_ _b=_4_ _c=_5 x=1.200000,y=2.400000,z=-3.600000 x+y=_3.600_ _y+z=-1.20_ _z+x=-2.40 c1=ˊaˊ_or_97(ASCII)

C语言程序设计重要知识点

C语言程序设计(第四版)重要知识点第一章、程序设 计;和C语言1、三个发展阶段 机器语言:计算机有效识别的语言,执行效率最高,不容易被记忆理解,最低级语言。 符号语言:相对于高级语言不容易被记忆和理解,语言普遍性较差,用符号来代替二进制代码。 高级语言:与人类思维比较接近,

方便学习和理解,接近与人们习惯使用的自然语言和数学语言,不能被计算机直接识别,需要编译成计算机能够识别的代码。 2、C语言的发展及其特点 ○1、语言简洁、紧凑,使用方便灵活。 ○2、运算符丰富。 ○3、数据类型丰富。 ○4、具有结构化的控制语句。 ○5、语法限制不太严格,程序设计自由度大。 ○6、C语言允许直接访问物理地址,能进行位(bit)操作,能实现汇编语言的大部分功能,可以直接对硬

件进行操作。 ○7、用C语言编写的程序可移植性好。 ○8、生成目标代码质量高,程序执行效率高。 1.4、最简单的C语言程序 # include”stdio.h” main() { printf(“This is a C program.\n”); } Stdio.h是系统提供的一个头文件名。用到#inaclude”stdio.h”是因为程序 中要用到标准函数库中的输入输 输出函数。 (1)以//开始的单行注释。此种

注释的范围从//开始,以换 行符结束。 (2)以/ *开始,以*/结束的块式注释。这种注释可以包含 多行内容。 ○注:1、/*和*/必须成对出现 2、/*不得存在空格 3、正常的注释语句只 能起到解释解释说明的作 用,不影响程序的运行。求两个整数之和 #include”stdio.h” Main() { int a,b,sum;

《c语言程序设计》课程标准

《C语言程序设计》课程标准 课程编码: 03012208 课程模块:专业必修课 学时: 90 课程类型: B类(理论+实践) 适用专业:计算机网络专业 1. 概述 C语言是一种结构化语言。它层次清晰,便于按模块化方式组织程序,易于调试和维护。C语言的表现能力和处理能力极强。它不仅具有丰富的运算符和数据类型,便于实现 各类复杂的数据结构。它还可以直接访问内存的物理地址,进行位(bit)一级的操作。由于 C语言实现了对硬件的编程操作,因此C语言集高级语言和低级语言的功能于一体。既可 用于系统软件的开发,也适合于应用软件的开发。 本课程旨在使学生掌握程序设计的基本方法及思维,形成程序设计基本思想,掌握程 序调试的基本方法,使学生初步具备程序设计能力,为学生进一步学习其他专业课程和今 后从事网络技术工作打下坚实的基础。 课程的性质 《C语言程序设计》是一门专业必修课,是学习其它工科课程的基础。掌握程序设计 的前提是掌握程序设计语言,C语言以其灵活性和实用性受到了广大计算机应用人员的喜爱。一旦掌握了C语言,就可以较为轻松地学习后续的Java程序设计、数据库技术等程序 设计语言。本课程以程序设计思想为主线,介绍C语言的基本概念和程序设计的思想和方法,培养学生运用C语言解决实际问题的能力。 前导课程:计算机文化基础等。 后继课程:Java程序设计、数据库技术、信息安全技术、网络操作系统等。

课程设计理念 通过对多家IT企业的走访,我们了解到自学能力、动手能力、团队协作能力是现代企业对人才的共同要求。因而我们也把培养具有这些能力的学生作为我们的课程目标,把对这些能力的培养融入到平常的授课过程中。对以计算机网络、汽车电子等专业为代表的学生,主要注重项目分析,流程图设计以及编程技巧和方法的培养,为进一步培养软件项目综合开发和应用能力作准备。 1、基于工作过程,以职业能力培养为重点,与行业企业合作进行的课程开发与设计。 在课程的讲授过程中,采用任务驱动的模式,以案例教学为主。在学生掌握了相应知识点后,再提出新的问题,要求学生通过修改已有的代码加以解决。在修改代码的过程中,既培养了学生的自学能力,又锻炼了学生的动手能力。 另外,我们把企业中的一些要求带到了教学过程中。例如代码必须加注释,标识名必须用英文单词等。 2、课程设计充分体现了职业性、实践性和开放性的要求。 通过积极与行业、企业合作开发课程,课程思路体现职业岗位的能力要求,使专业核心课程与职业岗位能力紧密对应。让企业参与到专业建设及课程设置的各个环节中,在校企合作中创新人才培养模式。 为此我们围绕实验、实训、实习环节,建立了完善的实训体系。充分利用好学校、企业及社会各自的办学资源,主动与行业、企业联系,加强校企合作,形成学校、行业、企业互动,共同合作参与学生专业技能的培养,加强校外紧密型实训基地建设。 课程开发思路 《C语言程序设计》课程的学习包括理论课、实验课和学生课外科研等形式。理论课的安排,以程序设计方法为主线,由浅入深,先讲授程序设计的基本结构,再从数组、函数、指针、结构和文件等方面讲授程序设计的方法,突出基本概念和基本技能,强调分析问题、解决问题的思路和方法。实验课的实习题目设计要紧密结合所学理论知识,引导学

c语言程序设计第五版习题答案

c语言程序设计第五版习 题答案 Prepared on 24 November 2020

习题解析与答案 第1章C语言概述 一.简答题 1.概述C语言的主要特点。 【解答】 (1)语言简洁、紧凑,使用方便、灵活。 (2)数据类型丰富,表达能力强。 (3)运算符多样。C语言中的运算符包含的范围非常广泛。 (4)具有结构化的控制语句。如if…else语句、while语句、dowhile语句、switch语句、for语句。 (5)允许直接访问物理地址。C语言中含有的位和指针运算,能够直接对内存地址进行访问操作。 (6)所生成的目标代码质量高,可移植性好。 2.构成C语言程序的基本单位是什么它由哪几部分组成 【解答】函数是构成C语言程序的基本单位。一个完整的C程序一般由文件包含、宏定义、函数说明、变量和一个或若干个函数组成。 3.C语言程序的运行一般要经过哪几个步骤 【解答】(1)编辑;(2)编译;(3)连接,生成EXE文件;(4)执行。 二.运行程序写结果 1.输入下面程序并运行。 main() { inta1,a2,x; a1=100; a2=50; x=a1-a2; printf(″x=%d\n″,x); } 【解答】运行结果为:x=50 2.输入下面程序并运行。 main()

{ inta1,a2,x; a1=10; a2=20; x=a1*a2; printf(″a1=%d,a2=%d\n″,a1,a2); printf(″x=%d\n″,x); } 【解答】运行结果为:a1=10,a2=20 x=200 三.编程题 1.参照本章例题,编写一个C程序,用于显示如下信息: ************************* IloveCprograms! ************************* 【分析与提示】 ①要有文件包含语句#include<>。C语言中没有数据的输入、输出等功 能,数据的输入、输出都是通过调用系统提供的库函数scanf和printf 等来实现的。这些函数的说明都包括在文件中。 ②main是主函数的名称。用{}括起来的内容是函数体,函数体由若干 条语句组成,这是计算机要执行的部分,每条语句以分号“;”结束。 ③注意显示的信息有三行,所以要用到换行符“\n”。 参考代码: #include<> main() { printf("************************\n"); printf("IloveCprograms!\n"); printf("************************\n"); } 第2章数据类型及其运算一.简答题 1.C语言中的数据类型主要有哪几类 【解答】短整型(shortint) 整型整型(int) 长整型(longint) 基本类型字符型(char)(float) 实型(浮点型) 双精度(double) 枚举类型(enum) 数组类型

C语言程序设计考核重点及复习资料

《C语言程序设计》课程考核重点及复习资料 一、单项选择题 【1.1】以下不正确的C语言标识符是____。 A) int B) a_1_2 C) ab1exe D) _x 【1.2】以下是正确的C语言标识符是____。 A) #define B) _123 C) %d D) \n 【1.3】下列四组字符串中都可以用作C语言程序标识符的一组是。 A) print _3d oodb aBc B) i\am one_half start$it 3pai C) Pxq My->book line# His.age D) str_l Cpp pow while 【1.4】下面各选项组中,均是C语言关键字的组是。 A) auto,enum,include B) switch,typedef,continue C) signed,union,scanf D) if,struct,type 【1.5】下列不属于C语言关键字的是。 A) default B) register C) enum D) external 【1.6】C语言程序从main()函数开始执行,所以这个函数要写在____。 A) 程序文件的开始 B) 程序文件的最后 C) 它所调用的函数的前面 D) 程序文件的任何位置 【1.7】下列关于C语言的叙述错误的是____ A) 大写字母和小写字母的意义相同 B) 不同类型的变量可以在一个表达式中 C) 在赋值表达式中等号(=)左边的变量和右边的值可以是不同类型 D) 同一个运算符号在不同的场合可以有不同的含义 【1.8】在C语言中,错误的int类型的常数是。 A) 32768 B) 0 C) 037 D) 0xAF 【1.9】执行语句 printf("%x",-1);屏幕显示____。 A) -1 B) 1 C) -ffff D) ffff 【1.10】已知 long i=32768;执行语句printf("%d",i);屏幕显示____。 A) -1 B) -32768 C) 1 D) 32768

《C语言程序设计教程》习题参考答案

《C语言程序设计教程》习题参考答案 祝胜林主编 华南理工大学出版社 【习题1】 (1) 【习题2】 (2) 【习题3】 (3) 【习题4】 (5) 【习题5】 (8) 【习题6】 (11) 【习题7】 (14) 【习题8】 (16) 【习题9】 (16) 【习题10】 (18)

一、简答题(在课本中寻找答案,略) 1.1C程序的基本结构包括哪些内容? 1.2如何定义标识符? 1.3输入格式、输出格式的组成包括哪些内容? 1.4C语言函数分为哪两类? 1.5计算表达式的值应该考虑哪些方面? 1.6上机调试程序的步骤如何? 二、判断并改错 1.7C程序执行的入口是main()函数,所以main函数必须放在程序的开头。 错误:main函数可以放在程序的任何位置。并不限定在程序的开头。 1.8定义一个函数包括数据说明部分和执行语句部分,两者可以交叉出现。 错误:不可以交叉出现,数据说明部分在执行语句部分的前面。 1.9编辑与编译不是一回事。 错误:不是一回事。编辑完成源程序的输入和修改;编译是对源程序进行语法检查,如果无语法错误,则生成目标程序。 1.10scanf("%d,%d",&x,&y);的正确输入格式:3,4<回车>。 正确 1.11注释内容太多会影响程序的执行效率。 错误:不会影响程序的执行效率。因为在编译时,编译系统将注释内容删除或用空格代替,所以注释内容不会编译到目标程序中。 1.12所有的数学公式可以直接出现在源程序中。 错误:数学公式需要转换成C语言能够接受的公式才能出现在源程序中。 三、编程题 1.13在屏幕上输出自己名字的拼音。 提示:中文名字叫?张三?,对应的拼音为?Zhang San?,输出用printf()函数。 1.14 输入圆的半径,求圆的周长,并将结果保留两位小数输出到屏幕上。 提示:定义圆的半径r,圆的周长:2*3.14*r,输出结果保留2位小数可以用%.2f 1.15输入两个整数,输出其中最大者。 提示:模仿例1.3 1.16 输入三个整数,输出其中最小者。 提示: min(min(a,b),c);

c语言程序设计题目及答案

一、程序设计共113 题第1 题题 号:319 #include "stdio.h" void wwjt(); int fun(int m) { int i,k=1; if(m<=1) k=0; for(i=2;i #include #define M 3 #define N 4 void wwjt(); void fun(int tt[M][N],int pp[N]) { int i,j; for(j=0;jpp[j]) pp[j]=tt[i][j]; } 第3 题题号:375 功能:从键盘上输入任意实数, 求出其所对应的函数值。 z=e 的x 次幂( x>10) z=log(x+3) (x>-3) z=sin(x)/((cos(x)+4) #include #include void wwjt(); double y(float x) { double z; if(x>10) z=exp(x); else if(x>-3) z=log(x+3); else z=sin(x)/(cos(x)+4); return(z); } 第4 题题号:334 功能:求给定正整数n 以内的素数之积。( n<28) #include #include"conio.h" void wwjt(); long fun(int n) { long i,k; long s=1; for(i=2;i<=n;i++) {for(k=2;k #include"conio.h" void wwjt(); long int fun(int n) { long s=1,i; for(i=2;i<=n;i++) if(n%i==0)s=s*i; return s; } 第6 题题号:50 功能:求出菲波那契数列的前一项与后一项之比的极限的 近似值例如:当误差为0.0001 时,函数值为0.618056 。 #include #include "math.h" void wwjt(); float fun() { float f1=1,f2=1,f3; float r1=2,r2; do {r2=r1; r1=f1/f2; f3=f1+f2; f1=f2; f2=f3; }while(fabs(r1-r2)>1e-4); return r1; } 第7 题题号:394 功能:产生20个[30,120] 上的随机整数放入二维数组 a[5][4] 中, 求其中的最小值。 #include "stdlib.h" #include void wwjt(); int amin(int a[5][4]) { int i,j,s; s=a[0][0];

相关文档
最新文档