day1
一、C++:c plus plus
C:更多的是面向过程,为了实现一个功能,如何通过一个过程来实现,注重的是算法
C++:更多的是面向对象,为了实现一个功能,如何构造一个对象模型来实现
二、C++的课程目标:
1、掌握语法规则
2、转变编程细想
三、C和C++的关系:
1、C++是向下兼容C的
2、C++在C的基础上新增了很多新增了很多新的特性、关键字、语法规则、设计思想
3、C更多的是面向过程,C++更多的是面向对象
4、面向对象也是通过面向过程来实现的
四、命名空间
作用:解决命名冲突
定义:
namespace命名空间名
{
变量定义;
函数定义;
类姓名定义;
};
namespace BB
{
extern int a;
extern void fun();
};
总结:
使用命名空间中的成员:
1、命名空间名::变量名;(::--作用域运算符)
2、Using namespace 命名空间名;变量名;
五、标准输入输出流
标准输入:键盘 scanf
标准输出:显示屏 printf
一切皆对象
标准输入流对象 cin
标准输出流对象 cout
对流的操作就是对文件的操作
文件--》输入流---》内存 键盘---》cin---》变量
内存---》输出流---》文件 变量---》cout---》显示屏
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int b = 'p';
float c = 2.4;
cout << "hello world"<<endl; // \n
cout <<"a = "<< a <<endl;
cout <<"b = "<< b << ",c = " << c << endl;
cout << "input: a b c" <<endl;
cin >> a >> b >> c;
cout <<"a = " << a << endl;
cout <<"b = " << b << ", c = "<< c << endl;
return 0;
}
六、引用
出现的原因:弱化指针的使用
引用:就是变量的别名
引用的定义:数据类型 &引用名 = 变量名;
比如:int a = 10;
int &b = a; //b就是a的别名
int &c = a; //c是a的别名
int &d = b; //d是b的别名,其实也就是a的别名
不需要额外开辟空间,是直接访问
注意:
1、引用必须初始化
2、引用一旦初始化完了,就不能再是其他变量的引用
3、对引用的操作就是对变量的操作
引用是为了弱化指针,那指针一般用再哪些地方呢?
1、操作连续的内存空间
2、作为函数形参——可以用引用
3、作为函数返回值---有时也可以用引用
七、函数重载
函数重载:
1、函数名相同(功能相似)
2、参数不同(类型、个数)
3、与返回值无关
最终会调用哪个函数,由所传的参数决定
函数名真的可以一样吗?
函数的本质:存储在内存代码段的一段二进制代码(占用了一块内存空间的)
函数名的本质:这块内存空间的首地址
所以函数名肯定不能一样
在C++中 g++ 1.cpp---》生成a.out
nm a.out nm:查看二进制文件中的符号
总结:函数重载的原理:g++编译器会对函数名按照函数形参来进行重命名
八、默认参数
九、C++如何调用C库函数
1、自己制作C库
C库无法改变
能改变的就是不要让g++对该C库的函数重命名
1、引用和指针的区别:
1、指针是一个变量,这个变量存储的是一个地址,指向内存的一个存储单元;
2、引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名。
3、引用不可以为空,当被创建的时候,必须初始化,指针可以是空值,可以在任何时候被初始化。
4、可以有const指针,但是没有const引用。
5、指针可以有多级指针,引用只能是一级引用。(int **p合法,int &&a不合法)
6、指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;
7、指针的值在初始化后可以改变(指向其他的存储单元),引用在初始化后就不会再改变了。
8、”sizeof引用 "得到的是所指向的变量(对象)的大小,而“sizeof指针”得到的是指针本身的大小
9、指针和引用的自增(++)运算意义不一样
10、如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄漏
DAY2
一、面向对象编程中的类和对象
面向对象编程:注重的是结果(点外卖,等吃)
面向过程编程:注重的是过程。按步骤一步一步的去做(把大象关进冰箱需要几步)
结构体:
为了描述一类事物的属性而自定义的数据类型。
C里面的结构体是面向对象,但是面向的不彻底,只能描绘属性。
面向对象:
把属性和行为整合起来,描述这一类师傅,更形象生动!
学生:学号、姓名、性别、专业、班级等
行为:上课、吃饭、写作业、睡觉等
C++中的结构体和C中的结构体的区别:
#include <stdio.h
struct std_t
{
//编号
int sno;
//姓名
char name[20];
char sex[4];
void show()
{
printf("sno = %d,name = %s,sex = %s\n",son,name,sex);
}
void study()
{
printf("study----\n");
}
//以上全部注释掉
};
int main()
{
printf("%d\n",std_t);
return 0;
}
1、C中的结构体里面不可以有函数;而C++中的结构体里面是可以有函数的!
2、C中空结构体占0字节,C++中空的结构体占1字节的内存空间!!
C++中的面向对象编程主要是通过类这个数据类型来实现的!
什么是类?
一种自定义的数据类型。为了描述一类事物的属性和行为而自定义的一种数据类型!
关键字:class
定义的格式:
class 类名 {属性和行为};
属性:数据成员(成员变量)
行为:成员函数
C++中类与C++中结构体的区别?
1、关键字不同,类的关键字是class,结构体的关键字是struct
2、初始化方式是不同的。
3、成员的默认访问权限是不同的,结构体中的访问权限默认是公有的,public;类中的成员访问权限默认是private,私有的!
私有的外部是不可以访问的!
类里面的访问限定符一共有三种:
1、public:公有的, 可以在类的内部以及类的外部直接访问
2、private:私有的, 可以在类的内部使用,类外部是不可以访问
3、protected:受保护的, 可以在类的内部使用,类外部是不可以访问
类的内部:{};里面就是类的内部
类的外部:{};外面就是类的外部
类定义的完整的格式:
class fly //(类名)
{
public:后面的内容都是public修饰的
private:
protected;
};
二、封装
什么是封装?
把一类事物共同的属性和行为提取出来,提取的这个过程称为抽象;
把提取出来的共性用类这种自定义的数据类型包起来,这个过程是封装。
把该隐藏的隐藏起来,把暴露的暴露出来,通过访问权限来设置!
哪些是需要隐藏的呢?
属性,也就是数据成员,放在private声明区
哪些是需要暴露的呢?
行为, 也就是成员函数, 放在public声明区
总结:
把一类事物的属性和行为提取出来,用类这个自定义的数据类型包起来,该隐藏的隐藏起来,该暴露的暴露。
类与对象是什么关系:
类是抽象的,对象是具体的!
类是对象的抽象,对象是类的具体实例!
三、this指针
谁调用成员函数,this指的就是哪个对象的地址!
this存在于类中的每一个成员函数中!在类的每一个成员函数中都有一个隐藏起来的参数,参数的类型就是当前这个类,类型的一个指针变量!类名* const this;
class Phone
{
public:
void take_photo(Phone *const this)
{
cout <<"拍照中..."<<endl;
}
};
注意:
1、在类的成员函数的实现中,访问类的成员都记得加this指针
2、当类的成员函数的形参的名字与类的数据成员名字一样的情况下,加上this指针便于区分谁是类的成员,谁是传进来的参数!
3、this指针保存的是类对象的地址,谁调用成员函数,this指向的就是谁的地址!
4、类的成员函数中都有一个隐藏的参数this,作为第一个参数存在,使用的时候不需要我们去传参,自己会偷偷的把对象的地址传进去,保存到this中
5、不管是在类的内部还是类的外部,访问类的成员,必须有对象!
普通的对象:对象名+.+成员;指针类型的对象:对象名+->+成员!
类中的数据成员,每创建一个对象它都拥有一份数据成员,而成员函数是所有对象都共用的。
四、两个特殊的成员函数—构造函数
本质:就是函数
作用:创建对象的时候,给对象(数据成员)在内存中分配空间,并且对数据成员进行初始化
什么时候去调用构造函数:创建对象的时候
谁去调用:系统调用,当我们自己没有写构造函数的时候,系统会帮你生成默认的构造函数;
如果我们自己写了,系统就不会帮我们生成这个构造函数!
验证:在创建对象的时候,到底有没有执行构造函数?
构造函数定义的格式:
函数名和类名字是一样的
类名(参数列表—参数是不确定的){}
说明:构造函数是可以重载的
构造函数是没有函数类型的。
什么是默认构造函数:
注意:在一个类中,默认构造函数只能有1个!!!
普通的构造函数:自己写的,有两个或者两个以上的参数,并且不是所有参数都有默认值!
创建对象的方式:
也可以创建数组:
拷贝构造函数
拷贝构造函数的定义格式:
函数名和类名是一样的,有一个参数,并且这个参数是类类型的引用
类名(类名& ){}
作用:就是将被拷贝对象的数据成员的值一一赋值给新对象的数据成员!
何时执行拷贝构造函数:用一个已经存在的对象去初始化一个正在创建的新的对象的时候,会执行拷贝构造函数!
分析原因:
如何解决:各自给各自的指针变量去堆区分配空间,保证存储的数据是一样就可以!
深拷贝:
当有在构造函数里面使用new的方式给数据成员去堆区分配空间的,在析构中有去delete的,如果要进行拷贝,必须进行深拷贝。 就是在拷贝构造函数中重新给指针类型的数据成员在堆区分配内存空间
注意:
系统帮我们生成的拷贝构造函数是浅拷贝构造函数!如要进行深拷贝,拷贝构造函数需要自己去写!
何时进行深拷贝,何时进行浅拷贝?
看构造中是否有new动态分配过空间!!!!
五、两个特殊的成员函数--析构函数
本质:函数
作用:在对象生命周期结束之前,做数据的清理工作!特别是在构造函数中如果有用new或者malloc的方式在堆区动态分配空间的变量,要进行释放的动作。
什么时候调用: 在对象生命周期结束之前。自己没有写,系统生成,自己写了,系统就不会再生成了!
谁去调用: 系统调用
析构函数的定义格式:
没有函数类型, 函数名和类名一样,前面加一个~符号,并且没有参数(不可以重载)。
~类名(){}
注意:析构函数是不可以被重载的!
注意:多对象创建,先构造的对象,后析构!
六、new与malloc的区别?
-
malloc是C中标准的库函数,free也是C中标准的库函数,要用到他们,需要加#include<stdlib.h>;而new和delete是C++中的运算符。
-
malloc开空间,首先要说明分配空间的大小;new开空间,只要跟数据类型就可以,如果是连续的空间,只要在类型后面加上[几块]。
-
malloc分配空间得到的地址是void*,一般情况下都要进行强转;而new分配得到的地址的类型与new之后的类型是一致的,不需要强转!
-
malloc分配的空间使用memset去清空,new分配的空间,只要在类型后面+()就可以自动进行初始化!
-
malloc分配的空间,要用free去释放,new的空间用delete去做释放
-
如果是对类对象在堆区分配空间,new的时候会自动调用构造函数,delete的时候会自动调用析构函数,malloc和free并不会这样子处理!
Day3
一、Static修饰类的成员
1、static修饰局部变量:延长局部变量的生命周期
2、static修饰全局变量:限制全局变量的作用域
3、static修饰函数:限制了作用域
static修饰的变量,在编译阶段就已经存在了
1-1static修饰类的数据成员
如何初始化:
数据类型 类名::变量名=初始值;
如何访问:
通过对象的方式去访问: 对象名.变量名;
通过类的方式去访问: 类名::变量名;
结论:
1、类的静态的数据成员要类内做声明,类外做初始化!
2、类的静态的数据成员属于类,不属于具体的某一个对象;但是所有对象都可以访问!
3、类的静态的数据成员没有对象,也可以访问!
1-2static修饰类的成员函数
结论:
1、类的静态的成员函数中是没有this指针的,因为类的静态的成员函数没有对象也是能使用的!this存在没有任何的意义!
2、类的静态的成员函数是不能访问类的非静态的成员的!
3、类的非静态的成员函数是可以访问类的静态的成员!
4、类的静态的成员函数的访问:对象的方式和类的方式都是ok的!
二、Const修饰类的成员
const的作用:
const int a = 100; //a的值不可以修改,此时a相当于常量
int b = 10;
const int *p = &b; // *p的值是不可以变化,p的指向是可以变化的
int * const p = &b;//p的指向是不可以变化的,但是地址中的值是可以修改的(*p)
strcpy(char *, const char *);
2-1、const修饰类的数据成员
如何解决这个问题:
使用初始化列表的方式给const修饰的数据成员进行初始化
初始化列表的顺序并不会影响数据成员的赋值!
初始化列表只能出现在构造函数的地方。
当一个类中都是1const修饰的数据成员,并且这个类里面有多个构造函数,那么在每一个构造函数中都要使用初始化列表对const修饰的数据成员进行初始化!
2-2、const修饰类的成员函数
说明:this是不可以修改的,但是*this里面的内容是可以修改的
说明: this是不可以修改的, *this的值也是不可以修改的。
总结:
1、const修饰类的成员函数,本质上修饰的是隐藏的this指针。
2、Const修饰的成员函数,只能访问类的成员,不能做修改。
3、Const修饰的成员函数就是常函数!
说明:const修饰的成员函数中不能访问非const修饰的成员函数;
说明:非const修饰的成员函数中是可以访问const修饰的成员函数!
2-3、const修饰类的对象
说明:const修饰的类对象只能调用const修饰的成员函数!
三、mutable关键字的作用
作用:解决const修饰类的成员函数不可以修改数据成员值的问题。但是尽量少用。
C++有提供约束的方法,同时也提供了打破约束的方法!
尽量少用!!!!
四、友元
什么是友元:就是朋友的意思
分类:友元函数以及友元类。
关键字:friend
为什么会有友元出现:
也是用来打破规则的!
在类外当要访问类的私有的数据成员,只能通过public区公有的接口函数来访问,如果访问次数比较少,调用接口函数对内存的消耗不会很大!如果是频繁的访问,调用函数要进行出入栈,出入栈的次数越多,对内存的开销就越大!此时再想,如果能够直接去访问到私有的数据成员多好!
实现方法:
使用友元
几种情况:友元函数:
1、普通函数做类的友元
2、类里面的成员函数做另外一个类的友元
3、友元类:类中多个函数中都要频繁的访问私有的数据成员,把整个类都作为友元!(一个类做另外一个类的友元)
4-1普通函数做类的友元
运行之后发现:![](https://img-blog.csdnimg.cn/61c0c417d1944c44bb95de40f8536e9e.png)
总结:什么时候执行拷贝构造
1、类对象作为函数形参的时候,调用函数的时候也会执行拷贝构造函数!
2、用一个已经存在的对象去初始化一个正在创建的新的对象的时候,执行拷贝构造函数!
如下使用引用传参:
注意:类对象要作为函数参数的时候,建议使用引用传参!
4-2、类的成员函数做类的友元
思路:当类B的成员函数要做类A的友元,要先定义类B,并且类B中的成员函数要在类的外部去定义!在类B中又出现类A这个类型,先对类A做前向声明!
注意:类的友元函数并不是类的成员,所以不受类访问限定符的限制!放在public、private、protected声明区都是可以的!
Day4
一、运算符重载
回顾什么是函数重载:
函数名相同,功能相似,参数不同(参数个数不同、参数类型不同,参数顺序不同),与返回值无关的一组函数互为重载。
运算符重载:
运算符有哪些? +、-、*、/、=、++、--、 只适用于基本数据类型之间的计算:数值类型、char!
为什么要进行运算符重载:
可以让各种类型的数据都可以参与相关的运算,让代码更丰富!
以复数类为例子讲解
运算符重载的方法:
1、友元函数重载来实现
格式: 类型 operator运算符(操作数1,操作数2){如何实现运算}
2、成员函数重载来实现
格式: 类型 operator运算符(操作数2){实现计算}
注意事项:
3、运算符重载完之后操作数的个数不能发生变化!+必须两个操作数、++、--必须是一个操作数!
1-1、+运算符重载
友元重载+:
代码验证:![](https://img-blog.csdnimg.cn/31355133e6aa46e7ac37227ef334389b.png)
成员重载+运算符:
说明:因为成员函数隐藏的this指针就能代表第一个操作数了,只需要传入第二个操作数即可!
1-2、++运算符重载
1-2-1、友元重载
++i: 先对i的值+1,然后再使用i的值参与运算
i++: 先使用i的值参与运算,然后再对i的值+1
《1》前置++的友元重载:
《2》后置++的友元重载:
1-2-2、成员重载
《1》前置++的成员重载:
《2》后置++成员重载:
问:什么时候函数类型可以使用引用?
当你传进来的是已经存在的对象,返回去的也是已经存在的对象,此时函数类型就使用引用类型就可以!
思考: 所有的运算符都可以使用成员和友元重载吗?
不是!!! =赋值运算符只能使用成员函数去重载。
<<运算符只能使用友元去重载!!!
1.3左移运算符重载
只能友元重载
二、模板
什么是模板:ppt模板、简历模板
泛型编程:广泛的数据类型。使用可变的数据类型来实现不变的算法
算法只要写一次好了,把可变的地方用形参来表示! 把数据类型给参数化。
实现两数求和
int add(int a, int b){return a+b;}
double add(double a, double b){return a+b;}
char add(char a, char b){return a+b;}
只要数据类型不同,就需要新增一个函数,来实现功能。多个函数就互为重载!
存在的问题:
就会造成代码冗余!!!
如何解决: 先提起框架(共同的逻辑提取出来,不同的地方用参数来代替)
T add(T a, T b){return a+b;}
模板分类: 函数模板、类模板
2-1函数模板
格式:
template<class/tyename T>
函数类型 函数名(参数列表){函数体}
注意:函数模板是不占内存空间的!!!只有变成真正的函数,才会在内存中占空间!
实例化:
函数模板是可以实现重载的!!
2-2、类模板
以单链表为例:每一个节点中有数据域+一个指针域
数据域:存什么类型的数据 int、double、结构体类型数据
指针域:存下一个节点的地址
通用链表设计:
数据域:保存地址 void* 可以接受任意类型的指针变量
指针域:存下一个节点的地址
链表类:
链表是由一个一个的节点组成。
节点类:
数据域+指针域
类模板实现单链表:
成员函数类外定义的格式如下:
说明:类模板中的每一个成员函数都是函数模板!!!
注意: 类模板不要分文件去实现! 不要把类模板的定义放在.h中,而成员函数的实现放在cpp中,是不支持的!
都要放在同一个文件中!!!
3、C++标准模板库STL
STL:标准模板库,里面全部都是用模板去实现!
STL中的6大组件是什么?
容器
算法
迭代器:面向对象版本中的指针 泛型指针
适配器
分配器
函数对象
STL中提供的容器: vector—数组、list—双向循环链表、map—关联容器
vector容器的结构:
动态数组: 数组的长度是不确定的!
在内存中的地址是连续的,支持索引访问,也支持迭代器访问;
动态如何体现:
当容器的容量用尽的时候,自动的会去内存中找一块更大的连续的内存空间,然后把原来内存空间中的数据拷贝到新的内存中,把原来的内存给释放掉还给系统!
更大的连续的空间是多大:
一般是原来空间的1.5倍或者是2倍的大小!!!
vector容器的最大容量?测试
回顾数组:
- 数组长度是固定,存在的问题:资源要么浪费,要么不够用
- 数组在内存中的地址是连续,支持索引的方式去访问;也可以通过指针偏移的方式去访问!访问效率的是高的;插入、删除效率是低,因为要进行大量的数据搬移!
- 数组名代表整个数组的首地址
使用:
day5
STL中list容器
结构: 双向链表
特点: 插入、删除的效率相对是高的;访问的效率相对vector来说是低的
使用:
#include <list>
创建list对象
增删改查的操作----》查看手册
2.STL中map容器
底层结构: 红黑树---》平衡二叉树
结构体设计不同: 多了一个数据成员表示黑色还是红色
Key-value的键值对
根据key可以找到对应的value数据,key与value是一一对应的关系!!!
特点:
查找速度会很快
结论:
map容器会自动进行排序,排序的方式根据key值从小到大!
map容器中是不允许出现相同key的数据的!
代码如何表示map容器
map<key, value> key能否是char *类型:不要设计成char *
头文件 #include<map>
3.继承
为什么要使用继承:
减少代码冗余,新写的类可以直接使用已经存在类的代码,提高开发效率,减少错误!提升代码的复用性!
什么时候使用继承:
当一个类是另外一个类的特殊版本的情况下,就可以使用继承!
学校 中学
继承是如何继承:
子类要全部继承父类中的成员函数+数据成员!
友元函数是否会被继承:不是类的成员,是不会被继承的,它不具有传递性!
继承的方式有几种:
3种,public、private、protected
继承的格式:
Class 子类名:继承方式 父类名,继承方式 父类名{};
以人类为基础 教师类
验证<1>.数据成员确实被继承到子类中!!!
验证<2>父类中的成员函数确实被子类继承了!!!
在子类中是可以调用到父类的成员函数的!!!
通过子类对象,是可以访问到父类中的成员函数的!
验证<3>三种不同继承方式的区别:
Public继承:
Private继承:
Protected继承:
总结:
- 只有使用public继承方式,父类中是什么样子的访问权限,在子类中仍然是什么样子的访问权限!
- 不管是public\private\protected,父类私有的数据成员在子类内、外都不可以访问!
- 父类的Protected成员不管是private、public、protected继承,在子类内都是可以访问的!
- 一般继承都采用public继承!!!
4.子类的构造函数
创建子类对象的时候,发现先执行了父类的构造函数,然后才执行了子类的构造函数!
规则:
1、数据成员在哪个类中写的,那么数据成员就应该由谁去做初始化!
2、创建子类对象的时候,首先执行父类的构造函数,默认执行的是父类的默认构造函数!我们可以指定让去执行父类的某一个构造函数。
如何指定:使用初始化列表的方式
3、子类只会对自己类内写的数据成员初始化,从父类继承的数据成员要由父类自己去做初始化!
5.子类的析构函数
子类对象生命周期结束的时候,先执行了子类的析构函数,再执行父类的析构函数!
先构造的,后析构!!!
6.多层继承
A》B-》C-》D
人类-》工人类-》教师类
<1>构造的执行:从最上层的父类的构造函数开始执行,最后执行自己的构造函数!
<2>析构的执行:先构造的后析构!
<3>在继承关系中,子类中出现了和父类同名的函数,此时父类中同名的函数被隐藏了,通过子类对象去调用的时候,执行的是子类中对应的函数!如果想要调用父类中的同名函数,要通过类名::函数名()的方式去调用!
<4>子类中什么时候需要写一个和父类同名的函数呢?
当子类从父类中继承来的成员函数,实现的效果并不能满足子类需求的时候,子类要重定义父类的同名函数。
重定义(隐藏)、重载的区别:
重定义是在继承关系中的父类与子类中出现的,子类的函数和父类的函数要同名!!!
重载是要在同一个作用域内的!!!
7.多重继承—菱形继承
A被B、C继承,B、C类被D继承了!
例子: 打印机、复印机、打印复印一体机
家具 沙发 、床 沙发床
人 工人类、 农民类 农民工
演示:
分析原因是什么?
因为类D继承于类B和类C;而类B继承类A,类C继承于类A,这样子就导致在类D中就包含了两份类A中的成员。此时通过类D去访问类A中的成员,就出现了访问不明确的问题!
如何解决:
采用虚继承!
类B和类C虚继承类A,即可解决这个问题!
原理:
8、类与类之间的关系
是的关系: is a的关系
中学 是学校中的一种
有的关系: has a的关系
学校里面有班级、有学生、有老师!
Day6
1、多态的概念
水:在不同的温度的下呈现不同的状态
雪、冰、水蒸气、水等!
在c++中代码,多态是如何体现的?
调用函数,函数名一样的,当传入不同的参数时,执行不用的函数体!--函数重载
函数功能相似的,函数名相同,参数不同与返回值无关的一组函数,就是函数重载!
函数重载、运算符重载就是一种多态!
2、静态多态和动态多态
静态库:在编译阶段,会链接到可执行文件中的
动态库:在运行阶段,会链接到可执行文件中的
Add(int,int) =è Z3_addii(int,int)
Add(double, double) =è Z3_adddd(double,double)
静态多态(静态绑定):在编译阶段,就知道执行哪一个函数体
动态多态(动态绑定):在运行阶段,才能知道要执行哪一个函数体
绑定:调用函数语句与对应的函数体进行了绑定!
总结区别: 链接的时刻是不同的,一个在编译阶段,一个在运行阶段!!!
案例:
圆形类 --计算面积和周长
矩形类 --计算面积和周长
三角形类 --计算面积和周长
形状类 计算面积和计算周长的函数
和我们想要的效果不一样,原因是:
把子类对象的地址赋值给父类类型的指针,通过父类类型的指针变量去操作子类对象,只能调用到子类从父类中继承来的成员函数!
画图分析:
指针的类型就决定了指针变量可以访问的内存大小了!
如何解决:
把父类对应的函数写成虚函数即可!
格式:virtual 函数类型 函数名(参数列表){}
成员函数在父类中是虚函数,被子类继承之后,在子类中,对应的这个函数也是虚函数!!!
总结动态多态的实现条件:
- 一个父类,多个子类(继承关系)
- 父类中有虚函数,子类要重写父类的虚函数
- 要使用父类的引用或者指针去操作子类对象,调用虚函数的时候。
问:有虚函数一定会触发动态吗?
不一定!必须要满足以上的三个条件之后,才会触发多态,缺一不可!!!
问:为什么要实现多态?
减少代码冗余,提高代码的复用性,提高执行效率。
3、多态的实现原理
引入虚函数表概念:说明这个表里面存的都是虚函数!函数只要知道了函数地址,就可执行对应的函数体!换句话来说:虚函数表中存的是多个虚函数的地址!==》函数指针数组:就可以保存多个函数的地址!本质上在类中只需要保存函数指针数组的数组名就可以了!
数组名代表地址,也就是说在类创建对象的时候需要分配4字节内存空间保存函数指针数组的数组名!
一个类中如果有虚函数,那么这个类里面就有一张虚函数表,表里存的就是这个类所有虚函数的地址!
如果这个类A被另外的一个类B继承了呢? B里面也有一张虚函数表,虚函数表里面存的就是这个类所有虚函数的地址!
画图分析:
总结:
如果父类中的虚函数没有被子类重写,那么在子类虚函数表中存的仍然是父类中虚函数的地址!此时通过父类的指针去操作子类对象,调用虚函数执行的是子类从父类中继承得来的函数(因为此时子类虚函数表中存的都是父类虚函数的地址)!如果在子类中重写了父类的虚函数,此时子类的虚函数表中存的就是子类重写后虚函数的地址!
此时通过父类的指针去操作子类对象,调用虚函数执行的就是子类重写后的函数!
4、重定义、重写、重载的区别?
重定义(隐藏):在继承关系中,子类定义了和父类同名的函数!
何时进行重定义:从父类中继承来的函数实现的效果满足不了子类的需求,此时子类可以重定义父类的函数!
重写(覆盖): 在继承关系中,子类定义了和父类一样的虚函数!要求:子类重写的函数的首部必须和父类中是一模一样的,只有函数体是不一样的!
重载: 必须在同一个作用域内,函数名相同,参数不同与返回值类型无关的一组函数!
5、多态中存在的问题以及解决方案
存在的问题:
通过父类的指针去释放子类的对象,发现只执行了父类的析构函数,并没有执行子类的析构函数!那如果有在子类的构造函数中使用new的方式动态分配内存空间,那么就需要在子类的析构中使用delete去做释放!没有执行子类的析构函数,会造成内存泄漏!
如何解决:
目标:是希望子类的析构函数能被调用!
方案:把父类析构函数变成虚函数!!!
1.在类设计的时候,为什么建议要把父类的析构函数写成虚析构函数呢?
当我们通过父类的指针去释放子类的对象,只会执行父类的析构函数,并不会执行子类的析构函数,就会造成内存泄漏的问题!
2.为什么不执行子类的析构函数,就会造成内存泄漏呢?
因为如果有在子类的构造函数中使用new的方式动态分配内存空间,那么就需要在子类的析构中使用delete去做释放!没有执行子类的析构函数,堆区空间释放不了,就会造成内存泄漏!
6、抽象类
抽象: 不够具体
抽象类:就是类中的虚函数我们不知道该怎么去实现,但是它的所有子类都有不同的实现方法,在此我们就把类中的这个虚函数写成纯虚函数!
就是在类中,这个函数不需要写函数体了!但是子类中都要去实现的!
抽象类:就是一个类中如果至少有一个纯虚函数,那么这个类就是抽象类!
作用: 就是为了被继承,然后去重写对应的纯虚函数的!
纯虚函数定义格式:
virtual 函数类型 函数名(参数列表) = 0; 不需要自己去实现(不需要写函数体)
说明:抽象类是不能创建对象的!如果子类继承之后,也没有去重写这个纯虚函数,那么子类也是一个抽象类,不能创建对象!
抽象类的子类要求都要去重写父类的纯虚函数!不然子类也是抽象类!
如果不想写成纯虚函数的话,在类中对应函数的函数体写成也是可以的!
7.接口类
接口类就是抽象类的应用!
接口类里面的所有的成员函数都纯虚函数,并且是没有数据成员的!
这样子的类作用是什么?就是为了描述一些能力或者是表达一些协议而存在的!
例子:
游戏 攻击能力
飞机: 起飞的能力、降落的能力
小鸟: 起飞的能力、降落的能力
题目:
当做起飞动作的时候,如果是小鸟,可以实现捕食的功能;如果是飞机的话,可以实现载客或者运输货物的功能!
Day7
1、类对象占的内存大小—空类的说明
分配多大的内存空间取决于 类的数据成员
遵循字节对齐的原则
空类占的内存大小: 1字节
为什么要给分配1字节呢? 作用:就是为了让你能调用它的成员函数!
空类中默认的成员函数有6个!!!!
默认的构造函数、析构函数、拷贝构造函数、赋值运算符、&运算符、const修饰的&运算符!
2、Explicit关键字
作用:防止构造函数单参数的情况下进行隐式类型的转换
使用explicit 关键字修饰的构造函数,被称为转换构造函数!!!
构造函数有几种:
默认构造: 无参系统生成的、无参的自己写的、有参的并且所有参数都有默认值
普通构造: 有两个或者两个以上的参数,并且不是所有参数都有默认值
拷贝构造函数: 深拷贝、浅拷贝
转换构造函数: explicit修饰的
5、final关键字
作用: 修饰类以及类中的成员函数!
6.Inline关键字
作用:修饰函数的,被inline关键字修饰的函数,被称为内联函数!
什么是内联函数:
- 短小的---3-5行
- 逻辑简单 不能有for、while、if等语句存在
- 频繁调用的
为什么会把这样子形式的函数写成内联函数呢?
频繁函数调用,要进行频繁的出栈入栈,出入栈次数多了,内存开销就大了!会影响程序的运行的效率!
在编译的时候直接把函数调用的语句用函数体直接去替换了!减少了调用函数的过程!
注意: 内联函数并不是你把函数用inline修饰,它就一定按内联的方式处理,这个是由编译器来判定的,它觉得这个函数能按内联处理,这个函数就是内联函数!
内联函数其实是我们程序员对编译器的建议!
宏函数: 本质是宏,但是可以实现函数的效果!
特点: 短小、逻辑简单的、使用时候直接进行文本替换!
内联函数与宏函数的区别?
思考: 宏函数与内联函数区别?
是什么? 宏函数本质是宏;内联函数本质是函数
使用: 宏是直接进行文本替换;内联函数是把函数体替换了调用语句
宏函数是不需要进行类型检查,默认都按文本方式去处理;内联函数要求传入参数的类型要与需要的参数类型要匹配!
总的来说,内联函数是C++中对宏函数的优化!
7、类型转换函数
C语言中的类型转换有2种:
<1>隐式类型转换
<2>强制类型转换
以上的这两种在C++中也是可以使用的!
C++中有额外的新增了4种类型转换函数模板。
dynamic_cast这个转换函数是经常会看到的,因为它的转换是相对安全的!!!会进行类型检查!!!
8、异常处理
写代码的过程,遇到的问题其实可以分为两类:
编译错误—语法错误
运行错误—逻辑错误
逻辑错误:
越界:数组
野指针:
空指针:动态开空间失败、文件打开失败
原来是如何避免逻辑错误的:
If语句对返回值做判断、switch语句
C++额外的新增了异常处理的机制:
异常定位:预测有可能出现异常的代码段 try{}
异常抛出:throw—异常就有各种各样的
异常捕获:catch(类型){ 对异常做出处理}
特点:
- 跨级捕获-并处理异常
- C++中有提供专门的异常基类 exception
- C++中抛出的异常可以是类对象
两数进行除法运算的子函数:
《1》分母为0 抛出这个异常
异常去哪里捕获:
哪里调用的这个函数,就去哪里进行异常的捕获
Main主函数中调用子函数。
在主函数中捕获并处理子函数中抛出的异常!
9、智能指针
也是模板
问题: 内存泄漏, new出来的空间没有使用delete去做释放!导致堆区的空间一直被占用。
解决: 有一种机制可以支持我们只要做new就可以了,释放不需要我们自己去做,让系统帮我们去做释放的工作!java中是有这样子的资源回收机制。
引入了智能指针。
C++中的智能智能: 共享智能指针、弱型智能指针、独享智能指针。
8、单例设计模式
面向对象中的设计模式 23种
单例模式、工厂模式 java
什么是单例:单个的实例 一个类只能有一个实例,一个类只能创建一个对象!
应用:
任务管理器:电脑只要开机,就只有一个任务管理器对象
数据库的操作:正常程序一启动,数据库打开一次就可以了,用到的地方直接去做增删改查!
在一个项目中,我们要保证数据库只要打开一次就可以了
思考:
- 这一个唯一的对象谁去创建? 由这个类自己去创建,创建对象会执行构造函数,所有构造函数要放在private声明区!(构造函数私有化)
- 创建对象的目的就是为了使用,通过对象要调用类中的public区的函数
获取到这个唯一的对象:怎么获取? 如何在类外访问类的成员:对象的方式
静态的成员函数获取到唯一的对象
- 唯一的对象要使用静态的数据成员保存
总结:
- 构造函数私有化
- 提供一个静态的成员函数获取唯一的对象
- 提供一个静态的数据成员保存唯一的对象
单例模式有两种,一种饿汉模式,一种是懒汉模式
懒汉模式:存在的问题:在多线程中,没法确保这个对象是否需要实例化!
如何解决:加锁
饿汉模式:存在的问题:有可能都没有使用到这个唯一的对象,就会造成资源的浪费!