读书笔记-Effective C++中文版-5、6+三种继承

本文详细探讨了C++中编译器自动生成构造函数、析构函数及赋值运算符的机制,并深入分析了公有、保护和私有继承的特性与区别,以及如何在类中正确管理拷贝构造函数和赋值运算符。

读书笔记-Effective C++中文版第三版-构造/析构/赋值运算

5. 了解C++默默编写并调用哪些函数

声明的一个空类,当时C++处理过后,编译器会为她生命一个copy构造函数, 一个copy assignment操作符和一个析构函数,若是没有声明任何构造函数,编译器会声明一个default构造函数。所有的这些都是public和inline(内联函数)。

class Empty {};

处理后:

class Empty
{
public:
	Empty() {}										//default构造函数
	Empty(const EMpty& rhs) {}						//copy构造函数
	~Empty(){}										//析构函数
													//virtual见稍后说明
	Empty& operator= (const Empty& rhs) {}			//copy assignment操作符
};

当这些函数被调用时,他们才会被编译器创建出来。编译器产生的新购函数是个non-virtual,除非这个class的base class自身声明有virtual 析构函数(这种情况下这个函数是虚属性;virtualness;主要来自base class)。

至于copy构造函数和copy assignment操作符,编译器创建的版本只是单纯地将来源对象的每一non-static成员变量拷贝到目标对象。

template <class T>
//template <typename T>
class NamedObject {
public:
	NamedObject(const char* name, const T& value);
	NamedObject(const std::string name, const T& value);

private:
	std::string nameValue;
	T objectValue;
};

声明构造函数后,当函数的参数匹配后,就不会调用default构造函数。

NamedObject没有声明copy构造函数,和copy assignment操作符,当被调用的时候,编译器回味起创建那些函数。

NamedObject<int> no1("small Prime", 2);
NamedObject<int>no2(no1);							//调用copy构造函数

编译器生成的copy构造函数必须以形参为自己函数参数的初值。no2.nameValue初始化的方式时调用string的copy构造函数并以no1.nameValue为实参。另一个成员NameObject::objectValue的类型是int,是内置类型,no2.nameValue会“拷贝no1.object Value内的每一个bits”完成初始化。

编译器为NameObject所生成的copy assignment 操作符,其行为基本上与从copy构造函数如出一辙,但是只有当生出的代码合法且适当机会证明它有意义,若有一个不符合,编译器决绝为class生成operator=。举例如下:

template <class T>
//template <typename T>
class NamedObject {
public:
	//以下构造函数不再接受一个const名称,因为nameValue
	//如今是个reference-to-non-const string,先前那个char*是个构造函数
	//已经过去了,因为有个string可供指涉
	NamedObject(std::string& name, const T& value);
	//如前并未声明operator=
private:
	std::string & nameValue;
	const T objectValue;
};


std::string newDog("Persephone");
std::string oldDog("Satch");
NamedObject<int> p(newDog, 2);

NamedObject<int> s(oldDog, 36);

p = s;

赋值之前,不论p.nameValue 和s.nameValue都指向string对象,但是在赋值之后,reference对象不可以被改动。因为C++不允许“让reference改指向不同对象”。

C++拒绝编译那一行赋值动作。在内含reference成员的class支持赋值操作,必须自己定义一个copy assignment操作符,同样const函数,编译器也是相同反应,更改const成员也是不合法的;base class将copy assignment 操作符声明为private,编译器拒绝为其derived class生成一个copy assignment操作符。编译器为derived class所生成的copy assignment 操作符可以处理base class成分。

remember:

编译器可以暗自为class创建default构造函数,copy构造函数,copy assignment操作符,以及析构函数。

06不像使用编译器自动生成的的函数,就该明确拒绝

如果想要生成的对象拒绝被拷贝。通常不希望class支持某一特定机能,不声明对应的函数即可。但是对于copy函数和copy assignment操作符却不起作用。当其他人调用它们,编译器会自动声明。

所有编译器生成的函数都是public,为了阻止这些函数被创建,可以自行声明它们为private,但是member函数和friend函数还是可以调用private,这样就需要只声明不定义,这样在其他人不慎调用任何一个就会得到一个连接错误。在C++ iostream程序库中阻止copying,就是这样。

class HomeForSale
{
public:
private:
	HomeForSale(const HomeForSale&);
	HomeForSale& operator= (const HomeForSale&);
};

这里没有写参数的名称,因为函数不会被实现,这里面的参数的名称也就不需要被使用,所以不用制定参数名称。

在上述class定义,当客户拷贝HomeForSale对象时,编译器会阻挠。如果在member函数或者friend函数做,编译器会抱怨。

将连接期错误一直编译期时可能的(好事,毕竟玉藻侦测出来错误愈好)。只要将copy构造函数和copy assignment操作符声明为private就可以办到,但不是在HomeforSale自身,而是专门为了阻止copying动作设计的base class内。

class Uncopyable
{
protected:
	Uncopyable() {}									//允许derived对象构造和析构
	~Uncopyable() {}
private:
	Uncopyable(const Uncopyable&);					//阻止copying
	Uncopyable& operator=(const Uncopyable&);
};

class HomeForSale : private Uncopyable
{

};

当member函数或者friend函数尝试拷贝HomeForSale对象,编译器尝试生成一个copy构造函数和一个copy assignment操作符,调用会被编译器拒绝,因为base class的拷贝函数时private。

Uncopyable class的实现和运用颇为精妙,包括不一定是public继承(见条款32,39)以及Uncopyable的析构函数不一定是virtual(见条款7)等。Uncopyable不含数据,因此符合条款39的empty base class optimization资格。但是由于总是扮演base class,可能导致多重继承,而多重继承有时会阻止empty base class optimization(条款39)。通常你可以忽略这些微妙点,只向上面使用Uncopyable,因为其可以向“广告”所说正常使用。也可以使用Boost(条款35)的class名为noncopyable。

标注:在C++11 下有了新的方法:

一旦程序员实现了这些函数的自定义版本,则编译器不会再自动生产默认版本。注意只是不自动生成默认版本,当然还是可手动生成默认版本的。当我们自己定义了待参数的构造函数时,我们最好是声明不带参数的版本以完成无参的变量初始化,此时编译是不会再自动提供默认的无参版本了。我们可以通过使用关键字default来控制默认构造函数的生成,显式地指示编译器生成该函数的默认版本。

class MyClass{  
public:    
    MyClass()=default;  //同时提供默认版本和带参版本,类型是POD的    
    MyClass(int i):data(i){}  
private:    
    int data;
};

有些时候我们希望限制默认函数的生成。典型的是禁止使用拷贝构造函数,以往的做法是将拷贝构造函数声明为private的并不提供实现,这样当拷贝构造对象时编译不能通过,C++11则使用delete关键字显式指示编译器不生成函数的默认版本。比如:

class MyClass{  
public:    
    MyClass()=default;    
    MyClass(const MyClass& )=delete;  
    ......
}

remember:

为了驳回编译器自动提供的机能,可以将相应的成员函数别声明为private并且不予实现。使用像Uncopyable这样的base class也是一种做法。

回顾一下,类的各种继承:

参考博客:Tom文星

博客来源:博客园

网址:https://www.cnblogs.com/duwenxing/p/7476469.html

公有继承

公有继承时C++中最常用的一种继承方式。

#include<iostream>
using namespace std;

class Father
{
public:
	Father() = default;
	void Father_show1()
	{
		cout << "Father class public: Father_show1()" << endl;
	}

protected:
	void Father_show2()
	{
		cout << "Father class protected: Father_show2()" << endl;
	}
	

private:
	void Father_show3()
	{
		cout << "Father class private: Father_show3()" << endl;
	}
};

class Son :public Father
{
public:
	Son() = default;
	void Son_show1()
	{
		cout << "Sun class public: Son_show1()" << endl;
		Father_show1();
		Father_show2();
		//Father_show3();				//错误:无法调用Father类的private方法
	}
	

protected:
	void Son_show2()
	{
		cout << "Sun class protected: Son_show2()" << endl;
		Father_show1();
		Father_show2();
		//Father_show3();				//错误:无法调用Father类的private方法
	}
	



private:
	void Son_show3()
	{
		cout << "Sun class protected: Son_show3()" << endl;
		Father_show1();
		Father_show2();
		//Father_show3();				//错误:无法调用Father类的private方法
	}

	
};

int main()
{
	Son s;
	s.Son_show1();
	s.Father_show1();
	//s.Son_show2();				//错误:不能能调用对象的protected方法
	//s.Father_show2();
	//s.Son_show3();				//错误:不能能调用对象的protected方法
	//s.Father_show2();

	return 0;

}

对公有继承的理解:

1.三种属性能力的强弱:public < protected<private

2.在C++的继承中,子类会继承父类中除构造函数和析构函数之外的所有成员(正所谓儿子无法继承父亲的生死) 。而公有继承(public)就相当于先将从父类那里继承的全部成员放到子类的public部分,如下:

class Son :public Father {
	/* 从Father类中继承的所有成员
	public:
		public:
			void Father_show1(){
				cout<<"调用Father类的public方法:Father_show1"<<endl;
			}
		protected:
			void Father_show2(){
				cout<<"调用Father类的protected方法:Father_show2"<<endl;
			}
		private:
			void Father_show1(){
				cout<<"调用Father类的public方法:Father_show1"<<endl;
			}
	*/
public:
	Son() = default;
	void Son_fun1() {
		cout << "调用Son类的public方法:Son_fun1" << endl;
		Father_show1();
		Father_show2();
		//Father_show3();//错误:无法调用Father类的private方法 
	}
protected:
	void Son_fun2() {
		cout << "调用Son类的protected方法:Son_fun2" << endl;
		Father_show1();
		Father_show2();
		//Father_show3();//错误: 无法调用Father类的private方法 
	}
private:
	void Son_fun3() {
		cout << "调用Son类的private方法:Son_fun3" << endl;
		Father_show1();
		Father_show2();
		//Father_show3();//错误: 无法调用Father类的private方法 
	}
};

然后根据三种属性能力的强弱决定成员的属性在子类中究竟是public、protected还是private:

Father_show1():在Father类中属于public方法,继承到子类Son后放在类的public部分,由于public=public,因此在子类Son中Father_show1()方法仍是public方法

• Father_show2():在Father类中属于protected方法,继承到子类Son后放在类的public部分,由于protected>public,因此子类Son中Father_show2()方法是protected方法

• Father_show3():在Father类中属于private方法,可以理解为“父亲的隐私”,继承到子类Son后放在类的public部分,由于private>public,因此子类Son中Father_show3()方法是private方法。然而正所谓“儿子即使继承了父亲的财产,也无法知晓父亲的隐私”,因此不管儿子以何种方式(public/protected/private)继承父亲的“财产”也无法利用父亲的“隐私”去进行“交易”,换句话说就是父类的private成员虽然可以被子类继承,但子类中的任何成员方法都不能在其函数体中调用这些从父类中继承而来的private成员。因此Son类中的成员方法不管其为与什么部分,都无法调用Father_show3

对象只能调用其public部分的成员而不能调用protected和private部分的成员

因此上例中Son类的对象s可以调用方法Son_fun1()和方法Father_show1(),而无法调用方法Son_fun2()、Son_fun3()、Father_show2()和Father_show3()

保护继承

将上面改为保护继承

class Son :public Father {
	/*
	protected:
		public:
			void Father_show1(){
				cout<<"调用Father类的public方法:Father_show1"<<endl;
			}
		protected:
			void Father_show2(){
				cout<<"调用Father类的protected方法:Father_show2"<<endl;
			}
		private:
			void Father_show1(){
				cout<<"调用Father类的public方法:Father_show1"<<endl;
			}
	*/
public:
	Son() = default;
	void Son_show1() {
		cout << "调用Son类的public方法:Son_fun1" << endl;
		Father_show1();
		Father_show2();
		//Father_show3();//错误:无法调用Father类的private方法 
	}
protected:
	void Son_show2() {
		cout << "调用Son类的protected方法:Son_fun2" << endl;
		Father_show1();
		Father_show2();
		//Father_show3();//错误: 无法调用Father类的private方法 
	}
private:
	void Son_show3() {
		cout << "调用Son类的private方法:Son_fun3" << endl;
		Father_show1();
		Father_show2();
		//Father_show3();//错误: 无法调用Father类的private方法 
	}
};

int main()
{
	Son s;
    s.Son_show1();
    //s.Son_fun2(); //错误:不能调用对象的protected方法
48  //s.Father_show1();
49  //s.Father_show2();
50  //s.Son_fun3(); //错误:不能调用对象的private方法
51  //s.Father_show3();
    return 0;   
}
对保护继承的理解:

1.三种属性能力的强弱:public< protected<private

2.保护继承相当于先将从父类继承的所用成员都放在子类的protected部分:

然后和公有继承一样,根据三种属性能力的强弱决定成员的属性在子类中究竟是public、protected还是private:

• 由于public<protected,yinci fnagfa Father_show1()在类son中式protected方法。

• 由于protected=protected,因此方法Father_show2()在类Son中是protected方法。

• 就像在公有继承中分析的那样,Father_show3()在类Son中虽然是private方法,但Son类中的任何成员方法都不能在其函数体中调用方法Father_show3()。

3.对象只能调用public部分的成员,此时方法Father_show1()是对象的protected方法,因此无法像公有继承那样再被显式调用了

私有继承
#include<iostream>
using namespace std;
class Father{
public:
    Father()=default;
    void Father_show1(){
        cout<<"调用Father类的public方法:Father_show1"<<endl;
    }
protected:
    void Father_show2(){
        cout<<"调用Father类的protected方法:Father_show2"<<endl;
    }
private:
    void Father_show3(){
        cout<<"调用Father类的private方法:Father_show3"<<endl;
    }
}; 

class Son:private Father{
public:
    Son()=default;
    void Son_fun1(){
        cout<<"调用Son类的public方法:Son_fun1"<<endl;
        Father_show1();
        Father_show2();
        //Father_show3(); //错误:无法调用Father类的private方法 
    }
protected:
    void Son_fun2(){
        cout<<"调用Son类的protected方法:Son_fun2"<<endl;
        Father_show1();
        Father_show2();
        //Father_show3(); //错误:无法调用Father类的private方法 
    }
private:
    void Son_fun3(){
        cout<<"调用Son类的private方法:Son_fun3"<<endl;
        Father_show1();
        Father_show2();
        //Father_show3(); //错误:无法调用Father类的private方法 
    }
};

int main(){
    Son s;
    s.Son_fun1(); //正确,只能调用对象的public方法
    //s.Son_fun2(); //错误:不能调用对象的protected方法
    //s.Son_fun3(); //错误:不能调用对象的private方法
    //s.Father_show1();
    //s.Father_show2();
    //s.Father_show3();
    return 0;
}
对私有继承的理解:

1.三种属性能力的强弱:public <protected<private

2.私有继承相当于先将从父类继承的所用成员都放在子类的private部分

然后和公有继承一样,根据三种属性能力的强弱决定成员的属性在子类中究竟是public、protected还是private:

由于public < private,因此方法Father_show1()在类中式private方法,但是Son中的成员方法可以在函数提内调用该方法。

由于private>protected,因此方法Father_show2()在类Son中是private方法,但类Son中的成员方法可以在函数体内调用该方法

就像在公有继承中分析的那样,Father_show3()在类Son中虽然是private方法,但Son类中的任何成员方法都不能在其函数体中调用方法Father_show3()

3.对象只能调用public部分的成员,此时方法Father_show1()是对象的private方法,因此无法像公有继承那样再被显式调用了

保护继承和私有继承有何不同

#include<iostream>
using namespace std;
class GrandFather{ //祖父类 
public:
    GrandFather()=default;
    void GrandFather_show(){
        cout<<"调用GrandFather类的方法:GrandFather_show"<<endl; 
    }
};
class Father:protected GrandFather{ //父类 
public:
    Father()=default;
};
class Son:public Father{ //子类
public:
    Son()=default;
    void Son_show(){
        cout<<"调用Son类的方法:Son_show"<<endl;
        GrandFather_show(); 
    }
}; 

int main(){
    Son s;
    s.Son_show();
    return 0;
}

上面的程序可以顺利运行,当Father类以保护方式(protected)继承GrandFather类时,GrandFather类中有公有方法GrandFather_show()会以protected方法的形式存在Father中,当类Son再以公有方式(public)继承类Father时,方法GrandFather_show()会仍以protected方法的形式存在与类Son中,由于一个类中的成员方法允许在其函数体内调用protected部分的成员,因此系统允许在Son类的成员方法Son_show()调用方法GrandFather_show(),从而使程序顺利运行。

现在我们将程序改为Father类以私有继承的方式继承GrandFather类,发现程序会报错,因为当Father类以私有(private)继承GrandFather类时,GrandFather类中的公有方法GrandFather_show()会以private方法的形式存在于类Father中,换句话说方法GrandFather_show()变成了类Father的“隐私”;当类Son再以公有方式(public)继承类Father时,由于“儿子无法利用父亲的“隐私”进行交易”,因此无法在Son类中的任何成员方法中调用GrandFather_show()方法,包括Son_show()。此时如果我们将类Son中成员函数Son_show()中的语句“GrandFather();"注释掉,程序便可以重新顺利执行。

父类中的访问属性继承方式子类中的访问属性
privatepublic/protected/private不允许访问
publicpublicpublic
publicprotectedprotected
publicprivateprivate
protectedpublicprotected
protectedprotectedprotected
protectedprivateprivate

还可以参考博客C++的三种继承方式

**项目概述:** 本资源提供了一套采用Vue.js与JavaScript技术栈构建的古籍文献文字检测与识别系统的完整源代码及相关项目文档。当前系统版本为`v4.0+`,基于`vue-cli`脚手架工具开发。 **环境配置与运行指引:** 1. **获取项目文件**后,进入项目主目录。 2. 执行依赖安装命令: ```bash npm install ``` 若网络环境导致安装缓慢,可通过指定镜像源加速: ```bash npm install --registry=https://registry.npm.taobao.org ``` 3. 启动本地开发服务器: ```bash npm run dev ``` 启动后,可在浏览器中查看运行效果。 **构建与部署:** - 生成测试环境产物: ```bash npm run build:stage ``` - 生成生产环境优化版本: ```bash npm run build:prod ``` **辅助操作命令:** - 预览构建后效果: ```bash npm run preview ``` - 结合资源分析报告预览: ```bash npm run preview -- --report ``` - 代码质量检查与自动修复: ```bash npm run lint npm run lint -- --fix ``` **适用说明:** 本系统代码经过完整功能验证,运行稳定可靠。适用于计算机科学、人工智能、电子信息工程等相关专业的高校师生、研究人员及开发人员,可用于学术研究、课程实践、毕业设计或项目原型开发。使用者可在现有基础上进行功能扩展或定制修改,以满足特定应用场景需求。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
【EI复现】基于阶梯碳交易的含P2G-CCS耦合和燃气掺氢的虚拟电厂优化调度(Matlab代码实现)内容概要:本文介绍了基于阶梯碳交易机制的虚拟电厂优化调度模型,重点研究了包含P2G-CCS(电转气-碳捕集与封存)耦合技术和燃气掺氢技术的综合能源系统在Matlab平台上的仿真与代码实现。该模型充分考虑碳排放约束与阶梯式碳交易成本,通过优化虚拟电厂内部多种能源设备的协同运行,提升能源利用效率并降低碳排放。文中详细阐述了系统架构、数学建模、目标函数构建(涵盖经济性与环保性)、约束条件处理及求解方法,并依托YALMIP工具包调用求解器进行实例验证,实现了科研级复现。此外,文档附带网盘资源链接,提供完整代码与相关资料支持进一步学习与拓展。; 适合人群:具备一定电力系统、优化理论及Matlab编程基础的研究生、科研人员或从事综合能源系统、低碳调度方向的工程技术人员;熟悉YALMIP和常用优化算法者更佳。; 使用场景及目标:①学习和复现EI级别关于虚拟电厂低碳优化调度的学术论文;②掌握P2G-CCS、燃气掺氢等新型低碳技术在电力系统中的建模与应用;③理解阶梯碳交易机制对调度决策的影响;④实践基于Matlab/YALMIP的混合整数线性规划或非线性规划问题建模与求解流程。; 阅读建议:建议结合提供的网盘资源,先通读文档理解整体思路,再逐步调试代码,重点关注模型构建与代码实现之间的映射关系;可尝试修改参数、结构或引入新的约束条件以深化理解并拓展应用场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值