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.继承方式

一共有三种继承方式:

  • 公共继承
  • 保护继承
  • 私有继承
    image
    父类中私有的成员只是被隐藏了,但还是会继承下去

公共继承

除父类中私有成员外,其他所有成员将会被显式继承,其访问权限保持不变

保护继承

除父类中私有成员外,其他所有成员将会被显式继承,其访问权限变为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.菱形继承(钻石继承)

两个子类继承同一个父类,又有某个类同时继承两个子类,这种继承被称为菱形继承(钻石继承)

image

问题

  1. 羊继承动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,会产生二义性
  2. 菱形继承导致数据有两份,资源浪费

解决方案: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.动态多态的满足条件

  1. 有继承关系
  2. 子类重写父类的虚函数

3.动态多态的使用

父类的指针或引用 执行子类的对象

4.多态的原理剖析

vfptr:虚函数(表)指针(virtual function pointer)
vftable:虚函数表(virtual function table)
image

5.多态的好处

  1. 组织结构清晰
  2. 可读性强
  3. 前期和后期扩展及维护性高

6.纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要是调用子类重写的内容
因此,可以将虚函数改为纯虚函数

语法

virtual 返回值类型 函数名 (参数列表) = 0; 

当类中有了纯虚函数,此类也叫抽象类

抽象类特点:

  1. 无法实例化对象
  2. 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

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...

王道机试指南题解(C/C++版)

第 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...

error: invalid operands of types 'int' and '<unresolved overloaded function type>' to binary 'operator<<' cout << a&&b <<endl错误

cout << a&&b <<endl这一行出现了这个错误
查了下是因为运算符优先级的问题,加个()就行了
cout << (a&&b) <<endl

 

...

黑马程序员-传智健康项目(第六章)

传智健康项目

黑马程序员-传智健康项目(第一章)
黑马程序员-传智健康项目(第二章)
黑马程序员-传智健康项目(第三章)
黑马程序员-传智健康项目(第四章)
黑马程序员-传智健康项目(第五章)
黑马程序员-传智健康项目(第六章)
黑马程序员-传智健康项目(第七章)
黑马程序员-传智健康项目(第八章)
黑马程序员-传智健康项目(第九章)
黑马程序员-传智健康项目(第十章)
黑马程序员-传智健康项...

让我手把手教你写一个强大、方便使用的 IOC 容器

一、介绍
    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 << ...

文章标题:C++ 第四章 类和对象
文章链接:https://www.dianjilingqu.com/1579.html
本文章来源于网络,版权归原作者所有,如果本站文章侵犯了您的权益,请联系我们删除,联系邮箱:saisai#email.cn,感谢支持理解。
THE END
< <上一篇
下一篇>>