c++基本语法

(一)、基本概念

  1. 面向对象:其实质是从对象的角度出发

  2. 面向过程:时间维度

    面向对象:事物维度

  3. 什么是面向对象?

    • 我们就所有对象分为一个个的类
    • 类是定义了对象的属性和操作
    • 对象至少属于某一个类
    • 类与类之间可以继承等关系
    • 对象可以包含对象,告诉对象做什么(而不是how to do?)
  4. 计算机学科与自然学科的不同?

    • 自然学科:先有对象,再有类
    • 计算机学科:现有类,再有对象(人为的学科)
  5. 松耦合:可替换

  6. 封装(interface)

    好处:

    • 便于交流(交流外部)
    • 保护内部(隐藏内部)

    实例:将数据用private修饰,用getter,setter修饰

  7. 当一个类没有归到特定的namespace时,它将归属于一个default namespace

  8. :::解析符(可以看做是一个域的限制)

    如:cat::say()表示cat中的say函数

  9. 抽象(abstract)

    • 从某一个层次去看
    • 忽略细节
  10. 最近原则:本地变量的优先级高于全局变量

  11. 字段(field):类中的成员变量

  12. c++复杂的体现

    • 三种内存模型
      • 堆栈(局部)
      • 堆(动态)
      • 全局变量空间
    • 三种内存访问方式
      • 直接
      • 指针
      • 引用
  13. 所有oop语言都是默认动态绑定的,只有c++是静态绑定的:为了效率

  14. 相对关系的名词

    • 声明(declaration)和定义(definition)
    • 初始化(initialization)和赋值(assignment)
  15. 一个类下应该自己重写的函数

    • 构造函数
    • virtual析构函数
    • 拷贝构造函数
  16. 全局变量初始化

    • 对于同一个文件来说是有序的
    • 对于不同文件来说是无序的
  17. assert:如果一个指令是错误的,则退出

  18. 在计算机中set为置1,reset为置0

(二)、编译过程

源文件

为了使中间文件变得简单,这里不用iostream头文件

  1. main.cpp

    #include "TicketMachine.h"  //在main函数中以双引号引用
    
    int main() {
        TicketMachine ticketMachine;
        ticketMachine.showBalance();
        return 0;
    }
    
  2. TicketMachine.h

    //
    // Created by Arno_vc on 2022/3/1.
    //
    
    #ifndef TICKETMACHINE_TICKETMACHINE_H
    #define TICKETMACHINE_TICKETMACHINE_H
    
    class TicketMachine {
    private:
        const int PRICE=0;
        int balance;
        int total;
    
    public:
        TicketMachine();
        void showPrompt();
        void getMoney();
        void printTicket();
        void showBalance();
        void printError();
    };
    
    #endif //TICKETMACHINE_TICKETMACHINE_H
    
  3. TicketMachine.cpp

    //
    // Created by Arno_vc on 2022/3/1.
    //
    
    #include "TicketMachine.h"
    
    void TicketMachine::showBalance() {  //不需要重新构建一个类
    }
    
    TicketMachine::TicketMachine() {
    }
    

尝试编译并生成中间文件

使用g++ main.cpp -saves-temp 保存中间文件

  1. 预编译

    对应文件:TicketMachine.ii

    # 1 "TicketMachine.cpp"
    # 1 "<built-in>"
    # 1 "<command-line>"
    # 1 "TicketMachine.cpp"
    
    
    
    
    # 1 "TicketMachine.h" 1
    # 10 "TicketMachine.h"
    class TicketMachine {
    private:
        const int PRICE=0;
        int balance;
        int total;
    
    public:
        TicketMachine();
        void showPrompt();
        void getMoney();
        void printTicket();
        void showBalance();
        void printError();
    };
    # 6 "TicketMachine.cpp" 2
    
    
    using namespace std;
    
    void TicketMachine::showBalance() {
    
    
    }
    
    TicketMachine::TicketMachine() {
    }
    

    程序:预处理器

    作用:将TicketMachine.h文件插入TicketMakchine.cpp文件

  2. 编译

    对应文件:TicketMachine.o

    	.file	"TicketMachine.cpp"
    	.text
    	.align 2
    	.globl	_ZN13TicketMachine11showBalanceEv
    	.def	_ZN13TicketMachine11showBalanceEv;	.scl	2;	.type	32;	.endef
    	.seh_proc	_ZN13TicketMachine11showBalanceEv
    _ZN13TicketMachine11showBalanceEv:
    .LFB0:
    	pushq	%rbp
    	.seh_pushreg	%rbp
    	movq	%rsp, %rbp
    	.seh_setframe	%rbp, 0
    	.seh_endprologue
    	movq	%rcx, 16(%rbp)
    	nop
    	popq	%rbp
    	ret
    	.seh_endproc
    	.align 2
    	.globl	_ZN13TicketMachineC2Ev
    	.def	_ZN13TicketMachineC2Ev;	.scl	2;	.type	32;	.endef
    	.seh_proc	_ZN13TicketMachineC2Ev
    _ZN13TicketMachineC2Ev:
    .LFB2:
    	pushq	%rbp
    	.seh_pushreg	%rbp
    	movq	%rsp, %rbp
    	.seh_setframe	%rbp, 0
    	.seh_endprologue
    	movq	%rcx, 16(%rbp)
    	movq	16(%rbp), %rax
    	movl	$0, (%rax)
    	nop
    	popq	%rbp
    	ret
    	.seh_endproc
    	.globl	_ZN13TicketMachineC1Ev
    	.def	_ZN13TicketMachineC1Ev;	.scl	2;	.type	32;	.endef
    	.set	_ZN13TicketMachineC1Ev,_ZN13TicketMachineC2Ev
    	.ident	"GCC: (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0"
    
    

    软件:编译器

    作用:将c++代码编译为更底层的汇编语句文件

    注:

    • 编译是以一个.cpp文件为单位进行编译的
  3. 汇编

    对应文件:TicketMachine.o(二进制)

    软件:汇编器

    作用:翻译为最底层的机器码

  4. 链接

    对应文件:main.exe(windows下)

    软件:链接器(ld)

    作用:将多个单元文件(.o)与库文件整合为可执行文件

    注:对于头文件的处理(.h)尽在预编译阶段,但是与之对应的.cpp文件却可以是在链接阶段.换句话说,我们不是根据.h文件与对应的.cpp文件没有必然的联系

    示例:

    • a.h

      void f(int i,int j=0);
      
    • a.cpp

      #include "a.h"
      
      #include <iostream>
      using namespace std;
      
      void f(int i,int j){
          cout << i << " " << j << endl;
      }
      
    • main.cpp

      void f(int i,int j=10);
      
      int main(){
          f(10);
          for(;;);
          return 0;
      }
      
    • 结果:10 10

    • 结论:

      • 尽管main.cpp中没有a.h,最后通过链接(g++ -o test main.cpp a.cpp)使用a.cpp中的f()定义
      • 单元a中尽管用的是a的头文件,最后使用的却是main中的f声明(输出结果为10 10而不是10 0),说明传入的默认参数是在编译时确定的,而不是运行时(具体说来,函数还是只需要两个参数,而这两个参数在编译时确定了。不是说多了两个函数,否则结果应该是0 10)

(三)、定义与声明

  1. 申明(declaration)与定义(definition)

  2. c++所有声明都会添加一个下划线:如global经过编译后变成了_global

  3. 声明的几种方式

    • extern变量
    • 函数声明
    • 类与结构体的声明
  4. extern关键字:表明该变量在其它模块(相当于在当前模块留下了一个"洞")

    • TicketMachine.h

      //
      // Created by Arno_vc on 2022/3/1.
      //
      
      #ifndef TICKETMACHINE_TICKETMACHINE_H
      #define TICKETMACHINE_TICKETMACHINE_H
      
      extern int temp;  //对外声明
      
      class TicketMachine {  //类声明
      private:
          const int PRICE=0;
          int balance;
          int total;
      
      public:
          TicketMachine();
          void showPrompt();
          void getMoney();
          void printTicket();
          void showBalance();
          void printError();
      };
      
      
      #endif //TICKETMACHINE_TICKETMACHINE_H
      
    • TicketMachine.cpp

      //
      // Created by Arno_vc on 2022/3/1.
      //
      
      #include "TicketMachine.h"
      #include <iostream>  //在cpp下加额外需要用的头文件
      
      using namespace std;  //需要加标准命名空间
      
      int temp;  //定义
      
      void TicketMachine::showBalance() {  //不需要重新构建一个类
          cout << balance << endl;
          cout << "temp:" << temp << endl;
      }
      
      TicketMachine::TicketMachine() {
      }
      

    为什么不能在.h文件放普遍变量定义(如int temp=9)?.h文件本身就是放声明的

    例外:const int temp=9,因为常量本身就是全局的

(四)、oop三大特性

封装

继承

  1. 目的:实现软件重用(还包括其它方式,如对象组合)

多态(Polymorphism)

  1. 基本实现

    • 上型
    • 动态绑定:在运行时确定调用哪个函数(搭配virtual关键字使用)
  2. c++有重载但没有覆写(即子类的函数会使父类的同名参数无效化) => c++可以通过virtual实现覆写

  3. 上型:父类属性是子类属性的子集,故子类对象可以作为父类对象调用

    内存空间:父类属性在前,子类属性在后,子类与父类的公共函数分开存放

  4. 强制转换和造型

    • 强制转换:改变了内存数据
    • 造型:改变看待一个对象的角度(从子类的角度=>从父类的角度)

(五)、命名空间

  1. 类全名:std::ostream

    即std命名空间下的类

二、类与头文件

(一)、基本格式

  1. 新建一个TicketMachine类

    • .h文件

      //
      // Created by Arno_vc on 2022/3/1.
      //
      
      #ifndef TICKETMACHINE_TICKETMACHINE_H  //.h文件不需要#include自己
      #define TICKETMACHINE_TICKETMACHINE_H
      
      
      class TicketMachine {
      private:
          const int PRICE=0;
          int balance;
          int total;
      
      public:
          TicketMachine();
          void showPrompt();
          void getMoney();
          void printTicket();
          void showBalance();
          void printError();
      };
      
      
      #endif //TICKETMACHINE_TICKETMACHINE_H
      
      
    • .cpp文件

      //
      // Created by Arno_vc on 2022/3/1.
      //
      
      #include "TicketMachine.h"
      #include <iostream>  //在cpp下加额外需要用的头文件
      
      using namespace std;  //需要加标准命名空间
      
      void TicketMachine::showBalance() {  //不需要重新构建一个类,直接定义
          cout << balance << endl;
      }
      
      TicketMachine::TicketMachine() {
      }
      
    • main文件

      #include <iostream>
      #include "TicketMachine.h"  //在main函数中以双引号引用
      
      int main() {
          TicketMachine ticketMachine;
          ticketMachine.showBalance();
          return 0;
      }
      
  2. 头文件形式

    • #incldue "xx.h":在当前目录找头文件
    • #include <xx.h>:在系统指定目录找头文件
    • #include <xx>:same as #include <xx.h>
  3. #ifndf#define

    • #ifndf XXX:如果没有XXX
    • #define XXX:那么定义XXX,知道后面的#endif为止

    尝试修改:

    • 在.h文件添加#define

      • .h文件

        //
        // Created by Arno_vc on 2022/3/1.
        //
        #define  TICKETMACHINE_TICKETMACHINE_H
        
        #ifndef TICKETMACHINE_TICKETMACHINE_H
        #define TICKETMACHINE_TICKETMACHINE_H
        
        extern int temp;
        
        class TicketMachine {
        private:
            const int PRICE=0;
            int balance;
            int total;
        
        public:
            TicketMachine();
            void showPrompt();
            void getMoney();
            void printTicket();
            void showBalance();
            void printError();
        };
        
        
        #endif //TICKETMACHINE_TICKETMACHINE_H
        
      • .cpp文件

        //
        // Created by Arno_vc on 2022/3/1.
        //
        
        #include "TicketMachine.h"
        //#include <iostream>  //在cpp下加额外需要用的头文件
        
        using namespace std;  //需要加标准命名空间
        
        int temp;
        
        void TicketMachine::showBalance() {  //不需要重新构建一个类
            cout << balance << endl;
            cout << "temp:" << temp << endl;
        }
        
        TicketMachine::TicketMachine() {
        }
        
      • 生成.ii

        # 1 "TicketMachine.cpp"
        # 1 "<built-in>"
        # 1 "<command-line>"
        # 1 "TicketMachine.cpp"
        
        
        # 1 "TicketMachine.h" 1
        # 6 "TicketMachine.cpp" 2
        
        
        using namespace std;
        
        int temp;
        
        void TicketMachine::showBalance() {
            cout << balance << endl;
            cout << "temp:" << temp << endl;
        }
        
        TicketMachine::TicketMachine() {
        }
        
      • 注:可以看到没有类定义

    • 在.cpp文件添加#define

      • .h文件

        //
        // Created by Arno_vc on 2022/3/1.
        //
        
        #ifndef TICKETMACHINE_TICKETMACHINE_H
        #define TICKETMACHINE_TICKETMACHINE_H
        
        extern int temp;
        
        class TicketMachine {
        private:
            const int PRICE=0;
            int balance;
            int total;
        
        public:
            TicketMachine();
            void showPrompt();
            void getMoney();
            void printTicket();
            void showBalance();
            void printError();
        };
        
        
        #endif //TICKETMACHINE_TICKETMACHINE_H
        
      • .cpp文件

        //
        // Created by Arno_vc on 2022/3/1.
        //
        
        #include "TicketMachine.h"
        //#include <iostream>  //在cpp下加额外需要用的头文件
        
        using namespace std;  //需要加标准命名空间
        
        int temp;
        
        void TicketMachine::showBalance() {  //不需要重新构建一个类
            cout << balance << endl;
            cout << "temp:" << temp << endl;
        }
        
        TicketMachine::TicketMachine() {
        }
        
      • 生成ii

        # 1 "TicketMachine.cpp"
        # 1 "<built-in>"
        # 1 "<command-line>"
        # 1 "TicketMachine.cpp"
        
        
        
        
        
        # 1 "TicketMachine.h" 1
        # 7 "TicketMachine.cpp" 2
        
        
        using namespace std;
        
        int temp;
        
        void TicketMachine::showBalance() {
            cout << balance << endl;
            cout << "temp:" << temp << endl;
        }
        
        TicketMachine::TicketMachine() {
        }
        
      • 注:可以看到没有类定义

    • 总结:由于.cpp文件的预编译是自上而下读取的,那么相同头文件时,就不会再读取,避免了出现重复定义等错误(需要注意到.cpp文件间的相互引用可以是比较复杂的关系)

      例:main.cpp引用了TicketMachine2.h和TicketMachine2.h,而TicketMachine2.h中实际也引用了TicketMachine.h

      • main.cpp

        #include <iostream>
        #include "TicketMachine.h"  //在main函数中以双引号引用
        #include "TicketMachine2.h"
        
        int main() {
            TicketMachine ticketMachine;
            ticketMachine.showBalance();
            return 0;
        }
        
      • TicketMachine.cpp

        //
        // Created by Arno_vc on 2022/3/1.
        //
        
        #ifndef TICKETMACHINE_TICKETMACHINE_H
        #define TICKETMACHINE_TICKETMACHINE_H
        
        extern int temp;
        
        class TicketMachine {
        private:
            const int PRICE=0;
            int balance;
            int total;
        
        public:
            TicketMachine();
            void showPrompt();
            void getMoney();
            void printTicket();
            void showBalance();
            void printError();
        };
        
        
        #endif //TICKETMACHINE_TICKETMACHINE_H
        
      • TicketMachine2.cpp

        //
        // Created by Arno_vc on 2022/3/2.
        //
        
        #ifndef TICKETMACHINE_TICKETMACHINE2_H
        #define TICKETMACHINE_TICKETMACHINE2_H
        
        #include "TicketMachine.h"
        
        class TicketMachine2 {
        
        };
        
        
        #endif //TICKETMACHINE_TICKETMACHINE2_H
        

      结果:可以正常运行

      对照(删去#ifndef):编译失败,且在main.ii文件下可以看到TicketMachine的重复引入

      main.ii

      # 1 "main.cpp"
      # 1 "<built-in>"
      # 1 "<command-line>"
      # 1 "main.cpp"
      
      # 1 "TicketMachine.h" 1
      
      
      
      
      
      
      
      extern int temp;
      
      class TicketMachine {
      private:
          const int PRICE=0;
          int balance;
          int total;
      
      public:
          TicketMachine();
          void showPrompt();
          void getMoney();
          void printTicket();
          void showBalance();
          void printError();
      };
      # 3 "main.cpp" 2
      # 1 "TicketMachine2.h" 1
      
      
      
      
      
      
      
      # 1 "TicketMachine.h" 1
      
      
      
      
      
      
      
      extern int temp;
      
      class TicketMachine {
      private:
          const int PRICE=0;
          int balance;
          int total;
      
      public:
          TicketMachine();
          void showPrompt();
          void getMoney();
          void printTicket();
          void showBalance();
          void printError();
      };
      # 9 "TicketMachine2.h" 2
      
      class TicketMachine2 {
      
      };
      # 4 "main.cpp" 2
      
      int main() {
          TicketMachine ticketMachine;
          ticketMachine.showBalance();
          return 0;
      }
      

(二)、类中的字段与函数

  1. 类中的字段也是声明的一部分(声明表示有这么个东西,定义表示这个东西在哪里)

  2. 字段是属于对象的,函数是属于类的

    类的函数调用的参数就是类本身

    • main.cpp

      #include <iostream>
      #include "TicketMachine.h"  //在main函数中以双引号引用
      
      using namespace std;
      
      int main() {
          TicketMachine ticketMachine;
          printf("&ticketMachine.balance:%p\n",&(ticketMachine.balance));
          ticketMachine.showBalance();
          return 0;
      }
      
    • TicketMachine.cpp

      //
      // Created by Arno_vc on 2022/3/1.
      //
      
      #include "TicketMachine.h"
      #include <stdio.h>
      #include <iostream>  //在cpp下加额外需要用的头文件
      
      using namespace std;  //需要加标准命名空间
      
      void TicketMachine::showBalance() {  //不需要重新构建一个类
          cout << balance << endl;
          printf("&balance:%p\n",&balance);  //%p输出内存地址
      }
      
      TicketMachine::TicketMachine() {
      }
      
    • 运行结果

      &ticketMachine.balance:0000006c383ff748
      0
      &balance:0000006c383ff748
      

    深入理解:每个类的函数都有this这个本地变量,作为指针指向调用函数的对象

    转换成c代码思考:f(&ticketMachine)

  3. this:类的成员变量,作为指针指向调用函数的对象

(三)、构造与析构函数

  1. 构造函数的特点

    • 构造函数的函数名就是类名
    • 构造函数无返回值
  2. 对象的初始化

    • 空间是在进入{...}后开始分配的
    • 调用构造函数是在执行到相应语句
  3. default constructor与auto default constructor

    • default constructor:没有参数的构造函数
    • auto default constructor:有编译器自动生成的没有参数的构造函数
  4. 析构函数

    格式:~ClassName(){}

    使用的两种情况:

    • 被动释放:生命周期结束(以{}分隔)
    • 主动释放:delete关键字

三、动态内存分配

  1. new

    本质;运算符(返回值为地址)

    作用:

    • 分配内存
    • 调用构造函数
  2. delete

    作用:

    • 调用析构函数
    • 收回内存
  3. 对应的堆中的内存分配

  4. delete []className:调用一组析构(与new className[]对应)

    • TicketMachine.cpp

      //
      // Created by Arno_vc on 2022/3/1.
      //
      
      #include "TicketMachine.h"
      #include <stdio.h>
      #include <iostream>  //在cpp下加额外需要用的头文件
      
      using namespace std;  //需要加标准命名空间
      
      void TicketMachine::showBalance() {  //不需要重新构建一个类
          cout << balance << endl;
          printf("&balance:%p\n",&balance);  //%p输出内存地址
      }
      
      TicketMachine::TicketMachine() {
      }
      
      void TicketMachine::setBalance(int balance) {
          this->balance = balance;  //使用this关键字
      }
      
      TicketMachine::~TicketMachine() {
          cout << "balance:" << balance << endl;
      }
      
    • main.cpp

      #include <iostream>
      #include "TicketMachine.h"  //在main函数中以双引号引用
      
      using namespace std;
      
      int main() {
          TicketMachine *ticketMachine = new TicketMachine[10];  //注意这里变量类型为指针
          for (int i = 0; i < 10; ++i) {
              ticketMachine[i].setBalance(i);
          }
          delete [] ticketMachine;
          return 0;
      }
      
    • 结果

      balance:9
      balance:8
      balance:7
      balance:6
      balance:5
      balance:4
      balance:3
      balance:2
      balance:1
      balance:0
      

      逆序释放空间

四、访问属性

  1. 常用修饰符

    • private
    • public
    • protected:自身及子孙可以访问
  2. private等修饰符实际上只在编译时刻起作用

    • main.cpp

      #include <iostream>
      #include "TicketMachine.h"  //在main函数中以双引号引用
      #include "TicketMachine2.h"
      
      using namespace std;
      
      int main() {
          TicketMachine *ticketMachine = new TicketMachine();  //注意这里变量类型为指针
          TicketMachine *ticketMachine1 = new TicketMachine();
          ticketMachine1->setBalance(10);
          cout << ticketMachine->getBalance(ticketMachine1);
          return 0;
      }
      
    • TicketMachine.cpp

      //
      // Created by Arno_vc on 2022/3/1.
      //
      
      #include "TicketMachine.h"
      
      #include "TicketMachine2.h"
      #include <stdio.h>
      #include <iostream>  //在cpp下加额外需要用的头文件
      
      using namespace std;  //需要加标准命名空间
      
      void TicketMachine::showBalance() {  //不需要重新构建一个类
          cout << balance << endl;
          printf("&balance:%p\n",&balance);  //%p输出内存地址
      }
      
      TicketMachine::TicketMachine() {
      }
      
      void TicketMachine::setBalance(int balance) {
          this->balance = balance;  //使用this关键字
      }
      
      int TicketMachine::getBalance(TicketMachine *ticketMachine) {
          return ticketMachine->balance;
      }
      
      int TicketMachine::getBalance2(TicketMachine2 *ticketMachine2) {
          return ticketMachine2->balance;  //无法用一个对象直接访问另一个对象的私有变量,编译都通不过
      }
      
      TicketMachine::~TicketMachine() {
          cout << "balance:" << balance << endl;
      }
      
      
    • 结论

      • 在运行时,一个对象可以读取另一个同类对象的private属性(因为oop的访问修饰符特性只在编译时体现)
      • 在运行时,一个对象不可读取另一个非同类对象的private属性(在编译时就无法通过)
  3. friends(友元):友元函数可以访问类的private属性

    • TicketMachine.cpp

      //
      // Created by Arno_vc on 2022/3/1.
      //
      
      #ifndef TICKETMACHINE_TICKETMACHINE_H
      #define TICKETMACHINE_TICKETMACHINE_H
      
      #include "TicketMachine2.h"
      
      class TicketMachine {
      private:
          const int PRICE=0;
          int balance;
          int total;
      
      public:
          TicketMachine();
          void showPrompt();
          void getMoney();
          void printTicket();
          void showBalance();
          void printError();
          void setBalance(int balance);
          int getBalance(TicketMachine *ticketMachine);
          int getBalance2(TicketMachine2 *ticketMachine2);
          friend void printBalance(TicketMachine ticketMachine);  //友元函数
          ~TicketMachine();
      };
      
      
      #endif //TICKETMACHINE_TICKETMACHINE_H
      
    • main.cpp

      #include <iostream>
      #include "TicketMachine.h"  //在main函数中以双引号引用
      #include "TicketMachine2.h"
      
      using namespace std;
      
      void printBalance(TicketMachine ticketMachine){
          cout << "printBalance:" << ticketMachine.balance << endl;  //是友元函数,可以直接访问private属性
      }
      
      /*
      void printBalance2(TicketMachine ticketMachine){
          cout << "printBalance2:" << ticketMachine.balance << endl;  //不是友元函数,无法直接访问private属性
      }
       */
      
      int main() {
          TicketMachine *ticketMachine = new TicketMachine();  //注意这里变量类型为指针
          ticketMachine->setBalance(10);
          printBalance(*ticketMachine);
          //printBalance2(*ticketMachine);
          return 0;
      }
      
    • 结论正确

  4. class与struct的区别:

    • class的属性默认修饰符为private
    • struct的属性默认修饰符为public

    测试:

    • TicketMachine.cpp

      //
      // Created by Arno_vc on 2022/3/1.
      //
      
      #ifndef TICKETMACHINE_TICKETMACHINE_H
      #define TICKETMACHINE_TICKETMACHINE_H
      
      #include "TicketMachine2.h"
      
      class TicketMachine {
          int temp;  //无访问修饰符
      private:
          const int PRICE=0;
          int balance;
          int total;
      
      public:
          TicketMachine();
          void showPrompt();
          void getMoney();
          void printTicket();
          void showBalance();
          void printError();
          void setBalance(int balance);
          int getBalance(TicketMachine *ticketMachine);
          int getBalance2(TicketMachine2 *ticketMachine2);
          ~TicketMachine();
      };
      
      
      #endif //TICKETMACHINE_TICKETMACHINE_H
      
    • main.cpp

      #include <iostream>
      #include "TicketMachine.h"  //在main函数中以双引号引用
      #include "TicketMachine2.h"
      
      using namespace std;
      
      struct Node{
          int a;
      };
      
      int main() {
          TicketMachine *ticketMachine = new TicketMachine();  //注意这里变量类型为指针
          ticketMachine->setBalance(10);
          printBalance(*ticketMachine);
          //printBalance2(*ticketMachine);
          cout << ticketMachine.temp << endl;  //私有默认属性,无法直接访问
          struct Node b;
          cout << b.a << endl;  //公有默认属性,可以直接访问
          return 0;
      }
      

五、初始化

(一)、初始化参数列表

  1. 使用初始化参数列表

    • TicketMachine.cpp

      //
      // Created by Arno_vc on 2022/3/1.
      //
      
      #include "TicketMachine.h"
      
      #include <stdio.h>
      #include <iostream>  //在cpp下加额外需要用的头文件
      
      using namespace std;  //需要加标准命名空间
      
      TicketMachine::TicketMachine(int balance, string name):balance(balance), account(name){  //使用初始化参数表,可以直接调用Account(string name)
      }
      
      TicketMachine::TicketMachine(int balance,int total,string name){  //使用构造函数初始化,对应的Account必须要有default Account,否早accout无法完成初始化
          this->balance = balance;
          this->total = total;
          this->account.setName(name);
      }
      
      void TicketMachine::setBalance(int balance) {
          this->balance = balance;  //使用this关键字
      }
      
      int TicketMachine::getBalance() {
          return this->balance;
      }
      
      void TicketMachine::show(){
          cout << "balance:" << balance << " total:" << total << " name:" << account.getName() << endl;
      };
      
      TicketMachine::~TicketMachine() {
      }
      
    • Acount.cpp

      //
      // Created by Arno_vc on 2022/3/3.
      //
      
      #include "Account.h"
      #include <string>
      
      Account::Account() {} //用于构造函数初始化
      
      Account::Account(string name):name(name) {}  //用于参数初始化列表初始化
      
      string Account::getName() {
          return this->name;
      }
      
      void Account::setName(string name) {
          this->name = name;
      }
      
    • main.cpp

      #include <iostream>
      #include "TicketMachine.h"  //在main函数中以双引号引用
      
      using namespace std;
      
      int main() {
          TicketMachine *ticketMachine = new TicketMachine(1,"test");
          TicketMachine *ticketMachine1 = new TicketMachine(1,2,"test");
          ticketMachine->show();  //使用参数化列表
          ticketMachine1->show();  //使用构造函数
          return 0;
      }
      
    • 结果

      balance:1 total:512 name:test
      balance:1 total:2 name:test
      
    • 结论

      • 参数化列表:初始化
      • 构造函数:初始 => 赋值
  2. 任何类里面的成员变量都应该initialized里面做初始化,而不应该在构造函数中做初始化(否则需要default constructor)

    使用构造函数初始化有时需要default constructor

六、对象组合与继承

对象组合和继承都是实现软件重用的方式

(一)、对象组合

  1. 两种使用方式
    • 包含
    • 引用(指针)

(二)、继承

  1. 格式

    class parent: public child{  //修饰符可以换
    
    }
    
  2. protected:父类留给子类的接口用于访问private

  3. 子类与父类的构造与析构先后

    • 父类先构造,子类后构造
    • 子类先析构,父类后析构

    代码:

    #include <iostream>
    #include <stdio.h>
    #include "TicketMachine.h"  //在main函数中以双引号引用
    
    using namespace std;
    
    class A{
    public:
        A(){
            cout << " A creator" << endl;
        }
        ~A(){
            cout << " A delete" << endl;
        }
    };
    
    class B : public A{
    public:
        B(){
            cout << " B creator" << endl;
        }
        ~B(){
            cout << " B delete" << endl;
        }
    };
    
    int main() {
        B b;
        return 0;
    }
    

    运行结果:

     A creator
     B creator
     B delete
     A delete
    

七、函数

(一)、函数重载

  1. c++的类中只有重载(overload)没有覆写(overwrite)

  2. 当子类有父类的同名函数,父类的同名参数(包括重载的)将全部被屏蔽

    代码

    #include <iostream>
    #include <stdio.h>
    #include "TicketMachine.h"  //在main函数中以双引号引用
    
    using namespace std;
    
    class A{
    public:
        A(){
            cout << " A creator" << endl;
        }
        void print(){
            cout << "A print" << endl;
        }
        void print(int i){
            cout << "A print(i)" << endl;
        }
        ~A(){
            cout << " A delete" << endl;
        }
    };
    
    class B : public A{
    public:
        B(){
            cout << " B creator" << endl;
        }
        void print(){
            cout << "B print" << endl;
        }
        ~B(){
            cout << " B delete" << endl;
        }
    };
    
    int main() {
        A a;
        B b;
        a.print();
        a.print(1);
        b.print();
        b.print(1);  //错误
        return 0;
    }
    

(二)、函数默认参数

  1. 默认参数值:只有右边的参数可以省略掉

    格式:

    • TIcketMachine.h

      //
      // Created by Arno_vc on 2022/3/1.
      //
      
      #ifndef TICKETMACHINE_TICKETMACHINE_H
      #define TICKETMACHINE_TICKETMACHINE_H
      
      #include "Account.h"
      
      class TicketMachine {
      private:
          int balance;
          int total;
      public:
          TicketMachine();
          void setBalance(int balance,int total = 9);  //default argument尽在.h文件添加
          int getBalance();
          void show();
          ~TicketMachine();
      };
      
      
      #endif //TICKETMACHINE_TICKETMACHINE_H
      
      
    • TicketMachine.cpp

      //
      // Created by Arno_vc on 2022/3/1.
      //
      
      #include "TicketMachine.h"
      
      #include <stdio.h>
      #include <iostream>
      
      using namespace std;
      
      TicketMachine::TicketMachine() {}
      
      void TicketMachine::setBalance(int balance,int total) {
          this->balance = balance; 
          this->total = total;
      }
      
      int TicketMachine::getBalance() {
          return this->balance;
      }
      
      void TicketMachine::show(){
          cout << "balance:" << balance << " total:" << total << endl;
      };
      
      TicketMachine::~TicketMachine() {
      }
      
    • main.cpp

      #include <iostream>
      #include <stdio.h>
      #include "TicketMachine.h"
      
      using namespace std;
      
      void f(int i,int j=10){};
      
      int main() {
          TicketMachine ticketMachine;
          ticketMachine.setBalance(1);
          ticketMachine.show();
          return 0;
      }
      
  2. default argument是在编译时起作用,而不是运行时

    • a.h

      void f(int i,int j=0);
      
    • a.cpp

      #include "a.h"
      
      #include <iostream>
      using namespace std;
      
      void f(int i,int j){
          cout << i << " " << j << endl;
      }
      
    • main.cpp

      void f(int i,int j=10);
      
      int main(){
          f(10);
          for(;;);
          return 0;
      }
      
    • 结果:10 10

    • 说明:

      • 若默认参数是多了重载函数:则这些函数的第二个参数必然为0,因为main.cpp与a.cpp仅在链接步骤出现关联,而链接步骤只做了链接.
      • 默认参数显然只能在编译时补上:由上显然只是一个函数然后适时补上了参数,都运行了还怎么补,那显然只能在编译时补(具体函数调用过程见下)
  3. 少用default value,以免阅读障碍等问题

(三)、内联函数

  1. 作用:在编译时将对应的函数嵌入在源代码中(汇编代码阶段,以空间换时间)

  2. 优点

    • 自身优点:避免了原函数需要的大量堆栈操作
    • 相较于宏定义:编译器无法对宏定义做类型检查,但可以对内联函数做检查
  3. 缺点:当函数代码过长或者使用了递归,编译器不会使用inline

  4. 调用格式:与default arguments不同,内联函数inline只需要在.h文件

    • 使用内联

      • main.cpp

        #include "a.h"
        
        int main(){
        	A a;
            a.f(1,2);
        	for(;;);
        	return 0;
        }
        
      • a.h

        class A{
        public:
            inline int f(int i,int j=0);  ////inline function definition
        };
        
        
        int inline A::f(int i,int j){  //inline function definition
            return i+j;
        }
        
      • 观察 汇编文件main.s,共49行

    • 不使用内联

      • main.cpp

        #include "a.h"
        
        int main(){
        	A a;
            a.f(1,2);
        	for(;;);
        	return 0;
        }
        
      • a.h

        class A{
        public:
            int f(int i,int j=0);  ////inline function definition
        };
        
      • a.cpp

        #include "a.h"
        
        int A::f(int i,int j){  //inline function definition
            return i+j;
        }
        
      • 观察汇编文件main.s,a.s,共27+24行(对于循环的调用应该效果会更明显吧)

  5. 类声明中的函数都是inline的,但通常更喜欢将需要inline的函数定义放在类外

八、const关键字

  1. extern const int bufsize

    • 外部有一个变量bufsize,这个bufsize是不是const无所谓
    • 在我这里不能修改
  2. 几种常见使用错误

    • const对应的变量为什么无法用来初始化数组

      #include <iostream>
      
      using namespace std;
      
      int main(){
          int x;
          cin >> x;
          const int y;
          int arr[y];
          return 0;
      }
      

      原因:函数的局部变量包括数组是存放在堆栈中的,在编译时由编译器分配对应的内存空间。当y是变量时,显然编译器无法分配数组需要的栈空间报错。

    • const变量的地址无法赋值给普通指针

      #include <iostream>
      
      using namespace std;
      
      int main(){
          int const x=3;
          int *p;
          *p=&x;
          return 0;
      }
      

      原因:x认为不能修改,但p认为可以修改,两者地位平等,存在逻辑冲突

  3. 指针用法

    • char * const q = "abc":指针无法修改
    • const char *p = "abc":无法通过改指针修改内容

    注:const在*后,对象是const;const在*后,指针是const

  4. 变量存放的三个区域:

    • 全局变量:全局数据区(其中常量在代码段中)
    • 本地变量:堆栈(由编译器分配)
    • 动态分配的内存:堆
  5. const char *s = "Hello world!"char s[] = "Hello world!"

    • main.cpp

      #include <iostream>
      #include <stdio.h>
      //#include "TicketMachine.h"
      
      using namespace std;
      
      int main() {
          const char* s1 = "Hello world!";  //指向代码段
          char s2[] = "Hello world!";       //指向堆栈
          printf("main:%p\n",main);
          printf("s1  :%p\n",s1);
          printf("s2  :%p\n",s2);
          return 0;
      }
      
    • 结果

      main:00007ff64e541731
      s1  :00007ff64e54b001
      s2  :000000c3957ff94b
      
    • 结论

      • 前者仅仅是将指针指向代码段(无法修改)
      • 后者是在堆栈中分配了内存空间并赋值
  6. 函数参数为什么要传const指针

    原因:

    • 传入对象要在对栈中开辟一个临时的数据空间,并放入数据
    • 传入指针可能会被修改,不安全\

    结论:

    • 传入const指针既能方便传入对象,又能保证不被修改
  7. const对象:对象中的值都是无法修改的 => 在函数后加const保证对象中的函数不会修改成员变量(声明和定义后都要加const,实际上表示this要加const)

    • TIcketMachine.h

      //
      // Created by Arno_vc on 2022/3/1.
      //
      #ifndef TICKETMACHINE_TICKETMACHINE_H
      #define TICKETMACHINE_TICKETMACHINE_H
      
      #include "Account.h"
      
      void f(int i,int j=0);
      
      class TicketMachine {
      private:
          int balance;
          int total;
      public:
          TicketMachine();
          int getBalance() const;  //对函数声明进行const
          void show();
          ~TicketMachine();
      };
      
      
      #endif //TICKETMACHINE_TICKETMACHINE_H
      
    • TicketMachine.cpp

      //
      // Created by Arno_vc on 2022/3/1.
      //
      
      #include "TicketMachine.h"
      
      #include <stdio.h>
      #include <iostream>  //在cpp下加额外需要用的头文件
      
      using namespace std;  //需要加标准命名空间
      
      //void f(int i,int j){
      //    cout << i << " " << j << endl;
      //}
      
      TicketMachine::TicketMachine() {}
      
      int TicketMachine::getBalance() const { //对函数定义加const
          return this->balance;
      }
      
      void TicketMachine::show(){
          cout << "balance:" << balance << " total:" << total << endl;
      };
      
      TicketMachine::~TicketMachine() {
      }
      
    • main.cpp

      #include <iostream>
      #include <stdio.h>
      #include "TicketMachine.h"
      
      using namespace std;
      
      int main() {
          const TicketMachine ticketMachine;  //const对象
          cout << ticketMachine.getBalance() << endl;  //可以运行
          ticketMachine.show();  //由于我们在声明ticketMachine时是const, 而show()不是const函数(编译器认为它不一定是安全的),故无法调用
          return 0;
      }
      
  8. const构成重载

    void f();        //A* this
    void f() const;  //const A* this
    

九、引用

  1. 基本定义:引用可以看做是别名

    本质:用const *实现

  2. 与const

    const int& z=x;  //z的应用对象本身不能改变的,这里const表示不能通过z修改x(类比*)
    
  3. 与*

    • int *& p;:一个int *类型的引用
    • int &* p:非法
  4. 使用:函数参数

    void f(int& x);  //注意这个是函数声明,以往见到的多是调用中加&符号
    
  5. 无法做函数重载

  6. 没有reference的reference

    int x;
    int &a=x;
    int &b=a;  //error
    

十、virtual关键字

  1. 使用virtual关键字的类第一个int字节不再是普通的属性,而是指针,指向vtable(虚拟函数表)

    • main.cpp

      #include <iostream>
      #include <stdio.h>
      
      using namespace std;
      
      class A{
      public:
          int i;
      public:
          A():i(10){};
          virtual void f(){
              cout << "A" << endl;
          }
      };
      
      typedef long long ll;
      
      int main() {  //int 4,long 4,long long 8
          A a;
          A b;
          ll *p = (ll*) &a;
          cout << *(ll*)p << " i:" << *(int *)(++p) << endl;  //8个字节*8=64位地址
          p = (ll *) &b;
          b.i=20;
          cout << *(ll*)p << " i:" << *(int *)(++p) << endl;
          ll *temp = (ll*)&b;
          cout << *(ll*)(*temp) << endl;  //vtable地址
          return 0;
      }
      
      
    • 结果

      140695808005440 i:10
      140695808005440 i:20
      140695807996512
      
    • 结论:在使用virtual情况下,同一个类的首地址指向vtable(虚拟函数表)

  2. 子类与父类的vtable位置不同

    • main.cpp

      #include <iostream>
      #include <stdio.h>
      
      using namespace std;
      
      class A{
      public:
          int i;
      public:
          A():i(10){};
          virtual void f(){
              cout << "A" << endl;
          }
      };
      
      class B:public A{
      public:
          virtual void f(){
              cout << "B" << endl;
          }
      };
      
      typedef long long ll;
      
      int main() {  //int 4,long 4,long long 8
          A a;
          B b;
          ll *p = (ll*) &a;
          cout << *(ll*)p << " i:" << *(int *)(++p) << endl;  //8个字节*8=64位地址
          p = (ll *) &b;
          b.i=20;
          cout << *(ll*)p << " i:" << *(int *)(++p) << endl;
          return 0;
      }
      
    • 结果

      140696722494832 i:10
      140696722494864 i:20
      
    • 结论:父子类的vtable地址不同(哪怕父类没有覆写过虚函数)

  3. 类的赋值与指针的赋值

    • main.cpp

      #include <iostream>
      #include <stdio.h>
      
      using namespace std;
      
      class A{
      public:
          int i;
      public:
          A():i(10){};
          virtual void f(){
              cout << "A" << endl;
          }
      };
      
      class B:public A{
      public:
          virtual void f(){
              cout << "B" << endl;
          }
      };
      
      typedef long long ll;
      
      int main() {  //int 4,long 4,long long 8
          B b;
          A a1 = b;  //赋值
          a1.f();
          A *a2 = &b;  //指针
          a2->f();
          return 0;
      }
      
    • 结果

      A
      B
      
    • 结论:不言自明

  4. 如果一个类里面有一个析构函数,那么析构函数也应该是virtual的

    • main.c

      #include <iostream>
      #include <stdio.h>
      
      using namespace std;
      
      class A{
      public:
          int i;
      public:
          A():i(10){};
          virtual void f(){
              cout << "A" << endl;
          }
          ~A(){
              cout << "delete A" << endl;
          }
      };
      
      class B:public A{
      public:
          virtual void f(){
              cout << "B" << endl;
          }
          ~B(){
              cout << "delete B" << endl;
          }
      };
      
      typedef long long ll;
      
      int main() {  //int 4,long 4,long long 8
          A *a = (A *)new B;
          delete a;
          return 0;
      }
      
    • 结果

      delete A
      
    • 结论:由于普通函数的静态链接导致这里调用的A类的析构函数(而不是本身的B类)

    • main.c修改为virtual函数

      #include <iostream>
      #include <stdio.h>
      
      using namespace std;
      
      class A{
      public:
          int i;
      public:
          A():i(10){};
          virtual void f(){
              cout << "A" << endl;
          }
          virtual ~A(){
              cout << "delete A" << endl;
          }
      };
      
      class B:public A{
      public:
          virtual void f(){
              cout << "B" << endl;
          }
          virtual ~B(){
              cout << "delete B" << endl;
          }
      };
      
      typedef long long ll;
      
      int main() {  //int 4,long 4,long long 8
          A *a = (A *)new B;
          delete a;
          return 0;
      }
      
    • 结果

      delete B
      delete A
      
    • 结论:成功自下而上调用析构函数

  5. 通过FatherClass::func()调用父类函数

    • main.c

      #include <iostream>
      #include <stdio.h>
      
      using namespace std;
      
      class A{
      public:
          int i;
      public:
          A():i(10){};
          virtual void f(){
              cout << "A" << endl;
          }
          virtual ~A(){
              cout << "delete A" << endl;
          }
      };
      
      class B:public A{
      public:
          virtual void f(){
              A::f();
          }
          virtual ~B(){
              cout << "delete B" << endl;
          }
      };
      
      typedef long long ll;
      
      int main() {
          A *a = (A *)new B;
          a->f();
          return 0;
      }
      
    • 结果

      A
      
    • 结论:子类用父类民调用父类函数

  6. 通过virtual实现override

    • main.cpp

      #include <iostream>
      #include <stdio.h>
      
      using namespace std;
      
      class A{
      public:
          int i;
      public:
          A():i(10){};
          virtual void f(){
              cout << "A" << endl;
          }
          virtual void f(int para){
              cout << "A " << para << endl;
          }
          virtual ~A(){
              cout << "delete A" << endl;
          }
      };
      
      class B:public A{
      public:
          virtual void f(){  //必须全部用virtual
              cout << "B" << endl;
          }
          virtual void f(int para){  //必须全部用virtual
              cout << "B " << para << endl;
          }
          virtual ~B(){
              cout << "delete B" << endl;
          }
      };
      
      typedef long long ll;
      
      int main() {
          A *a = (A *)new B;
          a->f();
          return 0;
      }
      
    • 结果

      B
      

十一、拷贝构造

  1. 特点

    • 没有调用普通构造函数(与普通的new比较)
    • 发生的不是字节到字节的拷贝,而是member到member的拷贝
  2. 基本格式:T::T(const T&); //由c++给出,可以自己覆盖

    • 函数参数

      • main.cpp

        #include <iostream>
        #include <stdio.h>
        
        using namespace std;
        
        int objectNum=0;
        
        class A{
        public:
            A(){
                objectNum++;
                cout << "objectNum: " << objectNum << endl;
            }
            ~A(){
                objectNum--;
                cout << "objectNum: " << objectNum << endl;
            }
        };
        
        A f(A a){
            cout << "in function, objectNum:" << objectNum << endl;
            return a;
        }
        
        int main() {
            A a;
            cout << "before function" << endl;
            A b = f(a);
            cout << "after function" << endl;
            return 0;
        }
        
      • 结果

        objectNum: 1
        before function
        in function, objectNum:1
        objectNum: 0
        objectNum: -1
        after function
        objectNum: -2
        
      • 结论:f中的参数和返回值所使用的都没有走构造函数,而是通过其它方式(拷贝构造)

    • 基本使用

      • main.cpp

        #include <iostream>
        #include <stdio.h>
        
        using namespace std;
        
        int objectNum=0;
        
        class A{
        public:
            A(int temp){
                objectNum++;
                cout << "objectNum: " << objectNum << endl;
            }
            ~A(){
                objectNum--;
                printf("%p ",this);
                cout << "objectNum: " << objectNum << endl;
            }
        };
        
        int main() {
            A a(1);
            A b = 2;
            return 0;
        }
        
      • 结果

        objectNum: 1
        objectNum: 2
        000000d4e11ff84e objectNum: 1
        000000d4e11ff84f objectNum: 0
        
      • 结论:A a = 1; //等效与A a(2)

    • 覆写

      • main.cpp

        #include <iostream>
        #include <stdio.h>
        
        using namespace std;
        
        int objectNum=0;
        
        class A{
        public:
            A(){
                objectNum++;
                cout << "objectNum: " << objectNum << endl;
            }
            A(const A &b){
                objectNum++;
                //A *temp = &b;
                printf("&b       :%p  this:%p\n",&b,this);  //被拷贝的对象地址,原对象地址
                cout << "objectNum: " << objectNum << endl;
            }
            ~A(){
                objectNum--;
                printf("%p ",this);
                cout << "objectNum: " << objectNum << endl;
            }
        };
        
        A f(A a){
            printf("para a   :%p\n",&a);   //参数a
            cout << "in function, objectNum:" << objectNum << endl;
            return a;
        }
        
        int main() {
            A a;
            printf("initial a:%p\n",&a);  //初始a
            cout << "before function" << endl;
            A b = f(a);  //但是f(a)也会是相同的效果
            printf("b:%p\n",&b);
            return 0;
        }
        
      • 结果

        objectNum: 1
        initial a:00000052915ff86e
        before function
        &b       :00000052915ff86e  this:00000052915ff86f  // a=>para a
        objectNum: 2
        para a   :00000052915ff86f
        in function, objectNum:2
        &b       :00000052915ff86f  this:00000052915ff86d  // para a => b
        objectNum: 3
        00000052915ff86f objectNum: 2
        b:00000052915ff86d
        00000052915ff86d objectNum: 1
        00000052915ff86e objectNum: 0
        

    问题:f(a)是一个拷贝构造,那么A b=f(a)为什么也只有一个拷贝构造?

    • main.cpp

      #include <iostream>
      #include <stdio.h>
      
      using namespace std;
      
      class A{
      public:
          A(){
      
          }
      
          A(const A &b){
              printf("&b       :%p  this:%p\n",&b,this);  //被拷贝的对象地址,原对象地址
          }
      };
      
      A f(A a){
          return a;
      }
      
      int main() {
          A a;
          f(a);
          cout << "--------------" << endl;
          A c = f(a);
          printf("c:%p",&c);
          return 0;
      }
      
    • 结果

      &b       :0000007e19fff90c  this:0000007e19fff90e
      &b       :0000007e19fff90e  this:0000007e19fff90d
      --------------
      &b       :0000007e19fff90c  this:0000007e19fff90f
      &b       :0000007e19fff90f  this:0000007e19fff90b
      c:0000007e19fff90b
      
    • 结论:根据翁凯老师的讲解,这里编译器做了个优化(即要不要return回一个临时对象)

      • 原形式

        f(){
            A b;
            return b;
        }
        
        Person @temp = f(a);
        A c = @temp;
        
      • 编译器优化后的形式

        f(){
            A b;
            return b;
        }
        
        A c = b;  //优化了一次拷贝构造
        
      • main.cpp验证

        #include <iostream>
        #include <stdio.h>
        
        using namespace std;
        
        class A{
        public:
            A(int t){
        
            }
            void show(){
                cout << "show" << endl;
            }
            A(const A &b){
                printf("&b       :%p  this:%p\n",&b,this);  //被拷贝的对象地址,原对象地址
            }
        };
        
        A f(int t){
            A a(t);  //非拷贝构造
            printf("a:%p\n",&a);
            return a;
        }
        
        int main() {
            A c = f(2);  //没有发生拷贝构造
            printf("c:%p",&c);
            return 0;
        }
        
      • 结果

        a:00000043cabff98f
        c:00000043cabff98f
        
      • 结论:没有使用拷贝构造(被优化了)

  3. 调用拷贝构造的几种情况

    • 对象作为函数的参数传入
    • 用一个对象初始化另一个对象
  4. 拷贝构造对指针属性的影响:将指针指向了同一个位置 => 然而许多时候我们并不希望这样

    • 注意到c++中的string有拷贝构造,而c中的char *没有,因此更推荐用string
    • 一般情况下建议自己写拷贝构造
  5. 禁止拷贝构造:将拷贝构造函数private

  6. 拷贝构造与初始化的不同

    • 拷贝构造:调用拷贝构造函数
    • 初始化:调用赋值运算符

十二、static关键字

  1. static在c中的使用

    • 本地变量(hidden):将变量(函数)限制在当前文件使用
    • 持久化存储(persistent):而不是在某个函数调用后就收回
  2. static在c++中的使用

    • 修饰类:存储是全局的(链接时分配),但初始化依然是在函数内部,只初始化一次
    • 修饰属性:属于类的静态成员属性和静态成员函数(这里表现的是其persistent特性,他的hidden特性由private替代了)
  3. 类中的static属性的初始化

    • 需要在.cpp中初始化

      • main.cpp

        #include <iostream>
        #include <stdio.h>
        
        using namespace std;
        
        class A{
        public:
            static int i;  //.h:声明
        };
        
        int A::i;  //.cpp:定义,不能在前面添加static,因为static属性可以在.cpp外文件访问
        
        int main() {
            cout << A::i << endl;
            return 0;
        }
        
      • 结果

        0
        
    • 不能通过初始化参数列表初始化

    • 在内部通过this访问

    • 在外部通过ClassName::field访问

  4. 静态成员函数调用静态参数

十三、运算符重载

  1. 限制

    • 只能对已有运算符重载
    • 重载必须对一个类或枚举类型
    • 重载必须保持原来的参数个数与优先级
  2. 通过operator关键字重载

  3. 运算符重载类型

    • 全局函数:完整写出所有参数

    • 成员函数:使用reciver作为实际调用运算符重载函数的对象(在全局的重载函数中是第一个参数)

      a + b为例,实际可以写作a.operator+(b),故reciver是左边的a

      应该选择作为成员函数的情况:

      • 单目运算符
      • 常见的如:=,(),->,->*
  4. 基本概念

    • return value

      • 修改当前对象
      • 返回新对象
        • 新对象可以被修改
        • 新对象不能被修改
    • 常见操作符原型

      • +-*/%^&|~

        const T operatorX(const T& l,const T& r) const;
        
      • ! && || < <= == >= >

        bool operatorX(const T& l,const T& r) const;
        
      • []

        T& T::operator[](int index);
        
      • ++ --

        const Integer& operator++();  //原值+1
        const Integer operator++(int);  //postfix,返回新的同值对象,原值+1
        const Integer& operator--();
        const Integer operator--(int);  //postfix
        

        注:通过int,编译器能够识别那个是prefix哪个是postfix

      • =

        T& T::operator=(const T& rhs){
        	if(this!=&rhs){
                //perform assignment
            }
            return *this;
        }
        
      • 强制转换

        • 通过构造函数转换

          • 隐式转换

            main.cpp

            #include <iostream>
            #include <stdio.h>
            
            using namespace std;
            
            class One{
            public:
                One(){};
            };
            
            class Two{
            public:
                Two(const One&){}
            };
            
            void f(Two t){
            
            }
            
            int main() {
                One one;
                f(one);  //隐式转换:给的是One,但是能自动转换为Tow
                return 0;
            }
            
          • 显示转换

            main.cpp

            #include <iostream>
            #include <stdio.h>
            
            using namespace std;
            
            class One{
            public:
                One(){};
            };
            
            class Two{
            public:
                explicit Two(const One&){}  //加入关键字构成显示转换
            };
            
            void f(Two t){
            
            }
            
            int main() {
                One one;
                f(Two(one));  //显示转换:此时编译无法通过
                return 0;
            }
            
        • 使用强制转换(少用)

          X::operator double() const;  //没有返回类型,因为double已经说明了将X对象转变为T
          

十四、模板

  1. 场景:不同类型的数据需要用到相同的代码段

    选择:

    • 使用基类
    • 代码克隆(不好管理)
    • 无类型列表(void *)
  2. 函数模板

    • main.cpp

      #include <iostream>
      
      using namespace std;
      
      //声明而非定义
      template <typename T>
      void Swap(T &a,T &b){  //注意不要用stdio.h中的swap
          T temp=a;
          a=b;
          b=temp;
          cout << "swap over" << endl;
      }
      
      int main() {
          int a=1,b=2;
          float c=1.0,d=2.0;
          cout << "a:" << a << " b:" << b << endl;
          Swap(a,b);
          cout << "a:" << a << " b:" << b << endl;
          cout << "c:" << c << " d:" << d << endl;
          Swap(c,d);
          cout << "c:" << c << " d:" << d << endl;
          return 0;
      }
      
    • 结果

      a:1 b:2
      swap over
      a:2 b:1
      c:1 d:2
      swap over
      c:2 d:1
      
    • 本质

      • 读取模板函数声明(有些在.h文件)
      • 确定所需要的模板函数参数类型
      • 生成函数
  3. 类模板:类模板里面的每个函数都是函数模板

    使用:

    • main.cpp

      #include <iostream>
      #include "TicketMachine.h"
      
      using namespace std;
      
      int main() {
          TicketMachine<int> t;  //放入int
          int a=1,b=2;
          cout << "a:" << a << " b:" << b << endl;
          t.swap(a,b);
          cout << "a:" << a << " b:" << b << endl;
          return 0;
      }
      
    • TicketMachine.h

      //
      // Created by Arno_vc on 2022/3/1.
      //
      
      #ifndef TICKETMACHINE_TICKETMACHINE_H
      #define TICKETMACHINE_TICKETMACHINE_H
      
      #include <iostream>
      
      using namespace std;
      
      //声明而非定义
      
      template <class T>
      class TicketMachine {
      public:
          void swap(T& a,T& b);
      };
      
      template <class T>  //.cpp这里也要加templete
      void TicketMachine<T>::swap(T &a,T &b){  //类名后需要加T
          T temp=a;
          a=b;
          b=temp;
          cout << "swap over" << endl;
      }
      
      
      #endif //TICKETMACHINE_TICKETMACHINE_H
      
    • 结果

      a:1 b:2
      swap over
      a:2 b:1
      

    注:

    • 模板类中函数的声明与定义都应该写在.h文件,否则链接错误(从编译角度可以理解,因为倘若放在另一个模块那是无法直接调用,必须将对应的函数定义放在同一个模块来真正构造函数)
    • 类模板可以有默认参数
    • 类模板可以继承

十五、异常

  1. 场景:不可控因素,例如读取一个文件,而那个文件实际不存在

  2. 优点:将业务逻辑和错误处理逻辑分开

  3. 使用

    • 抛出异常,中断并上溯:throw VectorIndexError(index);

      注:

      • 这个对象不是new出来的,故不在堆栈,而是在堆里

      • throw;会直接将catch到的异常继续上抛

      • new关键字无法申请到内存,则抛出bad_alloc异常

    • 捕获异常

      try {...}
      	catch ...
          catch ...
      

      注:

      • 参数中的...表示捕获所有异常

      • 匹配顺序(自上而下)

        • exact
        • base
        • ellipse(...)
      • 通过在函数定义后添加操作表示最多能抛出的异常

        void abc(int a):throw(MathErr){  //本人最多只能抛出MathErr,如果抛出其它则会被unexpected exception替代
            ...
        }
        
      • 异常传递:建议用引用

      • 构造函数丢异常也可能造成内存泄漏

        class A{
        public:
            A(){
                buf = new char[1024];
                delete this;
            }
            ~A(){
                delete buf;
            }
        }
        
        int notmain(){
            A a;
            A* p = new A();  //整个函数(以notmain函数为例)在此结束,无法通过delete p收回char[1024]的空间
            delete p;
            return 0;
        }
        

十六、流

  1. 特点:一维单方向

    • 缺点
      • 比较慢
      • 比较繁琐(内部)
    • 优点
      • 开发效率较高

    注:与之相对的fscanf等接口是随机访问的

  2. 二进制文件:不以人的阅读为目的的文件(因为从某个角度来说,所有的文件都是二进制的)

  3. c++中的流对象

    • cin
    • cout
    • cerr:错误
    • clog:日志
  4. 操纵器(manipulate):操纵器,例如:endl,flush

十七、STL模板

  1. 主要内容

    • 数据结构

      常用:

      • Vector
      • Deque:expands at both end
      • List:double-linked
      • sets and maps

      特点:

      • 自动增长
      • 建议用!=.end()判断是不是走到头了
    • 算法(函数模板)

      常用:

      • sort
      • search:二分
  2. 通用的iterator


  1. 运算符通常是有结果的

  2. 可以对对象进行赋值(虽然看起来毫无意义)

    #include <iostream>
    #include <stdio.h>
    #include "TicketMachine.h"  //在main函数中以双引号引用
    
    using namespace std;
    
    class B{
    public:
        B(int i){
    
        }
    };
    
    int main() {
        B b(1);  //相对于B *b,B b申请了内存空间
        b=0;
        return 0;
    }
    
  3. 尽量将代码建筑在已有的代码基础上

posted @ 2022-03-10 09:41  Arno_vc  阅读(77)  评论(0编辑  收藏  举报