C++学习笔记(转)
http://www.cnblogs.com/maowang1991/p/3290321.html
以下内容为自己一年多的C++学习心得,纯原创,转载请注明源地址。
一年多的C++学习过程中,自己阅读了很多C++经典著作,有《effective c++》,《more effective c++》,《c++ primer》等,每次阅读著作都会总结一些心得,现在拿出来和大家分享一下。
1.struct成员默认访问方式是public,而 class默认访问方式是private! 2.exit函数终止程序执行会调用析构函数 ,abort函数终止程序不会调用析构函数! 3.静态局部变量直到程序终止时才退出! 4.通过public 函数返回 private成员的引用有可能会破坏类的封装 ,造成外部变量可以改变类私有成员值! 5.常量对象只能调用常量成员函数,常量成员函数可以有非常量版本重载! 6.常量数据成员只能在定义时初始化或者在构造函数里用成员初始化值来初始化 ,不能用赋值语句来初始化! 7.要在析构函数里面使用delete来释放使用 new申请的内存空间! 8.编写析构函数来释放类中成员所申请的内存空间和使用深拷贝函数是好的编程习惯! 9.operator++()是相当与++d,operator++(int) 是相当于 d++ ! 10.如果父类中函数是虚拟函数 ,那么在每个子类中显式声明虚拟函数是好的编程习惯! 11.如果类作为基类,其析构函数要声明为虚析构函数 ,这样派生类才可以调用自己的析构函数! 12.一个new 就对应一个 delete是好的编程习惯! 13.istream输入read 是从文件中读取 ,ostream输出write 是写到文件中去! 14.istream是seekg 重置文件指针位置 ,ostream是seekp, 文件用file.eof()来判断是否读取到文件尾部! 15.assert(条件), 当满足条件时就不 assert,不满足条件才会assert! 16.对于不需要修改的变量,使用常量引用来传递参数可以既保证安全性又保证效率! 17.在循环之前调用srand(time(0)),然后在循环中调用 rand()函数可以使得每次随机的结果都不相同! 18.一维数组形参如下:int a[],二维数组形参如下 :int a[][SIZE],第一个[] 是空的,后面必须非空! 19.#define A 10 后面没有分号! 20.在局部函数中用new创建的变量是存在内存中的,即便局部函数执行完毕内存变量仍然存在,但是指向它的指针有可能是局部变量,则需要在局部函数结束前调用 delete释放内存空间,以免内存泄漏 21.数组声明最好用 T *a 来声明 ,这样不容易出错,创建对象最好用 new 而不是直接创建! 22.定义一个类时,析构函数(释放资源)和拷贝构造函数(深拷贝)最好显示定义! 23.C/C++语言输入结束符是ctrl+z(windows下 )! 24.C语言的printf(“%.9lf”,a); 比c++的 setprecision(10)来的更加精确,C++有时自动舍入精度! 25.理清状况,逻辑严谨,变量初始化、是还是否、边界判断判断正确! 26.要使得栈中的基本元素是模板类类型,必须要定义模板类的一些函数:默认构造函数,友员输出函数等! 27.用单件模式和全局函数替代全局变量,以便于拓展和维护! 28.用const char *ptr 表示ptr指向的变量为常量。 char* const ptr表明ptr 本身为常量是好的编程习惯! 29.const int *i = &a, 只是代表不能通过 i指针来修改,但是可以通过其它途径来修改 a,例如a=3 ! 30.灵活熟练运用语言,如 (a<b?a:b<c?b:c) = val();这句话就将3 个if语句合并成一句话! 31.桥接模式bridge 可以实现接口与实现分离 ,这样可以减少修改类时需要修改的类的范围! 32.优先级:i++ 高于 ++i ,注意,具体运算符优先级看下面的运算符优先级表! 33.递归算法符合大致算法思想就行了 ,不必深究思考,合理就可以了! 34.在C++ 中使用0来对指针初始化可以保证数字字面常量 0可以转换成任何一种指针类型对应的空指针! 35.非const 对象可以调用 const和非const 函数,但是 const对象只能调用const函数! 36.可以使用非const 类型变量赋给 const类型参数,不能用const类型变量赋值给非 const类型参数! 37.类重载二元运算符时,要么作为类的只带一个参数的内部函数 ,要么作为类中带两个参数的友元函数! 38.内建的operator-> 是二元的 ,而重载的operator-> 是一元的 ,如果重载了operator-> 则先调用重载的 ,直到重复调用到内建的operator->才终止! 39.尽量使用STL 是好的编程习惯! 40.++i返回的是内存变量,可以作为左值也可以作为右值 ,而i++ 返回的是字面量 ,不占内存, 不能作为左值右值! 41.for循环中使用continue 不会出现死循环 ,但是while 中使用continue容易出现死循环 ,因为可能i 没有自加! 42.取最大优先分析原则会取最长的类型 ,所以要这么义:list<vector<string> > lovos;右边两个>> 之间必须有空格 ,如果没有则编译器可能解释成 >>右移操作符! 43.对于平凡整型常量,可以用枚举量来表示! 44.派生类赋值给基类是不要使用对象继续赋值 ,而是使用指针或引用以避免发生截切! 45.不使用 void * 为类型转换的中介类型是良好的编程习惯 ,因为void * 会抹除指针的类型信息! 46.使用引用类型或标准库组件以避免引入多级指针的复杂性是较佳的设计! 47.在类对象中尽量避免使用二级指针 ,尤其是在基类指向派生类对象的指针时尤其要注意! 48.在C++ 类中尽量不要重载类型转换 operator函数, 而是使用有明确含义的如 toInt(),toChar()等函数替代! 49.C++中有时在类构造函数前加上 explicit关键字来禁止隐式类型转换可以减少很多错误! 50.多用引用传递参数,用值传递时会有大量的复制操作 ,效率很低, 多用引用是好的编程习惯! 51.函数对象就是重载了operator()的 class对象, 使用函数对象生成匿名临时对象是好的编程习惯! 52.使用dynamic_cast<>,static_cast<>,const_cast<>,reinterpret_cast<> ! 53.在派生类构造函数中尽量多使用成员初始化列表 ,静态数据成员和数组不允许在成员初始化表中进行初始化 ,可以在函数体中进行初始化! 54.在类中涉及到指针和引用等 ,最好显示撰写深拷贝构造函数和赋值运算符重载 operator=(),以免出现错误! 55.子类在继承父类时如果重写父类的虚函数或者非虚函数 ,要保证在子类中重写的函数访问修饰符和父类结合继承访问后得到的访问修饰符结果一致! 56.在写拷贝构造函数和重写赋值运算符函数时必须把类中所有变量都用 '='号显示写全了,不要写一部分 ,如果你没写, 则编译器只会调用默认构造函数进行变量初始化 ,而不会默认用'='号! 57.尽量不要在类中使用静态变量 ,永远不要做运行期静态初始化! 58.对class 对象或者有可能是 class对象的实体进行直接初始化而不是用赋值语句初始化 ,如:N n(0)! 59.搞清楚复制初始化和赋值初始化的不同 ,复制初始化是调用拷贝构造函数实现 ,而赋值是重载'='实现! 60.可以通过在private 区域声明拷贝构造函数来禁止类的复制初始化! 61.对象作为参数尽量使用引用传递 ,其次考虑指针,尽量不要用值传递 ,如果不想修改对象可以加 const! 62.抛出匿名临时异常对象,并以引用的方式捕获它们 ,如需重新抛出,则抛出既有的异常对象 ,而非处理原始的异常之后 ,抛出一个新的异常对象! 63.不要返回函数作用域内所分配的存储的引用(返回指针或直接传值可以) ,会造成内存泄漏! 64.使用类的构造函数和析构函数来封装一些成对的操作如加锁和解锁等 ,这样在函数执行开始时创建类对象 ,使用A a; 而不适A a();或 A()来创建, 在函数结束前会自动删除改对象 ,也就实现了开始时调用加锁操作 ,结束时调用解锁操作,俗称 RAII(资源获取即初始化)! 65.复制auto_ptr 模板实例化对象将指向的对象的控制权转移 ,然后把复制源设为空! 66.永远不要把auto_ptr 实例化的类型作为 STL容器的基本类型! 67.不要将auto_ptr 对象作为函数参数按值传递 ,会清空原有对象,造成不可预知的错误! 68.不要在虚函数中提供参数的默认初始化物! 69.访问层级(public protected private) 对重写虚函数没有任何影响 ,即使public 继承,你也可以将继承过来的本来应该是 public的函数写到private 作用域中 ,完全可以! 70.派生类中对基类中的非虚函数的重写或重载其实是遮掩 ,会将基类中同名的所有虚函数和非虚函数全部遮掩 ,使之无效, 这时想要构成重载 ,必须使用using A::fun() 函数来导入基类中 fun函数! 71.重载必须在同一个类作用域下才构成重载 ,否则只是遮掩! 72.重载虚函数要么对基类中的每一个都重写要么一个都不要重写 ,或者使用using A::fun() ! 73.C++中尽量避免使用指针,而是使用引用! 74.复制操作不经由模板成员函数来实现 ,必须显示给出复制操作的实现! 75.用户自定义版本的后置式形式自增自减运算符应该返回常量 ,因为后置后置不能作为左值! 76.前置式总是优于后置式,即 ++i总是优于i++ ! 77.非成员友员允许在第一个参数上施行类型转换 ,而成员函数不能在第一个参数上施行类型转换! 78.在进行指针重新复制时一定要注意是否可能造成内存泄漏 ,是否要先释放当前指针所指内存空间! 79.mutable变量可以在const 函数中被修改 ,其效果好于const_cast<>, 所以尽量使用 mutable! 80.类重载运算符时有时必须参数和函数都是 const,这样保证在其他函数调用时即使传递常量参数也没问题! 81.变量传递给常量参数可以 ,但是常量传递给变量参数却不行! 82.多使用虚函数实现不同的功能 ,把基类尽量细分成更小的基类! 83.使用动态绑定而不是条件式的分派实现类型相关的需求! 84.如果某个类型可能成为基类 ,则一开始就把它写成抽象基类! 85.以public 方式继承的基类一般应该是抽象的 ,应该集中精力于接口继承而不是代码复用! 86.接口类就是没有数据成员 ,析构函数声明为虚函数,普通成员函数皆为纯虚函数 ,不声明构造函数的类型! 87.要么public, 要么友元函数 ,其它如protected 继承后的函数交叉调用可能会出现问题! 88.尽量不使用class 数组,多用 STL容器, 如vector,而且容器类型是指针而不是对象 ,如vector<B *>! 89.wget w1 = w2;调用拷贝构造函数,而 wget w1;w1 = w2;调用的是类赋值运算符! 90.类参数传值传递是调用拷贝构造函数 ,多用const 引用! 91.enum{star=5};这里定义的star 可以当作常量字面量来用 ,和#define star 5 效果是一样的! 92.尽量使用const,enum,inline 来代替#define,使用模板 inline函数代替宏! 93.对于单纯常量,最好用 const和enum 代替#define,对于形似函数的宏 ,最好使用inline 函数来替代! 94.如果const 出现在*左边则所指的是常量 ,如果const 出现在*右边 ,则指针是常量! 95.STL中const vector<int>::iterator 相当于T*const,vector<int>::const_iterator相当于const T* ! 96.函数的const 版本可以和原版本重载 ,用非const 函数调用 const函数可以减少代码重复! 97.使用函数返回静态对象将 non-local static对象转化为locat static 对象! 98.引用的赋值相当于把等号右边别名所指的对象值替换了等号左边别名所指对象的值,还是 2个对象! 99.可以通过声明纯虚析构函数来定义一个抽象类 ,这时纯虚析构函数必须给出定义才行! 100.析构函数绝对不要吐出异常 ,即使出现异常也要在析构函数内部处理 ,要么结束要么吞下! 101.不要在构造函数和析构函数中调用 virtual函数, 不会下降到 derived class类别, 用向上传递参数替换! 102.令赋值操作符返回一个reference to *this ! 103.派生类中的拷贝构造函数和赋值符号不会自动拷贝或赋值基类部分 ,所以要显示调用基类拷贝或赋值函数! 104.在释放内存空间时先把原指针所指内存区域备份 ,再删除或指向新的内存空间 ,再delete 原来备份的区域! 105.多使用tr1::shared_ptr 和auto_ptr智能指针来自动释放内存空间! 106.类中尽量不要定义类型转换符号 ,即使定义也没有返回类型 ,因为类型转换符本身已经规定了返回类型! 107.shared_ptr对象如果有多个指向一个指针 ,则只有第一个需要使用指针初始化 ,其他的要么用拷贝构造函数 ,要么用赋值运算符初始化 ,指针初始化只能用一次! 108.对于内置类型、STL和函数对象 ,传值比较高效,但是对于其它类型 ,传递常量引用比较高效! 109.引用和指针一样支持多态性 ,因为引用的内部就是通过指针实现的! 110.尽量用non-member 和non-friend函数替换 member函数! 111.如果一个函数和类相关,但不是成员函数 ,那么该函数也不一定非得是友元函数 ,如果函数能通过类的公共接口完成功能 ,则完全不必声明为友元函数 ,但该函数返回值一般是const! 112.构造函数如果不是explicit则可以进行隐式类型转换 ,不管构造函数有几个参数 ,但前提是如果你给的实际参数个数小于构造函数需求参数个数 ,你得保证构造函数有默认构造参数! 113.一个类内对象内部函数可以访问对象自己的私有成员变量 ,但是如果有一个同类的另一个对象作为函数的参数 ,则该内部函数也可以访问另一个对象的私有成员变量! 114.尽量往后定义变量的出现 ,最好能等到变量不仅需要被使用而且初值也确定时再定义! 115.尽量避免使用转型,即使必须使用也得使用 C++特定的如static_cast 等! 116.虚析构函数都是public,没有其它的像 private和protected 类型的! 117.根据实际需要选准模式,是 is-a 还是has-a,private 继承和has-a类似 ,但是一般都是会选择has-a! 118.因为private 继承不是 is-a关系, 所以不能满足基类指针指向派生类的情况 ,当然不能使用多态性,只能看作是和 has-a一样的情况了! 119.virtual函数即使是private 函数且被 private继承, 虽然对于派生类不可访问 ,但派生类还是可以继承基类 virtual函数并重写的,并无影响! 120.指针可以改变其指向的对象 ,但是引用不行,引用一旦赋值后不能改变! 121.重载某个操作符时应该返回引用而不是指针! 122.隐式转换最多只能转换一次 ,超过一次就被禁止! 123.前缀返回引用,后缀返回 const value,后缀通过前缀实现,前缀效率比后缀高! 124.在析构函数中释放资源,尽量使用引用来捕捉异常 ,而不用传值或指针! 125.模板和异常规格不要混合使用 ,异常规格是一个应该被审慎使用的特性! 126.STL中对于iterator 尽量运用 (*iterator).first来取值, 而不是用 ->来取值! 127.C++禁止为非常量引用创建临时变量 ,所以非常量引用传递参数时不会发生隐式类型转换! 128.常量引用是会创建临时变量的 ,这时会发生隐式类型转换! 129.每一个重载运算符必须自带至少一个用户自定义类型的参数! 130.操作符 -> 优先级高于 * ! 131.每种类有一个vtbl,每个对象有一个 vptr,RTTI是在vtbl 中增加一项 ,所以只会增加类大小,而不会增加对象大小! 132.析构函数必须被定义! 133.auto_ptr对象最好使用const引用传递 ,而不要用值传递! 134.编译器只能默认帮你进行一次类型转换 ,超过一次就不行! 135.对灵巧指针绝对不要提供一个转换到原始指针类型的转换操作符! 136.当使用类型转换太麻烦时 ,不妨就使用模板吧! 137.一定要为含有指针的类提供深拷贝构造函数 ,以免在以后的运用中出现错误! 138.构造函数和析构函数可以被子类继承 ,但如果析构函数是虚函数 ,继承的子类的析构函数也默认是虚函数 ,但你最好加上virtual增加可读性! 139.cout<<setiosflags(ios::fixed)<<setprecision(n)<<s1<<endl; 用来设置 C++中输出小数点后位数! 140.使用extern “C” 可以将函数变成 c函数风格, 即obj文件中不能进行名变换! 141.将十进制化为二进制或判断能否被 2整除或者除以2可以使用右移操作和 &1操作! 142.C++和C语言中,把整数赋值给字符变量,是将整数当作ASCII码赋给字符变量,将字符变量赋值给整数也是将字符变量对应的ASCII码赋给整数! 143.C++中STL容器的工作方式是对内建类型是位拷贝,自定义对象是存入对象的拷贝,使用insert或是push_back时都是使用拷贝,如果创建对象时是在堆中,则传指针给STL,如果在栈中,则传递对象本身! 144.C++中使用STL容器时,若对象拷贝动作较多,基类又要记录派生类,则存放指针,若是基本类型或者很少拷贝,则存放对象!
以上心得我花了很多时间整理,希望能对大家学习C++有所帮助。