C++面向对象三大特性封装继承多态
C++初学者常用创建模板
#include <iostream>
Using namespace std;
Int main(void)
{
System(“pause”)
Return 0;
}
Cout<<
Cin>>str
Swich 不能判断范围。
Rand()%100生成0-99的随机数(伪随机数)
需要一个随机种子 srand((unsigned int)time(null)); //需要加头文件ctime.
数组定义 变量类型+数组名【个数】=
{
内容,
}
利用指针访问数组时,p++向后偏移四个字节,访问a[1]
Struct三种定义方式
Struct student
{
String name;
Int score;
Int age;
}s3;
1<——struct student s1;
S1.name=”张三”;
S1.age=”18”;
S1.score=”90”;
2<——struct student s2={“李四”,19, 60};
3<——s3.name=
S3.age= ;
S3.score= 。
定义结构体数组
Struct student
{
String name;
Int score;
Int age
}
Struct student stuarray[3]=
{
{“张三”,18,,100}
{“李四”,28, 99}
{“王五”, 60,70}
}
结构体嵌套结构体
访问时用两个.
C++中int new(数值)返回一个该数据类型的指针;
可以用int *进行储存;
用new创建数组
New int [10]开辟一个数组空间返回第一个值的指针
释放数组要加delete [] arr;对数组进行释放;
代码区:存放函数体的二进制代码,由操作系统进行管理的
全局区:存放全局变量和静态变量以及常量
栈区:由编译器自动分配释放,存放函数的参数值、局部变量
堆区:由程序员分配和释放,若程序员不释放,程序结束由操作系统回收+
引用
引用给变量起别名 int &b = a;
引用必须要初始化;
引用一旦初始化就不可以更改了;
Int * const *ref = &a;
Int &a=b;
定义全局变量 static
引用必须引一块合法的内存空间。
Const int & ref = 10;
(用来修饰形参,防止误操作)
Int temp = 10; const int & ref = temp;
Ref不能修改
加入const变为只读,不可修改
函数可以用默认参数,默认参数也可以改变大小
Int func(int a, int b = 10,int c = 30)
注意事项,如果某个位置已经有了默认参数,那么从这个位置往后,从左到右必须要有默认值
a有了默认参数那么c也要有默认参数
如果函数声明有默认参数,函数实现就不能有默认参数
函数中可以有占位参数,占位参数还可以有默认参数
Void func(int a, int = 10);
占位默认参数
Void func (int a, int);
重载函数:
同一个作用域下,函数名称相同,函数参数类型不同或者个数不同或者顺序不同
函数的返回值不可以作为函数重载的条件
Int 型函数和void函数不可以作为重载函数的条件
引用作为重载的条件
1Void func(int &a)
{
}
2Void func(const int &a)
{
}
如果函数的实参是一个变量名,那么会用1
如果函数的实参是一个常量,那么会用2
函数重载碰到默认参数,会在前面进行识别,会出现异常错入
例如:
Func(int x);
Func(int x, int);
类的创建
Class
{
Public:
加上成员
};
类中的属性和行为 我们统一称为成员(函数是行为,属性是变量)
属性 成员属性成员变量
行为 成员函数成员方法
类具有访问权限
Public 公共权限 类内可以访问,类外也可以访问
Protected 保护权限 类内可以访问,类外不可以访问 儿子可以访问父亲保护的内容
Private 私有权限 类内可以访问,类外不可以访问 儿子不可以访问父亲保护的内容
Struct 和 class默认的访问权限是不同的
其他是没有太大区别的
Struct 默认访问权限公共
Class默认权限私有
成员属性设置为私有
优点1:将成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性
通过权限设置可以由程序员自己控制
可以实现可读可写,只读,只写
设置的数据是私有的时候可以判断数据的有效性
可以将地址传入函数,减少内存的使用
构造函数的分类和调用
按参数分类:有参构造和无参构造(这也叫默认构造)
Person()
Person(int a)
按类型分为:普通构造和拷贝构造
Person(const person &p)//记住
用引用的方式进行传递7
问题:拷贝函数为什么必须要用引用
调用方式
1括号法
Person p1;
Person p2(10);
调用有参函数。
Person p3(p2);调用拷贝函数
注意事项:
调用默认构造函数的时候不要加()
因为编译器会把它当成函数的声明,不会认为是一个创建对象的过程0
2显示法
Person p1;
Person p2=person(10);
Person p3 = person(p2);
Person (10)匿名对象//匿名对象,当前行执行后系统会自动回收掉对象
会被析构掉
不要用拷贝构造函数,初始化匿名对象
Person (p3);
3隐式转化法
Person p4=10;
相当于person p4 = person(10)
Person p5 = p4;
C++拷贝函数的调用
1使用一个已经创建完毕的对象来初始化一个对象
2.值传递的方式给函数参数传参
实参传给形参的过程中会调用拷贝构造函数
实参拷贝给形参
3.以值方式返回局部对象
在用return时也会调用拷贝函数
在c++中,c++编译器至少给一个类添加三个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝和构造函数,对属性进行值拷贝
- 构造函数的调用规则如下:
- 如果用户定义有参构造函数,c++不再提供默认无参构造函数,但会提供默认拷贝构造(如果提供了有参,那么编译器不再提供无参)
- 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
深浅拷贝是面试经典问题也是常见的一个坑
浅拷贝:简单地赋值拷贝操作
{
编译器提供的拷贝构造函数,会做浅拷贝操作
编译器会完全复制粘贴指针的地址
两个同事进行析构释放内存时会被重复释放而报错
带来堆区内存重复释放的问题
解决方法:
浅拷贝的问题要利用深拷贝而解决
}
深拷贝:在堆区重新申请空间,进行拷贝操作
{
重新开辟内存,防止两个地址相同,重复释放
开辟内存时
Height=new(*p.heigt);
}
析构代码将堆区开辟的数据就行释放
初始化列表语法:
用途:给类中的属性进行初始化操作,利用初始化列表的语法
初始化:构造函数():属性1(值1),属性2(值2)…{ }
在构造函数体的同时,给属性赋初值
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
B类中有对象A作为成员,A为对象成员
当创建B对象时,A与B的构造和析构顺序到底谁先谁后
当其他类的对象作为本类成员,再构造自身;
构造过程类似于栈。
析构顺序和构造顺序是相反的。
静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
静态成员变量
- 所有成员共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
- 类外访问不到静态成员函数
静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
非静态成员变量必须通过对象进行访问
一c++对象模型和this指针
成员变量和成员函数分开储存
在c++中,类内成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
(静态成员函数和静态成员变量都不属于类的对象)
//空对象占用内存空间为1个字节,考试的时候注意
//因为c++编译器会给每个空对象分配一个字节内存空间,为了区分空对象占内存的位置
//每一个空对象都有独一无二的内存空间
静态成员函数不占内存
//非静态成员函数不属于类的对象上
静态成员函数更不属于类的对象上
This指针概念
C++中成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型对象会共用一块代码
那么问题是:这一块代码是如何区分哪个函数调用自己的呢?
C++提供特殊的对象指针,this指针,解决上述问题。This指针指向被调用的成员函数所属的对象。
This指针是隐含每一个非静态成员函数内的一个指针
This指针不需要定义,直接使用即可
This指针用途:
当形参和成员变量同名时,可以用this指针来区分
在类的非静态成员函数中返回对象本身,可使用return*this
Return person会通过拷贝重新拷贝
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
Const修饰成员函数
- 成员函数后加const后我们成为这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明是加关键字mutable,在常函数中依然可以修改
常对象
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
友元
在程序中,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的目的就是让一个函数或者类访问另一个类中私有成员
友元的关键字为friend
友元的三种实现
- 全局函数作为友元
- 类做友元
- 成员函数做友元
做友元需要加friend.
- 运算符重载
运算符重载
运算符重载概念:对已有的运算符进行重新定义,赋予其另一种功能,以适应不同的数据类型
加号运算符重载
作用实现两个自定义数据类型相加的运算
对于内置的数据类型编译器知道如何运算
通过成员函数重载+;传入一个参数
通过全局函数重载+;传入两个参数
本质上是自己定义函数
运算符重载也可以发生函数重载(同一个函数名称)
//同一个名称根据参数不同可以调用不同的函数
//内置的数据类型不能够发生运算符重载
//不要滥用运算符重载,不要把加变成减
<<运算符重载
直接输出对象
重载左移运算符
Ostrean & operator( ostream & x , person p)
{
Return x;
}
不要在类中进行<<的重载
在全局函数中进行重载
在类外无法修改private中的值,但可以通过友元进行访问
C++编译器至少给一个类添加4个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符 operator=,对属性进行拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
属性的值拷贝
//重载赋值运算符,存在深浅拷贝问题
//new int 返回int*
//一般是析构的时候出现深浅拷贝的问题
重载关系运算符
可以让两个自定义类型对象进行对比操作
函数调用运算符()也可以重载
由于重载后使用的方式非常像函数的调用,因此称为仿函数
仿函数没有固定的写法,非常灵活
在STL中用的非常多
仿函数非常灵活,没有一个固定的写法
不同于一个函数,他是通过一个类来访问,然后返回值
依照需求写对应的仿函数
STL会大量的写仿函数
了解仿函数是干嘛的,非常灵活
匿名函数对象
重载了小括号;
Myadd()(100,100);
当前行执行完了,立即释放。
继承学习
利用继承的技术,减少重复代码
我们看到很多网站,都有公共的头部,公共的
继承的方式:
公共继承、保护继承、私有继承。
语法class b:public a;
语法class b:protected a
语法 class b:private a
Public公共权限内容和保护权限不变
Protect公共权限变为保护权限,私有权限不变
私有继承全变为私有权限
从父类中继承过来的成员,哪些属于子类的对象中
通过vs能够使用开发人员命令提示工具,可以看父类和子类
- 跳转盘符 D:
- 跳转文件路径 cd 具体路径下
- 查看命令dir
- c1 /d1 reportSingleClassLayout类名 “文件名”
先构造父亲,再构造儿子,
先析构儿子,再析构父亲
当子类和父类出现同名成员函数
子类同名成员函数,会隐藏掉所有的父类成员函数
如果想访问到父类中的成员函数需要加作用域
继承同名静态成员 处理方式
继承中同名的静态成员在子类对象如何访问
访问子类同名成员,直接访问即可
放问父类同名成员,需要加作用域
Static需要进行类内声明类外初始化
静态成员通过对象访问数据,通过类名访问数据
不创建类就能够进行访问
如果子类出现同名静态成员函数,把父类中的同名成员函数全部隐藏掉
当父类中出现同名成员,需要加作用域
不建议做多继承的操作,父类出现同名情况,子类需要加作用域区分,开发中需要加作用域区分
菱形继承
概念:
- 两个派生类继承同一个基类
- 又有某个类同时继承两个派生类
- 这种继承被称为菱形继承,或者钻石继承
动物->驼、羊->草泥马
羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
//利用虚继承 解决菱形继承的问题
//继承之前加上关键字virtual变为虚继承
//Animal类成为虚基类
当我们发生虚继承之后这份数据只有一个
指针通过偏移量只有一个age,这就是虚继承
一.多态
多态分为两类
静态多态:函数重载和运算符重载属于静态堕胎,复用函数名
动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态的区别:
静态多态的函数地址
静态多态函数地址早绑定,编译阶段确定函数地址
动态多态函数地址晚绑定,运行阶段确定函数地址
多态满足条件:
有继承关系
子类重写父亲中的虚函数
多态使用条件:
父亲指针或引用指向子类对象
重写:函数返回值类型
函数名 参数列表完全一致成为重写
//执行说话的函数
//地址早绑定 在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数的地址既不能提前绑定,需要在运行阶段绑定,地址晚绑定
动态多态满足条件
- 有继承关系
- 子类重写父类的虚函数(重载,函数名相同,形参不同,(返回值类型不同不可以作为重载的条件))
//重写 函数返回值类型 函数名 参数列表 完全相同
动态多态的使用
//父类的指针或者引用 指向子类对象
指针指向子类
1.
//AbstructCalculator* abc = new AddCalculator;
//abc->m_Num1 = 10;
//abc->m_Num2 = 10;
//cout << abc->getResult() << endl;
//delete abc;
2.
多态的底层原理
//加入vfptr virtual function pointer虚函数指针,指向一个虚函数表
//指向vftable,virtual function table虚函数表,表内部记录一个虚函数的地址
//&Animal::speak
//子类的虚函数表中的虚函数的地址会替换掉父类继承下来的虚函数表中的地址
多态
//如果需要拓展新功能,要求修改源码
//如果是真开发中,提倡开闭原则
//开闭原则:对扩展进行开发,对修改进行关闭
//好处
多态好处:1.组织结构清晰
2可读性强.
3对于前期或者后期的拓展和可读性高
4.你看得懂别人的程序不是你牛逼,是写程序的那个人牛逼。
纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数更改为纯虚函数
纯虚函数的语法
Virtual 返回值类型 函数名 (参数列表)=0;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
无法实例化对象
子类必须参与重写抽象类中的纯虚函数,否则也属于抽象类
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构函数
解决方式:将父类中的析构函数改为虚析构或者纯虚构
虚析构和纯虚析构共性:
可以解决父类指针释放子类对象
都需要具体的函数实现
虚析构和纯虚析构区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
纯虚析构即需要声明又需要实现
虚析构或纯虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
如果子类中没有堆区数据,可以不写为虚析构或者纯虚析构
拥有纯虚析构函数的类也属于抽象类0
C++通过封装.继承、多态进行开发的操作方式
- 创造纯虚函数的基类
- 创造一个实现总函数
- 根据基类进行创建,多调用引用和指针进行实现
文件操作:
程序运行时产生的数据都属于临时数据程序一旦运行结束就会被释放
通过文件可以将数据持久化
C++中对文件操作需要包含头文件<fstream>
文件类型分为两种:
- 文本文件 文件以文本的ASCLL码形式储存在计算机中
- 二进制文件 文件以文本的二进制形式储存在计算机中,用户一般不能直接读懂他们
操作文件三大类
- ofstream 写操作
- ifstream 读操作
- fstream 读写操作
文件操作方式
- 包涵头文件#include <fstream>
- 创建流对象 ofstream ofs
- 打开文件 ofs.open(“文件路径”,打开方式);
- 写数据ofs<<”写入的数据”;//文件的输出流对象
- 关闭文件 ofs.close();
文件打开方式:
重点:
- ios::in 为读文件而打开文件
- ios::out 为写文件而打开文件
- ios::trunc 如果文件存在先删除,再创建
- ios::binary 二进制方式
非重点:
- ios::ate 初始位置:文件尾
- ios::app 追加方式写文件
- ios::trunc 如果文件存在先删除,再创建
- ios::binary 二进制方式
//在路径中产生在文件中,最常规的写文件操作
总结:文件操作必须包含头文件fstream
读文件可以利用ofstream,或者二fstream类
打开文件时候需要指定操作文件的路径以及打开方式
利用<<可以向文件中写数据
操作完毕,要关闭文件
读文件的步骤,读文件和写文件相似,但是读取方式相对比较多
读文件步骤如下:
1包含头文件
# include<fstream>
2创建流对象
Ifstream ifs;
3打开文件并判断文件是否打开成功
Ifs open(“文件路径”,打开方式)
4读数据
四种方式读取
5关闭文件
Ifs close();
二进制文件
以二进制方式进行文件读写操作
打开方式指定为ios::binary
二进制方式写文件主要利用流对象调用成员函数
函数原型:ostream& write(const char*buffer,int len)
参数结束:字符指针buffer指向内存中一段存储空间。Len是读写的字节数
void test01()
{
ofstream ofs("person.txt", ios::out | ios::binary);//两种方法都可以
Person p = { "张三",18 };
//ofs.open("person.txt",ios::out || ios::binary);
ofs.write((const char*)&p, sizeof(Person));
ofs.close();
}
读文件
二进制方式读文件主要利用流对象调用成员函数read
函数原型 iostream& read(char*buffer, int len);
字符指针buffer指向内存中一段储存空间,len是读写的字节数
职工管理系统项目
详见程序源代码
#pragma once防止头文件重复包含
模板:
Template<typenname(class) T>
函数声明或定义
Template声明创建模板
Typename 表明其后面的符号是一种数据类型
T通用的数据类型,名称可以替换,通常为大写字母’
自动类型推导,必须推导出一致的数据类型T,才可以使用
普通函数调用时可以发生自动类型转换(隐式类型转换)
函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
<指定类型>
如果利用显示指定类型的范式,可以发生隐式类型转换(指的是一个字母变成ASCLL码的值)
语法如下SWAPI<INT >(参数值)
Template<class T>
Void func()
{
Cout<<”func的调用”<<endl;
}
如果在函数中直接写func()会报错;
因为不知道T是什么类型的,所以会报错
如果函数模板和普通函数都可以实现,优先调用普通函数
可以通过空模板参数列表来强制调用函数模板在中间加一个<>
函数模板也可以发生重载
如果函数模板可以差生更好地匹配,优先调用函数模板
例如不发生强制类型转换的时候
提供了函数模板,就不要提供普通函数了,容易产生二义性
模板的局限性:
- 模板的通用性不是万能的
- 两个数组无法实现赋值
- 如果是自定义的数据类型,也无法正常运行
- 因此c++为了解决这种问题,提供了模板的重载,可以为这些特定的类型提供具体化的模板
可以定义类模板
掌握类模板配合友元函数的类内和类外实现
全局函数雷内实现,直接在类内声明友元即可
全局函数类外实现:需要提前让编译器知道全局函数的存在
类模板案例:
STL的诞生
长久以来,软件界一直希望建立一种可重复利用的东西
C++的面向对象(封装继承多态)和泛型编程思想,目的就是提高复用性
大多数情况下,数据结构和算法都未能有一套标准,导致被迫从事大量重复性工作
为了建立数据结构和算法的一套标准,诞生了STL
STL(基本概念)
STL(标准模板库)
STL从广义上分为:容器算法迭代器
容器和算法之间通过迭代器进行无缝连接
STL几乎所有代码都采用了模板类和模板函数
STL六大组件:
STL大体分为六大组件,分别是容器、算法、迭代器、仿函数、适配器(配机器)、空间配置器
- 容器:各种数据结构,如:vector、list、deque、set、map等,用来存放数据。
- 算法:各种常用的算法,如sort、find、copy、for_each等;
- 迭代器:扮演了容器和算法之间的胶合剂
- 仿函数:行为类似函数,可作为算法的某种策略
- 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西
- 空间配置器:负责空间的配置与管理
STL容器就是将运用最广泛的一些数据结构实现的
这些容器分为:序列式容器和关联式容器
序列式容器:强调值得排序,序列式容器中每个元素都有固定的位置
关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系//有自己的规则
算法:有限的步骤解决逻辑或数学上的问题
算法分为质变算法和非质变算法
质变算法:是指运算过程中会更改区间内的元素的内容,例如拷贝、替换、删除
非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、便利、寻找极值
迭代器:容器和算法之间的粘合剂
提供一种方法,使之能依序寻访某个容器中的各个元素,而又无需暴露该容器内部表示方式
每个容器都有自己专属的迭代器
迭代器非常类似于指针,初学阶段我们可以先理解迭代器为指针
迭代器种类
- 输入迭代器:对数据的只读访问,只读,支持++、==、!=
- 输出迭代器:对数据的只写访问,只写,支持++
- 前向迭代器:读写操作,并能向前推进迭代器,读写,支持++、==、!=
- 双向迭代器:读写操作,并能向前和向后操作,读写,支持++、-
- 随机访问迭代器:读写操作,可以跳跃的方式访问任意数据,功能最强的迭代器,读写,支持++、-、【n】、-n、<、<+、>、>=.
常用容器中迭代器种类为双向迭代器,或者随机访问迭代器
Vector容器:
容器写法:
Vector<Person >v;
迭代器写法
vector<Person>::iterator it = v.begin(); it != v.end(); it++
用iterator
遍历算法写法
for_each(v.begin(), v.end(), myPrint);
void myPrint(int val)
{
cout << val << endl;
}
Vector容器嵌套容器
//见程序
String 本质上是一个类
Char*是一个指针
String 是一个类,类内部封装了char*,管理这个字符串,是一个char*型的容器
String 类内部封装了很多成员方法
例如:查找find,拷贝copy,删除delete,替换replace,查人insert
String 管理char*所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责
String()//创建一个空的字符串,例如string str;
String(const char*s)//使用字符串s初始化
String (const string&str)//使用一个string对象初始化另一个string 对象
String(int n,char c);
使用n个字符串初始化=
string s5;
s5.assign("hellowc++", 5);
cout << s5 << endl;
将前几个字符赋值给字符串