C++ const && 二叉树合集
话说昨天因为校园网的问题导致现在才发博文~唉,想吐槽~
这个是昨天写的,觉得,用来回顾还是很不错的,比较具体的都在笔记中,尤其我觉得里面经验性的东西还是不错的。
2013-8-26
今天在回顾我以前写的笔记,笔记时间应该是在大二。话说,现在我在实验室一边听着胡彦斌的《葬英雄》一边写着学习笔记~
看了以往的笔记,感觉,以前的字写的确实不怎么样,现在嘛,嘿嘿,也不怎么样。不过,感觉还是很不一样的。想想当初学习C++的辛苦,在看着现在写出来的程序,觉得自己还是进步了不少的。C++这门语言,应该算是最难学的语言了吧,我觉得汇编是比C++简单些的。
还有一点,对于一门语言是否精通,我觉得有个很简单的标准,简单的有点苛刻,就是,能否独立的制作这门语言的编译器。如果连编译器都做出来了,那绝对是精通的。你应该会认同吧?
回到正题。
之前在写一些模板类和函数的时候,经常会遇到因为函数参数使用了引用类型而导致不能传递常量参数的情况。这种情况,我当时还是觉得有点无解的。毕竟C++中引用的本质是常指针,也就是:type * const typename;作为指针,肯定是要指向某个变量的地址的,使用空指针和未初始化的指针要命的。但是今天看了我两年前的笔记,突然发现,C++对于常引用提供了很好的解决方法,就是声明一个const的引用参数,形式如下:
1 int testfunction(const int & n) 2 3 { 4 5 return n; 6 7 }
对于这样的函数,给n传递一个常数,比如10,是能够正常使用的。其原理,我没搞懂,一般来说,汇编指令中的常数一般是夹杂在指令码中的,而引用的本质是指针,唉,哪位大神明白其原理,还望指导一下。
另外其实有个事情我上个学期的时候意识到的一点就是,当一个函数参数使用了const声明的时候,是有多一重好处的,就是,能够同时允许使用常量以及变量。比如,字符串类的构造函数其参数就是这样的,声明为const类型后,就能够使用字符串常量以及指向字符串的指针,好用得很。当然了,const最本质的限制就是,不能对传递来的数据进行更改,但是这里我记着存在两种逻辑,EffectiveC++中讲过的。
刚才上面提到了使用我之前对于CString类制作的尝试,在这里我刚才想了半天,只记得是和C++的const成员函数相关,但是具体是什么忘记了。刚才在书翻了半天,找到了const成员函数这一块,一看,就想起来了。其实const成员函数,我个人觉得最主要的用途之一,其实是为类的const类型对象提供相应的方法。
书中给了这样的一个例子,简单的两行代码。
1 onst Stock land = Stock(“balabalabala”); 2 3 land.show();
如果show函数不是cosnt成员数,编译器将拒绝(这词用的好)第二行代码,原因就是,show函数无法保证land中的值不会被修改。一般函数(非成员函数)是通过参数来保证的,而很显然,show函数无参数,无法保证。
这里想要调用show函数唯一的方法就是在类的声明中保证show函数不会修改参数,也就是将其声明为const类型的成员函数。就这样。
同时在我当时的代码的相关函数,比如复制构造函数也因此而导致了问题,因为赋值构造函数的参数是const类型的,因此导致当时未声明为const类型的函数在其中出现违规调用的情况,哪怕函数的功能其仅仅是返回一个字符串首地址。对我来说,这个教训还是很深刻的。后来解决方案也很简单,就是,将相关的函数声明为const类型,problem solved。
C++这门语言要注意的细节还真的是不少的。
另外关于const类型,还有一点要谈的,就是我在上个学期的一个小尝试,比较有趣,之前的博客园文章中貌似也发表过的,今天再长谈一番。
这个尝试就是,去修改一个类型为const的整数的值。当时出现这个念头,很大程度上是因为当时我对于数据存储的理解,也就是,内存中的数据用来表示什么,完全决定于你把它当成什么。
为了稍微体现一下我的这个观点,我在这先写一小段代码,展示下结果。
代码如下:
1 int num_str = 0x00313233; 2 3 cout << hex << num_str << endl; 4 5 cout << (char *)&num_str << endl;
输出结果:
就这么一段很简单的代码,输出结果却完全不一样,尽管,源自同一片内存
另外上面这段代码涉及大端小端的问题,有兴趣请留言啊。
接着往下说,就是如何修改一个const常量的值。这个是因为C/C++中,const常量是有内存去存储的,而非宏之类的单纯替换。这个方法和上面代码的方法是一致的,通过类型转换,将其改变为你想要的类型来进行翻译和使用。看着这段代码:
1 const int constval = 10; 2 3 int * pnval = (int *)&constval; 4 5 *pnval = 9; 6 7 cout << constval << endl;
现在看这段代码,觉得蛮有几分调皮的意思。而且这里的值和以前的那篇文章貌似都是一样的。
这段代码你运行后,一般的结果依旧是10,原因的话,我当时看了一下汇编代码,应该是编译器优化,最后一行的constval的值被直接用常数10给取代了。但是如果你以调试的模式来查看,就会发现,在第三行代码运行后,cosntval的值被改为了9.就是这样的。
现在有截图了,看运行截图:
现在,我们换个类型,改为结构体,来使编译器放弃这种优化,代码如下:
1 struct teststruct 2 3 { 4 5 int val; 6 7 }; 8 9 const teststruct constval = {10}; 10 11 teststruct * pnval = (teststruct *)&constval; 12 13 pnval->val = 9; 14 15 cout << constval.val << endl;
运行结果:
代码整体上是差不多的,但是在这里,结果如预期的那样,确实被修改了。
所以在这里,对于什么const也好,结构也好,指针也好,其存储本质都是内存中以字节为单位排列的二进制数,其意义,不同的解释方式会有不同的结果。
你当它是什么,他就是什么。
今天上午的时候不小心把流量超了,下午去买了张校园网充值卡,结果,到现在还是上不了网……同志,我不知道你看到这文章会是什么时候……
闲着也是闲着,室友们打逆战,当时我就比较感慨这种联网游戏是怎么实现的~唉,腾讯还是挺NB的。我上不了网,所以干脆把数据结构之二叉树又写一遍,写数据结构又不需要上网~
这次写二叉树,没用模板,但是改的话,还是比较快的。这个大家都懂得,我用char类型的做在是因为其输入方便。
二叉树的建立本次采用的是用先序的方式,#表示下一节点为空,常规的字符为节点数据。相信熟悉二叉树的同学和前辈们都了解的~
先看节点类和二叉树类的声明:
1 class BinaryNode 2 3 { 4 5 //定义为class比较好,有利于函数扩充 6 7 public: 8 9 BinaryNode(); 10 11 char m_val;//暂定为字符,代码简单 12 13 BinaryNode * pLeftNode; 14 15 BinaryNode * pRightNode; 16 17 }; 18 19 BinaryNode::BinaryNode():m_val(0), pLeftNode(NULL), pRightNode(NULL) 20 21 { 22 23 ; 24 25 } 26 27 class CBinaryTree 28 29 { 30 31 private: 32 33 BinaryNode m_Root; 34 35 void Method(BinaryNode * & pNode);//递归函数 36 37 void Del(BinaryNode * & pNode); 38 39 void FS(BinaryNode * pNode); 40 41 void MS(BinaryNode * pNode); 42 43 void BS(BinaryNode * pNode); 44 45 public: 46 47 CBinaryTree(); 48 49 ~CBinaryTree(); 50 51 void InputTree(); 52 53 void Search(int nMode = 0); 54 55 };
节点类其实就是个带初始化的结构体,没太多说的,常规套路。二叉树类主要两个方法,一个是树的创建,即InputTree,另外一个是Search方法,这个函数整合了三种遍历方式,即前 中 后,换个顺序而已,简单得很。
在二叉树类中,有五个内建方法,Method,用于二叉树的递归构造,Del用于二叉树的清除工作,同样使用递归,而下面的三个函数分别是前 中 后三种方法的递归调用函数。
可以注意到,Method函数以及Del函数的参数都是指针的引用,也就是说需要对指针的值进行修改。其实我刚才向类想Del方法虽然涉及删除,但是不使用指针引用貌似也完全可以。而Method函数则是必须使用指针引用的。因为每个递归函数,只负责本节点的数据处理,而单节点的来源只能来自于参数。这里的思想,我觉得有点类似于发派任务,函数调用后,只负责本节点的数据工作,对于子节点处理,仅有下级的递归函数处理。所以可以看到我的Method函数非常简单。
另外我的这段代码中,第一次的任务是由非递归函数处理的,算是,使用递归函数引导函数吧,通过引导函数,完成第一次的较为特殊的处理后,再由递归函数完成后面的重复性操作。
当然,如果我类中参数定义为指针而不是一个结构体对象,那么直接使用递归函数开始处理也是完全可以的。
还是上不了外网,连QQ截图都没法用,所以,我又把树的分层遍历写了~
刚才看室友们玩逆战的塔防局,他们有个高手带着,一晚上连续通关两次,不过这也是他们从上个学期开始玩逆战到现在唯二的两次通管局,以往都是最后一关失败。但是现在有大腿抱着就是不一样啊。
刚才写的分层遍历代码形式上和前两天发的那个图的遍历思路是一样的,都是通过使用栈来实现分层遍历。不过,我做的这个分层遍历找不到每一层的边界。所以是一起打出来的。但是根据输入能够判断出,确实实现了分层遍历。
贴下树遍历的截图,分别前中后,以及分层遍历。输入是以前序的方式输入的。
最后依旧附上二叉树的全部代码:
1 #include <iostream> 2 #include <queue> 3 4 using namespace std; 5 class BinaryNode 6 { 7 //定义为class比较好,有利于函数扩充 8 public: 9 BinaryNode(); 10 char m_val;//暂定为字符,代码简单 11 BinaryNode * pLeftNode; 12 BinaryNode * pRightNode; 13 }; 14 BinaryNode::BinaryNode():m_val(0), pLeftNode(NULL), pRightNode(NULL) 15 { 16 ; 17 } 18 class CBinaryTree 19 { 20 private: 21 BinaryNode m_Root; 22 void Method(BinaryNode * & pNode);//递归函数 23 void Del(BinaryNode * & pNode); 24 void FS(BinaryNode * pNode); 25 void MS(BinaryNode * pNode); 26 void BS(BinaryNode * pNode); 27 public: 28 CBinaryTree(); 29 ~CBinaryTree(); 30 void InputTree(); 31 void Search(int nMode = 0); 32 void LevelSearch();//分层遍历 33 }; 34 35 CBinaryTree::CBinaryTree() 36 { 37 ;//nothing 38 } 39 40 CBinaryTree::~CBinaryTree() 41 { 42 if(m_Root.m_val == 0) 43 return; 44 if(m_Root.pLeftNode != NULL) 45 Del(m_Root.pLeftNode); 46 if(m_Root.pLeftNode != NULL) 47 Del(m_Root.pRightNode); 48 } 49 50 void CBinaryTree::InputTree() 51 { 52 char val = 0; 53 cin >> val; 54 if(val == '#') 55 { 56 return; 57 } 58 else 59 { 60 m_Root.m_val = val; 61 Method(m_Root.pLeftNode); 62 Method(m_Root.pRightNode); 63 } 64 } 65 66 void CBinaryTree::Del(BinaryNode * & pNode) 67 { 68 if(pNode->pLeftNode != NULL) 69 Del(pNode->pLeftNode); 70 if(pNode->pRightNode != NULL) 71 Del(pNode->pRightNode); 72 delete pNode; 73 } 74 75 void CBinaryTree::Method(BinaryNode * & pNode) 76 { 77 char val = 0; 78 cin >> val; 79 if(val != '#') 80 { 81 pNode = new BinaryNode; 82 pNode->m_val = val; 83 Method(pNode->pLeftNode); 84 Method(pNode->pRightNode); 85 } 86 else 87 { 88 pNode = NULL; 89 } 90 } 91 92 void CBinaryTree::FS(BinaryNode * pNode) 93 { 94 cout << pNode->m_val; 95 if(NULL != pNode->pLeftNode) 96 FS(pNode->pLeftNode); 97 if(NULL != pNode->pRightNode) 98 FS(pNode->pRightNode); 99 } 100 101 void CBinaryTree::MS(BinaryNode * pNode) 102 { 103 if(NULL != pNode->pLeftNode) 104 MS(pNode->pLeftNode); 105 cout << pNode->m_val; 106 if(NULL != pNode->pRightNode) 107 MS(pNode->pRightNode); 108 } 109 110 void CBinaryTree::BS(BinaryNode * pNode) 111 { 112 if(NULL != pNode->pLeftNode) 113 BS(pNode->pLeftNode); 114 if(NULL != pNode->pRightNode) 115 BS(pNode->pRightNode); 116 cout << pNode->m_val; 117 } 118 119 void CBinaryTree::Search(int nMode) 120 { 121 if(0 == m_Root.m_val) 122 { 123 return; 124 } 125 switch(nMode) 126 { 127 case 0: 128 cout << m_Root.m_val; 129 if(NULL != m_Root.pLeftNode) 130 FS(m_Root.pLeftNode); 131 if(NULL != m_Root.pRightNode) 132 FS(m_Root.pRightNode); 133 break; 134 case 1: 135 if(NULL != m_Root.pLeftNode) 136 MS(m_Root.pLeftNode); 137 cout << m_Root.m_val; 138 if(NULL != m_Root.pRightNode) 139 MS(m_Root.pRightNode); 140 break; 141 case 2: 142 if(NULL != m_Root.pLeftNode) 143 BS(m_Root.pLeftNode); 144 if(NULL != m_Root.pRightNode) 145 BS(m_Root.pRightNode); 146 cout << m_Root.m_val; 147 break; 148 } 149 150 } 151 152 void CBinaryTree::LevelSearch() 153 { 154 queue<BinaryNode> qBN; 155 //cout << m_Root.m_val << endl; 156 qBN.push(m_Root); 157 while(!qBN.empty()) 158 { 159 cout << qBN.front().m_val; 160 if(NULL != qBN.front().pLeftNode) 161 qBN.push(*qBN.front().pLeftNode); 162 if(NULL != qBN.front().pRightNode) 163 qBN.push(*qBN.front().pRightNode); 164 qBN.pop(); 165 } 166 } 167 168 int main() 169 { 170 cout << "Hello world!" << endl; 171 CBinaryTree tree; 172 tree.InputTree(); 173 tree.Search(0); 174 cout << endl; 175 tree.Search(1); 176 cout << endl; 177 tree.Search(2); 178 cout << endl; 179 tree.LevelSearch(); 180 return 0; 181 }