基础知识复习

    1. 无参宏
    2. 有参宏 (带有缺陷)
      1. #define MAX(a,b) a>b?a:b
      2. int main()
      3. {
      4. int n1=1,n2=0;
      5. int n3 = MAX(n1 , n2); // n3 = 1
      6. // 在有参宏中使用自增运算符的时候, 就导致了
      7. // 逻辑上的错误. 且无法避免
      8. int n4 = MAX(n1++,n2++);// n4 = 2,n1=3, n2=1
      9. }
  1. inline 内联函数
    语法: 在函数名前加上inline关键字, 这个函数就有可能成为内联函数.
    成为内联函数的标准:

    1. 函数题必须足够小(代码行数少)
    2. 函数内部不能有复杂的逻辑(switch,函数递归,各种嵌套的循环或选择结构都是不行的)
    3. 用法和函数一样
    4. 功能和有参宏差不多(直接将函数调用替换为函数体的代码.) ```c inline int max(int a , int b){ return a > b ? a : b; }

int main() { int n1=1,n2=2; // 正常方式调用函数 // 但是在编译之后, 这里的函数调用会替换成函数体 // 就类似于有参宏在预编译之后会被直接替换一样. // 但是如果实参是表达式的时候,会先计算出表达式的 // 结果, 然后再进行替换(而有参宏是直接拿表达式来 // 替换) int n3 = max(n1++,n2++); }

  1. 3. `const`
  2. <br />`const`是一个常量修饰符, C++中常量的一些使用规则:
  3. 1. 常量必须被初始化
  4. 1. 在使用常量的时候, 编译器会直接将常量名用它的初始值来替换(和无参宏一样. 在源码中使用宏的名字,经过预处理之后会被替换成宏的内容)
  5. 1. 使用场合
  6. 1. 修饰指针
  7. 1. 常量指针 : 将指针指向的内容修饰为常量
  8. ```c
  9. char szBuff[100]={"hello"};
  10. szBuff[0] = 'A'; // 可以修改
  11. // p就是常量指针
  12. // 1. 指向的内容被修饰为常量,因此,不能通过p去修改内存.
  13. // 2. p自身是可以被修改的
  14. // 3. const只是在语法层面上限制一个指针不能修改它指向的内容, 至于指针指向的内容能不能被修改是不受const的改变.
  15. const char* p ; // 可以不用初始化
  16. p = szBuff;
  17. const char* p1 = "hahahah";
  18. szBuff[0] = 'B'; // 仍然可以被修改
  19. p[0] = 'A' ; // 语法报错.
  20. p1[0] = 'A'; // 语法报错
  1. 1. 指针常量: 将指针自身修饰为常量
  1. char szBuff[100]={"hello"};
  2. // 定义一个指针常量p, 指针常量必须被初始化.
  3. char* p const = szBuff;
  4. // 1. 指针不能再指向其它内存
  5. p = "hello" ; // 错误, p只能指向szBuff
  6. p[0] = 'A'; //在语法上是通过.
  7. char* p2 const = "hahah";
  8. p2[0] = 'A'; // 在语法上是通过.在运行时就错误.因为p2保存的是一个常量字符串的地址, 常量字符串不能修改.
  1. 1. 定义指针时 ,`*` `const`的左边, 指针是一个指针常量
  2. 1. 定义指针时 ,`*` `const`的右边, 指针是一个常量指针
  3. 1. 修饰引用
  1. int n = 100;
  2. // 定义一个引用.
  3. int& rNum = n;
  4. rNum = 10; // 实际就是在修改变量n
  5. // 常量引用.
  6. // 不能rNum2去修改了.
  7. const int& rNum2 = n;
  8. rNum2 = 20; // 报语法错误
  1. 1. 修饰成员函数


实际修饰的是this指针,也就是将this指针修饰成常量指针

  1. class MyClass{
  2. int m_nNum;
  3. public:
  4. void fun() {
  5. // m_nNum = 100;
  6. }
  7. };
  8. void fun(const MyClass* pObj){
  9. pObj->fun(); // 报语法错误.
  10. // 原因分析:
  11. // 1. pObj是一个常量指针
  12. // 2. 成员函数有条件修改自身的成员变量, 这样相当于通过指针调用了成员函数, 成员函数在内部修改了成员, 实际就相当于间接通过指针修改了指针指向的内存. 这是常量指针所不允许的.
  13. }
  1. 1. <br />修改版;
  1. class MyClass{
  2. int m_nNum;
  3. public:
  4. // const 修饰的是this指针.
  5. // this 就相当于 const MyClass* this;
  6. void fun() const {
  7. this->m_nNum = 100; // 此处会报语法错误.
  8. }
  9. };
  10. void fun(const MyClass* pObj){
  11. pObj->fun(); // 不会报错
  12. }
  1. 1. <br />总结:
  2. 1. 在成员函数后加上`const` , 成员函数就是一个常量成员函数, 在这样的函数内部不能修改自身的成员变量, 也不能调用其它的非常量成员函数
  3. 1. 通过常量对象指针(`const MyClass* pObj`), 只能调用常量成员函数.
  1. 引用
    性质:

    1. 引用类型的变量不占用内存空间. 其内存空间来自被引用的变量.
      1. 定义的时候必须初始化
      2. 不能再引用其它的变量
      3. 修改自身的时候, 被引用的变量也会被修改,被引用的变量被修改了, 引用类型的变量也会被修改, 因为它们使用是同一块内存空间.
    2. 使用场合

      1. 传参

        1. void swap(int& l, inr& r){
        2. int t = l;
        3. l=r;
        4. r =t;
        5. }
        6. int main(){
        7. int n1=10,n2=20;
        8. swap(n1,n2); // 执行之后, n1=20,n2=10
        9. }
      2. 传递函数返回值

        1. int& fun(int& n){
        2. return n;
        3. }
        4. int main()
        5. {
        6. int nNum = 100;
        7. fun(nNum) = 200; // 1. 修改了哪块内存?(nNum)
        8. int n2 = fun(nNum);
        9. n2 = 10; // nNum有被修改吗? 没有. 因为n2不是引用类型的变量, 它拥有自己的内存空间. 所以修改了n2不会影响nNum
        10. int& rNum = fun(nNum);
        11. rNum = 10; // 修改到nNum的内存空间了. 因为rNum是一个引用, 没有独立的内存空间,其内存空间使用的就是nNum的.
        12. }
  2. 类型转换

    1. const_cast:常量类型转换 • 去除常量类型

      1. void fun(const char* pStr){
      2. pStr[0] = 'A'; //语法报错.
      3. // 1. C语言风格
      4. char* p = (char*)pStr;
      5. p[0] = 'A'; // 不会报错
      6. // 2. c++风格
      7. char* p2 = const_cast<char*>(pStr);// 类似于(char*)pStr
      8. }
      9. int main()
      10. {
      11. char buff[100];
      12. fun(buff);
      13. }
    2. static_cast:静态类型转换 •编译器认可的,例如char转为int ```c void fun(int n){}

int main() { // 编译器会将3.14从double类型隐式转换成int类型再传参 fun( 3.14 ) ; // 语法能通过,但是会报一个警告:精度丢失…. // 1. C语言风格 fun( (int) 3.14 );// 强制转换成和实参一样的类型 // 2. c++ fun( static_cast(3.14) ); }

  1. 1. reinterpret_cast:强制类型转换 •编译器不认可的,例如int*转为int
  2. <br />用于编译器无法进行隐式转换时的类型转换.
  3. ```c
  4. int main()
  5. {
  6. int* p = 0x403000; // 语法报错.类型不匹配
  7. // C语言风格
  8. p = (int*)0x403000;
  9. //c++
  10. p = reinterpret_cast<int*>(0x403000);
  11. }
  1. dynamic_cast:动态类型转换,用于将父类和子类的指针或引用进行转换的(继承的时候会用到,能够将基类转换为派生类) ```c class Base{

}; class MyClass : public Base{ int nNum[100]; }; class B : public Base{ int m_nNum; };

int main() { Base obj = new B; MyClass pObj = obj; // 语法报错.

  1. // 可以在语法上通过.
  2. // 但在运行的时候会出现严重问题. 因为obj
  3. // 本质上并非是MyClass类型,而是B类型.
  4. pObj = (MyClass*) obj;
  5. // dynamic_cast可以检测是否能够转换
  6. // 如果不能就返回nullptr
  7. pObj = dynamic_cast<MyClass*>(obj);

}

  1. <a name="a2e6ee0b"></a>
  2. # 面向对象-类
  3. 类的组成:
  4. 1. 成员变量
  5. 1. 一个对象的数据组成. 一个对象的内存空间就是由成员变量组成.
  6. 1. 成员在类中的声明顺序决定了成员变量在内存中的顺序.
  7. 1. 使用成员变量
  8. 1. 都需要提供一个对象, 才能使用成员变量.
  9. 1. 在成员函数内部, this指针来表示当前对象
  10. ```c
  11. public:
  12. void fun() {
  13. // this也可以不写,
  14. this->m_nNum1 = 10;
  15. // 不写的时候编译器会自动加上.
  16. m_nNum2 = 100;
  17. }
  1. 1. 在类的外部, 只能通过对象变量来访问成员变量
  1. int main()
  2. {
  3. MyClass obj;
  4. // 必须通过对象来使用成员变量
  5. obj.m_nNum2 = 10;
  6. }
  1. 成员函数

    1. 成员函数内部会自带一个this指针. 这个指针从哪来的?
      1. 通过对象调用函数的时候c++会自动将对象的内存首地址赋值给成员函数内部的this指针.这样一来, 成员函数被调用之后this指针就保存着调用了这个成员函数的对象变量.
    2. 普通成员函数
      1. 需要通过对象才能调用.
      2. 通过this指针也能调用.
      3. 其它情况和普通函数一样. 也可以设置默认参数, 也可以进行函数重载.
    3. 构造函数

      1. 构造函数是一个特殊的成员函数. 它的作用是用于构造一个对象. 它没有返回值, 因为返回值默认就是一个类对象.
      2. 构造函数只能被自动调用.

        1. 定义变量时(定义局部变量, 全局变量)

          1. MyClass g_obj ; // 调用构造函数
          2. int main()
          3. {
          4. MyClass obj1; // 调用构造函数
          5. }
        2. 从堆空间申请对象时

          1. int main()
          2. {
          3. MyClass* pObj = new MyClass;//调用构造函数
          4. }
        3. 函数形参

          1. void fun(MyClass obj){}
          2. int main(){
          3. MyClass obj1; // 调用了构造函数
          4. fun( obj1 );// 会为形参obj调用构造函数.
          5. fun( obj1 );// 会为形参obj调用构造函数.
          6. }
        4. 函数返回值

          1. MyClass fun(){
          2. MyClass obj;
          3. return obj;
          4. }
          5. int main( ){
          6. MyClass obj2 ;
          7. MyClass obj3 = obj2 ;
          8. MyClass obj1 = fun(); // 给obj1调用构造函数
          9. }
      3. 构造函数可以进行重载

        1. 当构造函数没有进行重载的时候,只能调用无参的构造函数. 无参构造函数是C++编译器默认提供的一个构造函数(不需要定义就有了), 但如果定义了其它版本的构造函数, 编译器就不再提供无参构造.
        2. 再进行构造函数重载之后, 构造对象的时候, 就可以通过传参来决定调用哪个重载版本 ```c class MyClass{ public: /重载了构造函数/ MyClass(){} MyClass(int n){} MyClass(double d){} MyClass(int n , double d){} MyClass(MyClass& obj ){} }

void fun(MyClass obj){}

MyClsas fun2(){ return 2; }

int main(){ // 会为obj11调用转换构造函数. // MyClss obj11(2); MyClss obj11 = fun2();

  1. // 隐式转换:
  2. // C++编译器会尝试将整型的实参5,转换成MyClass类型的形参. 但C++编译器是不能没有任何依据地转换.规则: 形参是类类型,并且具有一个构造函数刚好可以将实参传递进去. 此时编译器就可以将实参传给形参的构造函数,直接构造出形参, 而不是直接将实参赋值给形参.
  3. fun( 5 ); // 不会报错, 且会给形参obj调用构造函数: MyClass(int n){}
  4. // 在构造对象时, 会根据实参来调用不同版本的构造函数.
  5. MyClass obj1(5);// MyClass(int n){}
  6. fun( obj1 );// 给形参obj调用了构造函数, 选择的版本:MyClass(MyClass& obj ){}
  7. MyClass *p = new MyClass(5,1);// MyClass(int n , double d){}
  8. MyClass obj2(obj1);// MyClass(MyClass& obj ){}

} ```

  1. 1. 构造函数的一些术语:
  2. 1. 默认构造 : 指的是没有形参的构造函数, 由编译器默认提供, 在某些场合编译器需要自动调用一个类对象的构造函数时, 只能调用默认构造.例如: 子类继承了父类,当子类对象被构造的时候, 父类的构造也会被自动调用,此时就只能自动调用父类的默认构造
  3. 1. 转换构造 : 指的是那些只有一个形参,且参数类型是非本类类型的构造函数们. 一般能够显式调用(例如 `MyClass obj(5)`), 也能隐式调用: `fun(5);` fun的形参是`MyClass`类型
  4. 1. 拷贝构造: 指的是只有一个形参, 且参数类型是本类类型的引用. 一般是在定义一个对象的时候, 将另一个对象作为初始值时,就会自动调用这个版本的构造函数, 一般编译器会默认提供一个拷贝构造, 默认提供的拷贝构造会将对象的内存空间进行拷贝.


类中包含有指针成员的时候, 一般就需要自己编写拷贝构造, 实现对指针指向的内存进行拷贝的功能. 否则默认拷贝构造是不会去拷贝指针指向的内容. (深拷贝和浅拷贝的区别)

  1. 1. 带参构造 : 含有两个以上的形参的构造函数统称带参构造.
  1. 析构函数
    1. 作用和构造函数相反, 当一个对象被销毁的时候,就会调用析构函数.
  2. 运算符重载函数
    1. 访问控制
  3. public : 类内类外都能访问.
  4. protected : 控制在子类内部能访问, 在类外不能访问.
  5. private : 在子类和类外都不能访问.
    1. 静态成员
    2. 友元