C++-获取struct成员变量的偏移量

示例

struct MyStruct {
	int a;
	float b;
	char c;
};

size_t offset = (size_t) & (((MyStruct*)0)->a);
std::cout << offset << std::endl;
offset = (size_t) & (((MyStruct*)0)->b);
std::cout << offset << std::endl;
offset = (size_t) & (((MyStruct*)0)->c);
std::cout << offset << std::endl;

输出:

0
4
8

改变struct member顺序

struct MyStruct {
	char c;
	int a;
	float b;
};

输出:

4
8
0

原理

(size_t) & (((MyStruct*)0)->a)

将表达式从内向外解析,首先给出0地址或者使用nullptr均可,然后强转成结构体指针类型,接着访问成员a,然后取a的地址,由于一开始给出的地址是0地址,所以a的地址便是相对于结构体起始地址的偏移量

参考:

  1. https://www.youtube.com/watch?v=4p3grlSpWYA&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=45
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
中文名: 你必须知道的495个C语言问题 高清PDF中文版 原名: C Programming FAQs 作者: (美)萨米特译者: 孙云 朱群英资源格式: PDF 版本: 扫描版 出版社: 人民邮电出版社书号: 9787115194329发行时间: 2009年02月01日 地区: 大陆 语言: 简体中文 简介:   内容简介   本书以问答的形式组织内容,讨论了学习或使用C语言的过程中经常遇到的一些问题。书中列出了C用户经常问的400多个经典问题,涵盖了初始化、数组、指针、字符串、内存分配、库函数、C预处理器等各个方面的主题,并分别给出了解答,而且结合代码示例阐明要点。   本书结构清晰,讲解透彻,是各高校相关专业C语言课程很好的教学参考书,也是各层次C程序员的优秀实践指南。 作者简介 Steve Summit,著名的C语言专家。Usenet C FAQ的创始人和维护者,有近30年的C编程经验。毕业于麻省理工学院。他曾在华盛顿大学教授C语言课程多年。除本书外,他还与人合著了C Unleashed一书。 编辑推荐 全球C语言程序员集体智慧的结晶   Amazon全五星图书   权威解答495个最常遇到的C语言问题   C是一门简洁精妙的语言,掌握基本语法容易,真正能够自如运用,就不那么简单了。你难免会遇到各种各样的问题,有些可能让你百思不得其解,甚至翻遍图书馆,也找不到问题的答案。   《你必须知道的495个C语言问题》的出版填补了这一空白。书中内容是世界各地的C语言用户多年来在新闻组comp.1ang.c中讨论的成果。作者在网络版CFAQ列表的基础上进行了大幅度的扩充和丰富,结合代码示例,权威而且详细深入地解答了实际学习和工作中最常遇到的495个C语言问题,涵盖了初始化、数组、指针、字符串、内存分配、库函数、C预处理器等各个方面的主题。许多知识点的阐述都是其他资料中所没有的,弥足珍贵。   涵盖C99标准   “本书是Summit以及C FAQ在线列表的许多参与者多年心血的结晶,是C语言界最为珍贵的财富之一。我向所有C语言程序员推荐本书。”.       ——Francis Glassborow,著名C/C++专家,ACCU(C/C++用户协会)前主席   “本书清晰地阐明了Kernighan与Ritchie的The C Programming Language一书中许多简略的地方,而且精彩地总结了C语言编程实践,强烈推荐!”       ——Yechiel M.Kimchi,以色列理工学院 目录: 第1章 声明和初始化 基本类型 1.1 我该如何决定使用哪种整数类型? 1.2 为什么不精确定义标准类型的大小? 1.3 因为C语言没有精确定义类型的大小,所以我一般都用typedef定义int16和int32。然后根据实际的机器环境把它们定义为int、short、long等类型。这样看来,所有的问题都解决了,是吗? 1.4 新的64位机上的64位类型是什么样的? 指针声明 1.5 这样的声明有什么问题?char *p1, p2; 我在使用p2的时候报错了。 1.6 我想声明一个指针,并为它分配一些空间,但却不行。这样的代码有什么问题?char *p; *p=malloc(10); 声明风格 1.7 怎样声明和定义全局变量和函数最好? 1.8 如何在C中实现不透明(抽象)数据类型? 1.9 如何生成“半全局变量”,就是那种只能被部分源文件中的部分函数访问的变量? 存储类型 1.10 同一个静态(static)函数或变量的所有声明都必须包含static存储类型吗? 1.11 extern在函数声明中是什么意思? 1.12 关键字auto到底有什么用途? 类型定义(typedef) 1.13 对于用户定义类型,typedef 和#define有什么区别? 1.14 我似乎不能成功定义一个链表。我试过typedef struct{char *item; NODEPTR next;}* NODEPTR; 但是编译器报了错误信息。难道在C语言中结构不能包含指向自己的指针吗? 1.15 如何定义一对相互引用的结构? 1.16 Struct{ } x1;和typedef struct{ } x2; 这两个声明有什么区别? 1.17 “typedef int(*funcptr)();”是什么意思? const 限定词 1.18 我有这样一组声明:typedef char *charp; const charp p; 为什么是p而不是它指向的字符为const? 1.19 为什么不能像下面这样在初始式和数组维度值中使用const值?const int n=5; int a[n]; 1.20 const char *p、char const *p和char *const p有什么区别? 复杂的声明 1.21 怎样建立和理解非常复杂的声明?例如定义一个包含N个指向返回指向字符的指针的函数的指针的数组? 1.22 如何声明返回指向同类型函数的指针的函数?我在设计一个状态机,用函数表示每种状态,每个函数都会返回一个指向下一个状态的函数的指针。可我找不到任何方法来声明这样的函数——感觉我需要一个返回指针的函数,返回的指针指向的又是返回指针的函数,如此往复,以至无穷。 数组大小 1.23 能否声明和传入数组大小一致的局部数组,或者由其他参数指定大小的参数数组? 1.24 我在一个文件中定义了一个extern数组,然后在另一个文件中使用,为什么sizeof取不到数组的大小? 声明问题 1.25 函数只定义了一次,调用了一次,但编译器提示非法重声明了。 1.26 main的正确定义是什么?void main正确吗? 1.27 我的编译器总在报函数原型不匹配的错误,可我觉得没什么问题。这是为什么? 1.28 文件中的第一个声明就报出奇怪的语法错误,可我看没什么问题。这是为什么? 1.29 为什么我的编译器不允许我定义大数组,如double array[256][256]? 命名空间 1.30 如何判断哪些标识符可以使用,哪些被保留了? 初始化 1.31 对于没有显式初始化的变量的初始值可以作怎样的假定?如果一个全局变量初始值为“零”,它可否作为空指针或浮点零? 1.32 下面的代码为什么不能编译? intf(){char a[]="Hello, world!";} 1.33 下面的初始化有什么问题?编译器提示“invalid initializers ”或其他信息。char *p=malloc(10); 1.34 char a[]= "string literal";和char *p="string literal"; 初始化有什么区别?当我向p[i] 赋值的时候,我的程序崩溃了。 1.35 char a{[3]}= "abc"; 是否合法? 1.36 我总算弄清楚函数指针的声明方法了,但怎样才能初始化呢? 1.37 能够初始化联合吗? 第2章 结构、联合和枚举 结构声明 2.1 struct x1{ };和typedef struct{ }x2; 有什么不同? 2.2 这样的代码为什么不对?struct x{ }; x thestruct; 2.3 结构可以包含指向自己的指针吗? 2.4 在C语言中用什么方法实现抽象数据类型最好? 2.5 在C语言中是否有模拟继承等面向对象程序设计特性的好方法? 2.6 为什么声明extern f(struct x *p); 给我报了一个晦涩难懂的警告信息? 2.7 我遇到这样声明结构的代码:struct name {int namelen; char namestr[1];};然后又使用一些内存分配技巧使namestr数组用起来好像有多个元素,namelen记录了元素个数。它是怎样工作的?这样是合法的和可移植的吗? 2.8 我听说结构可以赋给变量也可以对函数传入和传出。为什么K&R1;却明确说明不能这样做? 2.9 为什么不能用内建的==和!=操作符比较结构? 2.10 结构传递和返回是如何实现的? 2.11 如何向接受结构参数的函数传入常量值?怎样创建无名的中间的常量结构值? 2.12 怎样从/向数据文件读/写结构? 结构填充 2.13 为什么我的编译器在结构中留下了空洞?这导致空间浪费而且无法与外部数据文件进行“二进制”读写。能否关掉填充,或者控制结构域的对齐方式? 2.14 为什么sizeof返回的值大于结构大小的期望值,是不是尾部有填充? 2.15 如何确定域在结构中的字节偏移量? 2.16 怎样在运行时用名字访问结构中的域? 2.17 C语言中有和Pascal的with等价的语句吗? 2.18 既然数组名可以用作数组的基地址,为什么对结构不能这样? 2.19 程序运行正确,但退出时却“core dump ”(核心转储)了,怎么回事? 联合 2.20 结构和联合有什么区别? 2.21 有办法初始化联合吗? 2.22 有没有一种自动方法来跟踪联合的哪个域在使用? 枚举 2.23 枚举和一组预处理的#define有什么不同? 2.24 枚举可移植吗? 2.25 有什么显示枚举值符号的容易方法吗? 位域 2.26 一些结构声明中的这些冒号和数字是什么意思? 2.27 为什么人们那么喜欢用显式的掩码和位操作而不直接声明位域? 第3章 表达式 求值顺序 3.1 为什么这样的代码不行?a[i]= i++; 3.2 使用我的编译器,下面的代码int i= 7; printf("%d\n", i++ * i++); 打印出49。不管按什么顺序计算,难道不该是56吗? 3.3 对于代码int i=3; i=i++; 不同编译器给出不同的i值,有的为3,有的为4,哪个是正确的? 3.4 有这样一个巧妙的表达式:a^= b^= a^= b; 它不需要临时变量就可以交换a和b的值。 3.5 可否用显式括号来强制执行我所需要的计算顺序并控制相关的副作用?就算括号不行,操作符优先级是否能够控制计算顺序呢? 3.6 可是&&和||操作符呢?我看到过类似while((c = getchar()) != EOF && c != '\n')的代码 3.7 是否可以安全地认为,一旦&&和||左边的表达式已经决定了整个表达式的结果,则右边的表达式不会被求值? 3.8 为什么表达式printf("%d %d", f1(), f2()); 先调用了f2?我觉得逗号表达式应该确保从左到右的求值顺序。 3.9 怎样才能理解复杂表达式并避免写出未定义的表达式?“序列点”是什么? 3.10 在a[i] = i++;中,如果不关心a[]的哪一个分量会被写入,这段代码就没有问题,i也的确会增加1,对吗? 3.11 人们总是说i=i++的行为是未定义的。可我刚刚在一个ANSI编译器上尝试过,其结果正如我所期望的。 3.12 我不想学习那些复杂的规则,怎样才能避免这些未定义的求值顺序问题呢? 其他的表达式问题 3.13 ++i和i++有什么区别? 3.14 如果我不使用表达式的值,那我应该用i++还是++i来做自增呢? 3.15 我要检查一个数是不是在另外两个数之间,为什么if(a b c)不行? 3.16 为什么如下的代码不对?int a=1000, b=1000; long int c=a * b; 3.17 为什么下面的代码总是给出0?double degC, degF; degC= 5.0 / 9 * (degF - 32); 3.18 需要根据条件把一个复杂的表达式赋给两个变量中的一个。可以用下面这样的代码吗?((condition) ? a : = complicated_expression; 3.19 我有些代码包含这样的表达式。a ? b=c : d 有些编译器可以接受,有些却不能。为什么? 保护规则 3.20 “semantics of‘’change in ANSI C”的警告是什么意思? 3.21 “无符号保护”和“值保护”规则的区别在哪里? 第4章 指针 基本的指针应用 4.1 指针到底有什么好处? 4.2 我想声明一个指针并为它分配一些空间,但却不行。这些代码有什么问题呢?char *p; *p =malloc(10); 4.3 *p++自增p还是p所指向的变量? 指针操作 4.4 我用指针操作int数组的时候遇到了麻烦。 4.5 我有一个char *型指针碰巧指向一些int型变量,我想跳过它们。为什么((int *)p)++; 这样的代码不行? 4.6 为什么不能对void *指针进行算术操作? 4.7 我有些解析外部结构的代码,但是它却崩溃了,显示出了“unaligned access”(未对齐的访问)的信息。这是什么意思? 作为函数参数的指针 4.8 我有个函数,它应该接受并初始化一个指针:void f(int *ip){ static int dummy = 5; ip = &dummy;}但是当我如下调用时:int *ip; f(ip); 调用者的指针没有任何变化。 4.9 能否用void ** 通用指针作为参数,使函数模拟按引用传递参数? 48 4.10 我有一个函数extern intf(int *); ,它接受指向int型的指针。我怎样用引用方式传入一个常数?调用f(&5);似乎不行。 4.11 C语言可以“按引用传参”吗? 其他指针问题 4.12 我看到了用指针调用函数的不同语法形式。到底怎么回事? 4.13 通用指针类型是什么?当我把函数指针赋向void *类型的时候,编译通不过。 4.14 怎样在整型和指针之间进行转换?能否暂时把整数放入指针变量中,或者相反? 4.15 我怎样把一个int变量转换为char *型?我试了类型转换,但是不行。 第5章 空指针 空指针和空指针常量 5.1 臭名昭著的空指针到底是什么? 5.2 怎样在程序里获得一个空指针? 5.3 用缩写的指针比较“if(p)”检查空指针是否有效?如果空指针的内部表达不是0会怎样? NULL 宏 5.4 NULL是什么,它是怎么定义的? 5.5 在使用非零位模式作为空指针的内部表示的机器上,NULL 是如何定义的? 5.6 如果NULL定义成#define NULL((char *)0) ,不就可以向函数传入不加转换的NULL 了吗? 5.7 我的编译器提供的头文件中定义的NULL为0L。为什么? 5.8 NULL可以合法地用作函数指针吗? 5.9 如果NULL和0作为空指针常量是等价的,那我到底该用哪一个呢? 5.10 但是如果NULL的值改变了,比如在使用非零内部空指针的机器上,用NULL(而不是0) 不是更好吗? 5.11 我曾经使用过一个编译器,不使用NULL就不能编译。 5.12 我用预处理宏#define Nullptr(type)(type *)0帮助创建正确类型的空指针。 回顾 5.13 这有点奇怪:NULL可以确保是0,但空(null)指针却不一定? 5.14 为什么有那么多关于空指针的疑惑?为什么这些问题如此频繁地出现? 5.15 有没有什么简单点儿的办法理解所有这些与空指针有关的东西呢? 5.16 考虑到有关空指针的所有这些困惑,要求它们的内部表示都必须为0不是更简单吗? 5.17 说真的,真有机器用非零空指针吗,或者不同类型用不同的表示? 地址0上到底有什么? 5.18 运行时的整数值0转换为指针以后一定是空指针吗? 5.19 如何访问位于机器地址0处的中断向量?如果我将指针值设为0,编译器可能会自动将它转换为非零的空指针内部表示。 5.20 运行时的“null pointer assignment”错误是什么意思?应该怎样捕捉它? 第6章 数组和指针 数组和指针的基本关系 6.1 我在一个源文件中定义了char a[6],在另一个源文件中声明了extern char *a。为什么不行? 6.2 可是我听说char a[]和char *a是等价的。是这样的吗? 6.3 那么,在C语言中“指针和数组等价”到底是什么意思? 6.4 既然它们这么不同,那为什么作为函数形参的数组和指针声明可以互换呢? 数组不能被赋值 6.5 为什么不能这样向数组赋值?extern char *getpass(); char str[10]; str=getpass("Enter password:"); 6.6 既然不能向数组赋值,那这段代码为什么可以呢?int f(char str[]){ if(str[0] == '\0') str="none";} 6.7 如果你不能给它赋值,那么数组如何能成为左值呢? 回顾 6.8 现实地讲,数组和指针的区别是什么? 6.9 有人跟我讲,数组不过是常指针。这样讲准确吗? 6.10 我还是很困惑。到底指针是一种数组,还是数组是一种指针? 6.11 我看到一些“搞笑”的代码,包含5["abcdef"]这样的“表达式”。这为什么是合法的C语言表达式呢? 数组的指针 6.12 既然数组引用会退化为指针,如果array是数组,那么array和&array;又有什么区别呢? 6.13 如何声明一个数组的指针? 动态数组分配 6.14 如何在运行时设定数组的大小?怎样才能避免固定大小的数组? 6.15 我如何声明大小和传入的数组一样的局部数组? 6.16 如何动态分配多维数组? 6.17 有个很好的窍门,如果我这样写:int realarray[10]; int *array = &realarray;[-1]; 我就可以把“array”当作下标从1 开始的数组。 函数和多维数组 6.18 当我向一个接受指针的指针的函数传入二维数组的时候,编译器报错了。 6.19 我怎样编写接受编译时宽度未知的二维数组的函数? 6.20 我怎样在函数参数传递时混用静态和动态多维数组? 数组的大小 6.21 当数组是函数的参数时,为什么sizeof不能正确报告数组的大小? 6.22 如何在一个文件中判断声明为extern的数组的大小(例如,数组定义和大小在另一个文件中)?sizeof操作符似乎不行。 6.23 sizeof返回的大小是以字节计算的,怎样才能判断数组中有多少个元素呢? 第7章 内存分配 第8章 字符和字符串 第9章 布尔表达式和变量 第10章 C预处理器 第11章 ANSI/ISO标准C 第12章 标准输入输出库 第13章 库函数 第14章 浮点运算 第15章 可变参数列表 第16 章 奇怪的问题 第17章 风格 第18章 工具和资源 第19章 系统依赖 第20章 杂项 术语表 参考文献
定义数据结构如下: typedef struct tagMyNode { Mytype type; //元件类型 MySubtype Subtype; //元件子类型 tagMyNode* input1; //输入端1 tagMyNode* input2; //输入端1 tagMyNode* output1; //输出端1 UINT input1value; //输入端input1的值 UINT input2value; //输入端input2的值 UINT output1value; //输出端output1的值 int inputs; //当前已经有几个输入端有值 int number; //对于输入结点的序号 CPoint Orgpoint; //记录元件左上角位置 int width; //记录元件宽度 int height; //记录元件高度 }MyNode;  元件类型:元件类型Mytype type中Mytype是一个枚举类型定义如下: enum Mytype { Node, //结点 Gate, //门 }; 分为两种类型:Node结点和Gate门。  元件子类型:元件子类型MySubtype Subtype中MySubtype也是一个枚举类 型,定义如下: enum MySubtype { Input, //输入端 Output, //输出端 ANDGate, //与门 ORGate, //或门 NOTGate, //非门 NORGate, //或非门 NANDGate, //与非门 XORGate, //异或门 };  指针连接: tagMyNode* input1; tagMyNode* input2; tagMyNode* output1 是指向此结点的指针。由于元件之间是要相互连接的,于是设置这几个指针用于元件之间的连接。其中特殊情况有: 非门:由于非门只有一个输入端,所以非门不用tagMyNode* input2; 输入结点:输入结点只有一个链接端(这里称之为触点),采用tagMyNode* output1 输出结点:同输入结点,只有一个触点,采用tagMyNode* input1;  保存触点值:由于要进行仿真计算,所以还需保存各个触点的值: UINT input1value; UINT input2value; UINT output1value; 同指针连接,有3种特殊情况: 非门:不用UINT input2value; 输入结点:采用UINT output1value; 输出结点:采用UINT input1value;  进位标志:int inputs; 在进行仿真计算时,要用进位标志辅助计算。如与门只有在两个输入端都有值时,即inputs==2时,才能进位。  输入结点序号:int number; 每个输入结点都有不同的序号,从1开始递增。  元件位置和大小: CPoint Orgpoint; int width; int height; Orgpoint用于记录元件在视图中左上角的坐标 width用于记录元件宽度 height用于记录元件高度  电路图编辑模块 电路图编辑模块又分为两个子模块:鼠标放置元件模块,鼠标连接元件模块 首先在工具栏中可以选择这两种状态,如图4 图4 在按钮上单击可以切换状态。 定义一个枚举类型MyStatus来记录当前状态: enum MyStatus { NONE, //鼠标连接元件状态 ANDGATE, ORGATE, NOTGATE, NORGATE, NANDGATE, XORGATE, NODEINPUT, NODEOUTPUT }; MyStatus Status; 其中:NONE为鼠标连接状态,其他为鼠标放置状态。  鼠标放置元件模块 其算法如图5: 图5  DrawObject函数: 首先根据Status的状态,即六个门,两个端结点。共8种来调用DrawObject函数  引入准备好的八张位图(六个门,两个端) CBitmap MyBitMap; MyBitMap.LoadBitmap (nID);  将引入的位图拷贝入窗体窗户区 BITMAP bmpInfo; MyBitMap.GetBitmap (&bmpInfo); pOldBitmap=dc.SelectObject (&MyBitMap); ClientDC.BitBlt (point.x ,point.y,bmpInfo.bmWidth ,bmpInfo.bmHeight,&dc,0,0,SRCAND); dc.SelectObject (pOldBitmap);  用全局变量bmWidth和bmHeight来保存元件的宽度和高度 bmWidth=bmpInfo.bmWidth ; bmHeight=bmpInfo.bmHeight ;  CreateMyObject函数 函数声明为:CreateMyObject(Mytype type, MySubtype Subtype, CPoint point)  初始化元件 MyNode* pNode=new MyNode; pNode->type =type; pNode->Subtype =Subtype; pNode->input1 =0; pNode->input2 =0; pNode->output1 =0; pNode->output2 =0; pNode->Orgpoint =point; pNode->width =bmWidth; pNode->height =bmHeight; pNode->input1value =0; pNode->input2value =0; pNode->output1value =0; pNode->inputs =0;  如果创建的元件为输入结点,则要创建并画输入结点前的序号,这里 采用一个全局数组CArray<CPoint,CPoint> numpoint来记录结点前序号。 if(Subtype==Input) { //当创建Input时加入点到numpoint数组中 numpoint.Add (CPoint(point.x-15,point.y)); pNode->number =numpoint.GetSize (); //创建时重绘序号 redrawnum(); } 而redrawnum()函数就是将所有输入结点前的序号重绘。  最后将元件加入到全局链表CList<MyNode*,MyNode*> MyList中。 MyList.AddTail (pNode);  鼠标连接元件模块 鼠标连接元件模块分为三个过程模块:鼠标移动模块,鼠标按下模块,鼠标抬起模块。  鼠标移动模块 其算法如图6 图6 代码如下: void CMyView::OnMouseMove(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default //此时必然是非画图状态,所以status==NONE; if(Status==NONE) { //当前点在某个物件上吗? 并且 //当前点在该物件触点上吗? if(IsPointInObject(point) && IsPointInPut(point)) { //全局变量pNodeNow是在IsPointInObject()这个函数里面记录的 //circlepoint和put是在IsInInput1() IsInInput2() IsInOutput1() //这三个函数中记录的 //判断此时触点时否己连接非常重要 if(IsPutLinked()) { //如果此时触点己连接,则退出 return; } //此时鼠标移进触点 //当前是连接态吗? if(IsLink) { //连接态画图 LinkStatusDraw(point); } //开启画圆圈态 IsDrawCircle=TRUE; //画圆圈 DrawMyCircle(); } else//此时鼠标移出触点 { //如果此时已画圆圈,则要擦除圆圈 if(IsDrawCircle==TRUE) { EraserMyCircle(); //关闭画圆圈状态 IsDrawCircle=FALSE; //重绘连接线 moveoutredrawline(); //重绘圆圈所在的那个物件,因为擦除圆圈的时候可能擦除了部分物件 //------------------- redrawMyObject(pNodeNow); //如果此时是连接状态,连接态画图 } if(IsLink) { //连接态画图 LinkStatusDraw(point); } } } CView::OnMouseMove(nFlags, point); }  两个关键状态:可连接态IsDrawCircle和正在连接态IsLink  可连接态IsDrawCircle 当且仅当鼠标移动到某个元件上的某个尚未连接的触点上,才开启可连接态IsDrawCircle。之所以取名IsDrawCircle是因为此时会在鼠标停留的尚未连接的触点上画一个黑色小圆圈。 当鼠标移动离开触点,可连接态IsDrawCircle关闭。  正在连接态IsLink 当鼠标按下(见图5)并且此时可连接态IsDrawCircle开启(为TRUE)时正在连接态IsLink开启。  判断当前点是否在某个元件函数:IsPointInObject() 其算法如图7 图7  判断当前点是否在该元件触点上函数:IsPointInPut() 其算法如图8 图8 与门与其它5个门有所不同,与门只有一个输入端,所以要分开来判断 对于输入结点,则判断当前点是否在第一个输出端触点。 对于输出结点,则判断当前点是否在第一个输入端触点。 输入结点和输出结点的这样判断,一眼看上去似乎反了,但实际上有利于整个程序的编写。可以简单地这样分类:总共只有两种端,一种输入,一种输出。 这样,我们就可以将判断触点分为三个函数: IsInInput1() IsInInput2() IsInOutput1() 拿IsInInput1()来分析: centerpoint=GetCirclePoint(Input_1); if(IsInArea(point)) { //说明此时就在触点Input_1,用全局变量put记录下来 put=Input_1; //如果当前点在,则要保存触点中心点 circlepoint=centerpoint; return TRUE; } else { //如果移出触点,肯定不要再保存中心点 return FALSE; } 首先,调用函数GetCirclePoint()来取得当前触点的中心点。然后调用IsInArea(point)函数来判断当前点point是否在以当前触点中心点为中心的矩形区域内。如果是,则用一个全局枚举变量put来记录来前触点是两个输入端和一个输出端中哪一个。 我们看这个枚举类型: enum Myput { Input_1, Input_2, Output_1 }; 接下来用一个全局变量circlepoint来记录当前触点中心点。再返回真。 如果当前点不在以当前触点中心点为中心的矩形区域内,则返回假。这时千万不能记录当前触点中心点。这点不注意会出大错。  判断当前触点是否已连接函数:IsPutLinked() BOOL CMyView::IsPutLinked() { switch(put) { case Input_1: if(pNodeNow->input1 !=0) return TRUE; break; case Input_2: if(pNodeNow->input2 !=0) return TRUE; break; case Output_1: if(pNodeNow->output1 !=0) return TRUE; } return FALSE; } 这里根据全局变量put的类型和全局变量pNodeNow所指向的元件, 就可以判断当前元件的当前触点是否已连接。如果连接相应指针不为0。返回真,否则返回假。  连接态画图函数:LinkStatusDraw() void CMyView::LinkStatusDraw(CPoint point) { CClientDC clientDC(this); CPen whitepen(PS_SOLID,1,RGB(255,255,255)); CPen* pOldPen; pOldPen=clientDC.SelectObject (&whitepen); clientDC.MoveTo (startpoint); clientDC.LineTo (lastpoint); clientDC.SelectObject (pOldPen); CPen redpen(PS_DOT ,1,RGB(255,0,0)); pOldPen=clientDC.SelectObject (&redpen); clientDC.MoveTo (startpoint); clientDC.LineTo (point); clientDC.SelectObject (pOldPen); lastpoint=point; //重绘所有输入结点前的序号 redrawnum(); //重绘连接线 LinkLineRedraw(startpoint,point); //重绘物件 lineRedraw(startpoint,point); } 这里,startpoint是鼠标按下开始连接时起始元件触点中心点坐 标,lastpoint是上一次鼠标移动所停留的点。为了实在连接时鼠标移动 的动画效果,我们要先擦除上一次移动画的线(用白笔),然后再从startpoint到当前点point画线。移动时由于不信的擦除重画,可能将先前已画的元件,输入结点前的序号,和已经连接好的线擦除。于是我们需要重绘。 重绘所有输入结点前的序号 redrawnum(); void CMyView::redrawnum() { CClientDC dc(this); char buffer[20]; CPoint point; //重绘所有Input前的序号 for(int i=0;i<numpoint.GetSize ();i++) { //将数字转换成字符,存于buffer中 _itoa(i+1,buffer,10); point=numpoint.GetAt (i); dc.TextOut (point.x ,point.y ,buffer); } } 由于每创建一个输入结点,就要相应地记录一个序号。这个序号的位置点记录在numpoint这个数组中。数组声明如下: CArray<CPoint,CPoint> numpoint; 而数组的下标加1就为序号。 所以每次重绘为了方便,将所有序号都重绘。 重绘连接线 void CMyView::LinkLineRedraw(CPoint startpoint, CPoint point) { //将起点startpoint到终点point扩充成一个矩形drawrect CRect drawrect(startpoint,point); //rect用于产生连接线最大矩形 CRect rect; //rectInter用于计算两个矩形的相交区域 CRect rectInter; //point1和point2用于产生连接线最大矩形 CPoint point1; CPoint point2; drawrect.NormalizeRect (); drawrect.InflateRect (1,1); //遍历MyPointList链表 POSITION pos=MyPointList.GetHeadPosition (); while(pos!=0) { //pPointArray用于指向点数组对象首址 CArray<CPoint,CPoint>* pPointArray=MyPointList.GetNext (pos); point1=pPointArray->GetAt (0); switch(pPointArray->GetSize ()) { //分两种情况 :2个点和4,5个点的情况 case 2: //2个点时 point2=pPointArray->GetAt (1); break; default: //4,5个点时 point2=pPointArray->GetAt (3); } //用point1和point2设置矩形rect rect.left =point1.x ; rect.top =point1.y; rect.right =point2.x; rect.bottom =point2.y; rect.NormalizeRect (); rect.InflateRect (1,1); //如果两个矩形相交,则要重绘 if(rectInter.IntersectRect (&drawrect,&rect)) { DrawLinkLine(pPointArray); } } } 主要的算法思想是:将起点startpoint到当前点point扩充成一个矩形drawrect,然后遍历连接线链表,将每根连接线扩充成一个矩形rect,再判断这两个矩形是否相交,若相交,则需要重绘这根连接线。 连接线链表声明如下: CList<CArray<CPoint,CPoint>*,CArray<CPoint,CPoint>*> MyPointList; 链表中每个结点是一个数组对象的地址,而这个数组中每个元素是一个点。这样一个数组就表示了一根连接线,而一个链表可以遍历所以连接线。  画提示连接的小圆圈函数:DrawMyCircle() void CMyView::DrawMyCircle() { //此时全局变量circlepoint记录了要画圆圈的 //而pNodeNow指向了当前的物件 //将物件坐标中的circlepoint转换成VIEW中的坐标 int x,y; x=pNodeNow->Orgpoint .x +circlepoint.x; y=pNodeNow->Orgpoint .y +circlepoint.y; CClientDC dc(this); //创建一个黑色的画刷 CBrush brush(RGB(0,0,0)); //创建指针pOldBrush用于保存原来的画刷 CBrush* pOldBrush; //将黑色的画刷选进设备装置DC,并用pOldBrush保存原来的画刷 pOldBrush=dc.SelectObject (&brush); //画一个圆圈,圆心是(x,y) //半径是4 dc.Ellipse (x-4,y-4,x+4,y+4); //将原来的画刷选回 dc.SelectObject (pOldBrush); } 由于全局变量circlepoint保存的是元件内部的相对坐标,需要将它 转换成视图中的坐标 x=pNodeNow->Orgpoint .x +circlepoint.x; y=pNodeNow->Orgpoint .y +circlepoint.y; 以上两句完成坐标的转换。 然后以(x,y)为圆心,4为半径,画一个黑色小圆圈 dc.Ellipse (x-4,y-4,x+4,y+4)  擦除小圆圈函数:EraserMyCircle() void CMyView::EraserMyCircle() { int x,y; x=pNodeNow->Orgpoint .x +circlepoint.x; y=pNodeNow->Orgpoint .y +circlepoint.y; CClientDC dc(this); CPen whitepen(PS_SOLID,1,RGB(255,255,255)); CPen* pOldPen; pOldPen=dc.SelectObject (&whitepen); dc.Ellipse (x-4,y-4,x+4,y+4); dc.SelectObject (pOldPen); } 与画小圆圈不同的是,擦除时要选择白色的笔和白色的画刷(默认) CPen whitepen(PS_SOLID,1,RGB(255,255,255)); CPen* pOldPen; pOldPen=dc.SelectObject (&whitepen); 以上3句选择白色的笔。  鼠标移开触点重绘连接线函数:moveoutredrawline() 为什么需要这个函数,原因是在鼠标称出触点后,此时要擦除刚才画 的小圆圈,而如果此时已经生成了连接线,则会擦除掉连接线的一小部分。于是需要这个函数。 void CMyView::moveoutredrawline() { int x,y; x=pNodeNow->Orgpoint .x +circlepoint.x; y=pNodeNow->Orgpoint .y +circlepoint.y; CPoint point1; CPoint point2; point1.x=x-4; point1.y=y-4; point2.x=x+4; point2.y=y+4; LinkLineRedraw(point1,point2); } 此时pNodeNow指向刚擦除小圆圈的元件,而circlepoint则记录着 触点中心。于是只要将以ciclepoint为中心的半径为4的矩形的左上角点和右下角点为参数调用LinkLineRedraw即可。  重绘元件函数redrawMyObject() void CMyView::redrawMyObject(MyNode* pNode) { switch(pNode->Subtype ) { case ANDGate: DrawObject(pNode->Orgpoint ,IDB_ANDGATE); break; case ORGate: DrawObject(pNode->Orgpoint,IDB_ORGATE); break; case NOTGate: DrawObject(pNode->Orgpoint,IDB_NOTGATE); break; case NORGate: DrawObject(pNode->Orgpoint,IDB_NORGATE); break; case NANDGate: DrawObject(pNode->Orgpoint,IDB_NANDGATE); break; case XORGate: DrawObject(pNode->Orgpoint,IDB_XORGATE); break; case Input: DrawObject(pNode->Orgpoint,IDB_NODEINPUT); break; case Output: DrawObject(pNode->Orgpoint,IDB_NODEOUTPUT); break; } } 该函数参数为指向元件的指针,用于重绘所指向的元件。  鼠标按下模块 如图5 图5 前面已经分析了放置元件状态,现在看连接元件状态中的判断: “当前点是否在某个元件未连接的触点上”其实就是判断“可连接态”IsDrawCircle是否为真。代码如下: if(IsDrawCircle)//当前点在某个元件未连接的触点上 { //全局变量IsLink表示开始连接状态 IsLink=TRUE; //全局变量pNodeStart记录当前物件 pNodeStart=pNodeNow; //全局变量startpoint记录当前触点中心坐标(注,此时要进行坐标转换 startpoint.x=pNodeNow->Orgpoint .x +circlepoint.x; startpoint.y=pNodeNow->Orgpoint .y +circlepoint.y; //全局变量startput记录当前触点类别:Input_1,Input_2,Output_1; startput=put; //lastpoint用于鼠标移动时擦除线效果 lastpoint=startpoint; } 进行连接初始化:首先开启开始连接状态 IsLink=TRUE; 然后用全局变量pNodeStart指向当前元件 pNodeStart=pNodeNow 全局变量startpoint记录当前触点中心坐标(这时要进行坐标的转换) startpoint.x=pNodeNow->Orgpoint .x +circlepoint.x; startpoint.y=pNodeNow->Orgpoint .y +circlepoint.y; 全局变量startput记录当前触点类别 startput=put; 最后lastpoint用于鼠标移动时擦除线效果 lastpoint=startpoint;  鼠标抬起模块 其算法如图9 图9 代码如下: void CMyView::OnLButtonUp(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default if(IsLink) { //首先擦除从startpoint到point CClientDC clientDC(this); CClientDC* pDC=&clientDC; CPen whitepen(PS_SOLID,1,RGB(255,255,255)); CPen* pOldPen; pOldPen=clientDC.SelectObject (&whitepen); clientDC.MoveTo (startpoint); clientDC.LineTo (point); clientDC.SelectObject (pOldPen); //重绘所有输入结点前的序号 redrawnum(); //重绘连接线 LinkLineRedraw(startpoint,point); //重绘物件 lineRedraw(startpoint,point); if(IsDrawCircle) { //用全局变量pNodeCurrent记录终点连接的物体 pNodeCurrent=pNodeNow; //用全局变量currentput记录终点连接的触点 currentput=put; //用全局变量currentpoint记录终点触点的中心坐标 currentpoint.x=pNodeNow->Orgpoint .x +circlepoint.x; currentpoint.y=pNodeNow->Orgpoint .y +circlepoint.y; //IsTwoObjectsCanLink()函数判断两个物件是否能连接 if(IsTwoObjectsCanLink()) { //先擦除圆圈 //EraserMyCircle();没有必要,只要鼠标移开时重绘连接线就可 //开始两个物件的画图连接 LineLink(); //开始真正连接:指针连接 RealLink(); } } //关闭连接状态: IsLink=FALSE; } CView::OnLButtonUp(nFlags, point); }  判断两个元件是否可以连接 BOOL CMyView::IsTwoObjectsCanLink() { //判断两个物件是否能连接 //这两个物件分别由pNodeStart和pNodeCurrent指向 //两个触点分别由startput和currentput标识 //若所指同一物件 if(pNodeStart==pNodeCurrent) { MessageBox("连接错误!自身物件不能相互连接"); return FALSE; } //输出直接结输出 if(startput==Output_1 && currentput==Output_1) { MessageBox("连接错误!输出端不能相互连接"); return FALSE; } //输入直接连接输入 if( (startput==Input_1 || startput==Input_2) && (currentput==Input_1||currentput==Input_2) ) { MessageBox("连接错误!输入端不能相互连接"); return FALSE; } //循环连接 if( (startput==Output_1) &&(currentput==Input_1||currentput==Input_2) ) { if(pNodeCurrent->output1 ==pNodeStart) { MessageBox("连接错误!不能循环连接"); return FALSE; } } if( (startput==Input_1||startput==Input_2) &&(currentput==Output_1) ) { if(pNodeStart->output1 ==pNodeCurrent) { MessageBox("连接错误!不能循环连接"); return FALSE; } } //如果以上情况都不发生,表示可以连接 return TRUE; } 用图来表示上述几种错误: 同一元件不能连接 图10 输出端不能连接输出端 图11 输入端不能连接输入端 图12 两个元件不能循环连接 图13  两个元件的画图连接:LineLink() 该函数调用了recordLine() 代码如下: void CMyView::recordLine () { //记录两个物件之间的连接线经过的关键点 //先动态生成一个数组CArray<CPoint,CPoint>之对象 //记录下连接线的关键点,然后将这个数组对象之地址加入到 //CList<CArray<CPoint,CPoint>*,CArray<CPoint,CPoint>*> MyPointList中 int x0,y0,x1,y1,delta_x,delta_y; //(x0,y0)用于记录输出端起始点坐标 //(x1,y1)用于记录输入端终点坐标 //delta_x,delta_y用于记录x和y的偏移量 //一定是从输出端向输入端画线 if(startput==Output_1) { x0=startpoint.x; y0=startpoint.y; x1=currentpoint.x; y1=currentpoint.y; } else { x1=startpoint.x; y1=startpoint.y; x0=currentpoint.x; y0=currentpoint.y; } delta_x=5; //动态生成数组对象 CArray<CPoint,CPoint>* pPointArray=new CArray<CPoint,CPoint>; //根据点的位置分为三种情况:2个点,4个点,5个点 if(x0<x1) { if(y0==y1) { //两个点情况 pPointArray->Add (CPoint(x0,y0)); pPointArray->Add (CPoint(x1,y1)); } else { //4个点情况 pPointArray->Add (CPoint(x0,y0)); pPointArray->Add (CPoint(x0+delta_x,y0)); pPointArray->Add (CPoint(x0+delta_x,y1)); pPointArray->Add (CPoint(x1,y1)); } } else if(x0==x1) { //两个点情况 pPointArray->Add (CPoint(x0,y0)); pPointArray->Add (CPoint(x1,y1)); } else //x0>x1 { //5个点情况 if(y0<y1) { delta_y=20; } else { delta_y=-20; } pPointArray->Add (CPoint(x0,y0)); pPointArray->Add (CPoint(x0,y0+delta_y)); pPointArray->Add (CPoint(x1-delta_x,y0+delta_y)); pPointArray->Add (CPoint(x1-delta_x,y1)); pPointArray->Add (CPoint(x1,y1)); } //加入当前数组对象地址到MyPointList MyPointList.AddTail (pPointArray); //用数组中的点画线 DrawLinkLine(pPointArray); } 首先保证从输出端向输入端画线,这样可以统一画线操作。 然后动态生成数组: CArray<CPoint,CPoint>* pPointArray=new CArray<CPoint,CPoint>; 用指针pPointArray指向该数组,用于存储连接线的关键点。 连接线根据位置总共有三种线型,如下图所示: (1)两个关键点的连接线: 图14 (2)4个关键点的连接线 图15 (3)5个关键点的连接线 图16  两个元件的指针连接:RealLink(); 其代码如下: void CMyView::RealLink() { //一定是输入连接输出 或 输出连接输入 if(startput==Input_1||startput==Input_2) { //输入连接输出 if(startput==Input_1) { pNodeStart->input1 =pNodeCurrent; } else { pNodeStart->input2 =pNodeCurrent; } pNodeCurrent->output1 =pNodeStart; } else//startput==Output_1 { //输出连接输入 pNodeStart->output1 =pNodeCurrent; if(currentput==Input_1) { pNodeCurrent->input1 =pNodeStart; } else { pNodeCurrent->input2 =pNodeStart; } } } 指针连接只有两种情况:输入连接输出和输出连接输入。可以用下图来表示 输入端连接输出端 图17 输出端连接输入端 图18  元件库模块 代码如下: UINT CMyView::gatefunction(MyNode *pNode) { UINT result; switch(pNode->Subtype ) { case ANDGate: result=pNode->input1value & pNode->input2value ; break; case ORGate: result=pNode->input1value | pNode->input2value ; break; case NOTGate: result=pNode->input1value ; result=1-result; break; case NORGate: result=pNode->input1value | pNode->input2value ; result=1-result; break; case NANDGate: result=pNode->input1value & pNode->input2value ; result=1-result; break; case XORGate: result=pNode->input1value ^ pNode->input2value ; } return result; } 这里pNode是指向当前元件的指针,根据当前元件的类型,及当前元件的输入端的值input1value和input2value(注:非门只有一个输入端)来返回元件的输出端的值。 各个门真值表如下表所示: (1)与门 输入端1 输入端2 输出端 0 0 0 0 1 0 1 0 0 1 1 0 表1 (2)或门 输入端1 输入端2 输出端 0 0 0 0 1 1 1 0 1 1 1 1 表2 (3)非门 输入端 输出端 0 1 1 0 表3 (4)与非门 输入端1 输入端2 输出端 0 0 1 0 1 1 1 0 1 1 1 0 表4 (5)或非门 输入端1 输入端2 输出端 0 0 0 0 1 1 1 0 1 1 1 1 表5 (6)异或门 输入端1 输入端2 输出端 0 0 0 0 1 1 1 0 1 1 1 0 表6  计算结果(仿真)模块 仿真模块在整个仿真器中占有最重要的作用。 当在视图窗体上放置元件,连接元件后。接下工具栏开始按钮 开始计算结果,进行仿真。 其主要算法如图19 图19 代码如下: void CMyView::OnBegin() { //开始计算,输出真值表 // TODO: Add your command handler code here //判断是否能够连接 if(CalculateResult()==-1) { MessageBox("连接线路失败!请检查线路"); } else { //可以连接 //调用函数计算 beginCalculate(); //生成对话框对象 CMyDialog MyDialog; MyDialog.DoModal (); } } 其中判断线路是否正确调用了CalculateResult(),这是仿真中最重要的函数。它的返回值是最终输出结点的值。如果返回-1,说明线路有误。其具体的算法如图20: CalculateResult() 图20 代码如下: int CMyView::CalculateResult() { //用于从输入端开始计算输出端结果 //遍历所有输入结点 MyNode* pNode; MyNode* pNodeNext; POSITION pos=MyList.GetHeadPosition (); while(pos!=0) { pNode=MyList.GetNext (pos); //判断当前结点是否是输入结点 if(pNode->Subtype ==Input) { for(;;) { //判断当前的输入结点的输出端指向的结点是否为空 //如果为空,表示连接失败 if(pNode->output1 ==0) { //连接失败,返回-1 return -1; } //否则不为空 //输出到它指向结点的输入端 //此时要判断输入到哪个输入端:input1 OR input2; pNodeNext=pNode->output1 ; if(pNodeNext->input1 ==pNode) { //如果是输入到input1 pNodeNext->input1value =pNode->output1value ; //输入的值++,如果到了2,就可以计算进位了 pNodeNext->inputs ++; } else { //如果是输入到input2 pNodeNext->input2value =pNode->output1value ; pNodeNext->inputs ++; } //指针跳向下一个结点 pNode=pNodeNext; //判断此时是否是输出结点,如果是返回输出结点的值input1value; if(pNode->Subtype ==Output) { return pNode->input1value ; } //判断是否可以进位,对于非门,只要有一个输入值即可inputs==1 //对于其他门,要两个输入值inputs==2 if(pNode->Subtype==NOTGate) { //非门 if(pNode->inputs==1) { //可以进位 pNode->output1value =gatefunction(pNode); } else { //不能进位 break;//跳出for(;;) } } else { //其他门 if(pNode->inputs ==2) { pNode->output1value =gatefunction(pNode); } else { //不能进位 break;//跳出for(;;) } } //请空输入值个数inputs,以便下次计算 pNode->inputs =0; }//for(;;) }//判断当前结点是否是输入结点 }//while(pos!=0) //遍历完后若没有返回,说明连接失败 return -1; } 其算法主要思想是:遍历每一个输入结点,将输入结点的值送入到它所连接的元件的输入端,若此时该元件可以进位,则调用该元件进位函数gatefunction()计算该元件的输出端的值,再将该输出端的值送入它的下一个连接元件,再判断下一个元件能否进位,如此循环,直到输出结点。若此时不可以进位,则遍历下一个输入结点。 可以用下图来说明: 图21 假设此时输入结点遍历的顺序是1->2->3 (顺序不唯一)。假设此时1,2,3号输入结点取值0,1,0。首先将1号输入结点的值送入它所连接的或门的第一个输入端,即input1value=0。此时或门进位标志inputs= =1。于是不能进位。遍历下一个输入结点2,将2号1号输入结点的值送入它所连接的与门的第一个输入端,同样此时与门进位标志也为inputs= =1,不能进位。最后遍历到输入结点3,将值送入到与门的输入端2。由于此时有两个输入了,即与门进位标志inputs= =2,调用与门函数计算与门输出端output1value.然后将此值送入或门,同样或门进位标志inputs= =2,调用或门函数计算或门输出端值,最后送入输出结点,结束。 计算真值表:beginCalculate() 代码如下: void CMyView::beginCalculate() { //计算输入结点的个数,输出真值表 n=numpoint.GetSize (); //计算要进行循环的次数 int i; x=1; for(i=1;i<=n;i++) { x=x*2; } //动态生成x个字符串保存真值表 //用一个字符串格式化数据 CString str; //用一个数组I[1]~I[n]记录每个输入结点值 UINT* I=new UINT[n+1]; //用数组J[1]~J[n]辅助计算 UINT* J=new UINT[n+1]; //初始化J[1]~J[n] J[1]=1; for(i=2;i<=n;i++) { J[i]=J[i-1]*2; } //进行x次循环,计算真值表 for(i=0;i<x;i++) { //增加数组元素:加入一个字符串 strs.Add (CString()); for(int k=1;k<=n;k++) { I[k]=i & J[k]; //向右移位 I[k]=I[k]>>(k-1); //将输入端1~n的值加入字符串 str.Format ("%d ",I[k]); // //连接起来 strs[i]=strs[i]+str; } //给输入结点1~n初始化值 POSITION pos=MyList.GetHeadPosition (); MyNode* pNode; while(pos!=0) { pNode=MyList.GetNext (pos); //如果结点是输入结点 if(pNode->Subtype ==Input) { //结点中的pNode->number 记录了结点序号 //给结点初始化值 pNode->output1value =I[pNode->number ]; } } //调用函数计算,将计算结果保存 int result=CalculateResult(); //生成字符串以便输出 str.Format ("%10d",result); strs[i]+=str; } } 代码分析: (1) 首先得到输入结点的个数:n=numpoint.GetSize (); 这里numpoint是记录输入结点前序号位置点的数组,而有多少个这样的点,就有多少个输入结点。 (2)然后计算真值表的行数,因为有n个输入结点,真值表就有2^n行。 x=1; for(i=1;i<=n;i++) { x=x*2; } 这里我们用x来保存真值表的行数。 (3)产生所有的输入结点值的组合。 如果有3个输入结点,它的所有组合如下表 输入3 输入2 输入1 i 0 0 0 0 0 0 1 1 0 1 0 2 0 1 1 3 1 0 0 4 1 0 1 5 1 1 0 6 1 1 1 7 我们可以从表的第四列看出可以用数字i从0到x-1来分解出这些组合。 例如:当i为2时,它在内存中最后3位为:010。 此时 输入3=010 & 100=000,再右移2位即可得到0。 于是我们可以得到:输入1 & 001 再右移0位 输入2 & 010 再右移1位 输入1 & 100 再右移2位 我们用一个数组J[1]~J[n]来记录相与的数字。为1,2,4,......,2^(n-1) 初始化J[1]~J[n] J[1]=1; for(i=2;i<=n;i++) { J[i]=J[i-1]*2; } 用I[1]~I[n]记录输入1~输入n 产生一行输入值: strs.Add (CString()); for(int k=1;k<=n;k++) { I[k]=i & J[k]; //向右移位 I[k]=I[k]>>(k-1); //将输入端1~n的值加入字符串 str.Format ("%d ",I[k]); // //连接起来 strs[i]=strs[i]+str; } 给输入结点1~n初始化值 POSITION pos=MyList.GetHeadPosition (); MyNode* pNode; while(pos!=0) { pNode=MyList.GetNext (pos); //如果结点是输入结点 if(pNode->Subtype ==Input) { //结点中的pNode->number 记录了结点序号 //给结点初始化值 pNode->output1value =I[pNode->number ]; } } 调用函数计算,将计算结果保存 int result=CalculateResult(); 最后生成字符串以便输出 str.Format ("%10d",result); strs[i]+=str; 完成以上操作后,生成一个对话框,然后将字符串数组strs[]加入到列表框内。最终输出整个真值表。 参考文献: 1 Electronics Workbench 5.0 1992-1996 Interactive Image Technologies Ltd 2 MSDN Libary July 2000 Microsoft
这个问题有点模糊,具体生成类的方式和获取成员变量的指针的方式都没有明确说明。下面是一种可能的实现方式,仅供参考。 假设我们要生成一个名为MyClass的类,其中有两个成员变量x和y,可以使用如下代码生成这个类: ``` #include <stdio.h> #include <stddef.h> #define CLASS(MyClass) \ typedef struct { \ int x; \ int y; \ } MyClass; CLASS(MyClass) ``` 这里使用了C语言的宏定义来生成类。在定义类时,使用了typedef来定义结构体类型,将其命名为MyClass。这个结构体中包含了两个整型成员变量x和y。 接下来,我们需要生成一个函数来获取成员变量的指针。由于不知道成员变量的名称,我们可以使用offsetof宏来获取成员变量偏移量。然后,我们可以将这个偏移量加上对象的地址,就能得到成员变量的指针。下面是一个示例函数: ``` void* get_member_ptr(void* obj, size_t offset) { return (char*)obj + offset; } ``` 这个函数接受两个参数,第一个参数是对象的地址,第二个参数是成员变量偏移量。它将偏移量加上对象的地址,并返回结果指针。 使用这个函数来获取MyClass对象的成员变量指针的示例代码如下: ``` MyClass obj = {1, 2}; void* x_ptr = get_member_ptr(&obj, offsetof(MyClass, x)); void* y_ptr = get_member_ptr(&obj, offsetof(MyClass, y)); printf("x_ptr=%p, y_ptr=%p\n", x_ptr, y_ptr); ``` 这里创建了一个MyClass对象obj,并使用get_member_ptr函数获取了它的x和y成员变量的指针。注意,offsetof宏用于获取成员变量偏移量,需要指定结构体类型和成员变量名称。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mrbone11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值