C++ 第四章 类和对象
一、深拷贝与浅拷贝
浅拷贝:简单的赋值操作,会导致指针指向同一内存地址
如果利用编译器提供的拷贝构造函数,会做浅拷贝操作
浅拷贝带来的问题是:堆区内存重复释放,引发崩溃
深拷贝:在堆区重新申请空间,进行拷贝操作
public: int age; string name; int *height; person(string name, int age,int height) { this->age = age; this->name = name; this->height = new int(height); } person(const person& p) //深拷贝 { cout << "Person 拷贝构造函数调用" << endl; age = p.age; //height = p.height;编译器默认浅拷贝时进行的操作 height = new int (*p.height); } ~person()//析构函数:将堆区开辟的数据进行释放 { if (height != NULL) delete(height); cout << "析构" << endl; } };
总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
二、初始化列表
传统赋值初始化相当于:先声明类,再进行属性的赋值操作
初始化列表相当于:直接声明一个有初始值的类型,在构造函数语句前,省略了赋值操作
在大型项目中,class类中成员变量极多的情况下,初始化列表效率更高
person(string name, int age,int h) :age(age), name(name), height(new int(h)) { //注意:指针成员变量初始化时需用new 类型名(变量)来进行 }
三、类对象作为类成员
构造顺序:构造时先构造类的成员对象,再构造类自身
析构顺序:析构时先析构自身,再析构类的成员对象
class Phone { public: string p_Brand; Phone() { cout << "Phone created!" << endl; } Phone(string brand) { this->p_Brand = brand; cout << "Phone created!" << endl; } ~Phone() { cout << "Phone deleted!" << endl; } }; class person { public: int age; string name; int *height; Phone phone; } /* Phone created! Person created! 18 yxc 180 iphone person deleted! Phone deleted! */
四、静态成员
1.静态成员函数
两种访问方式:
person::func();//1.通过类名访问 person p1; p1.func();//2.通过对象访问
特点:
1.程序共享一个函数
2.静态成员函数只能访问静态成员变量
3.静态成员函数也有访问权限
2.静态成员变量
特点:
1.所有对象共享同一份数据,在内存中只有一份
2.在编译阶段分配内存
3.类内声明,类外初始化
class Animal { public: static const int head = 1; };
五、C++对象类型和this指针
1.成员变量和函数分开存储
在C++中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上
空变量占用内存空间为1字节,因为编译器会给每个空对象分配一个字节空间,以区分空对象占用内存的位置
一个含int成员变量的对象占用内存空间为4字节
class Person { int m_A; //非静态成员变量 属于类的对象 static int m_B; //静态成员变量 不属于类的对象 void func() //非静态成员函数 不属于类的对象 { } static void func() //静态成员函数 属于类的对象 { } };
2.this指针概念
this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针,无需定义,直接使用
class Person { public: int age; Person(int age) { this->age = age; } Person& AddAge(const Person p) { this->age += p.age; return *this; } }; int main() { Person p1(10); Person p2(20); p1.AddAge(p2).AddAge(p2).AddAge(p2); cout << p1.age << endl; return 0; }
3.空指针访问成员函数
C++中空指针也是可以调用成员函数的,但也要注意有没有用到this指针
如果用到this指针,需要加以判断代码的健壮性
class Person { void ShowAge() { if(this) { cout<<this->age<<endl; } } }
4.const修饰成员函数
常函数
成员函数后加const后叫常函数
常函数不可修改成员属性
但是成员属性声明时加关键字mutable后,在常函数、常对象中仍可修改
常对象
声明对象前加const称为常对象
常对象只能调用常函数
this指针的本质是指针常量,指针的指向是不可修改的
class Person { public: int age; mutable int height; //特殊变量,在常函数、常对象中也可修改 void ShowAge() const //常函数 { if (this) { cout << this->age << endl; this->height = 185; } } void ShowHeight() { if (this) { cout << this->height << endl; } } }; int main() { Person p1(10); const Person p2(10); p1.ShowAge(); p1.ShowHeight(); //p2.ShowHeight(); //错误:常对象只能调用常函数 return 0; }
五、友元
在程序里,有些私有属性,也想让类外特殊的一些函数或者类进行访问,友元的目的就是让一个函数或者类 访问另一个类中似有成员
友元的关键字:friend
友元的三种实现方法:
- 全局函数做友元
- 类做友元
- 成员函数做友元
1.全局函数做友元
class House { friend void GF(House* house);//告诉编译器:全局函数GF是类House的好朋友,可访问private内容 public: string Living_room; private: string Bed_room; public: House() { Living_room = "客厅"; Bed_room = "卧室"; } }; void GF(House* house) { cout << "GF is visiting " << house->Living_room << endl; cout << "GF is visiting " << house->Bed_room << endl; }
2.类做友元
class House { friend class GF; //类GF是House的好朋友,可以访问private内容 //无权限修饰,不是House的成员 public: House(); //类内声明函数,类外实现 public: string Living_room; private: string Bed_room; }; class GF { public: string name; House *house; public: GF(string name);//类内声明函数,类外实现 void visit(House* house);//类内声明函数,类外实现 }; House::House() //类外实现,注意明确命名空间 { Living_room = "客厅"; Bed_room = "卧室"; } void GF::visit(House* house)//注意声明命名空间的位置 { cout << name << " is visiting " << house->Living_room << endl; cout << name << " is visiting " << house->Bed_room << endl; } GF::GF(string name)//类外实现 { house = new House(); this->name = name; }
3.成员函数做友元
class House { friend void GF::visit(); public: House(); //类内声明函数,类外实现 public: string Living_room; private: string Bed_room; };
六、运算符重载
概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
1.加号运算符重载
通过成员函数重载+号
class Person { public: int age; Person operator+(const Person& p) { Person temp(0); temp.age = this->age + p.age; return temp; } Person() { } Person(int age) { this->age = age; } };
本质
Person p3 = p1.operator+(p2);
通过全局函数重载+号
Person operator+ (const Person& p1, const Person& p2) { Person temp; temp.age = p1.age + p2.age; return temp; }
本质
Person p3 = operator+(p1,p2);
2.左移运算符<<重载
只能利用全局函数重载左移运算符
因为利用成员运算符 左移运算符 p.operator<<(cout) 简化版本:p<<cout 无法实现p在左侧
ostream &operator<<(ostream& cout, const Person& p) //ostream是静态的,内存中只有一份 //本质:operator<<(cout,p) 简化:cout<<p p为引用类型,可以防止有开辟在堆区属性的对象崩溃 { cout << "姓名:" << p.name << " 年龄:" << p.age << endl; return cout; }
若需要输出类的私有属性,可以将重载<<的函数做类的友元
3.递增运算符++重载
前置递增
MyInteger& operator++() { this->val ++; //先加法运算 return *this;//再返回结果 }
注意:返回引用
后置递增
MyInteger operator++(int) //int代表占位参数,可以用于区分前置和后置递增 { MyInteger t = this->val;//保存原先结果 this->val++;//加法运算 return t;//返回原先结果,返回类型不是引用,因为要返回后置原先结果 }
理解:后置递增较为耗时,因为内部发生值传递与拷贝操作
4.赋值运算符=重载
背景知识
C++编译器至少给一个类添加4个函数
1.默认构造函数
2.默认析构函数
3.默认拷贝函数
4.赋值运算符operator=,对属性进行值拷贝(若类存在到堆区的属性,则涉及到深浅拷贝问题)
默认=运算符存在的问题:浅拷贝
若对象存在开辟在堆区的属性,用默认=运算符赋值后,在析构时会导致堆区内容重复释放,程序崩溃
解决方案:利用深拷贝
class Person { public: int* age; Person(int age) { this->age = new int(age); } ~Person() { if (age != NULL) { delete age; } } Person& operator=(const Person& p) //重载赋值运算符= 深拷贝 { if (this->age != NULL) //判断在堆区是否有内存,很重要:防止内存泄漏 { delete this->age; this->age = NULL; } this->age = new int(*p.age); //在堆区开辟新空间,拷贝值 return *this;//返回引用类型,链式编程思想 } }; ostream& operator<<(ostream& cout, Person& p) { cout << *p.age << endl; return cout; } int main() { Person p1(18),p2(19),p3(20); p2 = p1 = p3; cout << p1<<p2<<p3; return 0; }
5.关系运算符(<,==,>)重载
算法题中常用
bool operator<(const Person& p)const { return age < p.age; }
6.函数调用运算符()重载
函数调用运算符()也可以重载
由于重载后使用的方式非常像函数的调用,因此称为仿函数
仿函数没有固定写法,非常灵活
class Myprint { public: void operator()(string str) { cout << str << endl; } Myprint() { } }; int main() { Myprint()("12345"); //匿名对象 Myprint p1; p1("54321"); return 0; }
七、继承(OOP三大特征之一)
有些类与类之间存在特殊的关系,下级别的成员除了拥有上一级的共性,还有自己的特性
此时需要考虑利用继承的技术,减少重复代码
1.基本语法
class 子类:继承方式 父类 { };
2.概念
子类也称派生类,父类也称为基类
派生类中的成员包含从基类继承而来的,以及自己特有的成员
从基类继承而来的表现其共性,特有的成员表现其个性
3.继承方式
一共有三种继承方式:
- 公共继承
- 保护继承
- 私有继承
父类中私有的成员只是被隐藏了,但还是会继承下去
公共继承
除父类中私有成员外,其他所有成员将会被显式继承,其访问权限保持不变
保护继承
除父类中私有成员外,其他所有成员将会被显式继承,其访问权限变为protected
私有继承
除父类中私有成员外,其他所有成员将会被显式继承,其访问权限变为private
4.继承中构造和析构的顺序
先构造父类,再构造子类
析构顺序一般与构造顺序相反
class Sub : public Base { private: int z; public: Sub(int x, int y, int z):Base(x,y){ //构造子类时,对父类构造函数写法 this->z = z; } int getZ() { return z; } int calculate() { return Base::getX() * Base::getY() * this->getZ(); } };
5.继承同名成员处理方式
当子类中出现与父类同名的属性或函数时,
访问子类同名成员,直接访问即可
访问父类同名成员,需要加作用域
当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
6.继承同名静态成员处理方式
静态成员和非静态成员出现同名,处理方式一致
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
通过对象访问
cout<<s.m_A<<endl; cout<<s.Base::m_A<<endl;
通过类名访问
cout<<Son::m_A<<endl; cout<<Son::Base::m_A<<endl;
7.多继承语法
C++允许一个类继承多个类
实际开发中不建议使用,当父类中出现同名成员,需要加作用域区分
语法
class 子类:继承方式1 父类1,继承方式2 父类2… { };
8.菱形继承(钻石继承)
两个子类继承同一个父类,又有某个类同时继承两个子类,这种继承被称为菱形继承(钻石继承)
问题
- 羊继承动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,会产生二义性
- 菱形继承导致数据有两份,资源浪费
解决方案:virtual
利用虚继承virtual
class Animal { public: int age; }; class Sheep :virtual public Animal { }; class Tuo : virtual public Animal { }; class SheepTuo :public Sheep, public Tuo { }; int main() { SheepTuo st; st.Sheep::age = 20; st.Tuo::age = 15; st.age = 21; cout << st.age << endl; return 0; }
八、多态(OOP三大特性之一)
1.分类
静态多态:函数重载和运算符重载属于静态多态,复用函数名
动态多态:派生类和虚函数实现运行时多态
区别
静态多态的函数地址早绑定——编译阶段确定函数地址
动态多态的函数地址晚绑定——运行阶段确定函数地址
class Animal { public: int age; void speak() { cout << "动物在说话" << endl; } }; class Cat:public Animal { void speak() { cout << "喵~" << endl; } }; //静态多态:地址早绑定,在编译阶段确定函数地址 void DoSpeak(Animal &a) { a.speak(); } int main() { Cat c1; DoSpeak(c1);//动物在说话 return 0; }
若想实现子类调用函数,那么函数地址不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
class Animal { public: int age; virtual void speak() //改为虚继承即可 { cout << "动物在说话" << endl; } };
重写:函数返回值 函数名 形参列表 完全相同
2.动态多态的满足条件
- 有继承关系
- 子类重写父类的虚函数
3.动态多态的使用
父类的指针或引用 执行子类的对象
4.多态的原理剖析
vfptr:虚函数(表)指针(virtual function pointer)
vftable:虚函数表(virtual function table)
5.多态的好处
- 组织结构清晰
- 可读性强
- 前期和后期扩展及维护性高
6.纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要是调用子类重写的内容
因此,可以将虚函数改为纯虚函数
语法
virtual 返回值类型 函数名 (参数列表) = 0;
当类中有了纯虚函数,此类也叫抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
7.虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方法:将父类中的析构函数改为虚析构和纯虚析构
共性
都需要具体的函数实现
可以解决父类指针释放子类对象
不同
若是纯虚析构,则该类属于抽象类,无法实例化对象
语法
class Base { public: virtual Base() { } virtual ~Base() = 0;//纯虚析构 virtual ~Base() //虚析构 { } } Base::~Base()//纯虚析构需要有声明,也需要实现 { }
实例分析
class Animal { public: string* name; Animal(string name) { cout << "Animal created!" <<endl; this->name = new string(name); } virtual ~Animal() //虚析构 { cout << "Animal deleted!" << endl; if (name != NULL) { delete(name); name = NULL; } } }; class Cat :public Animal { public: Cat(string name):Animal(name) { cout << "Cat created!" << endl; this->name = new string(name); } ~Cat() { cout << "Cat deleted!" << endl; if (name != NULL) { delete(name); name = NULL; } } }; void doSpeak(Animal* a) { cout << *a->name << " is speaking!" << endl; delete(a); } int main() { doSpeak(new Cat("Tom")); return 0; }
class Animal { public: string* name; Animal(string name) { cout << "Animal created!" <<endl; this->name = new string(name); } virtual ~Animal() = 0; }; Animal::~Animal() //纯虚析构 { cout << "Animal deleted!" << endl; if (name != NULL) { delete(name); name = NULL; } }
总结
如果子类中没有堆区数据,可以不写虚析构或纯虚析构
推荐这些技术文章:
#include <iostream>
using namespace std;
/****************************************************************************************************
* 21.函数的占位参数:C++在声明函数时,可以设置占位参数。占位参数只有参数类型声明,而没...
#include <iostream>
using namespace std;
/****************************************************************************************************
* 11.在C++中尽量使用const替换宏(#define):#define MAX 1024...
Educational Codeforces Round 129 (Rated for Div. 2)
Educational Codeforces Round 129 (Rated for Div. 2)
https://codeforces.com/contest/1681
A. Game with Cards
直接比较最大的谁更大,谁就是赢家。
相等情况下,先手胜
#include <bits/stdc++.h>
using namespace std;
void solve...
第 2 章 经典入门
一 排序
例 2.1 排序
代码 2.1
冒泡排序(时间复杂度 \(O(n^2)\))
#include <iostream>
using std::cin; using std::cout; using std::endl;
#include <vector>
using std::vector;
#include <algorith...
.Net Core3.0 WebApi 项目框架搭建 十七:使用NewLife.Redis替换掉原来的Redis
.Net Core3.0 WebApi 项目框架搭建:目录
介绍
NewLife.Redis主要作者及经验介绍来源:大石头
源码: https://github.com/NewLifeX/NewLife.Redis
Nuget:NewLife.Redis
NewLife.Redis是一个Redis客户端组件,以高性能处理大数据实时计算为目标。
Redis...
cout << a&&b <<endl这一行出现了这个错误
查了下是因为运算符优先级的问题,加个()就行了
cout << (a&&b) <<endl
...
传智健康项目
黑马程序员-传智健康项目(第一章)
黑马程序员-传智健康项目(第二章)
黑马程序员-传智健康项目(第三章)
黑马程序员-传智健康项目(第四章)
黑马程序员-传智健康项目(第五章)
黑马程序员-传智健康项目(第六章)
黑马程序员-传智健康项目(第七章)
黑马程序员-传智健康项目(第八章)
黑马程序员-传智健康项目(第九章)
黑马程序员-传智健康项目(第十章)
黑马程序员-传智健康项...
一、介绍
1、介绍
最近无聊,也没什么事做,没事做总是要给自己找点事情做吧,毕竟人的生活在与折腾。于是,决定自己手动写一个 IOC 的框架。我们知道在 NetCore 的版本里面已经内置了 IOC 容器,它就是 ServiceCollection,一般情况下,该容器还是够用的,但是有时候还会有力不从心的时候,比如:我想要实现属性注入或者方法注入,NetCore 内置的框架...
EasyMvc--让MVC区域开发更Easy(提供源码下载)
核心:
主要利用MVC的区域功能,实现项目模块独立开发和调试。
目标:
各个模块以独立MVC应用程序存在,即模块可独立开发和调试。
动态注册各个模块路由。
一:新建解决方案目录结构
如图:
二:EasyMvc.Core即为核心库。
核心库三大主力:AreaConfig 、RouteConfig 、FilterConfig
AreaConfi...
to_string C++ typeid().name()看对象类型
#include<iostream>
#include <typeinfo>
#include <string>
using namespace std;
int main() {
int a = 10;
string s=to_string(a);
string s1 = "12ppp";
cout << ...
文章链接:https://www.dianjilingqu.com/1579.html
本文章来源于网络,版权归原作者所有,如果本站文章侵犯了您的权益,请联系我们删除,联系邮箱:saisai#email.cn,感谢支持理解。