C++语言入门到精通

前言:学习本文需要一定的C语言基础知识储备

文章目录

一、基础知识

  • 编译有四个阶段:源代码–>预处理(不判断语法是否正确)、编译–>汇编文件、汇编–>二进制文件、链接–>可执行文件
  • 使用QT开发:多行注释:ctrl+/
  • 函数重载:在同一个作用域内,有多个函数名相同,但是形参列表不同(参数类型不同,参数个数不同,参数顺序不同),返回值无关,即“一个接口,多种实现”
  • C++ new和malloc的区别 https://blog.csdn.net/ymhdt/article/details/125991187
  • 节约内存空间方法:1 函数 形参用 引用 2
  • 节约时间方法:1 使用内联函数可以减少函数调用的开销(时间开销),但是会增加内存空间开销
  • 批量注释:ctrl+/ 或 以下方式
class Data
{
    //类中:默认私有
private:
    int mA;

public:
#if 0
    //无参构造函数
    Data()
    {
        mA = 0;
        cout<<"无参构造函数"<<endl;
    }
#endif
#if 1
    //有参构造函数
    Data(int a)
    {
        mA = a;
        cout<<"有参构造函数"<<endl;
    }
#endif

};

二、指针

三、预处理

3.1 内存分区
  • 堆区 使用malloc calloc realloc free 动态申请 可读可写
  • 栈区 局部变量 函数形参 返回值>4B 可读可写 并可用“&”取地址
  • 全局区 __可读可写__并可用“&”取地址 全局区的变量若不初始化 默认为0
  • 文字常量 只读
  • 代码区 只读
3.1.1 动态内存 new与delete

对比静态内存 【分配在栈区或全局变量区】例如定义数组
动态内存:分配在堆区

3.2 变量的存储

注意两几个概念:作用范围以及生命周期;局部变量以及全局变量;定义 初始化 赋初值
普通全局变量与静态全局变量区别在于静态全局变量只能在当前源文件使用
全局函数与静态函数区别在于静态函数成员只能在当前源文件使用

3.3 头文件包含

#include <head.h>
#include “head.h”

3.4 宏定义
#define PI 3.14//只是纯粹的替换,所以不需要开辟空间

#undef PI //结束宏的作用域

特殊用法:(带参数的宏即宏函数)

#define ADD(a,b) a+b

cout<<ADD(10,9);//90

注意:宏 的参数不能有类型:#define ADD(int a,int b) a+b 错误
宏函数与普通函数区别:函数有作用域的限制,可以作为类的成员;而宏函数没有作用域的限制,因此不能作为类的成员

四、结构体

  • 浅拷贝 (当成员无指针时 浅拷贝完全能解决问题;但如果有指针成员,就要注意,因为会带来多次释放堆区空间的问题)
  • 原因在于:浅拷贝单纯的复制源内容,当成员包含指针时,指针指向的地址不会变,当要释放堆区空间时,是独立释放的,因此会带来多次释放堆区空间问题
  • 深拷贝 为指针成员申请独立空间

五、链表

前言:数组和链表的优缺点:
静态数组:易浪费空间,删除插入数据效率低
动态数组:动态申请空间,删除插入数据效率低
优点:遍历元素效率高
链表:动态申请,删除插入数据效率高
缺点:遍历效率低
链表定义:
在这里插入图片描述

六、C++对C的扩展

6.1 面向过程
6.2 面向对象
6.2.1 面向对象的三大特点

封装、继承、多态

6.3 作用域运算符::

功能一:当想要访问全局变量时,用::

6.4 命名空间 namespace
6.4.1 命名空间使用语法
  1. 创建一个命名空间(只能在全局空间创建)
namespace A {
    int a = 10;
}
namespace B {
    int a = 20;
}
void test05()
{
    cout<<"A::a "<<A::a<<endl;
    cout<<"B::a "<<B::a<<endl;
}

  1. 命名空间可以嵌套
namespace A {
    int a = 10;
    namespace B {
        int a = 20;
    }
}

void test05()
{
    cout<<"A::a "<<A::a<<endl;
    cout<<"A::B::a "<<A::B::a<<endl;
}
  1. 命名空间是开放的,即随时可以添加内容
namespace A {
    int a = 10;
    namespace B {
        int a = 20;
    }
}

namespace A {
    int b = 100;
}

void test05()
{
    cout<<"A::a "<<A::a<<endl;
    cout<<"A::b "<<A::b<<endl;
}
  1. 命名空间里的函数可以只声明, 在外面实现
namespace myspace
{
	void func(); 
}
void myspace::func()
{
	cout<<"huawei!"<<endl;
}
  1. 无名命名空间

相当于给里面的标识符加上了static,使其只能在本文件内访问

namespace {
    int a = 10;
    void func(){cout<<"huawei!"<<endl;}
}

int main()
{
    //test05();
    cout<<a<<endl;
    func();
    return 0;
}
  1. 命名空间别名
    注意:起别名后,原来的名称不会失效
namespace verylongname{}
//起别名
namespace shortname = verylongname
6.4.2 using声明
  • 注意:要避免命名冲突
namespace A {
    int a = 10;
    namespace B {
        int a = 20;
    }
}

namespace A {
    int b = 100;
}

void test05()
{
    //使用方式一 不会出现命名冲突
    cout<<"A::a "<<A::a<<endl;
    cout<<"A::b "<<A::b<<endl;
    //使用方式二 以下情况会出现命名冲突
    using A::a;
    cout<<a<<endl;
    //命名冲突
    int a = 111;
}
  • using 声明遇到函数重载 (很方便:只要using A::func 所有函数都声明了)
  • using声明 整个命名空间可用
using namespace A;//以下程序遇到变量 先在局部空间找,找不到 再到命名空间里找 不会出现命名冲突
6.5 struct 类型增强

结构体中既可以定义成员变量,又可以定义成员函数
定义时 可不加struct:

struct Stu
{
	int num;
}
Stu stuent1;//定义时 可不加struct
6.6 bool类型关键字
bool flag = true//true==1==任何非0数;false==0
6.7 引用

引用比指针更好

变量名实质是一段连续内存空间的别名 int a = 10;而引用就是:给一个已定义变量取别名

  • 一般的引用
void func1()
{
    cout<<"func1"<<endl;
}
void test05()
{
    int a = 10;
    //需求:给a取别名b;定义的时候 &修饰变量为引用 b就是a的别名(引用)
    //系统不会为引用开辟空间;a和b代表同一空间内容 (值相同,地址相同)
    int &b = a;//引用必须初始化

    //数组的引用
    int c[5] = {1,2,3,4,5};
    int (&d)[5] = c;
    for(int i = 0;i<5;i++)
    {
        cout<<d[i]<<"  ";
    }

    //指针变量的引用
    int num = 10;
    int* p = &num;
    int* &myp = p;
    cout<<*myp<<endl;

    //函数的引用
    void (&myfunc)() = func1;
    myfunc();

}
  • 引用作为函数的参数 (函数内部可以通过引用操作外部变量)–>引用处理场景最多时候
    C++中主张用引用传递取代地址传递
void swap(int &x,int &y)
{
    int p;
    p = x;
    x = y;
    y = p;
}

void test06()
{
    int a = 1;
    int b = 2;
    swap(a,b);
    cout<<"a = "<<a<<"  "<<"b = "<<b<<endl;
}
  • 引用作为函数的返回值类型
int& test07()
{
    int num = 10;
    //不要返回局部变量的引用,因为函数完,num所占内存就被释放了
    return num;
}

int main()
{
    int &b = test07();//b是num的别名
    cout<<b;//啥也没有
    return 0;
}
  • 常引用
  • 给常量取别名
const int &a = 10//a就是10的别名
  • 常引用 作为函数的参数 :可以防止函数内部修改外部的值
void printint(const int &a)
{
	cout<<a<<endl;
}
int main()
{
	int num = 100;
	printint(num);
}
6.8 内联函数

内联函数:必须在定义时使用关键字inline修饰,不能在申明时使用inline
作用:在编译阶段将内联函数的函数体替换函数调用处,避免函数调用时的开销
内联函数可以减少函数调用的开销(时间开销),但是会增加内存空间开销

inline int myadd(int x,inty)
{
    return x+y;
}

注意点:
宏函数和内联函数 都会在适当的位置 进行展开 避免函数调用开销。
宏函数在预处理阶段展开
内联函数在編泽阶段展开
宏函数的参数没有类型,不能保证参数的完整性,
内联函数的参数有类型 能保证参数的完垫性。
宏函数没有作用域的限制,不能作为命名空间、结构体、类的成员
内联函数有作用域的限制,能作为命名空间、结构体、类的成员

注意:加上inline只是我们希望这个函数变为内联函数,最终是否为内联函数 由编译器决定。加上 可能不是,不加也可能是。

6.9 函数重载

定义:同一个函数名在不同场景下可以具有不同的含义;或 用同一个函数名 代表不同的函数功能
函数重载是C++的多态的特性(静态多态)
同一个作用域,函数的参数类型不同、个数不同、顺序不同 都可以重载。(返回值类型不能作为重载的条件)

void printFun(int a)
{
    cout<<"huawei!"<<endl;
}

void printFun(int a,int b)
{
    cout<<"huawei!"<<endl;
}
void printFun(char c)
{
    cout<<"huawei!"<<endl;
}

int main()
{
    printFun(10);
    printFun(1,2);
    printFun('x');
    return 0;
}
6.10 函数的默认参数
//1 当形参b设置了默认参数,后面的所有形参也要设置 2 如果函数声明和定义分开(声明一定在定义之前),要在声明处设置默认参数,定义处不能设置
void Func(int a = 10 ,int b = 20,int c = 30){
	cout<<a+b;
}
Func();
Func(100);
Func(100,200);

**注意:**当默认参数和函数重载同时出现时,要注意会不会有冲突

6.11 占位参数
void Func(int a, int b,int{
}
6.12 extern "C"浅析

作用:实现C++代码能够调用C语言代码
在头文件.h中写:
(固定格式)

#if __cplusplus
	extern "C"{
#endif
	函数声明
#if __cplusplus
	}
#endif

七、类及封装

7.1 类和对象的基本概念

用 class 关键字;不要去初始化

#include<iostream>
#include<stdio.h>
#include<string.h>

using namespace std;

class Data
{
    //类中:默认私有
private:
    int a;

protected:
    int b;

public:
    int c;
    void showData()
    {
        cout<<a<<"  "<<b<<"  "<<c<<endl;
    }
};

int main()
{
    //类实例化一个对象
    Data abc;
    cout<<abc.c<<endl;
    abc.showData();
    return 0;
}

总结:设计一个类步骤:思考:1. 设计哪些数据 2. 这些数据通过哪些方法去操作 3. 数据为私有,方法为公有

7.2 类外或其他源文件实现类的成员函数

成员函数在类外实现(定义),在类中声明 (用作用域)
QT中快捷键:alter+enter(在外面实现函数)

  • 类外实现成员函数
#include<iostream>
#include<stdio.h>
#include<string.h>

using namespace std;


class Data
{
    //类中:默认私有
private:
    int num;

public:
    void set(int a);
    int get();
};

void Data::set(int a)
{
    num = a;
}

int Data::get()
{
    return num;
}


int main()
{
    //类实例化一个对象
    Data haha;
    haha.set(333);
    cout<<haha.get();

    return 0;
}
  • 其他源文件实现成员函数
    在项目中添加类:在.h文件中定义类,在.cpp中实现类的成员函数
    stu.h
#ifndef STU_H
#define STU_H


class Stu
{
private:
    int mA;
public:
    void setA(int a);
    int getA();
};

#endif // STU_H

stu.cpp

#include "stu.h"`在这里插入代码片`

void Stu::setA(int a)
{
    mA = a;
}

int Stu::getA()
{
    return mA;
}

main.cpp

int main()
{
    //类实例化一个对象
//    Data haha;
//    haha.set(333);
//    cout<<haha.get()<<endl;
    
    Stu George;
    George.setA(10000);
    cout<<George.getA()<<endl;
    return 0;
}
7.3 类的初始化和清理–>构造函数以及析构函数

注意: 这是编译器自动调用的函数,不需要我们手动调用

7.3.1 构造函数

相当于python中的初始化方法
编译器遇到类时,先给实例对象开辟空间,其次调用构造函数
构造函数定义:函数名与类名相同,没有返回值类型(连void都不行),可以有参数(可以重构),权限为public

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<stu.h>

using namespace std;


class Data
{
    //类中:默认私有
private:
    int mA;

public:
    //无参构造函数
    Data()
    {
        mA = 0;
        cout<<"无参构造函数"<<endl;
    }

    //有参构造函数
    Data(int a)
    {
        mA = a;
        cout<<"有参构造函数"<<endl;
    }


};


int main()
{
    //隐式调用无参函数
    Data ob1;

    //显式调用无参函数
    Data ob2 = Data();

    //隐式调用有参函数
    Data ob3(10);

    //显式调用有参函数
    Data ob4 = Data(10);

    //匿名对象 调用完后立即释放
    Data();
    Data(20);

    //构造函数隐式转换(类中只有一个数据成员)
    Data ob5 = 100;//这样写会转换成 Data ob5(100);

    return 0;
}
7.3.2 析构函数

当函数生命结束时,系统自动调用析构函数,最后释放对象空间
没返回值 没参数
注意:这边会涉及到面试题中 构造和析构的顺序<–创建的类对象都是放在区的,先进后出

一般情况下,空的析构函数就足够(即写不写都无所谓);但如果类有指针成员,这个类必须写析构函数,释放指针成员所指向空间。(因为系统会自动释放类对象的内存,而指针指向的堆区空间或其他 则不会自动释放,需要我们手动释放)

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<stu.h>

using namespace std;


class Data
{
public:
    char *name;
public:
    Data(){
        name = NULL;
    }
    Data(char *str)
    {
        name = new char[strlen(str)+1];//在堆区开辟内存空间
        strcpy(name,str);
        cout<<"有参构造"<<endl;
    }
    ~Data()
    {
        if(name!=NULL)
        {
            delete [] name;
        }
        cout<<"析构函数"<<endl;
    }
};


int main()
{
    Data ob("huawei!");
    cout<<ob.name<<endl;

    return 0;
}
7.3.3 拷贝构造函数

拷贝构造函数 如果自己不写,系统默认是浅拷贝;如果写了 就要自己拷贝

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<stu.h>

using namespace std;


class Data
{
public:
    char *name;
public:
    //构造函数 两个
    Data(){
        name = NULL;
    }
    Data(char *str)
    {
        name = new char[strlen(str)+1];//在堆区开辟内存空间
        strcpy(name,str);
        cout<<"有参构造"<<endl;
    }

    //拷贝构造函数
    Data(const Data &ob)//形参是Data类型的旧对象的常引用
    {
        //一旦自己实现了拷贝构造函数,必须手动完成赋值操作
        name = ob.name;
        cout<<"拷贝构造函数"<<endl;
    }
    
    //析构函数
    ~Data()
    {
        if(name!=NULL)
        {
            delete [] name;
        }
        cout<<"析构函数"<<endl;
    }
};


int main()
{
    Data ob("huawei!");
    cout<<ob.name<<endl;
    Data ob1 = ob;
    return 0;
}

7.4 初始化列表
  • 对象成员
    • 先调用成员的构造函数,再调用自身的构造函数。最后结束时,先调用自身的析构函数,再调用成员的析构函数(注意:类会自动调用对象成员的无参构造)
  • 初始化列表
    • 当类有对象成员,并且在实例化对象时,想调用对象成员的有参构造
    • 也能用于非对象成员的初始化
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<stu.h>

using namespace std;

class A
{
public:
    int ma = 1;
public:
    A(){
        cout<<"A的无参构造"<<endl;
    }
    A(int a)
    {
        ma = a;
        cout<<"A的有参构造"<<endl;
    }

};


class B
{
public:
    int mb = 2;
    A ob;//对象成员
public:
    B(){
        cout<<"B的无参构造"<<endl;
    }

    B(int a,int b):ob(a)//成员列表:相当于是 隐式调用有参函数
    {
        mb = b;
        cout<<"B的有参构造"<<endl;
    }

};


int main()
{
    B ob1(10,20);
    return 0;
}
  • explicit 关键字

c++提供了关键字explicit,禁止通过构造函数进行的隐式转换,声明为explicit的构造函数不能在隐式转换中使用。
注意 explicit是针对单参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造)而言

7.5 类的对象数组

本质是数组 数组的每个元素是对象

//A是类,调用有参构造函数
A arr[5] = {A(1),A(2),A(3),A(4),A(5)};
7.6 动态对象创建

当我们创建数组的时候,总是需要提前预定数组的长度,然后编译器分配预定长度的数组空间,在使用数组的时,会有这样的问题,数组
也许空间太大了,浪费空间,也许空间不足,所以对于数组来讲,如果能根据需要来分配空间大小再好不过。所以动态的意思意味着不
确定性。为了解决这个普遍的编程问题,在运行中可以创建和销毁对象是最基本的要求。当然c早就提供了动态内存分配 (dynamic
memory allocation),函数malloc和free可以在运行时从堆中分配存储单元。然而这些函数在c++中不能很好的运行,因为它不能帮我们完成对象的初始化工作。

C语言的malloc和free 这种方式动态分配空间函数太复杂。
C++用new和delete

Person* person = new Person;//new做了两件事:在堆区为对象分配内存并调用构造函数完成初始化
delete person;//delete做了两件事:调用析构函数以及释放空间

以下是一般的类创建与运用方法:
需要包含(无参构造,有参构造,析构函数【如果没有指针成员,就不需要写】,静态或动态创建对象)

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<stu.h>

using namespace std;

class Person
{
public:
    char* pName;
    int pAge;
public:
    Person()
    {
        cout<<"这是无参构造函数"<<endl;
        pName = new char[strlen("unknown")+1];
        strcpy(pName,"unknown");
        pAge = 0;
    }

    Person(char *name,int age)
    {
        cout<<"这是有参构造函数"<<endl;
        pName = new char[strlen(name)+1];
        strcpy(pName,name);
        pAge = age;
    }

    void messageprint()
    {
        cout<<"name:"<<pName<<"age:"<<pAge<<endl;
    }

    ~Person()
    {
        cout<<"这是析构函数"<<endl;
        if (pName!=NULL)
        {
            delete [] pName;
        }
    }
};




int main()
{
    Person person1;
    Person person2("huawei",22);
    Person* person3 = new Person;
    Person* person4 = new Person("apple",33);
    person1.messageprint();
    person2.messageprint();
    person3->messageprint();
    person4->messageprint();
    delete person3;
    delete person4;
}
7.7 动态对象数组创建
int main()
{
   //栈聚合初始化 可以不提供构造函数
   Person person[] = {Person("huawei",10),Person("apple",20)};
   cout<<person[1].pName<<endl;
   
   //创建堆上的对象数组 必须提供构造函数
   Person* workers = new Person[10];
   delete [] workers;
}
7.8 静态成员
  • 不管这个类创建了多少个对象,静态成员只有一个拷贝,这个拷贝被所有属于这个类的对象共享
    static 修饰的静态成员 属于类而不属于对象
    static 修饰的静态成员 必须在类中定义,类外初始化
class Data
{
public :
    int a;
    static int b;
};

int Data::b = 100;

void test01()
{
    //静态成员 可以通过类名直接访问(属于类)
    cout<<Data::b<<endl;//100
    
    Data ob1;
    cout<<ob1.b<<endl;//100
    ob1.b = 200
    
    Data ob2;
    cout<<ob2.b<<endl;//200
}
  • 注意:当静态数据设为private权限时,要同时设置一个public权限的静态函数来供得到那个静态数据;静态成员函数只能操作静态成员数据;而非静态成员函数既能操作静态成员数据,又能操作非静态成员数据。
  • 单例模式设计

单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个
实例而目该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
前三步是不变的 固定的 最后一步根据需求写

#include<iostream>
#include<stdio.h>
#include<string.h>

using namespace std;

//单例模式类
class Single
{
    //1、防止外部实例化对象,构造函数初始化
private:
    Single(){}
    Single(char *a){}
    ~Single(){}

    //2、定义一个静态的指针变量保存唯一实例的地址
    static Single* const p;// const 表示p是常量 一旦创建不能修改

public:
    //3、获得唯一的实例地址
    static Single* getp(void)
    {
        return p;
    }

    //4、用户定义的,任务函数
    void printstr(char *str)
    {
        cout<<"打印:"<<str<<endl;
    }

};

//静态成员初始化
Single* const Single::p = new Single;


int main()
{
   Single* p1 = Single::getp();
   p1->printstr("I am Iron Man!");
}
7.9 C++面向对象模型
7.9.1 成员变量和函数的存储

C++类对象中的变量和函数是分开存储的

7.9.2 this指针
  1. this指针的工作原理

由于C++中成员函数的代码 是多个同类型的实例共用,this就用来解决“区分哪个对象调用函数”的问题
c++通过提供特殊的对象指针,即this指针,解決上述问题。this指针指向被调用的成员函数所属的实例对象
成员函数通过this指针即可知道操作的是哪个对象的数据。this指针是一种隐含指针,它隐含于每个类的非静态成员函数中。this指针无
需定义,直接使用即可。
注意:静态成员函数内部没有this指针,静态成员函数不能操作非静态成员变量

  1. this指针的应用
  • 函数形参 和成员同名 可以使用this 指针
class Person
{
private:
	int a;
public:
	Person(int a)
	{
		this->a = a;
	}
	
}
  • this 完成链式操作
class  Data
{
public:
     Data myPrintf(char *str)
     {
         cout<<str<<"  ";
         return *this;
     }
};

void test01()
{
    Data().myPrintf("a").myPrintf("b").myPrintf("c");
}

int main()
{
   test01();
}
7.9.3 const修饰成员函数

在这里插入图片描述

7.10 友元
7.10.1 友元的语法

使用friend关键字声明友元。
friend关键字只出现在声明处,一个函数或者类 作为了另一个类的友元 那么这个函数或类 就可以直接访问 另一个类的私有数据。
友元主要用在运算符重载上。

7.10.2 普通全局函数作为类的友元

在这里插入图片描述

7.10.3 类的某个成员函数 作为另一个类的友元

在这里插入图片描述
注意:声明与定义的顺序

7.10.4 类 作为另一个类的友元

在这里插入图片描述

7.10.5 友元注意事项

1.友元关系不能被继承。
2.友元关系是单向的,类A是类B的朋友,但类B不一定是类A的朋友。
3.友元关系不具有传递性。类B是类A的朋友,类C是类B的朋友,但类C不一定是类A的朋友

7.11 运算符重载
7.11.1 运算符重载基本概念

运算符重载,就是对已有的运算符重新进行定义,赋与其另一种功能,以适应不同的数据类型。
语法:定义重载的运算符就像定义函数,只是该函数的名字是operator@,这里的@代表了被重载的运算符。
思路:
1、弄懂运算符的运算对象的个数。(个数决定了 重载函数的参数个数)
2、识别运算符左边的运算对象 是类的对象 还是其他.
类的对象:全局函数实现(不推荐) ;成员函数实现(推荐,少一个参数)
其他:只能是全局函数实现

7.11.2 重载<<运算符 (全局函数实现)
#include<iostream>
#include<string>//下面string name 要用

using namespace std;

class Person
{
    friend ostream& operator<<(ostream &out, Person &ob);
private:
    int num;
    string name;
    float score;
public:
    Person(){}
    Person(int num, string name, float score):num(num),name(name),score(score){}
};

ostream& operator<<(ostream &out, Person &ob)
{
    out<<ob.num<<"  "<<ob.name<<"  "<<ob.score;
    return out;
}

int main()
{
    Person lucy(10,"lucy",99.8f);
   //右击 查看 类型
   cout<<lucy<<endl;//operator<<(cout,lucy)这种写法一样
}
7.11.3 重载+运算符 (全局函数实现)

在这里插入图片描述

八、继承

8.1继承和派生

8.1.1 为什么需要继承

目的:提高代码重用,提高开发效率

8.1.2 继承的基本概念

C++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义
的成员。一个B类继承与A类,即从A类派生B类。由此,A类为父类(基类),B类为子类(派生类)。派生类中的成员,包含两大部分:一类是从基类继承过来的(体现出共性),一类是自己增加的成员(体现出个性)。
在这里插入图片描述

8.1.3 派生类的定义
class 父类{};
class 子类:继承方式 父类名
{
	//新增子类数据
};

继承方式:private protected public (推荐)
在这里插入图片描述
总结:所有父类私有在子类中不可访问,公共继承: 保持不变,保护继承:变保护,私有继承:变私有。

公共继承方式:子类中可以访问父类的公共数据以及保护数据,但子类的实例对象 只能访问父类的公共数据(保护数据无法访问)

8.2 继承中的构造和析构

8.2.1 子类的构造析构顺序

在这里插入图片描述

8.2.2 子类调用成员对象、父类的有参构造

子类实例化对象时 会自动调用 成员对象、父类的默认构造。
子类实例对象时 必须使用切始化列表 调用成员对象、父类的有参构造
初始化列表时:父类写类名称 成员对象用对象名

#include <iostream>

using namespace std;

class Father
{
private:
    int a;
public:
    Father(int a)
    {
        this->a = a;
        cout<<"Father的有参构造"<<endl;
    }
    ~Father()
    {
        cout<<"Father的析构函数"<<endl;
    }
};

class Other
{
private:
    int b;
public:
    Other(int b)
    {
        this->b = b;
        cout<<"Other的有参构造"<<endl;
    }
    ~Other()
    {
        cout<<"Other的析构函数"<<endl;
    }
};

class Son:public Father
{
private:
    int c;
    Other ob;
public:
    //父类用父类名,对象成员用对象名
    Son(int a, int b, int c) : Father(a),ob(b)
    {
        this->c = c;
         cout<<"Son的有参构造"<<endl;
    }
    ~Son()
    {
        cout<<"Son的析构函数"<<endl;
    }
};

int main()
{
    Son abc(1,2,3);
    return 0;
}

8.3 子类和父类的同名处理

同名成员:最简单 最安全的处理方式:加作用域

8.3.1 子类和父类 同名成员数据

子类默认优先访问 子类的同名成员
要想访问父类的同名成员 必须加父类作用域

ob.a
ob.Father::a
8.3.2 子类和父类 同名成员函数
ob.func01();
ob.Father::func01();
8.3.3 子类 重定义 父类的同名函数

在这里插入图片描述

8.4 子类不能继承的父类成员

不是所有的函数都能自动从基类继承到派生类中。构造函数析构函数用来处理对象的创建和析构操作,构造和析构函数只知道对它们的特定层次的对象做什么。 另外operator=重载赋值操作运算符也不能被继承,因为它完成类似构造函数的行为。

8.5 多继承

8.5.1 多继承的概念

我们可以从一个类继承,我们也可以能同时从多个类继承,这就是多继承。但是由于多继承是非常受争议的,从多个类继承可能会导致函数、变量等同名导致较多的岐义。

8.5.2 多继承的格式
class 父类1{};
class 父类2{};
class 子类:继承方式1 父类1, 继承方式2 父类2
{
	//新增子类数据
};
8.5.3 多继承中同名成员的处理

与单继承一样:加作用域

8.6 菱形继承

会出现多份公共祖先数据的问题

8.7 虚继承

虚继承只能用于 解决菱形继承带来的问题,其他时候不能用
在继承方式前加virtual 修饰,子类虚继承 父类,子类只会保存 一份公共数据

#include <iostream>

using namespace std;

class Animal
{
public:
    int data;
};

class Sheep:virtual public Animal{};

class Tuo :virtual public Animal{};

class SheepTuo:public Sheep, public Tuo{};

int main()
{
    SheepTuo ob;
    cout<<ob.data<<endl;
    return 0;
}

九、多态

多态是面向对象程序设计语言中数据抽象和继承之外的第三个基本特征。多态性(polymorphism)提供接口与具体实现之间的另一层隔
离,从而将’what”和how’分离开来。多态性改善了代码的可读性和組织性,同时也使创建的程序具有可扩展性,项目不仅在最初创建时
期可以扩展,而且当项目在需要有新的功能时也能扩展。
静态多态(编译时多态,早绑定):函数重载、运算符重载、重定义(重写)
动态多态(运行时多态,晚绑定):虛函数

9.1 虚函数

9.1.1 知识点引入

在这里插入图片描述

以下知识点都是围绕这个需求展开:设计一个算法,可以操作父类派生的所有子类
这导致 这个算法的形参必须为 父类的指针或引用,并且父类指针(引用)指向子类空间地址

9.1.2 父类指针保存子类空间地址(带来的问题)
#include <iostream>

using namespace std;

class Animal
{
public:
    void speak()
    {
        cout<<"动物在说话"<<endl;
    }
};

class Dog:public Animal
{
public:
    void speak()
    {
        cout<<"汪星人在叫"<<endl;
    }
};


int main()
{
    Animal *p = new Dog;
    p->speak();//动物在说话
    return 0;
}

这不满足需求:目标是输出 “汪星人在叫”
原因如下:
在这里插入图片描述

9.1.3 虚函数定义

以上遇到的问题 可用虚函数解决
成员函数前加 virtual修饰、子类重写父类的虚函数

#include <iostream>

using namespace std;

class Animal
{
public:
    virtual void speak()
    {
        cout<<"动物在说话"<<endl;
    }
};

class Dog:public Animal
{
public:
    //这边子类重写父类虚函数时 写不写 virtual都无所谓 已经默认是虚函数;注意:子类虚函数的返回值、函数名、形参类型个数顺序 这三者都必须完全和父类的虚函数保持一致
    virtual void speak()
    {
        cout<<"汪星人在叫"<<endl;
    }
};

class Cat:public Animal
{
public:
    //重写虚函数
    void speak()
    {
        cout<<"喵星人在叫"<<endl;
    }
};

int main()
{
    Animal *p = new Dog;
    p->speak();//汪星人在叫
    Animal *p1 = new Cat;
    p1->speak();//喵星人在叫
    return 0;
}

多态实现条件:有继承、子类重写父类的虚函数、父类指针指向子类空间

9.2 纯虚函数

如果基类一定派生出子类,而子类一定会重写父类的虚函数,那么父类的虚函数中的函数体感觉是无意义,可不可
以不写父类虛西数的西数体呢?可以的,那就必须了解纯虛西数

9.2.1 纯虚函数的定义方式
class Animal
{
public:
	//纯虚函数
	virtual void speak(void)=0;
};

一旦类中有纯虛函数,那么这个类 就是抽象类。
抽象类 不能实例化对象。 (Animal ob;错误)
抽象类必须被继承 同时 子类 必须重写 父类的所有纯虚函数,否则 子类也是抽象类。
抽象类主要的目的 是设计 类的接口

9.3 虚析构函数

只要在父类(带有虚函数)的析构函数前加 virtual就行
这样就能:通过父类指针 释放子类的所有空间

9.4 纯虚析构函数

纯虚析构的本质:是析构函数,各个类的回收工作。而且析构函数不能被继承(即子类要写自己的析构函数)
必须为纯虚析构函数提供一个函数体。(这是与纯虚函数不同之处)
纯虛析构函数 必须类中声明,类外实现

虚析构函数以及纯虚析构函数的区别:
虛析构:virtual修饰,有函数体,不会导致父类为抽象类,
纯虛析构:virtual修饰,=0,函数体必须类外实现,导致父类为抽象类。

#include <iostream>

using namespace std;

class Animal
{
public:
    virtual void speak()=0;
    virtual ~Animal()=0;
};

class Dog:public Animal
{
public:
    virtual void speak()
    {
        cout<<"汪星人在叫"<<endl;
    }
    ~Dog()
    {
        cout<<"Dog的析构函数"<<endl;
    }
};

class Cat:public Animal
{
public:
    void speak()
    {
        cout<<"喵星人在叫"<<endl;
    }
    ~Cat()
    {
        cout<<"Cat的析构函数"<<endl;
    }
};

int main()
{
    Animal *p = new Dog;
    delete p;
    Animal *p1 = new Cat;
    delete p1;
    return 0;
}
Animal::~Animal()
{
    cout<<"Animal的析构函数"<<endl;
}

十、模板

10.1 模板的概述

c++提供了函数模板(function template.)所谓函数模板.实际上是建立一个通用函数,其函数类型和形参类型不具体制定,

  • 个虚拟的
    类型来代表。这个通用函数就成为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次
    即可。在调用西数时系統会根据实参的类型来取代模板中的虛拟类型,从而实现不同西数的功能。c††提供两种模板机制;函数模板和类模
    板类属-类型参数化,又称参数模板
    总结:
    c++面向对象编程思想:封装、继承、多态
    c++泛型编程思想:模板
    模板的分类:函数模板、类模板
    将功能相同,类型不同的西数(类)的类型抽象成虛拟的类型。当调用西数(类实例化对象)的时候,编译器白动将虛拟的类型 具体

10.2 函数模板

10.2.1 函数模板的定义方式

模板关键字:template

#include <iostream>

using namespace std;

template<typename T> void swapAll(T &a,T &b)
{
    T tmp = a;
    a = b;
    b = tmp;
    return;
}

int main()
{
    int a = 10,b = 20;
    swapAll(a,b);
    cout<<"a = "<<a<<"b = "<<b<<endl;

    char a1 = 'a',b1 = 'b';
    swapAll(a1,b1);
    cout<<"a1 = "<<a1<<"b1 = "<<b1<<endl;
    return 0;
}

细节:
调用构造函数有三种方式

1 括号
Animal a1(1);//显示调用

2 等号
Animal a1 = 1;//隐式调用
3 手动调用构造函数
Animal a1 = Animal(1);//也是显式调用 

初始化的意思:对新空间 赋值
拷贝构造函数:

  • 格式:className(const className &obj)
  • 调用:
    • 用一个对象去初始化另一个对象
    • 函数形参是一个对象 不推荐这样写,应该这样:对象作为函数形参时最好使用引用或对象指针 以避免调用拷贝构造函数
    • 函数返回值是一个对象

析构函数在对象被销毀时调用,而对象的销毁时机与它所在的内存区域有关。在所有函数之外创建的对象是全局对象,它和全局变量类似,位于内存分区中的全局数据区,程序在结束执行时会调用这些对象的析构函数。
在函数内部或者代码块中创建的对象是局部对象,它和局部变量类似,位于栈区,出了当前作用域后会调用这些对象的析构函数。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笔记本IT

您的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值