×

微信扫一扫,快捷登录!

[转载] [C系列]C++程序员低手箴言1

标签: 暂无标签
[C系列]C++程序员低手箴言1 [C系列]C++程序员低手箴言 1 编程已经3年了 我时常觉得, 如果3年前的我由现在的我来指点的话,应该用3天就可以出师了。 我的这篇文章就是冲这个目的来的。在接下来远远少于3天的篇幅中,我将带领读者从菜鸟一直晋升到COM(因为我自己也只会这么多了),初学者读完后,应当小有所成,基本上能胜任通常各种小IT公司的常规开发任务了。如果你不能学成,please email me: oyd_admin@163.com 。 准备工作: 首先我想明确一点: 编程 与语言有关系吗?我的答案是:有!语言是思维的载体。如果你不能认同这一点,那么就不用往下看了,我们的思维方式不同,沟通起来肯定有困难。 因为与语言有关,所以我选定了一种语言--C++。没有兴趣学C++的或者还无法写出C语言版的Hello World的读者也不需要往下读了,这篇文章不是为你们而写的。 OK,到此为止,接下来,我保证不会再淘汰任何读者了。 在开始做程序之前,我希望首先要养成一种 代码风格 ,这是极为必要的。你程序写的烂,人家一眼也看不出来,可是如果你代码没风格,人家一眼就看出你是菜鸟了。对于代码风格的养成,可以借助我推荐的这个软件GC GreatCode,主页在 [ /cbeaudet] cbeaudet [/url] ,现在已经是开源软件了。你可以借助这个软件,选定一种你喜欢的代码风格,随时美化,随时纠正自己,你很快就能适应。 其实开发平台应该是与语言没有太大关系的,不过就我的经验,只能讲述在windows下做程序,至于linux/unix下面,有其特殊性,但是大部分原理都是相通的。 为了入门快速,强烈建议,不要用记事本写程序,你所用的编辑器一定要带语法高亮功能与自动完成功能,程序都是调出来的(比尔盖茨那样的天才除外),所以你也要选择一款调试器。忘了说, 编译器 也是必须要的。当你成为高手时,你会有需要自己选择这些工具,但是作为入门,我推荐你最省事的工具:VC6.0+Visual Assist 10。VC请务必安装时选中把CRT源代码也装上,这样你以后可以从里面抄很多东西。(有读者问我VC6不标准,为何我还要推荐它,其实真实的原因是为了用上Visual Assist,有了Visual Assist,你写代码的速度会上一个档次,而且极大减轻你的记忆负担,VS2003/2005尽管也能用Visual Assist,不过我嫌它们运行起来速度慢) 做完这些准备后,我们可以开始上路了。 抱歉说一句,我还想淘汰一种类型的读者:女性。我不是歧视,我曾经尝试过教我女朋友C++可是败的很惨,因为心理阴影,所以我没有把握。 另外,不要对本文抱太大希望。想成为编程高手是无法速成的,需要大量的经验积累、理论基础及悟性。可是许多时候你觉得困惑,并非遥不可及,而是有些简单的东西你还不知道而已。本文就是把这些简单的、但是你还不知道的东西告诉你,以达到速成的目的。 一、标准库 学习使用标准库 我记得高中时刚学Basic 语言 的时候,那时候只会一些简单的流程控制,连数组也不大会用,最高成就也就只编出了一个字符弹球的Demo。现在回想起来,能不能使用数组,可以看作会不会写程序的一个分水岭。为什么这么说呢?我大学时有一个同学,很勤奋好学也有一定思维能力,一次C语言作业要求编程序排序3 个数并输出,我作业基本上都是抄的,不过那次抄他的作业差点没把我恶心坏。他的排序方法是把3个数的6种大小情况用if/else全给列举出来,写的还很严谨,一种情况也没漏掉。我当时就想要是30个数排序,全世界生产的作业纸也不够你写的呀,于是自己用数组写了个排序程序(后来知道那是冒泡排序)。像那位同学这样的思维方式,不是在写程序。 数组可以算最简单的一种数据结构,数组之外,还有许多复杂的数据结构。如果不利用一些数据结构,很多程序根本就写不出来,甚至想都没法想。就像是不用数组而要做排序一样。这应该算是许多初学者遇到问题无从下手的原因之一:因为问题的复杂度超出了他能想象的范围。那么,我们需要熟记各种数据结构再来编程吗?不,你可以速成的。只要使用STL标准库,你能写出的程序级别马上能上好几个台阶。 标准库速成 标准库对我们的重大意义不在于它的iostream,cout, >等等花哨的东西。对于一般的文件读写、控制台输出等任务,我至今没看出iostream与传统的printf等相比有何优势。许多教科书上来就教iostream,结果只是让学生换了一种方式来做控制台输出,没得到什么好处就先消磨了大部分耐心。 我希望大家先学习用string、vector、list、map这四个最常用的类,它们的成员函数的详细使用请参看相关文档,我这里只是做一个入门性的介绍。 string 可以被看作是内置的字符串一样,请在通常程序中放弃掉用char[1024]之类的方式来存储字符串的写法。也请停止用strcpy、strcat来组织你的程序。只要一有机会,就不要用它们。下面是替代用法: // old char str[1024] = { 0 }; strcpy(str, "......"); strcat(str, "more"); // new string str = "......"; str += "more"; string相比C风格的用法来说,除了简洁,还安全的多,旧的方式下总要担心所拷贝的内容超过字符数组的长度的情况。 string还提供了一系列的成员函数可以利用,没什么太多好说的。值得一提的是string的下标运算[],如果没有十分必要和把握,请尽量避免使用。如果要修改,请用其replace函数,要截断,请用erase。要获取其C风格的字符串,请用c_str(). vector 比string要重要的多,string即使不用,许多场合下都有替代物,如MFC中的CString,即使是许多初学者也早就耳熟能详了。vector中文译作矢量,不过最好我们把它称为动态数组有助于理解。 我有一个有5年以上 编程经验 的同事,他的代码中不用vector。我经常能在他的代码中看到类似这样的语句: SOMETYPE g_something[10];//最大暂时支持10个 在有一次莫名其妙的Bug中,经过很长时间的Debug,发现是他的代码中是这样写的: BYTE g_cert[1024]; int g_certlength; 这是他假设的一个数字证书的最大长度,刚巧测试用证书长度不到1024,正式证书超过了1024。于是他把1024改成了2048,Bug就解决了。 我们提醒他2048也许还不够用呢?于是他又做了改动: BYTE* g_cert; //使用前 g_cert = (BYTE*)malloc(nCertLen); g_certlength = nCertLen; //使用后 free(g_cert); g_cert = NULL; g_certlength = 0; 他在他经手过的地方制造了成百上千处类似这样的代码。如果我是他上司,一定只安排他写文档。他这样的代码写得越多造成的损失越大。 让我们来看看vector是如何来简化操作的: vector vBuf(nCertLen); OK,现在可以使用这片缓冲区了。 大家注意到,C语言中的数组的声明是不可以用变量的,例如你不能像这样来声明一个数组: BYTE buf[nLength]; 于是当所需要的 空间 不固定时,需要 动态 分配空间。动态分配使用起来不方便,还很容易忘记释放。有时候为了释放这些空间,会把代码弄的要多难看有多难看: bool foo() { byte* b1 = new byte[n1]; if (!do_something(b1)) { delete[] b1; return false; } byte* b2 = new byte[n2]; if (!do_something(b2)) { delete[] b2; delete[] b1; return false; } // ... delete[] b1; delete[] b2; // ... return true; } 可以看到,本来是非常简单的 代码 ,为了要处理内存的释放,结果加进了许多冗余的代码。 请大家记住vector的用法,它用起来相当于可以用变量来声明的数组,你不再需要为释放内存而发愁。 vector除了初始化时可以指定大小以外,你还可以在使用的过程中增加大小,见下面的示例: vector vBuf; // 不指定初始大小,这时候大小为0 vBuf.resize(n1); // 将大小增加为n1 // do something vBuf.resize(vBuf.size() + n2); // 再增加n2的空间 // do something 通常,你完全可以用vector代替传统数组,除非是对性能要求极为苛刻的场合。有读者会问,许多函数需要BYTE*类型的参数,不用new动态分配怎么做呢?这个问题我也遇到过,事实上在我开始用vector之后的相当长一段时间内,我遇到BYTE*类型的参数时我还是用new BYTE[n]来做。直到有一天我实在无法忍受满世界的找指针delete时,我自己写了一个类,在析构函数中自动delete掉分配的空间。又过了不久,我开始意识到自己的愚蠢了:有现成的东西不用,竟然还在重新发明轮子。现在我举一个简单的例子来说明vector的这个应用,打开一个文件,读取文件中的内容到内存中,如下面的代码所示: CFile file; BOOL bRet = file.Open("c:\\test.bin", CFile::modeRead); if (bRet) { vector vBuf(file.GetLength()); file.Read(&vBuf[0], vBuf.size()); // 现在文件内容已经读入内存中,可以使用了 } 还有其他类似例子,读者可以自己举一反三,但是请相信,没有什么是数组可以做,而vector不能做或者做的不如数组好的。如果你找到反例,欢迎和我一起探讨。 vector还有一类很常见的用法,存储复杂对象的指针。对于复杂对象,将其直接存入vector会引起许多麻烦,比较明智的做法是存指针。我还举上面的例子,读入的文件的内容是 存储 了一组 对象 (假设为CDemo),现在需要从文件中将这一组对象还原出来: class CDemo { public: int Load(BYTE* pBuf, int nSize);// 该函数从内存中装载自己,返回其实际读取的字节数 private: //... } void LoadDemosFromBuffer(BYTE* pBuf, int nSize) { // 为了简明起见,这里略去了错误处理 vector vDemo; int nStart = 0; do { CDemo* pDemo = new CDemo; nStart += pDemo.Load(pBuf + nStart, nSize - nStart); vDemo.push_back(pDemo); // push_back用于将元素附加到vector的末尾,vector的大小会自动增长 } while ( nStart & ls) { typedef list::iterator Iter; for (Iter i = ls.begin(); i != ls.end(); i++) { CDemo* pDemo = *i; // use pDemo } } 从上面的例子来看,iterator从用法上就好像是指向容器元素的指针,在本例中,即CDemo*的指针,所以访问时用*i。 iterator的声明无一例外是使用如下的方式: string::iterator iter_string; vector::iterator iter_vector; map::iterator iter_map; ...... 由于这种声明很长,我们经常会用一个typedef来换一个短点的名称来简化书写,就像在foo中做的那样。所有的STL的容器也都提供begin()、end()这样的方法,你可以用类似foo中那样的循环语句来遍历任何一个STL容器。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x




上一篇:[笔记] 有人用过logitec LHD LAN160G硬盘吗?
下一篇:标题: 求助:怎样在服务器上封杀QQ,MSN
兼顾

写了 272 篇文章,拥有财富 1447,被 4 人关注

您需要登录后才可以回帖 登录 | 立即注册
B Color Link Quote Code Smilies

成为第一个吐槽的人

Powered by IT 运维管理
返回顶部