• 1. C++语言程序设计
  • 2. 从C到C++
  • 3. C++80年代由贝尔实验室的Bjarne Stroustrup开发 C++是强类型语言,对类型检查更严格 C++比C更丰富 支持面向对象 支持泛型编程 支持异常 运算符重载等
  • 4. C++之父给C程序员的建议在C++中几乎不需要用宏 用const 或 enum定义明显的常量,用inline避免函数调用的额外开销,用模板去刻画一族函数或类型,用namespace去避免命名冲突 不要在你需要变量之前去声明,以保证你能立即对它进行初始化。 不要用malloc,new运算会做的更好 避免使用void*、指针算术、联合和强制,大多数情况下,强制都是设计错误的指示器。 尽量少用数组和C风格的字符串,标准库中的string和vector可以简化程序 更加重要的是,试着将程序考虑为一组由类和对象表示的相互作用的概念,而不是一堆数据结构和一些去拨弄二进制
  • 5. 一些基本区别g++编译器 源程序扩展名,cpp, cc, .C, .cxx 不再使用C中的头文件,虽然还是可以用,要用可以用的头文件 scanf/printf==>cin/cout stdio.h==>iostream ,老一点有iostream.h 标准的c++头文件都没.h了
  • 6. C++中的命名空间名字空间是一种描述逻辑分组的机制,如果有一些声明按照某种准则在逻辑上属于同一集团,就可以将它们放入同一个名字空间,以表明这个事实。 防止命名冲突也是一个很重要的原因。一个人写的程序放到一个名字空间中也是一种可取的做法。 成员可以在名字空间的定义里声明,而后采用namespace-name::member-name的形式去定义 一个名字空间的成员必须采用如下的记法形式引入: namespace namespace-name{ //声明和定义 }
  • 7. Namespace我们不能在名字空间定义之外用加限定的语法形式为名字空间引入新成员。 也能捕捉到例如拼写或类型不匹配一类的错误。 一个名字空间也是一个作用域,一个程序越大,通过名字空间去描述其中逻辑上独立的各个部分也就越重要。 理想情况是,程序里的每个实体都属于某个可以识别的逻辑单位(模块),所以,一个非凡的程序里的每个声明都应该位于某个名字空间里,以此指明它在程序中所扮演的角色。
  • 8. 使用名字空间带限定词的名字。如果使用另外一个名字空间中的成员,需要加限定词,如:std::in. 使用声明:using std::in 使用指令:using namespace std; 无名名字空间 无名中的成员可以直接使用::访问
  • 9. 严格的类型检查enum 在c中直接当整数,而C++中是一种独立的类型 C++中,调用函数不做类型提升 C++增加了bool类型,true, false 函数的参数表严格匹配,空参代表没有任何参数。 函数参数表中的参数如果没有参数名,只有类型,称作为哑元,一般是为了兼容性的考虑。 C中的隐式声明在C++中去掉了,返回类型int型的函数必须声明或定义。
  • 10. C++中的类型转换()强转 static_cast<类型>() 转换时做静态检查,即在编译时进行 reinterprect_case<类型>() 允许强转任何类型的指针 把整数强转成指针,指针强转成整数 const_cast<类型>() 去掉cv限制 dynamic_case<类型>() 动态转换
  • 11. new和delete堆中分配和释放内存 int* p2 = new int;//不保证是零 int* p3 = new int(100); int* p4 = new int[5]; delete p2; delete p3; delete[] p4;
  • 12. 运算符的转换~ compl ^ xor ! not || or && and != not_eq |= or_eq &= and_eq | bitor & bitand
  • 13. 引用reference类型名& 引用是另外一个变量的别名 引用必须初始化,且必须用变量初始化。比如,不能写:int& a = 10,不过可以写成const int& a = 10; 不可能有空引用,必须确保引用和一块合法的存储单元关联 当一个引用被初始化为指向一个对象,它就不能改变为指向另一个对象 引用是用指针来实现的
  • 14. 引用在函数中的应用函数参数的引用 函数返回值的引用 函数参数传递时使用常量引用。这种做法好处很多,当函数不改变变量的值时,推荐使用常量引用。 常量的引用可以接受常量值的实参,否则会导致无法给引用参数传值。 结构类型的参数,如果不用引用和引用的区别。做为函数返回类型的情况。 永远不要返回对局部变量的引用,除非局部变量是静态的或是在动态内存中分配的。
  • 15. 函数的重载方法名相同,参数列表不同,返回值无关 当出现重载函数时,使用函数指针显得有点问题,当给函数指针赋值时,指针的类型代表着具体的函数。 当程序跨越编译器或由其它语言调用重载函数时,函数名在编译时编译器不能对其改变,否则无法调用,此时函数不允许重载,并且需要在函数名前加 extern "C" 以告诉编译器,像C语言那样进行编译处理。
  • 16. 函数参数的默认值函数的参数可以指定默认值,当调用时没有传参,就以默认值进行函数调用。 但是,有默认值的形参必须靠右,声明和定义分开时,默认值需在声明中指定,定义中不再指定。
  • 17. 内联函数宏在C++中不提倡使用 宏存在着一些潜在的危险 C++中的面向对象不允许预处理器访问类的成员数据。也意味着宏函数不能用作类的成员函数。 内联函数保持了函数的所有特点,但在需要的地方会像宏一样展开,不需要调用函数的开销。 类中直接定义的函数自动被处理成内联函数,所以一般把内联函数放在头文件中。 inline是一种请求,实现方式取决于编译器,特别是当函数较大或是递归的时候。
  • 18. 成员指针C++并不提倡指针,一切问题都可以不用指针来解决。 成员指针,成员地址,是一个相对地址。 访问方式 结构变量.*成员指针,结构指针->*成员指针
  • 19. 面向对象编程
  • 20. 面向对象编程万物皆对象 程序就是一组对象,对象之间通过发送消息互相通知做什么 每一个对象都有它自己的由其他对象构成的存储区 每一个对象有一个类型 一个特定类型的所有对象都能接收相同的消息
  • 21. 类类是用户定义的数据类型 在类中声明的变量和函数称为类的成员 变量称为数据成员 函数称为成员函数(有时称为方法) 可以用类来声明变量(也称为实例)。每个实例是类的一个对象 定义类的实例可以称为类的实例化
  • 22. 定义类用struct定义类 成员默认为公开 用class定义类 成员默认为私有 string类的使用 = += + > >= < <= == != size() length() find() str[i] s.c_str(转换成c风格,返回char*) 其他成员函数
  • 23. 构造函数和类名相同 没有返回值类型 构造对象时调用 构造函数初始化和花括号初始化 构造函数重载 默认的构造函数 默认的初始化值 默认构造函数中,包含const或引用成员的类不能进行默认初始化 在构造函数中使用初始化列表 将类的声明和实现分离
  • 24. This指针指向当前对象的指针 可将this做为参数传递 可从函数中返回this
  • 25. const对象和const成员函数成员函数可以声明成const函数(声明后加const) 对于const对象,只能调用const成员函数 Const函数和非const函数可以形成重载 对于非const对象的函数调用优先选择非const成员函数 对于类中的mutable数据成员,可以被const成员函数修改 练习 MyArray类
  • 26. 析构函数与类名同,但名称前有一个~ 一个类只有一个析构 析构没有参数,没有返回值 当一个对象释放前调用。释放可能是主动的,也可能是被动的。 一个对象的析构函数只调用一次,但语法上允许多次调用 new和delete的构造和析构。与malloc和free的区别 如果分别使用不同的分配方式创建数组,会怎么样? 默认析构函数 何时需要自定义析构呢?
  • 27. 拷贝构造函数默认的拷贝构造函数会完成所有成员的逐个复制 拷贝构造的调用时机: 函数值传递时 函数返回时 用同类型的对象初始时 何时需要自定义拷贝构造函数? 匿名对象 注意编译器的优化
  • 28. 静态成员静态成员的初始化要放在类的外面 静态成员最好直接通过 类名::成员来调用。 静态成员函数没有this指针 示例 单例模式
  • 29. 运算符重载
  • 30. 输入和输出cout <<对象 被编译器解释为: cout.operator<<(对象) 或 operator<<(cout, 对象) cin>>对象 被编译器解释为 cin.operator>>(对象) 或 operator>>(cin, 对象)
  • 31. 友元函数将函数声明成友元,以获得额外的权利 friend 一个成员函数的三项事情: 1)访问类中的私用部分 2)函数位于类的作用域中 3)必须经由对象调用(拥有this指针) 一个静态成员函数只有前两种性质,而友员函数只拥有第一个性质。
  • 32. 双目运算符重载+ 、- 、* 、/ … <、>、>=、<=、==、!= ……. 使用成员函数实现 使用友元函数实现 注意事项 重载函数的参数尽量使用const 尽量使用const函数实现重载
  • 33. 单目运算符重载~ 、!、- ++ 前++和后++ -- 前- -和后- -
  • 34. 不允许被用户重载的运算符:: 作用域解析 . 成员选择 .* 通过到成员的指针做成员选择 ? : 三目运算符 sizeof typeid 另外,不允许对基本数据类型进行运算符重载 不能创造运算符 也不能违反语法定义的形式,比如,不能将%重载为一元运算
  • 35. 只能定义为成员函数的运算符= 、+=、 -=、 *=、/=…… [] 下标运算 ()强制类型转换 -> 间接
  • 36. 隐式类型转换和explicit利用构造函数实现隐式类型转换 防止无意之中的隐式类型转换 class Array{ explicit Array(int = 10); }
  • 37. new 和 delete可以重载new 重载new时,必须重载delete
  • 38. 面向对象编程—继承
  • 39. 继承方式publicprotectedprivatePublic继承PublicProtected隐藏Protected继承ProtectedProtected隐藏Private继承PrivatePrivate隐藏
  • 40. 继承中构造和析构的调用子类隐式地调用父类的构造函数 有时,我们必需显式调用父类的构造函数 子类名(参数列表):父类名(参数列表); 析构函数会按照构造函数相反的顺序调用 子类不会继承父类的构造函数、析构函数和赋值运算符函数。但是子类的这些函数可以调用父类的这些函数
  • 41. 继承中的成员函数名字隐藏 静态成员函数的继承问题 静态函数可以被继承到子类 子类中定义了同名的,父类中的将会被隐藏 当私有继承时,成员 函数的调用
  • 42. 多重继承构造函数总是先调用父类的构造再执行自身的代码。 多个父类按继承顺序执行构造。 析构也会进行相反的调用。 多重继承的成员冲突及解决 将冲突的成员抽象到更高层的类中,并且为了让这个类只构造一个,采用虚继承,即在public前加关键字virtual,以保证这个类的对象在多个继承类中只保留一份。 虚继承中的基类(虚基类)的构造参数由底层子类直接传递
  • 43. 继承还是聚合(组合)is-a和has-a 使用继承而不是聚合进行代码的复用
  • 44. 面向对象编程—多态
  • 45. 多态对于同样的函数调用,每个对象会做出自己恰当的响应 将父类的指针指向子类的对象,在调用函数时有何表现? 可否将子类的指针指向父类的对象 ? 通过父类指针调用子类对象的函数可以吗? 进行强制类型转换后呢?
  • 46. 虚函数如果自用virtual函数,调用哪个版本的函数就由指针指向的对象类型来决定,而不是指针类型 Virtual函数经常需要子类做重写才能表现出多态来 一个函数声明成virtual函数,那么从整个继承层次的那一点开始起向下的所有类中,安将保持是virtual,即使子类重写了此函数而并没有显式地将其声明成virtual 函数的重写 函数名相同 参数列表相同 返回类型相同
  • 47. 静态绑定和动态绑定通过指向子类对象的父类指针或引用调用virtual函数时,程序会根据所指对象而不是指针指针类型,动态选择正确的子类函数。这种执行时选择合适的调用函数称为动态绑定或延迟绑定 当virtual通过对象名使用圆点运算符调用函数时,调用哪个函数是在编译期已经决定了的(称为静态绑定),此时的调用行为没没有多态性。
  • 48. 纯虚函数和抽象类永远不需要实例化的类应该定义为抽象类 一个虚函数不需要或不能写出任何实现时,可以定义为纯虚函数 Virtual void f()=0; 存在纯虚函数的类是 抽象的 抽象类为类层次中各种类定义提供公共的接口 抽象类的纯虚函数必须在子类中重写,否则子类也是抽象类 抽象类不能实例化 抽象类除不能实例化之外,其他功能和类完全相同
  • 49. 动态绑定的底层实现—虚函数表Vtable 每个类中的虚函数会记录在该类的Vtable表中 每次调用virtual函数时,程序都会利用vtable做出正确的选择 C++通过vtable和动态绑定实现的多态性非常高效,对性能的影响很小 在一些对性能要求非常高的实时程序中,多态性的开销可能难以接受
  • 50. 类型信息和类型转换 使用dynamic_cast来转换指针类型 在运行时检查一个父类对象是否是子类对象:dynamic_cast<子类*>(父类对象地址) 如果成功,返回子类对象地址,否则返回NULL。 执行的前提是,父类中必须有虚函数。 运算符typeid # typeid(类型), typeid(对象) 返回type_info对象的引用 name() 返回类型名 == != 是否同一种类型
  • 51. Virtual析构函数当通过父类指针delete一个子类对象时,构造的行为未定义 在父类的析构函数前加virual,使用之成为虚析构函数 父类的析构函数在子类的析构函数执行之后执行 如果一个类中有virtual函数,那么该类就应该提供一个virual析构函数。
  • 52. 输入/输出流
  • 53. C++中的标准输入输出cout 显示器,有缓冲,可重定向 C++中的格式输入输出是自动进行的,不同类型的处理是通过运算符重载实现。 cerr 显示器,无缓冲,不可重定向 clog 显示器,标准要求有缓冲,不要重定向,但一般实现如同cerr 输出缓冲遇到换行、有输入、flush、满、程序结束时自动刷新 cin
  • 54. IO流的类层次图
  • 55. 非格式化输入输出.get() 读取一个字符 get()读不到返回EOF,带参数时,参数为char引用,返回istream& 格式化输入输出会路过空白字符,get不会 .put() 写出一个字符 .getline() 输入一行 getline(char数组, 长度); 只能放长度-1个字符,放不下时能放多少放多少,并且置为错误状态。 istream& getline( istream&is,string& s,char delimiter='\n' ); .ignore() 忽略若干字符 ignore(1000,'\n')最多抛掉多少,到什么字符为止,如果没有第二个参数,就到文件尾,或到最多字符数。 经常用来清空缓冲区
  • 56. 非格式化输入输出.putback() 退回一个字符 .peek() 查看下一个要读取的字符 cin.clear();可以请除错误状态,不会请空缓冲区。 I/O 对象一旦处于错误状态,就拒绝读写。 eof() 是否结束 fail() 是否失败 bad() 是否坏了 good() 是否好
  • 57. 字符串IO#include istringstream is(a); ostringstream os; os.str(); 只要重载<<运算符,就可以用些来生成字符串
  • 58. 文件I/O#include ifstream fin(C风格字符串); 等同ifstream fin; fin.open(); if(!fin) 打开失败。 ofstream fout("文件名"); if(!fout) ..
  • 59. 文件读写fin.read(内存地址, 字节数) 规范规定,内存地址用char*类型,所以总是做(char*)转换。 fwrite(内存地址, 字节数) 使用时需要引入头文件 fin.gcount()取得刚才那次read到的字符个数。 ifstream(文件名, 打开方式) 打开方式可以有ios::binary, ios::in, ios::app ios::ate(打开文件后,自动定位到文件尾,会把文件清空) ios::trunc(清空原文件) 使用时可用"|"线合用。
  • 60. 文件读写read和write是有istream和ostream中定义的,所以可以用cin和cout来调用。 read和write对类型不关心,只关心什么位置,多少字节。 用istream.seekg(位置,相对参考点),设置当前位置读位置 相对参考点有: ios::beg ios::end ios::cur istream.tellg()获取当前读位置 ostream.seekp(),设置当前写位置。 ostream.tellp(), 获取当前写位置
  • 61. 格式化输入输出skipws //输入时跳过空白 left //左对齐 right //右对齐 intermal //两边对齐 boolalpha //用符号表示布尔 dec //十进制 hex //十六进制 oct //八进制 scientific //科学计数表示上数 fixed //普通小数表示
  • 62. 格式化输入输出showbase //输出前缀,八进制为0,十六进制为0x showpoint //打印尾零 showpos //正数加+号 uppercase //用E或ABCDEF而不是小写 unitbuf //一次输出操作后刷新 flags() //读标志 flags(fmtflags f) //添加标志 setf() //添加标志 unsetf() //清除标志
  • 63. 格式化输入输出示例: const ios::fmtflags my_opt = ios::left|ios::oct|ios::fixed; fout.flags(fout.flags()|ios::showpos); fout.setf(ios::oct, ios::basefield);//第二个参数是个"伪参数", 说明添加
  • 64. 格式化输入输出函数调用 需要头文件 cout.width(x);//设置最小宽度,只管一次 cout << setw(x);//指定宽度 cout.precision();//取得精度 cout.precision(x);//指定精度,科学计数表示小数们,普通表示有效数据位 cout << setprecision(x);//如果指定fixed,就为指定小数点位数。 cout.fill(char);//填充 no开头的代表取消某些标记
  • 65. 异常处理
  • 66. 异常在程序遇到一个无法处理的问题时,一个函数可以: 终止程序 返回一个表示错误的值 返回一个值,并让程序处于非法状态 调用一个预先准备好在出现问题时用的函数 抛出异常 throw 的作用是导致堆栈的一系统回退。直到找到一个适当的catch. 函数声明异常:throw (异常类型),throw()代表不抛出任何异常,不声明代表可能抛出任何异常。 为什么不写不代表不抛出异常呢? 如果一个异常未捕获,就会导致调用 std::terminate()函数。
  • 67. 处理异常异常的多态和捕获所有异常 处理异常的顺序 catch(...) 异常对象与临时对象有一些区别,它会一直保留直到处理,所以,catch一个异常对象是很正常的,并且我们经常使用引用。 异常的重新抛出 直接throw会重新抛出原先异常 由于异常导致的堆栈回退,申请的一些资源会自动调用析构造函数,所以程序出现异常时,对象的析构函数会释放资源。
  • 68. 构造和析构构造函数中出现异常,如果已经申请到的堆内存可能不能释放。解决的办法是不要要构造函数中调用过多会出现异常的函数或使用auto_ptr. 构造函数中出现无法处理的问题时可以抛出异常以告知调用者。 析构函数的调用时机:1、正常调用 2、异常中堆栈回退,异常处理机制退出一个作用域,其中包含的析构函数会调用。如果析构函数中抛出异常,将会非常麻烦。
  • 69. 在函数虚函数覆盖时的异常描述子类函数必须至少是父娄函数的异常描述一样受限(显式的或隐式的) class B{ public: virtual void f(); virtual void g() throw (X, Y); virtual void h() throw(X); }; class D: public B{ void f() throw (X);//ok void g() throw (X);//ok 比父类更受限 void h() throw (X, Y);//error };
  • 70. 标准库异常
  • 71. 由语言抛出: bad_alloc(new抛出) bad_cast(dynamic_cast) bad_typeid(typeid) 标准库异常: exception异常:标准库的根异常 在头文件中定义。 logic_error异常,exceptoin子异常 runtime_error exceptoin子异常
  • 72. 自定义异常在系统设计的开始阶段,就要把异常处理策略加入系统,在系统实现后再包含有效的异常处理是非常困难的 异常处理提供一个单独的、统一的处理问题的技术,这有助于在一个大的项目中的程序员互相了解各自的错误处理代码 应避免利用异常处理作为控制流程序的替代模式 异常处理能够简化软件的各部分的组合,通过预定义的组件将问题传递给特定的程序组件,从而使它有效在工作在一起 当没有异常发生时,异常处理代码会造成很少甚至没有性能损失。因此,实现异常处理的程序运行起来比将错误处理代码和程序逻辑混合起来的程序更高效。