含有的主要内容:
类型转换函数
转换构造函数
仿函数-重载()
智能指针 -- 重载 * 和 -> 运算符
一、类型转换函数
类型构造函数用于将一个类的对象转换为其他类型(基本类型或其他类类型)。它是类的成员函数,允许自定义对象在需要目标类型的上下文中自动(或者显式)转换。
#include <iostream>
using namespace std;
class MyInt {
private:
int value;
public:
// 类型转换构造函数:可以用int直接初始化MyInt对象
MyInt(int v) : value(v) {
cout << "类型转换构造函数被调用" << endl;
}
void show() const {
cout << "value = " << value << endl;
}
};
int main() {
MyInt a = 5; // int -> MyInt,调用类型转换构造函数
a.show();
MyInt b(10); // 也会调用类型转换构造函数
b.show();
return 0;
}
MyInt(int v) : value(v) {
cout << "类型转换构造函数被调用" << endl;
}
MyInt(int v) 这是一个构造函数,他接受一个int 类型的参数,因为它只有一个参数,所以被称为类型转换构造函数。 : value(v) - 这是构造函数的初始化列表,它将传入的整数赋值给私有变量 value
<<
{ cout << "类型转换构造函数被调用" << endl; }
- 构造函数的函数体,它输出一条消息表示构造函数被调用了。关键作用:这个构造函数允许我们用整数直接初始化或赋值给 MyInt 对象,如 MyInt a = 5; 或 MyInt b(10);。编译器会自动将整数转换为 MyInt 对象
//将自定义类 Fraction 转换为 double
#include<iostream>
using namespace std;
class Fraction{
public:
Fraction(int numer = 0,int den = 1) : numerator(numer),denominator(den){
if(denominator == 0)
{
cout << "Error: denominator is zero." << endl;
denominator = 1;
}
}
operator double(){
return static_cast<double>(numerator) / denominator;
}
private:
int numerator;
int denominator;
};
int main() {
Fraction f(3, 4);
cout << 2.1 + f << endl;
return 0;
}
显式类型转换函数
#include <iostream>
using namespace std;
class Fraction {
public:
Fraction(int numer = 0, int den = 1) : numerator(numer), denominator(den) {
if (denominator == 0)
{
cout << "Error: denominator is zero." << endl;
denominator = 1;
}
}
explicit operator double() {
return static_cast<double>(numerator) / denominator;
}
private:
int numerator; // 分子
int denominator; // 分母
};
int main() {
Fraction f(3, 4);
cout << 2.1 + static_cast<double>(f) << endl;
return 0;
}
// 这里的 [operator double()]显式类型转换函数.cpp )
// 函数被声明为 explicit(显式),这意味着编译器不允许进行隐式转换。所以你必须使用显式类型转换操作符
// (如 static_cast)来明确表示你要将分数对象转换为 double 类型。
在代码中使用 static_cast<double>(f) 是因为:
这里的 [operator double()
]显式类型转换函数.cpp ) 函数被声明为 explicit
(显式),这意味着编译器不允许进行隐式转换。所以你必须使用显式类型转换操作符(如 static_cast)来明确表示你要将分数对象转换为 double
类型。
转换构造函数
转换构造函数是一种特殊的构造函数,他可以将其他类型的数据转换为当前类的对象,转换构造函数通常为当前类的对象。转换构造函数通常只有一个参数,改参数的类型就是要转换的类型。
转换构造函数是一种特殊的构造函数,他可以将其他类型的数据转换为当前类的对象,他的主要特点:
1.只有一个参数(或者除了第一个参数外,其他参数为默认值)
2.不使用 explict 关键字修饰,(这样才能实现隐式转换)
3. 目的是为了实现其他类型到当前类型的转换
//编写 Distance 类。能够将整数类型转换为 Distance 对象
#include<iostream>
using namespace std;
class Distance{
private:
int meters;
public:
Distance(int m) : meters(m){
std::cout << "转换构造函数被调用" << std::endl;
}
void display(){
std::cout << "Distance" << meters << "meters" << std::endl;
}
};
int main()
{
int value = 100;
Distance d1 = value;
Distance d2(200); //这里就是直接进行初始化
Distance d3 = 300; //临时对象创建并赋值
d1.display();
d2.display();
d3.display();
//在赋值操作中也可以调用转换构造函数
d1 = 400;
d1.display();
return 0;
}
同样,如果在转换构造函数之前加上explicit,就只能显示转换。
#include<iostream>
using namespace std;
class Distance {
private:
int meters;
public:
explicit Distance(int m) : meters(m){
std::cout << "转换构造函数被调用" << std::endl;
}
void display(){
std::cout << "Distamce:" << meters << " meters" << std::endl;
}
};
int main()
{
int value = 100;
Distance d = static_cast<Distance>(value);
d = static_cast<Distance>(200);
d.display();
return 0;
}
类型转换函数和构造函数是双向转换的核心:
仿函数
仿函数,也称为函数对象,是一种重载了函数调用运算符()的类或结构体对象。
()除了改变运算优先级之外,放在函数名后面表示调用函数。
C++仿函数(函数对象)详解
仿函数(Functor)也称为函数对象(Function Object),是C++中一种特殊的设计模式,它允许一个类的对象像函数一样被调用。通过重载类的operator()
运算符,我们可以使类的对象在语法上表现得像函数一样。
仿函数的基本概念
仿函数是通过重载()
操作符(函数调用操作符)实现的,这使得类的对象可以像函数一样使用。与普通函数相比,仿函数有以下优势:
- 可以保持状态:仿函数可以在类中定义成员变量来存储状态信息
- 可以拥有类型:作为一个类,仿函数具有自己的类型
- 可以携带附加信息:通过成员变量或成员函数提供更多功能
- 通常比普通函数效率更高:编译器可以更好地优化内联函数对象
//仿函数,主要是通过重载()操作符实现的,这使得类的对象可以像函数一样调用,主要是通过operator()运算符
#include<iostream>
using namespace std;
//定义一个仿函数类
class AddNumber{
private:
int number;
public:
//构造函数,初始化状态
AddNumber(int n) : number(n) {}
//重载()操作符,使对象可以像函数一样调用
int operator() (int x) const{
return x + number;
}
};
int main()
{
//创建函数对象
AddNumber add5(5);
//使用函数对象,就像调用函数一样
cout << "10 + 5 = " << add5(10) << endl;
//创建另一个函数对象
AddNumber add10(10);
cout << "20 + 10 = " << add10(20) << endl;
//临时函数对象
cout << "7 + 15 = " << AddNumber(15)(7) << endl;
return 0;
}
虽然它是对象,但使用起来却像函数一样,可以像调用函数一样调用仿函数对象。
仿函数可以拥有成员变量,这些成员变量能够保存状态信息,在不同的调用中可以保持和使用这些状态,而普通函数通常无法做到这一点。
//仿函数,主要是通过重载()操作符实现的,这使得类的对象可以像函数一样调用,主要是通过operator()运算符
#include<iostream>
using namespace std;
//定义一个仿函数类
class AddNumber{
private:
int number;
public:
//构造函数,初始化状态
AddNumber(int n) : number(n) {}
//重载()操作符,使对象可以像函数一样调用
int operator() (int x) const{
return x + number;
}
};
int main()
{
//创建函数对象
AddNumber add5(5);
//使用函数对象,就像调用函数一样
cout << "10 + 5 = " << add5(10) << endl;
//创建另一个函数对象
AddNumber add10(10);
cout << "20 + 10 = " << add10(20) << endl;
//临时函数对象
cout << "7 + 15 = " << AddNumber(15)(7) << endl;
return 0;
}
智能指针
在C++中没有垃圾回收机制,堆内存资源的使用和释放需要自己编写程序实现,编写大型的程序可能会忘记释放内存导致内存泄漏。为了解决这个问题,C++标准提出了智能指针这种机制,解决了编程中堆内存泄漏的问题。智能指针的本质是使用引用计数的方式解决悬空指针的问题,通过重载*和->运算符实现。
一个类如果重载了*和->运算符,那么这个类的对象就好像指针那样使用了。而对象的管理是由系统自动管理的,不需要人为的delete,也不会漏掉delete.
#include<iostream>
using namespace std;
class Data{
public:
void dis() {cout << "data" << endl;}
~Data() {cout << "data destructor" << endl;}
};
class SmartDataPtr{
public:
SmartDataPtr(Data* pdata = 0) : pdata(pdata){
if(pdata)
refCount = new int(1);
else
refCount = 0;
}
SmartDataPtr(const SmartDataPtr& p) : pdata(p.pdata),refCount(p.refCount){
if(refCount)
++*refCount;
}
//赋值运算符
SmartDataPtr& operator=(const SmartDataPtr& p){
if(this != &p){
decount(); //先减少源资源的引用计数
pdata = p.pdata;
refCount = p.refCount;
if(refCount)
++*refCount; //增加共享资源的引用计数
}
return*this;
}
void decount() {
if (refCount) {
--*refCount;
if (*refCount == 0) {
delete refCount;
delete pdata;
}
}
}
~SmartDataPtr() { decount(); }
Data& operator*() { return *pdata; }
Data* operator->() { return pdata; }
private:
int* refCount; // 引用计数,这里是用于记录有多少个智能指针指向同一资源
Data* pdata; // 指向实际数据的指针
};
int main() {
{
SmartDataPtr p1(new Data()); // 创建第一个智能指针,引用计数为1
SmartDataPtr p2(p1); // 创建第二个智能指针,引用计数变为2
SmartDataPtr p3; // 创建空智能指针
p3 = p1; // 赋值后,引用计数变为3
p3->dis(); // 通过->访问对象的dis方法
} // 所有智能指针离开作用域,引用计数归零,Data对象被释放
int x;
cin >> x;
return 0;
}
为什么在赋值操作时需要调用decount()
在智能指针的赋值运算符重载中,decount()这一步是非常关键的。我来详细解释为什么需要这一步操作
赋值操作的本质
当我们执行赋值操作如p3 = p1;
时,实际上发生了两个关键步骤:
- 放弃旧资源:
p3
需要停止引用它当前指向的资源 - 获取新资源:
p3
需要开始引用p1
所指向的资源
为什么需要decount()
decount()调用处理的是第一步 - 放弃旧资源。它有两个重要作用:
1. 减少旧资源的引用计数
如果p3之前已经指向了某个资源,那么当p3要指向新的资源时,必须先通知系统它不再使用旧资源。这是通过减少旧资源的引用计数来实现的。