如何学好C++(转)

昨天写了一篇如何学好C语言,就有人回复问我如何学好C++,所以,我把我个人的一些学习经验写在这里,希望对大家有用。首先,因为如何学好C语言中谈到了算法和系统,所以这里就只谈C++语言。 C++是最难的语言。这个世界上最难的编程语言可能非C++莫属了。你千万不要以为几天就可以学好C++,C++的学习曲线是相当BT的,你可以看看这篇文章。C++是一门很自由的语言,自由到了有点BT和恐怖的地步。我甚至认为C++并不是一门成熟的编程语言,因为太容易犯错了。所以,你一定要在一开始就要有很小心谨慎的态度,并把C++当成一种难以训服的猛兽来看待。 多问“为什么要这样”的问题。学习C++一定要多问几个“为什么是这样”,“凭什么要这样”的问题。比如:很多人知道C++有拷贝构造函数和初始化列表,但你真的知道为什么要有拷贝构造函数?为什么要有初始化列表吗?为什么要有template,为什么要有RTTI,为什么不是别的呢?难道就是为了让一门语言变得Cool一些吗?完全不是这样的,C++中的任何一个feature都有些实实在在的原因,你一定要去了解为什么要把C++设计成这样的原因,你才能学好C++。有空看看《C++演化和设计》一书。 看书,大量的C++书。你可以按如下先后顺序阅读(下面这些书,我花了大约4-5年的时间,今天我还在随时温习) 《C++ Primer》,这本初级读本可能让会你啃得很痛苦,所有的语言的特性和为什么都在里面了,好好读读。当然由C++之父写的《C++程序设计语言》也不错。两本看一本就好了(我看的是前者)。 了解C++的语法仅仅是万里长征的第一步,你还需要看看《Effective C++》和《More Effective C++》这两本书并不厚,但我从02年就一直看到现在,每次读我都有新的体会,这两本书太经典了。如果你对C语言不熟,这两本书会让你回去补C语言的课。 《Think in C++》同样是另一本经典之极的书,学c++必读,但是中文版的翻译的很不好,所以还是去读英文版的吧。 《C++沉思录》同样非常值得一读,这里教的不是编程,而是思考的方法,这是相当珍贵的。 《Exceptional C++》和《More Exceptional C++》让你看看各种问题的解决方法和一些常见的经典错误。 《Advanced C++》和《Modern C++》可以让你知道C++各种神奇的用法。 《泛型编程与STL》是把C++实践到了极致的东西。很强大。STL——神一样的模板库(容器,算法和函数对象),不得不服。 《深入探索C++对象模型》让你了解编译器下的C++是什么样的,让你了解C++的性能并不差。这个对于C++的程序员太关键了。我以前写过的《C++虚函数表解析》还有《C++对象内存布局》属于这个范畴。 和Java语言做对比。我个人以为Java对C++这个并不成熟的语言做了很多调整,规范和限制。所以,对比一下Java和C++,想一想,为什么一些东西在C++中可以做,但在Java中却不行。比如:Java的异常是必需要catch的,不然就会编译不通过。为什么Java不提供操作符重载?为什么Java会引入接口来做多重继承?为什么Java没有像C++那样的I/O字符流?为什么Java不支持指针?为什么Java可以做到垃圾回收?等等。Java体现着很多面向对象设计的东西,学习Java有助于你学会怎么更好地使用C++来编程。 面向对象设计。虽然面向对象可能是个骗局。但是我觉得面向对象设计中的一些实践非常的不错,比如,单一原则,依赖倒置原则,等等,都非常地经典。《设计模式》必需一读,《面向对象的分析和设计》可以一读。但不可以设计模式为中心来编程,而应该是用设计模式来解藕。 类库学习。看看MFC是怎么封装Windows API的,看看ACE是怎么面向对象的,看看boost是怎么玩面向对象的,看看CPPUnit又是怎么设计的。当然,Java的JDK中有太多的设计模式,可以参考。 希望没有吓到大家,并欢迎大家补充。 —————更新 2011/03/30 19:20———— 更新几个观点: 1)我不擅长写书评,所以推荐的这些书可能会让你有点看点没有感觉,你可以上豆瓣或是China-pub上看看书评。 2)C++有很多奇淫技巧,有的很BT,包括虚函数表,也许会有人觉得有点没意思,但我觉得很有意思,一方面可以了解一门语言的实现细节,另一方面可以开阔思路。我从学习这些知识中受益很多。 3)上述是我的个人的学习历程,我觉得对我很有效,所以是经验之谈。 … “如何学好C++(转)”

Read More

如何学好C语言(转)

我相信,这可能是很多朋友的问题,我以前也有这样的感觉,编程编到一定的时候,发现能力到了瓶颈,既不深,也不扎实,半吊子。比如:你长期地使用Java和.NET ,这些有虚拟机的语言对于开发便利是便利,但是对于程序员来说可能并不太好,原因有两个: 虚拟机屏蔽了操作系统的系统调用,以及很多底层机制。 大量的封装好的类库也屏蔽了很多实现细节。 一段时间后,你会发现你知其然,不知所以然…我以前在CSDN上写过一篇《Java NIO类库Selector机制解析(上,下,续)》,在那篇文章中我说提到过(有讥讽的语气)Java的程序员不懂底层实现,所以很难把技术学得更扎实。此时,一部分程序员会不自然地想学学底层的技术,很自然的,C语言就被提了上来。 下面是我给这位朋友的一些建议: 鼓励并为你叫好。我鼓励你想要去学C语言的想法和精神,很多人都觉得C语言好学,其实并不然。(你可以看看《C语言的迷题》)现在的这个社会更多地去关注那些时髦的技术,而忽略了这个流行了40+年的C语言。一门技术如果能够流行40多年,这才是你需要去关注和学习的技术,而不是那些刚出来的技术(过度炒作的技术,Windows编程史)。这才是踏踏实实的精神。 不要找借口。这一条路走下来并不容易,不要给自己找借口。我最不喜欢听到的就是“很忙,没有时间”这样的借口。我以前在银行做项目,早9点到晚10点,周一到周六,我一样可以每天抽1个小时来看书和专研,一年下来也能精读5、6本书。我现在的工作项目和招聘任务很紧张,刚生的小孩只有自己和老婆两人带,还需要准备讲课,但是我还是能够找到时间看文章写文章维护酷壳。所以,我可以告诉你,“时间就像乳沟,只要你肯挤,就一定会有”。 学好C语言和系统编程。我认为,学好编程有四个方面:语言、算法和数据结构、系统调用和设计。 语言。我可以告诉你C语言有两大主题你要好好学,一个是内存管理,一个是指针!这个世界上90%以上的C/C++出的严重性错误全是和这两个有关。不要看谭浩强的那本书,那本是本烂书。推荐这本书给你《C程序设计语言(第2版·新版)》 算法和数据结构。我认为,用C语言实现算法和数据结构莫过于最爽的事情。推荐你看这本书——算法:C语言实现(第1~4部分)基础知识、数据结构、排序及搜索(原书第3版),还有那本经典的《算法导论》 系统编程。Windows下推荐两本书——《Windows 程序设计》和《Windows核心编程》,Unix/Linux下推荐两本书——《Unix高级环境编程》和《Unix网络编程卷1,套接字》《Unix网络编程卷2,进程间通信》尤其是《Unix网络编程》这本书,一通百通,无论Windows还是Unix/Linux,都是一样的。 系统设计。关于设计方面,我全力推荐《Unix编程艺术》,看完以后,你就明白什么是真正的编程文化了。然后,当你看到Windows的Fans的某些言论时,你就知道什么叫一笑了之了。 如果你能在2-3年内精读完这些书,并全部融会贯通,那么你就明白什么是一览众山小的感觉了!我足足花了5年时间才算是真正全部读完这些书的。最后,祝你好运!努力! ——-更新:2011/03/29 20:00——- 我想,这篇文章主要想告诉大家这么几件事: 编程编到一定时候,你就需要了解底层系统的机制,否则,知其然不知所以然。 我没有否定非C的程序员的逻辑,真正的逻辑是——如果你想要了解底层机制,请学习C语言和操作系统。 40多年的Unix/C影响深远。包括影响了Windows。如果你想一通百通,一定要了解Unix。那是计算机文化真正的根。 不要肤浅地去思考问题。比如,不要以为一个DBA就不会考虑数据库引擎的内存页面的问题。也不要以为Web程序员就不需要了解后台的服务器和脚本的运行性能以及TCP/IP的问题。 高手往往都是有很强的系统的基础知识的,表面的东西永远是肤浅的。

Read More

关于class和struct的认知.

花了几周的时间看了<<C++ Primer>>并模仿了点代码,感觉C++与C的关系就像vim与vi的关系.扩展了C,但是完完全全的兼容C. 总结了两者间的差异: – C++中的class关键字完全可以当作struct用,但因为它比struct多出的功能,又有些微的不同. – 基于class,C++有了内联inline,当然也可以不限于class中使用. – 定义在类中的成员函数缺省都是内联的,并且内联函数不可过于复杂,内联函数不能使用循环语句和递归. – inline仅是一个对编译器的建议,如果编辑器认为函数过于复杂,则会放弃内联. – 基于class,C++有了多态,继承,虚继承,虚函数,纯虚函数. class的数据成员在内存中的布局不一定是数据成员的声明顺序,C++只保证处于同一个access section的数据成员按照声明顺序排列. 在C++中,class和struct做类型定义是只有两点区别: – 默认继承权限不同,class继承默认是private继承,而struct默认是public继承. – class还可用于定义模板参数,像typename.但是关键字struct不能同于定义模板参数. C++保留struct关键字,原因: – 保证与C语言的向下兼容性,C++必须提供一个struct – 且为了百分百地保证与C语言中的struct的兼容性,把C++中的最基本的对象单元规定为class而不是struct,就是为了避免各种兼容性要求的限制. – 对struct定义的扩展使C语言的代码能够更容易的被移植到C++中.

Read More

关于位运算的认知

在计算机的世界中,一切数据皆为二进制.而二进制则是以位为单位,因此可以说位运算即使二进制的运算. 位运算的运算分量只能是整型或字符型数据.位运算把运算对象看作是由二进位组成的位串信息,按位完成指定的运算,得到位串信息的结果. 位运算的符号 按位与: & 按位或: | 按位异或: ^ 按位取反: ~ 左移: << 右移: >> 将一个数的各二进制位右移N位,移到右端的低位被舍弃,对于无符号数,高位补0 位运算符的优先级从高到低: 依次为: ~, &, ^, |. 其中,按位取反运算符是单目(一元)运算符,其余均为双目运算符.而且~的结合方向自右至左,且优先级高于算术运算符,其余运算符的结合方向都是自左至右,且优先级低于关系运算符. 按位与运算 按位与运算将两个运算分量的对应位按位遵照以下规则进行计算: 0 & 0 = 0 0 & 1 = 0 … “关于位运算的认知”

Read More

关于内存布局的认知

在没有运行程序前,或者说程序未加载到内存前,可执行程序内部已经分好3段信息,分别为代码段text/code,数据段data,未初始化数据段bss3 个部分. 程序在加载到内存前, code segment,data,bss的大小就是固定的.当运行可执行程序,系统把程序加载到内存后,除了根据可执行程序的信息分出代码段,数据段和未初始化数据段之外,还额外增加了栈区与堆区. 代码段(code segment) 代码段存放 CPU 执行的机器指令.通常代码段是可共享的(即另外的执行程序可以调用它),使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可. 代码段通常是只读的(如x86/Arm体系),使其只读的原因是防止程序意外地修改了它的指令.但特定的体系结构允许自修改代码段(如IBM/360型号可以实现Self-modifying code). 代码段一般放在堆或堆栈的下面,以防止堆和栈溢出覆盖到它. 数据段(data segment) 此段含了程序中已初始化的全局变量,静态变量(包括全局静态变量和局部静态变量)和常量数据,它有的部分是只读,有的部分是可读可写. 所有已初始化且含有const修饰的数据通常是在.rodata段(也可能是code segment). 在程序运行后,其生存周期为整个程序运行过程. 未初始化数据段(bss segment) bss存入的是全局未初始化变量和未初始化静态变量,未初始化数据区的数据在程序开始执行之前被内核初始化为0或者空NULL,其生存周期为整个程序运行过程. 栈区(stack segment) 栈是一种先进后出的内存结构.由编译器自动分配释放,存放函数的参数值/返回值/局部变量等.在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间. 堆区(heap segment) 堆是一个大容器,它的容量要远远大于栈,但没有栈那样先进后出的顺序. 堆用于动态内存分配.堆在内存中介于BSS区和栈区之间.一般由程序员分配和释放.若程序员不释放,程序结束时由操作系统回收.

Read More

关于结构体和联合体的认知

结构体的定义和初始化 声明结构体类型成员再定义此结构体的名称. 可以在声明类型的同时定义变量并直接初始化(也可以不初始化只定义变量). 可以直接定义结构体类型变量(无类型名,即匿名结构体声明). 只有在定义结构体变量的时候才可以初始化. 结构体类型和结构体变量关系: – 结构体类型: 指定了一个结构体类型,它相当于一个模型,但其中并无具体数据,系统对之也不分配实际内存单元.可以看作类似变量的声明,但实际是定义. – 结构体变量: 根据定义的结构体类型(内部成员的类型,数量和大小)为之分配空间. 结构体的赋值 普通结构体在使用时候,通过.运算符操作成员. 如果是指针结构体变量,则通过->运算符操作成员. 位域 位域(位段)的特点: 1. 位域声明和结构体类似. 2. 位域的成员必须是整型类型(包括枚举类型),如:int,unsigned int,short,unsigned short,long等; 3. 位域的成员名后边有一个冒号和一个数字: type [var]: digits,代表所占大小. 位域的优点: – 能够把长度为奇数的数据包装在一起,节省存储空间. – 方便访问整形值的部分内容. 位域的缺点: – … “关于结构体和联合体的认知”

Read More

关于作用域的认知

所谓作用域,其实就是指变量作用的范围. C语言变量的作用域按照类型可分为: 1. 代码块作用域(代码块是{}之间的一段代码) 2. 函数作用域 3. 文件作用域 普通局部变量 局部变量也叫auto自动变量(auto可写可不写),一般情况下代码块{}内部定义的变量都是自动变量,它有如下特点: – 在一个函数的形参或在其内部定义(即{}内),则只在本函数范围内有效. – 在复合语句中定义,只在复合语句中有效. – 只有在运行到定义变量的位置,才会开始分配空间. – 在函数调用结束或复合语句结束后局部变量的生命周期也结束. – 变量如果没有赋初值,则其内容为随机值. int i; // 等同于 auto int i; static修饰的局部变量 static局部变量生命周期和普通局部变量不一样,其它用法和普通局部变量一样 程序运行到static局部变量初始化的语句时,才进行初始化;下次执行到,则不再初始化.因此static局部变量只初始化一次,可以赋值多次. static局部变量的作用域也是在定义的函数内有效 static局部变量的生命周期和程序运行周期一样.在函数没有调用前,static局部变量就已经存在;函数调用完毕,static局部变量还存在.不释放,只有在整个程序结束才释放. static局部变量只能用常量初始化,不能用变量初始化.若未赋以初值,则由系统自动赋值: 数值型变量自动赋初值0. 字符型变量赋空字符’\0′. … “关于作用域的认知”

Read More

关于指针的认知

指针 指针的概念 内存区的每一个字节都有一个编号,这就是“地址”.如果在程序中定义了一个变量,在对程序进行编译或运行时,系统就会给这个变量分配内存单元,并确定它的内存地址(编号). – 指针的实质就是内存“地址”.指针就是地址,地址就是指针. – 指针是内存单元的编号,指针变量是存放地址的变量. – 指针也是一种数据类型,指针变量也是一种变量. – 指针变量指向谁,就把谁的地址赋值给指针变量. – “*”操作符操作的是指针变量指向的内存空间. 需要注意的是&符号可以取得一个变量在内存中的地址,但是不能取寄存器变量.因为寄存器变量不在内存里,而在CPU里面,所以是没有地址的. 指针的大小 指针的大小可以使用sizeof()测得,而sizeof()测得的是指针变量指向存储地址的大小. 不同的平台下的指针,其大小是不同的: – 在32位平台,所有的指针地址都是32位(4字节). – 在64位平台,所有的指针地址都是64位(8字节). 空指针和野指针 指针变量也是变量,是变量就可以任意赋值,只要其存储的值不要越界即可(32位为4字节,64位为8字节).但是,当将任意数值赋值给指针变量时,这时的指针就成了野指针,此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域).所以,野指针并不会直接引发错误.但当操作野指针指向的内存区域才会出问题. 但是,野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量(空闲可用),C语言中可以把NULL赋值给此指针,这样就标志此指针为空指针.没有任何指向.而NULL是一个值为0的宏常量. 万能指针 void* void *指针可以指向任意变量的内存空间. 关于const修饰指针的不同情况 当const修饰指针*时,指针指向内存区域不能修改,指针指向可以修改. 当const修饰变量名时,指针指向不能修改,指针指向的内存可以修改. 数组和指针的关系 数组名字是数组的首元素地址,但它是一个常量.可以用指针间接操作数组.即定义一个变量指向数组的地址. 指针数组,即指针的数组.它是数组,数组中的每个元素都是指针类型. 数组指针,即数组的指针.它是指针,它代表指向一个数组的指针. … “关于指针的认知”

Read More

关于数组的认知

数组 数组的概念: 数组就是在内存中连续的相同类型的变量空间.同一个数组所有的成员都是相同的数据类型,同时所有的成员在内存中的地址是连续的. 数组属于构造数据类型,一个数组可以分解为多个数组元素,按数组元素类型的不同,数组可分为:数值数组/字符数组/指针数组/结构数组等类别. 数组的初始化 需要注意的是,在定义数组时,下标中的数只能是常量,而在使用数组时,下标中的数可以是常量也可以是变量.而定义数组的同时进行赋值时,称之为初始化.若全局数组仅声明而不初始化,则编译器默认将数组中的值初始化为0.若仅局部数组未初始化,则其值为随机. 数组名 数组名既是一个地址的常量,同时也指向数组中首元素的地址,它还包含了数组的大小. 多维数组 根据数组的下标数,可分为一维数组或多维数组,而下标中的数则代表数组中的第某个元素. 数组类型 数组名 [n1][n2]…[nn]; 字符数组与字符串数组 By the way,在C语言中没有字符串这种数据类型,可以通过char的数组来替代.并且字符串一定是一个char的数组,但char的数组未必是字符串.以0结尾的char数组就是一个字符串,但如果char数组没有以0结尾,那么就不是一个字符串.所以字符串是一种特殊的char的数组.

Read More

关于类型转换的认知

数据有不同的类型,不同类型数据之间进行混合运算时必然涉及到类型的转换问题. 类型转换存在两种方法: – 隐式类型转换(自动转换): 遵循一定的规则,由编译系统自动完成. – 强制类型转换: 把表达式的运算结果强制转换成所需的数据类型. 隐式类型转换 隐式类型转换(自动转换)的原则: 占用内存字节数少(值域小)的类型,向占用内存字节数多(值域大)的类型转换,以保证精度不降低. 强制类型转换 强制类型转换指的是使用强制类型转换运算符,将一个变量或表达式转化成所需的类型,基本语法格式如下所示: (类型) (表达式) 示例: int i = 4; short s; s = short (i); 强制类型转换需要注意值域大向值域小的转换时,数据丢失的问题.

Read More