GoogleCode之SVN空间使用详解

singmelody Post in 资料库
0

转自:http://blog.163.com/lgh_2002/blog/static/44017526201010301542250/

读者要求:有一定的开发经验,熟悉版本控制的基本概念,熟悉 SVN 的简单使用。

下面是申请并开通 Google Code 的 SVN 空间的一个流程:

1. Google 账号
首先,要求你有一个 Google 的账号,如果没有的话可以申请一个 Gmail 的账号,有了这个账号, Google 的所有非收费的服务都可以获得,更何况一个区区的 SVN 空间。
这里是链接,手懒的话可以直接点击: http://www.gmail.com 。

2. 登录邮箱并开始申请
先登录邮箱,在页面左上角的服 务列表中选择【更多 】, 找到【 Code 】,点击进入即可。
进入 Google Code 首页,选择左下角的【开源计划 】,在接下来的页面中有一个【如何入门? 】,点击【创建新的开源项目 】, 在接下来的页面中选择【 Create a new project 】。

3. 创建项目
接下来会出现一个页面用来创建 一个新的项目,这个就要慎重填写了。

Project name 用来填写项目名,要求必须是英 文小写开头,后边可以跟上小写字母,数字和中划线,但不允许有空格。这个项目名字会组成你的项目的 URL 地址,而且一旦确定就无法修改,请慎重填写。
Project summary 用来填写项目的简要描 述,别人在浏览开源项目列表的时候,这个简要描述也会显示出来,如果你的项目要从外面招兵买马,就可以利用这个来做广告,说不定哪个能人会看上你的创意, 愿意帮你一马。
Project description 就是对你的项目 的详细描述,请如实描述你的项目的具体属性。
Version control system 用来选择 VCS (注意不是 CVS ,这里指的是版本控制系统),有两个选项可供选择,除了 SVN 之外还有 Mercurial ,这是是一个轻量级的 VCS ,采用 Python 实现,感兴趣的读者可以试一下, 作者本人没有用过,这里我就选 Subversion 了。
Source code license 要求你选择一个开源协议,对于初学者来说这些开源组织的协议似乎都差不多,但仔细研读的话还是有差别 的。选一个就可以了,这里我选择 Apache2.0 的,因为我对它最熟悉。这里要注意一下,源码和文档可以采用不同的协议管理,需要分别管理的可以自 行考虑,这里我也就不讲究那么多了。
Project labels 相当于你的项目的关键字。我推荐的做法就是把你的 project description 的内容精简一下,取出几个关键字填入。
最后点击【 Create project 】,项目创建成功。

4. 项目的 SVN 基本管理
项目创建成功之后,就可以使用 客户端进行项目的管理。目前人气最足的就是 TortoiseSVN 了,用小乌龟 来管理 SVN 空间是大部分的选择。如果你使用 Eclipse 的话也可以使用 Subversion 的插件,在安全插件时推荐使用 link 的方式,严重建议不要使用在线更新,因为根据我和从网上获知的大多数同仁的亲身体验,这种更新的速度 慢得让人吐血(注意:这跟你自己的网速没多大关系,它就是这么慢!!!)。
在这里我使用小乌龟来管理,这 个工具可以从下面的网站中获取:

http://tortoisesvn.tigris.org/

Eclipse 的插件可以从下面的网 站获取:

http://subclipse.tigris.org/

a. 客户端管理——使用 TortoiseSVN 管理 Google Code 的 SVN 空间
首先下载并安装 TortoiseSVN ,安装完成之后它会要求你重启机器,其实可以不重启,直接进行操作。找一个空的文件夹,建议不要使用 带有中文的目录,然后点击右键选择【 SVN Checkout 】。在接下来的对话框中,填入你的 SVN 仓库的 URL ,格式默认为: https://bjtu-delivery.googlecode.com/svn/trunk/
然后就可以 checkout 下你的仓库了,不过此时的仓库的是空的。这时你选的文件夹上会有一个带有对勾儿的小绿圆,表明该文 件已经被 TortoiseSVN 进行管理了。这时的这个空文件夹就被称为 workspace 或这 client view ,还有另外一种称呼,叫做 sandbox ,不过各人觉得这种说法不是很形象,没有前两种那么见名知意。
向仓库中提交文件
这时,新建一个文件,如 HelloWorld.java ,然后点击右键选择【 add 】,发现文 件上有一个加号,再次点击该文件,右键点击选择【 SVN commit 】,填写好提交日志之后, 点击【 OK 】,这时弹出一个窗口要求你输入用户名和密码。用户名就是你的 Google Account ,密码呢就不要填写你的 Google Account 密码了。 Google 的 SVN 空间中的密码是随机生成的,这时你要登录 Google Code 中你的项目的页面,选择【 settings 】。 如果你已经登录了你的 Google Account 的话这里会显示一个生成的密码,输入即 可。这个密码可以根据需要再次生成,在你需要对你的 SVN 空间进行管理时,这个密码是必须的。

b. 服务器端管理——使用 Google Code 的管理页面对 SVN 空间的属性进 行管理
登录你的 Google Account ,进入 Code 之后选择你的项目后,会在页面中管理你的 SVN 空间属性。在这里你可以修改处理项目名之外的几乎说有属性,如果你是刚开始使用 Google 的 SVN 空间,管理你的队员是首先需要做的。在【 Administer 】下的【 Project Members 】里可以配置你的队员了。不过这里填写的都是 Gmail 的账号,所以你的队员必须要有一个 Gmail 的账号,设置 owners 、 committers 、 contributors ,只要填入对应的 Gmail 密码就可以了,可以使用逗号或者换行。

二叉排序树的实现

singmelody Post in 数据结构
0

代码和思路参考自《数据结构》严老师版 P226-230

#include <stdio.h>
#include <malloc.h>

#define N 10		// 数据元素个数
#define Status int
#define true 1
#define false 0
typedef int KeyType;// 设关键字域为整型

typedef struct
{
	KeyType key;
	int others;
}ElemType;	// 数据元素类型

typedef ElemType TElemType;

typedef struct BiTNode
{
	// 二叉树的二叉链表存储表示 动态查找表(二叉排序树)
	TElemType data;
	struct BiTNode *lchild,*rchild;	// 左右孩子指针
}BiTNode,*BiTree;

Status InitDSTable(BiTree *DT)
{
	// 构造一个空的动态查找表DT
	*DT = NULL;
	return true;
}

void DestroyDSTable(BiTree *DT)
{
	// 销毁动态查找表
	if(*DT)// 非空树
	{
		if((*DT)->lchild)	// 有左孩子
			DestroyDSTable(&(*DT)->lchild);	// 销毁左孩子子树
		if((*DT)->rchild)	// 有右孩子
			DestroyDSTable(&(*DT)->rchild);	// 销毁右孩子子树
		free(*DT);	// 释放根结点
		*DT = NULL;	// 空指针赋0
	}
}

BiTree SearchBST(BiTree T,KeyType key)
{
	// 在根指针T所指二叉排序树中递归地查找某关键字等于key的数据元素
	// 若查找成功,则返回指向该数据元素结点的指针,否则返回空指针
	if((!T) || key == T->data.key)
		return T;
	else if (key < T->data.key)
		return SearchBST(T->lchild,key);
	else
		return SearchBST(T->rchild,key);
}

Status SearchBST1(BiTree T,KeyType key,BiTree f,BiTree *p)
{
	// 在根指针T所指二叉排序树中递归地查找某关键字等于key的数据元素
	// 若查找成功,则返回指针p指向该数据元素指针结点,并返回TRUE,否则指针p
	// 指向查找路径上访问的最后一个结点并返回FALSE,指针f指向T的双亲,其初始调用值为NULL
	if (!T)
	{
		*p = f;
		return false;
	}
	else if (key == T->data.key)
	{
		*p = T;
		return true;
	}
	else if (key < T->data.key)
	{
		return SearchBST1(T->lchild,key,T,p);
	}
	else
	{
		return SearchBST1(T->rchild,key,T,p);
	}
}

int InsertBST(BiTree *T,ElemType e)
{
	BiTree p,s;

	if (!SearchBST1(*T,e.key,NULL,&p))	// 查找不成功
	{
		s = (BiTree)malloc(sizeof(BiTNode));
		s->data = e;
		s->lchild = s->rchild = NULL;
		if (!p)
		{
			*T = s;	// 被插结点*s为新的根结点
		}
		else if (e.key < p->data.key)
		{
			p->lchild = s;	// 被插结点*s为左孩子,值小的在左边
		}
		else
		{
			p->rchild = s;	// 被插结点*s为右孩子,值小的在右边
		}
		return true;
	}
	else
		return false;		// 书中已经有关键字相同的结点,不再插入
}

Status Delete(BiTree *p)
{
	// 从二叉排序树中删除结点p,并重接的左或右子树
	BiTree q,s;
	if (!(*p)->rchild)
	{
		// 右子树空则只需重接他的左子树
		q = (*p);
		*p = (*p)->lchild;
		free(q);
	}
	else if (!(*p)->lchild)
	{
		// 只需重接他的右子树
		q = (*p);
		*p = (*p)->rchild;
		free(q);
	}
	else
	{
		// 左右子树均不空	P230 方式d
		q = *p; s = (*p)->lchild;
		while(s->rchild){
			q = s;
			s = s->rchild;
		}
		(*p)->data = s->data;

		// s指向被删结点的"前驱"(将被删结点前驱的值取代被删结点的值)
		if (q!=*p)
		{
			// 此种情况是指类似只有左右两个子结点的情况,类似情况下不需改变p->data = s->data已经完成所需改变
			q->rchild = s->lchild;
		}
		else
		{
			q->lchild = s->lchild;
		}
		free(s);
	}
	return true;
}

Status DeleteBST(BiTree *T,KeyType key)
{
	// 若二叉排序树T存在关键字等于key的数据元素的时候,则删除该数据元素结点
	// 并返回true,否则返回false
	if (!*T)
	{
		return false;
	}
	else
	{
		if (key == (*T)->data.key)
		{
			return Delete(T);
		}
		else if (key < (*T)->data.key)
		{
			return DeleteBST(&(*T)->lchild,key);
		}
		else
		{
			DeleteBST(&(*T)->rchild,key);
		}
		return 1;
	}
}

void TraverseDSTable(BiTree DT,void(*Visit)(ElemType))
{
	if (DT)
	{
		TraverseDSTable(DT->lchild,Visit);	// 先中序遍历左子树
		Visit(DT->data);					// 再访问根结点
		TraverseDSTable(DT->rchild,Visit);	// 最后中序遍历右子树
	}
}

void print(ElemType c)
{
	printf("(%d,%d)",c.key,c.others);
}

int main()
{
	BiTree dt,p;
	int i;
	KeyType j;

	ElemType r[N]={
		{45,1},{12,2},{53,3},{3,4},{37,5},{24,6},
		{100,7},{61,8},{90,9},{78,10}
	}; // 以教科书P227图9.7(a)为例 

	InitDSTable(&dt); // 构造空表

	for(i=0;i<N;i++)
		InsertBST(&dt,r[i]); // 依次插入数据元素 

	TraverseDSTable(dt,print);
	printf("\n请输入待查找的值:");
	scanf("%d",&j);
	p = SearchBST(dt,j);

	if (p)
	{
		printf("表中存在此值:");
		DeleteBST(&dt,j);
		printf("删除此值后:\n");
		TraverseDSTable(dt,print);
		printf("\n");
	}

	return 0;

}

求幂集链式实现

singmelody Post in 数据结构
0

思路参考自《数据结构》严老师版 P150
这版代码使用链表实现的 插入和删除的时候有优势 但是计算长度的时候就比较麻烦了。

#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>

#define TRUE  1
#define FALSE 0
#define OK    1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW   -2

#define ElemType int
#define Status   int

#define LIST_INIT_SIZE 100      // 线性表存储空间的初始分配量
#define LISTINCREMENT  10       // 线性表存储空间的分配增量

typedef struct LNode
{
	ElemType data;              // 存储的数据
	struct LNode *next;         // 后继的结点指针
}LNode,*LinkList;

void InitList(LinkList *L)
{
	// InitList_L即CreateList_L
	// 顺序(头插法)n个元素的值,建立带表头结点的单链线性表L
	int i;
	LNode *p;
	(*L) = (LinkList)malloc(sizeof(LNode));
	(*L)->next = NULL;      // 先建立一个带头结点的单链表

} // InitList

int ListLength(LinkList L)
{
	// 计算链表长度
	int i=0;
	LinkList p = L->next;
	while(p)
	{
		i++;            // 存在加1
		p = p->next; // 指向下一个结点
	}
	return i;
} // ListLength

Status GetElem(LinkList L,int i,ElemType *e)
{
	LNode *p;
	int j;
	// L为带头结点的单链表的头指针
	// 当第i个元素存在时,其值赋给e并返回OK,否则返回false
	p = L->next; j = 1;          // 初始化,p指向第一个结点,j为计数器

	while(p && j < i)
	{
		p = p->next; ++j;
	}

	if(!p || j>i)
		return ERROR;           // 第i个元素不存在

	*e = p->data;             // 取第i个元素
	return OK;
} // GetElem

Status ListInsert(LinkList *L,int i,ElemType e)
{
	LinkList p; int j;
	LinkList s = (LinkList)malloc(sizeof(LNode));   // 生成新结点
	// 在带头结点的单链线性表L中第i个位置之前插入元素e
	p = (*L); j = 0;
	while(p && j < i-1)
	{
		p = p->next; ++j;        // 寻找第i-1个结点
	}

	if(!p||j>i-1)
		return ERROR;           // i小于1或者大于表长+1

	s->data = e; s->next = p->next;                // 插入L中
	p->next = s;

	return OK;

} // ListInsert

Status ListDelete(LinkList *L,int i,ElemType *e)
{
	LinkList p,q; int j;
	// 在带头结点的单链线性表L中,删除第i个元素,并由e返回其值
	p = (*L); j = 0;
	while(p->next && j<i-1)
	{
		// 寻找第i个结点,并令p指向其前趋
		p = p->next; ++j;
	}

	if((!p->next) || j > i-1)
		return ERROR;           // 删除位置不合理

	q = p->next; p->next = q->next;    // 删除并释放结点
	*e = q->data; free(q);

	return OK;

} // ListDelete

void Output(LinkList L){
	int e,i = 1;

	int length = ListLength(L);

	for( i;i<=length;++i)
	{
		GetElem(L,i,&e);
		printf("%d ",e);
	}

	// 输出空集
	if (!length)
	{
		printf("Empty");
	}

	printf("\n");

	return OK;
}

// 算法6.15 P150
// 线性表A表示集合A,线性表B表示幂集ρ(A)的一个元素。
// 局部量k为进入函数时表B的当前长度。
// 第一次调用本函数时,B为空表,i=1。
void GetPowerSet(int i, LinkList A, LinkList *B) {
   ElemType x;
   int k;
   if (i > ListLength(A))
	   Output(*B); // 输出当前B值,即ρ(A)的一个元素
   else {
      GetElem(A, i, &x);
	  k = ListLength(*B);
      ListInsert(B, k+1, x);
	  GetPowerSet(i+1, A, B);
      ListDelete(B, k+1, &x);
	  GetPowerSet(i+1, A, B);
   }
}

int main(){
	int n,i;
	ElemType e;
	LinkList a,b;

	printf("请输入集合A的元素个数:\n");
	scanf("%d",&n);
	InitList(&a);

	printf("请输入集合中的元素:\n");
	for(i=1;i<=n;i++){
		scanf("%d",&e);
		ListInsert(&a,i,e);
	}
	InitList(&b);
	printf("进行冥集合运算!\n");
	GetPowerSet(1, a, &b);
	return 0;
}
/*
请输入集合A的元素个数:
3
请输入集合A的元素:(空格区分)
1 2 3
*/

Haffman树(最优二叉树)的线性实现

singmelody Post in 数据结构
0

思路参考自《数据结构》严老师版 P144-149
实现了两种建树的算法
1.从叶子到根逆向求每个字符的赫夫曼编码
2.从根到叶子正向求每个字符的赫夫曼编码

#include <stdio.h>
#include <memory.h>
#include <limits.h>

typedef struct
{
	unsigned int weight;
	unsigned int parent,lchild,rchild;
}HTNode,*HuffmanTree;	// 动态分配数组存储赫夫曼数

typedef char** HuffmanCode;	// 动态分配数组存储赫夫曼编码树

// 函数void select()调用
int min1(HuffmanTree t,int i)
{
	int j,flag;
	unsigned int k=UINT_MAX; // 取k为不小于可能的值
	for(j = 1; j <= i; j++)
	{
		if(t[j].weight < k && t[j].parent == 0)
			k = t[j].weight,flag = j;
	}
	t[flag].parent=1;
	return flag;
}

// s1为最小的两个值中序号小的那个
void Select(HuffmanTree t,int i,int *s1,int *s2)
{
	int j;
	*s1=min1(t,i);
	*s2=min1(t,i);
	if(*s1 > *s2)
	{
		j=*s1;
		*s1=*s2;
		*s2=j;
	}
}

void HuffmanCoding_FromLeaf(HuffmanTree *HT,HuffmanCode *HC,int *w,int n)
{
	// w存放n个字符的权值(均>0),构造赫夫曼树HT,并求出n个字符的赫夫曼编码HC
	int m,s1,s2,i,start;
	unsigned int c,f;
	HuffmanTree p;
	char * cd;

	if (n <= 1)
		return;

	m = 2*n-1;
	*HT = (HuffmanTree)malloc((m+1)*sizeof(HTNode));	// 0号单元未用

	for (p = *HT+1,i=1;i<=n;++i,++p,++w)
	{
		(*p).weight=*w;
		(*p).parent=0;
		(*p).lchild=0;
		(*p).rchild=0;
	}

	for(;i<=m;++i,++p)	//初始化双亲位置
		(*p).parent=0;

	for (i = n+1;i <=m;++i)
	{	// 建赫夫曼树
		// 在HT[1...i-1]
		Select(*HT,i-1,&s1,&s2);
		(*HT)[s1].parent = i; (*HT)[s2].parent = i;
		(*HT)[i].lchild = s1; (*HT)[i].rchild = s2;
		(*HT)[i].weight = (*HT)[s1].weight + (*HT)[s2].weight;
	}

	// 从叶子到根逆向求每个字符的赫夫曼编码
	*HC = (HuffmanCode)malloc((n+1)*sizeof(char *));// 分配n个字符编码的头指针向量
	cd = (char *)malloc(n*sizeof(char));			// 分配求编码的工作空间
	cd[n-1] = '\0';
	for (i=1;i<=n;++i)
	{
		start = n-1;
		for (c=i,f=(*HT)[i].parent;f!=0;c=f,f=(*HT)[f].parent)	// 从叶子到根逆向求编码
		{
			if((*HT)[f].lchild == c) cd[--start] = '0';
			else cd[--start] = '1';
		}

		(*HC)[i] = (char *)malloc((n-start)*sizeof(char));		// 为第i个字符分配空间
		strcpy((*HC)[i],&cd[start]);
	}
	free(cd);	// 释放工作空间
}

void HuffmanCoding_FromRoot(HuffmanTree *HT,HuffmanCode *HC,int *w,int n)
{
	// w存放n个字符的权值(均>0),构造赫夫曼树HT,并求出n个字符的赫夫曼编码HC
	int m,s1,s2,i,start,cdlen,index;
	unsigned int c,f;
	HuffmanTree p;
	char * cd;

	if (n <= 1)
		return;

	m = 2*n-1;
	*HT = (HuffmanTree)malloc((m+1)*sizeof(HTNode));	// 0号单元未用

	for (p = *HT+1,i=1;i<=n;++i,++p,++w)
	{
		(*p).weight=*w;
		(*p).parent=0;
		(*p).lchild=0;
		(*p).rchild=0;
	}

	for(;i<=m;++i,++p)	//初始化双亲位置
		(*p).parent=0;

	for (i = n+1;i <=m;++i)
	{	// 建赫夫曼树
		// 在HT[1...i-1]
		Select(*HT,i-1,&s1,&s2);
		(*HT)[s1].parent = i; (*HT)[s2].parent = i;
		(*HT)[i].lchild = s1; (*HT)[i].rchild = s2;
		(*HT)[i].weight = (*HT)[s1].weight + (*HT)[s2].weight;
	}

	// 从根到叶子正向求每个字符的赫夫曼编码
	*HC = (HuffmanCode)malloc((n+1)*sizeof(char *));
	// 分配n个字符编码的头指针向量([0]不用)
	cd=(char*)malloc(n*sizeof(char)); // 分配求编码的工作空间
	index = m; cdlen = 0;
	for (i=1;i<=m;++i)	(*HT)[i].weight = 0;	// 遍历赫夫曼树时用作结点状态标识

	while (index)
	{
		if ((*HT)[index].weight == 0)	// 向左
		{
			(*HT)[index].weight = 1;
			if ((*HT)[index].lchild != 0)
			{
				index = (*HT)[index].lchild;
				cd[cdlen++] = '0';
			}
			else if ((*HT)[index].rchild == 0)
			{
				// 等级叶子结点的字符的编码
				(*HC)[index] = (char *)malloc((cdlen+1)*sizeof(char));
				cd[cdlen] = '\0';
				strcpy((*HC)[index],cd);	// 复制编码(串)
			}
		}
		else if ((*HT)[index].weight == 1)	// 向右
		{
			(*HT)[index].weight = 2;
			if ((*HT)[index].rchild != 0)
			{
				index = (*HT)[index].rchild;
				cd[cdlen++]="1";
			}
		}
		else
		{
			// HT[p].weight == 2 退回
			(*HT)[index].weight = 0;
			index = (*HT)[index].parent; --cdlen;// 退回到父结点,编码长度减1
		}
	}
	free(cd);
}

int main()
{
	HuffmanTree HT;
	HuffmanCode HC;
	int *w,n,i;
	freopen("1.txt","r",stdin);

	printf("请输入权值的个数(>1):");
	scanf("%d",&n);
	w=(int*)malloc(n*sizeof(int));
	printf("请依次输入%d个权值(整型):\n",n);
	for(i=0;i<=n-1;i++)
		scanf("%d",w+i);
	HuffmanCoding_FromRoot(&HT,&HC,w,n);
	for(i=1;i<=n;i++)
		puts(HC[i]);

	return 0;
}
/*
测试数据
8
5 29 7 8 14 23 3 11

*/

欧拉角处理类

singmelody Post in 图形化
0

// EulerAngles.h

#pragma once

class Quaterion;
class Matrix4x3;
class RotationMatrix;

// heading - pitch - bank 欧拉角系统 y-x-z系统
class EulerAngles
{
public:
	float heading;
	float pitch;
	float bank;

	EulerAngles(void){}
	EulerAngles(float h,float p,float b):heading(h),pitch(p),bank(b){}

	// 置0
	void identity() { pitch = bank = heading = 0.0f; }

	// 变换为限制集欧拉角
	void canonize();

	// 从四元数转换到欧拉角
	// 输出的四元数假设为 物体->惯性 或者 惯性->物体 四元数,如其名所示
	void fromObjectToInertialQuation(const Quaterion &q);
	void fromInertialToObjectQuation(const Quaterion &q);

	// 从矩阵转换到欧拉角
	// 输入矩阵为 物体->世界 或者 世界->物体 转换矩阵
	// 平移部分省略,并且假设矩阵是正交的
	void fromObjectToWorldMatrix(const Matrix4x3 &m);
	void fromWorldMatrixToObjectMatrix(const Matrix4x3 &m);

	// 从旋转矩阵转换到欧拉角
	void fromRotationMatrix(const RotationMatrix &m);
};

// 全局单位欧拉角
extern const EulerAngles kEulerAnglesIdentity;

// EulerAngles.cpp

#include "EulerAngles.h"
#include "Vector3.h"
#include "MathUtil.h"
#include "Matrix4x3.h"
#include "Quaterion.h"
#include "RotationMatrix.h"

const EulerAngles kEulerAnglesIdentity(0.0f,0.0f,0.0f);

void EulerAngles::canonize()
{
	// 首先将pitch变换到-pi和pi之间
	pitch = wrapPi(pitch);

	// 现在将pitch变换到-pi/2到pi/2之间
	if (pitch < -HalfPI)
	{
		pitch = -PI - pitch;
		heading += PI;
		bank += PI;
	}
	else if (pitch > HalfPI)
	{
		pitch = PI - pitch;
		heading += PI;
		bank += PI;
	}

	// 检查万向锁
	if (fabs(pitch) > HalfPI - 1e-4)
	{
		// 在万向锁中,将所有绕垂直轴的旋转赋给heading
		heading += bank;
		bank = 0.0f;
	}
	else
	{
		// 非万向锁,将bank转换到限制集中
		bank = wrapPi(bank);
	}

	// 将heading转换等到限制集中
	heading = wrapPi(heading);

}

// 物体->惯性 四元数到欧拉角
void EulerAngles::fromObjectToInertialQuation(const Quaterion &q)
{
	// 计算sin(pitch)
	float sp = -2.0f*(q.y*q.z - q.w*q.x);

	// 检查万向锁,允许存在一定误差
	if (fabs(sp) >1 - 1e-4 )
	{
		// 朝正上或正下方看 趋近于HalfPI
		pitch = HalfPI * sp;
		// 计算heading,bank置零
		heading = atan2(-q.x*q.z+q.w*q.y,0.5f-q.y*q.y-q.z*q.z);
		bank = 0.0f;
	}
	else
	{
		// 计算角度,这里我们不必使用"安全的"asin函数,因为之前在检查万向锁问题时已
		// 检查过范围错误了
		pitch = asin(sp);
		heading = atan2(q.x*q.z-q.w*q.y,0.5f-q.x*q.x-q.z*q.z);
		bank = atan2(q.x*q.y+q.w*q.z,0.5f-q.x*q.x - q.z*q.z);
	}
}

// 惯性->物体 四元数到欧拉角
void EulerAngles::fromInertialToObjectQuation(const Quaterion &q)
{
	// 计算sin(pitch)
	float sp  = -2.0f *(q.y*q.z + q.w*q.x);

	// 检查万向锁,允许一定误差
	if (fabs(sp) >1 - 1e-4 )
	{
		// 朝正上或正下方看 趋近于HalfPI
		pitch = HalfPI * sp;
		// 计算heading,bank置零
		heading = atan2(-q.x*q.z-q.w*q.y,0.5f-q.y*q.y-q.z*q.z);
		bank = 0.0f;
	}
	else
	{
		// 计算角度
		pitch = asin(sp);
		heading = atan2(q.x*q.z-q.w*q.y,0.5f-q.x*q.x-q.y*q.y);
		bank	= atan2(q.x*q.y-q.w*q.z,0.5f-q.x*q.x-q.z*q.z);
	}
}

// 物体->世界坐标系 变换矩阵到欧拉角
// 假设矩阵正交 忽略平移部分
void EulerAngles::fromObjectToWorldMatrix(const Matrix4x3 &m)
{
	// 根据m23计算sin(pitch)
	float sp = -m.m23;

	// 检查万向锁的情况,允许一些误差
	if (fabs(sp) > 1e-4)
	{
		// 向正上或正下看
		heading = atan2(-m.m31,m.m11);
		pitch = HalfPI*sp;
		bank = 0.0f;
	}
	else
	{
		heading = atan2(m.m13,m.m33);
		pitch = asin(sp);
		bank = atan2(m.m21,m.m22);
	}
}

// 世界->物体 变换矩阵到欧拉角
// 假设矩阵正交 忽略平移部分
void EulerAngles::fromWorldMatrixToObjectMatrix(const Matrix4x3 &m)
{
	// 根据m23计算sin(pitch)
	float sp = -m.m23;

	// 检查万向锁的情况,允许一些误差
	if (fabs(sp) > 1e-4)
	{
		// 向正上或正下看
		heading = atan2(-m.m31,m.m11);
		pitch = HalfPI*sp;
		bank = 0.0f;
	}
	else
	{
		heading = atan2(m.m13,m.m33);
		pitch = asin(sp);
		bank = atan2(m.m21,m.m22);
	}
}

// 根据旋转矩阵构造欧拉角
void EulerAngles::fromRotationMatrix(const RotationMatrix &m)
{
	// 根据m23计算sin(pitch)
	float sp = -m.m23;

	// 检查万向锁的情况,允许一些误差
	if (fabs(sp) > 1e-4)
	{
		// 向正上或正下看
		heading = atan2(-m.m31,m.m11);
		pitch = HalfPI*sp;
		bank = 0.0f;
	}
	else
	{
		heading = atan2(m.m13,m.m33);
		pitch = asin(sp);
		bank = atan2(m.m21,m.m22);
	}
}

欧拉角转换为限制集欧拉角

singmelody Post in 图形化
0

其中主要的难度主要在于万向锁的问题。因为浮点数精度的原因,在判断的时候要做一些处理。
关于万向锁有两篇不错的博文供大家参考:
http://www.cnblogs.com/soroman/archive/2006/10/12/526163.html
http://www.cnblogs.com/soroman/archive/2008/03/24/1118996.html

这里使用的是heading(y) – pitch(x) – bank(z) 的坐标结构.heading和bank的限制范围在[-PI,PI],pitch的限制范围在[-PI/2,PI/2]
相关的欧拉角和限制范围的相关知识,参见《3D数学基础:图形与游戏开发》P135-141

void canonize()
{
	// 首先将pitch变换到-pi和pi之间
	pitch = wrapPi(pitch);

	// 现在将pitch变换到-pi/2到pi/2之间
	if (pitch < -HalfPI)
	{
		pitch = -PI - pitch;
		heading += PI;
		bank += PI;
	}
	else if (pitch > HalfPI)
	{
		pitch = PI - pitch;
		heading += PI;
		bank += PI;
	}

	// 检查万向锁
	if (fabs(pitch) > HalfPI - 1e-4)
	{
		// 在万向锁中,将所有绕垂直轴的旋转赋给heading
		heading += bank;
		bank = 0.0f;
	}
	else
	{
		// 非万向锁,将bank转换到限制集中
		bank = wrapPi(bank);
	}

	// 将heading转换等到限制集中
	heading = wrapPi(heading);

}

线索二叉树的二叉链表实现

singmelody Post in 数据结构
4

代码参考自: 《数据结构》严蔚敏版 P132-135

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define TElemType char
#define Status int
#define OVERFLOW -2
#define true	1
#define OK		1
#define false	0
#define ERROR	-1

typedef enum PointerTag { Link,Thread};	// Link == 0:指针,Thread == 1:线索

// --------------二叉树的二叉链表存储表示------------------
typedef struct BiThrNode
{
	TElemType		 data;
	struct BiThrNode *lchild,*rchild;	// 左右孩子指针
	enum PointerTag	 LTag,RTag;			// 左右标志
}BiThrNode,*BiThrTree;

TElemType Nil = '-';
BiThrTree pre; // 全局变量,始终指向刚刚访问过的结点 

//--------------各种visit()--------------------------------
Status PrintOrderTraverse(TElemType e)
{
	printf("%c ",e);
	return OK;
} // PrintOrderTraverse

//---------------二叉线索树的基本操作函数原型说明--------------
Status CreateBiThrTree(BiThrTree *T)
{
	// 按先序次序输入二叉树中结点的值(一个字符),空格字符表示空树
	// 构造二叉链表表示的二叉树
	TElemType ch;
	scanf("%c",&ch);
	if(ch == Nil)
		*T = NULL;
	else
	{
		if(!(*T = (BiThrNode *)malloc(sizeof(BiThrNode))))	exit(OVERFLOW);
		(*T)->data = ch;	// 生成根结点

		CreateBiThrTree(&(*T)->lchild);	// 构造左子树
		if((*T)->lchild) // 有左孩子
			(*T)->LTag=Link;
		else
			(*T)->LTag=Thread;

		CreateBiThrTree(&(*T)->rchild);	// 构造右子树
		if((*T)->rchild) // 有右孩子
			(*T)->RTag=Link;
		else
			(*T)->RTag=Thread;
	}

	return OK;
} // CreateBiTree

Status InOrderTraverse_Thr(BiThrTree T,Status (*Visit)(TElemType e))
{
	// T 指向头结点,头结点的左链lchild指向根结点,可参见线索化算法
	// 中序遍历二叉线索树T的非递归算法,对每个数据元素调用函数Visit
	BiThrTree p;
	p = T->lchild;	// 带有头结点,左子树指向根结点

	while(p!=T)		// 空树或者遍历结束时 p == T
	{
		while(p->LTag == Link)
			p = p->lchild;
		if(!Visit(p->data)) return ERROR;	// 访问其左子树为空的结点
		while(p->RTag == Thread && p->rchild != T)
		{
			p = p->rchild;	Visit(p->data);	// 访问后继结点
		}
		p = p->rchild;
	}

	return OK;
} // InOrderTraverse_Thr

void InThreading(BiThrTree p)
{
	if(p)
	{
		InThreading(p->lchild);		// 左子树线索化
		if(!p->lchild)
		{
			// 前缀线索
			p->LTag = Thread;
			p->lchild = pre;
		}
		if(!pre->rchild)
		{
			// 后继线索
			pre->RTag = Thread;
			pre->rchild = p;
		}
		pre = p;					// 保持pre指向p的前驱
		InThreading(p->rchild);		// 右子树线索化
	}
} // InThreading

Status InOrderThreading(BiThrTree *Thrt,BiThrTree T)
{
	// 中序遍历二叉树T,并将其中序线索化,Thrt指向头结点
	if(!(*Thrt = (BiThrTree)malloc(sizeof(BiThrNode)))) exit(OVERFLOW);
	(*Thrt)->LTag = Link;	(*Thrt)->RTag = Thread;		// 建头结点
	(*Thrt)->rchild = *Thrt;							// 右指针回指
	if(!T)
		(*Thrt)->lchild = *Thrt;						// 若二叉树为空,则左指针回指
	else
	{
		(*Thrt)->lchild = T;	pre = (*Thrt);
		InThreading(T);									// 中序遍历进行中序线索化
		pre->rchild  = (*Thrt);pre->RTag = Thread;		// 最后一个结点线索化
		(*Thrt)->rchild  = pre;
	}
	return OK;
} // InOrderThreading

int main()
{
	BiThrTree bitree1,bitree2;
//	freopen("1.txt","r",stdin);
	CreateBiThrTree(&bitree2);
	// 二叉树线索化
	InOrderThreading(&bitree1,bitree2);
	// 中序线索遍历
	InOrderTraverse_Thr(bitree1,PrintOrderTraverse);
	printf("\n");

	return 0;
}

测试数据:

ABC--DE-G--F---

在windows下cp命令的实现

singmelody Post in C
0

最近在学习TCPL的时候,因为想要熟悉windows下的_CRTIMP(即C run time implement)。
所以使用vc实现了TCPL 8.3节的内容。
代码如下 和原文代码中的区别在于引入文件的不同和几个宏的不同。
注意一下 TCPL原文中使用的syscalls.h 在现在的一般linux下为unistd.h中。

#include <fcntl.h>
#include <stdio.h>
#include <io.h>
#include <sys/stat.h>

#define PERMS _S_IREAD | _S_IWRITE

int main( int argc,char *argv[] )
{
   int f1,f2,n;
   char buf[BUFSIZ];
   memset(buf,0,sizeof(buf));

   printf("%d %s %s\n",argc,argv[0],argv[1]);

   // 测试打开第一个参数的文件名称
   if((f1 = open(argv[1] , _O_RDONLY ,0)) == -1)
		printf("cp: can't open %s\n",argv[1]);
   else
      printf( "Open succeeded on input file\n" );

   // 测试创建第二个参数的文件名称
   if((f2 = creat(argv[2],PERMS)) == -1)
	   printf("cp: can't create %s ,mode %x\n",argv[2],PERMS);

   // 开始复制内容
   while((n = read(f1,buf,BUFSIZ)) > 0)
   {
	   if(write(f2,buf,n) != n)
			printf("cp: write error on file %s\n",argv[2]);
   }

   return 0;
}

在填充数组之前建议memset一下,清空一下数组中的垃圾,这样write才能更好的cp第一个参数文件中的内容,拷贝文件中没有数据垃圾。

精简版 printf()和scanf()函数的实现

singmelody Post in C
0

主要用到了stdarg.h中可变长参数表的相关宏
1.这里主要使用到的函数是三个:
va_start();初始化
va_arg();按照定长偏移
va_end();销毁
这三个函数的详细用法参加《The C Programming Langauge》的7.3小节。
这里函数的实现主要是使用到了按位偏移,因为系统的原因,实现因不同的机器而不同,但提供的接口是一致的。
2.一个结构体
struct va_list,其结构体声明如下

typedef struct {
        char *a0;       /* pointer to first homed integer argument */
        int offset;     /* byte offset of next parameter */
} va_list;

*注意:va_start中的第一参数最好使用确定参数的最后一个,否则va_arg中

#include <stdio.h>
#include <string.h>
#include <stdarg.h>

// 精简printf
void miniprintf(char *fmt, ...)
{
	va_list ap;
	char *p,*sval,*temp;
	int inumber;
	double dnumber;
	va_start(ap,fmt);

	for(p=fmt;*p;p++)
	{
		if(*p!='%')
		{
			putchar(*p);
			continue;
		}

		switch(*(++p))
		{
		case 'd':
			inumber = va_arg(ap,int);
			printf("%d",inumber);
			break;
		case 'f':
			dnumber = va_arg(ap,double);
			printf("%lf",dnumber);
			break;
		case 's':
			sval = va_arg(ap,char *);
			for(temp = sval;*temp;++temp)
				putchar(*temp);
		default:
			putchar(*p);
			break;
		}
	}
	putchar('\n');

	va_end(ap);
}

// 精简scanf
void miniscanf(char *str,...)
{
	va_list ap;
	int *inumber;
	double * dnumber;
	char *p;

	va_start(ap,str);
	for(p=str;*p;++p)
	{
		if(*p!='%')
		{
			continue;
		}

		switch(*++p)
		{
		case 'd':
			inumber = va_arg(ap,int *);
			scanf("%d",inumber);
			break;
		case 'f':
			dnumber = va_arg(ap,double *);
			scanf("%lf",dnumber);
			break;
		}
	}
	va_end(ap);
}

int main ()
{
	int inumber = 30;
	double dnumber;
	miniscanf("%d %f",&inumber,&dnumber);
	miniprintf("%d %f",inumber,dnumber);
	return 0;
}

这里为了反映的问题的核心,miniprintf()和miniscanf()中直接使用了printf()和scanf().这就好像你没有一件东西,却在使用这件东西的功能,这是不现实的。
其实在miniscanf()和miniprintf()中使用的scanf()和printf()可以结合使用getchar(),putchar(),itoa()等来模拟实现。因为最基本的getchar和putchar()操作系统是提供的。
如果不提供,可以使用汇编的模拟IO中断来实现,现在2008的编译器可以支持C/C++中嵌套汇编程序。如果你想完整实现一遍printf和scanf可以考虑使用此种方法。

这种可变长的参数表让我想到了lua中的可变长参数,等有机会一定要在lua的源码中一探究竟。到底是不是也是使用的这种方法。

Acmer测试数据对比工具

singmelody Post in C
0

想找出到底哪组数据出问题怎么办?
看大牛的一行一行的代码,从逻辑中找出自己到底错在哪里?
还是手工一行一行对比测试数据?不如自己写段程序,来自动对比,也可以以后方便对比。
测试数据可以使用AC之后代码来生成几M的就可以了。
通过具体哪组测试数据找到错误,是最为简单快捷的方法了。

#include <stdio.h>
#include <string.h>
int main (int argc,char *argv[])
{
	FILE * first;
	FILE * second;
	char strf[10000];
	char strs[10000];
	int i = 0;
	first =	fopen(argv[1],"r");
	second=	fopen(argv[2],"r"); 

	if (first==NULL || second == NULL)
		perror ("Error opening file");
	else {
		while(!feof(first) && !feof(second))
		{
			fgets(strf,10000,first); fgets(strs,10000,second);
			i++;
			if(strcmp(strf,strs)==0);
			else
				break;
		}
	}

	if(feof(first)&&feof(second))
	{
		printf("AC\n");
	}
	else if(feof(first)||feof(second))
	{
		printf("Presentation Error\n");
	}
	else
		printf("at %d:\n->%s\n->%s\n",i,strf,strs);
	fclose(first);
	fclose(second);

	return 0;
}