当前位置: 首页 > news >正文

C++11QT复习 (四)

Day6-1 输入输出流运算符重载(2025.03.25)

1. 拷贝构造函数的调用时机
2. 友元
   2.1 友元函数
3. 输入输出流运算符重载
   3.1 关键知识点
   3.2 代码
   3.3 关键问题
   3.4 完整代码
4. 下标访问运算符 `operator[]`
   4.1 关键知识点
   4.2 代码
5. 函数调用运算符 `operator()`
   5.1 关键知识点
   5.2 代码
   5.3 示例
   5.4 完整代码
6. 总结

1. 回顾

1.1 拷贝构造函数的调用时机

拷贝构造函数在以下情况会被调用:

  1. 对象初始化

    • 当用一个已经存在的对象去初始化一个刚刚创建的对象时,调用拷贝构造函数。
    • 例:
      Complex c1(1, 2);
      Complex c2 = c1; // 调用拷贝构造函数
      
  2. 函数参数传递

    • 当形参与实参都是对象时,在函数调用时会调用拷贝构造函数。
    • 例:
      void func(Complex c) { }
      func(c1); // 形参与实参结合时调用拷贝构造函数
      
  3. 函数返回对象

    • 当函数返回一个对象时,可能调用拷贝构造函数(但现代 C++ 编译器会尝试优化此过程,如返回值优化 RVO)。
    • 例:
      Complex func() {
          Complex c(3, 4);
          return c; // 可能调用拷贝构造函数
      }
      

2. 友元

2.1 友元函数
  • 友元函数可以访问类的私有成员。
  • 友元声明可以出现在类的 publicprotectedprivate 部分,不影响其权限。
  • 友元关系是单向的、不可传递的
    • 单向:如果 AB 的友元,B 并不会自动成为 A 的友元。
    • 不可传递:如果 AB 的友元,BC 的友元,A 并不会自动成为 C 的友元。

3. 输入输出流运算符重载

3.1 关键知识点
  1. operator<< 必须是友元函数

    • 由于 cout << c1; 左操作数是 std::ostream,不能修改 std::ostream,所以 operator<< 不能是 Complex 的成员函数。
  2. operator>> 不能是 const 成员函数

    • 因为 operator>> 需要修改对象的值,因此不能加 const
3.2 代码
std::ostream& operator<<(std::ostream& os, const Complex& rhs) {
    if (rhs._imag > 0) {
        os << rhs._real << " + " << rhs._imag << "i";
    } else if (rhs._imag == 0) {
        os << rhs._real;
    } else {
        os << rhs._real << " - " << -rhs._imag << "i";
    }
    return os;
}

std::istream& operator>>(std::istream& is, Complex& rhs) {
    std::cout << "请输入复数的实部和虚部:" << std::endl;
    is >> rhs._real >> rhs._imag;
    return is;
}
3.3 关键问题
  • 为什么 operator<<operator>> 的返回值是 std::ostream&std::istream&

    • 这样可以实现连续输入输出
      cout << c1 << c2 << endl;  // 连续输出
      cin >> c1 >> c2;           // 连续输入
      
  • 为什么 operator<<ostream& 参数不能去掉 &

    • 因为 ostream 的拷贝构造函数已被 delete,不能复制 ostream 对象。
3.4 完整代码
#include <iostream>
#include <limits.h>
#include <ostream>

using namespace std;

//复数
class Complex
{
	friend Complex operator*(const Complex& lhs, const Complex& rhs);
	//Complex operator/(const Complex& rhs) const;
public:
	Complex(double r = 0, double i = 0)
		: _real(r), _imag(i)
	{
		cout << "Complex(double r = 0, double i = 0)" << endl;
	}

	~Complex()
	{
		cout << "~Complex()" << endl;
	}

	

	void display() const
	{

		if (_imag > 0)
		{
			cout << _real << " + " << _imag << "i" << endl;
		}
		else if (_imag == 0)
		{
			cout << _real << endl;
		}
		else
		{
			cout << _real << " - " << -_imag << "i" << endl;
		}
	}

	//成员函数,operator输出运算符重载 
	//cout << c1 << endl; 
	//第一个参数cout ,第二个参数c1
	//std::ostream& operator<<(std::ostream& os, const Complex& rhs); //error!隐含this指针

	//对于输出流运算符函数而言,不能写成成员函数的形式,因为违背了运算符重载的原则,不能改变操作数的顺序
	//std::ostream& operator<<(std::ostream& os);//error!this指针在参数列表的第一个位置

	

		/*友元函数可以放在类的 任何位置(public / protected / private),不影响其功能。
		从代码规范角度,建议统一放在类定义的开头或结尾,以提高可读性。
		友元关系是单向的,且不具备传递性(即类 A 的友元函数不会自动成为类 B 的友元)。
		*/
	friend std::ostream& operator<<(std::ostream& os, const Complex& rhs);
	//输入流运算符重载
	friend std::istream& operator>>(std::istream& is, Complex& rhs);

private:
	double _real;
	double _imag;
};
//问题1 :参数列表中ostrream的引用符号&能不能去掉?
//解答1 :不能去掉因为形参"os"和实参"cout"结合的时候会满足拷贝构造函数的调用时机,但是ostream中的拷贝构造函数已被delete

//问题2 : 函数返回类型中的引用符号&能不能删除?
//解答2 : 不能取掉,因为return os,返回类型满足拷贝构造函数的调用时机3

//注释:basic_ifstream( const basic_ifstream& rhs ) = delete; (7)	(since C++11)
//	   basic_ofstream( const basic_ofstream& rhs ) = delete; (7)	(since C++11)
//ifstram 和 ofstream 的拷贝构造函数已经从C++11开始删除了
std::ostream& operator<<(std::ostream& os, const Complex& rhs)
{
	cout << "std::ostream& operator<<(std::ostream& os, const Complex& rhs)" << endl;

	if (rhs._imag > 0)
	{
		os << rhs._real << " + " << rhs._imag << "i" << endl;
	}
	else if (rhs._imag == 0)
	{
		os << rhs._real << endl;
	}
	else
	{
		os << rhs._real << " - " << -rhs._imag << "i" << endl;
	}

	return os;
}

void readDouble(std::istream& is, double& rhs)
{
	while (is >> rhs, !is.eof())
	{
		if (is.bad())
		{
			std::cerr << "istream is bad" << endl;
			return;
		}
		else if (is.fail())
		{
			is.clear();//重置流的状态
			is.ignore(std::numeric_limits<::std::streamsize>::max(), '\n');//清空缓冲区
			cout << "请注意:需要输入double类型的数据!";
		}
		else
		{
			cout << "rhs = " << rhs << endl;
			break;
		}
	}
}

//输入流运算符重载
std::istream& operator>>(std::istream& is, Complex& rhs)//因为要修改rhs 的值,所以不能加const
{
	cout << "std::istream& operator>>(std::istream& is, Complex& rhs)" << endl;
	cout << "请分别输入复数的实部和虚部:" << endl;
	//is >> rhs._real >> rhs._imag;
	readDouble(is, rhs._real);
	readDouble(is, rhs._imag);
	return is;
}


void testOutputOperator()
{
	Complex c1(1, 2);
	cout << "c1 = ";
	c1.display();
	cout << endl << endl;


	//cout << "c1 = " << c1.display();
	//为什么没有做输出流运算符重载之前上面的写法 不可行呢? 解答:二元“<<”: 没有找到接受“void”类型的右操作数的运算符(或没有可接受的转换)

	cout << "c1 = " << c1 << endl;

	cout << endl;
	Complex c2;
	cin >> c2;
	cout << "c2 = " << c2 << endl;
}

int main(int argc, char* argv[])
{
	testOutputOperator();
	return 0;
}

4. 下标访问运算符 operator[]

4.1 关键知识点
  • operator[] 主要用于自定义数组类型,使得 obj[idx] 访问数组元素。
  • 返回值应为 T&,以保证能够修改数组内容。
  • 必须进行越界检查,以防止访问非法内存。
4.2 代码
char& CharArrar::operator[](size_t idx) {
    if (idx < _size) {
        return _data[idx];
    } else {
        static char charNull = '\0';
        return charNull;
    }
}

5. 函数调用运算符 operator()

5.1 关键知识点
  • 使对象像函数一样调用,称为仿函数(函数对象)
  • 可存储状态,例如调用次数。
5.2 代码
class FunctionObject {
public:
    int operator()(int x, int y) {
        ++_cnt;
        return x + y;
    }

    int operator()(int x, int y, int z) {
        ++_cnt;
        return x * y * z;
    }

private:
    int _cnt = 0;
};
5.3 示例
FunctionObject fo;
cout << "fo(3, 4) = " << fo(3, 4) << endl;
cout << "fo(3, 4, 5) = " << fo(3, 4, 5) << endl;
5.4 完整代码
//bracket.h
#pragma once
#include <iostream>
#include <string.h>

using namespace std;
class CharArrar
{
public:
	CharArrar(size_t sz = 10)
		:_size(sz)
		, _data(new char[_size])
	{
		cout << " CharArrar(size_t sz = 10)" << endl;
	}

	~CharArrar()
	{
		cout << "~CharArrar()" << endl;
		if (_data)
		{
			delete _data;
			_data = nullptr;
		}
	}

	size_t size() const
	{
		return _size;
	}

	//下标访问运算符的重载
	//int arr[10] = { 1,2,3,4,5 };
	//arr.operator[](idx);
	char& operator[](size_t idx);
private:
	size_t _size;
	char* _data;
};


//bracket.cpp
#include "bracket.h"

/*
要修复错误 C2106: “=”: 左操作数必须为左值,需要确保 CharArrar 类的
下标运算符 operator[] 返回一个可修改的左值。当前的 operator[] 声明
返回的是一个 char,这不是一个可修改的左值。我们需要将其修改为返回一个 char&。
*/
char& CharArrar::operator[](size_t idx)
{
	if (idx < _size)
	{
		return _data[idx];
	}
	else
	{
		static char charNUll = '\0';//静态变量延长生命周期
		return charNUll;   //使得返回的实体生命周期比函数的生命周期长
	}
}

//C++中优势:重载的下标访问运算符考虑了越界的问题
//引用什么时候需要加上?
//1.如果返回类型是类型的时候,可以减少拷贝构造函数的执行
//2.有可能需要一个左值(来调用右操作数),而不是拷贝后的右值、
//3.cout << "111" << c1 << endl << 10 << 1 << endl,像这种情况下连续使用的时候可以加上引用 

//parenthese.h
#pragma once
#include <iostream>

using namespace std;

class FunctionObject
{
public:
	int operator()(int x, int y);
	

	int operator()(int x, int y, int z);
	
private:
	int _cnt;//记录被调用的次数(函数对象状态)
};


parenthese.cpp
#include <iostream>
#include "parenthese.h"

using namespace std;



int FunctionObject::operator()(int x, int y)
{
	++_cnt;
	cout << "int operator()(int x, int y)" << endl;
	return x + y;
}

int FunctionObject::operator()(int x, int y, int z)
{
	cout << "int operator()(int x, int y,int z)" << endl;
	++_cnt;
	return x * y * z;
}


//main.cpp
#include <iostream>
#include "bracket.h"
#include "parenthese.h"


using namespace std;

int add(int x, int y)
{
	cout << "int add(int x,int y)" << endl;
	static int cnt = 0;
	++cnt;
	return x + y;
}


void testFunctionObject()
{
	FunctionObject fo;
	int a = 3;
	int b = 4;
	int c = 5;
	//fo本质上是一个对象,但是他的使用
	cout << "fo(a,b) = " << fo(a, b) << endl;
	cout << "fo(a, b ,c) = " << fo(a, b, c) << endl;

	cout << endl;
	//正经的函数
	cout << "add(a, b) = " << add(a, b) << endl;
}

void testCharArrar()
{
	
	//把字符串中的内容拷贝到CharArray
	const  char* pstr = "hello cpp";
	CharArrar ca(strlen(pstr) + 1);

	for(size_t idx = 0; idx != ca.size(); ++idx)
	{
		//ca[idx] = pstr[idx];
		//上面和下面两条代码等价
		ca.operator[](idx) = pstr[idx];
	}

	for (size_t idx = 0; idx != ca.size(); ++idx)
	{
		cout << ca[idx] << "  ";
	}
	cout << endl;
}

int main(int argc, char** argv)
{
	testFunctionObject();

	testCharArrar();
	return  0;
}

相关文章:

  • Linux上位机开发实践(积极使用SoC厂家的SDK)
  • Linux系统perf命令使用介绍,如何用此命令进行程序热点诊断和性能优化
  • 信息安全的数学本质与工程实践
  • 【算法工程】RAG:针对linux下文档解析出现乱码问题的解决
  • package.json版本前缀
  • 【Python 代码进阶-2】Python 中的 **(...)语法,字典解包操作符
  • 虫洞数观系列一 | 豆瓣电影TOP250数据采集与MySQL存储实战
  • 阿里巴巴1688类网站高保真原型设计
  • 国产化适配 - YashanDB、达梦数据库与MySQL 的兼容性及技术选型对比分析
  • 重学vue3(三):vue3基本语法及使用
  • AI驱动下的智能异常处置:海量多元异构数据的挑战与应对
  • 二分查找(二分答案)套路模板
  • JavaScript 改变 HTML 样式
  • 给Web开发者的HarmonyOS指南01-文本样式
  • Spring AI Alibaba ImageModel使用
  • vue的项目添加全局接口请求封装,并通过配置文件使接口请求变得更简洁易用
  • 13.2 kubelet containerRuntime接口定义和初始化
  • Java操作RabbitMQ
  • BCC-应用程序组件分析
  • 【身份安全】零信任安全框架梳理(一)
  • 王毅会见俄罗斯外长拉夫罗夫
  • 物业也能成为居家养老“服务员”,上海多区将开展“物业+养老”试点
  • 日韩 “打头阵”与美国贸易谈判,汽车、半导体产业忧虑重重
  • 人民日报:应对外贸行业风险挑战,稳企业就是稳就业
  • 原创话剧风向标!这个展演上《大宅门》《白鹿原》先后上演
  • 巴黎奥运后红土首秀落败,郑钦文止步马德里站次轮