C++复习

C++谭版期末复习

前言

好好复习,简单看看语法

从C到C++

C++的输入输出

  • cin cout cerr clog

const定义常变量

函数

  • 函数声明
  • 函数重载(函数参数的个数或类型不同)
  • 函数模板
    • template
    • T的类型也要一致
  • 带默认参数的函数
  • 内联函数 inline

变量的引用

  • NOTES
    • 一定要初始化
    • 从一而终
    • 只能引用变量
    • 不能建立void类型的引用 void &a = 1
    • 不能建立引用的数组 char &a[3] = "asd"
    • const int &p = (可以是常量或表达式)
  • 应用
    • 作为函数参数

作用域运算符 ::

字符串变量 string

  • 常用运算
    • +合并
    • = 赋值(覆盖)
    • == != > < >= <= 比较
    • s[i] 访问
  • 字符串数组 string st[4]

动态内存分配/撤销

  • 分配 new 类型 [初值]
    • 分配一个变量 int *t = new int(1)
    • 分配一个数组 int *s = new int[5]
    • 返回类型 指针
  • 撤销 delete[]指针变量
    • 变量 delete t
    • 数组 delete[] s

类和对象的特性

概述

  • 面向对象的特点:封装 继承 多态
  • 类是对象的抽象而对象则是类的具体表现形式
  • 多态:由继承而产生的相关的不同的类,其对象对同一消息会作出不同的晌应。

类的声明和对象的定义

  • 类的声明
    class lins {
       public:
       private:
       protected:
    }
    
  • 定义对象
    1. lins ls1;
    2. class lins ls2;
    3. class lins{} ls3;
    4. class {} ls4;
    
  • class与struct区别
    • 若不用publicprivate限制,class默认private,struct默认public

类的成员函数

  • 类外定义 void lins::display(){}
  • 内置成员函数 inline void lins::display(){}
  • 成员函数的存储方式
    • 同一类的不同对象,共用成员函数,不会跟成员数据一样开辟新的空间存储,且函数通过this指针来区别不同的对象

对象成员的引用

  • 通过对象名和成员运算符访问对象中的成员

    • ls.a
    • ls.display()
  • 通过指向对象的指针访问对象中的成员

    class lins ls, *p;
    p = &ls;
    p->display(); 
    
  • 通过对象的引用访问对象中的成员

    class lins ls;
    lins &p = &ls;
    p.display(); 
    

类的封装性和信息隐蔽

  • 公用接口与私有实现的分离
  • 类声明和成员函数定义的分离

类和对象的使用

构造函数

  • 对象初始化
    • 类里面的成员变量是不能直接赋初值的,因为类是一种抽象类型,没有分配存储空间。
    • 所以初始化需在创建对象的时候lins ls1={1,2} //表示给lins中两个成员赋了值,同等对象的赋值
  • 利用构造函数初始化
    class lins {
      public:
      lins();
      lins(int, int); //带参数,且可重载
      lins(int, int = 1); //带默认参数
      lins(int a, int b): aa(a), bb(b){}; //带参数表
      int a;
    }
    lins::lins(){ a = 0; } //构造函数
    //对于有指针的类,一定要在构造函数内初始化???
    //如果不初始化指针的指向是随机的,出现了野指针,不安全,也不利于非空判断
    
    NOTES:
      lins l1;//调用构造函数 
      lins l2 = l1;//复制l1的值不会调用构造函数
    
  • 带参数的构造函数
    • 带参数的构造函数中的形参,其对应的实参是在建立对象时给定的,如:lins l1(1,2)
  • 带参数表的构造函数
    • 如:lins::lins(int a, int b): aa(a), bb(b){}
    • 如果有数组成员,必须在{}内,不能通过参数表

析构函数

  • 四种情况
    • 局部对象、全局对象:在其作用域结束时调用析构
    • 静态(static)对象:在main结束或调用exit时调用析构
    • new对象:调用delete时,先调用析构
  • 调用构造与析构的顺序
    • 先构造的后析构,后构造的先析构

对象数组-对象指针

  1. 对象数组
    注意二义性
    class lins{
     lins(int a,int b,int c):aa(a),bb(b),cc(c){};
     int aa, bb, cc;
    }
    
    lins ls1[3] = {1,2,3}; //一个数据给一个对象
    lins ls2[3] = {
     lins(1,1,1),
     lins(2,2,2),
     lins(3,3,3)
    }
    
  2. 对象指针
    • 指向对象的指针 lins *p
    • 指向对象成员的指针
      1. 指向对象数据成员
       int *p;
       p = &t.a;
       cout << *p << endl;
      
      2. 指向对象成员函数
       void(lins::*p)();
       p = &lins::fun;
       // p = &t.fun; 错误!因为函数地址是公用的,在对象外的空间
       (*p) (); //调用fun函数
      
    • 指向当前对象的this指针

共用数据的保护

  1. 常对象
    • const lins ls(); = lins const ls();
    • NOTES:
      • 在定义常对象时,必须同时对之初始化,之后不能再改变
      • 常对象只能调用它的常成员函数
      • 常成员函数可访问常对象中的数据成员,但不允许修改常对象中数据成员的值
      • mutable int a; 可修改
  2. 常对象成员
    • 常数据成员:只能通过构造函数的参数初始化表进行初始化
    • 常成员函数:void fun() const;
    • note:常成员函数不能调用另一个非const成员函数
  3. 指向对象的常指针
    • lins * const p; --指向不能变,值可以变
    • 不能指向常对象
  4. 对象的常指针
    • const lins *p; --指向对象的值不能变,指向可以变
    • 可以指向常对象
  5. NOTE:非const能变为const,反之不能
  6. 对象的常引用
    • void fun(const lins &ls);
    • 经常用常指针和常引用作函数参数

对象的动态建立和释放

  • lins *p = new lins; 创建一个lins对象,调用构造函数,返回一个地址给p
  • 用new建立的动态对象一般是不用对象名的,是通过指针访问的,它主要应用于动态的数据结构,如链表。
  • delete p; 释放内存空间之前自动调用析构函数。

对象的赋值和复制

  1. 赋值
    • lins l1,l2; ... l2 = l1;
    • 只对其同类的数据赋值,不对函数赋值
    • 数据成员中不能包括动态分配的数据,否则在赋值时可能出现严重后果
  2. 复制
    • 复制构造函数:也是构造函数,但它只有一个本类对象的参数。编译系统提供一个默认的复制构造函数。
    • lins::lins(const lins &l);
    • lins l2(l1); lins l2 = l1, l3 = l1; 等价,均复制了一个l1
    • 调用情况
      1. lins l1 = l2;
      
      2. void fun(lins lt){};
         fun(l1); //调用复制构造函数创建lt
      
      3. lins fun(){ return l1; } 
         lins l2 = fun();//返回时调用复制构造函数
      

静态成员

  1. 静态数据成员
    • static int a;
    • 静态数据成员不属于某一个对象,静态数据成员在对象之外单独开辟空间。
    • 程序编译时被分配空间的,到程序结束时才释放空间。
    • 静态数据成员可以初始化,但只能在类体外进行初始化。int lins::a = 1;
    • 如果未对静态数据成员赋初值则编译系统会自动赋予初值0。
    • 引用
      • 对象名 l1.a;
      • 类名 lins::a;
      • 私有静态数据成员不能在类外直接引用,必须通过公用的成员函数引用
    • 公用静态数据成员与全局变量的不同,静态数据成员的作用域只限于定义该类的作用域内
  2. 静态成员函数
    • static int fun();
    • 类外调用公用的静态成员函数,lins::fun();l.fun();
    • 主要用来访问静态数据成员而不访问非静态成员。无this指针,如要访问通过l.a;的方式。

友元

  1. 友元函数
    • 将普通函数声明为友元函数 friend void display(lins &);
    • 友元成员函数
      • 需要类的提前引用声明
      • friend void lins::display(lins &); 表明lins类中的display函数能访问本类中的成员
    • 可以被多个类声明为“朋友”
  2. 友元类
    • friend lins; 表明类lins中的所有成员都可以访问本类中的成员
    • 友元的关系是单向的而不是双向的
    • 友元的关系不能传递

类模板

  • 声明:template
  • 应用:类模板名 <参数类型> 对象名(参数表); = lins ls(1,2);
  • 成员函数在类外定义需加上模板声明
    template 
    class compare {
    public:
      T max();
    private:
      T x, y;
    };
    
    template 
    T compare::max(){
        return (x > y) ? x : y;
    }
    

运算符重载

重载方法

  • 实质:函数的重载
  • 函数类型 operator 运算符(形参) { 处理 }
  • 原理
    class lins{
      public:
       lins operator + (lins &);
    }
    lins lins::operator + (lins &c2) {};
    
    c3 = c1 + c2;
    c3 = c1.operator + (c2); //等价
    

重载运算符规则

  1. 仅能对已有运算符重载
  2. 不允许重载的(五个)
    • .*::sizeof? :
  3. 不能改变运算符运算对象(即操作数)的个数,结合性,优先级
  4. 重载函数不能有默认的参数
  5. 重载的运算符必须和类的对象一起使用,其参数至少有一个是类对象(或类对象的引用)
  6. 类的运算符一般都需要重载,但= &不用

类的运算符重载

  1. 重载函数作为类的成员函数
  2. 重载函数作为普通函数,在声明为友元函数
  • Note:对于双目运算符,(1)仅需一个参数,因为还一个this,而(2)需要两个参数
  • 习惯规定
    • 赋值运算符=、下标运算符[]、函数调用运算符()、成员运算符->必须作为成员函数
    • 流插入<<和流提取运算符>>、类型转换运算符不能定义为类的成员函数,只能为友元
    • 一般单目为成员函数,双目为友元函数(双目需要交换律)

重载双目

class lins{
  private:
    int x;
  public:
    friend bool operator > (lins &l1, lins &l2);

    //或者
    bool operator > (lins &l2) {
      return this.x > l2.x;
    }
}

bool operator > (lins &l1, lins &l2){
  return l1.x > l2.x;
}

重载单目

特殊++,--
1. ++i  前置
2. i++  后置 多一个int参数,本身无意义

class lins {
  private:
    int x;
  public:
    //前置
    lins operator ++ () {
      x++;
      return *this;
    }
    //后置
    lins operator ++ (int) {
      lins t(*this);
      x++;
      return t;
    }
}

重载<<>>

  • istream& operator >> (istream&,自定义类&);
  • ostream& operator << (ostream&,自定义类&);
  • Note:只能用友元的方式重载,成员函数方式会有this参数且是第一个参数。
    • 去掉自定义类参数后,不能正常使用cout << ls ;,因为第一个参数是this(ls),第二个参数是ostream(cout),顺序反了所以错误,只能通过成员函数ls.operator<<(cout);来使用。
    • 不去掉自定义类,参数太多,顺序也不对。
  • 总结:对于双目中那些需要需要满足交换律的运算符,必须用友元方式重载。

不同数据类型间的转换

  • 编译器自动完成的 -- 隐式转换
    • int a = 1.5 + 1;
  • 自己指定的 -- 显式转换
    • C:int a = (int)1.5;
    • C++:int a = int(1.5);
  • 转换构造函数(没什么意义)
    • lins(double r){real = r; imag = 0;}将double型的参数r转换成lins类的对象,将r作为复数的实部,虚部为0。
  • 类型转换函数
    • operator 类型名() {实现转换的语句}
    • operator int() {return a;}将对象转化为int型数据,值为a

继承与派生

概述

  • 继承:继承基类内容
  • 派生:派生新内容
  • 派生类的声明 class lins1: public lins{}
  • 组合
    • 子对象可以是基类对象,也可以是另一个类的对象
    • 组合:在一个类中以另一个类的对象作为数据成员
    • 继承是纵向的,组合是横向的

派生类的构成

  1. 从基类接收成员
    • 除构造,析构函数外,都会接受。
    • 很容易产生冗余(可通过虚函数解决)
  2. 调整从基类接收的成员
    • 改变访问属性
    • 创建同名函数来覆盖(注意不是重载)
  3. 在声明派生类时增加的成员
  • Note:派生类是抽象基类的具体实现

派生类成员的访问属性

  1. 公用继承 public
    • 基类的公有、保护成员在派生类中保持原有访问属性
    • 基类私有成员仍为基类私有
  2. 私有继承 private
    • 基类的公有、保护成员在派生类中成了私有成员
    • 基类私有成员仍为基类私有
  3. 受保护继承 protected
    • 基类的公有、保护成员在派生类中成了保护成员
    • 基类私有成员仍为基类私有
  • 保护成员:可以在当前类与派生类调用,不可在类外调用

派生类的构造函数和析构函数

  • 简单的派生类的构造函数

    • 调用构造函数:基类 -- 派生类
    • 形式:
      lins1(int n, string nam, int a, string ad):lins(n,nam){
       对lins1的剩余成员赋值 
      }
      
  • 有子对象的派生类的构造函数

    • 调用构造函数:基类 -- 子对象 -- 派生类
    • 形式:
      lins1(int n, string nam, int n1, string nam1, int a, string ad):lins(n,nam), monitor(n1,nam1){
       对lins1的剩余成员赋值 
      };
      lins monitor;//monitor为lins基类的一个对象,子对象:对象中的对象
      
    • 子对象:对象中的对象
  • 多层派生时的构造函数

    • 从基类往下调用构造函数
  • 派生类的析构函数

    • 在执行派生类的析构函数时,系统会自动调用基类的析构函数和子对象的析构函数,对基类和子对象进行清理。
    • 顺序:后构造的,先析构

多重继承

  • 声明方式:class D:public A, private B, protected C{类D新增加的成员}

  • 构造函数

    • D(总参数表):A构造(参数表),B构造(参数表), C构造(参数表) {};
    • 调用顺序:基类构造调用按照声明时的顺序
  • 二义性问题

    • 多个基类中有同名成员:添加::来明确
    • 多个基类和派生类有同名成员:会屏蔽基类中的成员,而执行派生类中成员
    • N->A N->B A,B->C:C中调用A或B中继承N的成员需要::来明确
  • 虚基类

    • 形式
      • class 派生类名: virtual 继承方式 基类名
      • class lins1: virtual public lins {}
    • 作用:在继承间接共同基类时只保留一份成员(解决第三类二义性问题)
    • 初始化:虚基类A的初始化由派生类D直接给出(避免冲突)
    • Note
      • 一般而言派生类只对直接基类初始化
      • 对于B,C对A的初始化会被忽略

基类与派生类的转换

长的可以赋值给短的,短的指针可以指向长的(短的只能接受/访问部分)

  • 派生类对象可以向基类对象赋值(只给基类的部分),反之不行
  • 派生类对象可以替代基类对象向基类对象的引用进行赋值或初始化
    A a;
    B b;
    A& r = a; //r与a共用一段存储单元
    A& r = b; //变为 r与b共用一段存储单元
    
  • 如果函数的参数是基类对象或基类对象的引用,相应的实参可以用派生类对象
  • 指向基类对象的指针变量可以指向派生类对象

多态性与虚函数

多态性

由继承而产生的相关的不同的类,其对象对同一消息会作出不同的晌应。

表现:具有不同功能的函数可以用同一个函数名,这 样就可以用一个函数名调用不同内容的函数。

分类:

  • 静态多态性 -- 编译时的多态 -- 通过函数重载实现
  • 动态多态性 -- 运行时的多态 -- 通过虚函数实现

利用虚函数实现多态性

作用:允许在派生类中重新定义与基类同名的函数并且可以通过基类指针或引用来访问基类和派生类中的同名函数

虚函数的使用

  • virtual void display();
  • 定义派生虚函数:在派生类中重新定义此函数,函数名、函数类型、函数参数个数和类型必须与基类的虚函数相同,根据派生类的需要重新定义函数体
  • 定义指向基类对象的指针变量:使它指向同一类族中需要调用该函数的对象。
  • 通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。

Note:使用虚函数,系统要有一定的空间开销。当一个类带有虚函数时,编译系统会为该类构造一个虚函数表(virtual function table,简称 vtable),它是一个指针数组,存放每个虚函数的人口地址。系统在进行动态关联时的时间开销是很少的,因此,多态性是高效的。

虚析构函数:

  • 普通析构函数只会调用本类的析构,不会调用基类的
  • 虚析构函数会先调用派生类的析构函数,然后调用基类析构
  • 当基类的析构函数为虚函数时,无论指针指的是同一类族中的哪一个类对象,系统都会采用动态关联,调用相应类的析构函数,对该对象进行清理工作。

纯虚函数与抽象类

纯虚函数

  • virtual void display() = 0;
  • 基类中将某一成员函数定为虚函数,并不是基类本身的要求,而是派生类的需要,在基类中预留了一个函数名,具体功能留给派生类根据需要去定义

抽象类

  • 本身不生成对象。只用作基类去建立派生类。
  • 凡是包含纯虚函数的类都是抽象类。因为纯虚函数是不能被调用的,包含纯虚函数的类是无法建立对象的。

输入输出流

C++的输入输出

  • 标准的输入输出 -- 标准I/O
  • 文件的输入输出 -- 文件I/O
  • 字符串输入输出 -- 串I/O

cin coutiostream 的对象

C++的流库

  • 基类:ios streambuf
  • 派生:
    • istream ostream iostream "iostream"
    • ifstream ofstream fstream "fstream"
    • 字符串派生

标准输出流

  1. cout cerr和clog流
  2. 标准类型数据的格式输出
  3. 用流成员函数put输出字符 cout.put("a");

标准输入流

  1. cin流
  2. 用于字符输入的流成员函数 cin.get(); cin.getline();
  3. istream 类的其他成员函数

文件流

文件可分为:ASCII码、二进制

文件流类与文件流对象

  • ifstream ofstream fstream
  • "fstream" 头文件

文件的打开与关闭

  1. 打开磁盘文件
    ofstream outfile; ///定义ofstream类(输出文件流类)对象outfile 
    outfile.open("f1.txt", ios::out); //使文件流与 f1.txt文件建立关联 
    
    open的一般形式: 
    文件流对象.open(磁盘文件名, 输入输出方式); 
    
    参数
    ios::in, ios::out, ios::out||ios::binary, ios::in||ios::binary
    
  2. 关闭磁盘文件 outfile.close();

对ASCII文件的操作

  • << >>
  • 文件流的put get getline 等成员函数
    int a[5];
    ofstream outfile;
    outfile.open("f1.txt", ios::out);
    for (int i = 0; i < 5; i++)
    {
     cin >> a[i];
     outfile << a[i] << " "; //通过 << 操作文件
    }
    outfile.close();
    
    int b[5];
    ifstream infile;
    infile.open("f1.txt", ios::in);
    for (int i = 0; i < 5; i++)
    {
     infile >> a[i]; //通过 >> 操作文件
     cout << b[i] << " ";
    }
    infile.close();
    

对二进制文件的操作

  • 用成员函数readwrite读写二进制文件
    • istream& read(char* buffer, int len);
    • ostream& write(const char* buffer, int len);
  • 与文件指针有关的流成员函数
  • 随机访问二进制数据文件

字符串流

C++工具

异常处理

所谓异常处理指的是对运行时出现的差错以及其他例外情况的处理。

方法:检查(try)、抛出(throw)和捕捉(catch)

  • try-catch块
    try{  //可能出现异常的、需要检查的语句或程序段
      被检查的语句 //含有或内部函数含有throw
    } 
    catch (异常信息类型 变量名){  //若无错误则不执行
      进行异常处理的语句
    }
    
  • try块发生异常,则throw语句抛出异常信息。执行throw语句后,流程立即离开本函数,转到其上一级的函数。在throw中抛出什么样的数据由程序设计者自定,可以是任何类型的数据(包括自定义类型的数据,如类对象)
  • 异常信息提供给try-catch结构,系统会寻找类型匹配的catch子句。比如:throw a(a为int型),有catch(int){},则匹配。
  • catch(...)表示可捕获任何类型的信息

在函数声明中进行异常情况指定

  • double triangle(double, double) throw(int, double);
  • 表示只能抛出int double型的异常信息

析构函数:若try块中定义了对象,而throw又抛出了异常,此时需要结束本层而返回,本层的对象也要被清理,执行析构函数,然后匹配到catch执行语句。

命名空间

由来:引用不同文件,如果不同的文件中有相同的名称,编译会出错。

命名空间:一个由程序设计者命名的内存区域。程序设计者可以根据需要指定一些有名字的空间域,把一些全局实体分别放在各个命名空间中,从而与其他全局实体分隔开来

关键字:namespace

namespace ls1 { //命名空间成员
  int a,
  double b;
  class lins{

  };
} //注意后面没有分号
ls1::a; //访问

使用命名空间解决名字冲突

使用命名空间成员的方法

  • ls1::a;
  • 别名:namespace ls = ls1; ls::a;
  • using
    • using ls1::lins;表明在using的作用域中使用ls1中lins的成员不需要加::
    • using namespace ls1;表明在using的作用域中使用ls1中的成员不需要加::

标准命名空间std

你可能感兴趣的:(C++复习)