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

文章目录

  • 一、基础知识
  • 二、指针
  • 三、预处理
    • 3.1 内存分区
      • 3.1.1 动态内存 new与delete
    • 3.2 变量的存储
    • 3.3 头文件包含
    • 3.4 宏定义
  • 四、结构体
  • 五、链表
  • 六、C++对C的扩展
    • 6.1 面向过程
    • 6.2 面向对象
      • 6.2.1 面向对象的三大特点
    • 6.3 作用域运算符::
    • 6.4 命名空间 namespace
      • 6.4.1 命名空间使用语法
      • 6.4.2 using声明
    • 6.5 struct 类型增强
    • 6.6 bool类型关键字
    • 6.7 引用
    • 6.8 内联函数
    • 6.9 函数重载
    • 6.10 函数的默认参数
    • 6.11 占位参数
    • 6.12 extern "C"浅析
  • 七、类及封装
    • 7.1 类和对象的基本概念
    • 7.2 类外或其他源文件实现类的成员函数
    • 7.3 类的初始化和清理-->构造函数以及析构函数
      • 7.3.1 构造函数
      • 7.3.2 析构函数
      • 7.3.3 拷贝构造函数
    • 7.4 初始化列表
    • 7.5 类的对象数组
    • 7.6 动态对象创建
    • 7.7 动态对象数组创建
    • 7.8 静态成员
    • 7.9 C++面向对象模型
      • 7.9.1 成员变量和函数的存储
      • 7.9.2 this指针
      • 7.9.3 const修饰成员函数
    • 7.10 友元
      • 7.10.1 友元的语法
      • 7.10.2 普通全局函数作为类的友元
      • 7.10.3 类的某个成员函数 作为另一个类的友元
      • 7.10.4 类 作为另一个类的友元
      • 7.10.5 友元注意事项
    • 7.11 运算符重载
      • 7.11.1 运算符重载基本概念
      • 7.11.2 重载<<运算符 (全局函数实现)
      • 7.11.3 重载+运算符 (全局函数实现)
  • 八、继承
    • 8.1继承和派生
      • 8.1.1 为什么需要继承
      • 8.1.2 继承的基本概念
      • 8.1.3 派生类的定义
    • 8.2 继承中的构造和析构
      • 8.2.1 子类的构造析构顺序
      • 8.2.2 子类调用成员对象、父类的有参构造
    • 8.3 子类和父类的同名处理
      • 8.3.1 子类和父类 同名成员数据
      • 8.3.2 子类和父类 同名成员函数
      • 8.3.3 子类 重定义 父类的同名函数
    • 8.4 子类不能继承的父类成员
    • 8.5 多继承
      • 8.5.1 多继承的概念
      • 8.5.2 多继承的格式
      • 8.5.3 多继承中同名成员的处理
    • 8.6 菱形继承
    • 8.7 虚继承
  • 九、多态
    • 9.1 虚函数
      • 9.1.1 知识点引入
      • 9.1.2 父类指针保存子类空间地址(带来的问题)
      • 9.1.3 虚函数定义
    • 9.2 纯虚函数
      • 9.2.1 纯虚函数的定义方式
    • 9.3 虚析构函数
    • 9.4 纯虚析构函数
  • 十、模板
    • 10.1 模板的概述
    • 10.2 函数模板
      • 10.2.1 函数模板的定义方式

一、基础知识

  • 编译有四个阶段:源代码–>预处理(不判断语法是否正确)、编译–>汇编文件、汇编–>二进制文件、链接–>可执行文件
  • 使用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+bcout<<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 __cplusplusextern "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_Hclass 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;//100Data ob1;cout<<ob1.b<<endl;//100ob1.b = 200Data 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)
  • 调用:
    • 用一个对象去初始化另一个对象
    • 函数形参是一个对象 不推荐这样写,应该这样:对象作为函数形参时最好使用引用或对象指针 以避免调用拷贝构造函数
    • 函数返回值是一个对象

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

C++语言入门到精通相关推荐

  1. c语言是学电脑吗,c语言入门至精通这些天一直有人问我,c语言好学吗?我是个新手...

    这些天一直有人问我,c语言好学吗?我是个新手,该如何学习? 其实,这类问题困扰着很多新手.在如何学习之前,我们想简单的了解一下什么是C语言: C语言是一种计算机程序设计语言.它既有高级语言的特点,又具 ...

  2. 视频教程-C语言入门到精通-C/C++

    C语言入门到精通 15+年专业程序员从业经验,精通多门编程语言,多年系统架构设计经验,从事网络安全行业,项目数过100,代码量过100W.精通C/C++语言.Linux系统开发.Python语言.GO ...

  3. 【C语言入门到精通】04 数据类型

    [C语言入门到精通]04 数据类型 If a program manipulates a large amount of data, it does so in a small number of w ...

  4. python语言入门与精通-终于懂得python从入门到精通教程

    对只用一种语言来构建某个项目的情况而言,Javascript和它的框架是非常有用的.Angular.js可以控制展示给用户的网站前端.Node.js将作为管理网站所有内容的网络服务器.Express. ...

  5. c语言从入门到精通_C语言基本概念(上)【C语言入门到精通】

    C语言基本概念 Syntactic sugar causes cancer of the semi-colons.[0] 码字不易,对你有帮助 点赞/转发/关注 支持一下作者 微信搜公众号:不会编程的 ...

  6. python语言入门与精通-Python 为什么入门容易 精通难

    Python都说是最容易学习的编程语言,但是为什么还是有很多的小白在学习的过程中学的云里雾里,一天到晚都是报错,异常,这也怪不得会有那么多人说,学Python是从入门到入土! 其实一门编程语言不像语文 ...

  7. python语言入门与精通-Python从入门到精通

    来自于我个人学习Python的笔记,不过我尽力让它更加适合别人的口味. Python的官方介绍是: Python是一种简单易学,功能强大的编程语言,它有高效率的高层数据结构,简单而有效地实现面向对象编 ...

  8. c语言入门至精通(全集),C语言入门至精通(全集)知识讲解.ppt

    教材.参考书与课时安排;熟记C语言的基本概念熟悉Turbo C的上机操作环境会读.会编.会调试C程序 学习要点 熟记C语言的语法 学会算法分析与算法设计 ;第1章 C语言概述;;1.1 C语言发展历史 ...

  9. C语言 入门到精通100题

    C 语言编程经典 100例 [程序1]题目:有1.2.3.4个数字,能组成多少个互不相同且无重复数字的三位数?都是多少? 1.程序分析:可填在百位.十位.个位的数字都是1.2.3.4.组成所有的排列后 ...

最新文章

  1. RNN循环神经网络的直观理解:基于TensorFlow的简单RNN例子
  2. nacos持久化配置MySQL8_Nacos 数据持久化 mysql8.0
  3. java垃圾回收算法超详细全解
  4. MariaDB 10的复制 集群 高可用搭建 大表拆分【持续更新中】
  5. 华为二面!!!面试官直接问我Java中到底什么是NIO?这不是直接送分题???
  6. Linux 服务器注意事项
  7. uboot load address、entry point、 bootm address以及kernel运行地址的意义及联系
  8. [css] 用css创建一个三角形,并简述原理
  9. 116_Power Pivot 先进先出原则库龄库存计算相关
  10. Redhat7离线安装mysql_linux 离线安装mysql7或者8
  11. 利用ev3dev编程
  12. python计算贷款购房月供、利息
  13. Docker【1】 | 带你快速了解Docker,So easy
  14. python中content什么意思_python – 解析html时为什么我需要item.text和item.text_content()其他...
  15. 计算机组成原理课程设计-基本模型机的设计与实现
  16. 数字化转型:企业转型的数据治理方式
  17. Win8系统flash无法播放视频怎么办?
  18. 技术出身能做好管理吗?——能!
  19. M1 macbook spicy和numpy版本不匹配
  20. android+学籍管理,论文基于android的学籍管理系统的设计与实现.doc

热门文章

  1. [源码] Spark如何划分Stage
  2. Symantec Endpoint Protection 14最新卸载教程(亲测好用,无需密码,暴力删除)
  3. 电脑箭头,电脑箭头符号怎么打出来(往返箭头符号图案)
  4. Power BI与PowerQuery、PowerPivot的关系
  5. windows系统无法远程桌面提示CredSSP 身份验证协议和远程桌面客户端
  6. QPainter的渐变
  7. vue2.0实战案例之高级教程-老孟编程
  8. 8 行代码用Python画一个中国地图 !
  9. 关于No property 属性名 found for type 类名!的问题及解决方式之一
  10. matlab设置固定的窗宽窗位,【经验谈】如何设定窗宽窗位,附正常人体组织CT值...