c++知识点总结

BuladeMian

贡献于2016-10-26

字数:8617 关键词: C/C++开发 方案 C/C++

C++知识点总结 Day01 一、名字空间( 标准库中的标识符都定义在std名字空间中 ) 1、 名字空间的定义 namespace 名字空间名{ 名字空间成员 } 2、 名字空间指令 using namespace 名字空间名; 3、 名字空间声明 using 名字空间名::标示符; 4、 无名名字空间 namespace { 名字空间成员; } 5、无名名字空间的声明 ::标示符 二、C++的结构、联合和枚举 1、C++的结构里面可以定义函数 2、C++里定义结构型变量的时候可以省略struct关键字 3、C++中的声明联合类型的变量可以不加union关键字,支持匿名联合 4、C++的枚举是一个独立的数据类型,而不是整数类型。 三、C++的bool类型 bool类型变量只可以取true或false 四、C++的函数 1、函数支持重载 重载:在同一个作用域中,函数名相同,参数表不同的函数之间构成重载关系。 函数原型:返回类型+函数名+参数表 函数签名:函数名+参数表 //c++中,同一作用域中,函数签名必须是唯一的。 2、函数指针 函数指针的类型决定其具体指向的重载版本,而不是由实参的类型决定。 3、extern “C” 通过extern "C"指示编译器以C语言的方式处理C++源文件中的C函数(不做函数名置换)。 5、 缺省参数 如果为函数的一个参数指定了缺省值,那么该参数右面的所有参数必须都有缺省值。 注意:缺省参数只能用在函数的声明中。 6、 哑元 借助哑元参数保证函数的向后兼容。 借助哑元实现函数的重载。 7、 内联 内联函数保持了函数的特性,同时避免了函数调用的开销。 inline关键字仅仅表示希望该函数被编译为内联,通常情况下,大函数和递归函数不会被处理为内联。 五、内存分配 1、new/delete操作符 如果内存是以数组的形式分配的,那么也应该按照数组的方式释放——delete[]。 如果new失败,这时会抛出bad_alloc异常,程序应该捕获并处理该异常,否则进程将被系 统杀死。 Day02 一、引用& 1、引用实际上是一个变量的别名。 2、引用必须在声明的同时初始化 3、引用一旦初始化,再不能引用其他变量 4、只有const型引用才能引用常量 5、不能对常引用做赋值操作 二、C++中的类型转换 1、静态类型转换: static_cast<类型> (变量); 需要编译器对类型转换安全性进行检查; 将void*转换为具体类型的指针。 在源类型和目标类型之间只要有一个方向上可以做隐式类型转换,那么在两个方向上就都可以做静态类型转换。 2、动态类型转换:dynamic_cast<类型> (变量); 用于在具有多态特性的父子类之间的转换。 3、常量类型转换:const_cast<类型> (变量); 去除指针或者引用上的const属性。 4、重解释类型转换:reinterpret_cast<类型> (变量); 允许对任意类型的指针进行转换。 在指针和整型之间进行转换。 三、类和对象 1、类的声明和定义可以分开、类的成员变量、类的成员函数。 2、类的访问控制 1) public:公有成员,谁都可以访问。 2) private:私有成员,只有自己才能访问。 3) protected:保护成员,只有自己和自己的子类可以访问 访控限定符 | 访控属性 | 自己 | 子类 | 其它 ---------------------------------------------------------------------------- public | 公有 | Ok | Ok | Ok protected | 保护 | Ok | Ok | No private | 私有 | Ok | No | No 注意:类的缺省访控属性是私有,而结构体的缺省访控属性是公有。 3、构造函数 1)如果一个类中没有定义任何构造函数,系统就会自动提供一个默认的无参构造函数。 2)如果为一个类定义了构造函数,系统就不会在为该类提供任何构造函数了。 3)构造函数可以重载 4)构造函数初始化列表: 1>需要对类类型的成员变量做初始化; 2>含有引用或常量型成员; 4、对象的创建与销毁 1) 在栈中创建对象 类名 对象名 (构造参数); // 隐式构造 类名 对象名 = 类名 (构造参数); // 显式构造 2) 在栈中创建对象数组 类名 数组名[元素个数] = {类名 (构造参数), ...}; 3) 在堆中创建对象 类名* 对象指针 = new 类名 (构造参数); 4) 在堆中创建对象数组 类名* 对象数组指针 = new 类名[元素个数]; Day03 类和对象(下) 一、this指针 1、一般而言,关键字this是一个指针。对于一般成员函数,它指向调用该成员函数的对象,而对于构造函数,它则指向这个正在被构造的对象。 2、在构造函数中可以通过this指针区分成员变量和局部变量(包括参数)。 3、基于this指针的自身引用还被用于支持多重串联调用的场合。 4、将this指针作为函数的参数,实现对象间的交互。 二、常量型成员函数和常量型对象 1、常量型成员函数中的this指针为常量型,以此防止对成员变量的意外修改。对常量型对象(包括指针和引用),只能调用其常量型成员函数。 2、被声明为mutable的成员变量可以在常量型成员函数中被修改。 3、常量型成员函数和具有相同签名的非常量型成员函数构成重载关系。 4、当常量型成员函数与非常量型成员函数构成重载关系时,非常量对象会优先选择非常量型成员函数。 三、析构函数 1、没有参数,不能重载。 构造顺序:创建成员变量->构造函数体 析构顺序:析构函数体->销毁成员变量 2、缺省析构函数 1>对于未定义析构函数的类,系统会提供缺省析构函数,该析构函数负责销毁该类的成员变量。 2>缺省析构函数不负责释放动态分配的资源。 3、对于动态分配的资源,必须通过自己定义的析构函数进行释放。 4、析构函数是释放的资源不仅限于内存资源。 四、拷贝构造函数 1、拷贝构造:用一个已有的对象去构造另一个同类型的副本对象。 2、拷贝构造是通过拷贝构造函数实现的。 class 类名 { 类名 (const 类名& 源对象引用) { 从源对象到当前对象的复制; } }; 3、 如果没有为一个类提供自定义的拷贝构造函数,系统就会提供一个缺省的拷贝构造函 数,实现从源对象到目标对象的复制。 4、某些情况下系统提供的缺省拷贝构造函数可能无法满足具体的应用需求。这时就有必要提供自定义的拷贝构造函数。 5、拷贝构造发生的时机 1>构造对象副本; 2>以对象作为函数的参数和返回值; 3>以对象的方式捕获异常。 五、拷贝赋值运算符 1、拷贝赋值:用一个已有的对象赋值给另一个同类型的副本对象。 2、拷贝赋值是通过拷贝赋值函数实现的。 class 类名 { 类名& operator= (const 类名& 源对象引用) { 从源对象到当前对象的复制; } }; 3、 如果没有为一个类提供自定义的拷贝赋值函数,系统就会提供一个缺省的拷贝赋值函数,实现从源对象到目标对象的复制。 4、某些情况下系统提供的缺省拷贝赋值函数可能无法满足具体的应用需求。这时就有必要提供自定义的拷贝赋值函数。 六、静态成员 1、静态成员是属于类的,唯一的,可为该类对象所共享。 2、静态成员函数只能访问静态成员。 3、非静态成员函数既能访问静态成员,也能访问非静态成员。 4、静态成员变量必须在类外定义并初始化。 5、既可以通过类也可以通过对象访问静态成员,但最好通过类。 6、静态成员同样受类的访问控制属性的影响。 Day04 一、指向成员变量的指针 1、定义语法:成员变量类型 类名::*指针变量名; string Student::*pstrName; //pstrName是指向Student类中string类型的成员变量的指针 2、赋值即初始化语法:指针变量名= &类名::成员变量名; pstrName = &Student::m_strName; // pstrName指向Student类的m_strName成员变量 3、解引用语法:对象.*指针变量名 或者 对象指针->*指针变量名 Student student (...); cout << student.*pstrName << endl; Student* pStudent = &student; cout << pStudent->*pstrName << endl; 二、指向成员函数的指针 1、定义语法:成员函数返回类型 (类名::*指针变量名) (形参表); void (Student::*pfuncSetName) (const string&); 2、赋值和初始化语法:指针变量名 = &类名::成员函数名; pfuncSetName = &Student::SetName; 3、解引用语法:(对象.*指针变量名) (...)、(对象指针->*指针变量名) (...) Student student (...);//创建对象 (student.*puncSetName) (...); Student* pStudent = &student;//创建对象指针 ( pStudent->*pfuncSetName) (...); 三、对于静态成员,可以使用普通指针访问,不需要成员指针。 四、操作符重载 1、操作符的通用语法 1) 双目操作符:<左操作数><操作符><右操作数>,L#R。 2) 单目操作符:<操作数><操作符>或<操作符><操作数>,O#或#O 2、被重载操作符的操作数中至少有一个是类类型。 3、重载操作符不会改变优先级。 4、重载操作符无法改变操作数的个数。 5、除“( )”以外所有操作符函数都不能含有缺省参数。 6、所谓重载操作符实际上就是定一个提供操作符运算法则的函数(成员函数或全局函数)。 五、双目操作符重载 L#R 全局函数:operator# (L, R),如果需要可定义为友元。 成员函数:L.operator# (R) 六、单目操作符重载 #O 全局函数:operator# (O) 成员函数:O.opertor# () 取负运算“-“ 七、输入输出操作符重载 一般情况下,输入输出操作符比较适合通过全局(友元)函数来实现,避免修改标准C++库的iostream类。 重载输出运算符函数形式: ostream& operator<< (ostream& os, const 类名& 对象引用) { 输出对象的内容; return os; } 重载输出运算符函数形式: istream& operator>> (istream& is, 类名& 对象引用) { 输入对象的内容; return is; } 八、不允许重载的操作符 :: - 作用域解析 . - 成员访问 .* - 通过对象对成员指针解引用 ?: - 三目运算 sizeof - 取字节数 typeid - 取类型信息 九、只能定义为成员函数的操作符 = - 拷贝赋值 [] - 下标运算 () - 函数运算 -> - 间接成员访问 Day05 一、成员函数、友元函数 L#R <==> L.operator# (R) 1、一个操作符的左右操作数不一定是相同类型的对象,这就涉及到将该操作符函数定义为谁的成员,谁的友元问题。 2、一个操作符函数被声明为哪个类的成员,取决于该函数的调用对象(通常是左操作数)。 3、一个操作符函数被声明为哪个类的友元,取决于该函数的参数对象(通常是右操作数)。 二、类型转换操作符与自定义类型转换 1、类型转换操作符函数是转换源类型的成员函数。 2、类型转换操作符函数的形式: class 源类 { operator 目标类 (void) { 根据需求完成从源类型到目标类型的转换; } }; 3、一旦为转换源类型提供了到目标类型的转换操作符函数,就可以将源类型对象以隐式转换的方式转换为目标类型的对象,被应用于构造及初始化、赋值、传参、返回等场合。 4、除了类型转换操作符以外,也可以通过在目标类型中定义类型转换构造函数的方法实现从源类型到目标类型的隐式转换。形如: class 目标类 { 目标类 (const 源类& 源类对象引用) { 根据需求完成从源类型到目标类型的转换; } }; 把类型转换构造函数声明为explicit,强制使用显式类型转换。 C++2011标准也允许在类型转换操作符函数中使用explicit关键字达到同样的效果。 三、解引用操作符与智能指针 1、利用自动变量对象在离开作用域时其析构函数会被自动调用的特性,通过一个类类型的对象封装基本指针,在析构函数中释放该指针所指向的资源。 2、通过为该类定义->和*操作符,使该类型的对象可以采用与普通指针一样语法进行成员访问和解引用。 3、这样的类成为智能指针,标准C++库的智能指针: #include std::auto_ptr<类名> 智能指针对象 (new 类名 (...)); 自此无需再做delete操作。 四、下标操作符[] 五、函数操作符() 六、new/new[]/delete/delete[]操作符 如果希望仅针对特定类类型使用自定义new/delete操作符,则可将以上四个操作符函数定义为该类型的静态成员。 七、继承 基类派生子类,子类继承基类,任何时候子类对象都是基类对象,这种关系被称为IsA。 继承方式:public、private、protected。 八、继承的基本要点 1、任何子类对象的内部都包含一个基类类型的子对象,被称为子类对象中的基类子对象。 2、一个子类类型的对象任何时候都可以作为一个基类类型的对象,而不必进行显式类型转换,前提是两者都是通过指针或者引用进行操作的。 3、在子类中可以直接使用基类所有的公有和保护成员,就象它们在子类中声明的一样,但基类的私有成员在子类中虽然存在却不可见,故不能直接访问。 4、如果子类中出现与基类同名的标识符,会对其构成隐藏,但可以通过“::”显式地指明访问基类版本。 九、继承方式 继承方式影响基类中标识符的访控特性在子类中所发生的变化。 访控限定符 访控属性 基类 子类 外部 友元 ------------------------------------------------------------------------------------- public 公有成员 OK OK OK OK protected 保护成员 OK OK No OK private 私有成员 OK No No OK 基类中的 在公有子类中 保护子类中 在私有子类中 ----------------------------------------------------------------------------------------- 公有成员 公有成员 保护成员 私有成员 保护成员 保护成员 保护成员 私有成员 私有成员 私有成员 私有成员 私有成员 十、子类的构造与析构 1、如果子类的构造函数中没有显式地指明其基类子对象如何被构造,系统会采用无参构造函数构造基类子对象,前提是基类要有无参构造函数。 2、构造顺序:基类->成员->子类 析构顺序:子类->成员->基类 3、子类的析构函数(包括缺省析构函数)会自动地析构基类部分,但是反之基类的析构函数不会自动调用子类的析构函数。 十一、子类的拷贝构造函数和拷贝赋值运算符 1、子类的缺省拷贝构造函数会自动调用基类的拷贝构造函数,构造其基类子对象。 2、子类的自定义拷贝构造函数如果没有显式地拷贝构造基类部分,系统会通过基类的无参构造函数构造其基类子对象。 3.拷贝赋值也有类似的情况。 十二、子类的操作符重载:注意兼顾基类子对象。 十四、多重继承 钻石继承问题: 公共基类子对象在不同间接子类中各自存在一个实例,由此导致沿着不同继承路径访问公共基类子对象会产生数据不一致的问题。 解决方案:虚继承 1) virtual 2) 最终子类中可能需要显式调用虚基类的构造函数。 Day06 异常与I/O流 一、非虚的世界 1、对象的自洽性:每个对象在拥有数据的同时,也应该了解对自身的操作。 2、指针游戏 1) 通过指向子类对象的基类指针调用基类的成员函数,实际调用的就是基类类型中定义的版本。 2) 通过指向基类对象的子类指针调用函数,实际调用的是子类类型中定义的版本,如果该函数访问子类所特有的成员变量,编译不会有任何问题,但是执行结果可能会使用某些并未实例化的数据成员。结果将为不确定值,或者引发段错误。 3) 结论:通过一个指针或者引用调用类的成员函数,究竟调用哪个版本有该指针或引用的类型决定,而与其所指向或引用的具体对象无关。 二、虚函数与多态 1、通过指向子类对象的基类指针调用虚函数,实际被调用的是子类中的覆盖版本。 2、虚函数覆盖的限制 1) 基类中虚函数和子类中的覆盖版本必须拥有相同的签名。 2) 如果返回类型是基本类型,那么覆盖版本必须和基类版本具有相同的返回类型。 3) 如果返回类型是类类型的指针或引用,那么覆盖版本可以返回基类版本返回类型的子类。 4) 如果基类版本带有异常说明,那么覆盖版本不能抛出比基类版本更多的异常。 5) 子类中覆盖版本的访控属性与基类版本无关。 6) 只要一个函数在基类中被声明为虚函数,该函数在任何子类中的覆盖版本也就都是虚函数,即使不显式地使用virtual关键字。 3、多态=虚函数+指针/引用 class A { public: void foo (void) { bar (); // this -> bar (); } virtual void bar (void) { cout << "A" << endl; } }; class B : public A { public: void bar (void) { cout << "B" << endl; } }; int main (void) { B b; b.foo (); // 输出?B return 0; }//注意this指针的使用 4、操作符的多态行为:只有以成员函数方式实现的操作符函数才能表现出多态行为。 5、纯虚函数、抽象类与纯抽象类 纯虚函数:在基类中不给出函数的定义而以“=0”表示。 抽象类:至少包含一个纯虚函数的类。抽象类不能实例化为对象。 纯抽象类:完全由纯虚函数组成的类。一般也被成为接口类。 6、虚函数的实现机理——动态绑定 虚函数表 class A { virtual void foo (void) {...} }; class B { void foo (void) {...} }; A* pa = new B; pa -> foo (); // 实际调用B::foo() 当编译器看到pa->foo()语句时,它并不知道pa所指向对象的真实身份,编译器所能做的就是用一段代码替代这个函数调用语句,这段代码执行下列操作: 1) 首先明确指针pa所指向对象的类型; 2) 然后通过这个对象的虚函数表指针_vftbl访问虚函数表,并找到与foo()标识符相对应的虚函数入口地址; 3) 根据入口地址调用虚函数。 这个过程被称为动态绑定。 动态绑定对性能的影响: 1) 内存方面会有影响,存放虚函数表,占用内存空间。 2) 调用虚函数比调用普通函数多用几个CPU周期。 3) 妨碍编译器做内联优化。 三、运行时类型信息——RTTI 1、动态类型转换 dynamic_cast非常适合于具有多态特性的父子类指针或引用之间的显式类型转换,它会在运行时对指针或引用的目标类型进行动态检查,并以返回NULL指针或抛出异常的方式通知调用者转换失败。 2、typeid typeid (int) int n; typeid (n) 返回const type_info& 1) name() 2) ==/!= n if (typeid (n).name () == "i") // 兼容性不好 if (typeid (n) == typeid (int)) // 更好 四、虚析构函数 1、析构函数可以被定义为虚函数,当需要通过基类指针析构子类对象时,必须将基类的析构函数定义为虚函数,即便基类的析构函数什么也不做。原因是缺省析构函数不是虚函数。 2、一般而言,如果一个类中包含有虚函数,那么该类的析构函数也应该是虚函数。 3、一个类中: 构造函数,不可虚 析构函数,可虚 普通成员函数,可虚 静态成员函数,不可虚 操作符重载函数,可虚 五、异常 1、异常语法 1) 抛出异常:throw 异常对象; 2) 捕获异常: try { 可能引发异常的语句; } catch (异常类型1& ex) { 异常处理语句; } catch (异常类型2& ex) { 异常处理语句; } ... catch (...) { 异常处理语句; } 2、异常用法 1) 抛出基本类型异常。通过异常对象的值区分不同的异常。 2) 抛出类类型异常。通过异常类型区分不同的错误。 3) 使用异常说明。 4) 在catch块中重新抛出异常。 5) 不处理异常。 6) 异常对象可以包含更多信息 3、构造函数中的异常 1) 构造函数中可以抛出异常,而且很多时候在构造函数中抛出异常是向类的使用者提示错误的唯一选择。 2) 在构造过程中抛出异常,将导致对象被不完整构造,不完整构造的对象析构函数不会被调用。 3) 所有在构造过程中自动创建的资源(基类子对象、成员变量),会因构造函数抛出异常被自动地回滚释放,但是,动态分配的资源必须在throw之前手动释放,或者使用智能指针。 4、析构函数中的异常 1) 析构函数尽量避免出现异常。 2) 析构函数一旦引发异常往往会令程序进入不稳定状态。 3) 自己的代码不要在析构抛出异常,同时要截获调用代码可以抛出的异常。 A::~A (void) { try { 全部析构语句; } catch (...) { } } 5、标准库异常 Day07 I/O流 一、I/O流的基本概念 1、C++中I/O实际上就是在标准C的I/O流基础之上进行封装。 输入流:针对数据读取来源的流。 输出流:针对输入写入目标的流。 2、C++的I/O流体系 cin - 针对键盘设备的输入流对象 - stdin cout - 针对显示器设备的输出流对象 - stdout cerr - 针对出错设备的输出流对象 - stderr 3、文本I/O 1) 文本文件的格式化I/O 2) 文件位置 A. get和put位置 B. 随机读写 3) 非格式化I/O 4、二进制I/O

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享文档获得金币 ]
0 人已下载

下载文档

相关文档