分类 C/C++ 下的文章

二叉排序树

二叉排序树(Binary Sort Tree)性质:

(1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值;

(2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值;

(3)左、右子树也分别为二叉排序树;

- 阅读剩余部分 -

循环队列

循环队列的C语言实现,把整个队列当作环来处理。一段连续的空间当作队列,front指向队列头,rear指向队列的尾。每次有数据进入队列,则rear向后一个位置移动,如果超过了最大空间,那么取余数;数据出队列,则front向后一个位置移动。队列满的时候rear = front,队列空的时候也是rear = front,于是可以这样处理,如果 rear+1 = front的话就表明是满队列,这样虽然浪费了一个空间,但是可以处理好队列满和空的问题。

- 阅读剩余部分 -

单向链表

到期末了,准备最后一个月把数据结构的一些小算法总结下,都在Fedora上用 codeblocks+ddd写,刚好可以熟悉下linux平台。另外C++还是没开始学,于是就准备在写数据结构实现的过程中直接用C++,发现写成了C+,真是悲剧。。。。

先是简单的单向链表,书上的很简洁,但是看起来不是很懂,于是我就自己写了,虽然复杂了很多,但是还是比较简洁,效率应该是差不多的,我多的不过是几个分支。下面是几个注意点

- 阅读剩余部分 -

指针指向字符串和数组储存字符串区别

    #include <stdio.h>  

    void main()
    {
        char *p = NULL,*str = "aafg" ,aa[10] = "abc";
        p = strcpy(str,aa);
        printf("%s/n",p );
    }

定义了一个指向字符串的指针,str是在栈上分配的变量,储存的地址对应的是在常量区中分配的空间。"aafg"是存储在常量区里面,
是不能改变的。char *str 其实存在一种 const char *类型的隐式转换,而strcpy要做的改变常量,所以程序编译的时候没错,运行的
时候就出错了。

- 阅读剩余部分 -

(转)C/C++ 通用 Makefile

C/C++ 通用 Makefile
Generic Makefile for C/C++ Program

==================================================
Keywords: Makefile, make, Generic, C/C++
Author:   whyglinux (whyglinux AT hotmail DOT com)
Date:     2006-03-04
==================================================

本文提供了一个用于对 C/C++ 程序进行编译和连接以产生可执行程序的通用 Makefile。

在使用 Makefile 之前,只需对它进行一些简单的设置即可;而且一经设置,即使以后对源程序文件有所增减一般也不再需要改动 Makefile。因此,即便是一个没有学习过 Makefile 书写规则的人,也可以为自己的 C/C++ 程序快速建立一个可工作的 Makefile。

这个 Makefile 可以在 GNU Make 和 GCC 编译器下正常工作。但是不能保证对于其它版本的 Make 和编译器也能正常工作。

- 阅读剩余部分 -

assert小记

#include <assert.h>
void assert( int expression );

assert的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,并且能够指出错误在哪行,然后马上终止程序,有点if判断的味道。
#include <stdio.h>
#include <assert.h>
int main()
{
    char *p;
    char a[] ={"china"};
    p = (char *)malloc(strlen(a)+1);
    p = a;
    printf("%s %d",p,strlen(p));
    assert(p == NULL);
    return 0;
}

- 阅读剩余部分 -

数组和指针

数组访问数组和指针访问数组的区别:
char a[] = "abcdef"; char *p = "abcdef";
数组的访问是通过先获取数组的首地址,然后通过相对的偏移量来获取地址,通过地址读出内容;
指针的访问形式:假设指针本身的地址是4624,先是存储变量是4624的内容,假设是5081,取得i的值算出5081+i的值然后读取该地址的内容,也就是说指针访问数组的多了一次额外的提取。指针保存的是数据的地址,间接来访问数据,首先获得是存储的内容,将其作为地址,然后通过地址提取数据。而数组a[i]只是简单的以a+i为地址取得数据。
数组指针和指针数组的区别:
int *p[10];因为[]的优先级比*高,所以解释为 指针数组,意思就是 先是定义一个数组,然后前面加int *表示里面的成员都是int型指针,这就是 指针数组。数组每个元素都存放这int型数据的地址,也就是一个int型指针。还是来看代码吧(*^__^*) 。

- 阅读剩余部分 -

二叉树遍历--递归实现

递归这东西真是抽象,我看着看着算法,就囫囵吞枣地的写了下,写得囧了。这次先用递归实现先序,中序,后序遍历算法。先大概说下原理:我输入一大串字符,中间#就是代表了空,基本的储存结构就是二叉链表。主要就是二叉树的创建和三种顺序的遍历。二叉树的创建通过从左孩子开始创建不断递归,知道读取了#,开始创建对应的右孩子,继续递归。访问的时候对于三种顺序不过就是对于操作的顺序改变而已。

- 阅读剩余部分 -

二叉树按层次遍历--队列实现

二叉树(binary tree)

二叉树的基本形态
二叉树也是递归定义的,其结点有左右子树之分,逻辑上二叉树有五种基本形态:
(1)空二叉树——(a)
(2)只有一个根结点的二叉树——(b)
(3)只有左子树——(c)
(4)只有右子树——(d)
(5)完全二叉树——(e)

度(Degree):节点孩子的数目。
叶子(Leaf): 度为0的节点称为叶子。
深度(Depth):树中最大的层次(从最上0开始数)

满二叉树:一棵深度为k且有2^k -1个节点的二叉树。
完全二叉树:若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层有叶子节点,并且叶子节点都是从左到右依次排布,这就是完全二叉树。

性质1:在二叉树的第i层上至多有2^(i-1)个节点(i >= 1)
性质2:深度为k的二叉树至多有2^k - 1个节点
性质3:任意一棵二叉树T,若其叶子节点数为n0,度为2的节点数为n2,则n0 = n2 + 1

对于完全二叉树:

性质4:具有n个节点的完全二叉树的深度是[log2n] + 1
性质5:有N个结点的完全二叉树各结点如果用顺序方式存储,则结点之间有如下关系:
若I为结点编号则 如果I<>1,则其父结点的编号为I/2;
如果2*I<=N,则其左儿子(即左子树的根结点)的编号为2*I;若2*I>N,则无左儿子;
如果2*I+1<=N,则其右儿子的结点编号为2*I+1;若2*I+1>N,则无右儿子。

二叉树按层次遍历----队列实现
1.创建二叉树,输入#代表NULL,调用递归创建二叉树。
2.构建一个队列专门用来储存二叉树节点指针,先把根节点入队,假设是A,对A元素进行访问,然后对A的左右孩子依次入队,假设B,C。A出队列,再是对B进行访问,同样将B的左右孩子入队列,B出对列······重复以上,知道队列为空。

下面是的代码是实现按层次从上到下遍历二叉树:

[code]
#include <Windows.h>
#include <stdio.h>

#define ElemType char

typedef struct TreeNode{ //单位节点
struct TreeNode *LChild;
struct TreeNode *RChild;
ElemType c;
}TreeNode,* p_Tree;

typedef struct Link{ //单向链表
struct Link *Next;
TreeNode *Node;
}Link,*P_Link;

typedef struct Queue{ //形成队列
P_Link front;
P_Link rear;
int Q_Len;
}Queue,*p_Queue;

BOOL PrintElement(ElemType e); //对二叉树节点的操作,自定义,这里我设置为打印出内容
BOOL CreateTree(p_Tree* pBiTree); //创建二叉树
P_Link InitLink(P_Link L); //初始化链表
P_Link InsertLink(P_Link L,TreeNode *Tree_Node,p_Queue *Q); //插入队列
p_Queue GetTopLinkNode(P_Link L, BOOL(*Visit)(ElemType),p_Queue Q); //取出队列第一个元素,并且访问左右孩子
p_Queue InitQueue(p_Queue T,P_Link L); //初始化队列

int main()
{
p_Tree Tree = NULL;
P_Link Link = NULL;
p_Queue Queue = NULL;
if(!CreateTree(&Tree)) //创建二叉树
{
printf("Create Tree Error\n");
return 0;
}
p_Tree p_Temp_Node = Tree;
Link = InitLink(Link);
Queue = InitQueue(Queue,Link);

Link = InsertLink(Link,p_Temp_Node,&Queue); //根节点入队

BOOL(*Operate)(ElemType); //定义函数指针
Operate = PrintElement;

while(Queue->front != Queue->rear)
{
Queue = GetTopLinkNode(Link,Operate,Queue); //获取队列第一个节点
Link = InsertLink(Link,Queue->front->Node->LChild,&Queue); //左孩子入队
Link = InsertLink(Link,Queue->front->Node->RChild,&Queue); //右孩子入队
}

return 0;
}

BOOL CreateTree(p_Tree* pBiTree)
{
ElemType ch;
ch = getchar();
if (ch == '#') //#代表NULL
{
(*pBiTree) = NULL;
}
else
{
(*pBiTree) = (p_Tree)calloc(1,sizeof(TreeNode));
if (!(*pBiTree))
{
printf("Allocate Memory Error\n");
return FALSE;
}
else
{
(*pBiTree)->c = ch;
CreateTree(&(*pBiTree)->LChild); //调用递归创建二叉树
CreateTree(&(*pBiTree)->RChild);
}
}
return TRUE;
}

P_Link InitLink(P_Link L)
{
L = (P_Link)calloc(1,sizeof(Link));
if (!L)
{
printf("Memory Error\n");
return NULL;
}
L->Node = NULL;
L->Next = NULL;
return L;
}

p_Queue InitQueue(p_Queue T,P_Link L)
{
T = (p_Queue)calloc(1,sizeof(Queue));
T->Q_Len = 0;
T->front = L;
T->rear = L;
return T;
}

P_Link InsertLink(P_Link L,TreeNode *Tree_Node,p_Queue *Q)
{
if (Tree_Node == NULL)
{
return L;
}
else
{
static P_Link p = L;
P_Link pNew;
pNew = (P_Link)calloc(1,sizeof(Link));
if (!pNew)
{
printf("Memory Error\n");
return 0;
}
pNew->Node = Tree_Node; //New Node
pNew->Next = NULL;
p->Next = pNew; //p to Next
p = pNew;
(*Q)->rear = p; //Add Queue
(*Q)->Q_Len++ ; //Q len ++
return L;
}
}

p_Queue GetTopLinkNode(P_Link L, BOOL(*Visit)(ElemType),p_Queue Q)
{
Q->front = Q->front->Next; //remove first Node
Visit(Q->front->Node->c);
Q->Q_Len--;
return Q;
}

BOOL PrintElement(ElemType e)
{
// 输出元素e的值
printf("%c",e);
return TRUE;
}
[/code]

一个十几行简单的C程序引发的思考

 今天碰到一个很好玩的程序,我纠结了很久才弄明白。。。吐舌头

下面解释都是在Debug模式下的:

#include <STDIO.H>
#include <windows.h>

void Fun();

int main()
{
BYTE *p;
p = (BYTE *)Fun;
printf("p = %Xn",p);
printf("*p = %Xn",*p);
printf("Fun = %Xn",Fun);
Fun();
return 0;
}

void Fun()
{
printf("Hello Worldn");
}
在VC6.0下输出的结果是

这下我就奇怪了,*p为什么是E9呢??毫无根据可言额.....(没学过汇编的人就是傻额大哭

我就开始调试了,*p是233 = 0XE9

疑问1.为什么*p的值是E9呢?

疑问2.Fun的值怎么是0X00401060呢?打印出来不是0X401005吗?

这是为什么呢??p的值不就是该函数的入口地址吗??函数名就是函数的入口地址啊?为什么就对了呢??

我继续调试,跑到里头去看看了····

首先可以知道一点就是,p = (BYTE *)Fun; 和 printf("Fun = %Xn",Fun);是通过一个偏移获取的一个地址,目前不知道这个地址是不是真正的函数入口地址。生气

再看Fun();这里call 0X00401005地址,我于是跟到了Fun()函数里面。下面是Fun函数的入口地址:

这下可就差不多可以解释了,这才是真正的函数入口地址啊!!奋斗那前面那个 0X00401005是什么呢???

我现在可以推断的就是编译器通过0X00401005来找到函数真正的函数入口地址(0X00401060) 。也就是说p =  00401005,这里存在一个跳转指令(E9)跳到了真正的函数入口地址。

再引入下函数输入表(不知道是不是这么叫的惊讶)的一些知识,程序一跑起来,会有一个函数的输入表,这里记录了部分调用函数的地址。看看那么多的DLL是怎样运作的呢?不需要把函数代码都嵌入到程序里面,到了运行的时候直接去找到对应的函数入口地址就行了,那么函数入口地址怎么找呢?就是通过函数输入表(专业的好像叫符号列表,也叫导出段)。在debug模式下面访问一个函数的时候往往都是先通过一个相对地址跳转找到函数输入表,然后找到函数输入表指定条目-------这里面就是函数入口地址,再通过跳转到真正的函数入口地址,执行函数。

 

上面的两个疑问就解决了

1. *p的值 E9 就是跳转指令,用于跳转到真正的函数入口地址。


2. 0X00401060才是Fun函数真正的入口地址,0X00401005则是Fun函数这一条目对应于函数输入表中的地址。


 

之后我用OD调试了下debug模式,下面是部分截图:

这里是相对偏移偏移地址,也就是p = (BYTE *)Fun;这句代码,从这里可以看出debug模式下确实是通过相对偏移来找到输入表中函数的地址。并且p = (BYTE *)Fun;和printf("Fun = %Xn",Fun);都是通过下面这种形式----即通过相对地址找到函数输入表,然后访问到真正函数来实现的。

 这里是函数调用处,也就是执行Fun();这句代码的地方,指令就是E9 后面的56000000是双字节的偏移量,再来看 p = 0X00401005,*p就是第一个字节(byte类型),也就是jmp指令(E9)。这调指令执行后就到了Fun()过程。

这里是Fun函数的入口点,地址是0X00401060,在反汇编里面叫做过程····

 

       刚刚在请教醒哥的时候,听了他讲了很多,还是只是了解了一小部分,全都是似有似无的概念,不过醒哥确实厉害,讲了些基本的理念之后,提醒我叫我自己去调试,并且告诉了我Debug模式下和Release模式下是不一样的。回到寝室我重新调了下,自己好好分析了才发现原来真是很不一样,不过这时候才真正理解了醒哥说的那些。。。

首先程序的输出就不一样了

 

下面是Release模式下的OD调试结果:

上面的反汇编我都做了些注释,可能有不对,因为自己不是很会,所以···(菜鸟只能这样,勤能补拙)。这里要注意的一点是这里代码中的0X00401060可不是上面那个Fun()

函数的地址额,这里应该只是巧合吧····(不是很懂,有点困了)。。。

从上面可以看出了Release模式下和Debug下的很大区别。在Release模式下就没有了函数输入表这个概念,访问函数的时候直接到了函数的真正的内存中的地址(不是物理地址(*^__^*) ),p = (BYTE *)Fun;和printf("Fun = %Xn",Fun);都是采用的都是真正地址,这样就比上面Debug模式少了一次访问,这个更加高效,这样就是为什么成型产品是一个Debug模式的一个原因吧。

额····有点累了,睡觉了···(*^__^*) 

汇编基础知识

最近感觉自己学的到了一个瓶颈,学的不知道有多少是记住的,总是感觉自己要学的东西有那么多,时间又太少。专业课也越来越多,花在编程上的时间就少了,但是这毕竟是我的兴趣爱好,所以我不想放弃。昨晚突然有个想法,以后写博客的频率加快,尽管这样博客的质量也就下降了很多,但是我还是觉得博客就是来记录我成长的地方,我该把我的学到的,哪怕是点点滴滴都记录下来,呵呵,不知道能坚持多久。

- 阅读剩余部分 -

C 内存管理详解

  程序员们经常编写内存管理程序,往往提心吊胆。如果不想触雷,唯一的解决办法就是发现所有潜伏的地雷并且排除它们,躲是躲不了的。本文的内容比一般教科书的要深入得多,读者需细心阅读,做到真正地通晓内存管理。



1、内存分配方式


- 阅读剩余部分 -

matlab各类数学公式

                      matlab矩阵应用
clear
%建立矩阵的两种方式
A1 = [1 2 3 4 5; 6 7 8 9 10];
A2 = [
        1 2 3 4 5
        6 7 8 9 10
      ];
%一种是换行用引号,一种是自然写法

clear
A = [5 4 3 2 1; 6 7 8 9 10;1 2 3 4 5;24 24 24 24 24;25 25 25 25 25];
B = [5 4 3 2 1; 6 7 8 9 10;1 2 3 4 5;24 24 24 24 24;25 25 25 25 25];
A_sqare = [1 2 3 4 5;6 7 8 9 10;11 12 13 14 15;16 17 18 19 20;21 22 23 24 25];
k = 5;

- 阅读剩余部分 -