C++细节注意点

痴心易碎 提交于 2020-02-25 15:44:44

文章目录


实现和声明的分离

源文件中引用同名头文件,头文件只声明,源文件中进行定义
比如一个game1.cpp #include一个"game1.h" 然后再在main.cpp中调用"game1.h" 便可以使用game1.cpp中定义的函数,这个便是实现和声明的分离

不使用 using namespace std;

用std::来替代

C++对C语言的增强

各种检测增强,比如类型转换增强
C++中使用struct 可以不需要再写struct
在这里插入图片描述
C语言中没有bool类型,C++中有
C++中三目运算符增强
a>b? a:b;
在这里插入图片描述
在这里插入图片描述
因为C++最后返回的是变量,上面最后一个操作是b=100
C++中const 是真正的常量不会分配内存,放在符号表中;
在这里插入图片描述
C++中用地址修改,只是在修改临时空间里的值
在这里插入图片描述
而C语言中const是个伪常量可以通过地址来修改,编译器会分配内存

重点1 引用

在这里插入图片描述
(1)引用基本语法 Type &别名=原名
(2) 引用必须初始化 引用初始化不能修改
(3)对数组建立引用

int arr[10];
//给数组起别名
int(&pArr)[10]=arr;
cout<<pArr[i]<<endl;

(4)引用传递

void mySwap3(int &a,int &b)
{
}
mySwap2(&a,&b);

引用的注意事项

1.引用必须引一块合法的内存空间
2.不要返回一个局部变量的引用

int &a=10;//引用必须引一块合法的内存空间 这样子是会报错的

注意如果一个函数的返回值是引用,那么这个函数调用可以作为左值
在这里插入图片描述

引用的本质

在这里插入图片描述

ref=100;//ref转换这一步是C++隐式完成的 

指针引用

在这里插入图片描述
利用指针开辟空间 相同的目的 但是不需要**这种操作
可以用一级指针引用可以代替二级指针
在这里插入图片描述

常量型引用

其实常量引用也是可以改的
在这里插入图片描述
常量引用场景1:
在这里插入图片描述
在这里插入图片描述

成员函数调用const修饰对象实例

在这里插入图片描述
在这里插入图片描述
要在函数后面加 ‘const’ 明确指出这个成员函数不会对const对象进行修改 不然常规的成员函数不能调用const对象 因为编译器不知道成员函数是否对其进行了修改。

构造和析构函数实例

构造和析构一定要在public下
没有参数就不能重载(析构不能重载)
在这里插入图片描述
注意当我们提供了有参的构造函数后,系统便不再提供默认构造函数 但是默认的拷贝构造和析构都还在

拷贝构造函数

之所以要用引用形式是因为 如果不用引用就会出现死循环 传值的时候又会调用拷贝构造
在这里插入图片描述
调用方式 第二种就是拷贝构造
在这里插入图片描述

拷贝构造函数的调用时机

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

深拷贝浅拷贝


class Person
{
    Person(){}
    Person(char * name, int age){
        //m_Name=name;//char * 放到堆上好点
        m_Name=(char *)molloc(strlen(name)+1);//堆上内容的析构 不能用默认 需要手动free
        strcpy(m_Name,name);
        m_age=age;
    }
    char * m_Name;
    int m_age;
    //拷贝构造如果用默认此时就会报错
    ~Person(){
        if(m_Name!=NULL)
        {
            free(m_Name);
            m_Name=NULL;//防止野指针
        }
    }
};

在这里插入图片描述
这样子调用拷贝构造就会报错
报错原因
在这里插入图片描述
在p1的mName被释放后 p2的mName也是那个地址 又一次被释放 ,但此时地址已经为空,因此报错。
这种简单地把地址复制过去的拷贝叫做浅拷贝

深拷贝

自己创建一个全新的空间,不能用系统默认的拷贝构造
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

初始化列表

在这里插入图片描述

或者在这里插入图片描述

类对象作为类成员的案例

在这里插入图片描述

explicit 关键字

class Mystring{
public:
    Mystring(const char *str)
    {

    }
    explicit  Mystring(int a)//这行关键字就是防止隐式类型转换的
    {
        msize=a;
    }
    char * mstr;
    int msize;
};
void test()
{
        Mystring str="mystring";
        Mystring str3(10);//这是对的 很明显在调用int类型 有参构造
        Mystring str2=12;//会引发歧义 代码用途不明确 str2字符串为‘10’,字符串长度为10 不知道调用的是哪个有参构造
        //会引发一个隐式类型转换
        // 此时加上一个 explicit 这行代码会报错
}

所谓隐式转换,是指不需要用户干预,编译器私下进行的类型转换行为。很多时候用户可能都不知道进行了哪些转换。
C++隐式转换的原则
基本数据类型 基本数据类型的转换以取值范围的作为转换基础(保证精度不丢失)。
隐式转换发生在从小->大的转换中。比如从char转换为int。
从int-》long。
自定义对象子类对象可以隐式的转换为父类对象。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
** s4和s5分别把一个int型和一个char型,隐式转换成了分配若干字节的空字符串**
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

new动态对象创建 完美代替malloc

原始malloc操作
在这里插入图片描述
存在的问题
在这里插入图片描述
一行解决

Person person= new Person;//自动调用构造函数

在这里插入图片描述

class Person2{
public:
    Person2()
    {}
    //拷贝构造
    ~Person2()
    {}

};
void test2(){
    //Person2 person; 栈区开辟
    Person2 *p2=new Person2;//堆区开辟 此时不会自动释放
    //所有new出来的对像都会返回该类型的指针
    //malloc不会自动调用构造 但这里new会自动调用构造 new是个运算符 malloc是一个函数
    //要释放堆区空间 需要delete
    delete p2;//delete也是一个运算符 要配合着一个new用 不要和free malloc弄混
};


void test3()
{
    void *p2=new Person2;//当用void *接受new出来的指针时会出现释放的问题
    delete p2;//将无法释放要避免这种写法

}

void test4()
{
    //通过new开辟数组 注意一定要提供默认构造函数 写了自己的构造之后 也要写一个默认构造函数
    Person2 * pArray=new Person2[10];
    //当栈上开辟数组 可以指定有参构造 堆区必须要有默认 栈上不用
    //Person2 pArray[10]={Person2(1),Person2(2)};
    delete [] pArray;//必须要加中括号 释放数组时
}

如果没有中括号 delete就不知道那个数组大小记录3 不知道该调用几次析构
在这里插入图片描述

静态成员变量

在这里插入图片描述

class Person2{
public:
    Person2()
    {}
    static int m_Age;//加入static就是静态成员变量 会共享数据
    
    //静态成员变量,在类内声明,类外进行初始化
    ~Person2()
    {}

private:
    static int m_other;//私有权限 在类外不能访问 但可以初始化

};
int Person2::m_Age=10;//类外初始化实现
int Person2::m_other=10;//用Person::就相当于是类内

在这里插入图片描述

静态成员函数


class Person2{
public:
    Person2()
    {}
    static int m_Age;//加入static就是静态成员变量 会共享数据
    int m_time;
    //静态成员变量,在类内声明,类外进行初始化
    ~Person2()
    {}
    static void func()//不可以访问 普通成员变量 无法区分这个普通成员变量是谁传的
    {
        m_time=15;//错误
        m_Age=10;//正确 可以访问静态成员变量
        cout<<"func调用"<<endl;
    }
private:
    static int m_other;//私有权限 在类外不能访问 但可以初始化

};

原因
在这里插入图片描述
可以访问静态成员变量
静态成员函数也是有权限的 私有的不可类外访问
在这里插入图片描述
在这里插入图片描述

单例模式

在这里插入图片描述
比如系统的任务管理器作为一个对象 不管怎么右键打开 都只有一个窗口 不管怎么右键 创建都是同一个对象 如果一个类中只能实例出来一个对象 就叫单例模式

比如 主席类 只能 new一个主席对象
在这里插入图片描述

//创建主席类
//需求 单例模式 为了创建类中的对象 并且保证只有一个对象实例
class ChairMan
{
private://构造函数私有化
    ChairMan()
    {}

public:
    static ChairMan *singlemen;//类内声明 不需要通过对象得到这个主席
};
ChairMan * ChairMan::singlemen=new ChairMan;//通过命名空间使得可以访问私有构造函数

void test11()
{
//    ChairMan c1;
//    ChairMan * c2= new ChairMan;
//不论创建几个对象都是同一个主席
ChairMan *cm=ChairMan::singlemen;//这样就保证了只有一个主席 并且这个创建是在编译过程就完成了 在main运行之前就创建好了这个对象
ChairMan *cm2=ChairMan::singlemen;//这样就保证了只有一个主席 并且这个创建是在编译过程就完成了 在main运行之前就创建好了这个对象
//为了防止有人手贱修改这个主席 比如
ChairMan::singlemen=NULL;
//需要将所有的singleMan属性设置为私有 对外提供接口来进行访问

}

属性全部私有化之后


//创建主席类
//需求 单例模式 为了创建类中的对象 并且保证只有一个对象实例
class ChairMan
{
private://构造函数私有化
    ChairMan()
    {}

private:
    static ChairMan *singlemen;//类内声明 不需要通过对象得到这个主席
public:
    static ChairMan * getinstance()
    {
        return singlemen;
    }
};
ChairMan * ChairMan::singlemen=new ChairMan;//通过命名空间使得可以访问私有构造函数

void test11()
{
//    ChairMan c1;
//    ChairMan * c2= new ChairMan;
//不论创建几个对象都是同一个主席
//ChairMan *cm=ChairMan::singlemen;//这样就保证了只有一个主席 并且这个创建是在编译过程就完成了 在main运行之前就创建好了这个对象
//ChairMan *cm2=ChairMan::singlemen;//这样就保证了只有一个主席 并且这个创建是在编译过程就完成了 在main运行之前就创建好了这个对象
////为了防止有人手贱修改这个主席 比如
//ChairMan::singlemen=NULL;
//需要将所有的singleMan属性设置为私有 对外提供接口来进行访问
ChairMan* cm1=ChairMan::getinstance();

}

拷贝构造特殊情况
在这里插入图片描述
此时cm2和cm3不同 会有两个对象
所以还要进行拷贝构造函数私有化

成员变量和成员属性分开处理

通过this指针区分成员函数
在这里插入图片描述
在这里插入图片描述
一个空类的大小为1
在这里插入图片描述
在这里插入图片描述
此时类对象大小等于4 并不是想象中的8(函数指针4+成员变量4)
原因:
因为非静态的成员函数会单独放在另外一个地方 不在类对象中
同理静态变量也不属于类对象
在这里插入图片描述
字节对齐问题 下面的类对象不是12 而是16
在这里插入图片描述

加一句
#pragma pack()影响对齐方式在这里插入图片描述

this指针

在这里插入图片描述
在这里插入图片描述
编译器会给成员函数偷偷加一个this指针参数 用以保存这个对象的地址
在这里插入图片描述
在这里插入图片描述
此时就把p1p2的地址传进去了
谁调用了就指向谁
在这里插入图片描述
左边是普通的代码 右边是编译器转换后的代码
在这里插入图片描述

this指针的使用

![在这里插入图片描述](https://img-blog.csdnimg.cn/20200220160824632.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zOTU1ODIyNw==,size_16,color_FFFFFF,t_70)
又比如![在这里插入图片描述](https://img-blog.csdnimg.cn/20200220160927809.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zOTU1ODIyNw==,size_16,color_FFFFFF,t_70)
不加this其实也是可以的age就是this.age

年龄相加例子
在这里插入图片描述
如果像下面这样调用会报错
在这里插入图片描述
因为返回值是一个void
这里要注意 如果希望返回值是一个左值(这样才可以用上面这种形式 函数调用作为左值) 返回值需要是一个引用类型
*加粗样式
上面就是正确写法 返回的依旧是p1
在这里插入图片描述
p1.PlusAge(p2)返回的依旧是一个p1
注意这里返回的必须是引用

空指针访问成员的函数

如果成员函数没有用到this 那么空指针可以直接访问
防止空指针访问成员函数(如果用到了this指针 就不能用空指针访问)
在这里插入图片描述

常函数和常对象

const 修饰成员函数
指针指向不能修改
比如不能this=NULL;
但是指针指向的值可以修改
在这里插入图片描述
为了使得这个指针指向的值不能修改 实现类似 const Person * const this 使用常函数
在这里插入图片描述
如果执意要修改
使用mutable 关键字 在这里插入图片描述
常对象 const Person p2;
在这里插入图片描述
在这里插入图片描述
常对象不能调用普通成员函数 因为普通成员函数中可能会修改属性 所以要用常函数 后面加const

全局函数做友元函数

朋友可以访问私有的属性 类内声明的时候前面加一个friend
在这里插入图片描述
外面全局函数的定义就可以访问私有的内容
在这里插入图片描述

整个类做友元类

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
先写类的声明 让编译器知道
要让好基友类访问卧室 需要改成好朋友
注意关键字 class不能少了
在这里插入图片描述
在这里插入图片描述

让成员函数做友元函数

让单独一个函数做友元 不用整个类做友元
在这里插入图片描述
前面加friend 声明一下就行
在这里插入图片描述

自定义数组实例

需要myarray.h myarray.cpp main.cpp
首先是类的声明 在myarray.h 中

//
// Created by user1 on 2020/2/21.
//
#include <iostream>
#pragma once
using namespace std;
#ifndef UNTITLED_MYARRAY_H
#define UNTITLED_MYARRAY_H
class myarray{
public:
    //无参构造
    myarray();//默认100容量
    //有参 用户指定容量初始化
    explicit  myarray(int capacity);
    //拷贝构造
    myarray(const myarray& array);
    //用户操作接口
    //根据位置添加元素
    void SetData(int pos,int val);
    //获得指定位置数据
    int GetData(int pos);
    // 尾插法
    void PushBack(int val);
    //获得长度
    int GetLength();
    //得到容量
    int getCapacity();
    //析构函数,释放数组空间
    ~ myarray();

private:
    int mCapacity;//数组一共可以容纳多少个元素 一定大于mSize
    int mSize;//当前有多少个元素
    int *pAddredd;//指向存储数据的空间
};

#endif //UNTITLED_MYARRAY_H

然后是类的定义
在myarray.cpp中

//
// Created by user1 on 2020/2/21.
//

#include <iostream>
#pragma once
using namespace std;

#include "myarray.h"

myarray::myarray() {
    this->mCapacity=100;
    this->mSize=0;
    this->pAddredd=new int [this->mCapacity];//开辟一个数组
}
myarray::myarray(const myarray &array) {
    this->pAddredd=new int[array.mCapacity];//这里不能用array的地址去赋值 不然就会出现同个对象析构两次的问题
    this->mSize=array.mSize;
    this->mCapacity=array.mCapacity;
    //在大小一致后 还要将数据拷贝过来
    for(int i=0;i<array.mSize;i++)
    {
        this->pAddredd[i]=array.pAddredd[i];
    }
}
//提供数组容量
myarray::myarray(int capacity) {
    this->mCapacity=capacity;
    this->mSize=0;
    this->pAddredd=new int [this->mCapacity];//开辟一个数组
}
myarray::~myarray() {
    if (this->pAddredd != NULL)
    {
        delete[] this->pAddredd;
    }
}
void myarray::PushBack(int val) {
    //需要判断下越界问题?用户自己处理

    this->pAddredd[this->mSize]=val;
    this->mSize++;
}
int myarray::GetData(int pos) {

}

void myarray::SetData(int pos, int val) {
    this->pAddredd[pos]=val;
}

int myarray::GetLength() {
    return this->mCapacity;
}
int myarray::getCapacity() {
    return this->mSize;
}

最后是main的调用和类初始化

void test01()
{
    //堆区创建数组
    myarray *array=new myarray(30);
    //下面很重要
    myarray * arr2=new myarray(*array);//这是用new方式制定调用拷贝构造的方式1
    myarray arr3=*arr2;//构造函数返回的本体 也是调用拷贝构造方式2

    myarray *array2=array;//这种形式并不调用拷贝构造 只是声明一个指针和array执行的地址相同 所以不会调用拷贝构造
    for(int i=0;i<10;i++)
    {
        array2->PushBack(i);
    }
    for(int i=0;i<10;i++)
    {
        cout<<array2->GetData(i)<<endl;
    }
    delete array;
    cout<<arr2->getCapacity()<<endl;
    cout<<arr2->GetLength()<<endl;
    //如果我们想要获取设置数组内容 用更符合直观地方式
    //cout<<array[20]<<endl;
    //array2[0]=1000;
    //就需要重载运算符


}
int main()
{
    test01();
}
如果我们想要获取设置数组内容 用更符合直观地方式
cout<<array[20]<<endl;
array2[0]=1000;
就需要重载运算符

关于拷贝构造初始化的细节
在这里插入图片描述

运算符重载

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
成员函数写法
在这里插入图片描述
全局函数写法
在这里插入图片描述

二元重载

用全局来看几元 全局有几个参数就有几元
在这里插入图片描述
在这里插入图片描述

左移右移的重载的注意点

在这里插入图片描述
此时还不能用cout<<调用 必须修改返回值
在这里插入图片描述

属性私有后 要使用友元来进行重载

在这里插入图片描述

前置后置递增运算符

在这里插入图片描述

class myint
{
public:
  myint(){};
  myint(int a){
      this->ma=a;
  }
  //前置++重载 参数里不用加
  myint& operator++()
  {
      this->ma++;
      return *this;

  };
  //后置++
  myint& operator++(int){// 这是前置后置的一个固定搭配 前置返回引用
      //先保存好之前的数据
      myint tmp=*this;
      ma++;
      return tmp;
  };
  friend ostream & operator<<(ostream &cout,myint &p1);

private:
  int ma;

};

ostream & operator<<(ostream &cout, myint &p1)
{
  cout<<p1.ma<<endl;
  return cout;
}


void test6()
{
  myint p1;
  //cout<<p1++<<endl; 需要重载两处 一个是右移的重载一个是自加的重载
  cout<< ++p1<<endl;//自加的重载返回值需要是自身
  cout<< p1++<<endl;//后置++
}

在这里插入图片描述
这也是为啥前置比后置效率高的原因
前置和后置返回值不同的原因:
第一个例子输出是2
在这里插入图片描述
当把前置++的返回值改成引用后在这里插入图片描述
返回的是2,1
为了保证返回之后前置++本体要成2 第一次运算完结果是它本体 而不是一个临时变量 所以要用引用。
而后置数据 返回的是一个临时值 临时数据返回引用可能有报错

指针运算符重载(自定义一个智能指针)

用来托管 指针对象 就不用显示地delete
在这里插入图片描述
在这里插入图片描述
维护住这个指针
new person的写法也要改
在这里插入图片描述
解释上述代码 sp对象的构造函数是个有参的构造
里面维护的指针来自于new的那个Person对象
sp对象是开辟到栈上的会自动释放
所以智能delete操作就可以放进这个智能指针的析构中,就不管官Person的析构
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
对*进行重载
在这里插入图片描述

赋值运算符重载

在这里插入图片描述

在这里插入图片描述
情况2:
开辟了动态内存
在这里插入图片描述
(加入析构后)这时候就会产生错误
等号运算符 会进行简单值传递 p1和p2的pname都指向同一个对象 会被重复析构 就会崩溃。
此时就需要重载等号运算符
在里面重复开辟一个新的空间
在这里插入图片描述
特殊情况连续的等号的处理
在这里插入图片描述
原始的 p2==p1返回值是个void
修改返回值
要返回一个引用
在这里插入图片描述

[] 运算符的重载

在这里插入图片描述
在这里插入图片描述
上面的实现返回一个 值 不能作为左值 所以会报错
在这里插入图片描述

关系运算符重载

在这里插入图片描述
实现这种操作
在这里插入图片描述

实现不等号在这里插入图片描述
在这里插入图片描述

函数调用运算符重载

对小括号的重载 构成仿函数
在这里插入图片描述
比如这种形式
在这里插入图片描述

不要重载||和&&

在这里插入图片描述
重载后可能会失去短路特性
在这里插入图片描述
在这里插入图片描述

自定义string类

在这里插入图片描述
在这里插入图片描述
cin的重载
在这里插入图片描述
两种等号的重载
在这里插入图片描述
在这里插入图片描述
以及析构
在这里插入图片描述
[]的重载
在这里插入图片描述
字符串拼接

也是两个版本在这里插入图片描述
哈哈哈
关系运算符的重载
测试

继承

在这里插入图片描述

能够拥有basepage的所有属性 在这里插入图片描述
在这里插入图片描述

继承方式

在这里插入图片描述
继承方式是对于子类来说的 不可能从私有—》公有
如果父类中是私有的 永远是私有 但通过改变继承方式可以使的父类中的公有变成私有

继承中的对象模型

在这里插入图片描述
利用下述工具显示继承结构
在这里插入图片描述
在这里插入图片描述
输入 里面的son是类名 报告单个类的布局 test.cpp是属于哪个文件 要进行该文件路径进行操作
cl /d1 reportSingleClassLayoutSon test.cpp
在这里插入图片描述

继承中的构造和析构顺序

在这里插入图片描述
在这里插入图片描述
=,构造,析构不能被继承
在这里插入图片描述

继承中同名成员的处理

就近原则
若想调用父类的 加一个成员的作用域
在这里插入图片描述
在这里插入图片描述
必须显式说明

继承中静态成员的处理

在这里插入图片描述
如果子类父类中都有 则也是就近原则或者显式调用
在这里插入图片描述
Son::func() //静态成员函数的调用

多继承

在这里插入图片描述
在这里插入图片描述
二义性问题 俩爹都有同名成员
在这里插入图片描述
在这里插入图片描述
解决方法1 命名空间
在这里插入图片描述

菱形继承

在这里插入图片描述
在这里插入图片描述

解决二义性的方法

造成了资源浪费
在这里插入图片描述
菱形继承的解决方法 叫虚继承
在这里插入图片描述
虚继承后的图
在这里插入图片描述
可以看到m_age只出现了一次 并且多了vbptr 这个虚指针
在这里插入图片描述
虚基类指针指向了虚基类表
虚基类表中有偏移量
在这里插入图片描述

虚表是一个数组 偏移量是8在这里插入图片描述
0+8=8 找到m_age 4+4=8找到m_age 所以只要一份m_age的存储 数据没有浪费
在这里插入图片描述
现在没有二义性 可以直接访问
在这里插入图片描述
在这里插入图片描述

虚继承工作原理

在这里插入图片描述
(1) 找偏移量操作
1.&st 先取得对象地址
在这里插入图片描述
2.(int*)&st 强转成 int类型 用来后续改变步长操作
在这里插入图片描述
3. *(int *)&st 取星操作后才能得到真正的虚表
在这里插入图片描述
4.(int*)*(int *) &st+1 找到虚表后 改变类型用来改步长
在这里插入图片描述
5.改变步长后 继续int
转变类型 再取* 得到真正的8 找到偏移量操作完成
在这里插入图片描述
(2)得到m_age的操作
1.在这里插入图片描述
2.改变类型后把之前得到的偏移量加上去
在这里插入图片描述
3.此时再改变类型 是个animal类型
在这里插入图片描述
4.然后整体括起来 ->m_age
就找到了在这里插入图片描述

多态

父类的引用或者指针 指向子类对象
在这里插入图片描述

静态编联和动态编联

class Animal{
public:
    void speak(){
        cout<<"animal talking";
    }
};
class Cat:public Animal{
public:
    void speak()
    {
        cout<<"cat talking";
    }
};
void dospeak(Animal  & ani)
{
    ani.speak();
}


void test1(){
    Cat cat;
    dospeak(cat);
}

上面就是一个静态编联的例子
早就在编译阶段就绑定了animal类 输出是‘animal talking’
如果希望锚说话 就不能提前绑定函数的地址 需要在运行时候确定函数的地址
此时就叫动态联编
把父类中的speak改成虚函数

}
class Animal{
public:
    void virtual speak(){//这里加一个virtual
        cout<<"animal talking";
    }
};
class Cat:public Animal{
public:
    void speak()
    {
        cout<<"cat talking";
    }
};
void dospeak(Animal  & ani)
{
    ani.speak();
}


void test1(){
    Cat cat;
    dospeak(cat);
}

此时输出就是‘cat talking’ 此时发生了动态多态
此时 Animal &animal=cat 父类的引用指向了子类对象

多态原理解析

不加virtual

class Animal{
public:
    void  speak(){
        cout<<"animal talking";
    }
};
class Cat:public Animal{
public:
    void  speak()
    {
        cout<<"cat talking";
    }
};
void dospeak(Animal  & ani)
{
    ani.speak();
}


void test1(){
    Cat cat;
    dospeak(cat);
}
void test3()
{
    Animal an1;
    cout<< sizeof(Animal)<<endl;

}

在这里插入图片描述
Animal 此时大小是1 一个空类
如果加上virtual
在这里插入图片描述
输出会是4 此时Animal类已经发生了变化
类此时内部包含了一个虚指针
指向了animal内部函数的地址
在这里插入图片描述
cat继承之后 但不写speak
继承虚指针和虚表 同时改变虚指针的指向

在这里插入图片描述
重写了speak之后
重写必须返回值参数都相同
在这里插入图片描述
将父类虚表中的内容进行替换
用父类指针或者引用子类对象时 此时就调用虚表cat中的speak
在这里插入图片描述
在这里插入图片描述

多态案例1 计算器

原始状态 只有加法和减法

#include <iostream>
using namespace std;

class Calculator{
public:
    Calculator(){};
    void setv1(int v){
        this->val1=v;
    };
    void setv2(int v){
        this->val2=v;
    };
    int get_result(string oper){
        if (oper=="+")
        {
            return val1+val2;
        }
        else if (oper=="-"){
            return val1-val2;
        }
    };
    int val1;
    int val2;
};

void test1()
{
    Calculator ca1;
    ca1.setv1(10);
    ca1.setv2(10);
    cout<<ca1.get_result("+");
}


int main() {
    test1();
    return 0;
}

然后要添加乘法和除法功能
在这里插入图片描述
此时要利用多态实现

class Calculatorbase{
public:
    int virtual get_result(){
        return 0;
    };
    void setv1(int v){
        this->val1=v;
    };
    void setv2(int v){
        this->val2=v;
    };

    int val1;
    int val2;
};
//加法计算器
class Calculatorplus:public Calculatorbase{
public:
    int get_result(){
        return val1+val2;
    };
};
//减法计算器
class Calculatorsub:public Calculatorbase{
public:
    int get_result(){
        return val1-val2;
    }
};
void test4()
{
    Calculatorbase * abc=new Calculatorplus;//声明了一个加法计算器 发生多态 父类指向了子类
    abc->setv1(10);
    abc->setv2(12);
    cout<<abc->get_result()<<endl;
}

int main() {
    test4();
    return 0;
}

此时要添加新功能 不需要改原来的代码 只要加新的功能的子类代码

抽象类和纯虚基类

我们发现父类中的 get_result 没有任何意义 此时进行修改
在这里插入图片描述
将不需要的函数实现都删掉 直接等于0 变成纯虚函数
在这里插入图片描述

1.如果父类中有纯虚函数 那么子类继承父类必须实现这个纯虚函数
在这里插入图片描述
2.如果父类中有纯虚函数 父类此时就不能实例化了
3.这个类有个纯虚函数,也叫作抽象类
在这里插入图片描述

虚析构和纯虚析构函数

原始版本

class Animal{
public:
    void virtual speak()
    {
        cout<<"animal talking";
    }
    ~Animal(){
        cout<<"animal ~";
    }
};
class Cat:public Animal{
public:
    Cat(const char * name)
    {
        this->m_Name=new char[strlen(name)+1];
        strcpy(this->m_Name,name);
    }
    void virtual speak()
    {
        cout<<"cat talking";
    }
    char * m_Name;//名字
    ~Cat(){
        cout<<"cat ~"<<endl;
        if (this->m_Name!=NULL)
        {
            delete [] this->m_Name;
            this->m_Name=NULL;
        }
    }
};
void test5()
{
    Animal * animal=new Cat("TOM");
    animal->speak();
    delete animal;
}

此时会析构掉animal
在这里插入图片描述
这就会导致 没有释放干净 这种情况下就需要一个虚析构
普通的析构 不会调用子类的析构 要一个虚析构解决该问题
在这里插入图片描述
在animal的析构之前加一个~ 就变成了虚析构
此时输出为
在这里插入图片描述
先调用了cat的析构又调用了animal的析构 然后是纯虚析构 一样的方式=0
在这里插入图片描述
直接改成这样会报错:无法解析的外部命令
在这里插入图片描述
纯虚析构的注意点
如果直接把父类的析构改成纯虚 会只有声明没有实现 就会有无法解析的问题
所以纯虚析构需要声明还需要实现:类内声明 ,类外实现!!

class Animal{
public:
    void virtual speak()
    {
        cout<<"animal talking";
    }
    virtual ~Animal()=0;
};
Animal::~Animal() {
    
}

此时输出
在这里插入图片描述
如果此时有纯虚析构 这个类也叫作抽象类

向上类型转换向下类型转换

在这里插入图片描述
安全不安全指的是指针的寻址范围
把animal转成cat cat的寻址范围比animal大 可能会操纵到不是自己的内容
在这里插入图片描述
记一下家族谱 向下转换
基类转成子类 将是安全的
在这里插入图片描述
这就叫向上类型转换
在这里插入图片描述
如果发生了多态就一定是安全的
一开始寻址范围就是子类的大范围
在这里插入图片描述

游戏多态实例

首先设计类的属性和功能
在这里插入图片描述

模板

在这里插入图片描述
类型多,逻辑又非常相似
//类型参数化,泛型编程—模板技术
在这里插入图片描述
会自动进行类型替换 一开始参数不确定
在这里插入图片描述
必须要能够推导出来 ,下面就是一个推导不出来的二义性错误
在这里插入图片描述
调用的时候明确说明类型
在这里插入图片描述
class也可以写成typename 是等价关系的
在这里插入图片描述
使用时尽量使用显式指定类型 不用去自动推导

排序实例

template <class T> //告诉编译器 下面出现了T不要报错 T是一个通用类型
void myswap(T &a,T &b){
    T temp=a;
    a=b;
    b=temp;
}


template <class T>
void mySort(T arr[],int len)
{
    for (int i =0;i<len;i++)
    {
        int max=i;
        for (int j=i+1;j<len;j++)
        {
            if (arr[max]<arr[j])
            {
                max=j;
            }
        }
        if (max!=i)
        {
            myswap(arr[max],arr[i]);

        }
    }
}


void test6()
{
    char charArr[]="helloworld";
    int num=sizeof(charArr)/ sizeof(char);//求数组长度的通用方法
    mySort(charArr,num);
    
}

char 类型会按照asicii码来排序
在这里插入图片描述

普通函数和函数模板的区别

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
函数模板也能重载
在这里插入图片描述
在这里插入图片描述
用模板不用多进行一步隐式类型转换,是更好的匹配

模板机制内部原理

在这里插入图片描述
在这里插入图片描述
自定义的数据类型可能就无法处理
会进行两次编译!

函数模板的局限性

在这里插入图片描述
解决方法:需要模板的具体化

模板的具体化

如下面的例子,普通类型是可以使用模板比较的
在这里插入图片描述
放入自定义类型后 就会报错 能知道是Person类 但无法进行比较
在这里插入图片描述
//通过具体化自定义数据类型,解决上述问题

class Person{
public:
Person(string name,int age)
{
    m_name=name;
    m_age=age;
}
string m_name;
int m_age;
    };

template <class T>
bool myCompare( T &p1,T &p2)
{
    if (p1==p2)
    {
        return true;
    }
    return false;
}
template<> bool myCompare <Person>(Person &p1,Person &p2)//此时类型已经不是T
{
    if (p1.m_age==p2.m_age)
    {
        return true;
    }
    return false;
}

如果Person对两个模板都能匹配优先选择具体化的模板
在这里插入图片描述

类模板的使用

使用和函数模板类似

template <class T1,class T2>
class Person2{
public:
        Person2(T1 age, T2 name)
        {
            m_age=age;
            m_name=name;
        };
        T1 m_age;
        T2 m_name;
    };

区别1 :类模板不支持自动类型推导
在这里插入图片描述
必须显式指定类型
在这里插入图片描述
区别2: 类模板可以有默认类型
在这里插入图片描述

成员函数的创建时机

成员函数一开始不创建出来而是运行时创建



class Person1
{
public:
    void showPerson1()
    {
        cout<<"diao Person1"<<endl;
    }
};
class Person3
{
public:
    void showPerson3()
    {
        cout<<"diao Person3"<<endl;
    }
};
template <class T>
class myclass
{
public:
    T obj;
    void func1()
    {
        obj.showPerson1();
    }
    void func2()
    {
        obj.showPerson2();
    }
};

void test8()
{
    myclass <Person1> m;
    m.func1();
}

m.func2() //将会报错

类模板做函数的参数

在这里插入图片描述
方法1:指定传入类型
在这里插入图片描述
调用
在这里插入图片描述
方法2
参数模板化
不告诉具体类型了 函数模板配合类模板实现
在这里插入图片描述
在这里插入图片描述
方法3
整体类型化 把整个Person对象进行了模板化
在这里插入图片描述
查看T的类型
在这里插入图片描述
在这里插入图片描述

类模板的继承问题

在这里插入图片描述
正确方法 具体化类型
在这里插入图片描述更灵活的写法,把子类也变成模板
把一个T2传递给父类用于初始化分配内存
在这里插入图片描述
这样子之后就能由用户来指定类型

类模板类外实现成员函数

类内实现的方法
在这里插入图片描述

类外实现在这里插入图片描述
普通类的类外实现
在这里插入图片描述
模板类就是多了一个
在这里插入图片描述

在这里插入图片描述
再次强调 类模板生成对象的时候使用的时候必须要显式指定类型
在这里插入图片描述

类模板的分文件编写代码

1.头文件 Person.h

//
// Created by user1 on 2020/2/24.
// 头文件声明


#ifndef FENWENJIAN_PERSON_H
#define FENWENJIAN_PERSON_H
#pragma once
using namespace std;

#include <iostream>
#include <string>

template <class T1,class T2>
class Person{
public:
    Person(T1,T2);

    void showPerson();


    T1 mname;
    T2 mage;
};
#endif //FENWENJIAN_PERSON_H
  1. Person.cpp
#include <iostream>
#include "person.h"
using namespace std;

//
// Created by user1 on 2020/2/24.

template <class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age) {
    this->m_name=name;
    this->mage=age;

}

template <class T1,class T2>
void Person<T1,T2>::showPerson() {
    cout<<this->mname<<" "<<this->mage<<endl;
}

3.main.cpp

#include <iostream>
#include "person.h"
using namespace std;
int main() {
    std::cout << "Hello, World!" << std::endl;

    Person <string,int>p1("dasha",12);
    p1.showPerson();

    return 0;
}

上面的写法会报错 两个无法解析的外部命令
把#include “person.h” 改成 #include “person.cpp”
在这里插入图片描述
原因
因为在链接的时候找不到代码
main调用头文件的时候只看到头文件 类模板的成员方法并不会生成源文件的代码 类模板中的成员函数一开始不会创建而是运行时创建出来的,所以链接的时候找不到这些代码。
而调用person.cpp的时候就相当于把实现代码都放到main里面去了
所以模板一般不做分文件编写,都写到头文件上去
头文件就改成了.hpp的形式
.hpp就是用来表示模板类的头文件 约定俗成
在这里插入图片描述
总结
在这里插入图片描述

类模板的友元函数

友元函数是为了访问一个类中的私有属性
下面是原始状态
在这里插入图片描述
在这里插入图片描述

友元函数类外实现 --难

按下面的写法将会报错 这个友元函数的声明编译器看成了一个普通全局函数 只不过调用了模板类,而它的类外实现 编译器却看成了一个模板函数,这样子编译器优先去找普通全局函数的实现,但是找不到!!
在这里插入图片描述
在这里插入图片描述
解决方法:
1.利用空参数列表告诉编译器这个是模板函数的声明在这里插入图片描述
但还是有问题!!
必须提前告诉编译器有这个声明 而不能仅仅在类内声明
在这里插入图片描述
这样写还是有问题!
需要让编译器看到Person的声明
在这里插入图片描述

类模板的应用 写一个通用数组类

#include <iostream>
using namespace std;

//
// Created by user1 on 2020/2/24.
//
template <class T>
class Myarray {
public:
    Myarray( )
    {

    };
    explicit Myarray(int capacity)//防止隐式类型转换
    {
        this->mcapacity = capacity;
        this->msize = 0;
        this->paddress = new T[this->mcapacity];
    };

    Myarray(const Myarray &array) {
        this->msize = array.msize;
        this->mcapacity = array.mcapacity;
        this->paddress = new T[this->mcapacity];
        for (int i = 0; i < msize; i++) {
            this->paddress[i] = array[i];
        }
    }

    ~Myarray() {
        if (this->paddress != NULL) {
            delete[] this->paddress;
            this->paddress = NULL;
        }
    }

    //赋值操作符重载
    Myarray& operator=(Myarray &arr)
    {
        //先判断原始数据 有就清空
        if(this->paddress!=NULL)
        {
            delete [] this->paddress;
            this->paddress=NULL;
        }
        for (int i = 0; i < msize; i++) {
            this->paddress[i] = array[i];
        }
        return *this;

    }
    //[]重载
    T &operator[](int index)
    {
        return this->paddress[index];
    }
    //尾插法
    void pushback(T val)//不能使用引用这里
    {
        this->paddress[this->msize]=val;
        this->msize++;
    }
    //获取容量
    int getsize()
    {
        return this->msize;
    }
    //获取cap
    int getcap()
    {
        return this->mcapacity;
    }
private:
    T *paddress;//指向堆区指针
    int mcapacity;
    int msize;
};

C++类型转换

尽量少用类型转换

静态类型转换

在这里插入图片描述
派生类转换为基类是安全的,反之不安全
在这里插入图片描述
在这里插入图片描述
必须要有父子关系的类之间才能转换
引用也可以转换
在这里插入图片描述

动态类型转换

在这里插入图片描述

1.基础类型不能动态转换在这里插入图片描述
因为只要失去精度或者不安全都不可以转换
2.子类和父类之间转换 父类转子类会报错(如果没有发生多态)
在这里插入图片描述
在这里插入图片描述
如果发生了多态(比如父类指针指向子类),那么可以让基类转为派生类,向下转换
在这里插入图片描述
这样就能正常父转子,因为创建空间的时候就是按子类空间创建的!
在这里插入图片描述

常量转换

用于移除类型的const属性 或者加上const属性
只能用于引用和指针的变量
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

重新解释转换(没用)

在这里插入图片描述
非常不安全!!
在这里插入图片描述

异常

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
C++异常处理的优势
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

C++中异常的基本使用

先来看一下早期C的处理方式
在这里插入图片描述
C++的处理
在这里插入图片描述
抛出的-1是一个int类型所以要catch(int)
在这里插入图片描述
如果不用异常捕获 程序会被粗暴地终止(计算机对异常进行处理了)
在这里插入图片描述
用了异常捕获 得到了异常 将检测和处理阶段分开了
在这里插入图片描述
也可以抛出其他类型的异常
并且要知道异常必须要处理 要catch住
在这里插入图片描述
要写一个catch double类型去接
在这里插入图片描述
怎么处理那么多类型的异常:
C++提供了一种机制可以一试多用处理多种类型的异常
在这里插入图片描述

自定义异常类

在这里插入图片描述
myException()就是一个匿名对象 对象不需要起名字了
在这里插入图片描述

捕获自定义异常,并且调用成员函数在这里插入图片描述
总结
在这里插入图片描述

栈解旋

在这里插入图片描述

异常的接口声明

为了让异常可读性更高
在这里插入图片描述
抛出3.14就会报错
在这里插入图片描述

异常变量的生命周期

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
用引用能节省开销
在这里插入图片描述
用指针来控制 比较繁琐 还是引用好
在这里插入图片描述

异常的多态使用

在这里插入图片描述
在这里插入图片描述
父类的引用指向子类的对象 出现多态
在这里插入图片描述
因此实现的时候只需要catch一个基类就行了,使得代码更简洁

系统标准异常

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
what方法打印的就是上面的我们设置的“年龄越界了”这句话
在这里插入图片描述

自己实现一个异常类

使用系统的exception作为基类
主要是重写what方法和析构方法
在这里插入图片描述

string转 char*使用.c_str()
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

输入输出流

在这里插入图片描述
标准io:
输入文件===键盘
输出文件===屏幕
文件io:
在这里插入图片描述
串IO:
在这里插入图片描述

标准输入

在这里插入图片描述
缓冲区概念
在这里插入图片描述
在这里插入图片描述

一次读入一个字符在这里插入图片描述
第一次拿走了缓冲区中的a,后来拿走了s
在这里插入图片描述
第三次拿到了换行符
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
cin.get()读取字符串时不会把换行符拿走,把它遗留在缓冲区中
在这里插入图片描述
getline是会把换行符读取并且扔掉 并且等待下次输入
在这里插入图片描述
没有参数代表忽略一个字符
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

缓冲区中拿走之后又放回去了在这里插入图片描述

标准输入流案例

把偷窥完的数字放回缓冲区后又重新冲进num中进行输出
在这里插入图片描述
在这里插入图片描述
2.

错误案例在这里插入图片描述
输入一个char类型 标志类就会毁坏 出现严重错误
需要用cin.fail()
在这里插入图片描述
此时输入字符类型就会输出不正常的标志位
在这里插入图片描述

标准输出流

在这里插入图片描述
在这里插入图片描述

输出格式控制

要先使用该头文件
在这里插入图片描述
在这里插入图片描述
前面会有18个空格
fill 空格填充成*
在这里插入图片描述
setf 设置输出格式状态
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
把十进制改成了十六进制
在这里插入图片描述
在这里插入图片描述
卸载十六进制安装八进制
在这里插入图片描述

文件读写操作

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
把内容输出到文件中。

从文件中读数据

在这里插入图片描述
第一种方式比较好
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!