黑马程序员C++核心编程

int main() {
//int a = 10;//变量,可读可写
//func(a); //走func(int& a)
//func(10);//走func(const int &a)
//func2(10);//当函数重载碰到默认参数,出现二义性,报错,
//尽量避免,写函数重载时就不要加默认参数
return 0;
}



## 4 类和对象



### 4.1 封装



#### 4.1.1 封装的意义



#### 4.1.2 struct和class区别



#### 4.1.3 成员属性设置为私有




#include
#include//包含字符的头文件
#include"point.h"
#include"circle.h"
using namespace std;

/类和对象
C++面向对象的三大特性:封装、继承、多态
例如:人可以作为对象,属性有姓名,年龄,身高,体重,,行为有走,跑,跳,吃饭,唱歌
具有相同性质的对象,我们可以抽象为类,人属于人类,车属于车类
/
/*
-----------------------------------封装----------------------------------------------

意义一:将属性和行为作为一个整体,表现生活中的事物;将属性和行为加以权限控制
语法:class 类名{  访问权限: 属性  /行为  };

示例1:
	设计一个圆类,求圆的周长,面积
	公式:2*pi*半径
*/

const double pi = 3.14159;

class Circle//设计一个类,类后面紧跟是类名称
{
public://访问权限,公共权限
//类中的属性和行为统称为成员
int m_r;//属性(又称成员属性或成员变量):半径
int m_d;
double calculateMJ()//行为(又称成员函数或成员方法):获取圆的面积
{
return pi * m_r * m_r;
}
double calculateZC(int d)//给直径赋值,并获取圆的周长
{
m_d = d;
return pi * m_d;
}
};
/*
封装
意义二:类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
1、public 公共权限(成员 类内可以访问,类外可以访问)
2、protected 保护权限(成员 类内可以访问,类外不可以访问)儿子也可以访问父亲中的保护内容
3、private 私有权限(成员 类内不可以访问,类外不可以访问)儿子不可以访问父亲中的私有内容
*/
class Person
{
public://公共权限
string m_Name;//姓名

protected://保护权限
string m_Car;

private:
int m_Password;//银行卡密码

private:
void func()
{
m_Name = “张三”;
m_Car = “帕拉梅拉”;
m_Password = 123456;
}
};

/*
class与struct的区别:
struct默认权限是 公共 public
class 默认权限是 私有 private*/

/*
成员属性设置为私有优点:
	1、可以自己控制读写权限。(设置一个对外的接口[成员行为]为 public,对属性设置 可读可写
							只读或只写)
	2、对于写权限,我们可以检测数据的有效性。(让用户输入数据时,对其检测是否有效)
	设置一个对外的接口为 public,*/
	//案例:设计一个立方体cube,求其表面积和体积。分别用全局函数和成员函数判断两个立方体是否相等

class Cube
{
public://2、设置成员行为(对外接口)获取面积体积
void setL(int l)//设置长
{
m_L = l;
}
int getL()//获取长
{
return m_L;
}
void setW(int w)//设置宽
{
m_W = w;
}
int getW()//获取宽
{
return m_W;
}
void setH(int h)//设置高
{
m_H = h;
}
int getH()//获取高
{
return m_H;
}
int calculateS()//获取面积
{
return 2 * (m_L * m_W + m_L * m_H + m_W * m_H);
}
int calculateV()//获取体积
{
return m_L * m_W * m_H;
}
bool isSameByClass(Cube& cu)//利用成员函数判断两个立方体是否相等
//已经有一个立方体,只需传进一个参数看是否相等
{
if (m_L == cu.getL() && m_W == cu.getW() && m_H == cu.getH())//类内可以访问成员属性
{
return true;
}
else
{
return false;
}
}
private://1、设置成员属性 为私有
int m_L;
int m_W;
int m_H;
};

bool isSame(Cube& cu1, Cube& cu2)//利用全局函数判断两个立方体是否相等
{
if (cu1.getL() == cu2.getL() && cu1.getW() == cu2.getW() && cu1.getH() == cu2.getH())
{
return true;
}
else
{
return false;
}
}
/*
在类中,可以让另一个类 作为本类中的成员
将一个类拆分到几个文件中
案例二:判断点和圆的关系
*/
void isInCircle1(Circle1& c, Point& p)
{
int distance =
(c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) +
(c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY());
int rDistance = c.getR() * c.getR();
if (distance == rDistance)
{
cout << “点在圆上” << endl;
}
else if (distance > rDistance)
{
cout << “点在圆外” << endl;
}
else if (distance < rDistance)
{
cout << “点在圆内” << endl;
}
}
int main()
{
Circle c1;//通过圆类 创建具体的圆(对象),实例化对象
c1.m_r = 10;//给圆对象 的属性进行赋值
cout << "圆的面积是: " << c1.calculateMJ() << endl;
cout << "圆的周长是: " << c1.calculateZC(20) << endl;
//------------------------------------------------------------------------------------
Person p1;
p1.m_Name = “李四”;
//p1.m_Car = “猛禽”;//错误|保护权限在类外访问不到
//p1.Password = 123;//错误|私有权限在类外也访问不到
//p1.func();//错误|也不能调用类内的私有权限函数

//-------------------------------------------------------------------------------------

Cube cu1;//创建立方体对象
cu1.setL(10);
cu1.setW(10);
cu1.setH(10);
cout << "cu1的面积是:" << cu1.calculateS() << endl;
cout << "cu1的体积是:" << cu1.calculateV() << endl;

Cube cu2;//创建第二个立方体对象
cu2.setL(10);
cu2.setW(10);
cu2.setH(10);
bool ret = isSame(cu1, cu2);//利用全局函数
if (ret)
{
	cout << "cu1和cu2相等" << endl;
}
else
{
	cout << "cu1和cu2不相等" << endl;
}
bool ret1 = cu1.isSameByClass(cu2);//cu1是一个已知的立方体,用cu1来调用成员函数传入未知的cu2
if (ret1)
{
	cout << "cu1和cu2相等" << endl;
}
else
{
	cout << "cu1和cu2不相等" << endl;
}

//----------------------------------------------------------------------------

Circle1 c;
c.setR(10);
Point center;
center.setX(10);
center.setY(0);
c.setCenter(center);
Point p;
p.setX(10);
p.setY(9);
isInCircle1(c, p);

//----------------------------------继承----------------------------------------------

//----------------------------------多态----------------------------------------------

return 0;

}



point.h



#pragma once//防止头文件重复包含
#include
using namespace std;
class Point
{
public:
void setX(int x);
int getX();
void setY(int y);
int getY();
private:
int m_X;
int m_Y;
};



point.cpp



#include"point.h"
void Point::setX(int x)//不带Point::函数是全局函数,会报错。
//告诉程序这是Point作用域下的成员函数
{
m_X = x;
}
int Point::getX()
{
return m_X;
}
void Point::setY(int y)
{
m_Y = y;
}
int Point::getY()
{
return m_Y;
}



circle.h



#pragma once//防止头文件重复包含
#include
#include"point.h"//在类中用到另一个类,要包含其头文件
using namespace std;
class Circle1
{
public:
void setR(int r);
int getR();
void setCenter(Point center);
Point getCenter();
private:
int m_R;
Point m_Center;//在类中可以让另一个类作为本类中的成员
};



circle.cpp



#include"circle.h"

void Circle1::setR(int r)
{
m_R = r;
}
int Circle1::getR()
{
return m_R;
}
void Circle1::setCenter(Point center)
{
m_Center = center;
}
Point Circle1::getCenter()
{
return m_Center;
}



### 4.2 对象的初始化和清理



* 生活中电子产品的出厂设置, 不再使用时删除数据确保安全
* C++中的面向对象来源于生活, 每个对象也都有初始设置以及对象销毁前的清理数据的设置



#### 4.2.1 构造函数和析构函数


对象的**初始化**和**清理**也是两个非常重要的安全问题


一个对象或者变量没有初始状态, 对其使用后果是未知的


同样的使用完一个对象或变量, 没有及时清理,也会造成一定的安全问题



C++利用**构造函数**和**析构函数**解决上述问题, 这两个函数会被编译器自动调用, 完成对象初始化和清理工作


对象的初始化和先清理工作是编译器强制要我们做的事情, 因此如果我们不提供**构造和析构**, 编译器会提供


**编译器提供的构造函数和析构函数是空实现**



* 构造函数: 主要作用于创建对象时为对象的成员属性赋值, 构造函数由编译器自动调用, 无需手动调用
* 析构函数: 主要作用于对象**销毁前**系统自动调用, 执行一些清理工作



**构造函数语法:** `类名(){}`


1. 构造函数, 没有返回值, 也不写void
2. 函数名称与类名相同
3. 构造函数可以有参数, 因此可以发生重载
4. 程序在调用对象时, 会自动调用构造, 无需手动构造, 而且只会调用一次



**析构函数语法:** `~类名(){}`


1. 析构函数, 没有返回值, 也不写void
2. 函数名称与类名相同, 在名称前加上符号 ~
3. 析构函数不可以有参数, 因此不可以发生重载
4. 程序在对象销毁前会自动调用析构, 无需手动调用, 而且只会调用一次



#include
using namespace std;

//1.构造函数 进行初始化操作
class Person {
public:
//无返回值,不写void
//函数名与类名相同
//可以有参数,可以发生重载
//创建对象时,构造函数会自动调用,而且只调用一次
Person() {//你不写 编译器也会写,只是里面是空操作
cout << “Person 构造函数的调用” << endl;
}

//2.析构函数 进行清理的操作
// 无返回值, 不写void
//函数名与类名相同
//不可以有参数,不可以发生重载
//在对象销毁前,析构函数会自动调用,而且只调用一次
~Person() {
	cout << "Person 析构函数的调用" << endl;
}

};

//构造和析构都是必须有的,如果我们不提供,编译器会提供一个空实现的构造和析构
void test01() {
Person p;//在栈上的数据,test01执行完毕后,释放这个对象
}

int main() {
test01();
//Person p;

return 0;

}



#### 4.2.2 构造函数的分类及调用


两种分类方式:


1. 按参数分为: 有参构造(默认构造)和无参构造
2. 按类型分为: 普通构造和拷贝构造


三种调用方式:


1. 括号法
2. 显示法
3. 隐式转换法


**示例:**



#include
using namespace std;

//两种分类方式:

//1. 按参数分为 : 无参构造(默认构造)和有参构造
//2. 按类型分为 : 普通构造和拷贝构造

class Person {
public:
Person() {//无参
cout << “Person 的无参构造函数调用”<<endl;
}

Person(int a) {//有参
	age = a;
	cout << "Person 的有参构造函数调用" << endl;
}

Person(const Person& p) {//拷贝构造函数
	age = p.age;//将传入的人身上的所有属性,拷贝到我身上
	cout << "Person 的拷贝构造函数" << endl;
}

~Person() {
	
	cout << "Person 的析构函数调用" << endl;
}

int age;

};

//调用
void test01() {

//1. 括号法
//Person p1;//默认构造函数调用
//Person p2(10);//有参构造函数调用
//Person p3(p2);//拷贝构造函数

//注意事项:
// 1. 调用默认构造时,不要加()
// Person p1();//错误,编译器会认为这是一个函数声明,不会认为在创建对象

//cout << "P2的年龄: " << p2.age << endl;
//cout << "P3的年龄: " << p3.age << endl;//年龄相同

//2. 显示法
//Person p1;
//Person p2 = Person(10);//有参构造
//Person p3 = Person(p2);//拷贝构造

//Person(10);//匿名对象 特点: 当前行执行结束后,系统会立即回收掉匿名对象
//cout << " aaa " << endl;

//注意事项:
// 2. 不要利用拷贝构造函数 初始化匿名对象
// Person(p3);
//编译器会认为 Person(p3);<==>Person p3;函数声明

//3. 隐式转化法
Person p4 = 10;//相当于 写了 Person p4 = Person(10)  ;
Person p5 = p4;//拷贝构造

}
int main() {
test01();
return 0;
}




#### 4.2.3 拷贝构造函数调用时机



C++中拷贝构造函数调用时机通常有三种情况:


1. 使用一个已经创建完毕的对象来初始化一个新对象
2. 值传递的方式给函数传值
3. 以值方式返回局部对象


**示例:**



#include
using namespace std;

//C++中拷贝构造函数调用时机通常有三种情况:

class Person {
public:
Person() {
cout << “Person 的无参构造函数调用” << endl;
}

Person(int age) {
	m_Age = age;
	cout << "Person 的有参构造函数调用" << endl;
}

Person(const Person& p) {
	m_Age = p.m_Age;
	cout << "Person 的拷贝构造函数调用" << endl;
}

~Person() {
	cout << "Person 的析构函数调用" << endl;
}
int m_Age;

};

//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01() {
Person p1(20);
Person p2(p1);
cout << “p2 的年龄:” <<p2.m_Age<< endl;
}

//2. 值传递的方式给函数传值
void doWork(Person p) {

}
void test02() {
Person p;
doWork§;
}

//3. 以值方式返回局部对象
Person doWork2() {
Person p1;
return p1;
}
void test03() {
Person p = doWork2();
}
int main() {
//test01();
//test02();
test03();
return 0;
}



#### 4.2.4 构造函数调用规则


默认情况下,C++编译器至少给一个类添加三个函数


1. 默认构造函数(无参,函数体为空)
2. 默认析构函数(无参,函数体为空)
3. 默认拷贝构造函数,对属性进行值拷贝



构造函数调用规则如下:


* 如果用户定义有参构造函数,C++不再提供给默认无参构造,但仍会提供默认拷贝构造
* 如果用户定义拷贝构造函数,C++不会再提供其他构造函数


**示例:**



#include
using namespace std;

//默认情况下, C++编译器至少给一个类添加三个函数
//
//1. 默认构造函数(无参, 函数体为空)
//2. 默认析构函数(无参, 函数体为空)
//3. 默认拷贝构造函数, 对属性进行值拷贝

//构造函数调用规则如下:
//
//1.如果用户定义有参构造函数, C++不再提供给默认无参构造, 但仍会提供默认拷贝构造
//2.如果用户定义拷贝构造函数, C++不会再提供其他普通构造函数

class Person {
public:
/Person(){
cout << “默认构造函数调用” << endl;
}
/

Person(int age) {
	cout << "有参构造函数调用" << endl;
	m_Age = age;
}

/*Person(const Person& p) {
	cout << "拷贝构造函数调用" << endl;
	m_Age = p.m_Age;
}*/

~Person() {
	cout << "析构函数调用" << endl;
}

int m_Age;

};

//void test01() {
// Person p;
// p.m_Age = 18;
//
// Person p2§;
//
// cout << “p2的年龄:” << p2.m_Age << endl;
//}

void test02() {
Person p(18);
Person p2§;
cout << “p2的年龄:” << p2.m_Age << endl;
//}
}
int main() {
//test01();
test02();
return 0;
}



#### 4.2.5 深拷贝与浅拷贝



深浅拷贝是面试经典问题,也是常见的一个坑



浅拷贝: 简单的赋值拷贝操作



深拷贝: 在堆区重新申请空间,进行拷贝构造



**示例:**



#include
using namespace std;

class Person {
public:
Person() {
cout << “Person 的无参构造函数调用”<<endl;
}

	Person(int age,int height) {
		m_Age=age;
		m_Height=new int(height);
		cout << "Person 的有参构造函数调用" << endl;
	}
	
	//自己实现拷贝构造函数 解决浅拷贝带来的问题:堆区的内存重复释放
	Person(const Person& p) {
		cout << "Person 的拷贝函数的调用" << endl;
		m_Age = p.m_Age;//浅拷贝
		//m_Height = p.m_Height;浅拷贝编译器默认实现就是这行代码
		
		//深拷贝操作:在堆区重新申请空间,进行拷贝构造
		m_Height=new int(*p.m_Height);
	}
	~Person() {
		//析构代码,将堆区开辟的数据做释放操作
		if (m_Height != NULL) {
			delete m_Height;
			m_Height = NULL;
		}
		cout << "Person 的析构函数调用" << endl;
	}
	int m_Age;
	int* m_Height;
	
};

void test01() {
Person p1(18,172);
cout << “p1的年龄:” << p1.m_Age <<“p1的身高:” <<*p1.m_Height<< endl;

Person p2(p1);
cout << "p2的年龄:" << p2.m_Age << "p2的身高:" << *p2.m_Height << endl;

}
int main() {
test01();
return 0;
}


**总结:**如果属性有在堆区开辟的, 一定要自己提供拷贝构造函数, 防止浅拷贝带来的问题.


![](https://img-blog.csdnimg.cn/direct/6aa0f22f2a3841b5812cc7e3b0678bbe.png)





#### 4.2.6 初始化列表



**作用:**


C++提供了初始化列表语法, 用来初始化属性



**语法:**`构造函数():属性1(值1),属性2(值2)...{}`



**示例:**



#include
using namespace std;

//初始化列表
class Person {
public:
//传统初始化操作
/Person(int a, int b, int c) {
m_A = a;
m_B = b;
m_C = c;
}
/

//初始化列表初始化属性
Person(int a,int b,int c) :m_A(a), m_B(b), m_C(c) {

}

int m_A;
int m_B;
int m_C;

};

void test01() {
//Person p(0, 1, 2);
Person p(0,1,2);
cout << “m_A=” << p.m_A << endl;
cout << “m_B=” << p.m_B << endl;
cout << “m_C=” << p.m_C << endl;
}
int main() {
test01();
return 0;
}



#### 4.2.7 类对象作为类成员



C++类中的成员可以是类一个类的对象, 我们称该成员为 对象成员



例如:



class A{}
class B{
A a;
}



B类中有对象A作为成员, A为对象成员



那么当创建B对象时, A与B的构造和析构的顺序是谁先谁后?



**示例:**



#include
using namespace std;
#include

//类对象作为类成员

//手机类
class Phone {
public:
Phone(string pName) {
//cout << “手机” << endl;
m_PName = pName;
}
/~Phone() {
cout << “这是Phone析构函数调用” << endl;
}
/
string m_PName;
};

class Person {
public:
//Phone m_Phone=pName 隐式转换法
Person(string name, string pName) :m_Name(name), m_Phone(pName) {
//cout << “人” << endl;
}
/~Person() {
cout << “这是Person析构函数调用” << endl;
}
/
//姓名
string m_Name;
//手机
Phone m_Phone;
};

//当其他类对象作为类成员,构造时候先构造对象,再构造自身,
//析构的顺序与构造相反

void test01() {
Person p(“张三”, “HUAWEI”);
cout << p.m_Name << "的手机是: " << p.m_Phone.m_PName << endl;
}
int main() {
test01();
return 0;
}



#### 4.2.8 静态成员


静态成员就是在成员变量和成员函数前加上关键字static, 成为静态成员


静态成员分为:



* 静态成员变量


	+ 所有对象共享同一份数据
	+ 在编译阶段分配内存(程序还没运行之前)
	+ 类内声明, 类外初始化



**示例1:**静态成员变量



#include
using namespace std;

//静态成员变量
class Person {
public:
//1. 所有对象都共享同一份数据
//2. 在编译阶段分配内存(程序还没运行之前)
//3. 类内声明, 类外初始化操作

static int m_A;//类内声明

//静态成员变量也是有访问权限的

private:
static int m_B;

};

int Person::m_A = 100;//类外初始化,::这是Person作用域下的m_A
int Person::m_B = 200;

void test01() {
Person p;
cout << p.m_A << endl;

Person p2;
p2.m_A = 200;
cout << p.m_A << endl;

}

void test02() {
//静态成员变量不属于某一个对象上,所有的对象都共享同一份数据
//因此静态成员变量有两种访问方式

//1. 通过对象进行访问
/*Person p;
cout << p.m_A << endl;*/

//2. 通过各类名进行访问
cout << Person::m_A << endl;

//cout << Person::m_B << endl;//错误,类外不能访问私有静态成员变量

}
int main() {
//test01();
test02();
return 0;
}


**示例2:**



#include
using namespace std;

//静态成员函数特点:
// 所有对象共享同一个函数
// 静态成员函数只能访问静态成员变量

class Person {
public:
//静态成员函数
static void func() {
m_A = 100;//静态成员函数可以访问静态成员变量
//m_B = 200;//错误,静态成员函数 不可以 访问非静态成员变量
//无法区分到底是哪个对象的m_B属性
cout << “static void func的调用” << endl;
}

static int m_A;
int m_B;

//静态成员函数也是有访问权限的

private:
static void func2() {
cout << “static void func2的调用” << endl;
}
};
int Person::m_A=0;

//两种访问方式
void test01() {

//通过对象访问
Person p;
p.func();

//通过类名访问
Person::func();

//Person::func2();//错误 类外访问不到私有静态成员函数

}
int main() {
test01();
return 0;
}




### 4.3 C++对象模型和this指针



#### 4.3.1 成员变量和成员函数分开存储



在C++中, 类成员变量和成员函数分开存储


只有非静态成员变量才属于类的对象上



**示例:**



#include
using namespace std;

class Person {
int m_A;//非静态成员变量 属于类的对象上

static int m_B;//静态成员变量  不属于类对象上

void func() {//非静态成员函数  不属于类对象上
}
static void func2() {//静态成员函数  不属于类对象上
}

};

int Person::m_B=0;

void test01() {
Person p;
//空对象占用的内存空间为: 1
//C++编译器会给每个空对象也分配一个字节的空间,是为了区分对象占内存的位置
//每个空对象也应该有一个独一无二的内存地址
cout << "size of p = " << sizeof§ << endl;
}

void test02() {
Person p;
cout << "size of p = " << sizeof§ << endl;
}
int main() {
//test01();
test02();
return 0;
}



#### 4.3.2 this指针概念


通过4.3.1知道,在C++中, 成员变量和成员函数是分开存储的


每一个非静态成员函数只会诞生一份函数实例, 即多个同类型的对象会共用一块函数代码


问题是: 这一块代码如何区分哪个对象调用自己?



C++通过提供特殊的对象指针, this指针, 解决上述问题, **this指针指向被调用的成员函数所属的对象**



this指针是隐含在每一个非静态成员函数内的一种指针


this指针不需要定义, 直接使用即可



this指针的用途:


* 当形参和成员变量同名时,可用this指针来区分
* 在类的非静态成员函数中返回对象本身,可使用 return \*this



#include
using namespace std;

//this指针的用途:

class Person {
public:

//1. 解决名称冲突

//当形参和成员变量同名时, 可用this指针来区分

Person(int age) {

	//this指针指向 被调用的成员函数所属的对象,即p1
	 this->age = age;
}

//2. 返回对象本身,使用 return *this

Person& PesonAddAge(Person &p) {//以值对象返回(即不带&)相当于创建一个新的对象
								//以引用方式(带&)返回,不会创建新的对象,一直返回p2
	this->age += p.age;

	//this 指向p2的指针,而*this 指向的就是p2这个对象本体
	return *this;//返回p2
}
int age;

};

void test01() {
Person p1(18);
cout << "p1的年龄: " <<p1.age<< endl;
}

void test02() {
Person p1(10);
Person p2(20);

//链式编程思想
p2.PesonAddAge(p1).PesonAddAge(p2).PesonAddAge(p2);

cout << "p2的年龄为: " << p2.age << endl;

}

int main() {
//test01();
test02();
return 0;
}



#### 4.3.3 空指针访问成员函数



C++中空指针也可以调用函数的, 但是要注意有没有用到this指针



如果用到this指针,需要加以判断, 保证代码的健壮性



**示例:**



#include
using namespace std;

class Person {
public:

void showClassName() {
	cout << "this is Person class" << endl;
}

void showPersonAge() {

	//报错原因是 传入的指针是为NULL

	//解决
	if (this == NULL) {
		return; 
	}
	cout << "age = " << this->m_Age << endl;
}

int m_Age;

};

void test01() {
Person* p=NULL;
//p->showClassName();
p->showPersonAge();
}
int main() {
test01();
return 0;
}



#### 4.3.4 const修饰成员函数



**常函数:**


* 成员函数后加const后我们称这个函数为**常函数**
* 常函数内不可以修改成员属性
* 成员函数声明时加关键字mutable后, 在常函数中依旧可以修改



**常对象:**


* 声明对象前加const称该对象为常对象
* 常对象只能调用常函数



**示例:**



#include
using namespace std;

//常函数
class Person {
public:

//this指针的本质 是指针常量 指针的指向不可以修改
//在成员函数后加上 const ,修饰的是this指向,让指针指向也不可以修改
void showPerson() const{//相当于const Person* const this
	//this->m_A = 100;
	//this=NULL;//this 指针不可以修改指针指向
}

int m_A;
mutable int m_B;//特殊变量,即使在常函数中,也可以修改这个值,加上关键字mutable

void func() {
	cout<<"123" << endl;
}

};

void test01() {
Person p;
p.showPerson();
}

//常对象
void test02() {
const Person p;//在对象前加const,变为常对象
//p.m_A = 100;//常对象的属性也不能修改
p.m_B = 100;//,m_B是特殊值 ,常对象下也可以修改

//常对象只能调用常函数
//p.func();//常对象 不可以调用普通成员函数,因为普通成员函数可以修改属性

}
int main() {
test01();
test02();
return 0;
}



### 4.4 友元



生活中家里的客厅(Public), 卧室(Private)


客厅所有客人都能进, 但卧室是私有的,只有你自己能进


但是, 你也可以允许好朋友进去



在程序中, 有些私有属性 也想让类外特殊的一些函数或者类进行访问, 就需要用到友元的技术



友元的目的是让一个函数或者一个类 访问另一个类中私有成员



友元的关键字是: friend



友元的三种实现:


* 全局函数做友元
* 类做友元
* 成员函数做友元



#### 4.4.1 全局函数做友元


`friend void goodGay(Building* building);`



#include
using namespace std;
#include

//建筑物类
class Building {
//goodGuy全局函数是 Building 的好朋友,可以访问Building中私有成员
friend void goodGay(Building* building);
public:
Building() {
m_SittingRoom = “客厅”;
m_BedRoom = “卧室”;
}

public:
string m_SittingRoom;//客厅

private:
string m_BedRoom;//卧室

};

//1. 全局函数
void goodGay(Building* building) {
cout << "好基友全局函数正在访问: " << building->m_SittingRoom << endl;
cout << "好基友全局函数正在访问: " << building->m_BedRoom << endl;

}

void test01(){
Building building;
goodGay(&building);
}
int main() {
test01();
return 0;
}



#### 4.4.2 类做友元


``friend class GoodGuy;`



#include
using namespace std;
#include

//类做友元
class Building;
class GoodGuy {
public:
GoodGuy();
void visit();//参观函数访问Building中的属性

Building* building;

};

class Building {
//GoodGuy类是本类的好朋友,可以访问本类中的私有成员
friend class GoodGuy;

public:
Building();
public:
string m_SittingRoom;//客厅

private:
string m_BedRoom;//卧室
};

//类外写成员函数
Building::Building() {//Building类作用域下的BUuilding函数
m_SittingRoom = “客厅”;
m_BedRoom = “卧室”;
}

GoodGuy::GoodGuy() {
//创建建筑物的对象
building = new Building; //new什么类型,就返回什么类型的指针
}

void GoodGuy::visit() {
cout << “好基友类正在访问:” << building->m_SittingRoom << endl;
cout << “好基友类正在访问:” << building->m_BedRoom << endl;
}

void test01() {
GoodGuy gg;
gg.visit();
}
int main() {
test01();
return 0;
}



#### 4.4.3 成员函数做友元


``friend void GoodGay::visit();`



#include
using namespace std;
#include

class Building;
class GoodGay {
public:

GoodGay();

void visit();//让visit函数可以访问Building中私有成员
void visit2();//让visit2函数不可以访问Building中私有成员

Building* building;

};

class Building {

//告诉编译器,GoodGay类下的visit成员函数作为本类的好朋友,可以访问私有成员
friend void GoodGay::visit();

public:
Building();

public:
string m_SittingRoom;

private:
string m_BedRoom;
};

//类外实现成员函数
Building::Building() {
m_SittingRoom = “客厅”;
m_BedRoom = “卧室”;
}

GoodGay::GoodGay() {
building = new Building;
}

void GoodGay::visit() {
cout << “visit函数正在访问:” << building->m_SittingRoom << endl;
cout << “visit函数正在访问:” << building->m_BedRoom << endl;
}

void GoodGay::visit2() {
cout << “visit2函数正在访问:” << building->m_SittingRoom << endl;
//cout << “visit2函数正在访问:” << building->m_BedRoom << endl;
}

void test01() {
GoodGay gg;
gg.visit();
gg.visit2();
}
int main() {
test01();
return 0;
}



### 4.5 运算符重载



运算符重载概念:对已有的运算符重新定义,赋予另一种功能,以适应不同的数据类型



#### 4.5.1 加号运算符重载



作用: 实现两个自定义数据类型相加的运算




#include
using namespace std;

class Person {
public:
//1. 成员函数重载+号
/Person operator+(Person& p) {
Person temp;
temp.m_A= this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
/
int m_A;
int m_B;
};

//2. 全局函数重载+号
Person operator+(Person& p1, Person& p2) {
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}

//函数重载的版本
Person operator+(Person& p1, int num) {
Person temp;
temp.m_A = p1.m_A + num;
temp.m_B = p1.m_B + num;
return temp;
}

void test01() {
Person p1;
p1.m_A = 10;
p1.m_B = 10;

Person p2;
p2.m_A = 10;
p2.m_B = 10;

//成员函数重载本质的调用
//Person p3 = p1.operator+(p2);

//全局函数重载本质的调用
//Person p3 = operator+(p1, p2);

//两种本质调用可以简化为:
Person p3 = p1 + p2;
//运算符重载 也可以发生函数重载

Person p4 = p1 + 100;//

cout << "p3.m_A=" << p3.m_A << endl;
cout << "p3.m_B=" << p3.m_B << endl;

cout << "p4.m_A=" << p4.m_A << endl;
cout << "p4.m_B=" << p4.m_B << endl;

}

int main() {
test01();
return 0;
}


**总结:**


1. 对于内置的数据类型的表达式的运算符是不能改变的
2. 不要滥用运算符重载



#### 4.5.2 左移运算符重载



作用: 可以输出自定义的数据类型




#include
using namespace std;

//左移运算符重载

class Person {
public:
Person(int a, int b) {
m_A = a;
m_B = b;
}
friend ostream& operator<<(ostream& cout, Person& p);
private:

//利用成员函数重载 左移运算符
//不会利用成员函数重载<<运算符,因为无法实现cout在左侧
//void operator<<() {
//
//}

int m_A;
int m_B;

};

//只能利用全局函数重载左移运算符
//ostream 对象只能有一个
ostream& operator<<(ostream &cout, Person& p) {//本质 operator<< (cout,p) 简化为 cout<<p
cout << "m_A= " << p.m_A << " m_B= " << p.m_B;
return cout;
}

void test01() {
Person p(10,10);

cout << p<<"  Hello World!"<<endl;//链式编程思想

}
int main() {
test01();

return 0;

}



#### 4.5.3 递增运算符重载



作用: 通过重载底递增运算符, 实现自己的整形数据




#include
using namespace std;

//递增运算符重载

//自定义整型
class MyInteger {
friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
MyInteger() {
m_Num = 0;
}

//重载前置++运算符  返回引用是为了一直对一个数据进行递增操作
MyInteger& operator++() {
	//先进行++运算
	m_Num++;
	//再将自身做返回
	return *this;
}

//重载后置++运算符
//void operator++(int) int代表占位参数,可以用于区分前置和后置递增
MyInteger operator++(int) {
	//先记录当时结果
	MyInteger temp = *this;
	//后 递增
	m_Num++;
	//最后将记录结果做返回
	return temp;
}

private:
int m_Num;
};

//重载左移运算符
ostream& operator<<(ostream &cout, MyInteger myint) {
cout <<myint.m_Num;
return cout;
}

void test01() {
MyInteger myInt;

cout << ++(++myInt) << endl;
cout << myInt << endl;

}

void test02() {
MyInteger myInt;

cout << myInt++ << endl;
cout << myInt << endl;

}
int main() {
test01();
test02();
return 0;
}


**总结:**  前置递增返回引用,后置递增返回值



#### 4.5.4 赋值运算符



C++编译器至少给一个类添加4个函数


1. 默认构造函数(无参, 函数体为空)
2. 默认析构函数(无参, 函数体为空)
3. 默认拷贝函数 , 对属性进行值拷贝
4. 赋值运算符 0perator=, 对属性进行值拷贝



如果类中有属性指向堆区, 做赋值操作时也会出现深浅拷贝问题



**示例:**



#include
using namespace std;

class Person {
public:

Person(int age) {

	//将年龄数据开辟到堆区
	m_Age=new int(age);
}

//重载 赋值运算符
Person& operator=(Person &p) {
	//编译器是提供浅拷贝
	//m_Age=p.m_Age;

	//p2本身有一个堆区的内存,存放20,应该先判断是否有属性在堆区,如果有,先释放干净再深拷贝
	if (m_Age != NULL) {
		delete m_Age;
		m_Age = NULL;
	}

	//提供深拷贝,解决浅拷贝问题
	m_Age = new int(*p.m_Age);


	//返回对象本身
	return *this;
}

~Person() {
	if (m_Age != NULL) {
		delete m_Age;
		m_Age = NULL;
	}
}

int* m_Age;

};

void test01() {

Person p1(18);

Person p2(20);

Person p3(30);

p3 = p2 = p1;//赋值操作 

cout << "p1的年龄是: " << *p1.m_Age << endl;

cout << "p2的年龄是: " << *p2.m_Age << endl;

cout << "p3的年龄是: " << *p3.m_Age << endl;

}

int main() {
test01();

/*int a = 10;
int b = 20;
int c = 30;

c = b = a;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;*/
return 0;

}



#### 4.5.5 关系运算符重载



作用: 重载关系运算符, 可以让两个自定义类型对象对比操作



示例:



#include
using namespace std;

//重载关系运算符

class Person {
public:
Person(string name, int age) {
m_Name = name;
m_Age = age;
}

//重载关系运算符==

bool operator==(Person& p) {
	if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {
		return true;
	}
	return false;
}

//重载关系运算符!=

bool operator!=(Person& p) {
	if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {
		return false;
	}
	return true;
}
string m_Name;
int m_Age;

};

void test01() {
Person p1(“Tom”, 18);

Person p2("Tom", 18);

if (p1 == p2) {
	cout << "p1与p2相等!" << endl;
}
else {
	cout << "p1和p2不相等!" << endl;
}

if (p1 != p2) {
	cout << "p1与p2不相等!" << endl;
}
else {
	cout << "p1和p2相等!" << endl;
}

}

int main() {
test01();
return 0;
}



#### 4.5.6 函数调用运算符重载



* 函数调用运算符 () 也可以重载
* 由于重载后使用的方式非常像函数的调用,因此称为仿函数
* 仿函数没有固定写法, 非常灵活



**示例:**



#include
using namespace std;

//函数调用运算符重载

//打印输出类
class MyPrint {
public:
//重载函数调用运算符
void operator()(string test) {
cout << test << endl;
}
};

void MyPrint02(string test) {
cout << test << endl;
}

void test01() {
MyPrint myPrint;

myPrint("Hello World!");//由于使用起来非常类似于函数调用,因此称为仿函数

MyPrint02("Hello World!");

}

//仿函数非常灵活,没有固定写法

//加法类
class MyAdd {
public:

int operator()(int num1, int num2) {
	return num1 + num2;
}

};

void test02() {
MyAdd myadd;
int ret=myadd(100, 100);
cout << "ret= " << ret << endl;

//匿名函数对象
cout <<"MyAdd()(100, 100)=" << MyAdd()(100, 100) << endl;

}
int main() {

//test01();
test02();
return 0;

}



### 4.6 继承


**继承是面向对象的三大特性之一**


有些类与类之间存在特殊的关系,


例如:动物类中 有猫类和 狗类, 猫类和狗类都有自己的特点, 同时又具有 上一级 动物类的共性. 狗类中又有 哈士奇, 边牧, 金毛等类, 他们每种也是有自己的特点, 同时也具有上一级 狗类的共性.



在定义这些类时,下级别的成员除了拥有上一级的共性, 还有自己的特性



这个时候我们可以考虑利用继承的技术, 减少代码重复量



#### 4.6.1 继承的基本语法



例如很多网站, 都有公共的头部, 公共的底部, 甚至公共的左侧列表, 只有中心内容不同


下面我们分贝利用普通写法和继承的写法来实现网页内容,看一下继承存在的意义及好处



**继承的好处**:减少重复的代码 **继承的语法**:`class 子类 : 继承方式 父类`class A : public B


* 子类 也称为 派生类
* 父类 也成为 基类


**派生类种的成员, 包含两大部分:**


一类是从基类继承过来的,一类是自己增加的成员


从基类继承过来的表现其共性, 而新增成员体现了其个性



**普通实现:**



Java页面
class Java{
public:
void header() {
cout << “首页 公开课 登录 注册…(公共头部)” << endl;
}
void footer() {
cout << “帮助中心 交流合作 站内地图…(公共底部)” << endl;
}
void left() {
cout << “JAVA Python C++…(公共分类列表)” << endl;
}
void content() {
cout << “JAVA学科视频” << endl;
}
};

//Python页面
class Python {
public:
void header() {
cout << “首页 公开课 登录 注册…(公共头部)” << endl;
}
void footer() {
cout << “帮助中心 交流合作 站内地图…(公共底部)” << endl;
}
void left() {
cout << “JAVA Python C++…(公共分类列表)” << endl;
}
void content() {
cout << “Python学科视频” << endl;
}
};

//C++页面
class CPP {
public:
void header() {
cout << “首页 公开课 登录 注册…(公共头部)” << endl;
}
void footer() {
cout << “帮助中心 交流合作 站内地图…(公共底部)” << endl;
}
void left() {
cout << “JAVA Python C++…(公共分类列表)” << endl;
}
void content() {
cout << “C++学科视频” << endl;
}
};



**继承实现:**



class BasePage {
public:

void header() {
	cout << "首页 公开课 登录 注册...(公共头部)" << endl;
}
void footer() {
	cout << "帮助中心 交流合作 站内地图...(公共底部)" << endl;
}
void left() {
	cout << "JAVA Python C++...(公共分类列表)" << endl;
}

};

//Java页面
class Java : public BasePage {
public:
void content() {
cout << “Java学科视频” << endl;
}
};

//Python页面
class Python : public BasePage {
public:
void content() {
cout << “Python学科视频” << endl;
}
};
//C++页面
class CPP : public BasePage {
public:
void content() {
cout << “C++学科视频” << endl;
}
};

void test01() {
cout << "JAVA下载视频页面如下: " << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();

cout << "-------------------------" << endl;

cout << "Python下载视频页面如下:  " << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();

cout << "-------------------------" << endl;

cout << "C++下载视频页面如下:  " << endl;
CPP cpp;
cpp.header();
cpp.footer();
cpp.left();
cpp.content();

}

int main() {
test01();
return 0;
}



#### 4.6.2 继承方式


**继承的语法**:`class 子类 : 继承方式 父类`



**继承方式一共有三种:**


* 公共继承
* 保护继承
* 私有继承



![](https://img-blog.csdnimg.cn/direct/0dd63ec889d244a9a52e291c48595ac1.png)



#include
using namespace std;

//继承方式

//公共继承
class Base1{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};

class Son1 : public Base1 {
public:
void func() {
m_A = 10;//父类中的公共权限成员 到子类中依然是公共权限
m_B = 10;//父类中的保护权限成员 到子类中依然是保护权限
//m_C = 10;//父类中私有权限成员 到子类中访问不到
}
};

void test01() {
Son1 s1;
s1.m_A = 100;
//s1.m_B = 100;//报错,到Son1中m_B时保护权限,类内可以访问,类外访问不到
}

//保护继承
class Base2 {
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};

class Son2 :protected Base2 {
public:
void func() {
m_A = 10;//父类中的公共权限成员 到子类中变为保护权限
m_B = 10;//父类中的保护权限成员 到子类中依然是保护权限
//m_C = 10;//父类中私有权限成员 到子类中访问不到
}
};
void test02() {
Son2 s2;
//s2.m_A = 100;//报错,在Son2中,m_A变为保护权限,因此类外访问不到
//s2.m_B = 100;//报错,在Son2中,m_B依然是保护权限, 不可以访问
}

//私有继承
class Base3 {
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};

class Son3 :private Base3 {
public:
void func() {
m_A = 10;//父类中的公共权限成员 到子类中变为私有权限
m_B = 10;//父类中的保护权限成员 到子类中变为私有权限
//m_C = 10;//父类中私有权限成员 到子类中访问不到
}
};

class GrandSon3:public Son3 {
public:
void func() {
//m_A = 1000;//到了Son3中,m_A变为私有,即使是儿子,也访问不到
//m_B = 1000;//到了Son3中,m_A变为私有,即使是儿子,也访问不到
}
};

void test03() {
Son3 s3;
//s3.m_A = 100;//报错,在Son3中,m_A变为私有成员,因此类外访问不到
//s3.m_B = 100;//报错,在Son3中,m_B变为私有成员,因此类外访问不到
}
int main() {
test01();
return 0;
}



#### 4.6.3 继承中的对象模型



**问题:**从父类继承过来的成员, 哪些属于子类对象中?



**示例:**



#include
using namespace std;

//继承中的对象模型

class Base {
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};

class Son : public Base {
public:
int m_D;
};

//利用开发人员命令提示工具查看对象模型
//跳转文件路径 cd 具体路径下
//查看命名
// cl /d1 reportSingleClassLayout类名 文件名

void test01() {
//输出16
//父类中所有非静态成员属性都会被子类继承下去
//父类中私有成员属性 是被编译器给隐藏了,因此访问不到,但确实被继承下去
cout << "sizeof(Son)= " << sizeof(Son) << endl;
}

int main() {
test01();
return 0;
}



![](https://img-blog.csdnimg.cn/direct/de5694501fa14edca3606dbf89cd40eb.png)



#### 4.6.4 继承中的构造和析构顺序



子类继承父类之后, 当创建子类对象, 也会调用父类的构造函数



问题: 父类和子类的构造和析构顺序谁先谁后?



**示例:**



#include
using namespace std;

//继承中的构造和析构顺序
class Base{
public:
Base() {
cout << “Base构造函数” << endl;
}

~Base() {
	cout << "Base析构函数" << endl;
}

};

class Son :public Base {
public:
Son() {
cout << “Son构造函数” << endl;
}

~Son() {
	cout << "Son析构函数" << endl;
}

};

void test01() {
//Base b;

//继承中的构造和析构顺序如下:
//先构造父类,再构造子类,析构顺序与构造的顺序相反
Son s;

}

int main() {
test01();
return 0;
}


**总结:**继承中, 先调用父类构造函数,再调用子类构造函数, 析构顺序与构造的顺序相反



#### 4.6.5 继承同名成员处理方式



问题: 当子类与父亲出现同名的成员, 如何通过子类对象, 访问到子类或父类中同名的数据呢?



* 访问子类同名成员, 直接访问即可
* 访问父类同名成员, 需要加作用域



**示例:** 



#include
using namespace std;

//继承中同名成员处理
class Base{
public:
Base() {
m_A = 100;
}

void func() {
	cout << "Base-func()调用" << endl;
}

void func(int a) {
	cout << "Base-func(int a)调用" << endl;
}

int m_A;

};

class Son :public Base {
public:
Son() {
m_A = 200;
}

void func() {
	cout << "Son-func()调用" << endl;
}
int m_A;

};

void test01() {
Son s;
cout << "Son 下 m_A= " << s.m_A << endl;

//如果通过子类对象,  访问到父类中的同名成员,  需要加作用域
cout << "Base 下 m_A= " << s.Base::m_A << endl;

}

void test02() {
Son s;
s.func();//直接调用 调用的是子类中的同名函数

//调用父类中的同名成员函数
s.Base::func();

//如果子类中出现和父类同名的成员函数,子类的同名函数会隐藏掉父类中所用同名成员函数
//如果想访问到父类中被隐藏的同名函数,需要加作用域
s.Base::func(100);

}

int main() {
//test01();
test02();
return 0;
}


**总结:**


1. 子类对象可以直接访问到子类中同名成员
2. 子类对象加作用域可以访问到父类同名成员
3. 当子类和父类拥有同名的成员函数, 子类会隐藏同名成员函数,加作用可以访问到父类同名函数




#### 4.6.6 继承同名静态成员处理方式



问题: 继承同名的静态成员在子类对象上如何进行访问?



静态成员和非静态成员出现同名, 处理方式一致



* 访问子类同名成员, 直接访问即可
* 访问父类同名成员, 需要加作用域



**示例:**



#include
using namespace std;

//继承中的同名静态成员处理方式
class Base{
public:
static int m_A;

static void func() {
	cout << "Base-static void func()调用" << endl;
}

static void func(int a) {
	cout << "Son-static void func(int a)调用" << endl;
}

};
int Base::m_A = 100;

class Son :public Base {
public:
static int m_A;

static void func() {
	cout << "Son-static void func()调用" << endl;
}

};
int Son::m_A = 200;

//同名静态成员属性
void test01() {

//1. 通过对象访问
cout << "通过对象访问:" << endl;
Son s;
cout << "Son 下 m_A=" << s.m_A << endl;
cout << "Base 下 m_A=" << s.Base::m_A << endl;

//2. 通过类名访问
cout << "通过类名访问:" << endl;
cout << "Son 下 m_A=" << Son::m_A << endl;
//第一个::代表通过类名方式访问,  第二个::代表访问父类作用域下
cout << "Base 下 m_A=" << Son::Base::m_A << endl;

}

//同名静态成员函数
void test02() {
//1. 通过对象访问
cout << “通过对象访问:” << endl;
Son s;
s.func();
s.Base::func();

//2. 通过类名访问
cout << "通过类名访问:" << endl;
Son::func();
Son::Base::func();

//子类出现和父类同名静态成员函数,  也会隐藏父类中所有的同名成员函数
//如果想访问父类中被隐藏的同名成员函数,  需要加作用域  
Son::Base::func(100);

}

int main() {
//test01();
test02();
return 0;
}


**总结:** 静态成员处理方式和非静态成员处理方式一样, 只不过有两种访问方式(通过对象和通过类名)



#### 4.6.7 多继承语法



C++允许**一个类继承多个类**



语法: `class 子类:继承方式 父类1, 继承方式 父类2...`



多继承可能会引发父类中有同名成员出现,需要加作用域区分



**C++实际开发中不建议使用多继承**



**示例:**



#include
using namespace std;

//多继承语法

class Base1{
public:

Base1(){
	m_A = 100;
}

int m_A;

};

class Base2 {
public:

Base2(){
	m_A=200;
}

int m_A;

};

//子类,需要继承Base1和Base2
//语法:class 子类 :继承方式 父类1, 继承方式 父类2…
class Son :public Base1, public Base2 {
public:
Son() {
m_C = 300;
m_D = 400;
}

int m_C;
int m_D;

};
void test01() {
Son s;
cout << “sizeof Son=” << sizeof(s) << endl;
//当多个父类中出现同名成员,需要加作用域区分
cout << "Base1 下 m_A= " << s.Base1::m_A << endl;
cout << "Base2 下 m_A= " << s.Base2::m_A << endl;
}

int main() {
test01();
return 0;
}


**总结:** 当继承中多个父类中出现同名成员,子类使用时需要加作用域区分



#### 4.6.8 菱形继承



**菱形继承概念:**


两个派生类继承同一个基类


又有某个类同时继承着两个派生类


这种继承被称为菱形继承, 或者叫钻石继承



**典型的菱形继承案例:**


![](https://img-blog.csdnimg.cn/direct/1ed9cc7ac59b4d17803a531ee2664064.png)



**菱形继承问题:**


1. 羊继承了动物的数据, 驼同样继承了动物的数据, 当羊驼使用数据时, 就会产生二义性
2. 羊驼继承自动物的数据继承了两份, 但我们只需要一份就可以了



**示例:**



#include
using namespace std;

//动物类
class Animal{
public:
int m_Age;
};

//利用虚继承 可以解决菱形继承的问题
// 继承之前 加上关键字 virtual(虚的) 变为虚继承
// Animal 称为虚基类
//羊类
class Sheep :virtual public Animal{};

//驼类
class Tuo :virtual public Animal{};

//羊驼类
class SheepTuo :public Sheep, public Tuo{};

void test01() {
SheepTuo st;

st.Sheep::m_Age = 18;
st.Tuo::m_Age = 28;
//当出现菱形继承,两个父类拥有相同的数据,  需要加以作用域区分
cout << "st.Sheep::m_Age= " << st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age= " << st.Tuo::m_Age << endl;
cout << "st.m_Age= " << st.m_Age << endl;

//这份数据我们知道 只需要一份就可以,  菱形导致数据有两份,资源浪费

}

int main() {
test01();
return 0;
}


**总结:**


* 菱形继承带来的主要问题时子类继承两份相同的数据, 导致资源浪费以及毫无意义
![](https://img-blog.csdnimg.cn/img_convert/9a8cb5f8c0ec69e6499adead0da6e95b.png)



最全的Linux教程,Linux从入门到精通

======================

1.  **linux从入门到精通(第2版)**

2.  **Linux系统移植**

3.  **Linux驱动开发入门与实战**

4.  **LINUX 系统移植 第2版**

5.  **Linux开源网络全栈详解 从DPDK到OpenFlow**



![华为18级工程师呕心沥血撰写3000页Linux学习笔记教程](https://img-blog.csdnimg.cn/img_convert/59742364bb1338737fe2d315a9e2ec54.png)



第一份《Linux从入门到精通》466页

====================

内容简介

====

本书是获得了很多读者好评的Linux经典畅销书**《Linux从入门到精通》的第2版**。本书第1版出版后曾经多次印刷,并被51CTO读书频道评为“最受读者喜爱的原创IT技术图书奖”。本书第﹖版以最新的Ubuntu 12.04为版本,循序渐进地向读者介绍了Linux 的基础应用、系统管理、网络应用、娱乐和办公、程序开发、服务器配置、系统安全等。本书附带1张光盘,内容为本书配套多媒体教学视频。另外,本书还为读者提供了大量的Linux学习资料和Ubuntu安装镜像文件,供读者免费下载。



![华为18级工程师呕心沥血撰写3000页Linux学习笔记教程](https://img-blog.csdnimg.cn/img_convert/9d4aefb6a92edea27b825e59aa1f2c54.png)



**本书适合广大Linux初中级用户、开源软件爱好者和大专院校的学生阅读,同时也非常适合准备从事Linux平台开发的各类人员。**

> 需要《Linux入门到精通》、《linux系统移植》、《Linux驱动开发入门实战》、《Linux开源网络全栈》电子书籍及教程的工程师朋友们劳烦您转发+评论




**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以点击这里获取!](https://bbs.csdn.net/topics/618542503)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值