常量引用
函数中利用常量引用防止误操作修改实参
void showValue(const int& v) {
//v += 10;
cout << v << endl;
}
//发现是引用,转换为 const int* const ref = &a; 原本是值可以改 ,现在不能改了
引用本质:引用的本质在c++内部实现是一个指针常量.
函数默认参数
//1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
//2. 如果函数声明有默认值,函数实现的时候就不能有默认参数
3.3.2 函数重载注意事项
func(a); //调用无const 如果是变量传入,会调用可读可写的函数
func(10);//调用有const 常量传入,int &a= 10; 不合法 const in& a=10 合法 代码自动优化
封装
//三种权限
//公共权限 public 类内可以访问 类外可以访问
//保护权限 protected 类内可以访问 类外不可以访问
//私有权限 private 类内可以访问 类外不可以访问
struct和class区别
struct 默认权限为公共
class 默认权限为私有
对象的初始化和清理
构造函数和解析函数
括号法
person p1 默认构造函数调用
person p2(10) 有参构造函数
person p3(p2) 拷贝构造函数
调用默认构造不能 Person p2(); 如果加了编译器认为这是一个函数声明,不会认为在创建对象
//2.2 显式法
Person p2 = Person(10);
Person p3 = Person(p2);
等号右侧 Person(10)单独写就是匿名对象 当前行结束之后,马上执行析构函数,释放
//注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明
person(p3) = = perspn p3
//2.3 隐式转换法
Person p4 = 10; // Person p4 = Person(10);
Person p5 = p4; // Person p5 = Person(p4);
//Person p5(p4); 拷贝构造函数
4.2.3 拷贝构造函数调用时机
构造函数调用规则
https://blog.csdn.net/MakeYouClimax/article/details/129088789
深拷贝与浅拷贝
属性初始化了一个指针 int* m_height;
堆区的数据手动开辟 手动释放
Person p1(18, 180);
Person p2(p1); 函数中执行了拷贝构造
m_height = new int(height);
int* m_height; 把身高的数据开辟到堆区 m_height = new int(height); 用指针接收堆区的数据
这个时候就需要析构代码,将开辟的数据做释放操作。
if (m_height != NULL)
{
delete m_height;
为了防止野指针的出现,可以再做置空的操作 m_height = null
}
此时报错了!
先进后出, p2 会先释放。但堆区内存已经被释放,p1再去会报错。
浅拷贝带来的重复释放报错
此时编译器提供的拷贝构造不太好,需要自己提供
m_height = p.m_height 需要更改为 m_height = new int(*p.m_height); 再开辟一块内存
初始化列表
类对象作为类成员
//构造的顺序是 :先调用对象成员的构造,再调用本类构造
//析构顺序与构造相反
先手机 再人
结束时候 先人 再手机
通过对象访问 p1.func();
通过类名访问 Person::func();
内存对齐
还是用一个例子带出这个问题,看下面的小程序,理论上,32位系统下,int占4byte,char占一个byte,那么将它们放到一个结构体中应该占4+1=5byte;但是实际上,通过运行程序得到的结果是8 byte,这就是内存对齐所导致的。
c++对象模型 和 this指针
成员变量和成员函数分开存储
空类 空对象占用内存空间为 1 个字节
是为了区分空对象占内存的位置
每个空对象有一个独一无二的内存地址
当你初始化了一个 int 变量 变为4字节
初始化了以恶搞static int m 还是4 因为静态变量不在类对象上
非静态成员函数 静态对象函数都不在类对象上
4.3.2 this指针概念
this指针指向被调用的成员函数所属的对象
解决名称冲突
返回对象本身
Person(int age)
{
this指针指向被调用成员函数所属对象 ,成员函数person,其所属对象为p1
//1、当形参和成员变量同名时,可用this指针来区分
this->age = age; # 防止age = age; 指针指向所属对象,所属对象指向age属性
}
p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1); 执行函数多次
函数调用完成后返回p2本身就能多次调用
return *this; this指向对象指针 *号变成了对象本体
Person& PersonAddPerson(Person p) 返回引用 年龄增加到40 一直返回的对象本体
Person PersonAddPerson(Person p) 返回值 年龄是20 因为返回值 是拷贝生成一份新的 新的并不是对象本体
4.3.3 空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
p->ShowClassName(); //空指针,可以调用成员函数
但是如果成员函数中用到了this指针this->age = age;,就不可以了
4.3.4 const修饰成员函数
成员函数后加const后我们称为这个函数为常函数
常函数
class perspn
{public:
void showperson() const 加上 就可不可以修改m_a const Type* const pointer 此时指针指向和指向值都不能改了
{ 每个函数都有隐含的 this 指针
m_a =100; 此时可以 相当于this ->m_a =100; /this指针的本质是一个指针常量,指针的指向不可修改
}int m_a;};
//person.mA = 100; //常对象不能修改成员变量的值,但是可以访问
person.m_B = 100; //但是常对象可以修改mutable修饰成员变量
页面信息错误 长对象不能调用普通成员函数,因为普通成员函数可以修改属性
友元
友元的目的就是让一个函数或者类 访问另一个类中私有成员
Building b;
goodGay(&b); 函数调用的是指针 所以要把地址传入
函数在类外实现
类做友元
goodGay::goodGay()
{
building = new Building; 由于上面申明的是指针 所以这里new 也是返回的
}
int* p = new int;
声明指针 p 指向内存
class house;
class gayer
{
public:
goodgay();
void visit();
private:
house *house;}
class house
{
friend class gayer;
public:
house();
public:
string keting;
private:
string woshi;
}
house::house()
{
this->keting="";
this->woshi="";
}
gayer::goodgay()
{}
继承
class A : public B;
A 类称为子类 或 派生类
B 类称为父类 或 基类
公共继承
类内可以访问 公共与保护权限
类外只能访问公共
保护继承
类内可以访问 全是保护权限
类外全都无法访问。
私有继承
类内可以访问 全是私有权限
类外全都无法访问
此时如果再来一个继承,即使是继承,类内全都无法访问
继承中的对象模型
父类中所有非静态成员属性都会被继承, 私有属性被编译器隐藏,但是继承了
此时 16个字节大小。
F: 跳转到F盘
cd 目录
dir
cl /d1 reportSingleClassLayout查看的类名 所属文件名
声明 子类,此时
//继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
继承同名成员处理方式
子类对象可以直接访问到子类中同名成员
子类对象加作用域可以访问到父类同名成员
当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
继承同名静态成员处理方式
通过对象访问
通过类名访问
::前面必须是类名 不是声明的类名称
s.Base::m_A
Son::Base::m_A
菱形继承
虚继承后数据只有一份
虚基类指针指向 虚基类表 ,表中记录数据偏移量。 分别+8 +4后就可以到达age
多态
多态是C++面向对象三大特性之一
多态分为两类
静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
动态多态: 派生类和虚函数实现运行时多态
静态多态和动态多态区别:
静态多态的函数地址早绑定 - 编译阶段确定函数地址
动态多态的函数地址晚绑定 - 运行阶段确定函数地址
void DoSpeak(Animal & animal)
DoSpeak(cat);
父类的引用 但是传入了子类的对象
此处静态 。地址早绑定,在编译阶段就确定函数地址。
所以这里函数显示的是动物在说话
我们需要猫和狗说话 所以需要动态。地址晚绑定
此时,函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。子类virtual可写可不写
//多态满足条件:
//1、有继承关系
//2、子类重写父类中的虚函数
//父类指针或引用指向子类对象
Animal &animal =cat ;
重写和重载
重写是要 函数名 参数 全部相同
4.7.2 多态案例一-计算器类
如果想扩展新功能 需求修改源码
在真实开发中需要开闭原则
多态使用条件 父类指针或引用指向子类对象
int* p = new int;
利用new创建的数据,会返回该数据对应的类型的指针
AbstractCalculator *abc = new AddCalculator;
virtual int getResult() 子类也重写了
纯虚函数和抽象类
virtual 返回值类型 函数名 (参数列表)= 0 ;
抽象类特点:
无法实例化对象
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
void test01()
{
Base * base = NULL;
//base = new Base; // 错误,抽象类无法实例化对象 堆区开辟也不行
// Base b 不行 抽象类无法实例化对象
base = new Son; 子类必须重写抽象类中的纯虚函数,不然也不能创建对象
base->func();
delete base;//记得销毁
}
Base * base= new Son;
base->func();
虚析构和纯虚析构
m_Name = new string(name); new string指针返回 注意他这里是在子类里面
有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
虚析构和纯虚析构区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
if(m_Name != NULL)
{
delete m_Name;
m_Name = NULL;
}
后面
~Cat()
{
cout << “Cat析构函数调用!” << endl;
if (this->m_Name != NULL) {
delete m_Name;
m_Name = NULL;
}
}
没有执行猫的析构函数
父类的指针没能调用子类析构函数 咋曾内存泄漏
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
析构函数 只要改父类的
纯虚析构 要改父类和子类
但是这里有点不一样 纯虚析构必须要有代码实现,光是有一句
virtual ~Animal() = 0; 不够
需要加上
Animal::~Animal()
{
cout << “Animal 纯虚析构函数调用!” << endl;
}
1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3. 拥有纯虚析构函数的类也属于抽象类
多态案例三-电脑组装
文件操作
ofstream:写操作
ifstream: 读操作
fstream : 读写操作
cout 输出流对象
#include<iostream>
#include<fstream>
using namespace std;void test01()
{1 包含头文件2 创建流对象
ofstream ofs;
3 指定打开方式
ofs.open("text.txt",ios::out); 同级路径ofs << "姓名:张三" << endl;ofs << "性别:男" << endl;ofs << "年龄:18" << endl;ofs.close();}
读文件
if (!ifs.is_open()) 文件是否打开
while (ifs >> buf) 循环将三行数据全部读到后,循环会退出。读到头了会返回假的标志 \0
把文字读入 直到遇到假的标志 \0,然后执行while中语句
ifs.getline char* 这里数组名就是指向数组首地址,后面一个参数表明最多读取多少字符
所以 ifs.getline(buf,sizeof(buf))
getline(数据流,buf)第二个为准备好的字符串
while ((c = ifs.get()) != EOF) end of file 逐个读取字符 直到文件尾
二进制文件
二进制写
写字符串的时候不建议用string,会出现一些问题。建议用c语言的字符串char m_name[64];
char m_name[64];创建流对象 打开文件
ofstream ofs;
ofs.open()ofstream ofs("person.txt", ios::out | ios::binary);可以两部合成一部 ofstream ofs("person.txt", ios::out | ios::binary);ofs.write((const char *)&p, sizeof(p));
直接对p取地址,p是一个person型,但是取一个 const char* * 转换成相应的指针
后面可以sizeof(person)
二进制读
ifstream ifs("person.txt", ios::in | ios::binary);ifs.read((char *)&p, sizeof(p));